this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cue/interpreter/wasm: add Wasm support for abi=c

Add a new package, cuelang.org/go/cue/interpreter/wasm that enables
Wasm support in CUE as an external interpreter. Code wishing to use
Wasm must use an `@extern("wasm")` package attribute. Individual
functions are imported from Wasm modules like so:

add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64")
mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64")
not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool")

Where "foo.wasm" is a compiled Wasm module, and sig is the type
signature of the imported function.

So far, only the C ABI is supported, and only relatively few data
types can be exchanged with the Wasm module. Basically only fixed-sized
integers of any signness, fixed-sized floats, and booleans.

The Wasm module is instantiated in a sandbox, with no access to the
outside world. Users must ensure the functions exposed by the Wasm
modules are pure, that is, they always returns the same answer for
the same arguments. Functions may make use of global state for
memoization and other optimizations as long as purity is preserved
as viewed from the outside. Be aware that functions from the standard
libraries of many languages are often not pure.

Wasm is only enabled if the user explicitly imports
cuelang.org/go/cue/wasm, otherwise it is not available. The Go cue
package does not impart upon the user a dependency on the Wasm
runtime if Wasm is not explicitly requested.

Wasm is enabled and available in the command line tool.

Updates #2035.
Updates #2281.
Updates #2282.
Updates #2007.

Change-Id: I844ec1229ea465dfeca45bc54006e87ed8ef0460
Signed-off-by: Aram Hăvărneanu <aram@mgk.ro>
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/551738
Unity-Result: CUEcueckoo <cueckoo+gerrithub@cuelang.org>
TryBot-Result: CUEcueckoo <cueckoo+gerrithub@cuelang.org>
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
Reviewed-by: Roger Peppe <rogpeppe@gmail.com>

authored by

Aram Hăvărneanu and committed by
Aram Hăvărneanu
0520a3f9 88922d17

