Skip to content

Commit 9a9d7c1

Browse files
committed
Implement the memory example
1 parent d4fc62c commit 9a9d7c1

File tree

5 files changed

+339
-26
lines changed

5 files changed

+339
-26
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,10 @@ jobs:
6464
- name: zig build run -Dexample=gcd
6565
run: |
6666
zig build run -Dexample=gcd -Dlibrary-search-path="."
67+
- name: zig build run -Dexample=linking
68+
run: |
69+
zig build run -Dexample=linking -Dlibrary-search-path="."
70+
- name: zig build run -Dexample=memory
71+
run: |
72+
zig build run -Dexample=memory -Dlibrary-search-path="."
6773

example/memory.wat

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
(module
2+
(memory (export "memory") 2 3)
3+
4+
(func (export "size") (result i32) (memory.size))
5+
(func (export "load") (param i32) (result i32)
6+
(i32.load8_s (local.get 0))
7+
)
8+
(func (export "store") (param i32 i32)
9+
(i32.store8 (local.get 0) (local.get 1))
10+
)
11+
12+
(data (i32.const 0x1000) "\01\02\03\04")
13+
)

example/memory.zig

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
const wasmtime = @import("wasmtime");
4+
const fs = std.fs;
5+
const ga = std.heap.c_allocator;
6+
const Allocator = std.mem.Allocator;
7+
const assert = std.debug.assert;
8+
9+
pub fn main() !void {
10+
const wasm_path = if (builtin.os.tag == .windows) "example\\memory.wat" else "example/memory.wat";
11+
const wasm_file = try fs.cwd().openFile(wasm_path, .{});
12+
const wasm = try wasm_file.readToEndAlloc(ga, std.math.maxInt(u64));
13+
defer ga.free(wasm);
14+
15+
var engine = try wasmtime.Engine.init();
16+
defer engine.deinit();
17+
std.debug.print("Engine initialized...\n", .{});
18+
19+
var store = try wasmtime.Store.init(engine);
20+
defer store.deinit();
21+
std.debug.print("Store initialized...\n", .{});
22+
23+
var module = try wasmtime.Module.initFromWat(engine, wasm);
24+
defer module.deinit();
25+
std.debug.print("Wasm module compiled...\n", .{});
26+
27+
var instance = try wasmtime.Instance.init(store, module, &[_]*wasmtime.Func{});
28+
defer instance.deinit();
29+
std.debug.print("Instance initialized...\n", .{});
30+
31+
const memory = instance.getExportMem("memory").?;
32+
defer memory.deinit();
33+
34+
const size_func = instance.getExportFunc("size").?;
35+
const load_func = instance.getExportFunc("load").?;
36+
const store_func = instance.getExportFunc("store").?;
37+
38+
// verify initial memory
39+
assert(memory.pages() == 2);
40+
assert(memory.size() == 0x20_000);
41+
assert(memory.data()[0] == 0);
42+
assert(memory.data()[0x1000] == 1);
43+
assert(memory.data()[0x1003] == 4);
44+
45+
assertCall(size_func, 2);
46+
assertCall1(load_func, 0, 0);
47+
assertCall1(load_func, 0x1000, 1);
48+
assertCall1(load_func, 0x1003, 4);
49+
assertCall1(load_func, 0x1ffff, 0);
50+
assertTrap(load_func, 0x20_000);
51+
52+
// mutate memory
53+
memory.data()[0x1003] = 5;
54+
assertCall2(store_func, 0x1002, 6);
55+
assertTrap1(store_func, 0x20_000, 0);
56+
57+
// verify memory again
58+
assert(memory.data()[0x1002] == 6);
59+
assert(memory.data()[0x1003] == 5);
60+
assertCall1(load_func, 0x1002, 6);
61+
assertCall1(load_func, 0x1003, 5);
62+
63+
// Grow memory
64+
try memory.grow(1); // 'allocate' 1 more page
65+
assert(memory.pages() == 3);
66+
assert(memory.size() == 0x30_000);
67+
68+
assertCall1(load_func, 0x20_000, 0);
69+
assertCall2(store_func, 0x20_000, 0);
70+
assertTrap(load_func, 0x30_000);
71+
assertTrap1(store_func, 0x30_000, 0);
72+
73+
if (memory.grow(1)) |_| {} else |err| assert(err == error.OutOfMemory);
74+
try memory.grow(0);
75+
76+
// create stand-alone memory
77+
const mem_type = try wasmtime.c.MemoryType.init(.{ .min = 5, .max = 5 });
78+
defer mem_type.deinit();
79+
80+
const mem = try wasmtime.c.Memory.init(store, mem_type);
81+
defer mem.deinit();
82+
83+
assert(mem.pages() == 5);
84+
if (mem.grow(1)) |_| {} else |err| assert(err == error.OutOfMemory);
85+
try memory.grow(0);
86+
}
87+
88+
fn assertCall(func: *wasmtime.Func, result: u32) void {
89+
const res = func.call(@TypeOf(result), .{}) catch std.debug.panic("Unexpected error", .{});
90+
std.debug.assert(result == res);
91+
}
92+
93+
fn assertCall1(func: *wasmtime.Func, comptime arg: u32, result: u32) void {
94+
const res = func.call(@TypeOf(arg), .{arg}) catch std.debug.panic("Unexpected error", .{});
95+
std.debug.assert(result == res);
96+
}
97+
98+
fn assertCall2(func: *wasmtime.Func, comptime arg: u32, comptime arg2: u32) void {
99+
const res = func.call(void, .{ arg, arg2 }) catch std.debug.panic("Unexpected error", .{});
100+
std.debug.assert({} == res);
101+
}
102+
103+
fn assertTrap(func: *wasmtime.Func, comptime arg: u32) void {
104+
if (func.call(@TypeOf(arg), .{arg})) |_| {
105+
std.debug.panic("Expected Trap error, got result", .{});
106+
} else |err| std.debug.assert(err == error.Trap);
107+
}
108+
109+
fn assertTrap1(func: *wasmtime.Func, comptime arg: u32, comptime arg2: u32) void {
110+
if (func.call(void, .{ arg, arg2 })) |_| {
111+
std.debug.panic("Expected Trap error, got result", .{});
112+
} else |err| std.debug.assert(err == error.Trap);
113+
}

