Skip to content

Commit 4bacc21

Browse files
committed
feat: add more support for customizing factory
This adds some more Options that can be handled by the factory, such as: - providing custom Wasm bytes, rather than using the embedded ones - instantiating the WASI snapshot module in the runtime, required for WASI - providing a custom module config, e.g. to enable stdin/stdout when using WASI, or to customise the 'Start' function used by Wazero It also adds a new example which uses a guest module built using Go (big Go, not TinyGo, although that would also work). This example requires a few of these options to be configured - see the new basic_test.go file for details. Specifically it requires: - WASI enabled - architecture set to wasm64 (because big Go compiles to wasm64) - a custom module config with the `_initialize` function added to its list of start functions The Go guest module is generated using [this branch] of `wit-bindgen-go`. The custom branch is required because otherwise the generated Go code doesn't compile, of limitations with Go's //go:wasmexport functionality. There's a script (`gen-guest.sh`) that runs the `wit-bindgen-go` command. Go compilation to Wasm is then done in the same generate.go file as all the other examples. [this branch]: bytecodealliance/go-modules#367
1 parent df65772 commit 4bacc21

File tree

20 files changed

+583
-51
lines changed

20 files changed

+583
-51
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[workspace]
22
resolver = "3"
33
members = ["cmd/*", "examples/*"]
4+
exclude = ["examples/go"]
45

56
[patch.crates-io]
67
# # meanwhile, had to fork to add a feature flag for timers

cmd/gravity/src/codegen/factory.rs

Lines changed: 110 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use genco::prelude::*;
55
use crate::{
66
codegen::ir::AnalyzedImports,
77
go::{
8-
GoIdentifier, comment,
8+
comment,
99
imports::{
10-
CONTEXT_CONTEXT, ERRORS_NEW, SYNC_MUTEX, WAZERO_API_MEMORY, WAZERO_API_MODULE,
11-
WAZERO_COMPILED_MODULE, WAZERO_NEW_MODULE_CONFIG, WAZERO_NEW_RUNTIME, WAZERO_RUNTIME,
10+
CONTEXT_CONTEXT, ERRORS_NEW, SYNC_MUTEX, WASI_SNAPSHOT_PREVIEW1_MUST_INSTANTIATE,
11+
WAZERO_API_MEMORY, WAZERO_API_MODULE, WAZERO_COMPILED_MODULE, WAZERO_MODULE_CONFIG,
12+
WAZERO_NEW_MODULE_CONFIG, WAZERO_NEW_RUNTIME, WAZERO_RUNTIME,
1213
},
14+
GoIdentifier,
1315
},
1416
};
1517

