perf(ext/web): optimize node:buffer base64 encode/decode (#32647)
## Summary
Optimizes `node:buffer` base64 encode/decode/write to close the
performance gap with Node.js.
### Decode & Write
- Add dedicated `op_base64_decode_into` fast-call op that decodes base64
directly into a target buffer, used by `Buffer.prototype.write(str,
'base64')` — avoids allocating intermediate Uint8Array
- Two-tier decode strategy: `base64_simd::STANDARD.decode` fast path for
properly-padded base64 (zero intermediate copies), with fallback to
`forgiving_decode` for whitespace/missing padding
- Switch `op_base64_decode` to use `forgiving_decode_to_vec` for cleaner
single-pass decoding
### Encode
- Add `op_base64_encode_from_buffer` op that encodes a sub-range of a
buffer to base64 using `v8::String::new_from_one_byte` directly (base64
is always ASCII, avoids UTF-8 processing overhead)
- Stack allocation for inputs up to 6KB (≤8KB base64 output) to avoid
heap allocation
- Hybrid approach: small buffers (≤4KB) use lightweight `#[string]`
return path, large buffers use `new_from_one_byte` for better throughput
### JS dispatch
- Add base64 fast paths in `Buffer.prototype.toString` and
`Buffer.prototype.write` to skip `getEncodingOps` dispatch overhead
(avoids `toLowerCase()` on every call)
### Benchmark results (50K iterations, vs Node.js)
| Size | Encode | Decode | Write |
|------|--------|--------|-------|
| 32B | **1.38x** | 0.78x | **1.77x** |
| 128B | **1.15x** | **1.53x** | **2.67x** |
| 512B | 0.85x | **1.38x** | **1.67x** |
| 4KB | 0.63x | **1.02x** | 0.94x |
| 64KB | 0.74x | 0.96x | 0.85x |
| 1MB | **1.11x** | 0.88x | 0.85x |
Faster than Node on 7/18 benchmarks, within 0.85-0.96x on most of the
rest.
Towards https://github.com/denoland/deno/issues/24323
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>