mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
Pool small Buffers as Node does (#1101)
This commit is contained in:
@@ -1,3 +1 @@
|
||||
`Buffer.from` accepts untyped strings, byte arrays, arrays, and plain array-like or Buffer JSON objects. Encoding arguments must be supported literals. Custom input `valueOf` hooks and opaque class or native-handle inputs throw an unsupported-operation error.
|
||||
|
||||
Every Buffer owns an ArrayBuffer of its own length. Node allocates small Buffers from `Buffer.from`, `Buffer.allocUnsafe`, and `Buffer.concat` out of a shared 64 KiB pool, so for those Buffers `buf.buffer.byteLength` is the pool size in Node and the Buffer's length here, and `buf.byteOffset` is `0` here. Reading `buf.buffer` through `buf.byteOffset` and `buf.length` returns the same bytes in both. `Buffer.poolSize` is unsupported.
|
||||
|
||||
@@ -1 +1 @@
|
||||
- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage. Resizing, transferring, Float16 arrays, and BigInt arrays are unsupported. Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays. Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property. JavaScript numeric-array parameters and returns preserve typed-array storage.
|
||||
- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage. Resizing, transferring, Float16 arrays, and BigInt arrays are unsupported. Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays. Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property. JavaScript numeric-array parameters and returns preserve typed-array storage. `Buffer.from`, `Buffer.allocUnsafe`, and `Buffer.concat` return views into a per-thread pool of `Buffer.poolSize` bytes (64 KiB by default) when the size is below `Buffer.poolSize >>> 1`, as in Node; a pool slab stays in memory while any of its Buffers does. Node's own startup code allocates from the first slab, so the `byteOffset` of Buffers in that slab differs from Node's. Buffers returned by other Node APIs, such as `fs.readFileSync` and `zlib`, own their `ArrayBuffer`, where Node allocates small ones from the pool.
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"artifactVersion": "1c65fe186ed24618c053",
|
||||
"artifactVersion": "aaa7e74233dc5f826dcd",
|
||||
"rowCount": 3880
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@
|
||||
"apiEntries": 99,
|
||||
"static": {
|
||||
"supported": 16,
|
||||
"partial": 6,
|
||||
"partial": 7,
|
||||
"refused": 0,
|
||||
"not-implemented": 77,
|
||||
"not-implemented": 76,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -4584,9 +4584,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"tierItemCount": 3472,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"audit-partial": 1034,
|
||||
"verify-gap": 2311,
|
||||
"audit-partial": 1035,
|
||||
"verify-gap": 2310,
|
||||
"replace-refusal": 40,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
@@ -3733,12 +3733,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:buffer",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:buffer.poolSize",
|
||||
"tests": [
|
||||
"tests/corpus/5530-buffer-pool.ts",
|
||||
"tests/corpus/5531-buffer-pool-first-slab.ts",
|
||||
"tests/corpus/5532-buffer-pool-first-slab-untyped.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,9 +71,9 @@
|
||||
"apiEntries": 99,
|
||||
"static": {
|
||||
"supported": 16,
|
||||
"partial": 6,
|
||||
"partial": 7,
|
||||
"refused": 0,
|
||||
"not-implemented": 77,
|
||||
"not-implemented": 76,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -3144,6 +3144,9 @@
|
||||
"tests/corpus/5288-blob-validation.js",
|
||||
"tests/corpus/5289-blob-read-order.ts",
|
||||
"tests/corpus/5290-blob-dynamic.js",
|
||||
"tests/corpus/5530-buffer-pool.ts",
|
||||
"tests/corpus/5531-buffer-pool-first-slab.ts",
|
||||
"tests/corpus/5532-buffer-pool-first-slab-untyped.mjs",
|
||||
"tests/corpus/node24-buffer-encoding-errors.ts",
|
||||
"tests/corpus/node24-buffer-encoding.ts",
|
||||
"tests/fixtures/node-types/blob-file.ts",
|
||||
@@ -5432,8 +5435,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:buffer"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:buffer.poolSize",
|
||||
"tests": [
|
||||
"tests/corpus/5530-buffer-pool.ts",
|
||||
"tests/corpus/5531-buffer-pool-first-slab.ts",
|
||||
"tests/corpus/5532-buffer-pool-first-slab-untyped.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
|
||||
@@ -274,6 +274,7 @@
|
||||
{ "chapter": "http", "parent": "Class: `http.ServerResponse`", "signatures": ["`req` Type: {http.IncomingMessage}", "`sendDate` Type: {boolean}", "`strictContentLength` Type: {boolean} **Default:** `false`", "`statusMessage` Type: {string}", "`socket` Type: {stream.Duplex}", "`connection` Type: {stream.Duplex}", "`writableFinished` Type: {boolean}"], "status": "partial", "evidence": ["tests/corpus/2957-http-static-response-controls.ts"] },
|
||||
{ "chapter": "http", "signatures": ["`request.cork()`", "`request.flushHeaders()`", "`request.uncork()`", "`response.addTrailers(headers)`", "`response.cork()`", "`response.flushHeaders()`", "`response.uncork()`", "`headersDistinct` Type: {Object}", "`rawTrailers` Type: {string\\[]}", "`trailers` Type: {Object}", "`trailersDistinct` Type: {Object}", "`outgoingMessage.addTrailers(headers)`", "`outgoingMessage.cork()`", "`outgoingMessage.flushHeaders()`", "`outgoingMessage.uncork()`", "`writableCorked` Type: {number}"], "status": "partial", "evidence": ["tests/corpus/2700-http-static-trailers.ts", "tests/corpus/2701-http-static-trailer-wire.ts", "tests/corpus/2702-http-static-trailer-end.ts", "tests/corpus/2972-http-static-distinct-snapshots.ts", "tests/fixtures/server/cases/http-trailers/main.ts"] },
|
||||
{ "chapter": "http", "parent": "Class: `http.IncomingMessage`", "signatures": ["`rawHeaders` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/2972-http-static-distinct-snapshots.ts"] },
|
||||
{ "chapter": "buffer", "signatures": ["`poolSize` Type: {integer} **Default:** `65536`"], "status": "partial", "evidence": ["tests/corpus/5530-buffer-pool.ts", "tests/corpus/5531-buffer-pool-first-slab.ts", "tests/corpus/5532-buffer-pool-first-slab-untyped.mjs"] },
|
||||
{ "chapter": "buffer", "signatures": ["`buf.readBigInt64BE([offset])`", "`buf.readBigInt64LE([offset])`", "`buf.readBigUInt64BE([offset])`", "`buf.readBigUInt64LE([offset])`", "`buf.writeBigInt64BE(value[, offset])`", "`buf.writeBigInt64LE(value[, offset])`", "`buf.writeBigUInt64BE(value[, offset])`", "`buf.writeBigUInt64LE(value[, offset])`"], "status": "supported", "evidence": ["tests/corpus/2902-bigint-bytes.ts", "tests/corpus/2904-bigint-errors.ts"] },
|
||||
{ "chapter": "environment_variables", "symbols": ["export"], "status": "not-applicable" },
|
||||
{ "chapter": "globals", "symbols": ["fetch"], "status": "partial", "evidence": ["tests/harness/fetch-conformance.test.ts"] },
|
||||
|
||||
+5
-2
@@ -635,9 +635,12 @@ interface BufferConstructor {
|
||||
from(data: Uint8Array | readonly number[]): Buffer;
|
||||
alloc(size: number, fill?: string | number | Uint8Array, encoding?: BufferEncoding): Buffer;
|
||||
compare(buf1: Uint8Array, buf2: Uint8Array): number;
|
||||
/* alloc without the zero-fill guarantee — the lowering zero-fills
|
||||
* anyway (never handing out uninitialized memory costs one memset). */
|
||||
/* alloc without the zero-fill guarantee. Below Buffer.poolSize >>> 1
|
||||
* bytes it returns a view into the shared pool slab. */
|
||||
allocUnsafe(size: number): Buffer;
|
||||
allocUnsafeSlow(size: number): Buffer;
|
||||
/* The pool slab size for allocUnsafe, from, and concat. */
|
||||
poolSize: number;
|
||||
concat(list: readonly Uint8Array[], totalLength?: number): Buffer;
|
||||
isBuffer(obj: unknown): obj is Buffer;
|
||||
byteLength(input: string | Uint8Array, encoding?: BufferEncoding): number;
|
||||
|
||||
@@ -2120,7 +2120,7 @@ export class LlEmitter {
|
||||
// typed-array access GEPs through this directly: the IR type already
|
||||
// fixes elem, so the hot path needs neither a runtime kind load nor
|
||||
// the generic scr_bytes_get/set call.
|
||||
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
|
||||
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
|
||||
// The capture box { rc, kind, obj_retain, obj_release, obj_trace,
|
||||
// slot } — TDZ reads peek the payload slot (offset 40) directly.
|
||||
`%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
||||
|
||||
@@ -183,7 +183,7 @@ function emitSharedBytesSplit(host: LlvmEmitterContext, receiver: string): strin
|
||||
storage = B.tmp(),
|
||||
isShared = B.tmp(),
|
||||
expected = B.tmp();
|
||||
B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 8`);
|
||||
B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 9`);
|
||||
B.line(`${storage} = load ptr, ptr ${slot}`);
|
||||
B.line(`${isShared} = icmp ne ptr ${storage}, null`);
|
||||
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${isShared}, i1 false)`);
|
||||
|
||||
@@ -399,8 +399,18 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"buffer.fromStrChecked": "scr_bytes_from_str_checked",
|
||||
"buffer.brand": "scr_bytes_as_buffer",
|
||||
"buffer.fromDyn": "scr_buffer_from_dyn",
|
||||
"buffer.concat": "scr_bytes_concat",
|
||||
"buffer.concatLen": "scr_bytes_concat_len",
|
||||
"buffer.concat": "scr_buffer_concat",
|
||||
"buffer.concatLen": "scr_buffer_concat_len",
|
||||
"buffer.alloc": "scr_buffer_alloc",
|
||||
"buffer.allocUnsafe": "scr_buffer_alloc_unsafe",
|
||||
"buffer.allocUnsafeSlow": "scr_buffer_alloc_unsafe_slow",
|
||||
"buffer.poolFromStr": "scr_buffer_from_str",
|
||||
"buffer.poolFromStrChecked": "scr_buffer_from_str_checked",
|
||||
"buffer.poolFromBytes": "scr_buffer_from_bytes",
|
||||
"buffer.poolFromArray": "scr_buffer_from_arr",
|
||||
"buffer.poolFromDyn": "scr_buffer_from_dyn_pooled",
|
||||
"buffer.poolSize": "scr_buffer_pool_size",
|
||||
"buffer.setPoolSize": "scr_buffer_set_pool_size",
|
||||
"buffer.byteLenDyn": "scr_buffer_byte_length_dyn",
|
||||
"buffer.byteLenStr": "scr_bytes_byte_length_str",
|
||||
"buffer.isEncoding": "scr_bytes_is_encoding",
|
||||
@@ -1286,6 +1296,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.structuredClone": "scr_structured_clone",
|
||||
"dyn.cloneMissing": "scr_structured_clone_missing",
|
||||
"dyn.cloneTransferFail": "scr_structured_clone_transfer_fail",
|
||||
"dyn.cloneTransferFailWith": "scr_structured_clone_transfer_fail_with",
|
||||
"regex.new": "scr_regex_new",
|
||||
"regex.resetLastIndex": "scr_regex_reset_last_index",
|
||||
"regex.newChecked": "scr_regex_new_checked",
|
||||
|
||||
@@ -1357,7 +1357,7 @@ function lowerBufferStaticValue(
|
||||
if (args.length === 1) {
|
||||
const elems = argNode.elements.map((el) => lowerer.lowerExprExpecting(el, F64));
|
||||
const seed: IrExpr = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
||||
return { kind: "bytesNew", source: seed, type: BYTES_U8, loc };
|
||||
return { kind: "libCall", fn: "buffer.poolFromArray", args: [seed], type: BYTES_U8, loc };
|
||||
}
|
||||
} else {
|
||||
const srcIr = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
||||
@@ -1367,13 +1367,19 @@ function lowerBufferStaticValue(
|
||||
if (value.type.kind === "dyn") {
|
||||
const encName = args[1] ? bufEncoding(lowerer, "Buffer.from", args[1]) : "utf8";
|
||||
const enc: IrExpr = { kind: "strLit", value: encName, type: STRING, loc };
|
||||
return { kind: "libCall", fn: "buffer.fromDyn", args: [value, enc], type: BYTES_U8, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromDyn",
|
||||
args: [value, enc],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (srcIr?.kind === "string") {
|
||||
const s = lowerer.coerceInto(argNode, value, STRING);
|
||||
const encNode = args[1];
|
||||
let enc: IrExpr = { kind: "strLit", value: "utf8", type: STRING, loc };
|
||||
let fn: IrLibFn = "buffer.fromStr";
|
||||
let fn: IrLibFn = "buffer.poolFromStr";
|
||||
if (encNode) {
|
||||
const encType = lowerer.typeOf(encNode);
|
||||
const encName = encType.isStringLiteralType()
|
||||
@@ -1392,17 +1398,29 @@ function lowerBufferStaticValue(
|
||||
enc = defaultAfterUndefined(undefinedArg, enc);
|
||||
} else {
|
||||
enc = lowerOptionalArgument(lowerer, encNode, STRING, enc);
|
||||
fn = "buffer.fromStrChecked";
|
||||
fn = "buffer.poolFromStrChecked";
|
||||
}
|
||||
}
|
||||
}
|
||||
return { kind: "libCall", fn, args: [s, enc], type: BYTES_U8, loc };
|
||||
}
|
||||
if (args.length === 1 && srcIr?.kind === "bytes" && srcIr.elem === "u8") {
|
||||
return { kind: "bytesNew", source: value, type: BYTES_U8, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromBytes",
|
||||
args: [value],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (args.length === 1 && srcIr?.kind === "array" && srcIr.elem.kind === "f64") {
|
||||
return { kind: "bytesNew", source: value, type: BYTES_U8, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "buffer.poolFromArray",
|
||||
args: [value],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1414,17 +1432,21 @@ function lowerBufferStaticValue(
|
||||
"the buffer's storage; narrow unions first",
|
||||
);
|
||||
}
|
||||
if (member === "alloc" || member === "allocUnsafe") {
|
||||
if (member === "alloc" || member === "allocUnsafe" || member === "allocUnsafeSlow") {
|
||||
const maxArgs = member === "alloc" ? 3 : 1;
|
||||
if (args.length < 1 || args.length > maxArgs || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`Buffer.${member} with ${args.length} arguments`, call);
|
||||
}
|
||||
// allocUnsafe's contents are UNSPECIFIED in Node (uninitialized pool
|
||||
// memory); a zero-filled buffer is a valid instance of unspecified,
|
||||
// and the deterministic choice — a program observing the difference
|
||||
// is depending on garbage.
|
||||
// allocUnsafe's contents are unspecified in Node (uninitialized pool
|
||||
// memory). Pool slabs and unpooled buffers start zero-filled.
|
||||
const size = lowerer.lowerExprExpecting(args[0]!, F64);
|
||||
const fresh: IrExpr = { kind: "bytesNew", source: size, type: BYTES_U8, loc };
|
||||
const fn: IrLibFn =
|
||||
member === "alloc"
|
||||
? "buffer.alloc"
|
||||
: member === "allocUnsafe"
|
||||
? "buffer.allocUnsafe"
|
||||
: "buffer.allocUnsafeSlow";
|
||||
const fresh: IrExpr = { kind: "libCall", fn, args: [size], type: BYTES_U8, loc };
|
||||
if (args.length === 1) return fresh;
|
||||
// alloc(size, fill, encoding?): the fill semantics ARE fill()'s —
|
||||
// the fresh buffer is the receiver of a whole-range fill.
|
||||
|
||||
@@ -548,6 +548,30 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
ts.isArrayLiteralExpression(tr.initializer) &&
|
||||
tr.initializer.elements.length > 0
|
||||
) {
|
||||
// An ArrayBuffer first member may be a Buffer pool slab, which
|
||||
// Node reports as untransferable.
|
||||
const first = tr.initializer.elements[0]!;
|
||||
const firstType = ts.isSpreadElement(first) ? undefined : lowerer.typeOf(first);
|
||||
const parts = !firstType
|
||||
? []
|
||||
: firstType.isUnionType()
|
||||
? ts.constituentTypes(firstType)
|
||||
: [firstType];
|
||||
const isStorage =
|
||||
parts.length > 0 &&
|
||||
parts.every((part) => {
|
||||
const name = part.getSymbol()?.name;
|
||||
return name === "ArrayBuffer" || name === "SharedArrayBuffer";
|
||||
});
|
||||
if (isStorage) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dyn.cloneTransferFailWith",
|
||||
args: [toDynArg(first)],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
return { kind: "libCall", fn: "dyn.cloneTransferFail", args: [], type: DYN, loc };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4903,6 +4903,8 @@ export function lowerIntrinsicProperty(
|
||||
const numberMember = lowerer.stdlibGlobalMember(expr, "Number");
|
||||
if (numberMember === "parseInt" || numberMember === "parseFloat")
|
||||
return lowerNumberParserValue(lowerer, numberMember, locOf(expr));
|
||||
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "poolSize")
|
||||
return { kind: "libCall", fn: "buffer.poolSize", args: [], type: F64, loc: locOf(expr) };
|
||||
if (lowerer.stdlibGlobalMember(expr, "Buffer") === "isBuffer")
|
||||
return lowerCheckedPredicateValue(lowerer, "Buffer.isBuffer", "buffer", locOf(expr));
|
||||
if (lowerer.stdlibGlobalMember(expr, "Date") === "now")
|
||||
|
||||
@@ -6354,6 +6354,18 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
!expr.left.questionDotToken &&
|
||||
lowerer.stdlibGlobalMember(expr.left, "Buffer") === "poolSize"
|
||||
) {
|
||||
const loc = locOf(expr);
|
||||
const value = lowerer.lowerExprExpecting(expr.right, F64);
|
||||
return {
|
||||
kind: "exprStmt",
|
||||
expr: { kind: "libCall", fn: "buffer.setPoolSize", args: [value], type: VOID, loc },
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (
|
||||
!expr.left.questionDotToken &&
|
||||
lowerer.stdlibGlobalMember(expr.left, "process") === "exitCode"
|
||||
|
||||
@@ -454,6 +454,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.structuredClone",
|
||||
"dyn.cloneMissing",
|
||||
"dyn.cloneTransferFail",
|
||||
"dyn.cloneTransferFailWith",
|
||||
// the dyn Object walks throw on null/undefined receivers
|
||||
"dyn.objKeys",
|
||||
"dyn.forInKeys",
|
||||
@@ -630,6 +631,14 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"buffer.concatLen",
|
||||
"buffer.fromDyn",
|
||||
"buffer.fromStrChecked",
|
||||
"buffer.alloc",
|
||||
"buffer.allocUnsafe",
|
||||
"buffer.allocUnsafeSlow",
|
||||
"buffer.poolFromStr",
|
||||
"buffer.poolFromStrChecked",
|
||||
"buffer.poolFromBytes",
|
||||
"buffer.poolFromArray",
|
||||
"buffer.poolFromDyn",
|
||||
// The checked-dynamic compare/equals validators: Node's argument
|
||||
// ladders throw ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE catchably.
|
||||
"buffer.compareChk",
|
||||
|
||||
@@ -2435,6 +2435,22 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* opaque reference inputs retain explicit runtime refusals. */
|
||||
"buffer.fromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 },
|
||||
"buffer.concat": { argTypes: [arrayOf(BYTES_U8)], result: BYTES_U8 },
|
||||
/** The pooled Buffer factories (Node's allocate/fromStringFast/
|
||||
* fromArrayLike): allocUnsafe, Buffer.from of strings, Uint8Arrays,
|
||||
* number[] and checked values, and concat return views into the
|
||||
* per-thread pool slab below Buffer.poolSize >>> 1 bytes. alloc and
|
||||
* allocUnsafeSlow never pool. The size forms validate like Node
|
||||
* (ERR_OUT_OF_RANGE). poolSize reads and writes Buffer.poolSize. */
|
||||
"buffer.alloc": { argTypes: [F64], result: BYTES_U8 },
|
||||
"buffer.allocUnsafe": { argTypes: [F64], result: BYTES_U8 },
|
||||
"buffer.allocUnsafeSlow": { argTypes: [F64], result: BYTES_U8 },
|
||||
"buffer.poolFromStr": { argTypes: [STRING, STRING], result: BYTES_U8 },
|
||||
"buffer.poolFromStrChecked": { argTypes: [STRING, STRING], result: BYTES_U8 },
|
||||
"buffer.poolFromBytes": { argTypes: [BYTES_U8], result: BYTES_U8 },
|
||||
"buffer.poolFromArray": { argTypes: [arrayOf(F64)], result: BYTES_U8 },
|
||||
"buffer.poolFromDyn": { argTypes: [DYN, STRING], result: BYTES_U8 },
|
||||
"buffer.poolSize": { argTypes: [], result: F64 },
|
||||
"buffer.setPoolSize": { argTypes: [F64], result: VOID },
|
||||
/** Buffer.byteLength(string, enc) — enc a NORMALIZED literal like
|
||||
* fromStr's — and Buffer.isEncoding(name) over a runtime string
|
||||
* (case-insensitive against Node's alias set). Pure; never throw. */
|
||||
@@ -3082,6 +3098,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
* lowered directly (the list's values need no dyn representation to
|
||||
* fail). */
|
||||
"dyn.cloneTransferFail": { argTypes: [], result: DYN },
|
||||
/** The same refusal given the transfer list's first member, an
|
||||
* ArrayBuffer: a Buffer pool slab gets Node's untransferable message. */
|
||||
"dyn.cloneTransferFailWith": { argTypes: [DYN], result: DYN },
|
||||
/** `new RegExp(pattern, flags?)` (scr_regex.c): a heap regex over the
|
||||
* same libregexp engine the literals use. The pattern compiles EAGERLY
|
||||
* so an invalid pattern (or an unknown flag letter) throws Node's
|
||||
|
||||
@@ -271,7 +271,7 @@ test("LLVM emission performs typed-array element access directly on the valid pa
|
||||
const mod = fixture();
|
||||
expect(validateModule(mod)).toEqual([]);
|
||||
const ll = emitLlvmModule(mod);
|
||||
expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }");
|
||||
expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, i8, ptr }");
|
||||
expect(ll).toContain("getelementptr inbounds i8");
|
||||
expect(ll).toContain("getelementptr inbounds i32");
|
||||
expect(ll).toContain("getelementptr inbounds float");
|
||||
|
||||
@@ -345,7 +345,7 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => {
|
||||
#include <stdint.h>
|
||||
#include "scr_runtime.h"
|
||||
|
||||
/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }.
|
||||
/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, i8, ptr }.
|
||||
* Keep every ABI fact used by its field-index GEPs explicit here. */
|
||||
_Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t");
|
||||
_Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers");
|
||||
@@ -360,6 +360,7 @@ _Static_assert(offsetof(ScrBytes, backing) == 32, "LLVM ScrBytes.backing offset
|
||||
_Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, untransferable) == 43, "LLVM ScrBytes.untransferable offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed");
|
||||
`,
|
||||
);
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5530-buffer-pool.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5531-buffer-pool-first-slab.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5532-buffer-pool-first-slab-untyped.mjs"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -132,6 +132,7 @@ ScrBytes *scr_bytes_take_data(uint8_t *data, size_t len) {
|
||||
b->is_buffer = false;
|
||||
b->is_data_view = false;
|
||||
b->external = false;
|
||||
b->untransferable = false;
|
||||
b->shared = NULL;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
@@ -654,6 +655,7 @@ ScrBytes *scr_bytes_subarray(ScrBytes *b, double start, double end) {
|
||||
v->is_buffer = b->is_buffer;
|
||||
v->is_data_view = false;
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = owner->shared;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
@@ -890,6 +892,7 @@ ScrBytes *scr_dataview_new(ScrBytes *src, double byte_off, bool has_len, double
|
||||
v->is_buffer = false;
|
||||
v->is_data_view = true;
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = owner->shared;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
@@ -2478,14 +2481,30 @@ static const int8_t scr_b64_values[256] = {
|
||||
|
||||
/* Conversion destinations are private until their initialized length is set.
|
||||
* General typed-array allocation must remain zero-filled. */
|
||||
static ScrBytes *scr_bytes_conversion_storage(size_t capacity) {
|
||||
/* Where scr_bytes_encode_str writes: room bytes at `at` in the current pool
|
||||
* slab, or (at == NULL, or more than room needed) fresh private storage. */
|
||||
typedef struct {
|
||||
uint8_t *at;
|
||||
size_t room;
|
||||
ScrBytes header; /* the temporary view handed to the encoders */
|
||||
} ScrBytesSink;
|
||||
|
||||
static ScrBytes *scr_bytes_conversion_storage_in(ScrBytesSink *sink, size_t capacity) {
|
||||
if (sink && sink->at && capacity <= sink->room) {
|
||||
sink->header = (ScrBytes){.rc = 1, .len = 0, .elem = SCR_BYTES_U8, .data = sink->at};
|
||||
return &sink->header;
|
||||
}
|
||||
uint8_t *data = malloc(capacity ? capacity : 1);
|
||||
if (!data) scr_bytes_oom();
|
||||
return scr_bytes_take_data(data, 0);
|
||||
}
|
||||
|
||||
static ScrBytes *scr_bytes_decode_b64(const uint8_t *in, size_t n) {
|
||||
ScrBytes *b = scr_bytes_conversion_storage(n / 4 * 3 + 2);
|
||||
static ScrBytes *scr_bytes_conversion_storage(size_t capacity) {
|
||||
return scr_bytes_conversion_storage_in(NULL, capacity);
|
||||
}
|
||||
|
||||
static ScrBytes *scr_bytes_decode_b64(ScrBytesSink *sink, const uint8_t *in, size_t n) {
|
||||
ScrBytes *b = scr_bytes_conversion_storage_in(sink, n / 4 * 3 + 2);
|
||||
size_t i = 0, o = 0;
|
||||
unsigned acc = 0, have = 0;
|
||||
while (i < n) {
|
||||
@@ -2524,13 +2543,16 @@ static ScrBytes *scr_bytes_decode_b64(const uint8_t *in, size_t n) {
|
||||
return b;
|
||||
}
|
||||
|
||||
ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
/* Encodes s into sink's pool room when its upper bound fits there, else into
|
||||
* fresh private storage. The result's len is the bytes written; a pool
|
||||
* result is &sink->header, never released. */
|
||||
static ScrBytes *scr_bytes_encode_str(ScrBytesSink *sink, const ScrStr *s, const ScrStr *enc) {
|
||||
const uint8_t *in = (const uint8_t *)s->data;
|
||||
size_t n = s->len;
|
||||
if (scr_enc_is(enc, "latin1") || scr_enc_is(enc, "ascii")) {
|
||||
/* Node writes charCodeAt(i) & 0xFF for BOTH spellings — an astral
|
||||
* code point contributes its two surrogates' low bytes. */
|
||||
ScrBytes *b = scr_bytes_conversion_storage(n);
|
||||
ScrBytes *b = scr_bytes_conversion_storage_in(sink, n);
|
||||
size_t o = 0, i = 0;
|
||||
while (i < n) {
|
||||
uint32_t cp = scr_bytes_next_cp(in, &i);
|
||||
@@ -2547,7 +2569,7 @@ ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
}
|
||||
if (scr_enc_is(enc, "utf16le")) {
|
||||
if (n > SIZE_MAX / 2) scr_bytes_oom();
|
||||
ScrBytes *b = scr_bytes_conversion_storage(n * 2);
|
||||
ScrBytes *b = scr_bytes_conversion_storage_in(sink, n * 2);
|
||||
size_t o = 0, i = 0;
|
||||
while (i < n) {
|
||||
uint32_t cp = scr_bytes_next_cp(in, &i);
|
||||
@@ -2573,7 +2595,7 @@ ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
ScrBytes *narrowed = NULL;
|
||||
if (scr_bytes_ascii_prefix(in, n) != n) {
|
||||
ScrStr *latin1 = scr_str_new("latin1", 6);
|
||||
narrowed = scr_bytes_from_str(s, latin1);
|
||||
narrowed = scr_bytes_encode_str(NULL, s, latin1);
|
||||
scr_str_release(latin1);
|
||||
in = narrowed->data;
|
||||
n = narrowed->len;
|
||||
@@ -2581,7 +2603,7 @@ ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
ScrBytes *b;
|
||||
if (hex) {
|
||||
/* Stop at the first invalid pair, discarding an odd tail. */
|
||||
b = scr_bytes_conversion_storage(n / 2);
|
||||
b = scr_bytes_conversion_storage_in(sink, n / 2);
|
||||
size_t o = 0;
|
||||
for (size_t i = 0; i + 1 < n; i += 2) {
|
||||
int hi = scr_hex_val(in[i]), lo = scr_hex_val(in[i + 1]);
|
||||
@@ -2590,20 +2612,352 @@ ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
}
|
||||
b->len = o;
|
||||
} else {
|
||||
b = scr_bytes_decode_b64(in, n);
|
||||
b = scr_bytes_decode_b64(sink, in, n);
|
||||
}
|
||||
scr_bytes_release(narrowed);
|
||||
return b;
|
||||
}
|
||||
/* utf8: ScrStr storage IS the bytes, once lone surrogates become U+FFFD
|
||||
* (same length) */
|
||||
ScrBytes *b = scr_bytes_conversion_storage(n);
|
||||
ScrBytes *b = scr_bytes_conversion_storage_in(sink, n);
|
||||
memcpy(b->data, in, n);
|
||||
if (!s->ascii) scr_wtf8_to_utf8((char *)b->data, n);
|
||||
b->len = n;
|
||||
return b;
|
||||
}
|
||||
|
||||
ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
return scr_bytes_encode_str(NULL, s, enc);
|
||||
}
|
||||
|
||||
/* ── the Buffer pool ────────────────────────────────────────────────────
|
||||
* Node's lib/buffer.js: Buffer.poolSize starts at 64 KiB, and the first
|
||||
* slab exists from startup with that size. A pooled request (size <
|
||||
* Buffer.poolSize >>> 1) that does not fit the current slab's remainder
|
||||
* starts a new slab of Buffer.poolSize bytes; a slab created later uses the
|
||||
* value at that time. Views start at 8-byte aligned offsets. The pool holds
|
||||
* one reference to the current slab and each view another, so a slab is
|
||||
* freed once it is replaced and its last view is released (Node keeps a
|
||||
* slab alive the same way). */
|
||||
#define SCR_BUFFER_POOL_DEFAULT 65536.0
|
||||
size_t scr_num_received(double v, char out[48]);
|
||||
static SCR_TL double scr_pool_setting = SCR_BUFFER_POOL_DEFAULT;
|
||||
static SCR_TL ScrBytes *scr_pool_slab;
|
||||
static SCR_TL size_t scr_pool_offset;
|
||||
static SCR_TL bool scr_pool_started;
|
||||
static SCR_TL bool scr_pool_registered; /* scr_buffer_pool_reset runs at context exit */
|
||||
|
||||
/* Buffer.poolSize >>> 1, kept with the setting. */
|
||||
static SCR_TL size_t scr_pool_half = 32768;
|
||||
|
||||
double scr_buffer_pool_size(void) { return scr_pool_setting; }
|
||||
|
||||
void scr_buffer_set_pool_size(double size) {
|
||||
scr_pool_setting = size;
|
||||
double t = isfinite(size) ? fmod(trunc(size), 4294967296.0) : 0;
|
||||
if (t < 0) t += 4294967296.0;
|
||||
scr_pool_half = (size_t)((uint32_t)t >> 1);
|
||||
}
|
||||
|
||||
static size_t scr_pool_threshold(void) { return scr_pool_half; }
|
||||
|
||||
/* Bytes left in the slab. Aligning can move the offset past the end of a
|
||||
* slab whose size is not a multiple of 8; nothing is left then. */
|
||||
static size_t scr_pool_room(void) {
|
||||
if (!scr_pool_slab || scr_pool_offset >= scr_pool_slab->len) return 0;
|
||||
return scr_pool_slab->len - scr_pool_offset;
|
||||
}
|
||||
|
||||
/* Replaces the slab with one of `size` bytes (Node's createPool). false
|
||||
* with a pending RangeError when size is not a valid typed array length. */
|
||||
static bool scr_pool_create(double size) {
|
||||
ScrBytes *slab = scr_bytes_new(SCR_BYTES_U8, size);
|
||||
if (!slab) return false;
|
||||
slab->untransferable = true;
|
||||
if (!scr_pool_registered) {
|
||||
scr_atexit(scr_buffer_pool_reset);
|
||||
scr_pool_registered = true;
|
||||
}
|
||||
scr_bytes_release(scr_pool_slab);
|
||||
scr_pool_slab = slab;
|
||||
scr_pool_offset = 0;
|
||||
scr_pool_started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Ensures the current slab holds `reserve` more bytes (Node's `size >
|
||||
* poolSize - poolOffset` test before createPool). Node creates the first
|
||||
* slab at startup with the default size, so a request it holds goes there
|
||||
* even after Buffer.poolSize changed, and one it does not hold replaces it
|
||||
* with a slab of the current Buffer.poolSize. Callers write `reserve`
|
||||
* bytes at the pool offset, so the room is checked again after every new
|
||||
* slab: a pooled request is below Buffer.poolSize >>> 1, which a slab of
|
||||
* Buffer.poolSize bytes always holds, and anything else is an internal
|
||||
* error rather than a write past the slab. */
|
||||
static bool scr_pool_reserve(size_t reserve) {
|
||||
if (scr_pool_started && reserve <= scr_pool_room()) return true;
|
||||
bool startup = !scr_pool_started && (double)reserve <= SCR_BUFFER_POOL_DEFAULT;
|
||||
if (!scr_pool_create(startup ? SCR_BUFFER_POOL_DEFAULT : scr_pool_setting)) return false;
|
||||
if (reserve > scr_pool_room()) scr_trap("scriptc: internal error: Buffer pool slab smaller than its reservation\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A +1 Buffer view of `len` bytes at the pool offset, which then advances
|
||||
* past it to the next 8-byte boundary (Node's alignPool). */
|
||||
static ScrBytes *scr_pool_take(size_t len) {
|
||||
ScrBytes *v = malloc(sizeof(ScrBytes));
|
||||
if (!v) scr_bytes_oom();
|
||||
v->rc = 1;
|
||||
v->len = len;
|
||||
v->elem = SCR_BYTES_U8;
|
||||
v->data = scr_pool_slab->data + scr_pool_offset;
|
||||
v->backing = scr_bytes_retain(scr_pool_slab);
|
||||
v->is_buffer = true;
|
||||
v->is_data_view = false;
|
||||
v->external = false;
|
||||
v->untransferable = false;
|
||||
v->shared = NULL;
|
||||
#ifdef SCR_RC_AUDIT
|
||||
scr_live_bytes++;
|
||||
#endif
|
||||
scr_pool_offset += len;
|
||||
if (scr_pool_offset & 7) scr_pool_offset = (scr_pool_offset | 7) + 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
/* An empty Buffer (Node's new FastBuffer()): its own zero-length buffer. */
|
||||
static ScrBytes *scr_buffer_empty(void) {
|
||||
ScrBytes *b = scr_bytes_alloc(SCR_BYTES_U8, 0);
|
||||
b->is_buffer = true;
|
||||
return b;
|
||||
}
|
||||
|
||||
/* validateNumber(size, 'size', 0, kMaxLength). */
|
||||
static bool scr_buffer_check_size(double size) {
|
||||
if (size >= 0 && size <= 9007199254740991.0) return true;
|
||||
char recv[48], msg[160];
|
||||
scr_num_received(size, recv);
|
||||
int len = snprintf(msg, sizeof msg,
|
||||
"The value of \"size\" is out of range. It must be >= 0 && <= 9007199254740991. "
|
||||
"Received %s",
|
||||
recv);
|
||||
scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE");
|
||||
return false;
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_alloc(double size) {
|
||||
if (!scr_buffer_check_size(size)) return NULL;
|
||||
/* Sizes past 2^31 keep scr_bytes_new's typed array length rules. */
|
||||
ScrBytes *b = size < 2147483648.0 ? scr_bytes_alloc(SCR_BYTES_U8, (size_t)size)
|
||||
: scr_bytes_new(SCR_BYTES_U8, size);
|
||||
if (b) b->is_buffer = true;
|
||||
return b;
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_alloc_unsafe_slow(double size) { return scr_buffer_alloc(size); }
|
||||
|
||||
/* Node's allocate(size) for a validated size. */
|
||||
static ScrBytes *scr_buffer_allocate(double size) {
|
||||
if (!(size >= 1)) return scr_buffer_empty();
|
||||
if (size < (double)scr_pool_threshold()) {
|
||||
size_t n = (size_t)size;
|
||||
if (!scr_pool_reserve(n)) return NULL;
|
||||
return scr_pool_take(n);
|
||||
}
|
||||
return scr_buffer_alloc(size);
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_alloc_unsafe(double size) {
|
||||
if (!scr_buffer_check_size(size)) return NULL;
|
||||
return scr_buffer_allocate(size);
|
||||
}
|
||||
|
||||
/* Node's fromArrayLike: the values of a typed array or number[] (ToUint8). */
|
||||
ScrBytes *scr_buffer_from_bytes(const ScrBytes *src) {
|
||||
size_t n = src->len;
|
||||
if (n == 0) return scr_buffer_empty();
|
||||
if (n < scr_pool_threshold()) {
|
||||
if (!scr_pool_reserve(n)) return NULL;
|
||||
ScrBytes *v = scr_pool_take(n);
|
||||
if (src->elem == SCR_BYTES_U8 || src->elem == SCR_BYTES_U8C || src->elem == SCR_BYTES_I8) {
|
||||
scr_bytes_raw_in(src, v->data, src->data, n);
|
||||
} else {
|
||||
for (size_t i = 0; i < n; i++) v->data[i] = (uint8_t)scr_to_uint32(scr_bytes_get(src, (double)i));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
ScrBytes *b = scr_bytes_convert(SCR_BYTES_U8, src);
|
||||
b->is_buffer = true;
|
||||
return b;
|
||||
}
|
||||
|
||||
static void scr_bytes_fill_from_arr(uint8_t *out, ScrBytesElem elem, const ScrArr *arr);
|
||||
|
||||
ScrBytes *scr_buffer_from_arr(const ScrArr *arr) {
|
||||
size_t n = arr->len;
|
||||
if (n == 0) return scr_buffer_empty();
|
||||
if (n < scr_pool_threshold()) {
|
||||
/* Converting numbers runs no user code, so the values are written
|
||||
* straight into the slab. */
|
||||
if (!scr_pool_reserve(n)) return NULL;
|
||||
ScrBytes *v = scr_pool_take(n);
|
||||
scr_bytes_fill_from_arr(v->data, SCR_BYTES_U8, arr);
|
||||
return v;
|
||||
}
|
||||
ScrBytes *b = scr_bytes_from_arr(SCR_BYTES_U8, arr);
|
||||
b->is_buffer = true;
|
||||
return b;
|
||||
}
|
||||
|
||||
double scr_bytes_byte_length_str(ScrStr *s, const ScrStr *enc);
|
||||
|
||||
/* Node's createFromString: an unpooled Buffer of the encoding's byte
|
||||
* length; when fewer bytes are written it views the start of that
|
||||
* ArrayBuffer. */
|
||||
static ScrBytes *scr_buffer_from_str_unpooled(const ScrStr *s, const ScrStr *enc, size_t length) {
|
||||
ScrBytes *b = scr_bytes_encode_str(NULL, s, enc);
|
||||
b->is_buffer = true;
|
||||
if (b->len >= length) return b;
|
||||
/* The encoders' storage bound covers the byte length. */
|
||||
size_t actual = b->len;
|
||||
memset(b->data + actual, 0, length - actual);
|
||||
b->len = length;
|
||||
ScrBytes *view = scr_bytes_subarray(b, 0, (double)actual);
|
||||
scr_bytes_release(b);
|
||||
return view;
|
||||
}
|
||||
|
||||
/* Node's fromString/fromStringFast for a normalized encoding name. */
|
||||
ScrBytes *scr_buffer_from_str(const ScrStr *s, const ScrStr *enc) {
|
||||
if (s->len == 0) return scr_buffer_empty();
|
||||
size_t max = scr_pool_threshold();
|
||||
/* The UTF-16 length lies between len / 3 and len (each unit is one to
|
||||
* three stored bytes; an astral character's four bytes are two units).
|
||||
* Node's reservation is four bytes per unit; when four bytes per stored
|
||||
* byte already fits the threshold and the slab, the exact length cannot
|
||||
* change the outcome. */
|
||||
size_t units;
|
||||
if (s->ascii) {
|
||||
units = s->len;
|
||||
} else if (s->len < max / 4 && scr_pool_started && s->len * 4 <= scr_pool_room()) {
|
||||
units = s->len; /* an upper bound: reserve() succeeds without a new slab */
|
||||
} else {
|
||||
units = (size_t)scr_str_utf16_len((ScrStr *)s);
|
||||
}
|
||||
if (units >= max)
|
||||
return scr_buffer_from_str_unpooled(s, enc, (size_t)scr_bytes_byte_length_str((ScrStr *)s, enc));
|
||||
size_t length = units * 4;
|
||||
if (length >= max) {
|
||||
length = (size_t)scr_bytes_byte_length_str((ScrStr *)s, enc);
|
||||
if (length >= max) return scr_buffer_from_str_unpooled(s, enc, length);
|
||||
}
|
||||
if (!scr_pool_reserve(length)) return NULL;
|
||||
ScrBytesSink sink = {scr_pool_slab->data + scr_pool_offset, scr_pool_room()};
|
||||
ScrBytes *b = scr_bytes_encode_str(&sink, s, enc);
|
||||
size_t actual = b->len;
|
||||
if (b != &sink.header) {
|
||||
/* The encoder's storage bound exceeded the room; the bytes written
|
||||
* never exceed Node's reservation, which fits. */
|
||||
memcpy(scr_pool_slab->data + scr_pool_offset, b->data, actual);
|
||||
scr_bytes_release(b);
|
||||
}
|
||||
return scr_pool_take(actual);
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_from_str_checked(const ScrStr *s, const ScrStr *enc) {
|
||||
ScrStr *normalized = enc->len ? scr_bytes_normalize_encoding(enc) : scr_str_new("utf8", 4);
|
||||
if (!normalized) return NULL;
|
||||
ScrBytes *out = scr_buffer_from_str(s, normalized);
|
||||
scr_str_release(normalized);
|
||||
return out;
|
||||
}
|
||||
|
||||
static ScrBytes *scr_bytes_concat_item(const ScrArr *list, size_t i);
|
||||
|
||||
/* Buffer.concat(list[, totalLength]) into Node's allocate(totalLength). */
|
||||
static ScrBytes *scr_buffer_concat_into(const ScrArr *list, double total) {
|
||||
ScrBytes *out = scr_buffer_allocate(total);
|
||||
if (!out) return NULL;
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < list->len && pos < out->len; i++) {
|
||||
ScrBytes *part = scr_bytes_concat_item(list, i);
|
||||
if (!part) {
|
||||
scr_bytes_release(out);
|
||||
return NULL;
|
||||
}
|
||||
size_t take = part->len < out->len - pos ? part->len : out->len - pos;
|
||||
scr_bytes_read(part, 0, out->data + pos, take);
|
||||
pos += take;
|
||||
scr_bytes_release(part);
|
||||
}
|
||||
if (pos < out->len) memset(out->data + pos, 0, out->len - pos);
|
||||
return out;
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_concat(const ScrArr *list) {
|
||||
if (list->len == 0) return scr_buffer_empty();
|
||||
size_t total = 0;
|
||||
bool borrowed = true;
|
||||
for (size_t i = 0; i < list->len; i++) {
|
||||
/* Present parts are read borrowed; copying runs no script code. */
|
||||
const ScrBytes *part = scr_arr_has(list, (double)i) ? scr_arr_peek_ref(list, (double)i) : NULL;
|
||||
if (!part) {
|
||||
borrowed = false;
|
||||
break;
|
||||
}
|
||||
total += part->len;
|
||||
}
|
||||
if (!borrowed) {
|
||||
total = 0;
|
||||
for (size_t i = 0; i < list->len; i++) {
|
||||
ScrBytes *part = scr_bytes_concat_item(list, i);
|
||||
if (!part) return NULL;
|
||||
total += part->len;
|
||||
scr_bytes_release(part);
|
||||
}
|
||||
return scr_buffer_concat_into(list, (double)total);
|
||||
}
|
||||
ScrBytes *out = scr_buffer_allocate((double)total);
|
||||
if (!out) return NULL;
|
||||
size_t pos = 0;
|
||||
for (size_t i = 0; i < list->len; i++) {
|
||||
const ScrBytes *part = scr_arr_peek_ref(list, (double)i);
|
||||
scr_bytes_read(part, 0, out->data + pos, part->len);
|
||||
pos += part->len;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ScrBytes *scr_buffer_concat_len(const ScrArr *list, double total) {
|
||||
/* An empty list returns before the total validates, as in Node. */
|
||||
if (list->len == 0) return scr_buffer_empty();
|
||||
if (!scr_bytes_validate_off("length", total, 9007199254740991.0)) return NULL;
|
||||
return scr_buffer_concat_into(list, total);
|
||||
}
|
||||
|
||||
/* Moves an unpooled array-like copy into the pool when it is small enough
|
||||
* (Node's fromArrayLike). Consumes `b`. */
|
||||
ScrBytes *scr_buffer_pool_adopt(ScrBytes *b) {
|
||||
size_t n = b->len;
|
||||
if (b->backing || b->shared || n == 0 || n >= scr_pool_threshold()) return b;
|
||||
if (!scr_pool_reserve(n)) {
|
||||
scr_bytes_release(b);
|
||||
return NULL;
|
||||
}
|
||||
ScrBytes *v = scr_pool_take(n);
|
||||
memcpy(v->data, b->data, n);
|
||||
scr_bytes_release(b);
|
||||
return v;
|
||||
}
|
||||
|
||||
void scr_buffer_pool_reset(void) {
|
||||
scr_bytes_release(scr_pool_slab);
|
||||
scr_pool_slab = NULL;
|
||||
scr_pool_offset = 0;
|
||||
scr_pool_started = false;
|
||||
scr_buffer_set_pool_size(SCR_BUFFER_POOL_DEFAULT);
|
||||
}
|
||||
|
||||
/* Runtime encoding variables share the output conversion alias/error table.
|
||||
* Buffer.from treats an empty encoding as utf8, even for an empty source. */
|
||||
ScrBytes *scr_bytes_from_str_checked(const ScrStr *s, const ScrStr *enc) {
|
||||
@@ -2616,6 +2970,12 @@ ScrBytes *scr_bytes_from_str_checked(const ScrStr *s, const ScrStr *enc) {
|
||||
|
||||
ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr) {
|
||||
ScrBytes *b = scr_bytes_alloc_private(elem, arr->len);
|
||||
scr_bytes_fill_from_arr(b->data, elem, arr);
|
||||
return b;
|
||||
}
|
||||
|
||||
/* Writes arr's values, converted to `elem`, at `out` (arr->len elements). */
|
||||
static void scr_bytes_fill_from_arr(uint8_t *out, ScrBytesElem elem, const ScrArr *arr) {
|
||||
size_t width = scr_bytes_elem_size(elem);
|
||||
double values[128];
|
||||
for (size_t start = 0; start < arr->len;) {
|
||||
@@ -2630,10 +2990,9 @@ ScrBytes *scr_bytes_from_arr(ScrBytesElem elem, const ScrArr *arr) {
|
||||
else values[i] = NAN;
|
||||
} else values[i] = scr_arr_get_number(arr, (double)index);
|
||||
}
|
||||
scr_bytes_write_numbers(b->data + start * width, elem, values, count);
|
||||
scr_bytes_write_numbers(out + start * width, elem, values, count);
|
||||
start += count;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ── equals / compare / indexOf / fill / copy / swap / write (u8; the
|
||||
|
||||
@@ -54,6 +54,7 @@ typedef struct ScrBytes {
|
||||
bool is_buffer; /* Buffer brand belongs to the view, not its backing. */
|
||||
bool is_data_view; /* DataView is distinct from a numeric typed array. */
|
||||
bool external; /* Root aliases foreign memory; releasing it never frees data. */
|
||||
bool untransferable; /* A Buffer pool slab: its ArrayBuffer cannot be transferred. */
|
||||
ScrSharedBytes *shared; /* Neutral backing, borrowed by views from their local owner. */
|
||||
} ScrBytes;
|
||||
|
||||
@@ -334,6 +335,30 @@ ScrStr *scr_text_decode_legacy(const ScrBytes *b, double encoding);
|
||||
* "utf16le" writes LE code units, surrogate pairs included. Borrows
|
||||
* both; +1 u8 result. Never throws. */
|
||||
ScrBytes *scr_bytes_from_str(const ScrStr *s, const ScrStr *enc);
|
||||
/* The Buffer pool (Node's lib/buffer.js allocate/fromStringFast/
|
||||
* fromArrayLike): Buffer.allocUnsafe, Buffer.from of strings, arrays and
|
||||
* array-likes, and Buffer.concat return views into a per-thread slab of
|
||||
* Buffer.poolSize bytes when the size is below poolSize >>> 1. Views start
|
||||
* at 8-byte aligned offsets; a request that does not fit starts a new
|
||||
* slab. A slab lives while any of its views does. All return +1 Buffers. */
|
||||
double scr_buffer_pool_size(void);
|
||||
void scr_buffer_set_pool_size(double size);
|
||||
ScrBytes *scr_buffer_alloc(double size); /* zeroed, never pooled */
|
||||
ScrBytes *scr_buffer_alloc_unsafe(double size); /* pooled */
|
||||
ScrBytes *scr_buffer_alloc_unsafe_slow(double size); /* never pooled */
|
||||
ScrBytes *scr_buffer_from_str(const ScrStr *s, const ScrStr *enc);
|
||||
ScrBytes *scr_buffer_from_str_checked(const ScrStr *s, const ScrStr *enc);
|
||||
ScrBytes *scr_buffer_from_bytes(const ScrBytes *src); /* a copy of any typed array's values */
|
||||
ScrBytes *scr_buffer_from_arr(const ScrArr *arr); /* number[] */
|
||||
ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding);
|
||||
ScrBytes *scr_buffer_concat(const ScrArr *list);
|
||||
ScrBytes *scr_buffer_concat_len(const ScrArr *list, double total);
|
||||
/* Drops the pool's slab reference and restores Buffer.poolSize (process
|
||||
* exit audits and library session resets). */
|
||||
void scr_buffer_pool_reset(void);
|
||||
/* A pooled copy of an unpooled u8 Buffer below the pool threshold; others
|
||||
* are returned as they are. Consumes `b`. */
|
||||
ScrBytes *scr_buffer_pool_adopt(ScrBytes *b);
|
||||
ScrBytes *scr_bytes_from_str_checked(const ScrStr *s, const ScrStr *enc);
|
||||
|
||||
/* Buffer.byteLength(string, enc) — enc NORMALIZED like from_str — and
|
||||
|
||||
@@ -83,3 +83,53 @@ void scr_bytes_test_bulk_storage(void) {
|
||||
filled_storage();
|
||||
assert(scr_bytes_live_count() == before);
|
||||
}
|
||||
|
||||
/* The Buffer pool: views into one slab at 8-byte aligned offsets, the
|
||||
* threshold, a slab whose size is not a multiple of 8, the slab's lifetime
|
||||
* through its views, and the reset that drops the pool's own reference. */
|
||||
void scr_bytes_test_buffer_pool(void) {
|
||||
long before = scr_bytes_live_count();
|
||||
scr_buffer_pool_reset();
|
||||
ScrBytes *a = scr_buffer_alloc_unsafe(3);
|
||||
ScrBytes *b = scr_buffer_alloc_unsafe(9);
|
||||
ScrBytes *c = scr_buffer_alloc_unsafe(1);
|
||||
assert(a->backing && a->backing == b->backing && b->backing == c->backing);
|
||||
assert(a->backing->len == 65536 && a->backing->untransferable);
|
||||
assert(scr_bytes_byte_offset(a) == 0 && scr_bytes_byte_offset(b) == 8 &&
|
||||
scr_bytes_byte_offset(c) == 24);
|
||||
ScrBytes *slab = scr_bytes_retain(a->backing);
|
||||
ScrBytes *big = scr_buffer_alloc_unsafe(32768);
|
||||
assert(big && !big->backing && big->len == 32768);
|
||||
ScrBytes *edge = scr_buffer_alloc_unsafe(32767);
|
||||
assert(edge->backing == slab);
|
||||
|
||||
/* A 60-byte slab: after offset 56 four bytes remain, then none. */
|
||||
scr_buffer_set_pool_size(60);
|
||||
ScrBytes *t = scr_buffer_alloc_unsafe(29);
|
||||
while (scr_bytes_byte_offset(t) != 0) {
|
||||
scr_bytes_release(t);
|
||||
t = scr_buffer_alloc_unsafe(29);
|
||||
}
|
||||
assert(t->backing->len == 60);
|
||||
ScrBytes *u = scr_buffer_alloc_unsafe(20);
|
||||
ScrStr *euro = scr_str_new("\xe2\x82\xac", 3), *utf16 = scr_str_new("utf16le", 7);
|
||||
ScrBytes *w = scr_buffer_from_str(euro, utf16);
|
||||
assert(w->backing == t->backing && scr_bytes_byte_offset(w) == 56 && w->len == 2);
|
||||
assert(w->data[0] == 0xac && w->data[1] == 0x20);
|
||||
ScrBytes *x = scr_buffer_alloc_unsafe(1);
|
||||
assert(x->backing != t->backing && scr_bytes_byte_offset(x) == 0);
|
||||
scr_buffer_set_pool_size(0);
|
||||
ScrBytes *y = scr_buffer_alloc_unsafe(1);
|
||||
assert(!y->backing);
|
||||
|
||||
ScrBytes *views[] = {a, b, c, edge, t, u, w, x, y, big};
|
||||
for (size_t i = 0; i < sizeof views / sizeof views[0]; i++) scr_bytes_release(views[i]);
|
||||
/* The first slab lives while a reference to it does. */
|
||||
assert(slab->rc == 1);
|
||||
scr_bytes_release(slab);
|
||||
scr_str_release(euro);
|
||||
scr_str_release(utf16);
|
||||
scr_buffer_pool_reset();
|
||||
assert(scr_buffer_pool_size() == 65536);
|
||||
assert(scr_bytes_live_count() == before);
|
||||
}
|
||||
|
||||
@@ -1276,6 +1276,18 @@ ScrBytes *scr_buffer_from_dyn(const ScrDyn *value, const ScrStr *encoding) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Buffer.from over a checked value, with Node's pooling: strings take
|
||||
* fromStringFast, typed arrays and array-likes fromArrayLike. */
|
||||
ScrBytes *scr_buffer_from_dyn_pooled(const ScrDyn *value, const ScrStr *encoding) {
|
||||
if (value->kind == SCR_DYN_STR) return scr_buffer_from_str(value->v.str, encoding);
|
||||
if (value->kind == SCR_DYN_BYTES && !value->v.bytes->is_data_view)
|
||||
return scr_buffer_from_bytes(value->v.bytes);
|
||||
ScrBytes *out = scr_buffer_from_dyn(value, encoding);
|
||||
if (!out) return NULL;
|
||||
out->is_buffer = true;
|
||||
return scr_buffer_pool_adopt(out);
|
||||
}
|
||||
|
||||
/* ── the checked-dynamic Buffer compare/equals validators ──────────────
|
||||
* Node's argument ladders for buf.equals / buf.compare / Buffer.compare
|
||||
* over dyn-boxed arguments (the invalid-input probes: string needles,
|
||||
|
||||
@@ -68,6 +68,7 @@ extern long scr_dyn_live_count(void); /* scr_json.c */
|
||||
extern long scr_bytes_live_count(void); /* scr_bytes.c */
|
||||
|
||||
static void scr_rc_audit_at_exit(void) {
|
||||
scr_buffer_pool_reset(); /* the pool's own slab reference */
|
||||
/* Fibers suspended forever at loop exhaustion (awaits nobody resolves —
|
||||
* Node exits 0 there too) still hold their stacks and owned values by
|
||||
* design; a strict audit would only report that deliberate abandonment. */
|
||||
|
||||
@@ -793,6 +793,9 @@ void scr_dyn_throw(ScrDyn *d); /* moves; preserves boxed native Error classifica
|
||||
ScrDyn *scr_structured_clone(const ScrDyn *value, const ScrDyn *options);
|
||||
ScrDyn *scr_structured_clone_missing(void);
|
||||
ScrDyn *scr_structured_clone_transfer_fail(void); /* always throws DataCloneError */
|
||||
/* The same, naming a Buffer pool slab's ArrayBuffer as Node does when it is
|
||||
* the transfer list's first member (borrowed). */
|
||||
ScrDyn *scr_structured_clone_transfer_fail_with(const ScrDyn *first);
|
||||
/* Validates a structuredClone options dyn value (throws on failure —
|
||||
* callers must check scr_exc_pending). Borrowed, NULL-tolerant. */
|
||||
void scr_sc_validate_options(const ScrDyn *options);
|
||||
|
||||
@@ -8325,8 +8325,13 @@ void scr_sc_validate_options(const ScrDyn *options) {
|
||||
}
|
||||
if (tr->v.arr.len > 0) {
|
||||
/* Nothing in the static world is transferable — Node's own error for
|
||||
* a non-transferable list member. */
|
||||
scr_throw_domex("DataCloneError", "Found invalid value in transferList.");
|
||||
* a non-transferable list member. A Buffer pool slab's ArrayBuffer is
|
||||
* marked untransferable in Node, which reports it differently. */
|
||||
const ScrDyn *first = tr->v.arr.items[0];
|
||||
if (first && scr_array_buffer_is(first) && ((ScrBytes *)first->v.handle.ptr)->untransferable)
|
||||
scr_throw_domex("DataCloneError", "Cannot transfer object of unsupported type.");
|
||||
else
|
||||
scr_throw_domex("DataCloneError", "Found invalid value in transferList.");
|
||||
}
|
||||
scr_dyn_release(transferView);
|
||||
}
|
||||
@@ -8368,8 +8373,19 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up) {
|
||||
return scr_dyn_new_str(v->v.str);
|
||||
case SCR_DYN_BYTES: {
|
||||
/* A fresh byte copy; the Buffer flavor drops (Node: structuredClone
|
||||
* of a Buffer answers a plain Uint8Array). */
|
||||
ScrBytes *copy = scr_bytes_copy(v->v.bytes);
|
||||
* of a Buffer answers a plain Uint8Array). A typed array view over a
|
||||
* larger ArrayBuffer, such as a pooled Buffer, clones the whole
|
||||
* ArrayBuffer and keeps its offset and length, as Node does. */
|
||||
ScrBytes *src = v->v.bytes;
|
||||
ScrBytes *copy;
|
||||
if (src->backing && !src->is_data_view && !src->shared) {
|
||||
ScrBytes *storage = scr_bytes_copy(src->backing);
|
||||
copy = scr_bytes_buffer_view(storage, src->elem, scr_bytes_byte_offset(src), true,
|
||||
(double)src->len);
|
||||
scr_bytes_release(storage);
|
||||
} else {
|
||||
copy = scr_bytes_copy(src);
|
||||
}
|
||||
ScrDyn *result = scr_dyn_new_bytes(copy);
|
||||
scr_bytes_release(copy);
|
||||
return result;
|
||||
@@ -8454,6 +8470,16 @@ ScrDyn *scr_structured_clone_transfer_fail(void) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* A literal transfer list whose first member is `first`: a Buffer pool
|
||||
* slab's ArrayBuffer is untransferable in Node. Borrowed. */
|
||||
ScrDyn *scr_structured_clone_transfer_fail_with(const ScrDyn *first) {
|
||||
if (scr_array_buffer_is(first) && ((ScrBytes *)first->v.handle.ptr)->untransferable) {
|
||||
scr_throw_domex("DataCloneError", "Cannot transfer object of unsupported type.");
|
||||
return NULL;
|
||||
}
|
||||
return scr_structured_clone_transfer_fail();
|
||||
}
|
||||
|
||||
ScrDyn *scr_structured_clone_missing(void) {
|
||||
/* Node's message, verbatim (its own template double-wraps the text). */
|
||||
static const char msg[] =
|
||||
|
||||
@@ -26,6 +26,7 @@ long scr_bytes_live_count(void);
|
||||
static long total = 0, failed = 0;
|
||||
|
||||
void scr_bytes_test_bulk_storage(void);
|
||||
void scr_bytes_test_buffer_pool(void);
|
||||
|
||||
static void check(bool ok, const char *what) {
|
||||
total++;
|
||||
@@ -405,6 +406,7 @@ int main(int argc, char **argv) {
|
||||
test_coercion_matrix();
|
||||
test_slice_set_copy();
|
||||
scr_bytes_test_bulk_storage();
|
||||
scr_bytes_test_buffer_pool();
|
||||
test_encodings();
|
||||
test_concat_and_u32be();
|
||||
test_from_arr_and_random();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
// Small Buffers own exact-size ArrayBuffers here, while Node allocates them
|
||||
// from a shared 64 KiB pool, so buf.buffer.byteLength and buf.byteOffset
|
||||
// differ. Reading a Buffer's window of its ArrayBuffer through byteOffset
|
||||
// and length gives the same bytes either way, and Buffer.alloc is never
|
||||
// pooled.
|
||||
// Small Buffers from Buffer.from, Buffer.concat and Buffer.allocUnsafe are
|
||||
// views into a shared pool slab. Reading a Buffer's window of its
|
||||
// ArrayBuffer through byteOffset and length gives its own bytes, and
|
||||
// Buffer.alloc is never pooled.
|
||||
const small = Buffer.from("hello");
|
||||
const copy = small.buffer.slice(small.byteOffset, small.byteOffset + small.length);
|
||||
console.log(copy.byteLength, new TextDecoder().decode(new Uint8Array(copy)));
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// Buffer pooling: Buffer.from of strings, arrays and Uint8Arrays,
|
||||
// Buffer.allocUnsafe and Buffer.concat return views into a shared slab of
|
||||
// Buffer.poolSize bytes below poolSize >>> 1, at 8-byte aligned offsets;
|
||||
// Buffer.alloc and allocUnsafeSlow own their storage. Every observation is
|
||||
// recorded before printing: console output allocates from the pool too.
|
||||
const rows: string[] = [];
|
||||
const slabs: ArrayBufferLike[] = [];
|
||||
function slabOf(b: Buffer): number {
|
||||
const size = b.buffer.byteLength;
|
||||
if (size !== 65536 && size !== 1024 && size !== 60) return -1;
|
||||
let index = slabs.indexOf(b.buffer);
|
||||
if (index < 0) {
|
||||
slabs.push(b.buffer);
|
||||
index = slabs.length - 1;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
const note = (label: string, b: Buffer): void => {
|
||||
rows.push(`${label}: length ${b.length}, buffer ${b.buffer.byteLength}, offset ${b.byteOffset}, slab ${slabOf(b)}`);
|
||||
};
|
||||
|
||||
// Node's module loader allocates from the pool before the program runs.
|
||||
// Start from a fresh slab: a 32767-byte request at offset 0 opens one, a
|
||||
// second fills it, and the next request opens another.
|
||||
let start = Buffer.allocUnsafe(32767);
|
||||
while (start.byteOffset !== 0) start = Buffer.allocUnsafe(32767);
|
||||
Buffer.allocUnsafe(32767);
|
||||
rows.push(`poolSize ${Buffer.poolSize}`);
|
||||
note("allocUnsafe(1)", Buffer.allocUnsafe(1));
|
||||
note("from('abc')", Buffer.from("abc"));
|
||||
note("from('héllo')", Buffer.from("héllo"));
|
||||
note("from('€€')", Buffer.from("€€"));
|
||||
note("from('\\ud800abc')", Buffer.from("\ud800abc"));
|
||||
note("allocUnsafe(3)", Buffer.allocUnsafe(3));
|
||||
note("allocUnsafe(8)", Buffer.allocUnsafe(8));
|
||||
note("allocUnsafe(9)", Buffer.allocUnsafe(9));
|
||||
note("from([1, 2, 3])", Buffer.from([1, 2, 3]));
|
||||
const values = [256, -1, 1.9, NaN];
|
||||
note("from(number[])", Buffer.from(values));
|
||||
note("from(Uint8Array)", Buffer.from(new Uint8Array([7, 8])));
|
||||
note("from(Buffer)", Buffer.from(Buffer.from("copy")));
|
||||
note("from(hex)", Buffer.from("abcd", "hex"));
|
||||
note("from(bad hex)", Buffer.from("abzz", "hex"));
|
||||
note("from(base64)", Buffer.from("aGVsbG8=", "base64"));
|
||||
note("from(latin1)", Buffer.from("hé", "latin1"));
|
||||
note("from(utf16le)", Buffer.from("€", "utf16le"));
|
||||
const encoding: BufferEncoding = process.argv.length > 99 ? "hex" : "utf8";
|
||||
note("from(runtime encoding)", Buffer.from("xyz", encoding));
|
||||
note("concat", Buffer.concat([Buffer.from("a"), Buffer.from("bc")]));
|
||||
note("concat(total)", Buffer.concat([Buffer.from("ab"), Buffer.from("cd")], 7));
|
||||
note("concat([])", Buffer.concat([]));
|
||||
note("from('')", Buffer.from(""));
|
||||
note("allocUnsafe(0)", Buffer.allocUnsafe(0));
|
||||
note("alloc(8)", Buffer.alloc(8));
|
||||
note("alloc(8, 1)", Buffer.alloc(8, 1));
|
||||
note("allocUnsafeSlow(8)", Buffer.allocUnsafeSlow(8));
|
||||
note("subarray", Buffer.from("hello").subarray(1, 3));
|
||||
note("from(ArrayBuffer)", Buffer.from(new ArrayBuffer(8)));
|
||||
|
||||
// Sizes at the pool threshold, slab rollover, and Buffer.poolSize.
|
||||
note("from(32767 chars)", Buffer.from("a".repeat(32767)));
|
||||
note("from(32768 chars)", Buffer.from("a".repeat(32768)));
|
||||
note("from(11000 euro signs)", Buffer.from("€".repeat(11000)));
|
||||
note("from(8191 euro signs)", Buffer.from("€".repeat(8191)));
|
||||
note("allocUnsafe(32767)", Buffer.allocUnsafe(32767));
|
||||
note("allocUnsafe(32768)", Buffer.allocUnsafe(32768));
|
||||
note("allocUnsafe(20000)", Buffer.allocUnsafe(20000));
|
||||
note("allocUnsafe(20000)", Buffer.allocUnsafe(20000));
|
||||
note("from(large bad hex)", Buffer.from("zz" + "a".repeat(70000), "hex"));
|
||||
Buffer.poolSize = 1024;
|
||||
rows.push(`poolSize ${Buffer.poolSize}`);
|
||||
note("allocUnsafe(10) after poolSize = 1024", Buffer.allocUnsafe(10));
|
||||
note("allocUnsafe(600)", Buffer.allocUnsafe(600));
|
||||
note("allocUnsafe(511)", Buffer.allocUnsafe(511));
|
||||
// The 64 KiB slab stays current until a request does not fit; the next
|
||||
// slab then has the new size.
|
||||
let small = Buffer.allocUnsafe(500);
|
||||
let filled = 0;
|
||||
while (small.byteOffset !== 0) {
|
||||
small = Buffer.allocUnsafe(500);
|
||||
filled++;
|
||||
}
|
||||
note(`allocUnsafe(500) after ${filled} more`, small);
|
||||
note("allocUnsafe(500)", Buffer.allocUnsafe(500));
|
||||
note("allocUnsafe(500)", Buffer.allocUnsafe(500));
|
||||
// A 60-byte slab leaves 4 bytes after an aligned offset of 56: enough for
|
||||
// the 4-bytes-per-unit reservation of one UTF-16 unit.
|
||||
Buffer.poolSize = 60;
|
||||
let tiny = Buffer.allocUnsafe(29);
|
||||
while (tiny.byteOffset !== 0) tiny = Buffer.allocUnsafe(29);
|
||||
note("allocUnsafe(29) in a 60-byte slab", tiny);
|
||||
note("allocUnsafe(20)", Buffer.allocUnsafe(20));
|
||||
note("from('€', 'utf16le') in the last 4 bytes", Buffer.from("€", "utf16le"));
|
||||
note("from('€€', 'utf16le')", Buffer.from("€€", "utf16le"));
|
||||
note("from('é') in a new slab's room", Buffer.from("é"));
|
||||
Buffer.poolSize = 0;
|
||||
note("allocUnsafe(1) with poolSize 0", Buffer.allocUnsafe(1));
|
||||
Buffer.poolSize = 65536;
|
||||
|
||||
// Views over one slab share an ArrayBuffer and see each other's bytes.
|
||||
const first = Buffer.from("left");
|
||||
const second = Buffer.from("right");
|
||||
rows.push(`shared ArrayBuffer ${first.buffer === second.buffer}`);
|
||||
const whole = new Uint8Array(first.buffer);
|
||||
whole[second.byteOffset] = "R".charCodeAt(0);
|
||||
rows.push(`second through the slab ${second.toString()}`);
|
||||
first.fill("L");
|
||||
rows.push(`first ${first.toString()} second ${second.toString()}`);
|
||||
|
||||
// structuredClone copies the whole slab and keeps the offset; a transfer
|
||||
// of the slab is refused.
|
||||
const cloned = structuredClone(first);
|
||||
rows.push(`clone ${cloned.length} ${cloned.buffer.byteLength} ${cloned.byteOffset === first.byteOffset} ${new TextDecoder().decode(cloned)}`);
|
||||
try {
|
||||
structuredClone(first, { transfer: [first.buffer] });
|
||||
rows.push("transfer allowed");
|
||||
} catch (error) {
|
||||
rows.push(`transfer ${(error as Error).name}: ${(error as Error).message}`);
|
||||
}
|
||||
|
||||
// Invalid sizes.
|
||||
for (const size of [-1, NaN, 2 ** 53, 1.5]) {
|
||||
for (const [name, make] of [
|
||||
["alloc", (n: number) => Buffer.alloc(n)],
|
||||
["allocUnsafe", (n: number) => Buffer.allocUnsafe(n)],
|
||||
["allocUnsafeSlow", (n: number) => Buffer.allocUnsafeSlow(n)],
|
||||
] as const) {
|
||||
try {
|
||||
rows.push(`${name}(${size}) length ${make(size).length}`);
|
||||
} catch (error) {
|
||||
const e = error as Error & { code?: string };
|
||||
rows.push(`${name}(${size}) ${e.name} ${e.code}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of rows) console.log(row);
|
||||
@@ -0,0 +1,151 @@
|
||||
// Buffer.poolSize changes before and between pooled allocations. Node
|
||||
// creates the first 64 KiB slab at startup; a pooled request (size <
|
||||
// Buffer.poolSize >>> 1) that does not fit the current slab replaces it
|
||||
// with a slab of the current Buffer.poolSize. Each case runs in a fresh
|
||||
// child process, so its first pooled allocation meets the startup slab.
|
||||
// Node's own startup code allocates from that slab, so offsets into it
|
||||
// are not printed.
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const KiB = 1024;
|
||||
|
||||
function describe(label: string, b: Buffer, offset = true): void {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < b.length; i++) sum = (sum + b[i]! * (i + 1)) % 1000003;
|
||||
console.log(label, b.length, offset ? b.byteOffset : "-", b.buffer.byteLength, sum);
|
||||
}
|
||||
|
||||
// A follow-up allocation shows whether the slab is shared.
|
||||
function follow(first: Buffer): void {
|
||||
const next = Buffer.allocUnsafe(8);
|
||||
console.log("next", next.byteOffset, next.buffer.byteLength, next.buffer === first.buffer);
|
||||
}
|
||||
|
||||
function run(name: string): void {
|
||||
if (name === "string-ascii") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from("x".repeat(100000));
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "string-utf8") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from("é".repeat(50000));
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "string-hex") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from("a5".repeat(100000), "hex");
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "string-base64") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from("AQID".repeat(33334), "base64");
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "string-latin1") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from("ÿ".repeat(100000), "latin1");
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "array") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const values: number[] = [];
|
||||
for (let i = 0; i < 100000; i++) values.push(i);
|
||||
const b = Buffer.from(values);
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "uint8array") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const source = new Uint8Array(100000);
|
||||
for (let i = 0; i < source.length; i++) source[i] = i * 7;
|
||||
const b = Buffer.from(source);
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "buffer") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.from(Buffer.alloc(100000, 3));
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "alloc-unsafe") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.allocUnsafe(100000).fill(9);
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "concat") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.concat([Buffer.alloc(60000, 1), Buffer.alloc(40000, 2)]);
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "concat-length") {
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const b = Buffer.concat([Buffer.alloc(60000, 1), Buffer.alloc(30000, 2)], 100000);
|
||||
describe(name, b);
|
||||
follow(b);
|
||||
} else if (name === "threshold") {
|
||||
// Just under and at the enlarged threshold.
|
||||
Buffer.poolSize = 300000;
|
||||
const under = Buffer.allocUnsafe(149999).fill(1);
|
||||
describe("under", under);
|
||||
const at = Buffer.allocUnsafe(150000).fill(2);
|
||||
describe("at", at);
|
||||
follow(under);
|
||||
} else if (name === "shrink") {
|
||||
// A smaller setting keeps using the startup slab while requests fit;
|
||||
// the next slab has the new size.
|
||||
Buffer.poolSize = 64;
|
||||
const small = Buffer.from([1, 2, 3]);
|
||||
describe("small", small, false);
|
||||
const unpooled = Buffer.allocUnsafe(32).fill(4);
|
||||
describe("unpooled", unpooled);
|
||||
let last = Buffer.allocUnsafe(31);
|
||||
while (last.buffer === small.buffer) last = Buffer.allocUnsafe(31);
|
||||
const after = Buffer.allocUnsafe(20);
|
||||
console.log("moved", last.byteOffset, last.buffer.byteLength, after.byteOffset, after.buffer === last.buffer);
|
||||
} else if (name === "between") {
|
||||
// Growing and shrinking between allocations.
|
||||
const first = Buffer.allocUnsafe(1000).fill(1);
|
||||
describe("first", first, false);
|
||||
Buffer.poolSize = 1024 * KiB;
|
||||
const big = Buffer.allocUnsafe(400000).fill(5);
|
||||
describe("big", big);
|
||||
Buffer.poolSize = 128;
|
||||
const tiny = Buffer.allocUnsafe(60).fill(6);
|
||||
describe("tiny", tiny);
|
||||
console.log("same slab", tiny.buffer === big.buffer);
|
||||
Buffer.poolSize = 512 * KiB;
|
||||
const mid = Buffer.allocUnsafe(200000).fill(7);
|
||||
describe("mid", mid);
|
||||
console.log("same slab", mid.buffer === big.buffer);
|
||||
const over = Buffer.allocUnsafe(500000).fill(8);
|
||||
describe("over", over);
|
||||
}
|
||||
}
|
||||
|
||||
const cases = [
|
||||
"string-ascii",
|
||||
"string-utf8",
|
||||
"string-hex",
|
||||
"string-base64",
|
||||
"string-latin1",
|
||||
"array",
|
||||
"uint8array",
|
||||
"buffer",
|
||||
"alloc-unsafe",
|
||||
"concat",
|
||||
"concat-length",
|
||||
"threshold",
|
||||
"shrink",
|
||||
"between",
|
||||
];
|
||||
|
||||
const mode = process.argv.at(2);
|
||||
if (mode !== undefined) {
|
||||
run(mode);
|
||||
} else {
|
||||
for (const name of cases) {
|
||||
const child = spawnSync(process.execPath, [process.argv[1]!, name], { encoding: "utf8" });
|
||||
console.log(`${name}: status ${child.status}`);
|
||||
process.stdout.write(child.stdout);
|
||||
if (child.stderr.length > 0) console.log(`stderr: ${child.stderr}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// The dynamic Buffer.from paths (untyped strings, arrays, array-like
|
||||
// objects, Buffer JSON and Buffers) after Buffer.poolSize grows before
|
||||
// the first pooled allocation. Each case runs in a fresh child process;
|
||||
// see 5531 for the typed paths.
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
function describe(label, b) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < b.length; i++) sum = (sum + b[i] * (i + 1)) % 1000003;
|
||||
console.log(label, b.length, b.byteOffset, b.buffer.byteLength, sum);
|
||||
const next = Buffer.allocUnsafe(8);
|
||||
console.log("next", next.byteOffset, next.buffer.byteLength, next.buffer === b.buffer);
|
||||
}
|
||||
|
||||
const values = () => {
|
||||
const out = [];
|
||||
for (let i = 0; i < 100000; i++) out.push(i % 251);
|
||||
return out;
|
||||
};
|
||||
|
||||
const cases = {
|
||||
string: () => Buffer.from("y".repeat(100000)),
|
||||
"string-encoding": () => Buffer.from("0f".repeat(100000), "hex"),
|
||||
array: () => Buffer.from(values()),
|
||||
"array-like": () => {
|
||||
const like = { length: 100000 };
|
||||
for (let i = 0; i < 100000; i++) like[i] = i % 13;
|
||||
return Buffer.from(like);
|
||||
},
|
||||
"buffer-json": () => Buffer.from({ type: "Buffer", data: values() }),
|
||||
buffer: () => Buffer.from(Buffer.alloc(100000, 6)),
|
||||
};
|
||||
|
||||
const mode = process.argv[2];
|
||||
if (mode !== undefined) {
|
||||
Buffer.poolSize = 1024 * 1024;
|
||||
describe(mode, cases[mode]());
|
||||
} else {
|
||||
for (const name of Object.keys(cases)) {
|
||||
const child = spawnSync(process.execPath, [process.argv[1], name], { encoding: "utf8" });
|
||||
console.log(`${name}: status ${child.status}`);
|
||||
process.stdout.write(child.stdout);
|
||||
if (child.stderr.length > 0) console.log(`stderr: ${child.stderr}`);
|
||||
}
|
||||
}
|
||||
Vendored
-1
@@ -3,7 +3,6 @@
|
||||
* the SC2020-family fence naming @types/node instead of a raw TS error
|
||||
* or a binary that runs with the wrong behavior. */
|
||||
console.log(process.hasUncaughtExceptionCaptureCallback()); // the process introspection statics lower now; the capture-callback probe does not
|
||||
console.log(Buffer.poolSize);
|
||||
/* setInterval lowers, and under @types/node its Timeout RETURN now maps
|
||||
* to the numeric handle — holding one and unref()/ref()/hasRef()/refresh()
|
||||
* all compile. The Timeout surface BEYOND that (close, [Symbol.toPrimitive],
|
||||
|
||||
@@ -3,231 +3,222 @@ fenced.ts:5:13 - error SC2020: 'process.hasUncaughtExceptionCaptureCallback' is
|
||||
4 | * or a binary that runs with the wrong behavior. */
|
||||
5 | console.log(process.hasUncaughtExceptionCaptureCallback()); // the process introspection statics lower now; the capture-callback probe does not
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
6 | console.log(Buffer.poolSize);
|
||||
6 | /* setInterval lowers, and under @types/node its Timeout RETURN now maps
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:6:13 - error SC2020: 'Buffer.poolSize' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:15:1 - error SC2020: 'Timeout.close' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
5 | console.log(process.hasUncaughtExceptionCaptureCallback()); // the process introspection statics lower now; the capture-callback probe does not
|
||||
6 | console.log(Buffer.poolSize);
|
||||
| ^~~~~~~~~~~~~~~
|
||||
7 | /* setInterval lowers, and under @types/node its Timeout RETURN now maps
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:16:1 - error SC2020: 'Timeout.close' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
15 | timer.refresh();
|
||||
16 | timer.close();
|
||||
14 | timer.refresh();
|
||||
15 | timer.close();
|
||||
| ^~~~~~~~~~~~~
|
||||
17 | /* The web-platform globals ride in with @types/node (undici). Fetch,
|
||||
16 | /* The web-platform globals ride in with @types/node (undici). Fetch,
|
||||
|
||||
hint: unref(), ref(), hasRef(), and refresh() are the supported Timeout methods
|
||||
|
||||
fenced.ts:28:15 - error SC2020: 'Response.type' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:27:15 - error SC2020: 'Response.type' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
27 | const response = await fetch(url);
|
||||
28 | console.log(response.type);
|
||||
26 | const response = await fetch(url);
|
||||
27 | console.log(response.type);
|
||||
| ^~~~~~~~~~~~~
|
||||
29 | response.clone();
|
||||
28 | response.clone();
|
||||
|
||||
hint: the member is outside the native static handle projection
|
||||
|
||||
fenced.ts:29:3 - error SC2020: 'Response.clone' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:28:3 - error SC2020: 'Response.clone' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
28 | console.log(response.type);
|
||||
29 | response.clone();
|
||||
27 | console.log(response.type);
|
||||
28 | response.clone();
|
||||
| ^~~~~~~~~~~~~~
|
||||
30 | }
|
||||
29 | }
|
||||
|
||||
hint: the native fetch runtime does not implement this Response operation
|
||||
|
||||
fenced.ts:32:21 - error SC2020: 'ReadableStream.from() over Set<number> iterables' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:31:21 - error SC2020: 'ReadableStream.from() over Set<number> iterables' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
31 | void inspectResponse;
|
||||
32 | ReadableStream.from(new Set([1, 2]));
|
||||
30 | void inspectResponse;
|
||||
31 | ReadableStream.from(new Set([1, 2]));
|
||||
| ^~~~~~~~~~~~~~~
|
||||
33 | /* Members of SUPPORTED builtin modules beyond the lowered tables: they
|
||||
32 | /* Members of SUPPORTED builtin modules beyond the lowered tables: they
|
||||
|
||||
hint: arrays, Uint8Array, and strings are supported — spread other synchronous iterables into an array first: [...iterable]
|
||||
|
||||
fenced.ts:39:1 - error SC2020: 'fs.watchFile' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:38:1 - error SC2020: 'fs.watchFile' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
38 | import { win32 } from "path";
|
||||
39 | watchFile("x", () => {});
|
||||
37 | import { win32 } from "path";
|
||||
38 | watchFile("x", () => {});
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~
|
||||
40 | setPriority(0);
|
||||
39 | setPriority(0);
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:40:1 - error SC2020: 'os.setPriority' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:39:1 - error SC2020: 'os.setPriority' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
39 | watchFile("x", () => {});
|
||||
40 | setPriority(0);
|
||||
38 | watchFile("x", () => {});
|
||||
39 | setPriority(0);
|
||||
| ^~~~~~~~~~~~~~
|
||||
41 | console.log(win32.sep);
|
||||
40 | console.log(win32.sep);
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:41:13 - error SC2020: 'PlatformPath.sep' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:40:13 - error SC2020: 'PlatformPath.sep' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
40 | setPriority(0);
|
||||
41 | console.log(win32.sep);
|
||||
39 | setPriority(0);
|
||||
40 | console.log(win32.sep);
|
||||
| ^~~~~~~~~
|
||||
42 | /* URL getters and serialization lower under @types/node, but object-URL
|
||||
41 | /* URL getters and serialization lower under @types/node, but object-URL
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:47:13 - error SC2020: 'URL.createObjectURL' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:46:13 - error SC2020: 'URL.createObjectURL' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
46 | const u = new URL("https://example.com/x?a=1");
|
||||
47 | console.log(URL.createObjectURL(new Blob(["data"])));
|
||||
45 | const u = new URL("https://example.com/x?a=1");
|
||||
46 | console.log(URL.createObjectURL(new Blob(["data"])));
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
48 | u.searchParams.get("a");
|
||||
47 | u.searchParams.get("a");
|
||||
|
||||
hint: protocol, origin, username, password, pathname, href, host, hostname, port, search, hash, searchParams, toString(), and toJSON() are the supported URL members
|
||||
|
||||
fenced.ts:53:21 - error SC2020: 'deflateSync with these options' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:52:21 - error SC2020: 'deflateSync with these options' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
52 | import { brotliCompressSync, deflateSync } from "zlib";
|
||||
53 | deflateSync("data", { level: 9, strategy: 1 });
|
||||
51 | import { brotliCompressSync, deflateSync } from "zlib";
|
||||
52 | deflateSync("data", { level: 9, strategy: 1 });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
54 | brotliCompressSync(Buffer.from("data"));
|
||||
53 | brotliCompressSync(Buffer.from("data"));
|
||||
|
||||
hint: the static compression options form is { level: <integer literal from -1 through 9> }
|
||||
|
||||
fenced.ts:54:1 - error SC2020: 'zlib.brotliCompressSync' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:53:1 - error SC2020: 'zlib.brotliCompressSync' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
53 | deflateSync("data", { level: 9, strategy: 1 });
|
||||
54 | brotliCompressSync(Buffer.from("data"));
|
||||
52 | deflateSync("data", { level: 9, strategy: 1 });
|
||||
53 | brotliCompressSync(Buffer.from("data"));
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
55 | /* The http2 compatibility slice's @types/node-world fences (divergence
|
||||
54 | /* The http2 compatibility slice's @types/node-world fences (divergence
|
||||
|
||||
hint: Brotli needs a Brotli codec, which the static runtime does not vendor — the default-options deflate/inflate, raw, gzip/gunzip, and unzip sync/callback forms, crc32, and the Deflate/Inflate/Gzip/Gunzip/DeflateRaw/InflateRaw/Unzip streams are the lowered zlib surface
|
||||
|
||||
fenced.ts:63:71 - error SC2020: 'a createSecureServer SNICallback of 'undefined' values' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:62:71 - error SC2020: 'a createSecureServer SNICallback of 'undefined' values' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
62 | import * as http2 from "node:http2";
|
||||
63 | http2.createSecureServer({ allowHTTP1: true, cert: "pem", key: "pem", SNICallback: undefined });
|
||||
61 | import * as http2 from "node:http2";
|
||||
62 | http2.createSecureServer({ allowHTTP1: true, cert: "pem", key: "pem", SNICallback: undefined });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~
|
||||
64 | http2.createSecureServer({
|
||||
63 | http2.createSecureServer({
|
||||
|
||||
hint: SNICallback lowers as a direct option or the conditional spread ...(x ? { SNICallback: x } : {}) — the callback is (servername, cb) => void with cb: (err: Error | null, ctx?: tls.SecureContext) => void
|
||||
|
||||
fenced.ts:68:3 - error SC2020: 'createSecureServer options with a conditional spread' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:67:3 - error SC2020: 'createSecureServer options with a conditional spread' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
67 | key: "pem",
|
||||
68 | ...(1 ? { SNICallback: undefined } : {}),
|
||||
66 | key: "pem",
|
||||
67 | ...(1 ? { SNICallback: undefined } : {}),
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
69 | });
|
||||
68 | });
|
||||
|
||||
hint: the lowered conditional spread is exactly ...(x ? { SNICallback: x } : {}) — the included value must BE the condition
|
||||
|
||||
fenced.ts:70:71 - error SC2020: 'createSecureServer option 'streamResetBurst' with a non-literal value' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:69:71 - error SC2020: 'createSecureServer option 'streamResetBurst' with a non-literal value' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
69 | });
|
||||
70 | http2.createSecureServer({ allowHTTP1: true, cert: "pem", key: "pem", streamResetBurst: 1 + 0 });
|
||||
68 | });
|
||||
69 | http2.createSecureServer({ allowHTTP1: true, cert: "pem", key: "pem", streamResetBurst: 1 + 0 });
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~
|
||||
71 | http2.connect("https://localhost");
|
||||
70 | http2.connect("https://localhost");
|
||||
|
||||
hint: h2 session-tuning options are accepted with literal values but are not applied yet
|
||||
|
||||
fenced.ts:77:1 - error SC2020: 'crypto.generateKeyPair' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:76:1 - error SC2020: 'crypto.generateKeyPair' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
76 | import { checkPrimeSync, createCipheriv, generateKeyPair, setFips } from "node:crypto";
|
||||
77 | generateKeyPair("rsa", { modulusLength: 2048 }, () => {});
|
||||
75 | import { checkPrimeSync, createCipheriv, generateKeyPair, setFips } from "node:crypto";
|
||||
76 | generateKeyPair("rsa", { modulusLength: 2048 }, () => {});
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
78 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
77 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
|
||||
hint: asymmetric-key operations need a public-key stack (bignum, RSA/EC/EdDSA math) and public and private KeyObjects — neither exists in the static runtime, so no faithful lowering can be small; the lowered crypto surface is hashing, randomness, secret KeyObjects, and the introspection statics
|
||||
|
||||
fenced.ts:78:16 - error SC2020: 'crypto.createCipheriv with cipher 'aes-128-ccm'' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:77:16 - error SC2020: 'crypto.createCipheriv with cipher 'aes-128-ccm'' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
77 | generateKeyPair("rsa", { modulusLength: 2048 }, () => {});
|
||||
78 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
76 | generateKeyPair("rsa", { modulusLength: 2048 }, () => {});
|
||||
77 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
| ^~~~~~~~~~~~~
|
||||
79 | checkPrimeSync(7n);
|
||||
78 | checkPrimeSync(7n);
|
||||
|
||||
hint: the supported ciphers are aes-128-cbc, aes-192-cbc, aes-256-cbc, aes-128-ctr, aes-192-ctr, aes-256-ctr, aes-128-gcm, aes-192-gcm, aes-256-gcm, and chacha20-poly1305
|
||||
|
||||
fenced.ts:79:1 - error SC2020: 'crypto.checkPrimeSync' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:78:1 - error SC2020: 'crypto.checkPrimeSync' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
78 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
79 | checkPrimeSync(7n);
|
||||
77 | createCipheriv("aes-128-ccm", Buffer.alloc(16), Buffer.alloc(12), { authTagLength: 16 });
|
||||
78 | checkPrimeSync(7n);
|
||||
| ^~~~~~~~~~~~~~~~~~
|
||||
80 | setFips(false);
|
||||
79 | setFips(false);
|
||||
|
||||
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
|
||||
|
||||
fenced.ts:80:1 - error SC2020: 'crypto.setFips' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:79:1 - error SC2020: 'crypto.setFips' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
79 | checkPrimeSync(7n);
|
||||
80 | setFips(false);
|
||||
78 | checkPrimeSync(7n);
|
||||
79 | setFips(false);
|
||||
| ^~~~~~~~~~~~~~
|
||||
81 | fetch("https://example.invalid/", {
|
||||
80 | fetch("https://example.invalid/", {
|
||||
|
||||
hint: a compiled binary has no FIPS provider to enable, and Node itself throws on setFips(true) in a non-FIPS build — getFips() answers 0 here
|
||||
|
||||
fenced.ts:82:3 - error SC2020: 'RequestInit option 'integrity' in a static build' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:81:3 - error SC2020: 'RequestInit option 'integrity' in a static build' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
81 | fetch("https://example.invalid/", {
|
||||
82 | integrity: "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
80 | fetch("https://example.invalid/", {
|
||||
81 | integrity: "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
83 | });
|
||||
82 | });
|
||||
|
||||
hint: the compiler does not preserve this RequestInit member's conversion or transport behavior
|
||||
|
||||
fenced.ts:86:3 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:85:3 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
85 | const body = (await fetch(url)).body!;
|
||||
86 | body.tee();
|
||||
84 | const body = (await fetch(url)).body!;
|
||||
85 | body.tee();
|
||||
| ^~~~~~~~
|
||||
87 | const tee = body.tee;
|
||||
86 | const tee = body.tee;
|
||||
|
||||
hint: this operation is outside the native readable-stream implementation
|
||||
|
||||
fenced.ts:87:15 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:86:15 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
86 | body.tee();
|
||||
87 | const tee = body.tee;
|
||||
85 | body.tee();
|
||||
86 | const tee = body.tee;
|
||||
| ^~~~~~~~
|
||||
88 | body["tee"]();
|
||||
87 | body["tee"]();
|
||||
|
||||
hint: this operation is outside the native readable-stream implementation
|
||||
|
||||
fenced.ts:88:3 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:87:3 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
87 | const tee = body.tee;
|
||||
88 | body["tee"]();
|
||||
86 | const tee = body.tee;
|
||||
87 | body["tee"]();
|
||||
| ^~~~~~~~~~~
|
||||
89 | const bracketTee = body["tee"];
|
||||
88 | const bracketTee = body["tee"];
|
||||
|
||||
hint: this operation is outside the native readable-stream implementation
|
||||
|
||||
fenced.ts:89:22 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
fenced.ts:88:22 - error SC2020: 'ReadableStream.tee' is typed by @types/node but has no scriptc lowering yet
|
||||
|
||||
88 | body["tee"]();
|
||||
89 | const bracketTee = body["tee"];
|
||||
87 | body["tee"]();
|
||||
88 | const bracketTee = body["tee"];
|
||||
| ^~~~~~~~~~~
|
||||
90 | }
|
||||
89 | }
|
||||
|
||||
hint: this operation is outside the native readable-stream implementation
|
||||
|
||||
fenced.ts:95:23 - error SC2020: 'util.callbackify into a callback whose error parameter excludes null' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:94:23 - error SC2020: 'util.callbackify into a callback whose error parameter excludes null' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
94 | import { callbackify } from "node:util";
|
||||
95 | const callbackified = callbackify(async (n: number) => n);
|
||||
93 | import { callbackify } from "node:util";
|
||||
94 | const callbackified = callbackify(async (n: number) => n);
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
96 | void callbackified;
|
||||
95 | void callbackified;
|
||||
|
||||
hint: Node calls back with a null error on success; type the binding's callback as (err: NodeJS.ErrnoException | null, ...) => void
|
||||
|
||||
fenced.ts:100:1 - error SC2020: 'Cipheriv/Decipheriv.on' is part of the standard library types but has no scriptc lowering yet
|
||||
fenced.ts:99:1 - error SC2020: 'Cipheriv/Decipheriv.on' is part of the standard library types but has no scriptc lowering yet
|
||||
|
||||
99 | const cipherStream = createCipheriv("aes-128-cbc", Buffer.alloc(16), Buffer.alloc(16));
|
||||
100 | cipherStream.on("data", () => {});
|
||||
98 | const cipherStream = createCipheriv("aes-128-cbc", Buffer.alloc(16), Buffer.alloc(16));
|
||||
99 | cipherStream.on("data", () => {});
|
||||
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
101 | // End of the declared-but-not-lowered surface.
|
||||
100 | // End of the declared-but-not-lowered surface.
|
||||
|
||||
hint: update(), final(), setAAD(), setAutoPadding(), getAuthTag(), and setAuthTag() are supported; cipher streams are not
|
||||
Reference in New Issue
Block a user