@@ -148,10 +150,31 @@ impl<'a> FactoryGenerator<'a> {
148150
}
149151
}
150152
$['\n']
153+
func $(factory_name)WithWasmBytes[$(generics_with_constraints)](wasmBytes []byte) $options_name[$generics] {
154+
return func(factory *$factory_name_with_generics) {
155+
factory.wasmBytes = wasmBytes
156+
}
157+
}
158+
$['\n']
159+
func $(factory_name)WithModuleConfig[$(generics_with_constraints)](config $WAZERO_MODULE_CONFIG) $options_name[$generics] {
160+
return func(factory *$factory_name_with_generics) {
161+
factory.moduleConfig = config
162+
}
163+
}
164+
$['\n']
165+
func $(factory_name)WithWASI[$(generics_with_constraints)]() $options_name[$generics] {
166+
return func(factory *$factory_name_with_generics) {
167+
factory.enableWASI = true
168+
}
169+
}
170+
$['\n']
151171
type $factory_name[$(for (value_param, pointer_param, pointer_iface) in &type_param_data join (, ) => $value_param any, $pointer_param $pointer_iface[$value_param])] struct {
152172
runtime $WAZERO_RUNTIME
153173
module $WAZERO_COMPILED_MODULE
154174
architecture Architecture
175+
wasmBytes []byte
176+
moduleConfig $WAZERO_MODULE_CONFIG
177+
enableWASI bool
155178
$['\r']
156179
$(for (_iface_name, _resource_name, prefixed_name, type_param_name) in resource_info.iter() join ($['\r']) =>
157180
$(&GoIdentifier::public(format!("{}-resource-table", prefixed_name))) *$(&GoIdentifier::private(format!("{}-resource-table", prefixed_name)))[$(&GoIdentifier::public(format!("t-{}-value", prefixed_name))), $(&GoIdentifier::public(format!("p-{}", String::from(type_param_name))))])
@@ -164,40 +187,33 @@ impl<'a> FactoryGenerator<'a> {
164187
$['\r']
165188
opts ...$options_name[$generics],
166189
) (*$factory_name[$(for (value_param, pointer_param, _) in &type_param_data join (, ) => $value_param, $pointer_param)], error) {
167-
$['\r']
168-
wazeroRuntime := $WAZERO_NEW_RUNTIME(ctx)
169-
170190
$['\r']
171191
$(comment(&["Initialize resource tables before host module instantiation"]))
172192
$(for (_iface_name, _resource_name, prefixed_name, type_param_name) in resource_info.iter() join ($['\r']) =>
173193
$(&GoIdentifier::private(format!("{}_resource_table", prefixed_name))) := new$(&GoIdentifier::public(format!("{}-resource-table", prefixed_name)))[$(&GoIdentifier::public(format!("t-{}-value", prefixed_name))), $(&GoIdentifier::public(format!("p-{}", String::from(type_param_name))))]())
174194
$['\r']
175195

176-
$['\r']
177-
$(comment(&[
178-
"Compiling the module takes a LONG time, so we want to do it once and hold",
179-
"onto it with the Runtime",
180-
]))
181-
module, err := wazeroRuntime.CompileModule(ctx, $wasm_var_name)
182-
if err != nil {
183-
return nil, err
184-
}
185-
$['\r']
186-
187196
factory := &$factory_name[$(for (value_param, pointer_param, _) in &type_param_data join (, ) => $value_param, $pointer_param)]{
188-
runtime: wazeroRuntime,
189-
module: module,
190197
$(comment(["Initialize architecture to wasm32 by default"]))
191198
architecture: ArchitectureWasm32,
192199
$['\r']
193200
$(for (_iface_name, _resource_name, prefixed_name, _) in resource_info.iter() =>
194201
$(&GoIdentifier::public(format!("{}-resource-table", prefixed_name))): $(&GoIdentifier::private(format!("{}_resource_table", prefixed_name))),$['\r'])
195202
}
196203
$['\r']
204+
$(comment(&["Apply options before instantiation"]))
197205
for _, opt := range opts {
198206
opt(factory)
199207
}
200208
$['\r']
209+
wazeroRuntime := $WAZERO_NEW_RUNTIME(ctx)
210+
factory.runtime = wazeroRuntime
211+
$['\r']
212+
$(comment(&["Instantiate WASI if enabled"]))
213+
if factory.enableWASI {
214+
$WASI_SNAPSHOT_PREVIEW1_MUST_INSTANTIATE(ctx, wazeroRuntime)
215+
}
216+
$['\r']
201217
$(comment(&["Instantiate import host modules"]))
202218
$(for chain in self.config.import_chains.values() =>
203219
$chain
@@ -209,11 +225,31 @@ impl<'a> FactoryGenerator<'a> {
209225
$chain
210226
$['\r']
211227
)
228+
$['\r']
229+
$(comment(&[
230+
"Compiling the module takes a LONG time, so we want to do it once and hold",
231+
"onto it with the Runtime",
232+
]))
233+
wasmBytes := $wasm_var_name
234+
if factory.wasmBytes != nil {
235+
wasmBytes = factory.wasmBytes
236+
}
237+
module, err := wazeroRuntime.CompileModule(ctx, wasmBytes)
238+
if err != nil {
239+
wazeroRuntime.Close(ctx)
240+
return nil, err
241+
}
242+
factory.module = module
243+
$['\r']
212244
return factory, nil
213245
}
214246
$['\n']
215247
func (f *$factory_name[$(for (value_param, pointer_param, _) in &type_param_data join (, ) => $value_param, $pointer_param)]) Instantiate(ctx $CONTEXT_CONTEXT) (*$instance_name[$(for (value_param, pointer_param, _) in &type_param_data join (, ) => $value_param, $pointer_param)], error) {
216-
if module, err := f.runtime.InstantiateModule(ctx, f.module, $WAZERO_NEW_MODULE_CONFIG()); err != nil {
248+
config := f.moduleConfig
249+
if f.moduleConfig == nil {
250+
config = $WAZERO_NEW_MODULE_CONFIG()
251+
}
252+
if module, err := f.runtime.InstantiateModule(ctx, f.module, config); err != nil {
217253
return nil, err
218254
} else {
219255
return &$instance_name[$(for (value_param, pointer_param, _) in &type_param_data join (, ) => $value_param, $pointer_param)]{
@@ -242,10 +278,31 @@ impl<'a> FactoryGenerator<'a> {
242278
}
243279
}
244280
$['\n']
281+
func $(factory_name)WithWasmBytes(wasmBytes []byte) $(factory_name)Options {
282+
return func(factory *$factory_name) {
283+
factory.wasmBytes = wasmBytes
284+
}
285+
}
286+
$['\n']
287+
func $(factory_name)WithModuleConfig(config $WAZERO_MODULE_CONFIG) $(factory_name)Options {
288+
return func(factory *$factory_name) {
289+
factory.moduleConfig = config
290+
}
291+
}
292+
$['\n']
293+
func $(factory_name)WithWASI() $(factory_name)Options {
294+
return func(factory *$factory_name) {
295+
factory.enableWASI = true
296+
}
297+
}
298+
$['\n']
245299
type $factory_name struct {
246300
runtime $WAZERO_RUNTIME
247301
module $WAZERO_COMPILED_MODULE
248302
architecture Architecture
303+
wasmBytes []byte
304+
moduleConfig $WAZERO_MODULE_CONFIG
305+
enableWASI bool
249306
}
250307
$['\n']
251308
func $constructor_name(
@@ -254,39 +311,54 @@ impl<'a> FactoryGenerator<'a> {
254311
$(&interface.constructor_param_name) $(&interface.go_interface_name),)
255312
opts ...$(factory_name)Options,
256313
) (*$factory_name, error) {
257-
wazeroRuntime := $WAZERO_NEW_RUNTIME(ctx)
258-
$['\r']
259-
$(comment(&[
260-
"Compiling the module takes a LONG time, so we want to do it once and hold",
261-
"onto it with the Runtime",
262-
]))
263-
module, err := wazeroRuntime.CompileModule(ctx, $wasm_var_name)
264-
if err != nil {
265-
return nil, err
266-
}
267314
factory := &$factory_name{
268-
runtime: wazeroRuntime,
269-
module: module,
270315
$(comment(["Initialize architecture to wasm32 by default"]))
271316
architecture: ArchitectureWasm32,
272317
}
273-
274318
$['\r']
319+
$(comment(&["Apply options before instantiation"]))
275320
for _, opt := range opts {
276321
opt(factory)
277322
}
278323
$['\r']
324+
wazeroRuntime := $WAZERO_NEW_RUNTIME(ctx)
325+
factory.runtime = wazeroRuntime
326+
$['\r']
327+
$(comment(&["Instantiate WASI if enabled"]))
328+
if factory.enableWASI {
329+
$WASI_SNAPSHOT_PREVIEW1_MUST_INSTANTIATE(ctx, wazeroRuntime)
330+
}
331+
$['\r']
279332
$(comment(&["Instantiate import host modules"]))
280333
$(for chain in self.config.import_chains.values() =>
281-
$chain
282-
$['\r']
334+
$chain
335+
$['\r']
283336
)
284-
337+
$['\r']
338+
$(comment(&[
339+
"Compiling the module takes a LONG time, so we want to do it once and hold",
340+
"onto it with the Runtime",
341+
]))
342+
wasmBytes := $wasm_var_name
343+
if factory.wasmBytes != nil {
344+
wasmBytes = factory.wasmBytes
345+
}
346+
module, err := wazeroRuntime.CompileModule(ctx, wasmBytes)
347+
if err != nil {
348+
wazeroRuntime.Close(ctx)
349+
return nil, err
350+
}
351+
factory.module = module
352+
$['\r']
285353
return factory, nil
286354
}
287355
$['\n']
288356
func (f *$factory_name) Instantiate(ctx $CONTEXT_CONTEXT) (*$instance_name, error) {
289-
if module, err := f.runtime.InstantiateModule(ctx, f.module, $WAZERO_NEW_MODULE_CONFIG()); err != nil {
357+
config := f.moduleConfig
358+
if f.moduleConfig == nil {
359+
config = $WAZERO_NEW_MODULE_CONFIG()
360+
}
361+
if module, err := f.runtime.InstantiateModule(ctx, f.module, config); err != nil {
290362
return nil, err
291363
} else {
292364
return &$instance_name{module, f.architecture}, nil
@@ -618,7 +690,7 @@ mod tests {
618690
use genco::lang::go::Tokens;
619691

620692
use crate::{
621-
codegen::{FactoryGenerator, factory::FactoryConfig, ir::AnalyzedImports},
693+
codegen::{factory::FactoryConfig, ir::AnalyzedImports, FactoryGenerator},
622694
go::GoIdentifier,
623695
};
624696

cmd/gravity/src/go/imports.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,11 @@ pub static WAZERO_API_DECODE_F64: GoImport =
3838
GoImport("github.com/tetratelabs/wazero/api", "DecodeF64");
3939
pub static REFLECT_VALUE_OF: GoImport = GoImport("reflect", "ValueOf");
4040
pub static SYNC_MUTEX: GoImport = GoImport("sync", "Mutex");
41+
pub static OS_STDOUT: GoImport = GoImport("os", "Stdout");
42+
pub static OS_STDERR: GoImport = GoImport("os", "Stderr");
43+
pub static WAZERO_MODULE_CONFIG: GoImport =
44+
GoImport("github.com/tetratelabs/wazero", "ModuleConfig");
45+
pub static WASI_SNAPSHOT_PREVIEW1_MUST_INSTANTIATE: GoImport = GoImport(
46+
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1",
47+
"MustInstantiate",
48+
);

examples/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
*/*.go
22
!*/*_test.go
3-
*/*.wasm
3+
**/*.wasm

examples/generate.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ package examples
88
//go:generate cargo build -p example-resources --target wasm32-unknown-unknown --release
99
//go:generate cargo build -p example-resources-simple --target wasm32-unknown-unknown --release
1010
//go:generate cargo build -p example-tuples --target wasm32-unknown-unknown --release
11+
//go:generate sh -c "cd go/guest && GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -ldflags=-checklinkname=0 -o go-guest.wasm main.go"
1112

1213
//go:generate cargo run --bin gravity -- --world basic --output ./basic/basic.go --wit-file ./basic/wit/basic.wit ../target/wasm32-unknown-unknown/release/example_basic.wasm
1314
//go:generate cargo run --bin gravity -- --world example --output ./iface-method-returns-string/example.go --wit-file ./iface-method-returns-string/wit/example.wit ../target/wasm32-unknown-unknown/release/example_iface_method_returns_string.wasm
@@ -16,3 +17,4 @@ package examples
1617
//go:generate cargo run --bin gravity -- --world resources --output ./resources/resources.go --wit-file ./resources/wit/resources.wit ../target/wasm32-unknown-unknown/release/example_resources.wasm
1718
//go:generate cargo run --bin gravity -- --world resources --output ./resources-simple/resources.go --wit-file ./resources-simple/wit/world.wit ../target/wasm32-unknown-unknown/release/example_resources_simple.wasm
1819
//go:generate cargo run --bin gravity -- --world tuples --output ./tuples/tuples.go --wit-file ./tuples/wit/tuple.wit ../target/wasm32-unknown-unknown/release/example_tuples.wasm
20+
//go:generate cargo run --bin gravity -- --world basic --output ./go/basic.go --wit-file ./go/wit/basic.wit ./go/guest/go-guest.wasm

examples/go/basic_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package basic
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"testing"
7+
8+
"github.com/tetratelabs/wazero"
9+
)
10+
11+
type SlogLogger struct{}
12+
13+
func (s SlogLogger) Debug(ctx context.Context, msg string) { slog.DebugContext(ctx, msg) }
14+
func (s SlogLogger) Info(ctx context.Context, msg string) { slog.InfoContext(ctx, msg) }
15+
func (s SlogLogger) Warn(ctx context.Context, msg string) { slog.WarnContext(ctx, msg) }
16+
func (s SlogLogger) Error(ctx context.Context, msg string) { slog.ErrorContext(ctx, msg) }
17+
18+
func newFactory(t *testing.T) *BasicFactory {
19+
t.Helper()
20+
fac, err := NewBasicFactory(t.Context(), SlogLogger{},
21+
BasicFactoryWithWASI(),
22+
BasicFactoryWithModuleConfig(wazero.NewModuleConfig().WithStartFunctions("_initialize")),
23+
BasicFactoryWithArchitecture(ArchitectureWasm64),
24+
)
25+
if err != nil {
26+
t.Fatal(err)
27+
}
28+
return fac
29+
}
30+
31+
func TestBasic(t *testing.T) {
32+
fac := newFactory(t)
33+
defer fac.Close(t.Context())
34+
35+
ins, err := fac.Instantiate(t.Context())
36+
if err != nil {
37+
t.Fatal(err)
38+
}
39+
defer ins.Close(t.Context())
40+
41+
message, err := ins.Hello(t.Context())
42+
if err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
const want = "Hello, world!"
47+
if message != want {
48+
t.Errorf("wanted: %s, but got: %s", want, message)
49+
}
50+
}
51+
52+
func TestNoPrimitiveCleanup(t *testing.T) {
53+
fac := newFactory(t)
54+
defer fac.Close(t.Context())
55+
56+
ins, err := fac.Instantiate(t.Context())
57+
if err != nil {
58+
t.Fatal(err)
59+
}
60+
defer ins.Close(t.Context())
61+
62+
actual := ins.Primitive(t.Context())
63+
64+
const expected = true
65+
if actual != expected {
66+
t.Errorf("expected: %t, but got: %t", expected, actual)
67+
}
68+
}
69+
70+
func TestNoOptionalPrimitiveCleanup(t *testing.T) {
71+
fac := newFactory(t)
72+
defer fac.Close(t.Context())
73+
74+
ins, err := fac.Instantiate(t.Context())
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
defer ins.Close(t.Context())
79+
80+
actual, ok := ins.OptionalPrimitive(t.Context(), true)
81+
if !ok {
82+
t.Fatal(err)
83+
}
84+
85+
const expected = true
86+
if actual != expected {
87+
t.Errorf("expected: %t, but got: %t", expected, actual)
88+
}
89+
}
90+
91+
func TestResultPrimitiveCleanup(t *testing.T) {
92+
fac := newFactory(t)
93+
defer fac.Close(t.Context())
94+
95+
ins, err := fac.Instantiate(t.Context())
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
defer ins.Close(t.Context())
100+
101+
actual, err := ins.ResultPrimitive(t.Context())
102+
if err != nil {
103+
t.Fatal(err)
104+
}
105+
106+
const expected = true
107+
if actual != expected {
108+
t.Errorf("expected: %t, but got: %t", expected, actual)
109+
}
110+
}

0 commit comments

Comments
 (0)