cuelang.org/go@v0.10.1/cue/interpreter/wasm/wasm.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 "path/filepath" 19 "strings" 20 "sync" 21 22 "cuelang.org/go/cue/build" 23 "cuelang.org/go/cue/cuecontext" 24 "cuelang.org/go/cue/errors" 25 "cuelang.org/go/cue/token" 26 "cuelang.org/go/internal" 27 "cuelang.org/go/internal/core/adt" 28 coreruntime "cuelang.org/go/internal/core/runtime" 29 ) 30 31 // interpreter is a [cuecontext.ExternInterpreter] for Wasm files. 32 type interpreter struct{} 33 34 // New returns a new Wasm interpreter as a [cuecontext.ExternInterpreter] 35 // suitable for passing to [cuecontext.New]. 36 func New() cuecontext.ExternInterpreter { 37 return &interpreter{} 38 } 39 40 func (i *interpreter) Kind() string { 41 return "wasm" 42 } 43 44 // NewCompiler returns a Wasm compiler that services the specified 45 // build.Instance. 46 func (i *interpreter) NewCompiler(b *build.Instance, r *coreruntime.Runtime) (coreruntime.Compiler, errors.Error) { 47 return &compiler{ 48 b: b, 49 runtime: r, 50 wasmRuntime: newRuntime(), 51 instances: make(map[string]*instance), 52 }, nil 53 } 54 55 // A compiler is a [coreruntime.Compiler] 56 // that provides Wasm functionality to the runtime. 57 type compiler struct { 58 b *build.Instance 59 runtime *coreruntime.Runtime 60 wasmRuntime runtime 61 62 // mu serializes access to instances. 63 mu sync.Mutex 64 65 // instances maps absolute file names to compiled Wasm modules 66 // loaded into memory. 67 instances map[string]*instance 68 } 69 70 // Compile searches for a Wasm function described by the given `@extern` 71 // attribute and returns it as an [adt.Builtin] with the given function 72 // name. 73 func (c *compiler) Compile(funcName string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) { 74 baseFile, err := fileName(a) 75 if err != nil { 76 return nil, errors.Promote(err, "invalid attribute") 77 } 78 // TODO: once we have position information, make this 79 // error more user-friendly by returning the position. 80 if !strings.HasSuffix(baseFile, "wasm") { 81 return nil, errors.Newf(token.NoPos, "load %q: invalid file name", baseFile) 82 } 83 84 file, found := findFile(baseFile, c.b) 85 if !found { 86 return nil, errors.Newf(token.NoPos, "load %q: file not found", baseFile) 87 } 88 89 inst, err := c.instance(file) 90 if err != nil { 91 return nil, errors.Newf(token.NoPos, "can't load Wasm module: %v", err) 92 } 93 94 args, err := argList(a) 95 if err != nil { 96 return nil, errors.Newf(token.NoPos, "invalid function signature: %v", err) 97 } 98 builtin, err := generateCallThatReturnsBuiltin(funcName, scope, args, inst) 99 if err != nil { 100 return nil, errors.Newf(token.NoPos, "can't instantiate function: %v", err) 101 } 102 return builtin, nil 103 } 104 105 // instance returns the instance corresponding to filename, compiling 106 // and loading it if necessary. 107 func (c *compiler) instance(filename string) (inst *instance, err error) { 108 c.mu.Lock() 109 defer c.mu.Unlock() 110 inst, ok := c.instances[filename] 111 if !ok { 112 inst, err = c.wasmRuntime.compileAndLoad(filename) 113 if err != nil { 114 return nil, err 115 } 116 c.instances[filename] = inst 117 } 118 return inst, nil 119 } 120 121 // findFile searches the build.Instance for the given file name 122 // and reports its full name and whether it was found. 123 func findFile(name string, b *build.Instance) (path string, found bool) { 124 for _, f := range b.OrphanedFiles { 125 if filepath.Base(f.Filename) == name { 126 return f.Filename, true 127 } 128 } 129 return "", false 130 } 131 132 // fileName returns the file name of the external module specified in a, 133 // which must be an extern attribute. 134 func fileName(a *internal.Attr) (string, error) { 135 file, err := a.String(0) 136 if err != nil { 137 return "", err 138 } 139 return file, nil 140 }