cuelang.org/go@v0.13.0/cue/interpreter/wasm/call.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  	"fmt"
    19  
    20  	"cuelang.org/go/cue"
    21  	"cuelang.org/go/internal/pkg"
    22  	"github.com/tetratelabs/wazero/api"
    23  )
    24  
    25  func encBool(b bool) uint64 {
    26  	if b {
    27  		return api.EncodeU32(1)
    28  	}
    29  	return api.EncodeU32(0)
    30  }
    31  
    32  // encNumber returns the Wasm/System V ABI representation of the number
    33  // wrapped into val, which must conform to the type of typ.
    34  func encNumber(typ cue.Value, val cue.Value) (r uint64) {
    35  	ctx := val.Context()
    36  
    37  	_int32 := ctx.CompileString("int32")
    38  	if _int32.Subsume(typ) == nil {
    39  		i, _ := val.Int64()
    40  		return api.EncodeI32(int32(i))
    41  	}
    42  
    43  	_int64 := ctx.CompileString("int64")
    44  	if _int64.Subsume(typ) == nil {
    45  		i, _ := val.Int64()
    46  		return api.EncodeI64(i)
    47  	}
    48  
    49  	_uint32 := ctx.CompileString("uint32")
    50  	if _uint32.Subsume(typ) == nil {
    51  		i, _ := val.Uint64()
    52  		return api.EncodeU32(uint32(i))
    53  	}
    54  
    55  	_uint64 := ctx.CompileString("uint64")
    56  	if _uint64.Subsume(typ) == nil {
    57  		i, _ := val.Uint64()
    58  		return i
    59  	}
    60  
    61  	_float32 := ctx.CompileString("float32")
    62  	if _float32.Subsume(typ) == nil {
    63  		f, _ := val.Float64()
    64  		return api.EncodeF32(float32(f))
    65  	}
    66  
    67  	_float64 := ctx.CompileString("float64")
    68  	if _float64.Subsume(typ) == nil {
    69  		f, _ := val.Float64()
    70  		return api.EncodeF64(f)
    71  	}
    72  
    73  	panic("encNumber: unsupported argument type")
    74  }
    75  
    76  func decBool(v uint64) bool {
    77  	return api.DecodeU32(v) == 1
    78  }
    79  
    80  // decNumber decodes the Wasm/System V ABI encoding of the
    81  // val number of type typ into a Go value.
    82  func decNumber(typ cue.Value, val uint64) (r any) {
    83  	ctx := typ.Context()
    84  
    85  	_int32 := ctx.CompileString("int32")
    86  	if _int32.Subsume(typ) == nil {
    87  		return api.DecodeI32(val)
    88  	}
    89  
    90  	_uint32 := ctx.CompileString("uint32")
    91  	if _uint32.Subsume(typ) == nil {
    92  		return api.DecodeU32(val)
    93  	}
    94  
    95  	_int64 := ctx.CompileString("int64")
    96  	if _int64.Subsume(typ) == nil {
    97  		return int64(val)
    98  	}
    99  
   100  	_uint64 := ctx.CompileString("uint64")
   101  	if _uint64.Subsume(typ) == nil {
   102  		return val
   103  	}
   104  
   105  	_float32 := ctx.CompileString("float32")
   106  	if _float32.Subsume(typ) == nil {
   107  		return api.DecodeF32(val)
   108  	}
   109  
   110  	_float64 := ctx.CompileString("float64")
   111  	if _float64.Subsume(typ) == nil {
   112  		return api.DecodeF64(val)
   113  	}
   114  
   115  	panic(fmt.Sprintf("unsupported argument type %v (kind %v)", typ, typ.IncompleteKind()))
   116  }
   117  
   118  func encBytes(i *instance, b []byte) *memory {
   119  	m, _ := i.Alloc(uint32(len(b)))
   120  	m.WriteAt(b, 0)
   121  	return m
   122  }
   123  
   124  // cABIFunc implements the Wasm/System V ABI translation. The named
   125  // function, which must be loadable by the instance, and must be of
   126  // the specified sig type, will be called by the runtime after its
   127  // arguments will be converted according to the ABI. The result of the
   128  // call will be then also be converted back into a Go value and handed
   129  // to the runtime.
   130  func cABIFunc(i *instance, name string, sig []cue.Value) func(*pkg.CallCtxt) {
   131  	// Compute the layout of all encountered structs (arguments
   132  	// and result) such that we will have it available at the time
   133  	// of an actual call.
   134  	argsTyp, resTyp := splitLast(sig)
   135  	argLayouts := make([]*structLayout, 0, len(argsTyp))
   136  	var retLayout *structLayout
   137  	for _, typ := range argsTyp {
   138  		switch typ.IncompleteKind() {
   139  		case cue.StructKind:
   140  			argLayouts = append(argLayouts, structLayoutVal(typ))
   141  		default:
   142  			argLayouts = append(argLayouts, nil)
   143  		}
   144  	}
   145  	if resTyp.IncompleteKind() == cue.StructKind {
   146  		retLayout = structLayoutVal(resTyp)
   147  	}
   148  
   149  	fn, _ := i.load(name)
   150  	return func(c *pkg.CallCtxt) {
   151  		argsTyp, resTyp := splitLast(sig)
   152  		args := make([]uint64, 0, len(argsTyp))
   153  		for k, typ := range argsTyp {
   154  			switch typ.IncompleteKind() {
   155  			case cue.BoolKind:
   156  				args = append(args, encBool(c.Bool(k)))
   157  			case cue.IntKind, cue.FloatKind, cue.NumberKind:
   158  				args = append(args, encNumber(typ, c.Value(k)))
   159  			case cue.StructKind:
   160  				ms := encodeStruct(i, c.Value(k), argLayouts[k])
   161  				defer i.FreeAll(ms)
   162  
   163  				args = append(args, uint64(ms[0].ptr))
   164  			default:
   165  				panic(fmt.Sprintf("unsupported argument type %v (kind %v)", typ, typ.IncompleteKind()))
   166  			}
   167  		}
   168  
   169  		var retMem *memory
   170  		if resTyp.IncompleteKind() == cue.StructKind {
   171  			retMem, _ = i.Alloc(uint32(retLayout.size))
   172  			// TODO: add support for structs containing pointers.
   173  			defer i.Free(retMem)
   174  			args = append(args, uint64(retMem.ptr))
   175  		}
   176  
   177  		if c.Do() {
   178  			res, err := fn.Call(i.ctx, args...)
   179  			if err != nil {
   180  				c.Err = err
   181  				return
   182  			}
   183  			switch resTyp.IncompleteKind() {
   184  			case cue.BoolKind:
   185  				c.Ret = decBool(res[0])
   186  			case cue.IntKind, cue.FloatKind, cue.NumberKind:
   187  				c.Ret = decNumber(resTyp, res[0])
   188  			case cue.StructKind:
   189  				c.Ret = decodeStruct(retMem.Bytes(), retLayout)
   190  			default:
   191  				panic(fmt.Sprintf("unsupported result type %v (kind %v)", resTyp, resTyp.IncompleteKind()))
   192  			}
   193  		}
   194  	}
   195  }