forked from tetratelabs/wazero
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gojs.go
199 lines (183 loc) · 7.13 KB
/
gojs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Package gojs allows you to run wasm binaries compiled by Go when
// `GOOS=js GOARCH=wasm`. See https://wazero.io/languages/go/ for more.
//
// # Experimental
//
// Go defines js "EXPERIMENTAL... exempt from the Go compatibility promise."
// Accordingly, wazero cannot guarantee this will work from release to release,
// or that usage will be relatively free of bugs. Moreover, `GOOS=wasi` will
// happen, and once that's available in two releases wazero will remove this
// package.
//
// Due to these concerns and the relatively high implementation overhead, most
// will choose TinyGo instead of gojs.
package gojs
import (
"context"
"errors"
"github.com/AR1011/wazero"
"github.com/AR1011/wazero/api"
"github.com/AR1011/wazero/internal/gojs"
internalconfig "github.com/AR1011/wazero/internal/gojs/config"
"github.com/AR1011/wazero/internal/gojs/run"
"github.com/AR1011/wazero/internal/wasm"
)
// MustInstantiate calls Instantiate or panics on error.
//
// This is a simpler function for those who know host functions are not already
// instantiated, and don't need to unload them separate from the runtime.
func MustInstantiate(ctx context.Context, r wazero.Runtime, guest wazero.CompiledModule) {
if _, err := Instantiate(ctx, r, guest); err != nil {
panic(err)
}
}
// Instantiate detects and instantiates host functions for wasm compiled with
// `GOOS=js GOARCH=wasm`. `guest` must be a result of `r.CompileModule`.
//
// # Notes
//
// - Failure cases are documented on wazero.Runtime InstantiateModule.
// - Closing the wazero.Runtime has the same effect as closing the result.
// - To add more functions to `goModule`, use FunctionExporter.
func Instantiate(ctx context.Context, r wazero.Runtime, guest wazero.CompiledModule) (api.Closer, error) {
goModule, err := detectGoModule(guest.ImportedFunctions())
if err != nil {
return nil, err
}
builder := r.NewHostModuleBuilder(goModule)
NewFunctionExporter().ExportFunctions(builder)
return builder.Instantiate(ctx)
}
// detectGoModule is needed because the module name defining host functions for
// `GOOS=js GOARCH=wasm` was renamed from "go" to "gojs" in Go 1.21. We can't
// use the version that compiles wazero because it could be different from what
// compiled the guest.
//
// See https://github.com/golang/go/commit/02411bcd7c8eda9c694a5755aff0a516d4983952
func detectGoModule(imports []api.FunctionDefinition) (string, error) {
for _, f := range imports {
moduleName, _, _ := f.Import()
switch moduleName {
case "go", "gojs":
return moduleName, nil
}
}
return "", errors.New("guest wasn't compiled with GOOS=js GOARCH=wasm")
}
// FunctionExporter builds host functions for wasm compiled with
// `GOOS=js GOARCH=wasm`.
type FunctionExporter interface {
// ExportFunctions builds functions to an existing host module builder.
//
// This should be named "go" or "gojs", depending on the version of Go the
// guest was compiled with. The module name changed from "go" to "gojs" in
// Go 1.21.
ExportFunctions(wazero.HostModuleBuilder)
}
// NewFunctionExporter returns a FunctionExporter object.
func NewFunctionExporter() FunctionExporter {
return &functionExporter{}
}
type functionExporter struct{}
// ExportFunctions implements FunctionExporter.ExportFunctions
func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
hfExporter := builder.(wasm.HostFuncExporter)
hfExporter.ExportHostFunc(gojs.GetRandomData)
hfExporter.ExportHostFunc(gojs.Nanotime1)
hfExporter.ExportHostFunc(gojs.WasmExit)
hfExporter.ExportHostFunc(gojs.CopyBytesToJS)
hfExporter.ExportHostFunc(gojs.ValueCall)
hfExporter.ExportHostFunc(gojs.ValueGet)
hfExporter.ExportHostFunc(gojs.ValueIndex)
hfExporter.ExportHostFunc(gojs.ValueLength)
hfExporter.ExportHostFunc(gojs.ValueNew)
hfExporter.ExportHostFunc(gojs.ValueSet)
hfExporter.ExportHostFunc(gojs.WasmWrite)
hfExporter.ExportHostFunc(gojs.ResetMemoryDataView)
hfExporter.ExportHostFunc(gojs.Walltime)
hfExporter.ExportHostFunc(gojs.ScheduleTimeoutEvent)
hfExporter.ExportHostFunc(gojs.ClearTimeoutEvent)
hfExporter.ExportHostFunc(gojs.FinalizeRef)
hfExporter.ExportHostFunc(gojs.StringVal)
hfExporter.ExportHostFunc(gojs.ValueDelete)
hfExporter.ExportHostFunc(gojs.ValueSetIndex)
hfExporter.ExportHostFunc(gojs.ValueInvoke)
hfExporter.ExportHostFunc(gojs.ValuePrepareString)
hfExporter.ExportHostFunc(gojs.ValueInstanceOf)
hfExporter.ExportHostFunc(gojs.ValueLoadString)
hfExporter.ExportHostFunc(gojs.CopyBytesToGo)
hfExporter.ExportHostFunc(gojs.Debug)
}
// Config extends wazero.ModuleConfig with GOOS=js specific extensions.
// Use NewConfig to create an instance.
type Config interface {
// WithOSWorkdir sets the initial working directory used to Run Wasm to
// the value of os.Getwd instead of the default of root "/".
//
// Here's an example that overrides this to the current directory:
//
// err = gojs.Run(ctx, r, compiled, gojs.NewConfig(moduleConfig).
// WithOSWorkdir())
//
// Note: To use this feature requires mounting the real root directory via
// wazero.FSConfig `WithDirMount`. On windows, this root must be the same drive
// as the value of os.Getwd. For example, it would be an error to mount `C:\`
// as the guest path "", while the current directory is inside `D:\`.
WithOSWorkdir() Config
}
// NewConfig returns a Config that can be used for configuring module instantiation.
func NewConfig(moduleConfig wazero.ModuleConfig) Config {
return &cfg{moduleConfig: moduleConfig, internal: internalconfig.NewConfig()}
}
type cfg struct {
moduleConfig wazero.ModuleConfig
internal *internalconfig.Config
}
func (c *cfg) clone() *cfg {
return &cfg{moduleConfig: c.moduleConfig, internal: c.internal.Clone()}
}
// WithOSWorkdir implements Config.WithOSWorkdir
func (c *cfg) WithOSWorkdir() Config {
ret := c.clone()
ret.internal.OsWorkdir = true
return ret
}
// Run instantiates a new module and calls "run" with the given config.
//
// # Parameters
//
// - ctx: context to use when instantiating the module and calling "run".
// - r: runtime to instantiate both the host and guest (compiled) module in.
// - compiled: guest binary compiled with `GOOS=js GOARCH=wasm`
// - config: the Config to use including wazero.ModuleConfig or extensions of
// it.
//
// # Example
//
// After compiling your Wasm binary with wazero.Runtime's `CompileModule`, run
// it like below:
//
// // Instantiate host functions needed by gojs
// gojs.MustInstantiate(ctx, r)
//
// // Assign any configuration relevant for your compiled wasm.
// config := gojs.NewConfig(wazero.NewConfig())
//
// // Run your wasm, notably handing any ExitError
// err = gojs.Run(ctx, r, compiled, config)
// if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() != 0 {
// log.Panicln(err)
// } else if !ok {
// log.Panicln(err)
// }
//
// # Notes
//
// - Wasm generated by `GOOS=js GOARCH=wasm` is very slow to compile: Use
// wazero.RuntimeConfig with wazero.CompilationCache when re-running the
// same binary.
// - The guest module is closed after being run.
func Run(ctx context.Context, r wazero.Runtime, compiled wazero.CompiledModule, moduleConfig Config) error {
c := moduleConfig.(*cfg)
return run.Run(ctx, r, compiled, c.moduleConfig, c.internal)
}