src/c.zig

Lines changed: 169 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ const Instance = @import("main.zig").Instance;
55

66
pub const WasmError = opaque {
77
/// Gets the error message
8-
pub fn getMessage(self: *WasmError) ByteVec {
9-
var bytes: ByteVec = undefined;
8+
pub fn getMessage(self: *WasmError) *ByteVec {
9+
var bytes: ?*ByteVec = null;
1010
wasmtime_error_message(self, &bytes);
11-
return bytes;
11+
return bytes.?;
1212
}
1313

1414
/// Frees the error resources
@@ -17,27 +17,181 @@ pub const WasmError = opaque {
1717
}
1818

1919
extern fn wasmtime_error_delete(*WasmError) void;
20-
extern fn wasmtime_error_message(*WasmError, *ByteVec) void;
20+
extern fn wasmtime_error_message(?*const WasmError, *?*ByteVec) void;
2121
};
2222

2323
pub const Trap = opaque {
2424
pub fn deinit(self: *Trap) void {
2525
wasm_trap_delete(self);
2626
}
2727

28+
/// Returns the trap message.
29+
/// Memory of the returned `ByteVec` must be freed using `deinit`
30+
pub fn message(self: *Trap) *ByteVec {
31+
var bytes: ?*ByteVec = null;
32+
wasm_trap_message(&bytes);
33+
return bytes.?;
34+
}
35+
2836
extern fn wasm_trap_delete(*Trap) void;
37+
extern fn wasm_trap_message(?*const Trap, out: *?*ByteVec) void;
2938
};
3039

3140
pub const Extern = opaque {
3241
/// Returns the `Extern` as a function
33-
pub fn asFunc(self: *Extern) ?*Func {
34-
return wasm_extern_as_func(self);
42+
/// returns `null` when the given `Extern` is not a function
43+
///
44+
/// Asserts `Extern` is of type `Func`
45+
pub fn asFunc(self: *Extern) *Func {
46+
return wasm_extern_as_func(self).?;
47+
}
48+
49+
/// Returns the `Extern` as a `Memory` object
50+
/// returns `null` when the given `Extern` is not a memory object
51+
///
52+
/// Asserts `Extern` is of type `Memory`
53+
pub fn asMemory(self: *Extern) *Memory {
54+
return wasm_extern_as_memory(self).?;
55+
}
56+
57+
/// Returns the `Extern` as a `Global`
58+
/// returns `null` when the given `Extern` is not a global
59+
///
60+
/// Asserts `Extern` is of type `Global`
61+
pub fn asGlobal(self: *Extern) *Global {
62+
return wasm_extern_as_global(self).?;
63+
}
64+
65+
/// Returns the `Extern` as a `Table`
66+
/// returns `null` when the given `Extern` is not a table
67+
///
68+
/// Asserts `Extern` is of type `Table`
69+
pub fn asTable(self: *Extern) *Table {
70+
return wasm_extern_as_table(self).?;
71+
}
72+
73+
/// Frees the memory of the `Extern`
74+
pub fn deinit(self: *Extern) void {
75+
wasm_extern_delete(self);
3576
}
3677

37-
extern fn wasm_extern_as_func(external: *c_void) ?*Func;
78+
/// Creates a copy of the `Extern` and returns it
79+
/// Memory of the copied version must be freed manually by calling `deinit`
80+
///
81+
/// Asserts the copy succeeds
82+
pub fn copy(self: *Extern) *Extern {
83+
return wasm_extern_copy(self).?;
84+
}
85+
86+
/// Checks if the given externs are equal and returns true if so
87+
pub fn eql(self: *const Extern, other: *const Extern) bool {
88+
return wasm_extern_same(self, other);
89+
}
90+
91+
extern fn wasm_extern_as_func(?*Extern) ?*Func;
92+
extern fn wasm_extern_as_memory(?*Extern) ?*Memory;
93+
extern fn wasm_extern_as_global(?*Extern) ?*Global;
94+
extern fn wasm_extern_as_table(?*Extern) ?*Table;
95+
extern fn wasm_extern_delete(?*Extern) void;
96+
extern fn wasm_extern_copy(?*Extern) ?*Extern;
97+
extern fn wasm_extern_same(?*const Extern, ?*const Extern) bool;
3898
};
3999

100+
pub const Memory = opaque {
101+
/// Creates a new `Memory` object for the given `Store` and `MemoryType`
102+
pub fn init(store: *Store, mem_type: *const MemoryType) !*Memory {
103+
return wasm_memory_new(store, mem_type) orelse error.MemoryInit;
104+
}
105+
106+
/// Returns the `MemoryType` of a given `Memory` object
107+
pub fn getType(self: *const Memory) *MemoryType {
108+
return wasm_memory_type(self).?;
109+
}
110+
111+
/// Frees the memory of the `Memory` object
112+
pub fn deinit(self: *Memory) void {
113+
wasm_memory_delete(self);
114+
}
115+
116+
/// Creates a copy of the given `Memory` object
117+
/// Returned copy must be freed manually.
118+
pub fn copy(self: *const Memory) ?*Memory {
119+
return wasm_memory_copy(self);
120+
}
121+
122+
/// Returns true when the given `Memory` objects are equal
123+
pub fn eql(self: *const Memory, other: *const Memory) bool {
124+
return wasm_memory_same(self, other);
125+
}
126+
127+
/// Returns a pointer-to-many bytes
128+
///
129+
/// Tip: Use toSlice() to get a slice for better ergonomics
130+
pub fn data(self: *Memory) [*]u8 {
131+
return wasm_memory_data(self);
132+
}
133+
134+
/// Returns the data size of the `Memory` object.
135+
pub fn size(self: *const Memory) usize {
136+
return wasm_memory_data_size(self);
137+
}
138+
139+
/// Returns the amount of pages the `Memory` object consists of
140+
/// where each page is 65536 bytes
141+
pub fn pages(self: *const Memory) u32 {
142+
return wasm_memory_size(self);
143+
}
144+
145+
/// Convenient helper function to represent the memory
146+
/// as a slice of bytes. Memory is however still owned by wasm
147+
/// and must be freed by calling `deinit` on the original `Memory` object
148+
pub fn toSlice(self: *Memory) []const u8 {
149+
var slice: []const u8 = undefined;
150+
slice.ptr = self.data();
151+
slice.len = self.size();
152+
return slice;
153+
}
154+
155+
/// Increases the amount of memory pages by the given count.
156+
pub fn grow(self: *Memory, page_count: u32) error{OutOfMemory}!void {
157+
if (!wasm_memory_grow(self, page_count)) return error.OutOfMemory;
158+
}
159+
160+
extern fn wasm_memory_delete(?*Memory) void;
161+
extern fn wasm_memory_copy(?*const Memory) ?*Memory;
162+
extern fn wasm_memory_same(?*const Memory, ?*const Memory) bool;
163+
extern fn wasm_memory_new(?*Store, ?*const MemoryType) ?*Memory;
164+
extern fn wasm_memory_type(?*const Memory) ?*MemoryType;
165+
extern fn wasm_memory_data(?*Memory) [*]u8;
166+
extern fn wasm_memory_data_size(?*const Memory) usize;
167+
extern fn wasm_memory_grow(?*Memory, delta: u32) bool;
168+
extern fn wasm_memory_size(?*const Memory) u32;
169+
};
170+
171+
pub const Limits = extern struct {
172+
min: u32,
173+
max: u32,
174+
};
175+
176+
pub const MemoryType = opaque {
177+
pub fn init(limits: Limits) !*MemoryType {
178+
return wasm_memorytype_new(&limits) orelse return error.InitMemoryType;
179+
}
180+
181+
pub fn deinit(self: *MemoryType) void {
182+
wasm_memorytype_delete(self);
183+
}
184+
185+
extern fn wasm_memorytype_new(*const Limits) ?*MemoryType;
186+
extern fn wasm_memorytype_delete(?*MemoryType) void;
187+
};
188+
189+
// TODO: implement table and global types
190+
pub const Table = opaque {};
191+
pub const Global = opaque {};
192+
40193
pub const ExportType = opaque {
194+
/// Returns the name of the given `ExportType`
41195
pub fn name(self: *ExportType) *ByteVec {
42196
return self.wasm_exporttype_name().?;
43197
}
@@ -48,6 +202,9 @@ pub const ExportTypeVec = extern struct {
48202
size: usize,
49203
data: [*]?*ExportType,
50204

205+
/// Returns a slice of an `ExportTypeVec`
206+
/// memory is still owned by wasmtime and can only be freed using
207+
/// `deinit()` on the original `ExportTypeVec`
51208
pub fn toSlice(self: *const ExportTypeVec) []const ?*ExportType {
52209
return self.data[0..self.size];
53210
}
@@ -64,6 +221,7 @@ pub const InstanceType = opaque {
64221
self.wasm_instancetype_delete();
65222
}
66223

224+
/// Returns a vector of `ExportType` in a singular type `ExportTypeVec`
67225
pub fn exports(self: *InstanceType) ExportTypeVec {
68226
var export_vec: ExportTypeVec = undefined;
69227
self.wasm_instancetype_exports(&export_vec);
@@ -76,7 +234,6 @@ pub const InstanceType = opaque {
76234

77235
pub const Callback = fn (?*const Valtype, ?*Valtype) callconv(.C) ?*Trap;
78236

79-
// Bits
80237
pub const ByteVec = extern struct {
81238
size: usize,
82239
data: [*]u8,
@@ -309,14 +466,18 @@ pub const Linker = opaque {
309466
wasmtime_linker_delete(self);
310467
}
311468

469+
/// Defines a `WasiInstance` for the current `Linker`
312470
pub fn defineWasi(self: *Linker, wasi: *const WasiInstance) ?*WasmError {
313471
return wasmtime_linker_define_wasi(self, wasi);
314472
}
315473

474+
/// Defines an `Instance` for the current `Linker` object using the given `name`
316475
pub fn defineInstance(self: *Linker, name: *const NameVec, instance: *const Instance) ?*WasmError {
317476
return wasmtime_linker_define_instance(self, name, instance);
318477
}
319478

479+
/// Instantiates the `Linker` for the given `Module` and creates a new `Instance` for it
480+
/// Returns a `WasmError` when failed to instantiate
320481
pub fn instantiate(
321482
self: *const Linker,
322483
module: *const Module,

0 commit comments

Comments
 (0)