mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-28 06:53:42 +08:00
Copy typed array sets of the same element kind on shared memory as bytes (#2094)
This commit is contained in:
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8106-shared-typed-array-set.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/8107-shared-typed-array-set-workers.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -755,12 +755,29 @@ void scr_bytes_set_from(ScrBytes *dst, const ScrBytes *src, double offset) {
|
||||
}
|
||||
size_t width = scr_bytes_elem_size(dst->elem);
|
||||
if (dst->shared || src->shared) {
|
||||
/* Convert a private copy of the source, then publish the target range. */
|
||||
SCR_BYTES_SNAPSHOT(src);
|
||||
size_t bytes = src->len * width;
|
||||
uint8_t *target = dst->data + (size_t)t * width;
|
||||
if (dst->elem == src->elem) {
|
||||
/* The same element kind copies the source's bytes, NaN payloads
|
||||
* included. A source range that overlaps the target range (views of
|
||||
* one buffer, where no snapshot was taken) is copied out first. */
|
||||
uintptr_t to = (uintptr_t)target, from = (uintptr_t)src->data;
|
||||
bool overlap = to <= from ? from - to < bytes : to - from < bytes;
|
||||
if (!overlap) {
|
||||
scr_bytes_raw_out(dst, target, src->data, bytes);
|
||||
return;
|
||||
}
|
||||
uint8_t *copy = scr_bytes_scratch(bytes);
|
||||
scr_bytes_raw_in(src, copy, src->data, bytes);
|
||||
scr_bytes_raw_out(dst, target, copy, bytes);
|
||||
free(copy);
|
||||
return;
|
||||
}
|
||||
/* Convert a private copy of the source, then publish the target range. */
|
||||
uint8_t *scratch = scr_bytes_scratch(bytes);
|
||||
scr_bytes_convert_span(scratch, dst->elem, src->data, src->elem, src->len);
|
||||
scr_bytes_raw_out(dst, dst->data + (size_t)t * width, scratch, bytes);
|
||||
scr_bytes_raw_out(dst, target, scratch, bytes);
|
||||
free(scratch);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,52 @@ static void external_set(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Same-kind sets into and out of shared storage copy raw bytes, NaN
|
||||
* payloads included, also between overlapping views of one shared buffer. */
|
||||
static void shared_set(void) {
|
||||
enum { COUNT = 37 };
|
||||
for (ScrBytesElem elem = SCR_BYTES_U8; elem <= SCR_BYTES_F16; elem++) {
|
||||
size_t width = scr_bytes_elem_size(elem), bytes = COUNT * width;
|
||||
uint8_t pattern[COUNT * 8], expected[COUNT * 8];
|
||||
for (size_t i = 0; i < bytes; i++) pattern[i] = (uint8_t)(i * 37 + 0x7f);
|
||||
/* Signaling NaNs, which a conversion through double would quiet. */
|
||||
const uint16_t nan16 = 0x7d01;
|
||||
const uint32_t nan32 = 0x7fa00001;
|
||||
const uint64_t nan64 = UINT64_C(0x7ff4000000000001);
|
||||
for (size_t i = 1; i < COUNT; i += 6) {
|
||||
if (elem == SCR_BYTES_F16) memcpy(pattern + i * width, &nan16, width);
|
||||
if (elem == SCR_BYTES_F32) memcpy(pattern + i * width, &nan32, width);
|
||||
if (elem == SCR_BYTES_F64) memcpy(pattern + i * width, &nan64, width);
|
||||
}
|
||||
for (int direction = 0; direction < 2; direction++) {
|
||||
ScrBytes *shared = scr_bytes_new(elem, COUNT);
|
||||
scr_bytes_make_shared(shared);
|
||||
memcpy(shared->data, pattern, bytes);
|
||||
ScrBytes *src = direction ? scr_bytes_subarray(shared, 3, COUNT) : scr_bytes_subarray(shared, 0, COUNT - 3);
|
||||
double offset = direction ? 0 : 3;
|
||||
memcpy(expected, pattern, bytes);
|
||||
memmove(expected + (size_t)offset * width, pattern + (direction ? 3 : 0) * width, (COUNT - 3) * width);
|
||||
scr_bytes_set_from(shared, src, offset);
|
||||
assert(!scr_exc_pending());
|
||||
assert(memcmp(shared->data, expected, bytes) == 0);
|
||||
scr_bytes_release(src);
|
||||
|
||||
ScrBytes *local = scr_bytes_new(elem, COUNT);
|
||||
memcpy(local->data, pattern, bytes);
|
||||
ScrBytes *head = scr_bytes_subarray(local, 0, COUNT - 1);
|
||||
scr_bytes_set_from(shared, head, 1);
|
||||
memcpy(expected + width, pattern, bytes - width);
|
||||
assert(memcmp(shared->data, expected, bytes) == 0);
|
||||
memset(local->data, 0, bytes);
|
||||
scr_bytes_set_from(local, shared, 0);
|
||||
assert(memcmp(local->data, expected, bytes) == 0);
|
||||
scr_bytes_release(head);
|
||||
scr_bytes_release(local);
|
||||
scr_bytes_release(shared);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void copied_storage(void) {
|
||||
/* Same-kind copies preserve NaN payloads, signed zero and ownership. */
|
||||
const uint64_t bits[] = {UINT64_C(0x7ff8000000000042), UINT64_C(0x8000000000000000),
|
||||
@@ -79,6 +125,7 @@ static void filled_storage(void) {
|
||||
void scr_bytes_test_bulk_storage(void) {
|
||||
long before = scr_bytes_live_count();
|
||||
external_set();
|
||||
shared_set();
|
||||
copied_storage();
|
||||
filled_storage();
|
||||
assert(scr_bytes_live_count() == before);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// TypedArray.prototype.set where the source, the target or both are views of
|
||||
// a SharedArrayBuffer: same element kinds (bytes copied as is, NaN payloads
|
||||
// included), different kinds (converted per element), offsets, overlapping
|
||||
// ranges of one buffer, Buffer targets and out-of-bounds offsets.
|
||||
function bits(label: string, buffer: ArrayBufferLike): void {
|
||||
const words = new Uint32Array(buffer);
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < words.length; i++) out.push(words[i]!.toString(16));
|
||||
console.log(label, out.join(","));
|
||||
}
|
||||
|
||||
const shared = new SharedArrayBuffer(32);
|
||||
const bytes = new Uint8Array(shared);
|
||||
|
||||
// Private source into a shared target, then shared into shared and shared into private.
|
||||
bytes.set(new Uint8Array([1, 2, 3, 4, 5]), 2);
|
||||
console.log("private into shared", bytes.subarray(0, 8).join(","));
|
||||
const other = new Uint8Array(new SharedArrayBuffer(8));
|
||||
other.set(bytes.subarray(2, 7));
|
||||
console.log("shared into shared", other.join(","));
|
||||
const local = new Uint8Array(12);
|
||||
local.set(other, 3);
|
||||
console.log("shared into private", local.join(","));
|
||||
|
||||
// Overlapping ranges of the same buffer, forward and backward.
|
||||
const overlap = new Uint8Array(new SharedArrayBuffer(10));
|
||||
overlap.set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
||||
overlap.set(overlap.subarray(0, 6), 3);
|
||||
console.log("overlap forward", overlap.join(","));
|
||||
overlap.set(overlap.subarray(4, 10), 1);
|
||||
console.log("overlap backward", overlap.join(","));
|
||||
|
||||
// Same kind, wider elements: a signaling NaN keeps its bits.
|
||||
const floatBits = new Uint32Array(new SharedArrayBuffer(8));
|
||||
floatBits[0] = 0x7fa00001;
|
||||
floatBits[1] = 0xffc00002;
|
||||
const floatsShared = new Float32Array(floatBits.buffer);
|
||||
const floatsLocal = new Float32Array(2);
|
||||
floatsLocal.set(floatsShared);
|
||||
bits("f32 shared into private", floatsLocal.buffer);
|
||||
const floatsBack = new Float32Array(new SharedArrayBuffer(8));
|
||||
floatsBack.set(floatsLocal);
|
||||
bits("f32 private into shared", floatsBack.buffer);
|
||||
|
||||
const doubleBits = new Uint32Array(new SharedArrayBuffer(16));
|
||||
doubleBits[0] = 5;
|
||||
doubleBits[1] = 0x7ff40000;
|
||||
doubleBits[2] = 0;
|
||||
doubleBits[3] = 0xc0090000;
|
||||
const doublesShared = new Float64Array(doubleBits.buffer);
|
||||
const doublesCopy = new Float64Array(new SharedArrayBuffer(16));
|
||||
doublesCopy.set(doublesShared);
|
||||
bits("f64 shared into shared", doublesCopy.buffer);
|
||||
console.log("f64 values", doublesCopy[1]);
|
||||
|
||||
const signed = new Int16Array(new SharedArrayBuffer(8));
|
||||
signed.set(new Int16Array([-32768, -1, 32767]), 1);
|
||||
console.log("i16 private into shared", signed.join(","));
|
||||
|
||||
// Different kinds convert each element.
|
||||
const clamped = new Uint8ClampedArray(new SharedArrayBuffer(4));
|
||||
clamped.set(new Int16Array([300, -5, 128, 255]));
|
||||
console.log("i16 into shared clamped", clamped.join(","));
|
||||
const wrapped = new Uint8Array(new SharedArrayBuffer(4));
|
||||
wrapped.set(new Int16Array(signed.buffer, 2, 3));
|
||||
console.log("shared i16 into shared u8", wrapped.join(","));
|
||||
const narrowed = new Int8Array(4);
|
||||
narrowed.set(new Float64Array(doublesShared.buffer, 8, 1));
|
||||
narrowed.set(new Uint8Array([200, 127]), 2);
|
||||
console.log("f64 and u8 into i8", narrowed.join(","));
|
||||
const fromClamped = new Uint8Array(new SharedArrayBuffer(4));
|
||||
fromClamped.set(clamped);
|
||||
console.log("shared clamped into shared u8", fromClamped.join(","));
|
||||
|
||||
// Buffer views over shared memory take the same paths.
|
||||
const buf = Buffer.from(shared, 16, 8);
|
||||
buf.set(Buffer.from("scriptc"), 1);
|
||||
console.log("buffer", buf.toString("latin1", 1, 8), bytes[17], bytes[23]);
|
||||
const into = Buffer.alloc(4);
|
||||
into.set(buf.subarray(1, 4));
|
||||
console.log("buffer into private", into.toString("hex"));
|
||||
|
||||
// Offsets past the end throw before writing.
|
||||
for (const offset of [-1, 30, 31]) {
|
||||
try {
|
||||
bytes.set(new Uint8Array([9, 9]), offset);
|
||||
console.log("set at", offset, bytes[30], bytes[31]);
|
||||
} catch (error) {
|
||||
console.log("set at", offset, (error as Error).name, (error as Error).message);
|
||||
}
|
||||
}
|
||||
try {
|
||||
floatsBack.set(new Float32Array(3));
|
||||
} catch (error) {
|
||||
console.log("too long", (error as Error).name, (error as Error).message);
|
||||
}
|
||||
console.log("unchanged", floatsBack.join(","));
|
||||
@@ -0,0 +1,63 @@
|
||||
// TypedArray.prototype.set on SharedArrayBuffer views in a program with
|
||||
// workers: the same cases on the main thread and in a worker (same element
|
||||
// kinds with NaN payloads, overlapping ranges of one buffer, converted
|
||||
// kinds), and a worker's set into a buffer that the main thread reads after
|
||||
// a message.
|
||||
import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads";
|
||||
|
||||
interface Job {
|
||||
shared: SharedArrayBuffer;
|
||||
}
|
||||
|
||||
function hex(buffer: ArrayBufferLike): string {
|
||||
const words = new Uint32Array(buffer);
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < words.length; i++) out.push(words[i]!.toString(16));
|
||||
return out.join(",");
|
||||
}
|
||||
|
||||
function cases(where: string): string[] {
|
||||
const lines: string[] = [];
|
||||
const bytes = new Uint8Array(new SharedArrayBuffer(12));
|
||||
bytes.set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
||||
bytes.set(bytes.subarray(0, 8), 4);
|
||||
lines.push(`${where} overlap forward ${bytes.join(",")}`);
|
||||
bytes.set(bytes.subarray(5, 12), 0);
|
||||
lines.push(`${where} overlap backward ${bytes.join(",")}`);
|
||||
|
||||
const floatBits = new Uint32Array(new SharedArrayBuffer(8));
|
||||
floatBits[0] = 0x7f800001;
|
||||
floatBits[1] = 0x7fc12345;
|
||||
const local = new Float32Array(2);
|
||||
local.set(new Float32Array(floatBits.buffer));
|
||||
const back = new Float32Array(new SharedArrayBuffer(12));
|
||||
back.set(local, 1);
|
||||
lines.push(`${where} f32 ${hex(local.buffer)} ${hex(back.buffer)}`);
|
||||
|
||||
const halves = new Uint16Array(new SharedArrayBuffer(8));
|
||||
halves.set(new Uint16Array([65535, 1, 2]), 1);
|
||||
const signed = new Int16Array(4);
|
||||
signed.set(halves);
|
||||
const clamped = new Uint8ClampedArray(new SharedArrayBuffer(4));
|
||||
clamped.set(signed);
|
||||
lines.push(`${where} kinds ${halves.join(",")} ${signed.join(",")} ${clamped.join(",")}`);
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
for (const line of cases("main")) console.log(line);
|
||||
const shared = new SharedArrayBuffer(16);
|
||||
const job: Job = { shared };
|
||||
const worker = new Worker(new URL(import.meta.url), { workerData: job });
|
||||
worker.on("message", (message: string) => {
|
||||
console.log(message);
|
||||
console.log("worker wrote", new Uint8Array(shared).join(","));
|
||||
void worker.terminate();
|
||||
});
|
||||
} else {
|
||||
const { shared } = workerData as Job;
|
||||
const target = new Uint8Array(shared);
|
||||
target.set(new Uint8Array([9, 8, 7]), 2);
|
||||
target.set(target.subarray(2, 5), 10);
|
||||
parentPort!.postMessage(cases("worker").join("\n"));
|
||||
}
|
||||
Reference in New Issue
Block a user