mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Store boolean arrays as one byte per element (#947)
This commit is contained in:
@@ -70,7 +70,7 @@ Without `-o`, artifacts are written to `.scriptc/` next to the input. Different
|
||||
|
||||
### External linking
|
||||
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v17` checks runtime compatibility at link time.
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v18` checks runtime compatibility at link time.
|
||||
|
||||
`--print=native-link-info` emits the program object and a JSON recipe containing the runtime pack and link order for external builds. This object ABI is experimental and requires an exact runtime version. See [Native Program Objects](/docs/native-objects) for examples.
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ $ ./app-ld
|
||||
|
||||
`cc` uses the C compiler as a linker driver. `ld` links the same precompiled runtime objects directly with the selected Apple SDK. The object defines `main`; it is a complete program object, not a library to load into another process. Use `scriptc build --lib --profile ...` for a host-callable static library.
|
||||
|
||||
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v17`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
|
||||
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v18`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
|
||||
|
||||
@@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
minimum_os: "14.0",
|
||||
},
|
||||
program: { object, entry_symbol: "main" },
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
runtime_pack: {
|
||||
kind: "precompiled",
|
||||
package: "@scriptc/runtime-darwin-arm64",
|
||||
@@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
() => null,
|
||||
(failure: { stderr?: string }) => failure,
|
||||
);
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v17");
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v18");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -77,6 +77,22 @@ test.each([32, 64] as const)("strict scalar reads check dense storage inline (%i
|
||||
if (bits === 32) expect(fn).toContain("trunc i64");
|
||||
});
|
||||
|
||||
test.each([32, 64] as const)("boolean reads load one byte per element (%i-bit)", (bits) => {
|
||||
const read: IrExpr = { kind: "arrayGet", arr: ref("b", flags), index, type: BOOL, loc };
|
||||
const llvm = emitLlvmModule(module("flag", BOOL, [{ kind: "return", value: read, loc }]), {
|
||||
pointerBits: bits,
|
||||
});
|
||||
const fn = body(llvm, "flag");
|
||||
const size = bits === 32 ? "i32" : "i64";
|
||||
expect(fn).toMatch(
|
||||
new RegExp(
|
||||
`getelementptr inbounds i8, ptr %t\\d+, ${size} %t\\d+\\n %t(\\d+) = load i8, ptr .*\\n %t\\d+ = icmp ne i8 %t\\1, 0`,
|
||||
),
|
||||
);
|
||||
expect(fn).not.toMatch(/getelementptr inbounds i64/);
|
||||
expect(fn).toMatch(/call i1 @scr_arr_get_bool\(.*\) cold/);
|
||||
});
|
||||
|
||||
test("reference reads retain inline and keep a cold +1 runtime fallback", () => {
|
||||
const read: IrExpr = { kind: "arrayGet", arr: ref("s", words), index, type: STRING, loc };
|
||||
// An element removal in the body keeps the read from being lent as a
|
||||
@@ -114,7 +130,10 @@ test("element writes store inline, extend the length, and release replaced refer
|
||||
);
|
||||
const fn = body(llvm, "store");
|
||||
expect(fn).toMatch(/store double 0x4004000000000000, ptr /);
|
||||
expect(fn).toMatch(/zext i1 .* to i64/);
|
||||
// Boolean elements are one byte each.
|
||||
expect(fn).toMatch(
|
||||
/getelementptr inbounds i8, ptr .*\n %t\d+ = zext i1 .* to i8\n store i8 %t\d+, ptr /,
|
||||
);
|
||||
expect(fn).toMatch(/store i8 1, ptr /);
|
||||
expect(fn).toMatch(/icmp uge i64 .*/);
|
||||
expect(fn).toContain("call void @scr_str_release(");
|
||||
@@ -143,7 +162,8 @@ test.each([32, 64] as const)(
|
||||
const size = bits === 32 ? "i32" : "i64";
|
||||
expect(fn.match(new RegExp(`icmp ult ${size} `, "g"))).toHaveLength(3);
|
||||
expect(fn).toMatch(/store double 0x4004000000000000, ptr /);
|
||||
expect(fn).toMatch(/zext i1 .* to i64/);
|
||||
expect(fn).toMatch(/zext i1 .* to i8/);
|
||||
expect(fn.match(/getelementptr inbounds i8, ptr /g)).toHaveLength(4);
|
||||
expect(fn.match(/store i8 1, ptr /g)).toHaveLength(3);
|
||||
expect(fn.match(new RegExp(`add nuw ${size} .*, 1$`, "gm"))).toHaveLength(3);
|
||||
// Appends never release: the slot past the length holds no value.
|
||||
|
||||
@@ -189,14 +189,21 @@ function statePointer(
|
||||
return statePtr;
|
||||
}
|
||||
|
||||
/** The dense slot type: boolean arrays store one byte per element, every
|
||||
* other element kind an 8-byte slot. */
|
||||
function slotType(acc: ArrayAccess): "i8" | "i64" {
|
||||
return acc === "bool" ? "i8" : "i64";
|
||||
}
|
||||
|
||||
function valuePointer(
|
||||
host: LlvmEmitterContext,
|
||||
guard: Pick<DenseGuard, "offset" | "data">,
|
||||
acc: ArrayAccess,
|
||||
): string {
|
||||
const B = host.B;
|
||||
const valuePtr = B.tmp();
|
||||
B.line(
|
||||
`${valuePtr} = getelementptr inbounds i64, ptr ${guard.data}, ${host.sizeType} ${guard.offset}`,
|
||||
`${valuePtr} = getelementptr inbounds ${slotType(acc)}, ptr ${guard.data}, ${host.sizeType} ${guard.offset}`,
|
||||
);
|
||||
host.markMemoryPointer(valuePtr, "array:elements");
|
||||
return valuePtr;
|
||||
@@ -244,7 +251,7 @@ export function emitDenseArrayGet(
|
||||
B.line(`${present} = icmp eq i8 ${state}, ${SCR_ARR_VALUE}`);
|
||||
B.condBr(present, valueLabel, slow);
|
||||
B.startBlock(valueLabel);
|
||||
const ptr = valuePointer(host, guard);
|
||||
const ptr = valuePointer(host, guard, acc);
|
||||
let fast: string;
|
||||
let fastLabel = valueLabel;
|
||||
if (acc === "f64") {
|
||||
@@ -253,8 +260,8 @@ export function emitDenseArrayGet(
|
||||
} else if (acc === "bool") {
|
||||
const raw = B.tmp();
|
||||
fast = B.tmp();
|
||||
B.line(`${raw} = load i64, ptr ${ptr}${host.fieldAliasAttachment(ptr)}`);
|
||||
B.line(`${fast} = icmp ne i64 ${raw}, 0`);
|
||||
B.line(`${raw} = load i8, ptr ${ptr}${host.fieldAliasAttachment(ptr)}`);
|
||||
B.line(`${fast} = icmp ne i8 ${raw}, 0`);
|
||||
} else {
|
||||
const raw = B.tmp(),
|
||||
nonnull = B.tmp();
|
||||
@@ -373,14 +380,14 @@ export function emitDenseArraySet(
|
||||
B.line(`store double ${value}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
} else if (acc === "bool") {
|
||||
const bits = B.tmp();
|
||||
B.line(`${bits} = zext i1 ${value} to i64`);
|
||||
B.line(`store i64 ${bits}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
B.line(`${bits} = zext i1 ${value} to i8`);
|
||||
B.line(`store i8 ${bits}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
} else {
|
||||
B.line(`store ptr ${value}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
}
|
||||
};
|
||||
const statePtr = statePointer(host, guard);
|
||||
const valuePtr = valuePointer(host, guard);
|
||||
const valuePtr = valuePointer(host, guard, acc);
|
||||
let oldState: string | null = null;
|
||||
let old: string | null = null;
|
||||
if (acc === "ref") {
|
||||
@@ -444,7 +451,7 @@ export function emitDenseArraySet(
|
||||
// Below the bound the slot holds a present value: replace it, leaving
|
||||
// the state alone, and release a replaced reference after the store.
|
||||
B.startBlock(filledLabel);
|
||||
const filledValuePtr = valuePointer(host, guard);
|
||||
const filledValuePtr = valuePointer(host, guard, acc);
|
||||
let filledOld: string | null = null;
|
||||
if (acc === "ref") {
|
||||
filledOld = B.tmp();
|
||||
@@ -504,13 +511,13 @@ export function emitDenseArrayPush(
|
||||
B.line(`${data} = load ptr, ptr ${dataPtr}${host.fieldAliasAttachment(dataPtr)}`);
|
||||
B.line(`${states} = load ptr, ptr ${statesPtr}${host.fieldAliasAttachment(statesPtr)}`);
|
||||
const statePtr = statePointer(host, { offset: len, states });
|
||||
const valuePtr = valuePointer(host, { offset: len, data });
|
||||
const valuePtr = valuePointer(host, { offset: len, data }, acc);
|
||||
if (acc === "f64") {
|
||||
B.line(`store double ${value}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
} else if (acc === "bool") {
|
||||
const bits = B.tmp();
|
||||
B.line(`${bits} = zext i1 ${value} to i64`);
|
||||
B.line(`store i64 ${bits}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
B.line(`${bits} = zext i1 ${value} to i8`);
|
||||
B.line(`store i8 ${bits}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
} else {
|
||||
B.line(`store ptr ${value}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
}
|
||||
|
||||
@@ -466,7 +466,7 @@ function emitDenseReferenceArrayRead(
|
||||
no: string,
|
||||
slow: string,
|
||||
join: string,
|
||||
loadType: "ptr" | "i64" = "ptr",
|
||||
loadType: "ptr" | "i64" | "i8" = "ptr",
|
||||
): void {
|
||||
const B = host.B;
|
||||
const range = B.newLabel("local.array.range"),
|
||||
@@ -552,7 +552,11 @@ function emitDenseReferenceArrayRead(
|
||||
);
|
||||
host.markMemoryPointer(dataPtr, "array:header");
|
||||
B.line(`${data} = load ptr, ptr ${dataPtr}${host.fieldAliasAttachment(dataPtr)}`);
|
||||
B.line(`${valuePtr} = getelementptr inbounds i64, ptr ${data}, ${host.sizeType} ${offset}`);
|
||||
// Boolean arrays store one byte per element; every other kind an 8-byte slot.
|
||||
const slotType = loadType === "i8" ? "i8" : "i64";
|
||||
B.line(
|
||||
`${valuePtr} = getelementptr inbounds ${slotType}, ptr ${data}, ${host.sizeType} ${offset}`,
|
||||
);
|
||||
host.markMemoryPointer(valuePtr, "array:elements");
|
||||
B.line(`${raw} = load ${loadType}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
present(raw);
|
||||
@@ -663,7 +667,7 @@ export function emitLocalArrayRead(
|
||||
if (read.element.kind === "bool") {
|
||||
const nonzero = B.tmp();
|
||||
bits = B.tmp();
|
||||
B.line(`${nonzero} = icmp ne i64 ${value}, 0`);
|
||||
B.line(`${nonzero} = icmp ne i8 ${value}, 0`);
|
||||
B.line(`${bits} = zext i1 ${nonzero} to i64`);
|
||||
}
|
||||
B.line(`store i64 ${bits}, ptr ${payload}`);
|
||||
@@ -684,7 +688,7 @@ export function emitLocalArrayRead(
|
||||
no,
|
||||
slow,
|
||||
join,
|
||||
scalar ? "i64" : "ptr",
|
||||
!scalar ? "ptr" : read.element.kind === "bool" ? "i8" : "i64",
|
||||
);
|
||||
else B.br(slow);
|
||||
B.startBlock(slow);
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* of the link contract: a program object keeps this symbol undefined and the
|
||||
* matching runtime defines it, so incompatible manual links fail before the
|
||||
* program can start. */
|
||||
export const RUNTIME_ABI_VERSION = 17 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v17" as const;
|
||||
export const RUNTIME_ABI_VERSION = 18 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v18" as const;
|
||||
|
||||
/** Object consumption is useful today, but the complete scr_* surface may
|
||||
* still change before 1.0. The versioned marker prevents accidental mixing;
|
||||
|
||||
@@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
|
||||
@@ -116,7 +116,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
compiler: {
|
||||
command: "clang",
|
||||
identity: "fixture clang",
|
||||
|
||||
@@ -312,7 +312,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
|
||||
expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]);
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v17");
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v18");
|
||||
|
||||
const clangExe = join(dir, "clang-program");
|
||||
const linkDriver = join(dir, "clang-driver.c");
|
||||
@@ -646,7 +646,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
|
||||
.split("\n");
|
||||
expect(undefinedSymbols).toContain("_sf_scale");
|
||||
expect(undefinedSymbols).toContain("_sf_callback_mix");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v17");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v18");
|
||||
});
|
||||
|
||||
test("helper and clang object paths retain the same outbound FFI C ABI", async () => {
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5080-boolean-array-storage.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5081-boolean-array-dynamic-views.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -306,7 +306,7 @@ async function build() {
|
||||
package: packageManifest.name,
|
||||
version: packageManifest.version,
|
||||
target: RUNTIME_PACK_MATRIX.target,
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
compiler: {
|
||||
command: compiler,
|
||||
identity: compilerVersion,
|
||||
|
||||
@@ -402,7 +402,7 @@ async function build() {
|
||||
package: packageManifest.name,
|
||||
version: packageManifest.version,
|
||||
target: matrix.target,
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
compiler: {
|
||||
command: compiler,
|
||||
identity: compilerVersion,
|
||||
|
||||
@@ -165,8 +165,19 @@ typedef struct {
|
||||
ScrArrSparseSlot *sparse;
|
||||
size_t sparse_len;
|
||||
size_t sparse_cap;
|
||||
uint8_t elem; /* the owning array's element kind: the dense slot width */
|
||||
} ScrArrStorage;
|
||||
|
||||
static uint64_t scr_arr_storage_get(const ScrArrStorage *s, size_t index) {
|
||||
if (s->elem == SCR_ELEM_BOOL) return ((const uint8_t *)s->data)[index];
|
||||
return s->data[index];
|
||||
}
|
||||
|
||||
/* The address of dense slot `index` (<= cap). */
|
||||
static uint8_t *scr_arr_dense_at(const ScrArr *a, size_t index) {
|
||||
return (uint8_t *)a->data + index * scr_arr_slot_width(a);
|
||||
}
|
||||
|
||||
static size_t scr_arr_sparse_lower_bound(const ScrArrSparseSlot *slots,
|
||||
size_t n, size_t index) {
|
||||
size_t lo = 0, hi = n;
|
||||
@@ -280,8 +291,10 @@ static void scr_arr_sparse_drop_prefix(ScrArr *a, size_t count) {
|
||||
x->sparse_len -= count;
|
||||
}
|
||||
|
||||
/* Bytes per dense slot: the 8-byte value and its state byte. */
|
||||
/* Bytes per dense slot: the value and its state byte. Boolean arrays
|
||||
* store a one-byte value. */
|
||||
#define SCR_ARR_SLOT_BYTES (sizeof(uint64_t) + sizeof(uint8_t))
|
||||
#define SCR_ARR_BOOL_SLOT_BYTES (2 * sizeof(uint8_t))
|
||||
/* Appends to an array smaller than this grow straight to it. */
|
||||
#define SCR_ARR_MIN_GROWTH ((size_t)4)
|
||||
|
||||
@@ -297,13 +310,15 @@ typedef enum {
|
||||
* literal keeps a 16-byte block rather than reserving 4 slots in a 48-byte
|
||||
* one. Growth keeps its own sequence (4, 8, 16, ...): rounding it as well
|
||||
* moves array storage into other size classes and measured slower. */
|
||||
static size_t scr_arr_block_cap(size_t cap) {
|
||||
static size_t scr_arr_block_cap(size_t cap, bool bools) {
|
||||
#if SCR_SMALL_ALLOC
|
||||
size_t bytes = cap * SCR_ARR_SLOT_BYTES;
|
||||
size_t bytes = cap * (bools ? SCR_ARR_BOOL_SLOT_BYTES : SCR_ARR_SLOT_BYTES);
|
||||
if (bytes <= SCR_SA_MAX) {
|
||||
size_t block = (bytes + SCR_SA_STEP - 1) / SCR_SA_STEP * SCR_SA_STEP;
|
||||
return block / SCR_ARR_SLOT_BYTES;
|
||||
return bools ? block / SCR_ARR_BOOL_SLOT_BYTES : block / SCR_ARR_SLOT_BYTES;
|
||||
}
|
||||
#else
|
||||
(void)bools;
|
||||
#endif
|
||||
return cap;
|
||||
}
|
||||
@@ -317,9 +332,12 @@ static size_t scr_arr_block_cap(size_t cap) {
|
||||
static void scr_arr_grow_dense_to(ScrArr *a, size_t need, ScrArrGrow mode) {
|
||||
if (need <= a->cap) return;
|
||||
if (need > SCR_ARR_MAX_LENGTH) scr_arr_oom();
|
||||
bool bools = a->elem == SCR_ELEM_BOOL;
|
||||
size_t width = scr_arr_slot_width(a);
|
||||
size_t unit = bools ? SCR_ARR_BOOL_SLOT_BYTES : SCR_ARR_SLOT_BYTES;
|
||||
size_t cap;
|
||||
if (mode == SCR_ARR_GROW_EXACT) {
|
||||
cap = scr_arr_block_cap(need);
|
||||
cap = scr_arr_block_cap(need, bools);
|
||||
} else {
|
||||
cap = a->cap < SCR_ARR_MIN_GROWTH ? SCR_ARR_MIN_GROWTH : a->cap;
|
||||
while (cap < need) {
|
||||
@@ -332,13 +350,13 @@ static void scr_arr_grow_dense_to(ScrArr *a, size_t need, ScrArrGrow mode) {
|
||||
* appends benefit from geometric headroom. */
|
||||
if (mode == SCR_ARR_GROW_SPAN && need > SCR_ARR_DENSE_LIMIT) cap = need;
|
||||
}
|
||||
if (cap > SIZE_MAX / SCR_ARR_SLOT_BYTES) scr_arr_oom();
|
||||
if (cap > SIZE_MAX / SCR_ARR_SLOT_BYTES) scr_arr_oom(); /* bounds both slot sizes */
|
||||
if (!a->data && SCR_AX(a)->sparse_len == 0) {
|
||||
/* First storage: nothing to copy, move or absorb. */
|
||||
uint64_t *data = scr_mem_alloc(cap * SCR_ARR_SLOT_BYTES);
|
||||
uint64_t *data = scr_mem_alloc(cap * unit);
|
||||
if (!data) scr_arr_oom();
|
||||
a->data = data;
|
||||
a->present = (uint8_t *)(data + cap);
|
||||
a->present = (uint8_t *)data + cap * width;
|
||||
memset(a->present, SCR_ARR_HOLE, cap);
|
||||
a->cap = cap;
|
||||
return;
|
||||
@@ -346,17 +364,17 @@ static void scr_arr_grow_dense_to(ScrArr *a, size_t need, ScrArrGrow mode) {
|
||||
/* Values and states share one allocation. Move the old states before
|
||||
* publishing the larger capacity: their previous location is now part
|
||||
* of the value area. Existing data and state pointers keep their ABI. */
|
||||
uint64_t *data = scr_mem_realloc(a->data, cap * SCR_ARR_SLOT_BYTES);
|
||||
uint64_t *data = scr_mem_realloc(a->data, cap * unit);
|
||||
if (!data) scr_arr_oom();
|
||||
uint8_t *present = (uint8_t *)(data + cap);
|
||||
if (a->cap) memmove(present, (uint8_t *)(data + a->cap), a->cap);
|
||||
uint8_t *present = (uint8_t *)data + cap * width;
|
||||
if (a->cap) memmove(present, (uint8_t *)data + a->cap * width, a->cap);
|
||||
memset(present + a->cap, SCR_ARR_HOLE, cap - a->cap);
|
||||
a->data = data;
|
||||
a->present = present;
|
||||
a->cap = cap;
|
||||
size_t moved = scr_arr_sparse_lower_bound(SCR_AX(a)->sparse, SCR_AX(a)->sparse_len, cap);
|
||||
for (size_t i = 0; i < moved; i++) {
|
||||
a->data[SCR_AX(a)->sparse[i].index] = SCR_AX(a)->sparse[i].slot;
|
||||
scr_arr_dense_put(a, SCR_AX(a)->sparse[i].index, SCR_AX(a)->sparse[i].slot);
|
||||
a->present[SCR_AX(a)->sparse[i].index] = SCR_AX(a)->sparse[i].state;
|
||||
}
|
||||
scr_arr_sparse_drop_prefix(a, moved);
|
||||
@@ -416,7 +434,7 @@ static uint8_t scr_arr_state_at_storage(const ScrArrStorage *s, size_t index,
|
||||
uint64_t *out) {
|
||||
if (index < s->cap) {
|
||||
if (s->present[index] == SCR_ARR_HOLE) return SCR_ARR_HOLE;
|
||||
if (out) *out = s->data[index];
|
||||
if (out) *out = scr_arr_storage_get(s, index);
|
||||
return s->present[index];
|
||||
}
|
||||
size_t pos = scr_arr_sparse_lower_bound(s->sparse, s->sparse_len, index);
|
||||
@@ -431,14 +449,14 @@ static bool scr_arr_slot_at_storage(const ScrArrStorage *s, size_t index,
|
||||
}
|
||||
|
||||
static bool scr_arr_slot_at(const ScrArr *a, size_t index, uint64_t *out) {
|
||||
ScrArrStorage s = {
|
||||
a->data, a->present, a->cap, SCR_AX(a)->sparse, SCR_AX(a)->sparse_len, SCR_AX(a)->sparse_cap};
|
||||
ScrArrStorage s = {a->data, a->present, a->cap, SCR_AX(a)->sparse,
|
||||
SCR_AX(a)->sparse_len, SCR_AX(a)->sparse_cap, a->elem};
|
||||
return scr_arr_slot_at_storage(&s, index, out);
|
||||
}
|
||||
|
||||
static uint8_t scr_arr_state_at(const ScrArr *a, size_t index, uint64_t *out) {
|
||||
ScrArrStorage s = {
|
||||
a->data, a->present, a->cap, SCR_AX(a)->sparse, SCR_AX(a)->sparse_len, SCR_AX(a)->sparse_cap};
|
||||
ScrArrStorage s = {a->data, a->present, a->cap, SCR_AX(a)->sparse,
|
||||
SCR_AX(a)->sparse_len, SCR_AX(a)->sparse_cap, a->elem};
|
||||
return scr_arr_state_at_storage(&s, index, out);
|
||||
}
|
||||
|
||||
@@ -449,7 +467,7 @@ static void scr_arr_store_state_owned(ScrArr *a, size_t index, uint64_t slot,
|
||||
if (state != SCR_ARR_VALUE) scr_arr_cut(a, index);
|
||||
scr_arr_grow_for_index(a, index);
|
||||
if (index < a->cap) {
|
||||
a->data[index] = slot;
|
||||
scr_arr_dense_put(a, index, slot);
|
||||
a->present[index] = state;
|
||||
return;
|
||||
}
|
||||
@@ -472,9 +490,9 @@ static bool scr_arr_take_state(ScrArr *a, size_t index, uint64_t *out,
|
||||
uint8_t state = a->present[index];
|
||||
if (state == SCR_ARR_HOLE) return false;
|
||||
scr_arr_cut(a, index);
|
||||
*out = a->data[index];
|
||||
*out = scr_arr_dense_get(a, index);
|
||||
a->present[index] = SCR_ARR_HOLE;
|
||||
a->data[index] = 0;
|
||||
scr_arr_dense_put(a, index, 0);
|
||||
if (state_out) *state_out = state;
|
||||
return true;
|
||||
}
|
||||
@@ -498,8 +516,8 @@ static void scr_arr_replace_state_owned(ScrArr *a, size_t index, uint64_t slot,
|
||||
if (state != SCR_ARR_VALUE) scr_arr_cut(a, index);
|
||||
if (index < a->cap) {
|
||||
old_state = a->present[index];
|
||||
if (old_state == SCR_ARR_VALUE) old = a->data[index];
|
||||
a->data[index] = slot;
|
||||
if (old_state == SCR_ARR_VALUE) old = scr_arr_dense_get(a, index);
|
||||
scr_arr_dense_put(a, index, slot);
|
||||
a->present[index] = state;
|
||||
} else {
|
||||
size_t pos = scr_arr_sparse_lower_bound(SCR_AX(a)->sparse, SCR_AX(a)->sparse_len, index);
|
||||
@@ -610,9 +628,8 @@ static bool scr_arr_prop_delete(ScrArr *a, double key) {
|
||||
}
|
||||
|
||||
static ScrArrStorage scr_arr_take_storage(ScrArr *a) {
|
||||
ScrArrStorage old = {
|
||||
a->data, a->present, a->cap, SCR_AX(a)->sparse, SCR_AX(a)->sparse_len,
|
||||
SCR_AX(a)->sparse_cap};
|
||||
ScrArrStorage old = {a->data, a->present, a->cap, SCR_AX(a)->sparse,
|
||||
SCR_AX(a)->sparse_len, SCR_AX(a)->sparse_cap, a->elem};
|
||||
a->data = NULL;
|
||||
a->present = NULL;
|
||||
a->cap = 0;
|
||||
@@ -641,7 +658,7 @@ static bool scr_arr_is_dense(const ScrArr *a) {
|
||||
static void scr_arr_move_dense(ScrArr *a, size_t to, size_t from, size_t count) {
|
||||
if (count == 0) return;
|
||||
scr_arr_cut(a, to);
|
||||
memmove(a->data + to, a->data + from, count * sizeof(*a->data));
|
||||
memmove(scr_arr_dense_at(a, to), scr_arr_dense_at(a, from), count * scr_arr_slot_width(a));
|
||||
memmove(a->present + to, a->present + from, count);
|
||||
}
|
||||
|
||||
@@ -649,24 +666,26 @@ static void scr_arr_clear_dense(ScrArr *a, size_t from, size_t count) {
|
||||
if (count == 0) return;
|
||||
scr_arr_cut(a, from);
|
||||
memset(a->present + from, SCR_ARR_HOLE, count);
|
||||
memset(a->data + from, 0, count * sizeof(*a->data));
|
||||
memset(scr_arr_dense_at(a, from), 0, count * scr_arr_slot_width(a));
|
||||
}
|
||||
|
||||
/* The destination owns no values in this range. Only retain adapters run
|
||||
* while copying references: no release can collect an intermediate copy.
|
||||
* Self-appends use disjoint ranges after reserving capacity. */
|
||||
* Self-appends use disjoint ranges after reserving capacity. Both arrays
|
||||
* hold the same element kind, so their slots have the same width. */
|
||||
static void scr_arr_copy_dense(ScrArr *dst, size_t to, const ScrArr *src,
|
||||
size_t from, size_t count, bool reverse,
|
||||
bool materialize_holes) {
|
||||
if (count == 0) return;
|
||||
scr_arr_cut(dst, to);
|
||||
if (!reverse) {
|
||||
memcpy(dst->data + to, src->data + from, count * sizeof(*dst->data));
|
||||
memcpy(scr_arr_dense_at(dst, to), scr_arr_dense_at(src, from),
|
||||
count * scr_arr_slot_width(dst));
|
||||
memcpy(dst->present + to, src->present + from, count);
|
||||
} else {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
size_t source = from + count - i - 1;
|
||||
memcpy(dst->data + to + i, src->data + source, sizeof(*dst->data));
|
||||
scr_arr_dense_put(dst, to + i, scr_arr_dense_get(src, source));
|
||||
dst->present[to + i] = src->present[source];
|
||||
}
|
||||
}
|
||||
@@ -676,12 +695,21 @@ static void scr_arr_copy_dense(ScrArr *dst, size_t to, const ScrArr *src,
|
||||
if (dst->present[i] == SCR_ARR_VALUE) {
|
||||
if (refs) dst->data[i] = scr_elem_retain_slot(src, dst->data[i]);
|
||||
} else if (materialize_holes && dst->present[i] == SCR_ARR_HOLE) {
|
||||
dst->data[i] = 0;
|
||||
scr_arr_dense_put(dst, i, 0);
|
||||
dst->present[i] = SCR_ARR_UNDEFINED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Store `count` 8-byte value slots into dense storage starting at `to`. */
|
||||
static void scr_arr_put_slots(ScrArr *a, size_t to, size_t count, const uint64_t *slots) {
|
||||
if (a->elem != SCR_ELEM_BOOL) {
|
||||
memcpy(a->data + to, slots, count * sizeof(*slots));
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < count; i++) scr_arr_dense_put(a, to + i, slots[i]);
|
||||
}
|
||||
|
||||
ScrArr *scr_arr_new(ScrElemKind elem, size_t initial_cap) {
|
||||
ScrArr *a = scr_mem_alloc(sizeof(ScrArr));
|
||||
if (!a) scr_arr_oom();
|
||||
@@ -926,9 +954,10 @@ void scr_arr_set_len(ScrArr *a, double length) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t i = dense_stop; i < dense_end; i++) {
|
||||
a->present[i] = 0;
|
||||
a->data[i] = 0;
|
||||
if (dense_stop < dense_end) {
|
||||
memset(a->present + dense_stop, 0, dense_end - dense_stop);
|
||||
memset(scr_arr_dense_at(a, dense_stop), 0,
|
||||
(dense_end - dense_stop) * scr_arr_slot_width(a));
|
||||
}
|
||||
if (a->ext) a->ext->sparse_len = scr_arr_sparse_lower_bound(a->ext->sparse, a->ext->sparse_len, next);
|
||||
}
|
||||
@@ -1053,7 +1082,8 @@ static ScrArr *scr_arr_fill_slot(ScrArr *a, uint64_t slot, uint8_t state,
|
||||
if (scr_arr_dense_ok(a, until, until - from)) scr_arr_grow_dense(a, until);
|
||||
if (!refs && until <= a->cap) {
|
||||
if (state != SCR_ARR_VALUE) scr_arr_cut(a, from);
|
||||
for (size_t i = from; i < until; i++) a->data[i] = slot;
|
||||
if (a->elem == SCR_ELEM_BOOL) memset(scr_arr_dense_at(a, from), (int)slot, until - from);
|
||||
else for (size_t i = from; i < until; i++) a->data[i] = slot;
|
||||
memset(a->present + from, state, until - from);
|
||||
} else {
|
||||
for (size_t i = from; i < until; i++) {
|
||||
@@ -1241,8 +1271,8 @@ bool scr_arr_index_eq(const ScrArr *a, double i, const ScrArr *b, double j) {
|
||||
bool ap = ai < a->len && a->present[ai] == SCR_ARR_VALUE;
|
||||
bool bp = bi < b->len && b->present[bi] == SCR_ARR_VALUE;
|
||||
if (!ap || !bp) return ap == bp;
|
||||
av = a->data[ai];
|
||||
bv = b->data[bi];
|
||||
av = scr_arr_dense_get(a, ai);
|
||||
bv = scr_arr_dense_get(b, bi);
|
||||
} else {
|
||||
bool ap = scr_arr_read_value(a, i, &av);
|
||||
bool bp = scr_arr_read_value(b, j, &bv);
|
||||
@@ -1352,7 +1382,7 @@ double scr_arr_push_many(ScrArr *a, size_t count, const uint64_t *slots) {
|
||||
if (scr_arr_dense_ok(a, next, count)) {
|
||||
scr_arr_grow_dense(a, next);
|
||||
if (count) {
|
||||
memcpy(a->data + from, slots, count * sizeof(*slots));
|
||||
scr_arr_put_slots(a, from, count, slots);
|
||||
memset(a->present + from, SCR_ARR_VALUE, count);
|
||||
}
|
||||
} else {
|
||||
@@ -1467,7 +1497,7 @@ static double scr_arr_unshift_slot(ScrArr *a, uint64_t slot) {
|
||||
/* The prefix moves up behind the new first value. */
|
||||
size_t f = a->filled;
|
||||
scr_arr_move_dense(a, 1, 0, old_len);
|
||||
a->data[0] = slot;
|
||||
scr_arr_dense_put(a, 0, slot);
|
||||
a->present[0] = SCR_ARR_VALUE;
|
||||
a->len++;
|
||||
a->filled = (uint32_t)(f + 1);
|
||||
@@ -1477,7 +1507,8 @@ static double scr_arr_unshift_slot(ScrArr *a, uint64_t slot) {
|
||||
a->len = 0;
|
||||
scr_arr_store_owned(a, 0, slot);
|
||||
for (size_t i = 0; i < old.cap; i++) {
|
||||
if (old.present[i]) scr_arr_store_state_owned(a, i + 1, old.data[i], old.present[i]);
|
||||
if (old.present[i])
|
||||
scr_arr_store_state_owned(a, i + 1, scr_arr_storage_get(&old, i), old.present[i]);
|
||||
}
|
||||
for (size_t i = 0; i < old.sparse_len; i++) {
|
||||
scr_arr_store_state_owned(a, old.sparse[i].index + 1, old.sparse[i].slot,
|
||||
@@ -1509,7 +1540,7 @@ double scr_arr_unshift_many(ScrArr *a, size_t count, const uint64_t *slots) {
|
||||
scr_arr_grow_dense(a, old_len + count);
|
||||
size_t f = a->filled;
|
||||
scr_arr_move_dense(a, count, 0, old_len);
|
||||
memcpy(a->data, slots, count * sizeof(*slots));
|
||||
scr_arr_put_slots(a, 0, count, slots);
|
||||
memset(a->present, SCR_ARR_VALUE, count);
|
||||
a->len = old_len + count;
|
||||
a->filled = (uint32_t)(f + count);
|
||||
@@ -1519,7 +1550,7 @@ double scr_arr_unshift_many(ScrArr *a, size_t count, const uint64_t *slots) {
|
||||
for (size_t i = 0; i < count; i++) scr_arr_store_owned(a, i, slots[i]);
|
||||
for (size_t i = 0; i < old.cap; i++) {
|
||||
if (old.present[i] != SCR_ARR_HOLE)
|
||||
scr_arr_store_state_owned(a, i + count, old.data[i], old.present[i]);
|
||||
scr_arr_store_state_owned(a, i + count, scr_arr_storage_get(&old, i), old.present[i]);
|
||||
}
|
||||
for (size_t i = 0; i < old.sparse_len; i++) {
|
||||
scr_arr_store_state_owned(a, old.sparse[i].index + count,
|
||||
@@ -1562,7 +1593,7 @@ double scr_arr_unshift_spread(ScrArr *a, const ScrArr *src) {
|
||||
}
|
||||
for (size_t i = 0; i < old.cap; i++) {
|
||||
if (old.present[i] != SCR_ARR_HOLE) {
|
||||
scr_arr_store_state_owned(a, i + add, old.data[i], old.present[i]);
|
||||
scr_arr_store_state_owned(a, i + add, scr_arr_storage_get(&old, i), old.present[i]);
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < old.sparse_len; i++) {
|
||||
@@ -1582,10 +1613,9 @@ ScrArr *scr_arr_reverse(ScrArr *a) {
|
||||
if (a->filled != len) scr_arr_cut(a, 0);
|
||||
for (size_t i = 0; i < len / 2; i++) {
|
||||
size_t j = len - i - 1;
|
||||
uint64_t slot;
|
||||
memcpy(&slot, a->data + i, sizeof slot);
|
||||
memcpy(a->data + i, a->data + j, sizeof slot);
|
||||
memcpy(a->data + j, &slot, sizeof slot);
|
||||
uint64_t slot = scr_arr_dense_get(a, i);
|
||||
scr_arr_dense_put(a, i, scr_arr_dense_get(a, j));
|
||||
scr_arr_dense_put(a, j, slot);
|
||||
uint8_t state = a->present[i];
|
||||
a->present[i] = a->present[j];
|
||||
a->present[j] = state;
|
||||
@@ -1596,7 +1626,8 @@ ScrArr *scr_arr_reverse(ScrArr *a) {
|
||||
ScrArrStorage old = scr_arr_take_storage(a);
|
||||
a->len = len;
|
||||
for (size_t i = 0; i < old.cap; i++) {
|
||||
if (old.present[i]) scr_arr_store_state_owned(a, len - 1 - i, old.data[i], old.present[i]);
|
||||
if (old.present[i])
|
||||
scr_arr_store_state_owned(a, len - 1 - i, scr_arr_storage_get(&old, i), old.present[i]);
|
||||
}
|
||||
for (size_t i = 0; i < old.sparse_len; i++) {
|
||||
scr_arr_store_state_owned(a, len - 1 - old.sparse[i].index,
|
||||
@@ -1645,7 +1676,7 @@ uint8_t scr_arr_shift_state(ScrArr *a, uint64_t *slot_out) {
|
||||
size_t old_len = a->len;
|
||||
if (scr_arr_is_dense(a)) {
|
||||
uint8_t state = a->present[0];
|
||||
if (slot_out && state == SCR_ARR_VALUE) *slot_out = a->data[0];
|
||||
if (slot_out && state == SCR_ARR_VALUE) *slot_out = scr_arr_dense_get(a, 0);
|
||||
/* The prefix slides down with the tail. */
|
||||
size_t f = a->filled;
|
||||
scr_arr_move_dense(a, 0, 1, old_len - 1);
|
||||
@@ -1659,7 +1690,8 @@ uint8_t scr_arr_shift_state(ScrArr *a, uint64_t *slot_out) {
|
||||
uint8_t state = scr_arr_state_at_storage(&old, 0, &s);
|
||||
a->len = 0;
|
||||
for (size_t i = 1; i < old.cap; i++) {
|
||||
if (old.present[i]) scr_arr_store_state_owned(a, i - 1, old.data[i], old.present[i]);
|
||||
if (old.present[i])
|
||||
scr_arr_store_state_owned(a, i - 1, scr_arr_storage_get(&old, i), old.present[i]);
|
||||
}
|
||||
for (size_t i = 0; i < old.sparse_len; i++) {
|
||||
if (old.sparse[i].index > 0) {
|
||||
@@ -1716,7 +1748,7 @@ ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount) {
|
||||
size_t old_len = a->len;
|
||||
if (scr_arr_is_dense(a)) {
|
||||
if (n) {
|
||||
memcpy(out->data, a->data + from, n * sizeof(*a->data));
|
||||
memcpy(out->data, scr_arr_dense_at(a, from), n * scr_arr_slot_width(a));
|
||||
memcpy(out->present, a->present + from, n);
|
||||
}
|
||||
scr_arr_refill(out);
|
||||
@@ -1735,16 +1767,17 @@ ScrArr *scr_arr_splice(ScrArr *a, double start, double deleteCount) {
|
||||
for (size_t i = 0; i < old.cap; i++) {
|
||||
if (old.present[i] == SCR_ARR_HOLE) continue;
|
||||
uint8_t state = old.present[i];
|
||||
uint64_t slot = scr_arr_storage_get(&old, i);
|
||||
size_t dst;
|
||||
if (i < from) {
|
||||
dst = i;
|
||||
scr_arr_store_state_owned(a, dst, old.data[i], state);
|
||||
scr_arr_store_state_owned(a, dst, slot, state);
|
||||
} else if (i >= from + n) {
|
||||
dst = i - n;
|
||||
scr_arr_store_state_owned(a, dst, old.data[i], state);
|
||||
scr_arr_store_state_owned(a, dst, slot, state);
|
||||
} else {
|
||||
dst = i - from;
|
||||
scr_arr_store_state_owned(out, dst, old.data[i], state);
|
||||
scr_arr_store_state_owned(out, dst, slot, state);
|
||||
}
|
||||
}
|
||||
for (size_t j = 0; j < old.sparse_len; j++) {
|
||||
@@ -1790,7 +1823,7 @@ ScrArr *scr_arr_splice_insert(ScrArr *a, double start, double deleteCount,
|
||||
? scr_arr_new_ops(a->ops, n)
|
||||
: scr_arr_new(a->elem, n);
|
||||
if (n) {
|
||||
memcpy(removed->data, a->data + from, n * sizeof(*a->data));
|
||||
memcpy(removed->data, scr_arr_dense_at(a, from), n * scr_arr_slot_width(a));
|
||||
memcpy(removed->present, a->present + from, n);
|
||||
}
|
||||
removed->len = n;
|
||||
@@ -1858,7 +1891,7 @@ void scr_arr_splice_drop(ScrArr *a, double start, double deleteCount, size_t cou
|
||||
bool packed = a->filled == old_len;
|
||||
scr_arr_move_dense(a, from + count, from + n, old_len - from - n);
|
||||
if (count) {
|
||||
memcpy(a->data + from, slots, count * sizeof(*slots));
|
||||
scr_arr_put_slots(a, from, count, slots);
|
||||
memset(a->present + from, SCR_ARR_VALUE, count);
|
||||
}
|
||||
if (next_len < old_len) scr_arr_clear_dense(a, next_len, old_len - next_len);
|
||||
@@ -1927,7 +1960,7 @@ double scr_arr_index_of_f64(ScrArr *a, double v) {
|
||||
|
||||
double scr_arr_index_of_bool(ScrArr *a, bool v) {
|
||||
for (size_t i = 0; i < a->cap && i < a->len; i++) {
|
||||
if (a->present[i] == SCR_ARR_VALUE && ((a->data[i] != 0) == v)) return (double)i;
|
||||
if (a->present[i] == SCR_ARR_VALUE && ((scr_arr_dense_get(a, i) != 0) == v)) return (double)i;
|
||||
}
|
||||
for (size_t i = 0; i < SCR_AX(a)->sparse_len; i++) {
|
||||
if (SCR_AX(a)->sparse[i].state == SCR_ARR_VALUE && (SCR_AX(a)->sparse[i].slot != 0) == v) return (double)SCR_AX(a)->sparse[i].index;
|
||||
@@ -2042,7 +2075,7 @@ ScrArr *scr_arr_slice(ScrArr *a, double start, double end) {
|
||||
if (i < from || a->present[i] == SCR_ARR_HOLE) continue;
|
||||
uint8_t state = a->present[i];
|
||||
scr_arr_store_state_owned(out, i - from,
|
||||
scr_arr_retain_state_slot(a, a->data[i], state),
|
||||
scr_arr_retain_state_slot(a, scr_arr_dense_get(a, i), state),
|
||||
state);
|
||||
}
|
||||
for (size_t i = 0; i < SCR_AX(a)->sparse_len; i++) {
|
||||
@@ -2179,7 +2212,7 @@ ScrArr *scr_arr_sort_primitive(ScrArr *a, bool copy) {
|
||||
if (count && !values) scr_arr_oom();
|
||||
size_t used = 0;
|
||||
for (size_t i = 0; i < dense; i++)
|
||||
if (a->present[i] == SCR_ARR_VALUE) values[used++] = a->data[i];
|
||||
if (a->present[i] == SCR_ARR_VALUE) values[used++] = scr_arr_dense_get(a, i);
|
||||
for (size_t i = 0; i < SCR_AX(a)->sparse_len; i++)
|
||||
if (SCR_AX(a)->sparse[i].state == SCR_ARR_VALUE) values[used++] = SCR_AX(a)->sparse[i].slot;
|
||||
ScrNumberSortKey *numbers = NULL;
|
||||
@@ -2203,12 +2236,13 @@ ScrArr *scr_arr_sort_primitive(ScrArr *a, bool copy) {
|
||||
ScrArr *out = copy ? scr_arr_new(a->elem, present) : a;
|
||||
if (copy) out->len = a->len;
|
||||
if (!copy && scr_arr_is_dense(a)) {
|
||||
if (count) memcpy(a->data, values, count * sizeof(*values));
|
||||
if (count) scr_arr_put_slots(a, 0, count, values);
|
||||
scr_arr_cut(a, count);
|
||||
memset(a->present, SCR_ARR_VALUE, count);
|
||||
memset(a->present + count, SCR_ARR_UNDEFINED, undefined_count);
|
||||
scr_arr_clear_dense(a, count + undefined_count, a->len - count - undefined_count);
|
||||
if (undefined_count) memset(a->data + count, 0, undefined_count * sizeof(*a->data));
|
||||
if (undefined_count)
|
||||
memset(scr_arr_dense_at(a, count), 0, undefined_count * scr_arr_slot_width(a));
|
||||
} else {
|
||||
ScrArrStorage old = {0};
|
||||
if (!copy) old = scr_arr_take_storage(a);
|
||||
@@ -2457,7 +2491,7 @@ void scr_arr_sort_values(ScrArr *a, double count, ScrClosure *f, uint32_t arity)
|
||||
/* A permutation of the same owned slots: no reference count changes. */
|
||||
for (size_t i = 0; sorted && i < n; i++) {
|
||||
if (i < a->cap) {
|
||||
a->data[i] = src[i];
|
||||
scr_arr_dense_put(a, i, src[i]);
|
||||
} else {
|
||||
size_t pos = scr_arr_sparse_lower_bound(SCR_AX(a)->sparse, SCR_AX(a)->sparse_len, i);
|
||||
SCR_AX(a)->sparse[pos].slot = src[i];
|
||||
|
||||
@@ -126,7 +126,7 @@ static void scr_enable_utf8_console_output(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void scr_runtime_abi_v17(void) {}
|
||||
void scr_runtime_abi_v18(void) {}
|
||||
|
||||
void scr_init(void) {
|
||||
#ifdef SCR_WORKERS
|
||||
|
||||
@@ -7625,7 +7625,7 @@ ScrArr *scr_set_to_arr_bool(const ScrMap *s) {
|
||||
if (scr_set_to_arr_sparse(s, out)) return out;
|
||||
for (size_t i = 0; i < s->nentries; i++) {
|
||||
if (!s->entries[i].hash) continue;
|
||||
out->data[out->len] = s->entries[i].key;
|
||||
scr_arr_dense_put(out, out->len, s->entries[i].key != 0);
|
||||
out->present[out->len++] = SCR_ARR_VALUE;
|
||||
}
|
||||
return out;
|
||||
|
||||
@@ -1462,7 +1462,7 @@ void scr_set_add_all(ScrMap *set, ScrArr *values) {
|
||||
* Avoid taking a temporary owner only to release it after insertion.
|
||||
* Sparse/missing positions retain the existing accessor semantics. */
|
||||
if (i < values->cap && values->present[i] == SCR_ARR_VALUE) {
|
||||
uint64_t key = values->data[i];
|
||||
uint64_t key = scr_arr_dense_get(values, i);
|
||||
if (set->key_kind == SCR_MAP_KEY_F64) key = scr_map_f64_bits(scr_map_slot_to_f64(key));
|
||||
scr_map_set(set, scr_map_hash_key(set, key), key, 0);
|
||||
} else if (values->elem == SCR_ELEM_STR) {
|
||||
|
||||
@@ -74,7 +74,7 @@ void scr_init(void);
|
||||
/* Program objects emitted by the bundled LLVM helper reference this symbol.
|
||||
* Its versioned spelling makes a mismatched manual runtime link fail before
|
||||
* the program can start. */
|
||||
void scr_runtime_abi_v17(void);
|
||||
void scr_runtime_abi_v18(void);
|
||||
void scr_runtime_workers_v8(void);
|
||||
|
||||
/* ── the trap funnel (scr_console.c; scr_library.c under -DSCR_LIB) ──────
|
||||
@@ -1536,8 +1536,9 @@ ScrStr *scr_b64_missing_arg(void);
|
||||
|
||||
/* ── arrays ─────────────────────────────────────────────────────────
|
||||
* Monomorphic, growable, refcounted. Elements live in 8-byte slots
|
||||
* (doubles/bools/pointers via memcpy and casts); `elem` records what the
|
||||
* slots hold so the array can release its own reference elements — ScrStr's
|
||||
* (doubles/pointers via memcpy and casts), except SCR_ELEM_BOOL, whose
|
||||
* dense storage holds one byte (0 or 1) per element; `elem` records what
|
||||
* the slots hold so the array can release its own reference elements — ScrStr's
|
||||
* layout is untouched (interned-literal statics depend on it), so the
|
||||
* element kind lives here instead of in a shared object header. A separate
|
||||
* presence bitset distinguishes a hole from a present `undefined`-carrying
|
||||
@@ -1614,6 +1615,9 @@ typedef struct ScrArr {
|
||||
uint32_t filled;
|
||||
/* SCR_ELEM_REF only; NULL for every other element kind. */
|
||||
const ScrArrOps *ops;
|
||||
/* Dense values: `cap` 8-byte slots, or `cap` bytes for SCR_ELEM_BOOL
|
||||
* (scr_arr_slot_width). Sparse entries and properties keep 8-byte slots
|
||||
* for every kind; a bool slot is 0 or 1 wherever it lives. */
|
||||
uint64_t *data;
|
||||
uint8_t *present; /* one state per dense slot; heap arrays share data allocation */
|
||||
/* Rarely used storage (sparse indices, noncanonical numeric properties,
|
||||
@@ -1642,6 +1646,22 @@ _Static_assert(offsetof(ScrArr, filled) == 3 * sizeof(size_t) + 4,
|
||||
_Static_assert(offsetof(ScrArr, data) == 3 * sizeof(size_t) + 8 + sizeof(void *),
|
||||
"inline element reads use ScrArr.data as %ScrArr field 8");
|
||||
|
||||
/* Bytes per dense value slot. */
|
||||
static inline size_t scr_arr_slot_width(const ScrArr *a) {
|
||||
return a->elem == SCR_ELEM_BOOL ? 1 : sizeof(uint64_t);
|
||||
}
|
||||
|
||||
/* The dense value at `index` (< cap) as an 8-byte slot, and its store. */
|
||||
static inline uint64_t scr_arr_dense_get(const ScrArr *a, size_t index) {
|
||||
if (a->elem == SCR_ELEM_BOOL) return ((const uint8_t *)a->data)[index];
|
||||
return a->data[index];
|
||||
}
|
||||
|
||||
static inline void scr_arr_dense_put(ScrArr *a, size_t index, uint64_t slot) {
|
||||
if (a->elem == SCR_ELEM_BOOL) ((uint8_t *)a->data)[index] = (uint8_t)slot;
|
||||
else a->data[index] = slot;
|
||||
}
|
||||
|
||||
/* Lower the hole-free prefix to at most `index`: call before storing a
|
||||
* non-value state at `index`, or before moving states into [index, ...). */
|
||||
static inline void scr_arr_cut(ScrArr *a, size_t index) {
|
||||
|
||||
@@ -1365,6 +1365,107 @@ static void test_filled_prefix(void) {
|
||||
scr_arr_release(r);
|
||||
}
|
||||
|
||||
/* Boolean arrays store one byte per dense element, followed by the states in
|
||||
* the same allocation. Growth moves the states past the larger value area,
|
||||
* densifying absorbs sparse entries into bytes, and every moving and copying
|
||||
* operation keeps values, holes and present undefined in place. */
|
||||
static bool bool_layout_ok(const ScrArr *a) {
|
||||
return a->cap == 0 || a->present == (const uint8_t *)a->data + a->cap;
|
||||
}
|
||||
|
||||
static bool bool_states(const ScrArr *a, const char *want) {
|
||||
if (scr_arr_len(a) != (double)strlen(want)) return false;
|
||||
for (size_t i = 0; want[i]; i++) {
|
||||
double state = scr_arr_state(a, (double)i);
|
||||
char got = state == SCR_ARR_HOLE ? '_'
|
||||
: state == SCR_ARR_UNDEFINED ? 'u'
|
||||
: scr_arr_get_bool((ScrArr *)a, (double)i) ? 't'
|
||||
: 'f';
|
||||
if (got != want[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void test_bool_bytes(void) {
|
||||
ScrArr *a = scr_arr_new(SCR_ELEM_BOOL, 0);
|
||||
for (int i = 0; i < 1000; i++) scr_arr_push_bool(a, i % 3 == 0);
|
||||
check(bool_layout_ok(a) && a->filled == 1000, "bool pushes grow one-byte storage");
|
||||
bool values_ok = true;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
values_ok &= ((const uint8_t *)a->data)[i] == (i % 3 == 0);
|
||||
values_ok &= scr_arr_get_bool(a, i) == (i % 3 == 0);
|
||||
}
|
||||
check(values_ok, "bool values are stored as 0/1 bytes");
|
||||
check(scr_arr_index_of_bool(a, false) == 1 && scr_arr_includes_bool(a, true),
|
||||
"bool search reads bytes");
|
||||
scr_arr_set_len(a, 5);
|
||||
scr_arr_set_len(a, 8);
|
||||
scr_arr_set_undefined(a, 6);
|
||||
check(bool_layout_ok(a) && bool_states(a, "tfftf_u_"), "bool truncation clears bytes");
|
||||
|
||||
ScrArr *s = scr_arr_new(SCR_ELEM_BOOL, 0);
|
||||
scr_arr_set_bool(s, 0, true);
|
||||
scr_arr_set_bool(s, 3000000, true);
|
||||
scr_arr_set_bool(s, 2000000, false);
|
||||
check(arr_sparse_len(s) == 2 && !scr_arr_get_bool(s, 2000000) && scr_arr_get_bool(s, 3000000),
|
||||
"bool sparse entries keep their values");
|
||||
scr_arr_set_len(s, 2000001);
|
||||
scr_arr_release(scr_arr_fill_bool(s, true, 1, 2000000));
|
||||
check(arr_sparse_len(s) == 0 && bool_layout_ok(s) && !scr_arr_get_bool(s, 2000000) &&
|
||||
scr_arr_get_bool(s, 1999999) && scr_arr_index_of_bool(s, false) == 2000000,
|
||||
"densifying absorbs sparse bool entries into bytes");
|
||||
scr_arr_release(s);
|
||||
|
||||
ScrArr *m = scr_arr_new(SCR_ELEM_BOOL, 0);
|
||||
uint64_t slots[3] = {1, 0, 1};
|
||||
scr_arr_push_many(m, 3, slots);
|
||||
scr_arr_unshift_many(m, 2, slots + 1);
|
||||
check(bool_states(m, "fttft"), "slot buffers narrow to bytes");
|
||||
scr_arr_unshift_bool(m, false);
|
||||
scr_arr_set_undefined(m, 2);
|
||||
scr_arr_delete(m, 4);
|
||||
check(bool_states(m, "ffut_t"), "unshift moves bytes and states together");
|
||||
ScrArr *removed = scr_arr_splice(m, 1, 2);
|
||||
check(bool_states(removed, "fu") && bool_states(m, "ft_t"), "splice moves bytes out");
|
||||
scr_arr_release(removed);
|
||||
ScrArr *items = scr_arr_new(SCR_ELEM_BOOL, 0);
|
||||
scr_arr_push_bool(items, true);
|
||||
scr_arr_push_bool(items, false);
|
||||
scr_arr_release(scr_arr_splice_insert(m, 1, 1, items));
|
||||
check(bool_states(m, "ftf_t"), "splice insertion copies bytes in");
|
||||
scr_arr_splice_drop(m, 0, 1, 2, slots);
|
||||
check(bool_states(m, "tftf_t"), "statement splice stores bytes");
|
||||
scr_arr_release(scr_arr_reverse(m));
|
||||
check(bool_states(m, "t_ftft"), "reverse swaps bytes");
|
||||
ScrArr *copy = scr_arr_slice(m, 1, INFINITY);
|
||||
check(bool_states(copy, "_ftft") && bool_layout_ok(copy), "slice copies bytes");
|
||||
scr_arr_release(copy);
|
||||
copy = scr_arr_to_reversed(m);
|
||||
check(bool_states(copy, "tftfut"), "toReversed copies bytes and materializes holes");
|
||||
scr_arr_release(copy);
|
||||
scr_arr_concat_copy(m, items);
|
||||
scr_arr_push_spread(m, m);
|
||||
check(bool_states(m, "t_ftfttftuftfttf"), "concat keeps holes; self spread reads them as undefined");
|
||||
scr_arr_release(scr_arr_copy_within(m, 0, 2, 5));
|
||||
check(scr_arr_index_eq(m, 0, items, 1) && scr_arr_index_eq(m, 1, items, 0),
|
||||
"copyWithin moves bytes and index equality reads them");
|
||||
check(scr_arr_shift_bool(m) == false && scr_arr_pop_bool(m) == false, "shift and pop read bytes");
|
||||
scr_arr_release(scr_arr_sort_primitive(m, false));
|
||||
bool sorted = true;
|
||||
for (double i = 1; i < scr_arr_len(m); i++)
|
||||
if (scr_arr_state(m, i) == SCR_ARR_VALUE && scr_arr_state(m, i - 1) == SCR_ARR_VALUE)
|
||||
sorted &= scr_arr_get_bool(m, i - 1) <= scr_arr_get_bool(m, i);
|
||||
check(sorted, "default sort orders bytes");
|
||||
ScrStr *sep = scr_str_new(",", 1);
|
||||
ScrStr *joined = scr_arr_join(items, sep);
|
||||
check(strcmp(joined->data, "true,false") == 0, "join reads bytes");
|
||||
scr_str_release(joined);
|
||||
scr_str_release(sep);
|
||||
scr_arr_release(items);
|
||||
scr_arr_release(m);
|
||||
scr_arr_release(a);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1) {
|
||||
ScrArr *a = scr_arr_new(SCR_ELEM_F64, 0);
|
||||
@@ -1410,6 +1511,7 @@ int main(int argc, char **argv) {
|
||||
test_bulk_reference_ownership();
|
||||
test_splice_drop();
|
||||
test_filled_prefix();
|
||||
test_bool_bytes();
|
||||
|
||||
fprintf(stderr, "%ld/%ld cases passed\n", total - failed, total);
|
||||
return failed == 0 ? 0 : 1;
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
// boolean[] keeps one byte per element in dense storage. Every array
|
||||
// operation reads and writes those bytes: element access below and past the
|
||||
// hole-free prefix, growth and truncation, the moving operations (push, pop,
|
||||
// shift, unshift, splice, reverse, copyWithin), the copying ones (slice,
|
||||
// concat, spread, toReversed, toSpliced, with), fill, sort, search, join,
|
||||
// iteration and JSON. Holes and present undefined stay distinct throughout.
|
||||
function show(label: string, xs: boolean[]): void {
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < xs.length; i++) parts.push(i in xs ? String(xs[i]) : "_");
|
||||
let visits = 0;
|
||||
let trues = 0;
|
||||
xs.forEach((x) => {
|
||||
visits++;
|
||||
if (x) trues++;
|
||||
});
|
||||
console.log(label, xs.length, parts.join(","), "visits", visits, "trues", trues);
|
||||
}
|
||||
|
||||
function showMaybe(label: string, xs: (boolean | undefined)[]): void {
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < xs.length; i++) parts.push(i in xs ? String(xs[i]) : "_");
|
||||
let visits = 0;
|
||||
xs.forEach(() => visits++);
|
||||
console.log(label, xs.length, parts.join(","), "visits", visits);
|
||||
}
|
||||
|
||||
// A hole at `at` without `delete`: copying a hole deletes the target.
|
||||
function punch(xs: boolean[], at: number): void {
|
||||
const n = xs.length;
|
||||
xs.length = n + 1;
|
||||
xs.copyWithin(at, n, n + 1);
|
||||
xs.length = n;
|
||||
}
|
||||
|
||||
// Element writes inside, at and past the length.
|
||||
const a: boolean[] = new Array<boolean>(6).fill(false);
|
||||
show("fill", a);
|
||||
a[1] = true;
|
||||
a[4] = true;
|
||||
show("write", a);
|
||||
a[8] = true;
|
||||
show("write-past", a);
|
||||
console.log("in", 6 in a, 7 in a, 8 in a, a[7]);
|
||||
a[6] = false;
|
||||
a[7] = true;
|
||||
show("close-gap", a);
|
||||
a.length = 3;
|
||||
show("truncate", a);
|
||||
a.length = 5;
|
||||
show("regrow", a);
|
||||
a[3] = true;
|
||||
a[4] = false;
|
||||
show("rewrite", a);
|
||||
|
||||
// Moving operations.
|
||||
const b: boolean[] = [true, false, true, true, false, false, true];
|
||||
punch(b, 2);
|
||||
show("punch", b);
|
||||
console.log("push", b.push(true), b.push(false, true));
|
||||
show("pushed", b);
|
||||
console.log("pop", b.pop(), b.pop(), b.length);
|
||||
console.log("shift", b.shift(), b.shift(), b.length);
|
||||
show("shifted", b);
|
||||
console.log("unshift", b.unshift(false), b.unshift(true, true));
|
||||
show("unshifted", b);
|
||||
console.log("splice", b.splice(1, 2).join(","));
|
||||
show("spliced", b);
|
||||
console.log("splice-insert", b.splice(2, 1, true, false, false).join(","));
|
||||
show("inserted", b);
|
||||
b.splice(0, 2, false);
|
||||
show("splice-statement", b);
|
||||
b.reverse();
|
||||
show("reverse", b);
|
||||
b.copyWithin(0, 3, 6);
|
||||
show("copyWithin", b);
|
||||
const holey: boolean[] = [true, false, true, false];
|
||||
punch(holey, 1);
|
||||
holey.reverse();
|
||||
show("reverse-holes", holey);
|
||||
console.log("shift-hole", holey.shift(), holey.shift(), holey.length);
|
||||
show("after-shift-hole", holey);
|
||||
|
||||
// Copying operations preserve holes, except where iteration reads them.
|
||||
const c: boolean[] = [false, true, false, true, true];
|
||||
punch(c, 3);
|
||||
show("source", c);
|
||||
show("slice", c.slice(1, 5));
|
||||
show("slice-neg", c.slice(-3));
|
||||
show("concat", c.concat([true], c));
|
||||
show("spread", [...c, false]);
|
||||
show("toReversed", c.toReversed());
|
||||
show("toSpliced", c.toSpliced(1, 2, true));
|
||||
show("with", c.with(0, true));
|
||||
show("from", Array.from(c));
|
||||
const d: boolean[] = [];
|
||||
d.push(...c);
|
||||
show("push-spread", d);
|
||||
d.unshift(...c);
|
||||
show("unshift-spread", d);
|
||||
|
||||
// fill, sort and search.
|
||||
const e: boolean[] = new Array<boolean>(10).fill(true, 0, 10);
|
||||
e.fill(false, 2, 5);
|
||||
e.fill(false, -2);
|
||||
show("fill-range", e);
|
||||
show("toSorted", e.toSorted());
|
||||
e.sort();
|
||||
show("sort", e);
|
||||
const f: boolean[] = [true, false, false, true];
|
||||
f.sort((x, y) => Number(y) - Number(x));
|
||||
show("sort-cmp", f);
|
||||
console.log("indexOf", e.indexOf(true), e.indexOf(false), e.lastIndexOf(false), [true].indexOf(false));
|
||||
console.log("includes", e.includes(true), [false, false].includes(true));
|
||||
console.log("find", e.find((x) => x), e.findIndex((x) => x), e.findLast((x) => !x), e.findLastIndex((x) => !x));
|
||||
console.log("some/every", e.some((x) => x), e.every((x) => x), [true, true].every((x) => x));
|
||||
console.log("count", e.filter((x) => x).length, e.map((x) => (x ? 1 : 0)).join(""));
|
||||
console.log("reduce", e.reduce((n, x) => (x ? n + 1 : n), 0));
|
||||
|
||||
// Strings, iteration and JSON.
|
||||
const g: boolean[] = [true, false, true];
|
||||
punch(g, 1);
|
||||
console.log("join", g.join(), g.join(" | "), String(g), `${g}`);
|
||||
const seen: string[] = [];
|
||||
for (const x of g) seen.push(String(x));
|
||||
for (const [i, x] of g.entries()) seen.push(`${i}:${x}`);
|
||||
for (const i of g.keys()) seen.push(`k${i}`);
|
||||
console.log("iterate", seen.join(" "));
|
||||
console.log("json", JSON.stringify(g), JSON.stringify([true, false]), JSON.stringify({ flags: [false, true] }));
|
||||
const parsed: boolean[] = JSON.parse("[true,false,false,true]") as boolean[];
|
||||
parsed.push(true);
|
||||
console.log("parsed", parsed.length, parsed[3], parsed.join(","));
|
||||
console.log("inspect", [true, false], g);
|
||||
|
||||
// Sets of booleans in both directions.
|
||||
const fromArray = new Set<boolean>([true, true, false, true]);
|
||||
console.log("set", fromArray.size, fromArray.has(false));
|
||||
const back: boolean[] = [...fromArray];
|
||||
back.push(false);
|
||||
show("set-spread", back);
|
||||
show("set-from", Array.from(new Set<boolean>([false, true])));
|
||||
|
||||
// Nested arrays and flat.
|
||||
const grid: boolean[][] = [[true, false], [false], [true, true, false]];
|
||||
grid[1]![3] = true;
|
||||
show("flat", grid.flat());
|
||||
console.log("grid", grid.map((row) => row.join("")).join("/"));
|
||||
|
||||
// Present undefined stays distinct from a hole.
|
||||
const m: (boolean | undefined)[] = [true, undefined, false];
|
||||
m[4] = true;
|
||||
showMaybe("maybe", m);
|
||||
m.splice(1, 0, undefined, false);
|
||||
showMaybe("maybe-splice", m);
|
||||
showMaybe("maybe-slice", m.slice(1));
|
||||
showMaybe("maybe-reverse", m.reverse());
|
||||
console.log("maybe-includes", m.includes(undefined), m.indexOf(undefined), m.indexOf(false));
|
||||
console.log("maybe-json", JSON.stringify(m));
|
||||
const n: (boolean | undefined)[] = new Array<boolean | undefined>(4).fill(undefined);
|
||||
n[2] = true;
|
||||
showMaybe("maybe-fill", n);
|
||||
|
||||
// Growth across allocation sizes, large sparse indices and densifying.
|
||||
const big: boolean[] = [];
|
||||
for (let i = 0; i < 70000; i++) big.push(i % 3 === 0);
|
||||
let bigTrues = 0;
|
||||
for (let i = 0; i < big.length; i++) if (big[i]) bigTrues++;
|
||||
console.log("big", big.length, bigTrues, big[69999], big.lastIndexOf(true));
|
||||
const sparse: boolean[] = [true];
|
||||
sparse[3000000] = true;
|
||||
sparse[2000000] = false;
|
||||
console.log("sparse", sparse.length, 1 in sparse, sparse[2000000], sparse[3000000], sparse.indexOf(false));
|
||||
for (let i = 1; i < 200; i++) sparse[i] = i % 2 === 0;
|
||||
console.log("sparse-dense", sparse.slice(0, 6).join(","), sparse.indexOf(false), sparse.includes(false));
|
||||
sparse.length = 4;
|
||||
show("sparse-truncate", sparse);
|
||||
const sieve: boolean[] = new Array<boolean>(100001).fill(false);
|
||||
let primes = 0;
|
||||
for (let i = 2; i <= 100000; i++) {
|
||||
if (sieve[i]) continue;
|
||||
primes++;
|
||||
for (let j = i * i; j <= 100000; j += i) sieve[j] = true;
|
||||
}
|
||||
console.log("sieve", primes, sieve.indexOf(false, 100), sieve.lastIndexOf(false));
|
||||
@@ -0,0 +1,23 @@
|
||||
// A boolean[] seen through unknown and any: reads, writes and growth through
|
||||
// the dynamic view land in the same one-byte element storage, and JSON,
|
||||
// structuredClone, deep equality and inspect read it back.
|
||||
import { deepStrictEqual } from "node:assert";
|
||||
import { inspect, isDeepStrictEqual } from "node:util";
|
||||
const flags: boolean[] = [true, false, true];
|
||||
flags[5] = false;
|
||||
const view: unknown = flags;
|
||||
console.log(Array.isArray(view), JSON.stringify(view));
|
||||
const anyView = view as any;
|
||||
console.log(anyView[0], anyView[1], anyView[3], anyView.length);
|
||||
anyView[1] = true;
|
||||
anyView[3] = true;
|
||||
console.log(flags[1], flags[3], 4 in flags, flags.length);
|
||||
const cloned = structuredClone(flags);
|
||||
console.log(cloned.length, cloned.join(","), isDeepStrictEqual(cloned, flags));
|
||||
deepStrictEqual([true, false], [true, false]);
|
||||
console.log(inspect(flags), inspect([false]));
|
||||
const back = view as boolean[];
|
||||
back.push(true);
|
||||
console.log(flags.length, flags[6]);
|
||||
const rec: Record<string, unknown> = { flags };
|
||||
console.log(JSON.stringify(rec));
|
||||
@@ -96,7 +96,7 @@ for (const backend of ["llvm"] as const) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 17, marker: "scr_runtime_abi_v17" },
|
||||
runtime_abi: { version: 18, marker: "scr_runtime_abi_v18" },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: [], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
|
||||
Reference in New Issue
Block a user