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); }