github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/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/bananabytelabs/wazero" 24 "github.com/bananabytelabs/wazero/api" 25 "github.com/bananabytelabs/wazero/experimental/sys" 26 "github.com/bananabytelabs/wazero/internal/wasip1" 27 "github.com/bananabytelabs/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 exporter.ExportHostFunc(sockSendTo) 229 exporter.ExportHostFunc(sockRecvFrom) 230 exporter.ExportHostFunc(sockGetPeerAddr) 231 exporter.ExportHostFunc(sockGetLocalAddr) 232 exporter.ExportHostFunc(sockGetSockOpt) 233 exporter.ExportHostFunc(sockSetSockOpt) 234 exporter.ExportHostFunc(sockOpen) 235 exporter.ExportHostFunc(sockConnect) 236 } 237 238 // writeOffsetsAndNullTerminatedValues is used to write NUL-terminated values 239 // for args or environ, given a pre-defined bytesLen (which includes NUL 240 // terminators). 241 func writeOffsetsAndNullTerminatedValues(mem api.Memory, values [][]byte, offsets, bytes, bytesLen uint32) sys.Errno { 242 // The caller may not place bytes directly after offsets, so we have to 243 // read them independently. 244 valuesLen := len(values) 245 offsetsLen := uint32(valuesLen * 4) // uint32Le 246 offsetsBuf, ok := mem.Read(offsets, offsetsLen) 247 if !ok { 248 return sys.EFAULT 249 } 250 bytesBuf, ok := mem.Read(bytes, bytesLen) 251 if !ok { 252 return sys.EFAULT 253 } 254 255 // Loop through the values, first writing the location of its data to 256 // offsetsBuf[oI], then its NUL-terminated data at bytesBuf[bI] 257 var oI, bI uint32 258 for _, value := range values { 259 // Go can't guarantee inlining as there's not //go:inline directive. 260 // This inlines uint32 little-endian encoding instead. 261 bytesOffset := bytes + bI 262 offsetsBuf[oI] = byte(bytesOffset) 263 offsetsBuf[oI+1] = byte(bytesOffset >> 8) 264 offsetsBuf[oI+2] = byte(bytesOffset >> 16) 265 offsetsBuf[oI+3] = byte(bytesOffset >> 24) 266 oI += 4 // size of uint32 we just wrote 267 268 // Write the next value to memory with a NUL terminator 269 copy(bytesBuf[bI:], value) 270 bI += uint32(len(value)) 271 bytesBuf[bI] = 0 // NUL terminator 272 bI++ 273 } 274 275 return 0 276 } 277 278 func newHostFunc( 279 name string, 280 goFunc wasiFunc, 281 paramTypes []api.ValueType, 282 paramNames ...string, 283 ) *wasm.HostFunc { 284 return &wasm.HostFunc{ 285 ExportName: name, 286 Name: name, 287 ParamTypes: paramTypes, 288 ParamNames: paramNames, 289 ResultTypes: []api.ValueType{i32}, 290 ResultNames: []string{"errno"}, 291 Code: wasm.Code{GoFunc: goFunc}, 292 } 293 } 294 295 // wasiFunc special cases that all WASI functions return a single Errno 296 // result. The returned value will be written back to the stack at index zero. 297 type wasiFunc func(ctx context.Context, mod api.Module, params []uint64) sys.Errno 298 299 // Call implements the same method as documented on api.GoModuleFunction. 300 func (f wasiFunc) Call(ctx context.Context, mod api.Module, stack []uint64) { 301 // Write the result back onto the stack 302 errno := f(ctx, mod, stack) 303 if errno != 0 { 304 stack[0] = uint64(wasip1.ToErrno(errno)) 305 } else { // special case ass ErrnoSuccess is zero 306 stack[0] = 0 307 } 308 } 309 310 // stubFunction stubs for GrainLang per #271. 311 func stubFunction(name string, paramTypes []wasm.ValueType, paramNames ...string) *wasm.HostFunc { 312 return &wasm.HostFunc{ 313 ExportName: name, 314 Name: name, 315 ParamTypes: paramTypes, 316 ParamNames: paramNames, 317 ResultTypes: []api.ValueType{i32}, 318 ResultNames: []string{"errno"}, 319 Code: wasm.Code{ 320 GoFunc: api.GoModuleFunc(func(_ context.Context, _ api.Module, stack []uint64) { stack[0] = uint64(wasip1.ErrnoNosys) }), 321 }, 322 } 323 }