feat(wasm): use zig-js's exact JS interop ABI
Ghostty's wasm code talks to JavaScript through the zig-js library (mitchellh/zig-js, pinned in build.zig.zon). The first port invented its own import ABI because the dependency wasn't available; now: - ghostty-os/src/wasm/js.rs is a Rust port of zig-js's Zig side (Ref, Value, Object get/set/call/new, global, runtime, string) using its "zig-js" import module, function names and NaN-boxed value refs. - web_canvas face and the atlas debug canvas make the same JS calls as Ghostty's Zig code. - zig-js's unmodified JS host (+ LICENSE) is vendored under crates/ghostty-wasm/js/zig-js and drives the node test. Test Plan: - cargo test -p ghostty-wasm (node >= 24; asserts zig-js imports and no leaked host values)
This commit is contained in:
1 parent
28f55ff2dd
commit
a07b224869
16 files changed
+1532
-479
No files matched your search
@@ -0,0 +1,303 @@
|
||||
import { ZigJS, predefined } from '../src';
|
||||
|
||||
test('predefined values', () => {
|
||||
const st = new ZigJS();
|
||||
expect(st.loadValue(0)).toBeNaN();
|
||||
expect(st.loadValue(1)).toEqual(null);
|
||||
expect(st.loadValue(2)).toEqual(true);
|
||||
expect(st.loadValue(3)).toEqual(false);
|
||||
expect(st.loadValue(4)).toEqual(undefined);
|
||||
expect(st.loadValue(5)).toEqual(globalThis);
|
||||
});
|
||||
|
||||
test('valueGet', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
const f = obj["zig-js"].valueGet;
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "__zigjs_number";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Write our key into the global value
|
||||
globalThis[key] = 1234;
|
||||
|
||||
// Read it
|
||||
f(64, predefined.globalThis, 0, write.written ?? 0);
|
||||
expect(view.getFloat64(64, true)).toEqual(1234);
|
||||
});
|
||||
|
||||
test('valueGet with shared memory', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
const f = obj["zig-js"].valueGet;
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1, maximum: 10, shared: true });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "__zigjs_number";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Write our key into the global value
|
||||
globalThis[key] = 1234;
|
||||
|
||||
// Read it
|
||||
f(64, predefined.globalThis, 0, write.written ?? 0);
|
||||
expect(view.getFloat64(64, true)).toEqual(1234);
|
||||
});
|
||||
|
||||
test('valueGet: runtime', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
const f = obj["zig-js"].valueGet;
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "memory";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Read it
|
||||
f(64, predefined.runtime, 0, write.written ?? 0);
|
||||
expect(st.loadValue(st.loadRefId(64))).toEqual(memory);
|
||||
});
|
||||
|
||||
test('valueGet: string', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "__zigjs_string";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Write our key into the global value
|
||||
globalThis[key] = "橋本";
|
||||
|
||||
// Read it
|
||||
const refAddr = 64;
|
||||
const f = obj["zig-js"].valueGet;
|
||||
f(refAddr, predefined.globalThis, 0, write.written ?? 0);
|
||||
expect(st.loadRef(refAddr)).toEqual("橋本");
|
||||
|
||||
// Read the string length
|
||||
{
|
||||
const stringLen = obj["zig-js"].valueStringLen;
|
||||
const len = stringLen(st.loadRefId(refAddr));
|
||||
expect(len).not.toBeNaN();
|
||||
expect(len).toEqual(6);
|
||||
}
|
||||
|
||||
// Copy the string into memory
|
||||
const offset = 12;
|
||||
{
|
||||
const stringCopy = obj["zig-js"].valueStringCopy;
|
||||
stringCopy(st.loadRefId(refAddr), offset, 64);
|
||||
}
|
||||
|
||||
// Read it
|
||||
const str = new TextDecoder('utf-8').decode(new DataView(memory.buffer, 12, 6));
|
||||
expect(str).toEqual("橋本");
|
||||
});
|
||||
|
||||
test('valueSet: number', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
const f = obj["zig-js"].valueSet;
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "__zigjs_number";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Write our argument
|
||||
const refAddr = 64;
|
||||
view.setFloat64(refAddr, 42, true);
|
||||
|
||||
// Write our key into the global value
|
||||
globalThis[key] = 12;
|
||||
|
||||
// Set it
|
||||
f(predefined.globalThis, 0, write.written ?? 0, refAddr);
|
||||
expect(globalThis[key]).toEqual(42);
|
||||
});
|
||||
|
||||
test('valueSet: ref', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
const f = obj["zig-js"].valueSet;
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string
|
||||
const key = "__zigjs_boolean";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Write our argument
|
||||
const refAddr = 64;
|
||||
st.storeValue(refAddr, true);
|
||||
|
||||
// Write our key into the global value
|
||||
globalThis[key] = false;
|
||||
|
||||
// Set it
|
||||
f(predefined.globalThis, 0, write.written ?? 0, refAddr);
|
||||
expect(globalThis[key]).toEqual(true);
|
||||
});
|
||||
|
||||
test('valueStringCreate', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Write our string into memory
|
||||
const value = "hello, world!";
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(value, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Read it
|
||||
let f = obj["zig-js"].valueStringCreate;
|
||||
const refAddr = 64;
|
||||
f(refAddr, 0, write.written ?? 0);
|
||||
expect(st.loadValue(st.loadRefId(refAddr))).toEqual(value);
|
||||
});
|
||||
|
||||
test('valueObjectCreate', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Read it
|
||||
let f = obj["zig-js"].valueObjectCreate;
|
||||
const refAddr = 64;
|
||||
f(refAddr);
|
||||
expect(st.loadValue(st.loadRefId(refAddr))).toEqual({});
|
||||
});
|
||||
|
||||
test('valueNew', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Set our function
|
||||
const key = "Uint8Array";
|
||||
|
||||
// Write our string
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Construct
|
||||
const funcAddr = 64;
|
||||
const f = obj["zig-js"].valueGet;
|
||||
f(funcAddr, predefined.globalThis, 0, write.written ?? 0);
|
||||
|
||||
// Setup our args
|
||||
view.setFloat64(0, 24, true);
|
||||
|
||||
// Call it!
|
||||
const resultAddr = 0;
|
||||
obj["zig-js"].valueNew(
|
||||
resultAddr,
|
||||
st.loadRefId(funcAddr),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
|
||||
const arr = st.loadRef(resultAddr);
|
||||
expect(arr.length).toEqual(24);
|
||||
});
|
||||
|
||||
test('funcApply', () => {
|
||||
const st = new ZigJS();
|
||||
const obj = st.importObject();
|
||||
|
||||
// Set our memory
|
||||
const memory = new WebAssembly.Memory({ initial: 1 });
|
||||
const view = new DataView(memory.buffer);
|
||||
st.memory = memory;
|
||||
|
||||
// Set our function
|
||||
const key = "__zigjs_func";
|
||||
globalThis[key] = (x: number): number => x * 2;
|
||||
|
||||
// Write our string
|
||||
const encoder = new TextEncoder();
|
||||
const write = encoder.encodeInto(key, new Uint8Array(memory.buffer));
|
||||
expect(write.written).toBeGreaterThan(0);
|
||||
|
||||
// Read the func
|
||||
const funcAddr = 64;
|
||||
const f = obj["zig-js"].valueGet;
|
||||
f(funcAddr, predefined.globalThis, 0, write.written ?? 0);
|
||||
expect(st.loadValue(st.loadRefId(funcAddr))).toBeInstanceOf(Function);
|
||||
|
||||
// Setup our args
|
||||
view.setFloat64(0, 24, true);
|
||||
|
||||
// Call it!
|
||||
const resultAddr = 0;
|
||||
obj["zig-js"].funcApply(
|
||||
resultAddr,
|
||||
st.loadRefId(funcAddr),
|
||||
predefined.undefined,
|
||||
0,
|
||||
1,
|
||||
);
|
||||
expect(st.loadRef(resultAddr)).toEqual(48);
|
||||
});
|
||||
|
||||
// We need to extend our global value for test keys
|
||||
declare global {
|
||||
var __zigjs_boolean: boolean;
|
||||
var __zigjs_number: number;
|
||||
var __zigjs_string: string;
|
||||
var __zigjs_func: any;
|
||||
}
|
||||
Reference in new issue
Block a user