wasm-runtime/src/wasm/runtime.zig
Lorenzo Torres b574d39a39 first commit
2026-02-24 14:28:56 +01:00

115 lines
3.8 KiB
Zig

const std = @import("std");
const module = @import("module.zig");
pub const PAGE_SIZE: u32 = 65536;
pub const Value = union(module.ValType) {
i32: i32,
i64: i64,
f32: f32,
f64: f64,
};
pub const Memory = struct {
bytes: []u8,
max_pages: ?u32,
pub fn init(allocator: std.mem.Allocator, min_pages: u32, max_pages: ?u32) !Memory {
const size = @as(usize, min_pages) * PAGE_SIZE;
const bytes = try allocator.alloc(u8, size);
@memset(bytes, 0);
return .{ .bytes = bytes, .max_pages = max_pages };
}
pub fn deinit(self: *Memory, allocator: std.mem.Allocator) void {
allocator.free(self.bytes);
self.* = undefined;
}
/// Grow by delta_pages. Returns old page count, or error.OutOfMemory / error.GrowthExceedsMax.
pub fn grow(self: *Memory, allocator: std.mem.Allocator, delta_pages: u32) !u32 {
const old_pages: u32 = @intCast(self.bytes.len / PAGE_SIZE);
const new_pages = old_pages + delta_pages;
if (self.max_pages) |max| {
if (new_pages > max) return error.GrowthExceedsMax;
}
const new_size = @as(usize, new_pages) * PAGE_SIZE;
const new_bytes = try allocator.realloc(self.bytes, new_size);
@memset(new_bytes[self.bytes.len..], 0);
self.bytes = new_bytes;
return old_pages;
}
pub fn load(self: *const Memory, comptime T: type, addr: u32) !T {
const size = @sizeOf(T);
if (@as(usize, addr) + size > self.bytes.len) return error.OutOfBounds;
const Bits = std.meta.Int(.unsigned, @bitSizeOf(T));
const raw = std.mem.readInt(Bits, self.bytes[addr..][0..size], .little);
return @bitCast(raw);
}
pub fn store(self: *Memory, comptime T: type, addr: u32, value: T) !void {
const size = @sizeOf(T);
if (@as(usize, addr) + size > self.bytes.len) return error.OutOfBounds;
const Bits = std.meta.Int(.unsigned, @bitSizeOf(T));
const raw: Bits = @bitCast(value);
std.mem.writeInt(Bits, self.bytes[addr..][0..size], raw, .little);
}
};
pub const Table = struct {
elements: []?u32,
max: ?u32,
pub fn init(allocator: std.mem.Allocator, min: u32, max: ?u32) !Table {
const elems = try allocator.alloc(?u32, min);
@memset(elems, null);
return .{ .elements = elems, .max = max };
}
pub fn deinit(self: *Table, allocator: std.mem.Allocator) void {
allocator.free(self.elements);
self.* = undefined;
}
};
test "memory load/store round-trip i32" {
const ally = std.testing.allocator;
var mem = try Memory.init(ally, 1, null);
defer mem.deinit(ally);
try mem.store(i32, 0, 42);
const v = try mem.load(i32, 0);
try std.testing.expectEqual(@as(i32, 42), v);
}
test "memory out-of-bounds returns error" {
const ally = std.testing.allocator;
var mem = try Memory.init(ally, 1, null);
defer mem.deinit(ally);
try std.testing.expectError(error.OutOfBounds, mem.load(i32, PAGE_SIZE - 2));
}
test "memory grow" {
const ally = std.testing.allocator;
var mem = try Memory.init(ally, 1, 4);
defer mem.deinit(ally);
const old = try mem.grow(ally, 1);
try std.testing.expectEqual(@as(u32, 1), old);
try std.testing.expectEqual(@as(usize, 2 * PAGE_SIZE), mem.bytes.len);
}
test "memory grow beyond max fails" {
const ally = std.testing.allocator;
var mem = try Memory.init(ally, 1, 2);
defer mem.deinit(ally);
try std.testing.expectError(error.GrowthExceedsMax, mem.grow(ally, 2));
}
test "memory store/load f64" {
const ally = std.testing.allocator;
var mem = try Memory.init(ally, 1, null);
defer mem.deinit(ally);
try mem.store(f64, 8, 3.14);
const v = try mem.load(f64, 8);
try std.testing.expectApproxEqAbs(3.14, v, 1e-10);
}