+1296 -4
+2 -1
cmd/cue/cmd/root.go
··· 24 24 "cuelang.org/go/cue" 25 25 "cuelang.org/go/cue/cuecontext" 26 26 "cuelang.org/go/cue/errors" 27 + "cuelang.org/go/cue/interpreter/wasm" 27 28 "cuelang.org/go/internal/core/adt" 28 29 "cuelang.org/go/internal/encoding" 29 30 "cuelang.org/go/internal/filetypes" ··· 125 126 c := &Command{ 126 127 Command: cmd, 127 128 root: cmd, 128 - ctx: cuecontext.New(), 129 + ctx: cuecontext.New(cuecontext.Interpreter(wasm.New())), 129 130 } 130 131 131 132 cmdCmd := newCmdCmd(c)
+78
cue/interpreter/wasm/abi_c.go
··· 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 + "cuelang.org/go/internal/pkg" 19 + "github.com/tetratelabs/wazero/api" 20 + ) 21 + 22 + func decodeRet(r uint64, t typ) any { 23 + switch t { 24 + case _bool: 25 + u := api.DecodeU32(r) 26 + if u == 1 { 27 + return true 28 + } 29 + return false 30 + case _int8, _int16, _int32: 31 + return api.DecodeI32(r) 32 + case _uint8, _uint16, _uint32: 33 + return api.DecodeU32(r) 34 + case _int64, _uint64: 35 + return r 36 + case _float32: 37 + return api.DecodeF32(r) 38 + case _float64: 39 + return api.DecodeF64(r) 40 + } 41 + panic("unsupported return type") 42 + } 43 + 44 + // loadArg load the i'th argument (which must be of type t) 45 + // passed to a function call represented by the call context. 46 + // It returns the argument as an uint64, so it can be passed 47 + // directly to Wasm functions. 48 + func loadArg(c *pkg.CallCtxt, i int, t typ) uint64 { 49 + switch t { 50 + case _bool: 51 + b := c.Bool(i) 52 + if b { 53 + return api.EncodeU32(1) 54 + } 55 + return api.EncodeU32(0) 56 + case _int8: 57 + return api.EncodeI32(int32(c.Int8(i))) 58 + case _int16: 59 + return api.EncodeI32(int32(c.Int16(i))) 60 + case _int32: 61 + return api.EncodeI32(c.Int32(i)) 62 + case _int64: 63 + return api.EncodeI64(c.Int64(i)) 64 + case _uint8: 65 + return api.EncodeU32(uint32(c.Uint8(i))) 66 + case _uint16: 67 + return api.EncodeU32(uint32(c.Uint16(i))) 68 + case _uint32: 69 + return api.EncodeU32(c.Uint32(i)) 70 + case _uint64: 71 + return c.Uint64(i) 72 + case _float32: 73 + return api.EncodeF32(float32(c.Float64(i))) 74 + case _float64: 75 + return api.EncodeF64(c.Float64(i)) 76 + } 77 + panic("unsupported argument type") 78 + }
+48
cue/interpreter/wasm/builtin.go
··· 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 + "cuelang.org/go/internal/core/adt" 19 + "cuelang.org/go/internal/pkg" 20 + ) 21 + 22 + // builtin attempts to load the named function of type typ from the 23 + // instance, returning it as an *adt.Builtin if successful, otherwise 24 + // returning any encountered errors. 25 + func builtin(name string, typ fnTyp, i *instance) (*adt.Builtin, error) { 26 + b, err := loadBuiltin(name, typ, i) 27 + if err != nil { 28 + return nil, err 29 + } 30 + return pkg.ToBuiltin(b), nil 31 + } 32 + 33 + // loadBuiltin attempts to load the named function of type typ from 34 + // the instance, returning it as an *pkg.Builtin if successful, otherwise 35 + // returning any encountered errors. 36 + func loadBuiltin(name string, typ fnTyp, i *instance) (*pkg.Builtin, error) { 37 + fn, err := i.load(name) 38 + if err != nil { 39 + return nil, err 40 + } 41 + b := &pkg.Builtin{ 42 + Name: name, 43 + Params: params(typ), 44 + Result: typ.ret.kind(), 45 + Func: i.callCtxFunc(fn, typ), 46 + } 47 + return b, nil 48 + }
+157
cue/interpreter/wasm/runtime.go
··· 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 + "context" 19 + "fmt" 20 + "os" 21 + 22 + "cuelang.org/go/internal/pkg" 23 + "github.com/tetratelabs/wazero" 24 + "github.com/tetratelabs/wazero/api" 25 + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" 26 + ) 27 + 28 + // defaultRuntime is a global runtime for all Wasm modules used in a 29 + // CUE process. It acts as a compilation cache, however, every module 30 + // instance is independent. The same module loaded by two different 31 + // CUE packages will not share memory, although it will share the 32 + // excutable code produced by the runtime. 33 + var defaultRuntime runtime 34 + 35 + func init() { 36 + ctx := context.Background() 37 + defaultRuntime = runtime{ 38 + ctx: ctx, 39 + Runtime: newRuntime(ctx), 40 + } 41 + } 42 + 43 + // A runtime is a Wasm runtime that can compile, load, and execute 44 + // Wasm code. 45 + type runtime struct { 46 + // ctx exists so that we have something to pass to Wazero 47 + // functions, but it's unused otherwise. 48 + ctx context.Context 49 + 50 + wazero.Runtime 51 + } 52 + 53 + func newRuntime(ctx context.Context) wazero.Runtime { 54 + r := wazero.NewRuntime(ctx) 55 + wasi_snapshot_preview1.MustInstantiate(ctx, r) 56 + return r 57 + } 58 + 59 + // compile takes the name of a Wasm module, and returns its compiled 60 + // form, or an error. 61 + func (r *runtime) compile(name string) (*module, error) { 62 + buf, err := os.ReadFile(name) 63 + if err != nil { 64 + return nil, fmt.Errorf("can't compile Wasm module: %w", err) 65 + } 66 + 67 + mod, err := r.Runtime.CompileModule(r.ctx, buf) 68 + if err != nil { 69 + return nil, fmt.Errorf("can't compile Wasm module: %w", err) 70 + } 71 + return &module{ 72 + runtime: r, 73 + name: name, 74 + CompiledModule: mod, 75 + }, nil 76 + } 77 + 78 + // compileAndLoad is a convenience function that compile a module then 79 + // loads it into memory returning the loaded instance, or an error. 80 + func compileAndLoad(name string) (*instance, error) { 81 + m, err := defaultRuntime.compile(name) 82 + if err != nil { 83 + return nil, err 84 + } 85 + i, err := m.load() 86 + if err != nil { 87 + return nil, err 88 + } 89 + return i, nil 90 + } 91 + 92 + // A module is a compiled Wasm module. 93 + type module struct { 94 + *runtime 95 + name string 96 + wazero.CompiledModule 97 + } 98 + 99 + // load loads the compiled module into memory, returning a new instance 100 + // that can be called into, or an error. Different instances of the 101 + // same module do not share memory. 102 + func (m *module) load() (*instance, error) { 103 + cfg := wazero.NewModuleConfig().WithName(m.name) 104 + wInst, err := m.Runtime.InstantiateModule(m.ctx, m.CompiledModule, cfg) 105 + if err != nil { 106 + return nil, fmt.Errorf("can't instantiate Wasm module: %w", err) 107 + } 108 + 109 + inst := instance{ 110 + module: m, 111 + instance: wInst, 112 + } 113 + return &inst, nil 114 + } 115 + 116 + // An instance is a Wasm module loaded into memory. 117 + type instance struct { 118 + *module 119 + instance api.Module 120 + } 121 + 122 + // load attempts to load the named function from the instance, returning 123 + // it if found, or an error. 124 + func (i *instance) load(funcName string) (api.Function, error) { 125 + f := i.instance.ExportedFunction(funcName) 126 + if f == nil { 127 + return nil, fmt.Errorf("can't find function %q in Wasm module %v", funcName, i.module.Name()) 128 + } 129 + return f, nil 130 + } 131 + 132 + // callCtxFunc returns a function that wraps fn, which is assumed to 133 + // be of type typ, into a function that knows how to load its arguments 134 + // from CUE, call fn with the arguments, then pass its result 135 + // back to CUE. 136 + func (i *instance) callCtxFunc(fn api.Function, typ fnTyp) func(*pkg.CallCtxt) { 137 + return func(c *pkg.CallCtxt) { 138 + var args []uint64 139 + for k, t := range typ.args { 140 + // 141 + // TODO: support more than abi=c here. 142 + // 143 + args = append(args, loadArg(c, k, t)) 144 + } 145 + if c.Do() { 146 + results, err := fn.Call(i.ctx, args...) 147 + if err != nil { 148 + c.Err = err 149 + return 150 + } 151 + // 152 + // TODO: support more than abi=c here. 153 + // 154 + c.Ret = decodeRet(results[0], typ.ret) 155 + } 156 + } 157 + }
+8
cue/interpreter/wasm/testdata/README
··· 1 + The .wasm files are not generated by `go generate`, because we don't 2 + want to burden CUE developers with a Rust dependency. Rather, each 3 + .rs file contains instructions on how to compile it to Wasm in its 4 + header. 5 + 6 + A better option might be to have `go generate` compile the rust 7 + code conditional on some environment variable, but we don't have 8 + that yet.
+12
cue/interpreter/wasm/testdata/basic.golden
··· 1 + { 2 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 3 + mul: mul @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 4 + not: not() @extern("foo.wasm", abi=c, sig="func(bool): bool") 5 + x0: 3 6 + x1: 1 7 + x2: 101 8 + y0: 15.0 9 + y1: -8.425 10 + y2: 7.006652 11 + z: false 12 + }
+18
cue/interpreter/wasm/testdata/basic/foo.cue
··· 1 + // This file checks basic Wasm functionality. 2 + 3 + @extern("wasm") 4 + package p 5 + 6 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 7 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 8 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 9 + 10 + x0: add(1, 2) 11 + x1: add(-1, 2) 12 + x2: add(100, 1) 13 + 14 + y0: mul(3.0, 5.0) 15 + y1: mul(-2.5, 3.37) 16 + y2: mul(1.234, 5.678) 17 + 18 + z: not(true)
+27
cue/interpreter/wasm/testdata/basic/foo.rs
··· 1 + /* 2 + rustc -O --target wasm32-wasi --crate-type cdylib -C link-arg=--strip-debug -Cpanic=abort $% 3 + */ 4 + 5 + #![no_std] 6 + 7 + use core::panic::PanicInfo; 8 + 9 + #[panic_handler] 10 + fn panic(_info: &PanicInfo) -> ! { 11 + loop {} 12 + } 13 + 14 + #[no_mangle] 15 + pub extern "C" fn add(a: i64, b: i64) -> i64 { 16 + a + b 17 + } 18 + 19 + #[no_mangle] 20 + pub extern "C" fn mul(a: f64, b: f64) -> f64 { 21 + a * b 22 + } 23 + 24 + #[no_mangle] 25 + pub extern "C" fn not(x: bool) -> bool { 26 + !x 27 + }
cue/interpreter/wasm/testdata/basic/foo.wasm

