github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/imports/wasi_snapshot_preview1/wasi.go (about) 1 // Package wasi_snapshot_preview1 contains Go-defined functions to access 2 // system calls, such as opening a file, similar to Go's x/sys package. These 3 // are accessible from WebAssembly-defined functions via importing ModuleName. 4 // All WASI functions return a single Errno result: ErrnoSuccess on success. 5 // 6 // e.g. Call Instantiate before instantiating any wasm binary that imports 7 // "wasi_snapshot_preview1", Otherwise, it will error due to missing imports. 8 // 9 // ctx := context.Background() 10 // r := wazero.NewRuntime(ctx) 11 // defer r.Close(ctx) // This closes everything this Runtime created. 12 // 13 // wasi_snapshot_preview1.MustInstantiate(ctx, r) 14 // mod, _ := r.Instantiate(ctx, wasm) 15 // 16 // See https://github.com/WebAssembly/WASI 17 package wasi_snapshot_preview1 18 19 import ( 20 "context" 21 "encoding/binary" 22 23 "github.com/tetratelabs/wazero" 24 "github.com/tetratelabs/wazero/api" 25 "github.com/tetratelabs/wazero/experimental/sys" 26 "github.com/tetratelabs/wazero/internal/wasip1" 27 "github.com/tetratelabs/wazero/internal/wasm" 28 ) 29 30 // ModuleName is the module name WASI functions are exported into. 31 // 32 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md 33 const ModuleName = wasip1.InternalModuleName 34 35 const i32, i64 = wasm.ValueTypeI32, wasm.ValueTypeI64 36 37 var le = binary.LittleEndian 38 39 // MustInstantiate calls Instantiate or panics on error. 40 // 41 // This is a simpler function for those who know the module ModuleName is not 42 // already instantiated, and don't need to unload it. 43 func MustInstantiate(ctx context.Context, r wazero.Runtime) { 44 if _, err := Instantiate(ctx, r); err != nil { 45 panic(err) 46 } 47 } 48 49 // Instantiate instantiates the ModuleName module into the runtime. 50 // 51 // # Notes 52 // 53 // - Failure cases are documented on wazero.Runtime InstantiateModule. 54 // - Closing the wazero.Runtime has the same effect as closing the result. 55 func Instantiate(ctx context.Context, r wazero.Runtime) (api.Closer, error) { 56 return NewBuilder(r).Instantiate(ctx) 57 } 58 59 // Builder configures the ModuleName module for later use via Compile or Instantiate. 60 // 61 // # Notes 62 // 63 // - This is an interface for decoupling, not third-party implementations. 64 // All implementations are in wazero. 65 type Builder interface { 66 // Compile compiles the ModuleName module. Call this before Instantiate. 67 // 68 // Note: This has the same effect as the same function on wazero.HostModuleBuilder. 69 Compile(context.Context) (wazero.CompiledModule, error) 70 71 // Instantiate instantiates the ModuleName module and returns a function to close it. 72 // 73 // Note: This has the same effect as the same function on wazero.HostModuleBuilder. 74 Instantiate(context.Context) (api.Closer, error) 75 } 76 77 // NewBuilder returns a new Builder. 78 func NewBuilder(r wazero.Runtime) Builder { 79 return &builder{r} 80 } 81 82 type builder struct{ r wazero.Runtime } 83 84 // hostModuleBuilder returns a new wazero.HostModuleBuilder for ModuleName 85 func (b *builder) hostModuleBuilder() wazero.HostModuleBuilder { 86 ret := b.r.NewHostModuleBuilder(ModuleName) 87 exportFunctions(ret) 88 return ret 89 } 90 91 // Compile implements Builder.Compile 92 func (b *builder) Compile(ctx context.Context) (wazero.CompiledModule, error) { 93 return b.hostModuleBuilder().Compile(ctx) 94 } 95 96 // Instantiate implements Builder.Instantiate 97 func (b *builder) Instantiate(ctx context.Context) (api.Closer, error) { 98 return b.hostModuleBuilder().Instantiate(ctx) 99 } 100 101 // FunctionExporter exports functions into a wazero.HostModuleBuilder. 102 // 103 // # Notes 104 // 105 // - This is an interface for decoupling, not third-party implementations. 106 // All implementations are in wazero. 107 type FunctionExporter interface { 108 ExportFunctions(wazero.HostModuleBuilder) 109 } 110 111 // NewFunctionExporter returns a new FunctionExporter. This is used for the 112 // following two use cases: 113 // - Overriding a builtin function with an alternate implementation. 114 // - Exporting functions to the module "wasi_unstable" for legacy code. 115 // 116 // # Example of overriding default behavior 117 // 118 // // Export the default WASI functions. 119 // wasiBuilder := r.NewHostModuleBuilder(ModuleName) 120 // wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) 121 // 122 // // Subsequent calls to NewFunctionBuilder override built-in exports. 123 // wasiBuilder.NewFunctionBuilder(). 124 // WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) { 125 // // your custom logic 126 // }).Export("proc_exit") 127 // 128 // # Example of using the old module name for WASI 129 // 130 // // Instantiate the current WASI functions under the wasi_unstable 131 // // instead of wasi_snapshot_preview1. 132 // wasiBuilder := r.NewHostModuleBuilder("wasi_unstable") 133 // wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) 134 // _, err := wasiBuilder.Instantiate(testCtx, r) 135 func NewFunctionExporter() FunctionExporter { 136 return &functionExporter{} 137 } 138 139 type functionExporter struct{} 140 141 // ExportFunctions implements FunctionExporter.ExportFunctions 142 func (functionExporter) ExportFunctions(builder wazero.HostModuleBuilder) { 143 exportFunctions(builder) 144 } 145 146 // ## Translation notes 147 // ### String 148 // WebAssembly 1.0 has no string type, so any string input parameter expands to two uint32 parameters: offset 149 // and length. 150 // 151 // ### iovec_array 152 // `iovec_array` is encoded as two uin32le values (i32): offset and count. 153 // 154 // ### Result 155 // Each result besides Errno is always an uint32 parameter. WebAssembly 1.0 can have up to one result, 156 // which is already used by Errno. This forces other results to be parameters. A result parameter is a memory 157 // offset to write the result to. As memory offsets are uint32, each parameter representing a result is uint32. 158 // 159 // ### Errno 160 // The WASI specification is sometimes ambiguous resulting in some runtimes interpreting the same function ways. 161 // Errno mappings are not defined in WASI, yet, so these mappings are best efforts by maintainers. When in doubt 162 // about portability, first look at /RATIONALE.md and if needed an issue on 163 // https://github.com/WebAssembly/WASI/issues 164 // 165 // ## Memory 166 // In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means api.Memory is always the 167 // wasm.Store Memories index zero: `store.Memories[0].Buffer` 168 // 169 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md 170 // See https://github.com/WebAssembly/WASI/issues/215 171 // See https://wwa.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0. 172 173 // exportFunctions adds all go functions that implement wasi. 174 // These should be exported in the module named ModuleName. 175 func exportFunctions(builder wazero.HostModuleBuilder) { 176 exporter := builder.(wasm.HostFuncExporter) 177 178 // Note: these are ordered per spec for consistency even if the resulting 179 // map can't guarantee that. 180 // See https://github.com/WebAssembly/WASI/blob/snapshot-01/phases/snapshot/docs.md#functions 181 exporter.ExportHostFunc(argsGet) 182 exporter.ExportHostFunc(argsSizesGet) 183 exporter.ExportHostFunc(environGet) 184 exporter.ExportHostFunc(environSizesGet) 185 exporter.ExportHostFunc(clockResGet) 186 exporter.ExportHostFunc(clockTimeGet) 187 exporter.ExportHostFunc(fdAdvise) 188 exporter.ExportHostFunc(fdAllocate) 189 exporter.ExportHostFunc(fdClose) 190 exporter.ExportHostFunc(fdDatasync) 191 exporter.ExportHostFunc(fdFdstatGet) 192 exporter.ExportHostFunc(fdFdstatSetFlags) 193 exporter.ExportHostFunc(fdFdstatSetRights) 194 exporter.ExportHostFunc(fdFilestatGet) 195 exporter.ExportHostFunc(fdFilestatSetSize) 196 exporter.ExportHostFunc(fdFilestatSetTimes) 197 exporter.ExportHostFunc(fdPread) 198 exporter.ExportHostFunc(fdPrestatGet) 199 exporter.ExportHostFunc(fdPrestatDirName) 200 exporter.ExportHostFunc(fdPwrite) 201 exporter.ExportHostFunc(fdRead) 202 exporter.ExportHostFunc(fdReaddir) 203 exporter.ExportHostFunc(fdRenumber) 204 exporter.ExportHostFunc(fdSeek) 205 exporter.ExportHostFunc(fdSync) 206 exporter.ExportHostFunc(fdTell) 207 exporter.ExportHostFunc(fdWrite) 208 exporter.ExportHostFunc(pathCreateDirectory) 209 exporter.ExportHostFunc(pathFilestatGet) 210 exporter.ExportHostFunc(pathFilestatSetTimes) 211 exporter.ExportHostFunc(pathLink) 212 exporter.ExportHostFunc(pathOpen) 213 exporter.ExportHostFunc(pathReadlink) 214 exporter.ExportHostFunc(pathRemoveDirectory) 215 exporter.ExportHostFunc(pathRename) 216 exporter.ExportHostFunc(pathSymlink) 217 exporter.ExportHostFunc(pathUnlinkFile) 218 exporter.ExportHostFunc(pollOneoff) 219 exporter.ExportHostFunc(procExit) 220 exporter.ExportHostFunc(procRaise) 221 exporter.ExportHostFunc(schedYield) 222 exporter.ExportHostFunc(randomGet) 223 exporter.ExportHostFunc(sockAccept) 224 exporter.ExportHostFunc(sockRecv) 225 exporter.ExportHostFunc(sockSend) 226 exporter.ExportHostFunc(sockShutdown) 227 } 228 229 // writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values 230 // for args or environ, given a pre-defined bytesLen (which includes NUL 231 // terminators). 232 func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno { 233 // The caller may not place bytes directly after offsets, so we have to 234 // read them independently. 235 valuesLen := len(values) 236 offsetsLen := uint32(valuesLen * 4) // uint32Le 237 offsetsBuf, ok := mem.Read(offsets, offsetsLen) 238 if !ok { 239 return sys.EFAULT 240 } 241 bytesBuf, ok := mem.Read(bytes, bytesLen) 242 if !ok { 243 return sys.EFAULT 244 } 245 246 // Loop through the values, first writing the location of its data to 247 // offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI] 248 var oI, bI uint32 249 for _, value := range values { 250 // Go can't guarantee inlining as there's not //go:inline directive. 251 // This inlines uint32 little-endian encoding instead. 252 bytesOffset := bytes + bI 253 offsetsBuf[oI] = byte(bytesOffset) 254 offsetsBuf[oI+1] = byte(bytesOffset >> 8) 255 offsetsBuf[oI+2] = byte(bytesOffset >> 16) 256 offsetsBuf[oI+3] = byte(bytesOffset >> 24) 257 oI += 4 // size of uint32 we just wrote 258 259 // Write the next value to memory with a NUL terminator 260 copy(bytesBuf[bI:], value) 261 bI += uint32(len(value)) 262 bytesBuf[bI] = 0 // NUL terminator 263 bI++ 264 } 265 266 return 0 267 } 268 269 func newHostFunc( 270 name string, 271 goFunc wasiFunc, 272 paramTypes []api.ValueType, 273 paramNames ...string, 274 ) *wasm.HostFunc { 275 return &wasm.HostFunc{ 276 ExportName: name, 277 Name: name, 278 ParamTypes: paramTypes, 279 ParamNames: paramNames, 280 ResultTypes: []api.ValueType{i32}, 281 ResultNames: []string{"errno"}, 282 Code: wasm.Code{GoFunc: goFunc}, 283 } 284 } 285 286 // wasiFunc special cases that all WASI functions return a single Errno 287 // result. The returned value will be written back to the stack at index zero. 288 type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno 289 290 // Call implements the same method as documented on api.GoModuleFunction. 291 func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) { 292 // Write the result back onto the stack 293 errno := f(ctx, mod, stack) 294 if errno != 0 { 295 stack[0] = uint64(wasip1.ToErrno(errno)) 296 } else { // special case ass ErrnoSuccess is zero 297 stack[0] = 0 298 } 299 } 300 301 // stubFunction stubs for GrainLang per #271. 302 func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc { 303 return &wasm.HostFunc{ 304 ExportName: name, 305 Name: name, 306 ParamTypes: paramTypes, 307 ParamNames: paramNames, 308 ResultTypes: []api.ValueType{i32}, 309 ResultNames: []string{"errno"}, 310 Code: wasm.Code{ 311 GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }), 312 }, 313 } 314 }