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/cue" 19 "cuelang.org/go/internal/core/adt" 20 "cuelang.org/go/internal/pkg" 21 "cuelang.org/go/internal/value" 22 ) 23 24 // generateCallThatReturnsBuiltin returns a call expression to a nullary 25 // builtin that returns another builtin that corresponds to the external 26 // Wasm function declared by the user. name is the name of the function, 27 // args are its declared arguments, scope is a CUE value that represents 28 // the structure into which to resolve the arguments and i is the 29 // loaded Wasm instance that contains the function. 30 // 31 // This function is implemented as a higher-order function to solve a 32 // bootstrapping issue. The user can specifies arbitrary types for the 33 // function's arguments, and these types can be arbitrary CUE types 34 // defined in arbitrary places. This function is called before CUE 35 // evaluation at a time where identifiers are not yet resolved. This 36 // higher-order design solves the bootstrapping issue by deferring the 37 // resolution of identifiers (and selectors, and expressions in general) 38 // until runtime. At compile-time we only generate a nullary builtin 39 // that we call, and being nullary, it does not need to refer to any 40 // unresolved identifiers, rather the identifiers (and selectors) are 41 // saved in the closure that executes later, at runtime. 42 // 43 // Additionally, resolving identifiers (and selectors) requires an 44 // OpContext, and we do not have an OpContext at compile time. With 45 // this higher-order design we get an appropiate OpContext when the 46 // runtime calls the nullary builtin hence solving the bootstrapping 47 // problem. 48 func generateCallThatReturnsBuiltin(name string, scope adt.Value, args []string, i *instance) (adt.Expr, error) { 49 // ensure that the function exists before trying to call it. 50 _, err := i.load(name) 51 if err != nil { 52 return nil, err 53 } 54 55 call := &adt.CallExpr{Fun: &adt.Builtin{ 56 Result: adt.TopKind, 57 Name: name, 58 Func: func(opctx *adt.OpContext, _ []adt.Value) adt.Expr { 59 scope := value.Make(opctx, scope) 60 sig := compileStringsInScope(args, scope) 61 args, result := splitLast(sig) 62 b := &pkg.Builtin{ 63 Name: name, 64 Params: params(args), 65 Result: result.Kind(), 66 Func: cABIFunc(i, name, sig), 67 } 68 return pkg.ToBuiltin(b) 69 }, 70 }} 71 return call, nil 72 } 73 74 // param converts a CUE value that represents the type of a function 75 // argument into its pkg.Param counterpart. 76 func param(arg cue.Value) pkg.Param { 77 param := pkg.Param{ 78 Kind: arg.IncompleteKind(), 79 } 80 if param.Kind == adt.StructKind || ((param.Kind & adt.NumberKind) != 0) { 81 _, v := value.ToInternal(arg) 82 param.Value = v 83 } 84 return param 85 } 86 87 // params converts a list of CUE values that represent the types of a 88 // function's arguments into their pkg.Param counterparts. 89 func params(args []cue.Value) []pkg.Param { 90 var params []pkg.Param 91 for _, a := range args { 92 params = append(params, param(a)) 93 } 94 return params 95 } 96