This is a binary file and will not be displayed.

+116
cue/interpreter/wasm/testdata/def.txtar
··· 1 + exec cue def ./basic ./morewasm ./noload ./unusedwasm 2 + cmp stdout eval.out 3 + 4 + -- basic/foo.cue -- 5 + @extern("wasm") 6 + package p 7 + 8 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 9 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 10 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 11 + 12 + x0: add(1, 2) 13 + x1: add(-1, 2) 14 + x2: add(100, 1) 15 + 16 + y0: mul(3.0, 5.0) 17 + y1: mul(-2.5, 3.37) 18 + y2: mul(1.234, 5.678) 19 + 20 + z: not(true) 21 + -- basic/foo.wasm -- 22 + -- morewasm/foo.cue -- 23 + @extern("wasm") 24 + package p 25 + 26 + neg32: _ @extern("bar.wasm", abi=c, sig="func(int32): int32") 27 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 28 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 29 + 30 + x0: add(1, 2) 31 + x1: add(-1, 2) 32 + x2: add(100, 1) 33 + 34 + y0: mul(3.0, 5.0) 35 + y1: mul(-2.5, 3.37) 36 + y2: mul(1.234, 5.678) 37 + 38 + z: not(true) 39 + -- morewasm/bar.cue -- 40 + @extern("wasm") 41 + package p 42 + 43 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 44 + isPrime: _ @extern("bar.wasm", abi=c, name=is_prime, sig="func(uint64): bool") 45 + fact: _ @extern("bar.wasm", abi=c, sig="func(uint64): uint64") 46 + 47 + a0: neg32(42) 48 + 49 + b1: isPrime(127) 50 + b2: isPrime(128) 51 + 52 + c1: fact(7) 53 + c2: fact(9) 54 + -- morewasm/foo.wasm -- 55 + -- morewasm/bar.wasm -- 56 + -- noload/foo.cue -- 57 + @extern("wasm") 58 + package p 59 + 60 + x: 42 61 + -- unusedwasm/foo.cue -- 62 + @extern("wasm") 63 + package p 64 + 65 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 66 + 67 + x0: add(1, 2) 68 + x1: add(-1, 2) 69 + x2: add(100, 1) 70 + -- unusedwasm/foo.wasm -- 71 + -- unusedwasm/empty.wasm -- 72 + -- eval.out -- 73 + package p 74 + 75 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 76 + mul: mul @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 77 + not: not() @extern("foo.wasm", abi=c, sig="func(bool): bool") 78 + x0: add(1, 2) 79 + x1: add(-1, 2) 80 + x2: add(100, 1) 81 + y0: mul(3.0, 5.0) 82 + y1: mul(-2.5, 3.37) 83 + y2: mul(1.234, 5.678) 84 + z: not(true) 85 + // --- 86 + package p 87 + 88 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 89 + isPrime: is_prime() @extern("bar.wasm", abi=c, name=is_prime, sig="func(uint64): bool") 90 + fact: fact @extern("bar.wasm", abi=c, sig="func(uint64): uint64") 91 + a0: neg32(42) 92 + b1: isPrime(127) 93 + b2: isPrime(128) 94 + c1: fact(7) 95 + neg32: neg32 @extern("bar.wasm", abi=c, sig="func(int32): int32") 96 + mul: mul @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 97 + not: not() @extern("foo.wasm", abi=c, sig="func(bool): bool") 98 + x0: add(1, 2) 99 + x1: add(-1, 2) 100 + x2: add(100, 1) 101 + y0: mul(3.0, 5.0) 102 + y1: mul(-2.5, 3.37) 103 + y2: mul(1.234, 5.678) 104 + c2: fact(9) 105 + z: not(true) 106 + // --- 107 + package p 108 + 109 + x: 42 110 + // --- 111 + package p 112 + 113 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 114 + x0: add(1, 2) 115 + x1: add(-1, 2) 116 + x2: add(100, 1)
+36
cue/interpreter/wasm/testdata/error.txtar
··· 1 + ! exec cue eval -E -i -a ./nowasm ./wrongsig ./wrongwasm 2 + cmp stderr eval.out 3 + 4 + -- nowasm/foo.cue -- 5 + package p 6 + 7 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 8 + 9 + x00: add(1, 2) 10 + -- wrongsig/foo.cue -- 11 + @extern("wasm") 12 + package p 13 + 14 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64)") 15 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): []") 16 + not: _ @extern("foo.wasm", abi=c, sig="func(*): bool") 17 + -- wrongsig/foo.wasm -- 18 + -- wrongwasm/foo.cue -- 19 + @extern("wasm") 20 + package p 21 + 22 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 23 + sub: _ @extern("foo.wasm1", abi=c, sig="func(int64, int64): int64") 24 + 25 + x: add(1, 2) 26 + -- eval.out -- 27 + can't load from external module: invalid function signature: expected ':', found newline: 28 + ./wrongsig/foo.cue:4:8 29 + can't load from external module: invalid function signature: expected identifier, found *ast.ListLit: 30 + ./wrongsig/foo.cue:5:8 31 + can't load from external module: invalid function signature: expected operand, found ')': 32 + ./wrongsig/foo.cue:6:8 33 + can't load from external module: load "foo.wasm": file not found: 34 + ./wrongwasm/foo.cue:4:8 35 + can't load from external module: load "foo.wasm1": invalid file name: 36 + ./wrongwasm/foo.cue:5:8
+108
cue/interpreter/wasm/testdata/eval.txtar
··· 1 + exec cue eval -a ./basic ./morewasm ./noload ./unusedwasm 2 + cmp stdout eval.out 3 + 4 + -- basic/foo.cue -- 5 + @extern("wasm") 6 + package p 7 + 8 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 9 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 10 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 11 + 12 + x0: add(1, 2) 13 + x1: add(-1, 2) 14 + x2: add(100, 1) 15 + 16 + y0: mul(3.0, 5.0) 17 + y1: mul(-2.5, 3.37) 18 + y2: mul(1.234, 5.678) 19 + 20 + z: not(true) 21 + -- basic/foo.wasm -- 22 + -- morewasm/foo.cue -- 23 + @extern("wasm") 24 + package p 25 + 26 + neg32: _ @extern("bar.wasm", abi=c, sig="func(int32): int32") 27 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 28 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 29 + 30 + x0: add(1, 2) 31 + x1: add(-1, 2) 32 + x2: add(100, 1) 33 + 34 + y0: mul(3.0, 5.0) 35 + y1: mul(-2.5, 3.37) 36 + y2: mul(1.234, 5.678) 37 + 38 + z: not(true) 39 + -- morewasm/bar.cue -- 40 + @extern("wasm") 41 + package p 42 + 43 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 44 + isPrime: _ @extern("bar.wasm", abi=c, name=is_prime, sig="func(uint64): bool") 45 + fact: _ @extern("bar.wasm", abi=c, sig="func(uint64): uint64") 46 + 47 + a0: neg32(42) 48 + 49 + b1: isPrime(127) 50 + b2: isPrime(128) 51 + 52 + c1: fact(7) 53 + c2: fact(9) 54 + -- morewasm/foo.wasm -- 55 + -- morewasm/bar.wasm -- 56 + -- noload/foo.cue -- 57 + @extern("wasm") 58 + package p 59 + 60 + x: 42 61 + -- unusedwasm/foo.cue -- 62 + @extern("wasm") 63 + package p 64 + 65 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 66 + 67 + x0: add(1, 2) 68 + x1: add(-1, 2) 69 + x2: add(100, 1) 70 + -- unusedwasm/foo.wasm -- 71 + -- unusedwasm/empty.wasm -- 72 + -- eval.out -- 73 + add: add 74 + mul: mul 75 + not: not() 76 + x0: 3 77 + x1: 1 78 + x2: 101 79 + y0: 15.0 80 + y1: -8.425 81 + y2: 7.006652 82 + z: false 83 + // --- 84 + add: add 85 + isPrime: is_prime() 86 + fact: fact 87 + a0: -42 88 + b1: true 89 + b2: false 90 + c1: 5040 91 + neg32: neg32 92 + mul: mul 93 + not: not() 94 + x0: 3 95 + x1: 1 96 + x2: 101 97 + y0: 15.0 98 + y1: -8.425 99 + y2: 7.006652 100 + c2: 362880 101 + z: false 102 + // --- 103 + x: 42 104 + // --- 105 + add: add 106 + x0: 3 107 + x1: 1 108 + x2: 101
+20
cue/interpreter/wasm/testdata/morewasm.golden
··· 1 + { 2 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 3 + isPrime: is_prime() @extern("bar.wasm", abi=c, name=is_prime, sig="func(uint64): bool") 4 + fact: fact @extern("bar.wasm", abi=c, sig="func(uint64): uint64") 5 + a0: -42 6 + b1: true 7 + b2: false 8 + c1: 5040 9 + neg32: neg32 @extern("bar.wasm", abi=c, sig="func(int32): int32") 10 + mul: mul @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 11 + not: not() @extern("foo.wasm", abi=c, sig="func(bool): bool") 12 + x0: 3 13 + x1: 1 14 + x2: 101 15 + y0: 15.0 16 + y1: -8.425 17 + y2: 7.006652 18 + c2: 362880 19 + z: false 20 + }
+17
cue/interpreter/wasm/testdata/morewasm/bar.cue
··· 1 + // These files check that we can load multiple Wasm modules into the 2 + // same CUE package. 3 + 4 + @extern("wasm") 5 + package p 6 + 7 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 8 + isPrime: _ @extern("bar.wasm", abi=c, name=is_prime, sig="func(uint64): bool") 9 + fact: _ @extern("bar.wasm", abi=c, sig="func(uint64): uint64") 10 + 11 + a0: neg32(42) 12 + 13 + b1: isPrime(127) 14 + b2: isPrime(128) 15 + 16 + c1: fact(7) 17 + c2: fact(9)
+35
cue/interpreter/wasm/testdata/morewasm/bar.rs
··· 1 + /* 2 + rustc -O --target wasm32-wasi --crate-type cdylib -C link-arg=--strip-debug -Cpanic=abort $% 3 + */ 4 + 5 + #![no_std] 6 + 7 + use core::panic::PanicInfo; 8 + 9 + #[panic_handler] 10 + fn panic(_info: &PanicInfo) -> ! { 11 + loop {} 12 + } 13 + 14 + #[no_mangle] 15 + pub extern "C" fn is_prime(n: u64) -> bool { 16 + for k in 2..n { 17 + if n % k == 0 { 18 + return false; 19 + } 20 + } 21 + true 22 + } 23 + 24 + #[no_mangle] 25 + pub extern "C" fn fact(n: u64) -> u64 { 26 + if n == 1 { 27 + return 1; 28 + } 29 + n * fact(n - 1) 30 + } 31 + 32 + #[no_mangle] 33 + pub extern "C" fn neg32(x: i32) -> i32 { 34 + -x 35 + }
cue/interpreter/wasm/testdata/morewasm/bar.wasm

