...

Source file src/cuelang.org/go/cue/interpreter/wasm/wasm.go

Documentation: cuelang.org/go/cue/interpreter/wasm

     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  	file, 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(file, "wasm") {
    81  		return nil, errors.Newf(token.NoPos, "load %q: invalid file name", file)
    82  	}
    83  
    84  	file, found := findFile(file, c.b)
    85  	if !found {
    86  		return nil, errors.Newf(token.NoPos, "load %q: file not found", file)
    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 name. If found, it returnes
   122  // its full path name and true, otherwise it returns the original name
   123  // and false.
   124  func findFile(name string, b *build.Instance) (path string, found bool) {
   125  	for _, f := range b.UnknownFiles {
   126  		if f.Filename == name {
   127  			return filepath.Join(b.Dir, name), true
   128  		}
   129  	}
   130  	return name, false
   131  }
   132  
   133  // fileName returns the file name of the external module specified in a,
   134  // which must be an extern attribute.
   135  func fileName(a *internal.Attr) (string, error) {
   136  	file, err := a.String(0)
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  	return file, nil
   141  }
   142  

View as plain text