cuelang.org/go@v0.13.0/cue/interpreter/wasm/runtime.go (about) 1 // Copyright 2023 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package wasm 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "os" 22 "sync" 23 24 "github.com/tetratelabs/wazero" 25 "github.com/tetratelabs/wazero/api" 26 "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" 27 ) 28 29 // A runtime is a Wasm runtime that can compile, load, and execute 30 // Wasm code. 31 type runtime struct { 32 // ctx exists so that we have something to pass to Wazero 33 // functions, but it's unused otherwise. 34 ctx context.Context 35 36 wazero.Runtime 37 } 38 39 func newRuntime() runtime { 40 ctx := context.Background() 41 r := wazero.NewRuntime(ctx) 42 wasi_snapshot_preview1.MustInstantiate(ctx, r) 43 44 return runtime{ 45 ctx: ctx, 46 Runtime: r, 47 } 48 } 49 50 // compile takes the name of a Wasm module, and returns its compiled 51 // form, or an error. 52 func (r *runtime) compile(name string) (*module, error) { 53 buf, err := os.ReadFile(name) 54 if err != nil { 55 return nil, fmt.Errorf("can't compile Wasm module: %w", err) 56 } 57 58 mod, err := r.Runtime.CompileModule(r.ctx, buf) 59 if err != nil { 60 return nil, fmt.Errorf("can't compile Wasm module: %w", err) 61 } 62 return &module{ 63 runtime: r, 64 name: name, 65 CompiledModule: mod, 66 }, nil 67 } 68 69 // compileAndLoad is a convenience method that compiles a module then 70 // loads it into memory returning the loaded instance, or an error. 71 func (r *runtime) compileAndLoad(name string) (*instance, error) { 72 m, err := r.compile(name) 73 if err != nil { 74 return nil, err 75 } 76 i, err := m.load() 77 if err != nil { 78 return nil, err 79 } 80 return i, nil 81 } 82 83 // A module is a compiled Wasm module. 84 type module struct { 85 *runtime 86 name string 87 wazero.CompiledModule 88 } 89 90 // load loads the compiled module into memory, returning a new instance 91 // that can be called into, or an error. Different instances of the 92 // same module do not share memory. 93 func (m *module) load() (*instance, error) { 94 cfg := wazero.NewModuleConfig().WithName(m.name) 95 wInst, err := m.Runtime.InstantiateModule(m.ctx, m.CompiledModule, cfg) 96 if err != nil { 97 return nil, fmt.Errorf("can't instantiate Wasm module: %w", err) 98 } 99 100 inst := instance{ 101 module: m, 102 instance: wInst, 103 alloc: wInst.ExportedFunction("allocate"), 104 free: wInst.ExportedFunction("deallocate"), 105 } 106 return &inst, nil 107 } 108 109 // An instance is a Wasm module loaded into memory. 110 type instance struct { 111 // mu serializes access the whole struct. 112 mu sync.Mutex 113 114 *module 115 instance api.Module 116 117 // alloc is a guest function that allocates guest memory on 118 // behalf of the host. 119 alloc api.Function 120 121 // free is a guest function that frees guest memory on 122 // behalf of the host. 123 free api.Function 124 } 125 126 // load attempts to load the named function from the instance, returning 127 // it if found, or an error. 128 func (i *instance) load(funcName string) (api.Function, error) { 129 i.mu.Lock() 130 defer i.mu.Unlock() 131 132 f := i.instance.ExportedFunction(funcName) 133 if f == nil { 134 return nil, fmt.Errorf("can't find function %q in Wasm module %v", funcName, i.module.Name()) 135 } 136 return f, nil 137 } 138 139 // Alloc returns a reference to newly allocated guest memory that spans 140 // the provided size. 141 func (i *instance) Alloc(size uint32) (*memory, error) { 142 i.mu.Lock() 143 defer i.mu.Unlock() 144 145 res, err := i.alloc.Call(i.ctx, uint64(size)) 146 if err != nil { 147 return nil, fmt.Errorf("can't allocate memory: requested %d bytes", size) 148 } 149 return &memory{ 150 i: i, 151 ptr: uint32(res[0]), 152 len: size, 153 }, nil 154 } 155 156 // Free frees previously allocated guest memory. 157 func (i *instance) Free(m *memory) { 158 i.mu.Lock() 159 defer i.mu.Unlock() 160 161 i.free.Call(i.ctx, uint64(m.ptr), uint64(m.len)) 162 } 163 164 // Free frees several previously allocated guest memories. 165 func (i *instance) FreeAll(ms []*memory) { 166 i.mu.Lock() 167 defer i.mu.Unlock() 168 169 for _, m := range ms { 170 i.free.Call(i.ctx, uint64(m.ptr), uint64(m.len)) 171 } 172 } 173 174 // memory is a read and write reference to guest memory that the host 175 // requested. 176 type memory struct { 177 i *instance 178 ptr uint32 179 len uint32 180 } 181 182 // Bytes return a copy of the contents of the guest memory to the host. 183 func (m *memory) Bytes() []byte { 184 m.i.mu.Lock() 185 defer m.i.mu.Unlock() 186 187 p, ok := m.i.instance.Memory().Read(m.ptr, m.len) 188 if !ok { 189 panic(fmt.Sprintf("can't read %d bytes from Wasm address %#x", m.len, m.ptr)) 190 } 191 return bytes.Clone(p) 192 } 193 194 // WriteAt writes p at the given relative offset within m. 195 // It panics if buf doesn't fit into m, or if off is out of bounds. 196 func (m *memory) WriteAt(p []byte, off int64) (int, error) { 197 if (off < 0) || (off >= 1<<32-1) { 198 panic(fmt.Sprintf("can't write %d bytes to Wasm address %#x", len(p), m.ptr)) 199 } 200 201 m.i.mu.Lock() 202 defer m.i.mu.Unlock() 203 204 ok := m.i.instance.Memory().Write(m.ptr+uint32(off), p) 205 if !ok { 206 panic(fmt.Sprintf("can't write %d bytes to Wasm address %#x", len(p), m.ptr)) 207 } 208 return len(p), nil 209 } 210 211 // Args returns a memory in the form of pair of arguments directly 212 // passable to Wasm. 213 func (m *memory) Args() []uint64 { 214 return []uint64{uint64(m.ptr), uint64(m.len)} 215 }