This is a binary file and will not be displayed.

+19
cue/interpreter/wasm/testdata/morewasm/foo.cue
··· 1 + // These files check that we can load multiple Wasm modules into the 2 + // same CUE package. 3 + 4 + @extern("wasm") 5 + package p 6 + 7 + neg32: _ @extern("bar.wasm", abi=c, sig="func(int32): int32") 8 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): float64") 9 + not: _ @extern("foo.wasm", abi=c, sig="func(bool): bool") 10 + 11 + x0: add(1, 2) 12 + x1: add(-1, 2) 13 + x2: add(100, 1) 14 + 15 + y0: mul(3.0, 5.0) 16 + y1: mul(-2.5, 3.37) 17 + y2: mul(1.234, 5.678) 18 + 19 + z: not(true)
+27
cue/interpreter/wasm/testdata/morewasm/foo.rs
··· 1 + /* 2 + rustc -O --target wasm32-wasi --crate-type cdylib -C link-arg=--strip-debug -Cpanic=abort $% 3 + */ 4 + 5 + #![no_std] 6 + 7 + use core::panic::PanicInfo; 8 + 9 + #[panic_handler] 10 + fn panic(_info: &PanicInfo) -> ! { 11 + loop {} 12 + } 13 + 14 + #[no_mangle] 15 + pub extern "C" fn add(a: i64, b: i64) -> i64 { 16 + a + b 17 + } 18 + 19 + #[no_mangle] 20 + pub extern "C" fn mul(a: f64, b: f64) -> f64 { 21 + a * b 22 + } 23 + 24 + #[no_mangle] 25 + pub extern "C" fn not(x: bool) -> bool { 26 + !x 27 + }
cue/interpreter/wasm/testdata/morewasm/foo.wasm

