Skip to content
This repository has been archived by the owner on May 11, 2020. It is now read-only.

Commit

Permalink
exec,wasm: add support for host Go functions
Browse files Browse the repository at this point in the history
* Add host and compiled functions to the same list.
* Remove `doCall` since it is redundant with call
  • Loading branch information
gballet authored and sbinet committed May 20, 2018
1 parent 6fb5730 commit d4bc452
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 43 deletions.
34 changes: 3 additions & 31 deletions exec/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,6 @@ package exec

import "errors"

func (vm *VM) doCall(compiled compiledFunction, index int64) {
newStack := make([]uint64, 0, compiled.maxDepth)
locals := make([]uint64, compiled.totalLocalVars)

for i := compiled.args - 1; i >= 0; i-- {
locals[i] = vm.popUint64()
}

// save execution context
prevCtxt := vm.ctx

vm.ctx = context{
stack: newStack,
locals: locals,
code: compiled.code,
pc: 0,
curFunc: index,
}

rtrn := vm.execCode(compiled)

// restore execution context
vm.ctx = prevCtxt

if compiled.returns {
vm.pushUint64(rtrn)
}
}

var (
// ErrSignatureMismatch is the error value used while trapping the VM when
// a signature mismatch between the table entry and the type entry is found
Expand All @@ -48,7 +19,8 @@ var (

func (vm *VM) call() {
index := vm.fetchUint32()
vm.doCall(vm.compiledFuncs[index], int64(index))

vm.funcs[index].call(vm, int64(index))
}

func (vm *VM) callIndirect() {
Expand Down Expand Up @@ -81,5 +53,5 @@ func (vm *VM) callIndirect() {
}
}

vm.doCall(vm.compiledFuncs[elemIndex], int64(elemIndex))
vm.funcs[elemIndex].call(vm, int64(elemIndex))
}
155 changes: 155 additions & 0 deletions exec/call_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright 2018 The go-interpreter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package exec

import (
"bytes"
"reflect"
"testing"

"github.com/go-interpreter/wagon/wasm"
)

func TestHostCall(t *testing.T) {
const secretValue = 0xdeadbeef

var secretVariable int = 0

// a host function that can be called by WASM code.
testHostFunction := func() {
secretVariable = secretValue
}

m := wasm.NewModule()
m.Start = &wasm.SectionStartFunction{Index: 0}

// A function signature. Both the host and WASM function
// have the same signature.
fsig := wasm.FunctionSig{
Form: 0,
ParamTypes: []wasm.ValueType{},
ReturnTypes: []wasm.ValueType{},
}

// List of all function types available in this module.
// There is only one: (func [] -> [])
m.Types = &wasm.SectionTypes{
Entries: []wasm.FunctionSig{fsig, fsig},
}

m.Function = &wasm.SectionFunctions{
Types: []uint32{0, 0},
}

// The body of the start function, that should only
// call the host function
fb := wasm.FunctionBody{
Module: m,
Locals: []wasm.LocalEntry{},
// code should disassemble to:
// call 1 (which is host)
// end
Code: []byte{0x02, 0x00, 0x10, 0x01, 0x0b},
}

// There was no call to `ReadModule` so this part emulates
// how the module object would look like if the function
// had been called.
m.FunctionIndexSpace = []wasm.Function{
{
Sig: &fsig,
Body: &fb,
},
{
Sig: &fsig,
Host: reflect.ValueOf(testHostFunction),
},
}

m.Code = &wasm.SectionCode{
Bodies: []wasm.FunctionBody{fb},
}

// Once called, NewVM will execute the module's main
// function.
vm, err := NewVM(m)
if err != nil {
t.Fatalf("Error creating VM: %v", vm)
}

if len(vm.funcs) < 1 {
t.Fatalf("Need at least a start function!")
}

// Only one entry, which should be a function
if secretVariable != secretValue {
t.Fatalf("x is %d instead of %d", secretVariable, secretValue)
}
}

var moduleCallHost = []byte{
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x01, 0x1A, 0x06, 0x60, 0x01, 0x7F, 0x00, 0x60,
0x01, 0x7F, 0x01, 0x7F, 0x60, 0x00, 0x01, 0x7F, 0x60, 0x00, 0x00, 0x60, 0x00, 0x01, 0x7C, 0x60,
0x01, 0x7F, 0x01, 0x7F, 0x02, 0x0F, 0x01, 0x03, 0x65, 0x6E, 0x76, 0x07, 0x5F, 0x6E, 0x61, 0x74,
0x69, 0x76, 0x65, 0x00, 0x05, 0x03, 0x02, 0x01, 0x02, 0x04, 0x04, 0x01, 0x70, 0x00, 0x02, 0x06,
0x10, 0x03, 0x7F, 0x01, 0x41, 0x00, 0x0B, 0x7F, 0x01, 0x41, 0x00, 0x0B, 0x7F, 0x00, 0x41, 0x01,
0x0B, 0x07, 0x09, 0x01, 0x05, 0x5F, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x01, 0x09, 0x01, 0x00, 0x0A,
0x08, 0x01, 0x06, 0x00, 0x41, 0x00, 0x10, 0x00, 0x0B,
}

func add3(x int32) int32 {
return x + 3
}

func importer(name string) (*wasm.Module, error) {
m := wasm.NewModule()
m.Types = &wasm.SectionTypes{
// List of all function types available in this module.
// There is only one: (func [int32] -> [int32])
Entries: []wasm.FunctionSig{
{
Form: 0,
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
},
}
m.FunctionIndexSpace = []wasm.Function{
{
Sig: &m.Types.Entries[0],
Host: reflect.ValueOf(add3),
Body: &wasm.FunctionBody{},
},
}
m.Export = &wasm.SectionExports{
Entries: map[string]wasm.ExportEntry{
"_native": {
FieldStr: "_naive",
Kind: wasm.ExternalFunction,
Index: 0,
},
},
}

return m, nil
}

func TestHostSymbolCall(t *testing.T) {
m, err := wasm.ReadModule(bytes.NewReader(moduleCallHost), importer)
if err != nil {
t.Fatalf("Could not read module: %v", err)
}
vm, err := NewVM(m)
if err != nil {
t.Fatalf("Could not instantiate vm: %v", err)
}
rtrns, err := vm.ExecCode(1)
if err != nil {
t.Fatalf("Error executing the default function: %v", err)
}
if int(rtrns.(uint32)) != 3 {
t.Fatalf("Did not get the right value. Got %d, wanted %d", rtrns, 3)
}
}
2 changes: 1 addition & 1 deletion exec/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (fn goFunction) call(vm *VM, index int64) {
case reflect.Uint32, reflect.Uint64:
val.SetUint(raw)
case reflect.Int32, reflect.Int64:
val.SetUint(raw)
val.SetInt(int64(raw))
default:
panic(fmt.Sprintf("exec: args %d invalid kind=%v", i, kind))
}
Expand Down
46 changes: 35 additions & 11 deletions exec/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ type context struct {
type VM struct {
ctx context

module *wasm.Module
globals []uint64
memory []byte
compiledFuncs []compiledFunction
module *wasm.Module
globals []uint64
memory []byte
funcs []function

funcTable [256]func()

Expand Down Expand Up @@ -87,12 +87,28 @@ func NewVM(module *wasm.Module) (*VM, error) {
copy(vm.memory, module.LinearMemoryIndexSpace[0])
}

vm.compiledFuncs = make([]compiledFunction, len(module.FunctionIndexSpace))
vm.funcs = make([]function, len(module.FunctionIndexSpace))
vm.globals = make([]uint64, len(module.GlobalIndexSpace))
vm.newFuncTable()
vm.module = module

nNatives := 0
for i, fn := range module.FunctionIndexSpace {
// Skip native methods as they need not be
// disassembled; simply add them at the end
// of the `funcs` array as is, as specified
// in the spec. See the "host functions"
// section of:
// https://webassembly.github.io/spec/core/exec/modules.html#allocation
if fn.IsHost() {
vm.funcs[i] = goFunction{
typ: fn.Host.Type(),
val: fn.Host,
}
nNatives++
continue
}

disassembly, err := disasm.Disassemble(fn, module)
if err != nil {
return nil, err
Expand All @@ -104,7 +120,7 @@ func NewVM(module *wasm.Module) (*VM, error) {
totalLocalVars += int(entry.Count)
}
code, table := compile.Compile(disassembly.Code)
vm.compiledFuncs[i] = compiledFunction{
vm.funcs[i] = compiledFunction{
code: code,
branchTables: table,
maxDepth: disassembly.MaxDepth,
Expand Down Expand Up @@ -260,15 +276,19 @@ func (vm *VM) ExecCode(fnIndex int64, args ...uint64) (rtrn interface{}, err err
}
}()
}
if int(fnIndex) > len(vm.compiledFuncs) {
if int(fnIndex) > len(vm.funcs) {
return nil, InvalidFunctionIndexError(fnIndex)
}
if len(vm.module.GetFunction(int(fnIndex)).Sig.ParamTypes) != len(args) {
return nil, ErrInvalidArgumentCount
}
compiled := vm.compiledFuncs[fnIndex]

vm.ctx.stack = make([]uint64, 0, compiled.maxDepth)
compiled, ok := vm.funcs[fnIndex].(compiledFunction)
if !ok {
panic(fmt.Sprintf("exec: function at index %d is not a compiled function", fnIndex))
}
if len(vm.ctx.stack) < compiled.maxDepth {
vm.ctx.stack = make([]uint64, 0, compiled.maxDepth)
}
vm.ctx.locals = make([]uint64, compiled.totalLocalVars)
vm.ctx.pc = 0
vm.ctx.code = compiled.code
Expand Down Expand Up @@ -334,7 +354,11 @@ outer:
case ops.BrTable:
index := vm.fetchInt64()
label := vm.popInt32()
table := vm.compiledFuncs[vm.ctx.curFunc].branchTables[index]
cf, ok := vm.funcs[vm.ctx.curFunc].(compiledFunction)
if !ok {
panic(fmt.Sprintf("exec: function at index %d is not a compiled function", vm.ctx.curFunc))
}
table := cf.branchTables[index]
var target compile.Target
if label >= 0 && label < int32(len(table.Targets)) {
target = table.Targets[int32(label)]
Expand Down
24 changes: 24 additions & 0 deletions wasm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package wasm
import (
"errors"
"io"
"reflect"

"github.com/go-interpreter/wagon/wasm/internal/readpos"
)
Expand All @@ -22,6 +23,13 @@ const (
type Function struct {
Sig *FunctionSig
Body *FunctionBody
Host reflect.Value
}

// IsHost indicates whether this function is a host function as defined in:
// https://webassembly.github.io/spec/core/exec/modules.html#host-functions
func (fct *Function) IsHost() bool {
return fct.Host != reflect.Value{}
}

// Module represents a parsed WebAssembly module:
Expand All @@ -44,6 +52,7 @@ type Module struct {
// The function index space of the module
FunctionIndexSpace []Function
GlobalIndexSpace []GlobalEntry

// function indices into the global function space
// the limit of each table is its capacity (cap)
TableIndexSpace [][]uint32
Expand All @@ -59,6 +68,21 @@ type Module struct {
}
}

// NewModule creates a new empty module
func NewModule() *Module {
return &Module{
Types: &SectionTypes{},
Import: &SectionImports{},
Table: &SectionTables{},
Memory: &SectionMemories{},
Global: &SectionGlobals{},
Export: &SectionExports{},
Start: &SectionStartFunction{},
Elements: &SectionElements{},
Data: &SectionData{},
}
}

// ResolveFunc is a function that takes a module name and
// returns a valid resolved module.
type ResolveFunc func(name string) (*Module, error)
Expand Down

0 comments on commit d4bc452

Please sign in to comment.