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 package assemblyscript
26
27 import (
28 "context"
29 "encoding/binary"
30 "fmt"
31 "io"
32 "strconv"
33 "strings"
34 "unicode/utf16"
35
36 "github.com/tetratelabs/wazero"
37 "github.com/tetratelabs/wazero/api"
38 experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
39 . "github.com/tetratelabs/wazero/internal/assemblyscript"
40 internalsys "github.com/tetratelabs/wazero/internal/sys"
41 "github.com/tetratelabs/wazero/internal/wasm"
42 "github.com/tetratelabs/wazero/sys"
43 )
44
45 const (
46 i32, f64 = wasm.ValueTypeI32, wasm.ValueTypeF64
47 )
48
49
50
51
52
53 func MustInstantiate(ctx context.Context, r wazero.Runtime) {
54 if _, err := Instantiate(ctx, r); err != nil {
55 panic(err)
56 }
57 }
58
59
60
61
62
63
64
65
66
67 func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) {
68 builder := r.NewHostModuleBuilder("env")
69 NewFunctionExporter().ExportFunctions(builder)
70 return builder.Instantiate(ctx)
71 }
72
73
74
75
76
77
78
79
80 type FunctionExporter interface {
81
82
83 WithAbortMessageDisabled() FunctionExporter
84
85
86
87 WithTraceToStdout() FunctionExporter
88
89
90
91
92
93
94 WithTraceToStderr() FunctionExporter
95
96
97
98 ExportFunctions(wazero.HostModuleBuilder)
99 }
100
101
102 func NewFunctionExporter() FunctionExporter {
103 return &functionExporter{abortFn: abortMessageEnabled, traceFn: traceDisabled}
104 }
105
106 type functionExporter struct {
107 abortFn, traceFn *wasm.HostFunc
108 }
109
110
111 func (e *functionExporter) WithAbortMessageDisabled() FunctionExporter {
112 return &functionExporter{abortFn: abortMessageDisabled, traceFn: e.traceFn}
113 }
114
115
116 func (e *functionExporter) WithTraceToStdout() FunctionExporter {
117 return &functionExporter{abortFn: e.abortFn, traceFn: traceStdout}
118 }
119
120
121 func (e *functionExporter) WithTraceToStderr() FunctionExporter {
122 return &functionExporter{abortFn: e.abortFn, traceFn: traceStderr}
123 }
124
125
126 func (e *functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) {
127 exporter := builder.(wasm.HostFuncExporter)
128 exporter.ExportHostFunc(e.abortFn)
129 exporter.ExportHostFunc(e.traceFn)
130 exporter.ExportHostFunc(seed)
131 }
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146 var abortMessageEnabled = &wasm.HostFunc{
147 ExportName: AbortName,
148 Name: "~lib/builtins/abort",
149 ParamTypes: []api.ValueType{i32, i32, i32, i32},
150 ParamNames: []string{"message", "fileName", "lineNumber", "columnNumber"},
151 Code: wasm.Code{GoFunc: api.GoModuleFunc(abortWithMessage)},
152 }
153
154 var abortMessageDisabled = abortMessageEnabled.WithGoModuleFunc(abort)
155
156
157 func abortWithMessage(ctx context.Context, mod api.Module, stack []uint64) {
158 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
159 mem := mod.Memory()
160
161 message := uint32(stack[0])
162 fileName := uint32(stack[1])
163 lineNumber := uint32(stack[2])
164 columnNumber := uint32(stack[3])
165
166
167 if stderr, ok := fsc.LookupFile(internalsys.FdStderr); ok {
168 if msg, msgOk := readAssemblyScriptString(mem, message); msgOk && stderr != nil {
169 if fn, fnOk := readAssemblyScriptString(mem, fileName); fnOk {
170 s := fmt.Sprintf("%s at %s:%d:%d\n", msg, fn, lineNumber, columnNumber)
171 _, _ = stderr.File.Write([]byte(s))
172 }
173 }
174 }
175 abort(ctx, mod, stack)
176 }
177
178
179 func abort(ctx context.Context, mod api.Module, _ []uint64) {
180
181
182 exitCode := uint32(255)
183
184
185 _ = mod.CloseWithExitCode(ctx, exitCode)
186
187
188 panic(sys.NewExitError(exitCode))
189 }
190
191
192 var traceDisabled = traceStdout.WithGoModuleFunc(func(context.Context, api.Module, []uint64) {})
193
194
195 var traceStdout = &wasm.HostFunc{
196 ExportName: TraceName,
197 Name: "~lib/builtins/trace",
198 ParamTypes: []api.ValueType{i32, i32, f64, f64, f64, f64, f64},
199 ParamNames: []string{"message", "nArgs", "arg0", "arg1", "arg2", "arg3", "arg4"},
200 Code: wasm.Code{
201 GoFunc: api.GoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) {
202 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
203 if stdout, ok := fsc.LookupFile(internalsys.FdStdout); ok {
204 traceTo(mod, stack, stdout.File)
205 }
206 }),
207 },
208 }
209
210
211 var traceStderr = traceStdout.WithGoModuleFunc(func(_ context.Context, mod api.Module, stack []uint64) {
212 fsc := mod.(*wasm.ModuleInstance).Sys.FS()
213 if stderr, ok := fsc.LookupFile(internalsys.FdStderr); ok {
214 traceTo(mod, stack, stderr.File)
215 }
216 })
217
218
219
220
221
222
223
224
225
226
227
228 func traceTo(mod api.Module, params []uint64, file experimentalsys.File) {
229 message := uint32(params[0])
230 nArgs := uint32(params[1])
231 arg0 := api.DecodeF64(params[2])
232 arg1 := api.DecodeF64(params[3])
233 arg2 := api.DecodeF64(params[4])
234 arg3 := api.DecodeF64(params[5])
235 arg4 := api.DecodeF64(params[6])
236
237 msg, ok := readAssemblyScriptString(mod.Memory(), message)
238 if !ok {
239 return
240 }
241 var ret strings.Builder
242 ret.WriteString("trace: ")
243 ret.WriteString(msg)
244 if nArgs >= 1 {
245 ret.WriteString(" ")
246 ret.WriteString(formatFloat(arg0))
247 }
248 if nArgs >= 2 {
249 ret.WriteString(",")
250 ret.WriteString(formatFloat(arg1))
251 }
252 if nArgs >= 3 {
253 ret.WriteString(",")
254 ret.WriteString(formatFloat(arg2))
255 }
256 if nArgs >= 4 {
257 ret.WriteString(",")
258 ret.WriteString(formatFloat(arg3))
259 }
260 if nArgs >= 5 {
261 ret.WriteString(",")
262 ret.WriteString(formatFloat(arg4))
263 }
264 ret.WriteByte('\n')
265 _, _ = file.Write([]byte(ret.String()))
266 }
267
268 func formatFloat(f float64) string {
269 return strconv.FormatFloat(f, 'g', -1, 64)
270 }
271
272
273
274
275
276
277
278
279
280
281 var seed = &wasm.HostFunc{
282 ExportName: SeedName,
283 Name: "~lib/builtins/seed",
284 ResultTypes: []api.ValueType{f64},
285 ResultNames: []string{"rand"},
286 Code: wasm.Code{
287 GoFunc: api.GoModuleFunc(func(ctx context.Context, mod api.Module, stack []uint64) {
288 r := mod.(*wasm.ModuleInstance).Sys.RandSource()
289 buf := make([]byte, 8)
290 _, err := io.ReadFull(r, buf)
291 if err != nil {
292 panic(fmt.Errorf("error reading random seed: %w", err))
293 }
294
295
296 stack[0] = binary.LittleEndian.Uint64(buf)
297 }),
298 },
299 }
300
301
302 func readAssemblyScriptString(mem api.Memory, offset uint32) (string, bool) {
303
304 byteCount, ok := mem.ReadUint32Le(offset - 4)
305 if !ok || byteCount%2 != 0 {
306 return "", false
307 }
308 buf, ok := mem.Read(offset, byteCount)
309 if !ok {
310 return "", false
311 }
312 return decodeUTF16(buf), true
313 }
314
315 func decodeUTF16(b []byte) string {
316 u16s := make([]uint16, len(b)/2)
317
318 lb := len(b)
319 for i := 0; i < lb; i += 2 {
320 u16s[i/2] = uint16(b[i]) + (uint16(b[i+1]) << 8)
321 }
322
323 return string(utf16.Decode(u16s))
324 }
325
View as plain text