This is a binary file and will not be displayed.

+3
cue/interpreter/wasm/testdata/noload.golden
··· 1 + { 2 + x: 42 3 + }
+6
cue/interpreter/wasm/testdata/noload/foo.cue
··· 1 + // This file check that we can enable Wasm without using it. 2 + 3 + @extern("wasm") 4 + package p 5 + 6 + x: 42
+4
cue/interpreter/wasm/testdata/nowasm.golden
··· 1 + { 2 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 3 + x00: _|_ // x00: cannot call non-function add (type _) 4 + }
+8
cue/interpreter/wasm/testdata/nowasm/foo.cue
··· 1 + // This file checks that we can't load Wasm without enabling it at the 2 + // package-level. 3 + 4 + package p 5 + 6 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 7 + 8 + x00: add(1, 2)
+6
cue/interpreter/wasm/testdata/unusedwasm.golden
··· 1 + { 2 + add: add @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 3 + x0: 3 4 + x1: 1 5 + x2: 101 6 + }
cue/interpreter/wasm/testdata/unusedwasm/empty.wasm

This is a binary file and will not be displayed.

+11
cue/interpreter/wasm/testdata/unusedwasm/foo.cue
··· 1 + // This file checks that an unused Wasm module does not impact CUE 2 + // evaluation. 3 + 4 + @extern("wasm") 5 + package p 6 + 7 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 8 + 9 + x0: add(1, 2) 10 + x1: add(-1, 2) 11 + x2: add(100, 1)
+17
cue/interpreter/wasm/testdata/unusedwasm/foo.rs
··· 1 + /* 2 + rustc -O --target wasm32-wasi --crate-type cdylib -C link-arg=--strip-debug -Cpanic=abort $% 3 + */ 4 + 5 + #![no_std] 6 + 7 + use core::panic::PanicInfo; 8 + 9 + #[panic_handler] 10 + fn panic(_info: &PanicInfo) -> ! { 11 + loop {} 12 + } 13 + 14 + #[no_mangle] 15 + pub extern "C" fn add(a: i64, b: i64) -> i64 { 16 + a + b 17 + }
cue/interpreter/wasm/testdata/unusedwasm/foo.wasm

This is a binary file and will not be displayed.

+1
cue/interpreter/wasm/testdata/wrongsig.golden
··· 1 + _|_ // can't load from external module: invalid function signature: expected ':', found newline (and 2 more errors)
+8
cue/interpreter/wasm/testdata/wrongsig/foo.cue
··· 1 + // This file checks that invalid function signatures cause errors. 2 + 3 + @extern("wasm") 4 + package p 5 + 6 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64)") 7 + mul: _ @extern("foo.wasm", abi=c, sig="func(float64, float64): []") 8 + not: _ @extern("foo.wasm", abi=c, sig="func(*): bool")
+27
cue/interpreter/wasm/testdata/wrongsig/foo.rs
··· 1 + /* 2 + rustc -O --target wasm32-wasi --crate-type cdylib -C link-arg=--strip-debug -Cpanic=abort $% 3 + */ 4 + 5 + #![no_std] 6 + 7 + use core::panic::PanicInfo; 8 + 9 + #[panic_handler] 10 + fn panic(_info: &PanicInfo) -> ! { 11 + loop {} 12 + } 13 + 14 + #[no_mangle] 15 + pub extern "C" fn add(a: i64, b: i64) -> i64 { 16 + a + b 17 + } 18 + 19 + #[no_mangle] 20 + pub extern "C" fn mul(a: f64, b: f64) -> f64 { 21 + a * b 22 + } 23 + 24 + #[no_mangle] 25 + pub extern "C" fn not(x: bool) -> bool { 26 + !x 27 + }
cue/interpreter/wasm/testdata/wrongsig/foo.wasm

This is a binary file and will not be displayed.

+1
cue/interpreter/wasm/testdata/wrongwasm.golden
··· 1 + _|_ // can't load from external module: load "foo.wasm": file not found (and 1 more errors)
+9
cue/interpreter/wasm/testdata/wrongwasm/foo.cue
··· 1 + // This file checks that missing Wasm modules cause errors. 2 + 3 + @extern("wasm") 4 + package p 5 + 6 + add: _ @extern("foo.wasm", abi=c, sig="func(int64, int64): int64") 7 + sub: _ @extern("foo.wasm1", abi=c, sig="func(int64, int64): int64") 8 + 9 + x: add(1, 2)
+113
cue/interpreter/wasm/types.go
··· 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 + "cuelang.org/go/internal/core/adt" 19 + "cuelang.org/go/internal/core/compile" 20 + "cuelang.org/go/internal/pkg" 21 + ) 22 + 23 + // typ represents the Go and CUE types that we can exchange with Wasm. 24 + // It doesn't map directly to Wasm types, for example Wasm doesn't 25 + // have a bool type, but we can use this type as a marker to remember 26 + // to convert between a Wasm integer and a Go bool type. 27 + type typ string 28 + 29 + const ( 30 + _bool typ = "bool" 31 + _int8 = "int8" 32 + _int16 = "int16 " 33 + _int32 = "int32" 34 + _int64 = "int64" 35 + _uint8 = "uint8" 36 + _uint16 = "uint16" 37 + _uint32 = "uint32" 38 + _uint64 = "uint64" 39 + _float32 = "float32" 40 + _float64 = "float64" 41 + ) 42 + 43 + func (t typ) kind() adt.Kind { 44 + switch t { 45 + case _bool: 46 + return adt.BoolKind 47 + case _int8, _int16, _int32, _int64, _uint8, _uint16, _uint32, _uint64: 48 + return adt.IntKind 49 + case _float32, _float64: 50 + return adt.FloatKind 51 + } 52 + return adt.BottomKind 53 + } 54 + 55 + func (t typ) value() adt.Value { 56 + if isScalar(t) { 57 + return predeclared(string(t)).(adt.Value) 58 + } 59 + return nil 60 + } 61 + 62 + func (t typ) param() pkg.Param { 63 + p := pkg.Param{ 64 + Kind: t.kind(), 65 + Value: t.value(), 66 + } 67 + return p 68 + } 69 + 70 + func predeclared(s string) adt.Expr { 71 + switch s { 72 + case "bool": 73 + return &adt.BasicType{K: adt.BoolKind} 74 + case "int8", "int16", "int32", "int64", 75 + "uint8", "uint16", "uint32", "uint64", 76 + "float32", "float64": 77 + return compile.LookupRange(s) 78 + } 79 + panic("unexpected type") 80 + } 81 + 82 + func params(f fnTyp) []pkg.Param { 83 + var params []pkg.Param 84 + for _, v := range f.args { 85 + params = append(params, v.param()) 86 + } 87 + return params 88 + } 89 + 90 + func isScalar(t typ) bool { 91 + return is32(t) || is64(t) 92 + } 93 + 94 + func is32(t typ) bool { 95 + switch t { 96 + case _bool, _int8, _int16, _int32, _uint8, _uint16, _uint32: 97 + return true 98 + } 99 + return false 100 + } 101 + 102 + func is64(t typ) bool { 103 + switch t { 104 + case _uint64, _int64: 105 + return true 106 + } 107 + return false 108 + } 109 + 110 + type fnTyp struct { 111 + args []typ 112 + ret typ 113 + }
+183
cue/interpreter/wasm/wasm.go
··· 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 + 21 + "cuelang.org/go/cue/ast" 22 + "cuelang.org/go/cue/build" 23 + "cuelang.org/go/cue/cuecontext" 24 + "cuelang.org/go/cue/errors" 25 + "cuelang.org/go/cue/parser" 26 + "cuelang.org/go/cue/token" 27 + "cuelang.org/go/internal" 28 + "cuelang.org/go/internal/core/adt" 29 + coreruntime "cuelang.org/go/internal/core/runtime" 30 + ) 31 + 32 + // interpreter is a [cuecontext.ExternInterpreter] for Wasm files. 33 + type interpreter struct{} 34 + 35 + // New returns a new Wasm interpreter as a [cuecontext.ExternInterpreter] 36 + // suitable for passing to [cuecontext.New]. 37 + func New() cuecontext.ExternInterpreter { 38 + return &interpreter{} 39 + } 40 + 41 + func (i *interpreter) Kind() string { 42 + return "wasm" 43 + } 44 + 45 + // NewCompiler returns a Wasm compiler that services the specified 46 + // build.Instance. 47 + func (i *interpreter) NewCompiler(b *build.Instance) (coreruntime.Compiler, errors.Error) { 48 + return &compiler{ 49 + b: b, 50 + instances: make(map[string]*instance), 51 + }, nil 52 + } 53 + 54 + // A compiler is a [coreruntime.Compiler] 55 + // that provides Wasm functionality to the runtime. 56 + type compiler struct { 57 + b *build.Instance 58 + 59 + // instances maps absolute file names to compiled Wasm modules 60 + // loaded into memory. 61 + instances map[string]*instance 62 + } 63 + 64 + // Compile searches for a Wasm function described by the given `@extern` 65 + // attribute and returns it as an [adt.Builtin] with the given function 66 + // name. 67 + func (c *compiler) Compile(funcName string, a *internal.Attr) (*adt.Builtin, errors.Error) { 68 + file, err := fileName(a) 69 + if err != nil { 70 + return nil, errors.Promote(err, "invalid attribute") 71 + } 72 + // TODO: once we have position information, make this 73 + // error more user-friendly by returning the position. 74 + if !strings.HasSuffix(file, "wasm") { 75 + return nil, errors.Newf(token.NoPos, "load %q: invalid file name", file) 76 + } 77 + 78 + file, found := findFile(file, c.b) 79 + if !found { 80 + return nil, errors.Newf(token.NoPos, "load %q: file not found", file) 81 + } 82 + 83 + inst, err := c.instance(file) 84 + if err != nil { 85 + return nil, errors.Newf(token.NoPos, "can't load Wasm module: %v", err) 86 + } 87 + 88 + funcType, err := funcType(a) 89 + if err != nil { 90 + return nil, errors.Newf(token.NoPos, "invalid function signature: %v", err) 91 + } 92 + 93 + builtin, err := builtin(funcName, funcType, inst) 94 + if err != nil { 95 + return nil, errors.Newf(token.NoPos, "can't instantiate function: %v", err) 96 + } 97 + return builtin, nil 98 + } 99 + 100 + // instance returns the instance corresponding to filename, compiling 101 + // and loading it if necessary. 102 + func (c *compiler) instance(filename string) (inst *instance, err error) { 103 + inst, ok := c.instances[filename] 104 + if !ok { 105 + inst, err = compileAndLoad(filename) 106 + if err != nil { 107 + return nil, err 108 + } 109 + c.instances[filename] = inst 110 + } 111 + return inst, nil 112 + } 113 + 114 + // findFile searches the build.Instance for name. If found, it returnes 115 + // its full path name and true, otherwise it returns the original name 116 + // and false. 117 + func findFile(name string, b *build.Instance) (path string, found bool) { 118 + for _, f := range b.UnknownFiles { 119 + if f.Filename == name { 120 + return filepath.Join(b.Dir, name), true 121 + } 122 + } 123 + return name, false 124 + } 125 + 126 + // fileName returns the file name of the external module specified in a, 127 + // which must be an extern attribute. 128 + func fileName(a *internal.Attr) (string, error) { 129 + file, err := a.String(0) 130 + if err != nil { 131 + return "", err 132 + } 133 + return file, nil 134 + } 135 + 136 + // funcType parses the attribute and returns the found function signature 137 + // as a fnTyp, or an error. 138 + func funcType(a *internal.Attr) (fnTyp, error) { 139 + funcSig, ok, err := a.Lookup(1, "sig") 140 + if err != nil { 141 + return fnTyp{}, err 142 + } 143 + if !ok { 144 + return fnTyp{}, errors.New(`missing "sig" key`) 145 + } 146 + return parseFuncSig(funcSig) 147 + } 148 + 149 + // parseFuncSig parses the string and returns the found function 150 + // signature as a fnTyp, or an error. 151 + func parseFuncSig(sig string) (fnTyp, error) { 152 + expr, err := parser.ParseExpr("", sig, parser.ParseFuncs) 153 + if err != nil { 154 + return fnTyp{}, err 155 + } 156 + return toFnTyp(expr) 157 + } 158 + 159 + // toFnTyp convert e, which must be an *ast.Func, to a fnTyp. 160 + func toFnTyp(e ast.Expr) (fnTyp, error) { 161 + f, ok := e.(*ast.Func) 162 + if !ok { 163 + // TODO: once we have position information, make this 164 + // error more user-friendly by returning the position. 165 + return fnTyp{}, errors.New("not a function") 166 + } 167 + 168 + var args []typ 169 + for _, arg := range append(f.Args, f.Ret) { 170 + switch v := arg.(type) { 171 + case *ast.Ident: 172 + args = append(args, typ(v.Name)) 173 + default: 174 + // TODO: once we have position information, make this 175 + // error more user-friendly by returning the position. 176 + return fnTyp{}, errors.Newf(token.NoPos, "expected identifier, found %T", v) 177 + } 178 + } 179 + return fnTyp{ 180 + args: args[:len(args)-1], 181 + ret: args[len(args)-1], 182 + }, nil 183 + }
+160
cue/interpreter/wasm/wasm_test.go
··· 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_test 16 + 17 + import ( 18 + "io/fs" 19 + "os" 20 + "path/filepath" 21 + "runtime" 22 + "strings" 23 + "testing" 24 + 25 + "cuelang.org/go/cmd/cue/cmd" 26 + "cuelang.org/go/cue" 27 + "cuelang.org/go/cue/ast" 28 + "cuelang.org/go/cue/build" 29 + "cuelang.org/go/cue/cuecontext" 30 + "cuelang.org/go/cue/format" 31 + "cuelang.org/go/cue/interpreter/wasm" 32 + "cuelang.org/go/cue/parser" 33 + "cuelang.org/go/internal/cuetest" 34 + 35 + "github.com/rogpeppe/go-internal/gotooltest" 36 + "github.com/rogpeppe/go-internal/testscript" 37 + ) 38 + 39 + // We are using TestMain because we want to ensure Wasm is enabled and 40 + // works as expected with the command-line tool. 41 + func TestMain(m *testing.M) { 42 + os.Exit(testscript.RunMain(m, map[string]func() int{ 43 + "cue": cmd.MainTest, 44 + })) 45 + } 46 + 47 + // TestExe tests Wasm using the command-line tool. 48 + func TestExe(t *testing.T) { 49 + root := must(filepath.Abs("testdata"))(t) 50 + p := testscript.Params{ 51 + Dir: "testdata", 52 + UpdateScripts: cuetest.UpdateGoldenFiles, 53 + RequireExplicitExec: true, 54 + Setup: func(e *testscript.Env) error { 55 + copyWasmFiles(t, e.WorkDir, root) 56 + return nil 57 + }, 58 + Condition: cuetest.Condition, 59 + } 60 + if err := gotooltest.Setup(&p); err != nil { 61 + t.Fatal(err) 62 + } 63 + testscript.Run(t, p) 64 + } 65 + 66 + // TestWasm tests Wasm using the API. 67 + func TestWasm(t *testing.T) { 68 + files := must(os.ReadDir("testdata"))(t) 69 + for _, f := range files { 70 + if !f.IsDir() { 71 + continue 72 + } 73 + dir := f.Name() 74 + t.Run(dir, func(t *testing.T) { 75 + v := loadDir(t, filepath.Join("testdata", dir)) 76 + got := must(format.Node(v.Syntax( 77 + cue.All(), cue.Concrete(true), cue.ErrorsAsValues(true), 78 + )))(t) 79 + check(t, dir, string(got)) 80 + }) 81 + } 82 + } 83 + 84 + func copyWasmFiles(t *testing.T, dstDir, srcDir string) { 85 + filepath.WalkDir(dstDir, func(path string, d fs.DirEntry, err error) error { 86 + if filepath.Ext(path) != ".wasm" { 87 + return nil 88 + } 89 + relPath := must(filepath.Rel(dstDir, path))(t) 90 + from := filepath.Join(srcDir, relPath) 91 + copyFile(t, path, from) 92 + return nil 93 + }) 94 + } 95 + 96 + func copyFile(t *testing.T, dst, src string) { 97 + buf := must(os.ReadFile(src))(t) 98 + err := os.WriteFile(dst, buf, 0664) 99 + if err != nil { 100 + t.Fatal(err) 101 + } 102 + } 103 + 104 + func check(t *testing.T, dir string, got string) { 105 + golden := filepath.Join("testdata", dir) + ".golden" 106 + 107 + if cuetest.UpdateGoldenFiles { 108 + os.WriteFile(golden, []byte(got), 0644) 109 + } 110 + 111 + want := string(must(os.ReadFile(golden))(t)) 112 + if got != want { 113 + t.Errorf("want %v, got %v", want, got) 114 + } 115 + } 116 + 117 + func loadDir(t *testing.T, name string) cue.Value { 118 + ctx := cuecontext.New(cuecontext.Interpreter(wasm.New())) 119 + bi := dirInstance(t, name) 120 + return ctx.BuildInstance(bi) 121 + } 122 + 123 + func dirInstance(t *testing.T, name string) *build.Instance { 124 + ctx := build.NewContext(build.ParseFile(loadFile)) 125 + inst := ctx.NewInstance(name, nil) 126 + 127 + files := must(os.ReadDir(name))(t) 128 + for _, f := range files { 129 + if strings.HasSuffix(f.Name(), "cue") { 130 + inst.AddFile(filepath.Join(name, f.Name()), nil) 131 + } 132 + if strings.HasSuffix(f.Name(), "wasm") { 133 + f := &build.File{ 134 + Filename: f.Name(), 135 + } 136 + inst.UnknownFiles = append(inst.UnknownFiles, f) 137 + } 138 + } 139 + inst.Complete() 140 + return inst 141 + } 142 + 143 + func loadFile(filename string, src any) (*ast.File, error) { 144 + return parser.ParseFile(filename, src, parser.ParseFuncs) 145 + } 146 + 147 + func must[T any](v T, err error) func(t *testing.T) T { 148 + fail := false 149 + if err != nil { 150 + fail = true 151 + } 152 + return func(t *testing.T) T { 153 + if fail { 154 + _, file, line, _ := runtime.Caller(1) 155 + file = filepath.Base(file) 156 + t.Fatalf("unexpected error at %v:%v: %v", file, line, err) 157 + } 158 + return v 159 + } 160 + }
+1
go.mod
··· 16 16 github.com/spf13/cobra v1.7.0 17 17 github.com/spf13/pflag v1.0.5 18 18 github.com/stretchr/testify v1.2.2 19 + github.com/tetratelabs/wazero v1.0.2 19 20 golang.org/x/mod v0.9.0 20 21 golang.org/x/net v0.8.0 21 22 golang.org/x/text v0.8.0
+4
go.sum
··· 42 42 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 43 43 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 44 44 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 45 + github.com/tetratelabs/wazero v1.0.0-rc.2 h1:OA3UUynnoqxrjCQ94mpAtdO4/oMxFQVNL2BXDMOc66Q= 46 + github.com/tetratelabs/wazero v1.0.0-rc.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= 47 + github.com/tetratelabs/wazero v1.0.2 h1:lpwL5zczFHk2mxKur98035Gig+Z3vd9JURk6lUdZxXY= 48 + github.com/tetratelabs/wazero v1.0.2/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= 45 49 golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= 46 50 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 47 51 golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+1
internal/core/runtime/extern.go
··· 326 326 327 327 b, err := c.Compile(name, &attr) 328 328 if err != nil { 329 + err = errors.Newf(info.attr.Pos(), "can't load from external module: %v", err) 329 330 d.errs = errors.Append(d.errs, err) 330 331 return true 331 332 }
+2 -1
internal/core/runtime/testdata/compile.txtar
··· 5 5 6 6 foo: _ @extern("file.xx", fail) 7 7 -- out/extern -- 8 - TEST: fail compilation 8 + can't load from external module: TEST: fail compilation: 9 + ./compile.cue:5:8
+3 -2
internal/pkg/builtin.go
··· 78 78 if b.Const != "" { 79 79 v = mustParseConstBuiltin(ctx, b.Name, b.Const) 80 80 } else { 81 - v = toBuiltin(ctx, b) 81 + v = ToBuiltin(b) 82 82 } 83 83 st.Decls = append(st.Decls, &adt.Field{ 84 84 Label: f, ··· 108 108 return obj 109 109 } 110 110 111 - func toBuiltin(ctx *adt.OpContext, b *Builtin) *adt.Builtin { 111 + // ToBuiltin converts a Builtin into an adt.Builtin. 112 + func ToBuiltin(b *Builtin) *adt.Builtin { 112 113 params := make([]adt.Param, len(b.Params)) 113 114 for i, p := range b.Params { 114 115 params[i].Value = p.Value