ghostty/src/os/wasm.zig
Mitchell Hashimoto 241bfee7d4 wasm: use shared, imported memory
This switches our wasm build to use "shared" memory. Shared memory can
be shared across multiple web workers, which is something we'll want to
support for our multi-threaded behaviors later.

Shared memory has a number of different restrictions so this updates
zig-js to support it as well as updates some of our functions that need
to be aware of it.
2022-12-24 16:24:43 -08:00

120 lines
3.8 KiB
Zig

//! 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()) {
@compileError("wasm.zig should only be analyzed for wasm32 builds");
}
}
/// 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
/// unless toHostOwned is called on them. In this case, the caller is expected
/// to call free. If a pointer is NOT host-owned, then the wasm module is
/// expected to call the normal alloc.free/destroy functions.
///
/// TODO: we should NOT be using page_allocator because we're getting
/// full 64kb pages for every allocation. I plan on changing this to the
/// new stdlib wasm allocator once that is merged and available.
pub const alloc = if (builtin.is_test)
std.testing.allocator
else
std.heap.page_allocator;
/// For host-owned allocations:
/// We need to keep track of our own pointer lengths because Zig
/// allocators usually don't do this and we need to be able to send
/// a direct pointer back to the host system. A more appropriate thing
/// to do would be to probably make a custom allocator that keeps track
/// of size.
var allocs: std.AutoHashMapUnmanaged([*]u8, usize) = .{};
/// Allocate len bytes and return a pointer to the memory in the host.
/// The data is not zeroed.
pub export fn malloc(len: usize) ?[*]u8 {
return alloc_(len) catch return null;
}
fn alloc_(len: usize) ![*]u8 {
// Create the allocation
const slice = try alloc.alloc(u8, len);
errdefer alloc.free(slice);
// Store the size so we can deallocate later
try allocs.putNoClobber(alloc, slice.ptr, slice.len);
errdefer _ = allocs.remove(slice.ptr);
return slice.ptr;
}
/// Free an allocation from malloc.
pub export fn free(ptr: ?[*]u8) void {
if (ptr) |v| {
if (allocs.get(v)) |len| {
const slice = v[0..len];
alloc.free(slice);
_ = allocs.remove(v);
}
}
}
/// Convert an allocated pointer of any type to a host-owned pointer.
/// This pushes the responsibility to free it to the host. The returned
/// pointer will match the pointer but is typed correctly for returning
/// to the host.
pub fn toHostOwned(ptr: anytype) ![*]u8 {
// Convert our pointer to a byte array
const info = @typeInfo(@TypeOf(ptr)).Pointer;
const T = info.child;
const size = @sizeOf(T);
const casted = @intToPtr([*]u8, @ptrToInt(ptr));
// Store the information about it
try allocs.putNoClobber(alloc, casted, size);
errdefer _ = allocs.remove(casted);
return casted;
}
/// Returns true if the value is host owned.
pub fn isHostOwned(ptr: anytype) bool {
const casted = @intToPtr([*]u8, @ptrToInt(ptr));
return allocs.contains(casted);
}
/// Convert a pointer back to a module-owned value. The caller is expected
/// to cast or have the valid pointer for alloc calls.
pub fn toModuleOwned(ptr: anytype) void {
const casted = @intToPtr([*]u8, @ptrToInt(ptr));
_ = allocs.remove(casted);
}
test "basics" {
const testing = std.testing;
var buf = malloc(32).?;
try testing.expect(allocs.size == 1);
free(buf);
try testing.expect(allocs.size == 0);
}
test "toHostOwned" {
const testing = std.testing;
const Point = struct { x: u32 = 0, y: u32 = 0 };
var p = try alloc.create(Point);
errdefer alloc.destroy(p);
const ptr = try toHostOwned(p);
try testing.expect(allocs.size == 1);
try testing.expect(isHostOwned(p));
try testing.expect(isHostOwned(ptr));
free(ptr);
try testing.expect(allocs.size == 0);
}