diff --git a/build.zig b/build.zig index b9f56e776..a9794aa13 100644 --- a/build.zig +++ b/build.zig @@ -112,16 +112,44 @@ pub fn build(b: *std.build.Builder) !void { // wasm { + // Build our Wasm target. + const wasm_target: std.zig.CrossTarget = .{ + .cpu_arch = .wasm32, + .os_tag = .freestanding, + .cpu_model = .{ .explicit = &std.Target.wasm.cpu.mvp }, + .cpu_features_add = std.Target.wasm.featureSet(&.{ + // We use this to explicitly request shared memory. + .atomics, + + // Not explicitly used but compiler could use them if they want. + .bulk_memory, + .reference_types, + .sign_ext, + }), + }; + + // Whether we're using wasm shared memory. Some behaviors change. + // For now we require this but I wanted to make the code handle both + // up front. + const wasm_shared: bool = true; + exe_options.addOption(bool, "wasm_shared", wasm_shared); + const wasm = b.addSharedLibrary( "ghostty-wasm", "src/main_wasm.zig", .{ .unversioned = {} }, ); - wasm.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding }); + wasm.setTarget(wasm_target); wasm.setBuildMode(mode); wasm.setOutputDir("zig-out"); wasm.addOptions("build_options", exe_options); + // So that we can use web workers with our wasm binary + wasm.import_memory = true; + wasm.initial_memory = 65536 * 25; + wasm.max_memory = 65536 * 65536; // Maximum number of pages in wasm32 + wasm.shared_memory = wasm_shared; + // Stack protector adds extern requirements that we don't satisfy. wasm.stack_protector = false; @@ -136,7 +164,7 @@ pub fn build(b: *std.build.Builder) !void { // it lets us test some basic functionality. const test_step = b.step("test-wasm", "Run all tests for wasm"); const main_test = b.addTest("src/main_wasm.zig"); - main_test.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .wasi }); + main_test.setTarget(wasm_target); main_test.addOptions("build_options", exe_options); try addDeps(b, main_test, true); test_step.dependOn(&main_test.step); diff --git a/example/app.ts b/example/app.ts index f66771371..40a81d2c2 100644 --- a/example/app.ts +++ b/example/app.ts @@ -4,9 +4,11 @@ const zjs = new ZigJS(); const importObject = { module: {}, env: { + memory: new WebAssembly.Memory({ initial: 25, maximum: 65536, shared: true }), log: (ptr: number, len: number) => { - const view = new DataView(zjs.memory.buffer, ptr, Number(len)); - const str = new TextDecoder('utf-8').decode(view); + const arr = new Uint8ClampedArray(zjs.memory.buffer, ptr, len); + const data = arr.slice(); + const str = new TextDecoder('utf-8').decode(data); console.log(str); }, }, @@ -20,8 +22,8 @@ fetch(url.href).then(response => ).then(bytes => WebAssembly.instantiate(bytes, importObject) ).then(results => { + const memory = importObject.env.memory; const { - memory, malloc, free, face_new, diff --git a/src/font/Atlas.zig b/src/font/Atlas.zig index f38af41df..30d0b5cba 100644 --- a/src/font/Atlas.zig +++ b/src/font/Atlas.zig @@ -423,9 +423,17 @@ pub const Wasm = struct { defer mem_buf.deinit(); // Create an array that points to our buffer - const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray"); - defer Uint8ClampedArray.deinit(); - const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len }); + const arr = arr: { + const Uint8ClampedArray = try js.global.get(js.Object, "Uint8ClampedArray"); + defer Uint8ClampedArray.deinit(); + const arr = try Uint8ClampedArray.new(.{ mem_buf, buf.ptr, buf.len }); + if (!wasm.shared_mem) break :arr arr; + + // If we're sharing memory then we have to copy the data since + // we can't set ImageData directly using a SharedArrayBuffer. + defer arr.deinit(); + break :arr try arr.call(js.Object, "slice", .{}); + }; defer arr.deinit(); // Create the image data from our array diff --git a/src/os/wasm.zig b/src/os/wasm.zig index 5d3c65830..31435314b 100644 --- a/src/os/wasm.zig +++ b/src/os/wasm.zig @@ -1,6 +1,7 @@ //! This file contains helpers for wasm compilation. const std = @import("std"); const builtin = @import("builtin"); +const options = @import("build_options"); comptime { if (!builtin.target.isWasm()) { @@ -8,6 +9,10 @@ comptime { } } +/// True if we're in shared memory mode. If true, then the memory buffer +/// in JS will be backed by a SharedArrayBuffer and some behaviors change. +pub const shared_mem = options.wasm_shared; + /// The allocator to use in wasm environments. /// /// The return values of this should NOT be sent to the host environment diff --git a/vendor/zig-js b/vendor/zig-js index 5e3a5ce77..c89c1965c 160000 --- a/vendor/zig-js +++ b/vendor/zig-js @@ -1 +1 @@ -Subproject commit 5e3a5ce776f7b424022494a830f66ace224fe7ff +Subproject commit c89c1965cc6bf6ede97c1b891b624ce5282853d1