115 lines
3.8 KiB
Zig
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);
|
|
}
|