core/vm: unvendor wagon

This commit is contained in:
Guillaume Ballet 2018-10-11 13:09:03 +02:00
parent c4b48d1196
commit 0f775eaf2d
46 changed files with 0 additions and 5805 deletions

View file

@ -1,24 +0,0 @@
Copyright ©2017 The go-interpreter Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the go-interpreter project nor the names of its authors and
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,28 +0,0 @@
wagon
=====
[![Build Status](https://travis-ci.org/go-interpreter/wagon.svg?branch=master)](https://travis-ci.org/go-interpreter/wagon)
[![codecov](https://codecov.io/gh/go-interpreter/wagon/branch/master/graph/badge.svg)](https://codecov.io/gh/go-interpreter/wagon)
[![GoDoc](https://godoc.org/github.com/go-interpreter/wagon?status.svg)](https://godoc.org/github.com/go-interpreter/wagon)
`wagon` is a [WebAssembly](http://webassembly.org)-based interpreter in [Go](https://golang.org), for [Go](https://golang.org).
**NOTE:** `wagon` requires `Go >= 1.9.x`.
## Purpose
`wagon` aims to provide tools (executables+libraries) to:
- decode `wasm` binary files
- load and execute `wasm` modules' bytecode.
`wagon` doesn't concern itself with the production of the `wasm` binary files;
these files should be produced with another tool (such as [wabt](https://github.com/WebAssembly/wabt) or [binaryen](https://github.com/WebAssembly/binaryen).)
`wagon` *may* provide a utility to produce `wasm` files from `wast` or `wat` files (and vice versa.)
The primary goal of `wagon` is to provide the building blocks to be able to build an interpreter for Go code, that could be embedded in Jupyter or any Go program.
## Contributing
See the [CONTRIBUTING](https://github.com/go-interpreter/license/blob/master/CONTRIBUTE.md) guide for pointers on how to contribute to `go-interpreter` and `wagon`.

View file

@ -1,62 +0,0 @@
// 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 disasm
import (
"bytes"
"encoding/binary"
"math"
"github.com/go-interpreter/wagon/wasm/leb128"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
// Assemble encodes a set of instructions into binary representation.
func Assemble(instr []Instr) ([]byte, error) {
body := new(bytes.Buffer)
for _, ins := range instr {
body.WriteByte(ins.Op.Code)
switch op := ins.Op.Code; op {
case ops.Block, ops.Loop, ops.If:
leb128.WriteVarint64(body, int64(ins.Block.Signature))
case ops.Br, ops.BrIf:
leb128.WriteVarUint32(body, ins.Immediates[0].(uint32))
case ops.BrTable:
cnt := ins.Immediates[0].(uint32)
leb128.WriteVarUint32(body, cnt)
for i := uint32(0); i < cnt; i++ {
leb128.WriteVarUint32(body, ins.Immediates[i+1].(uint32))
}
leb128.WriteVarUint32(body, ins.Immediates[1+cnt].(uint32))
case ops.Call, ops.CallIndirect:
leb128.WriteVarUint32(body, ins.Immediates[0].(uint32))
if op == ops.CallIndirect {
leb128.WriteVarUint32(body, ins.Immediates[1].(uint32))
}
case ops.GetLocal, ops.SetLocal, ops.TeeLocal, ops.GetGlobal, ops.SetGlobal:
leb128.WriteVarUint32(body, ins.Immediates[0].(uint32))
case ops.I32Const:
leb128.WriteVarint64(body, int64(ins.Immediates[0].(int32)))
case ops.I64Const:
leb128.WriteVarint64(body, ins.Immediates[0].(int64))
case ops.F32Const:
f := ins.Immediates[0].(float32)
var b [4]byte
binary.LittleEndian.PutUint32(b[:], math.Float32bits(f))
body.Write(b[:])
case ops.F64Const:
f := ins.Immediates[0].(float64)
var b [8]byte
binary.LittleEndian.PutUint64(b[:], math.Float64bits(f))
body.Write(b[:])
case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32:
leb128.WriteVarUint32(body, ins.Immediates[0].(uint32))
leb128.WriteVarUint32(body, ins.Immediates[1].(uint32))
case ops.CurrentMemory, ops.GrowMemory:
leb128.WriteVarUint32(body, uint32(ins.Immediates[0].(uint8)))
}
}
return body.Bytes(), nil
}

View file

@ -1,464 +0,0 @@
// Copyright 2017 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 disasm provides functions for disassembling WebAssembly bytecode.
package disasm
import (
"bytes"
"encoding/binary"
"errors"
"io"
"math"
"github.com/go-interpreter/wagon/internal/stack"
"github.com/go-interpreter/wagon/wasm"
"github.com/go-interpreter/wagon/wasm/leb128"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
// Instr describes an instruction, consisting of an operator, with its
// appropriate immediate value(s).
type Instr struct {
Op ops.Op
// Immediates are arguments to an operator in the bytecode stream itself.
// Valid value types are:
// - (u)(int/float)(32/64)
// - wasm.BlockType
Immediates []interface{}
NewStack *StackInfo // non-nil if the instruction creates or unwinds a stack.
Block *BlockInfo // non-nil if the instruction starts or ends a new block.
Unreachable bool // whether the operator can be reached during execution
// IsReturn is true if executing this instruction will result in the
// function returning. This is true for branches (br, br_if) to
// the depth <max_relative_depth> + 1, or the return operator itself.
// If true, NewStack for this instruction is nil.
IsReturn bool
// If the operator is br_table (ops.BrTable), this is a list of StackInfo
// fields for each of the blocks/branches referenced by the operator.
Branches []StackInfo
}
// StackInfo stores details about a new stack created or unwinded by an instruction.
type StackInfo struct {
StackTopDiff int64 // The difference between the stack depths at the end of the block
PreserveTop bool // Whether the value on the top of the stack should be preserved while unwinding
IsReturn bool // Whether the unwind is equivalent to a return
}
// BlockInfo stores details about a block created or ended by an instruction.
type BlockInfo struct {
Start bool // If true, this instruction starts a block. Else this instruction ends it.
Signature wasm.BlockType // The block signature
// Indices to the accompanying control operator.
// For 'if', this is the index to the 'else' operator.
IfElseIndex int
// For 'else', this is the index to the 'if' operator.
ElseIfIndex int
// The index to the `end' operator for if/else/loop/block.
EndIndex int
// For end, it is the index to the operator that starts the block.
BlockStartIndex int
}
// Disassembly is the result of disassembling a WebAssembly function.
type Disassembly struct {
Code []Instr
MaxDepth int // The maximum stack depth that can be reached while executing this function
}
func (d *Disassembly) checkMaxDepth(depth int) {
if depth > d.MaxDepth {
d.MaxDepth = depth
}
}
func pushPolymorphicOp(indexStack [][]int, index int) {
indexStack[len(indexStack)-1] = append(indexStack[len(indexStack)-1], index)
}
func isInstrReachable(indexStack [][]int) bool {
return len(indexStack[len(indexStack)-1]) == 0
}
var ErrStackUnderflow = errors.New("disasm: stack underflow")
// Disassemble disassembles the given function. It also takes the function's
// parent module as an argument for locating any other functions referenced by
// fn.
func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) {
code := fn.Body.Code
reader := bytes.NewReader(code)
disas := &Disassembly{}
// A stack of int arrays holding indices to instructions that make the stack
// polymorphic. Each block has its corresponding array. We start with one
// array for the root stack
blockPolymorphicOps := [][]int{{}}
// a stack of current execution stack depth values, so that the depth for each
// stack is maintained indepepdently for calculating discard values
stackDepths := &stack.Stack{}
stackDepths.Push(0)
blockIndices := &stack.Stack{} // a stack of indices to operators which start new blocks
curIndex := 0
var lastOpReturn bool
for {
op, err := reader.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
logger.Printf("stack top is %d", stackDepths.Top())
opStr, err := ops.New(op)
if err != nil {
return nil, err
}
instr := Instr{
Op: opStr,
}
if op == ops.End || op == ops.Else {
// There are two possible cases here:
// 1. The corresponding block/if/loop instruction
// *is* reachable, and an instruction somewhere in this
// block (and NOT in a nested block) makes the stack
// polymorphic. In this case, this end/else is reachable.
//
// 2. The corresponding block/if/loop instruction
// is *not* reachable, which makes this end/else unreachable
// too.
isUnreachable := blockIndices.Len() != len(blockPolymorphicOps)-1
instr.Unreachable = isUnreachable
} else {
instr.Unreachable = !isInstrReachable(blockPolymorphicOps)
}
logger.Printf("op: %s, unreachable: %v", opStr.Name, instr.Unreachable)
if !opStr.Polymorphic && !instr.Unreachable {
top := int(stackDepths.Top())
top -= len(opStr.Args)
stackDepths.SetTop(uint64(top))
if top < -1 {
return nil, ErrStackUnderflow
}
if opStr.Returns != wasm.ValueType(wasm.BlockTypeEmpty) {
top++
stackDepths.SetTop(uint64(top))
}
disas.checkMaxDepth(top)
}
switch op {
case ops.Unreachable:
pushPolymorphicOp(blockPolymorphicOps, curIndex)
case ops.Drop:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 1)
}
case ops.Select:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 2)
}
case ops.Return:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - uint64(len(fn.Sig.ReturnTypes)))
}
pushPolymorphicOp(blockPolymorphicOps, curIndex)
lastOpReturn = true
case ops.End, ops.Else:
// The max depth reached while execing the current block
curDepth := stackDepths.Top()
blockStartIndex := blockIndices.Pop()
blockSig := disas.Code[blockStartIndex].Block.Signature
instr.Block = &BlockInfo{
Start: false,
Signature: blockSig,
}
if op == ops.End {
instr.Block.BlockStartIndex = int(blockStartIndex)
disas.Code[blockStartIndex].Block.EndIndex = curIndex
} else { // ops.Else
instr.Block.ElseIfIndex = int(blockStartIndex)
disas.Code[blockStartIndex].Block.IfElseIndex = int(blockStartIndex)
}
// The max depth reached while execing the last block
// If the signature of the current block is not empty,
// this will be incremented.
// Same with ops.Br/BrIf, we subtract 2 instead of 1
// to get the depth of the *parent* block of the branch
// we want to take.
prevDepthIndex := stackDepths.Len() - 2
prevDepth := stackDepths.Get(prevDepthIndex)
if op != ops.Else && blockSig != wasm.BlockTypeEmpty && !instr.Unreachable {
stackDepths.Set(prevDepthIndex, prevDepth+1)
disas.checkMaxDepth(int(stackDepths.Get(prevDepthIndex)))
}
if !lastOpReturn {
elemsDiscard := int(curDepth) - int(prevDepth)
if elemsDiscard < -1 {
return nil, ErrStackUnderflow
}
instr.NewStack = &StackInfo{
StackTopDiff: int64(elemsDiscard),
PreserveTop: blockSig != wasm.BlockTypeEmpty,
}
logger.Printf("discard %d elements, preserve top: %v", elemsDiscard, instr.NewStack.PreserveTop)
} else {
instr.NewStack = &StackInfo{}
}
logger.Printf("setting new stack for %s block (%d)", disas.Code[blockStartIndex].Op.Name, blockStartIndex)
disas.Code[blockStartIndex].NewStack = instr.NewStack
if !instr.Unreachable {
blockPolymorphicOps = blockPolymorphicOps[:len(blockPolymorphicOps)-1]
}
stackDepths.Pop()
if op == ops.Else {
stackDepths.Push(0)
blockIndices.Push(uint64(curIndex))
if !instr.Unreachable {
blockPolymorphicOps = append(blockPolymorphicOps, []int{})
}
}
case ops.Block, ops.Loop, ops.If:
sig, err := leb128.ReadVarint32(reader)
if err != nil {
return nil, err
}
logger.Printf("if, depth is %d", stackDepths.Top())
stackDepths.Push(stackDepths.Top())
// If this new block is unreachable, its
// entire instruction sequence is unreachable
// as well. To make sure that isInstrReachable
// returns the correct value, we don't push a new
// array to blockPolymorphicOps.
if !instr.Unreachable {
// Therefore, only push a new array if this instruction
// is reachable.
blockPolymorphicOps = append(blockPolymorphicOps, []int{})
}
instr.Block = &BlockInfo{
Start: true,
Signature: wasm.BlockType(sig),
}
blockIndices.Push(uint64(curIndex))
instr.Immediates = append(instr.Immediates, wasm.BlockType(sig))
case ops.Br, ops.BrIf:
depth, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, depth)
if int(depth) == blockIndices.Len() {
instr.IsReturn = true
} else {
curDepth := stackDepths.Top()
// whenever we take a branch, the stack is unwound
// to the height of stack of its *parent* block, which
// is why we subtract 2 instead of 1.
// prevDepth holds the height of the stack when
// the block that we branch to started.
prevDepth := stackDepths.Get(stackDepths.Len() - 2 - int(depth))
elemsDiscard := int(curDepth) - int(prevDepth)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
// No need to subtract 2 here, we are getting the block
// we need to branch to.
index := blockIndices.Get(blockIndices.Len() - 1 - int(depth))
instr.NewStack = &StackInfo{
StackTopDiff: int64(elemsDiscard),
PreserveTop: disas.Code[index].Block.Signature != wasm.BlockTypeEmpty,
}
}
if op == ops.Br {
pushPolymorphicOp(blockPolymorphicOps, curIndex)
}
case ops.BrTable:
if !instr.Unreachable {
stackDepths.SetTop(stackDepths.Top() - 1)
}
targetCount, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, targetCount)
for i := uint32(0); i < targetCount; i++ {
entry, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, entry)
var info StackInfo
if int(entry) == blockIndices.Len() {
info.IsReturn = true
} else {
curDepth := stackDepths.Top()
branchDepth := stackDepths.Get(stackDepths.Len() - 2 - int(entry))
elemsDiscard := int(curDepth) - int(branchDepth)
logger.Printf("Curdepth %d branchDepth %d discard %d", curDepth, branchDepth, elemsDiscard)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
index := blockIndices.Get(blockIndices.Len() - 1 - int(entry))
info.StackTopDiff = int64(elemsDiscard)
info.PreserveTop = disas.Code[index].Block.Signature != wasm.BlockTypeEmpty
}
instr.Branches = append(instr.Branches, info)
}
defaultTarget, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, defaultTarget)
var info StackInfo
if int(defaultTarget) == blockIndices.Len() {
info.IsReturn = true
} else {
curDepth := stackDepths.Top()
branchDepth := stackDepths.Get(stackDepths.Len() - 2 - int(defaultTarget))
elemsDiscard := int(curDepth) - int(branchDepth)
if elemsDiscard < 0 {
return nil, ErrStackUnderflow
}
index := blockIndices.Get(blockIndices.Len() - 1 - int(defaultTarget))
info.StackTopDiff = int64(elemsDiscard)
info.PreserveTop = disas.Code[index].Block.Signature != wasm.BlockTypeEmpty
}
instr.Branches = append(instr.Branches, info)
pushPolymorphicOp(blockPolymorphicOps, curIndex)
case ops.Call, ops.CallIndirect:
index, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, index)
if op == ops.CallIndirect {
reserved, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, reserved)
}
if !instr.Unreachable {
var sig *wasm.FunctionSig
top := int(stackDepths.Top())
if op == ops.CallIndirect {
if module.Types == nil {
return nil, errors.New("missing types section")
}
sig = &module.Types.Entries[index]
top--
} else {
sig = module.GetFunction(int(index)).Sig
}
top -= len(sig.ParamTypes)
top += len(sig.ReturnTypes)
stackDepths.SetTop(uint64(top))
disas.checkMaxDepth(top)
}
case ops.GetLocal, ops.SetLocal, ops.TeeLocal, ops.GetGlobal, ops.SetGlobal:
index, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, index)
if !instr.Unreachable {
top := stackDepths.Top()
switch op {
case ops.GetLocal, ops.GetGlobal:
top++
stackDepths.SetTop(top)
disas.checkMaxDepth(int(top))
case ops.SetLocal, ops.SetGlobal:
top--
stackDepths.SetTop(top)
case ops.TeeLocal:
// stack remains unchanged for tee_local
}
}
case ops.I32Const:
i, err := leb128.ReadVarint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, i)
case ops.I64Const:
i, err := leb128.ReadVarint64(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, i)
case ops.F32Const:
var b [4]byte
if _, err := io.ReadFull(reader, b[:]); err != nil {
return nil, err
}
i := binary.LittleEndian.Uint32(b[:])
instr.Immediates = append(instr.Immediates, math.Float32frombits(i))
case ops.F64Const:
var b [8]byte
if _, err := io.ReadFull(reader, b[:]); err != nil {
return nil, err
}
i := binary.LittleEndian.Uint64(b[:])
instr.Immediates = append(instr.Immediates, math.Float64frombits(i))
case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32:
// read memory_immediate
flags, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, flags)
offset, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, offset)
case ops.CurrentMemory, ops.GrowMemory:
res, err := leb128.ReadVarUint32(reader)
if err != nil {
return nil, err
}
instr.Immediates = append(instr.Immediates, uint8(res))
}
if op != ops.Return {
lastOpReturn = false
}
disas.Code = append(disas.Code, instr)
curIndex++
}
if logging {
for _, instr := range disas.Code {
logger.Printf("%v %v", instr.Op.Name, instr.NewStack)
}
}
return disas, nil
}

View file

@ -1,33 +0,0 @@
// Copyright 2017 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 disasm
import (
"io/ioutil"
"log"
"os"
)
var (
logger *log.Logger
logging bool
)
func SetDebugMode(l bool) {
w := ioutil.Discard
logging = l
if l {
w = os.Stderr
}
logger = log.New(w, "", log.Lshortfile)
logger.SetFlags(log.Lshortfile)
}
func init() {
SetDebugMode(false)
}

View file

@ -1,6 +0,0 @@
// Copyright 2017 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 wagon is a WebAssembly-based interpreter in Go, for Go.
package wagon

View file

@ -1,57 +0,0 @@
// Copyright 2017 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 "errors"
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
// in a call_indirect operation.
ErrSignatureMismatch = errors.New("exec: signature mismatch in call_indirect")
// ErrUndefinedElementIndex is the error value used while trapping the VM when
// an invalid index to the module's table space is used as an operand to
// call_indirect
ErrUndefinedElementIndex = errors.New("exec: undefined element index")
)
func (vm *VM) call() {
index := vm.fetchUint32()
vm.funcs[index].call(vm, int64(index))
}
func (vm *VM) callIndirect() {
index := vm.fetchUint32()
fnExpect := vm.module.Types.Entries[index]
_ = vm.fetchUint32() // reserved (https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/BinaryEncoding.md#call-operators-described-here)
tableIndex := vm.popUint32()
if int(tableIndex) >= len(vm.module.TableIndexSpace[0]) {
panic(ErrUndefinedElementIndex)
}
elemIndex := vm.module.TableIndexSpace[0][tableIndex]
fnActual := vm.module.FunctionIndexSpace[elemIndex]
if len(fnExpect.ParamTypes) != len(fnActual.Sig.ParamTypes) {
panic(ErrSignatureMismatch)
}
if len(fnExpect.ReturnTypes) != len(fnActual.Sig.ReturnTypes) {
panic(ErrSignatureMismatch)
}
for i := range fnExpect.ParamTypes {
if fnExpect.ParamTypes[i] != fnActual.Sig.ParamTypes[i] {
panic(ErrSignatureMismatch)
}
}
for i := range fnExpect.ReturnTypes {
if fnExpect.ReturnTypes[i] != fnActual.Sig.ReturnTypes[i] {
panic(ErrSignatureMismatch)
}
}
vm.funcs[elemIndex].call(vm, int64(elemIndex))
}

View file

@ -1,21 +0,0 @@
// Copyright 2017 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
func (vm *VM) i32Const() {
vm.pushUint32(vm.fetchUint32())
}
func (vm *VM) i64Const() {
vm.pushUint64(vm.fetchUint64())
}
func (vm *VM) f32Const() {
vm.pushFloat32(vm.fetchFloat32())
}
func (vm *VM) f64Const() {
vm.pushFloat64(vm.fetchFloat64())
}

View file

@ -1,17 +0,0 @@
// Copyright 2017 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 "errors"
// ErrUnreachable is the error value used while trapping the VM when
// an unreachable operator is reached during execution.
var ErrUnreachable = errors.New("exec: reached unreachable")
func (vm *VM) unreachable() {
panic(ErrUnreachable)
}
func (vm *VM) nop() {}

View file

@ -1,93 +0,0 @@
// Copyright 2017 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 (
"math"
)
func (vm *VM) i32Wrapi64() {
vm.pushUint32(uint32(vm.popUint64()))
}
func (vm *VM) i32TruncSF32() {
vm.pushInt32(int32(math.Trunc(float64(vm.popFloat32()))))
}
func (vm *VM) i32TruncUF32() {
vm.pushUint32(uint32(math.Trunc(float64(vm.popFloat32()))))
}
func (vm *VM) i32TruncSF64() {
vm.pushInt32(int32(math.Trunc(vm.popFloat64())))
}
func (vm *VM) i32TruncUF64() {
vm.pushUint32(uint32(math.Trunc(vm.popFloat64())))
}
func (vm *VM) i64ExtendSI32() {
vm.pushInt64(int64(vm.popInt32()))
}
func (vm *VM) i64ExtendUI32() {
vm.pushUint64(uint64(vm.popUint32()))
}
func (vm *VM) i64TruncSF32() {
vm.pushInt64(int64(math.Trunc(float64(vm.popFloat32()))))
}
func (vm *VM) i64TruncUF32() {
vm.pushUint64(uint64(math.Trunc(float64(vm.popFloat32()))))
}
func (vm *VM) i64TruncSF64() {
vm.pushInt64(int64(math.Trunc(vm.popFloat64())))
}
func (vm *VM) i64TruncUF64() {
vm.pushUint64(uint64(math.Trunc(vm.popFloat64())))
}
func (vm *VM) f32ConvertSI32() {
vm.pushFloat32(float32(vm.popInt32()))
}
func (vm *VM) f32ConvertUI32() {
vm.pushFloat32(float32(vm.popUint32()))
}
func (vm *VM) f32ConvertSI64() {
vm.pushFloat32(float32(vm.popInt64()))
}
func (vm *VM) f32ConvertUI64() {
vm.pushFloat32(float32(vm.popUint64()))
}
func (vm *VM) f32DemoteF64() {
vm.pushFloat32(float32(vm.popFloat64()))
}
func (vm *VM) f64ConvertSI32() {
vm.pushFloat64(float64(vm.popInt32()))
}
func (vm *VM) f64ConvertUI32() {
vm.pushFloat64(float64(vm.popUint32()))
}
func (vm *VM) f64ConvertSI64() {
vm.pushFloat64(float64(vm.popInt64()))
}
func (vm *VM) f64ConvertUI64() {
vm.pushFloat64(float64(vm.popUint64()))
}
func (vm *VM) f64PromoteF32() {
vm.pushFloat64(float64(vm.popFloat32()))
}

View file

@ -1,109 +0,0 @@
// Copyright 2017 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 (
"fmt"
"math"
"reflect"
"github.com/go-interpreter/wagon/exec/internal/compile"
)
type function interface {
call(vm *VM, index int64)
}
type compiledFunction struct {
code []byte
branchTables []*compile.BranchTable
maxDepth int // maximum stack depth reached while executing the function body
totalLocalVars int // number of local variables used by the function
args int // number of arguments the function accepts
returns bool // whether the function returns a value
}
type goFunction struct {
val reflect.Value
typ reflect.Type
}
func (fn goFunction) call(vm *VM, index int64) {
// numIn = # of call inputs + vm, as the function expects
// an additional *VM argument
numIn := fn.typ.NumIn()
args := make([]reflect.Value, numIn)
proc := NewProcess(vm)
// Pass proc as an argument. Check that the function indeed
// expects a *Process argument.
if reflect.ValueOf(proc).Kind() != fn.typ.In(0).Kind() {
panic(fmt.Sprintf("exec: the first argument of a host function was %s, expected %s", fn.typ.In(0).Kind(), reflect.ValueOf(vm).Kind()))
}
args[0] = reflect.ValueOf(proc)
for i := numIn - 1; i >= 1; i-- {
val := reflect.New(fn.typ.In(i)).Elem()
raw := vm.popUint64()
kind := fn.typ.In(i).Kind()
switch kind {
case reflect.Float64, reflect.Float32:
val.SetFloat(math.Float64frombits(raw))
case reflect.Uint32, reflect.Uint64:
val.SetUint(raw)
case reflect.Int32, reflect.Int64:
val.SetInt(int64(raw))
default:
panic(fmt.Sprintf("exec: args %d invalid kind=%v", i, kind))
}
args[i] = val
}
rtrns := fn.val.Call(args)
for i, out := range rtrns {
kind := out.Kind()
switch kind {
case reflect.Float64, reflect.Float32:
vm.pushFloat64(out.Float())
case reflect.Uint32, reflect.Uint64:
vm.pushUint64(out.Uint())
case reflect.Int32, reflect.Int64:
vm.pushInt64(out.Int())
default:
panic(fmt.Sprintf("exec: return value %d invalid kind=%v", i, kind))
}
}
}
func (compiled compiledFunction) call(vm *VM, index int64) {
newStack := make([]uint64, 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)
}
}

View file

@ -1,186 +0,0 @@
// Copyright 2017 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 (
ops "github.com/go-interpreter/wagon/wasm/operators"
)
func (vm *VM) newFuncTable() {
vm.funcTable[ops.I32Clz] = vm.i32Clz
vm.funcTable[ops.I32Ctz] = vm.i32Ctz
vm.funcTable[ops.I32Popcnt] = vm.i32Popcnt
vm.funcTable[ops.I32Add] = vm.i32Add
vm.funcTable[ops.I32Sub] = vm.i32Sub
vm.funcTable[ops.I32Mul] = vm.i32Mul
vm.funcTable[ops.I32DivS] = vm.i32DivS
vm.funcTable[ops.I32DivU] = vm.i32DivU
vm.funcTable[ops.I32RemS] = vm.i32RemS
vm.funcTable[ops.I32RemU] = vm.i32RemU
vm.funcTable[ops.I32And] = vm.i32And
vm.funcTable[ops.I32Or] = vm.i32Or
vm.funcTable[ops.I32Xor] = vm.i32Xor
vm.funcTable[ops.I32Shl] = vm.i32Shl
vm.funcTable[ops.I32ShrS] = vm.i32ShrS
vm.funcTable[ops.I32ShrU] = vm.i32ShrU
vm.funcTable[ops.I32Rotl] = vm.i32Rotl
vm.funcTable[ops.I32Rotr] = vm.i32Rotr
vm.funcTable[ops.I32Eqz] = vm.i32Eqz
vm.funcTable[ops.I32Eq] = vm.i32Eq
vm.funcTable[ops.I32Ne] = vm.i32Ne
vm.funcTable[ops.I32LtS] = vm.i32LtS
vm.funcTable[ops.I32LtU] = vm.i32LtU
vm.funcTable[ops.I32GtS] = vm.i32GtS
vm.funcTable[ops.I32GtU] = vm.i32GtU
vm.funcTable[ops.I32LeS] = vm.i32LeS
vm.funcTable[ops.I32LeU] = vm.i32LeU
vm.funcTable[ops.I32GeS] = vm.i32GeS
vm.funcTable[ops.I32GeU] = vm.i32GeU
vm.funcTable[ops.I64Clz] = vm.i64Clz
vm.funcTable[ops.I64Ctz] = vm.i64Ctz
vm.funcTable[ops.I64Popcnt] = vm.i64Popcnt
vm.funcTable[ops.I64Add] = vm.i64Add
vm.funcTable[ops.I64Sub] = vm.i64Sub
vm.funcTable[ops.I64Mul] = vm.i64Mul
vm.funcTable[ops.I64DivS] = vm.i64DivS
vm.funcTable[ops.I64DivU] = vm.i64DivU
vm.funcTable[ops.I64RemS] = vm.i64RemS
vm.funcTable[ops.I64RemU] = vm.i64RemU
vm.funcTable[ops.I64And] = vm.i64And
vm.funcTable[ops.I64Or] = vm.i64Or
vm.funcTable[ops.I64Xor] = vm.i64Xor
vm.funcTable[ops.I64Shl] = vm.i64Shl
vm.funcTable[ops.I64ShrS] = vm.i64ShrS
vm.funcTable[ops.I64ShrU] = vm.i64ShrU
vm.funcTable[ops.I64Rotl] = vm.i64Rotl
vm.funcTable[ops.I64Rotr] = vm.i64Rotr
vm.funcTable[ops.I64Eqz] = vm.i64Eqz
vm.funcTable[ops.I64Eq] = vm.i64Eq
vm.funcTable[ops.I64Ne] = vm.i64Ne
vm.funcTable[ops.I64LtS] = vm.i64LtS
vm.funcTable[ops.I64LtU] = vm.i64LtU
vm.funcTable[ops.I64GtS] = vm.i64GtS
vm.funcTable[ops.I64GtU] = vm.i64GtU
vm.funcTable[ops.I64LeS] = vm.i64LeS
vm.funcTable[ops.I64LeU] = vm.i64LeU
vm.funcTable[ops.I64GeS] = vm.i64GeS
vm.funcTable[ops.I64GeU] = vm.i64GeU
vm.funcTable[ops.F32Eq] = vm.f32Eq
vm.funcTable[ops.F32Ne] = vm.f32Ne
vm.funcTable[ops.F32Lt] = vm.f32Lt
vm.funcTable[ops.F32Gt] = vm.f32Gt
vm.funcTable[ops.F32Le] = vm.f32Le
vm.funcTable[ops.F32Ge] = vm.f32Ge
vm.funcTable[ops.F32Abs] = vm.f32Abs
vm.funcTable[ops.F32Neg] = vm.f32Neg
vm.funcTable[ops.F32Ceil] = vm.f32Ceil
vm.funcTable[ops.F32Floor] = vm.f32Floor
vm.funcTable[ops.F32Trunc] = vm.f32Trunc
vm.funcTable[ops.F32Nearest] = vm.f32Nearest
vm.funcTable[ops.F32Sqrt] = vm.f32Sqrt
vm.funcTable[ops.F32Add] = vm.f32Add
vm.funcTable[ops.F32Sub] = vm.f32Sub
vm.funcTable[ops.F32Mul] = vm.f32Mul
vm.funcTable[ops.F32Div] = vm.f32Div
vm.funcTable[ops.F32Min] = vm.f32Min
vm.funcTable[ops.F32Max] = vm.f32Max
vm.funcTable[ops.F32Copysign] = vm.f32Copysign
vm.funcTable[ops.F64Eq] = vm.f64Eq
vm.funcTable[ops.F64Ne] = vm.f64Ne
vm.funcTable[ops.F64Lt] = vm.f64Lt
vm.funcTable[ops.F64Gt] = vm.f64Gt
vm.funcTable[ops.F64Le] = vm.f64Le
vm.funcTable[ops.F64Ge] = vm.f64Ge
vm.funcTable[ops.F64Abs] = vm.f64Abs
vm.funcTable[ops.F64Neg] = vm.f64Neg
vm.funcTable[ops.F64Ceil] = vm.f64Ceil
vm.funcTable[ops.F64Floor] = vm.f64Floor
vm.funcTable[ops.F64Trunc] = vm.f64Trunc
vm.funcTable[ops.F64Nearest] = vm.f64Nearest
vm.funcTable[ops.F64Sqrt] = vm.f64Sqrt
vm.funcTable[ops.F64Add] = vm.f64Add
vm.funcTable[ops.F64Sub] = vm.f64Sub
vm.funcTable[ops.F64Mul] = vm.f64Mul
vm.funcTable[ops.F64Div] = vm.f64Div
vm.funcTable[ops.F64Min] = vm.f64Min
vm.funcTable[ops.F64Max] = vm.f64Max
vm.funcTable[ops.F64Copysign] = vm.f64Copysign
vm.funcTable[ops.I32Const] = vm.i32Const
vm.funcTable[ops.I64Const] = vm.i64Const
vm.funcTable[ops.F32Const] = vm.f32Const
vm.funcTable[ops.F64Const] = vm.f64Const
vm.funcTable[ops.I32ReinterpretF32] = vm.i32ReinterpretF32
vm.funcTable[ops.I64ReinterpretF64] = vm.i64ReinterpretF64
vm.funcTable[ops.F32ReinterpretI32] = vm.f32ReinterpretI32
vm.funcTable[ops.F64ReinterpretI64] = vm.f64ReinterpretI64
vm.funcTable[ops.I32WrapI64] = vm.i32Wrapi64
vm.funcTable[ops.I32TruncSF32] = vm.i32TruncSF32
vm.funcTable[ops.I32TruncUF32] = vm.i32TruncUF32
vm.funcTable[ops.I32TruncSF64] = vm.i32TruncSF64
vm.funcTable[ops.I32TruncUF64] = vm.i32TruncUF64
vm.funcTable[ops.I64ExtendSI32] = vm.i64ExtendSI32
vm.funcTable[ops.I64ExtendUI32] = vm.i64ExtendUI32
vm.funcTable[ops.I64TruncSF32] = vm.i64TruncSF32
vm.funcTable[ops.I64TruncUF32] = vm.i64TruncUF32
vm.funcTable[ops.I64TruncSF64] = vm.i64TruncSF64
vm.funcTable[ops.I64TruncUF64] = vm.i64TruncUF64
vm.funcTable[ops.F32ConvertSI32] = vm.f32ConvertSI32
vm.funcTable[ops.F32ConvertUI32] = vm.f32ConvertUI32
vm.funcTable[ops.F32ConvertSI64] = vm.f32ConvertSI64
vm.funcTable[ops.F32ConvertUI64] = vm.f32ConvertUI64
vm.funcTable[ops.F32DemoteF64] = vm.f32DemoteF64
vm.funcTable[ops.F64ConvertSI32] = vm.f64ConvertSI32
vm.funcTable[ops.F64ConvertUI32] = vm.f64ConvertUI32
vm.funcTable[ops.F64ConvertSI64] = vm.f64ConvertSI64
vm.funcTable[ops.F64ConvertUI64] = vm.f64ConvertUI64
vm.funcTable[ops.F64PromoteF32] = vm.f64PromoteF32
vm.funcTable[ops.I32Load] = vm.i32Load
vm.funcTable[ops.I64Load] = vm.i64Load
vm.funcTable[ops.F32Load] = vm.f32Load
vm.funcTable[ops.F64Load] = vm.f64Load
vm.funcTable[ops.I32Load8s] = vm.i32Load8s
vm.funcTable[ops.I32Load8u] = vm.i32Load8u
vm.funcTable[ops.I32Load16s] = vm.i32Load16s
vm.funcTable[ops.I32Load16u] = vm.i32Load16u
vm.funcTable[ops.I64Load8s] = vm.i64Load8s
vm.funcTable[ops.I64Load8u] = vm.i64Load8u
vm.funcTable[ops.I64Load16s] = vm.i64Load16s
vm.funcTable[ops.I64Load16u] = vm.i64Load16u
vm.funcTable[ops.I64Load32s] = vm.i64Load32s
vm.funcTable[ops.I64Load32u] = vm.i64Load32u
vm.funcTable[ops.I32Store] = vm.i32Store
vm.funcTable[ops.I64Store] = vm.i64Store
vm.funcTable[ops.F32Store] = vm.f32Store
vm.funcTable[ops.F64Store] = vm.f64Store
vm.funcTable[ops.I32Store8] = vm.i32Store8
vm.funcTable[ops.I32Store16] = vm.i32Store16
vm.funcTable[ops.I64Store8] = vm.i64Store8
vm.funcTable[ops.I64Store16] = vm.i64Store16
vm.funcTable[ops.I64Store32] = vm.i64Store32
vm.funcTable[ops.CurrentMemory] = vm.currentMemory
vm.funcTable[ops.GrowMemory] = vm.growMemory
vm.funcTable[ops.Drop] = vm.drop
vm.funcTable[ops.Select] = vm.selectOp
vm.funcTable[ops.GetLocal] = vm.getLocal
vm.funcTable[ops.SetLocal] = vm.setLocal
vm.funcTable[ops.TeeLocal] = vm.teeLocal
vm.funcTable[ops.GetGlobal] = vm.getGlobal
vm.funcTable[ops.SetGlobal] = vm.setGlobal
vm.funcTable[ops.Unreachable] = vm.unreachable
vm.funcTable[ops.Nop] = vm.nop
vm.funcTable[ops.Call] = vm.call
vm.funcTable[ops.CallIndirect] = vm.callIndirect
}

View file

@ -1,377 +0,0 @@
// Copyright 2017 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 compile is used internally by wagon to convert standard structured
// WebAssembly bytecode into an unstructured form suitable for execution by
// it's VM.
// The conversion process consists of translating block instruction sequences
// and branch operators (br, br_if, br_table) to absolute jumps to PC values.
// For instance, an instruction sequence like:
// loop
// i32.const 1
// get_local 0
// i32.add
// set_local 0
// get_local 1
// i32.const 1
// i32.add
// tee_local 1
// get_local 2
// i32.eq
// br_if 0
// end
// Is "compiled" to:
// i32.const 1
// i32.add
// set_local 0
// get_local 1
// i32.const 1
// i32.add
// tee_local 1
// get_local 2
// i32.eq
// jmpnz <addr> <preserve> <discard>
// Where jmpnz is a jump-if-not-zero operator that takes certain arguments
// plus the jump address as immediates.
// This is in contrast with original WebAssembly bytecode, where the target
// of branch operators are relative block depths instead.
package compile
import (
"bytes"
"encoding/binary"
"github.com/go-interpreter/wagon/disasm"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
// A small note on the usage of discard instructions:
// A control operator sequence isn't allowed to access nor modify (pop) operands
// that were pushed outside it. Therefore, each sequence has its own stack
// that may or may not push a value to the original stack, depending on the
// block's signature.
// Instead of creating a new stack every time we enter a control structure,
// we record the current stack height on encountering a control operator.
// After we leave the sequence, the stack height is restored using the discard
// operator. A block with a signature will push a value of that type on the parent
// stack (that is, the stack of the parent block where this block started). The
// OpDiscardPreserveTop operator allows us to preserve this value while
// discarding the remaining ones.
// Branches are rewritten as
// <jmp> <addr>
// Where the address is an 8 byte address, initially set to zero. It is
// later "patched" by patchOffset.
var (
// OpJmp unconditionally jumps to the provided address.
OpJmp byte = 0x0c
// OpJmpZ jumps to the given address if the value at the top of the stack is zero.
OpJmpZ byte = 0x03
// OpJmpNz jumps to the given address if the value at the top of the
// stack is not zero. It also discards elements and optionally preserves
// the topmost value on the stack
OpJmpNz byte = 0x0d
// OpDiscard discards a given number of elements from the execution stack.
OpDiscard byte = 0x0b
// OpDiscardPreserveTop discards a given number of elements from the
// execution stack, while preserving the value on the top of the stack.
OpDiscardPreserveTop byte = 0x05
)
// Target is the "target" of a br_table instruction.
// Unlike other control instructions, br_table does jumps and discarding all
// by itself.
type Target struct {
Addr int64 // The absolute address of the target
Discard int64 // The number of elements to discard
PreserveTop bool // Whether the top of the stack is to be preserved
Return bool // Whether to return in order to take this branch/target
}
// BranchTable is the structure pointed to by a rewritten br_table instruction.
// A rewritten br_table instruction is of the format:
// br_table <table_index>
// where <table_index> is the index to an array of
// BranchTable objects stored by the VM.
type BranchTable struct {
Targets []Target // A list of targets, br_table pops an int value, and jumps to Targets[val]
DefaultTarget Target // If val > len(Targets), the VM will jump here
patchedAddrs []int64 // A list of already patched addresses
blocksLen int // The length of the blocks map in Compile when this table was initialized
}
// block stores the information relevant for a block created by a control operator
// sequence (if...else...end, loop...end, and block...end)
type block struct {
// the byte offset to which the continuation of the label
// created by the block operator is located
// for 'loop', this is the offset of the loop operator itself
// for 'if', 'else', 'block', this is the 'end' operator
offset int64
// Whether this block is created by an 'if' operator
// in that case, the 'offset' field is set to the byte offset
// of the else branch, once the else operator is reached.
ifBlock bool
// if ... else ... end is compiled to
// jmpnz <else-addr> ... jmp <end-addr> ... <discard>
// elseAddrOffset is the byte offset of the else-addr address
// in the new/compiled byte buffer.
elseAddrOffset int64
// Whether this block is created by a 'loop' operator
// in that case, the 'offset' field is set at the end of the block
loopBlock bool
patchOffsets []int64 // A list of offsets in the bytecode stream that need to be patched with the correct jump addresses
discard disasm.StackInfo // Information about the stack created in this block, used while creating Discard instructions
branchTables []*BranchTable // All branch tables that were defined in this block.
}
// Compile rewrites WebAssembly bytecode from its disassembly.
// TODO(vibhavp): Add options for optimizing code. Operators like i32.reinterpret/f32
// are no-ops, and can be safely removed.
func Compile(disassembly []disasm.Instr) ([]byte, []*BranchTable) {
buffer := new(bytes.Buffer)
branchTables := []*BranchTable{}
curBlockDepth := -1
blocks := make(map[int]*block) // maps nesting depths (labels) to blocks
blocks[-1] = &block{}
for _, instr := range disassembly {
if instr.Unreachable {
continue
}
switch instr.Op.Code {
case ops.I32Load, ops.I64Load, ops.F32Load, ops.F64Load, ops.I32Load8s, ops.I32Load8u, ops.I32Load16s, ops.I32Load16u, ops.I64Load8s, ops.I64Load8u, ops.I64Load16s, ops.I64Load16u, ops.I64Load32s, ops.I64Load32u, ops.I32Store, ops.I64Store, ops.F32Store, ops.F64Store, ops.I32Store8, ops.I32Store16, ops.I64Store8, ops.I64Store16, ops.I64Store32:
// memory_immediate has two fields, the alignment and the offset.
// The former is simply an optimization hint and can be safely
// discarded.
instr.Immediates = []interface{}{instr.Immediates[1].(uint32)}
case ops.If:
curBlockDepth++
buffer.WriteByte(OpJmpZ)
blocks[curBlockDepth] = &block{
ifBlock: true,
elseAddrOffset: int64(buffer.Len()),
}
// the address to jump to if the condition for `if` is false
// (i.e when the value on the top of the stack is 0)
binary.Write(buffer, binary.LittleEndian, int64(0))
continue
case ops.Loop:
// there is no condition for entering a loop block
curBlockDepth++
blocks[curBlockDepth] = &block{
offset: int64(buffer.Len()),
ifBlock: false,
loopBlock: true,
discard: *instr.NewStack,
}
continue
case ops.Block:
curBlockDepth++
blocks[curBlockDepth] = &block{
ifBlock: false,
discard: *instr.NewStack,
}
continue
case ops.Else:
ifInstr := disassembly[instr.Block.ElseIfIndex] // the corresponding `if` instruction for this else
if ifInstr.NewStack != nil && ifInstr.NewStack.StackTopDiff != 0 {
// add code for jumping out of a taken if branch
if ifInstr.NewStack.PreserveTop {
buffer.WriteByte(OpDiscardPreserveTop)
} else {
buffer.WriteByte(OpDiscard)
}
binary.Write(buffer, binary.LittleEndian, ifInstr.NewStack.StackTopDiff)
}
buffer.WriteByte(OpJmp)
ifBlockEndOffset := int64(buffer.Len())
binary.Write(buffer, binary.LittleEndian, int64(0))
curOffset := int64(buffer.Len())
ifBlock := blocks[curBlockDepth]
code := buffer.Bytes()
buffer = patchOffset(code, ifBlock.elseAddrOffset, curOffset)
// this is no longer an if block
ifBlock.ifBlock = false
ifBlock.patchOffsets = append(ifBlock.patchOffsets, ifBlockEndOffset)
continue
case ops.End:
depth := curBlockDepth
block := blocks[depth]
if instr.NewStack.StackTopDiff != 0 {
// when exiting a block, discard elements to
// restore stack height.
if instr.NewStack.PreserveTop {
// this is true when the block has a
// signature, and therefore pushes
// a value on to the stack
buffer.WriteByte(OpDiscardPreserveTop)
} else {
buffer.WriteByte(OpDiscard)
}
binary.Write(buffer, binary.LittleEndian, instr.NewStack.StackTopDiff)
}
if !block.loopBlock { // is a normal block
block.offset = int64(buffer.Len())
if block.ifBlock {
code := buffer.Bytes()
buffer = patchOffset(code, block.elseAddrOffset, int64(block.offset))
}
}
for _, offset := range block.patchOffsets {
code := buffer.Bytes()
buffer = patchOffset(code, offset, block.offset)
}
for _, table := range block.branchTables {
table.patchTable(table.blocksLen-depth-1, int64(block.offset))
}
delete(blocks, curBlockDepth)
curBlockDepth--
continue
case ops.Br:
if instr.NewStack != nil && instr.NewStack.StackTopDiff != 0 {
if instr.NewStack.PreserveTop {
buffer.WriteByte(OpDiscardPreserveTop)
} else {
buffer.WriteByte(OpDiscard)
}
binary.Write(buffer, binary.LittleEndian, instr.NewStack.StackTopDiff)
}
buffer.WriteByte(OpJmp)
label := int(instr.Immediates[0].(uint32))
block := blocks[curBlockDepth-int(label)]
block.patchOffsets = append(block.patchOffsets, int64(buffer.Len()))
// write the jump address
binary.Write(buffer, binary.LittleEndian, int64(0))
continue
case ops.BrIf:
buffer.WriteByte(OpJmpNz)
label := int(instr.Immediates[0].(uint32))
block := blocks[curBlockDepth-int(label)]
block.patchOffsets = append(block.patchOffsets, int64(buffer.Len()))
// write the jump address
binary.Write(buffer, binary.LittleEndian, int64(0))
var stackTopDiff int64
// write whether we need to preserve the top
if instr.NewStack == nil || !instr.NewStack.PreserveTop || instr.NewStack.StackTopDiff == 0 {
buffer.WriteByte(byte(0))
} else {
stackTopDiff = instr.NewStack.StackTopDiff
buffer.WriteByte(byte(1))
}
// write the number of elements on the stack we need to discard
binary.Write(buffer, binary.LittleEndian, stackTopDiff)
continue
case ops.BrTable:
branchTable := &BranchTable{
// we subtract one for the implicit block created by
// the function body
blocksLen: len(blocks) - 1,
}
targetCount := instr.Immediates[0].(uint32)
branchTable.Targets = make([]Target, targetCount)
for i := range branchTable.Targets {
// The first immediates is the number of targets, so we ignore that
label := int64(instr.Immediates[i+1].(uint32))
branchTable.Targets[i].Addr = label
branch := instr.Branches[i]
branchTable.Targets[i].Return = branch.IsReturn
branchTable.Targets[i].Discard = branch.StackTopDiff
branchTable.Targets[i].PreserveTop = branch.PreserveTop
}
defaultLabel := int64(instr.Immediates[len(instr.Immediates)-1].(uint32))
branchTable.DefaultTarget.Addr = defaultLabel
defaultBranch := instr.Branches[targetCount]
branchTable.DefaultTarget.Return = defaultBranch.IsReturn
branchTable.DefaultTarget.Discard = defaultBranch.StackTopDiff
branchTable.DefaultTarget.PreserveTop = defaultBranch.PreserveTop
branchTables = append(branchTables, branchTable)
for _, block := range blocks {
block.branchTables = append(block.branchTables, branchTable)
}
buffer.WriteByte(ops.BrTable)
binary.Write(buffer, binary.LittleEndian, int64(len(branchTables)-1))
}
buffer.WriteByte(instr.Op.Code)
for _, imm := range instr.Immediates {
err := binary.Write(buffer, binary.LittleEndian, imm)
if err != nil {
panic(err)
}
}
}
// writing nop as the last instructions allows us to branch out of the
// function (ie, return)
addr := buffer.Len()
buffer.WriteByte(ops.Nop)
// patch all references to the "root" block of the function body
for _, offset := range blocks[-1].patchOffsets {
code := buffer.Bytes()
buffer = patchOffset(code, offset, int64(addr))
}
for _, table := range branchTables {
table.patchedAddrs = nil
}
return buffer.Bytes(), branchTables
}
// replace the address starting at start with addr
func patchOffset(code []byte, start int64, addr int64) *bytes.Buffer {
var shift uint
for i := int64(0); i < 8; i++ {
code[start+i] = byte(addr >> shift)
shift += 8
}
buf := new(bytes.Buffer)
buf.Write(code)
return buf
}
func (table *BranchTable) patchTable(block int, addr int64) {
if block < 0 {
panic("Invalid block value")
}
for i, target := range table.Targets {
if !table.isAddr(target.Addr) && target.Addr == int64(block) {
table.Targets[i].Addr = addr
}
}
if table.DefaultTarget.Addr == int64(block) {
table.DefaultTarget.Addr = addr
}
table.patchedAddrs = append(table.patchedAddrs, addr)
}
// Whether the given value is an instruction (or the block depth)
func (table *BranchTable) isAddr(addr int64) bool {
for _, t := range table.patchedAddrs {
if t == addr {
return true
}
}
return false
}

View file

@ -1,214 +0,0 @@
// Copyright 2017 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 (
"errors"
"math"
)
// ErrOutOfBoundsMemoryAccess is the error value used while trapping the VM
// when it detects an out of bounds access to the linear memory.
var ErrOutOfBoundsMemoryAccess = errors.New("exec: out of bounds memory access")
func (vm *VM) fetchBaseAddr() int {
return int(vm.fetchUint32() + uint32(vm.popInt32()))
}
// inBounds returns true when the next vm.fetchBaseAddr() + offset
// indices are in bounds accesses to the linear memory.
func (vm *VM) inBounds(offset int) bool {
addr := endianess.Uint32(vm.ctx.code[vm.ctx.pc:]) + uint32(vm.ctx.stack[len(vm.ctx.stack)-1])
return int(addr)+offset < len(vm.memory)
}
// curMem returns a slice to the memeory segment pointed to by
// the current base address on the bytecode stream.
func (vm *VM) curMem() []byte {
return vm.memory[vm.fetchBaseAddr():]
}
func (vm *VM) i32Load() {
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint32(endianess.Uint32(vm.curMem()))
}
func (vm *VM) i32Load8s() {
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushInt32(int32(int8(vm.memory[vm.fetchBaseAddr()])))
}
func (vm *VM) i32Load8u() {
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint32(uint32(uint8(vm.memory[vm.fetchBaseAddr()])))
}
func (vm *VM) i32Load16s() {
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushInt32(int32(int16(endianess.Uint16(vm.curMem()))))
}
func (vm *VM) i32Load16u() {
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint32(uint32(endianess.Uint16(vm.curMem())))
}
func (vm *VM) i64Load() {
if !vm.inBounds(7) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint64(endianess.Uint64(vm.curMem()))
}
func (vm *VM) i64Load8s() {
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushInt64(int64(int8(vm.memory[vm.fetchBaseAddr()])))
}
func (vm *VM) i64Load8u() {
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint64(uint64(uint8(vm.memory[vm.fetchBaseAddr()])))
}
func (vm *VM) i64Load16s() {
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushInt64(int64(int16(endianess.Uint16(vm.curMem()))))
}
func (vm *VM) i64Load16u() {
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint64(uint64(endianess.Uint16(vm.curMem())))
}
func (vm *VM) i64Load32s() {
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushInt64(int64(int32(endianess.Uint32(vm.curMem()))))
}
func (vm *VM) i64Load32u() {
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushUint64(uint64(endianess.Uint32(vm.curMem())))
}
func (vm *VM) f32Store() {
v := math.Float32bits(vm.popFloat32())
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint32(vm.curMem(), v)
}
func (vm *VM) f32Load() {
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushFloat32(math.Float32frombits(endianess.Uint32(vm.curMem())))
}
func (vm *VM) f64Store() {
v := math.Float64bits(vm.popFloat64())
if !vm.inBounds(7) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint64(vm.curMem(), v)
}
func (vm *VM) f64Load() {
if !vm.inBounds(7) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.pushFloat64(math.Float64frombits(endianess.Uint64(vm.curMem())))
}
func (vm *VM) i32Store() {
v := vm.popUint32()
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint32(vm.curMem(), v)
}
func (vm *VM) i32Store8() {
v := byte(uint8(vm.popUint32()))
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.memory[vm.fetchBaseAddr()] = v
}
func (vm *VM) i32Store16() {
v := uint16(vm.popUint32())
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint16(vm.curMem(), v)
}
func (vm *VM) i64Store() {
v := vm.popUint64()
if !vm.inBounds(7) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint64(vm.curMem(), v)
}
func (vm *VM) i64Store8() {
v := byte(uint8(vm.popUint64()))
if !vm.inBounds(0) {
panic(ErrOutOfBoundsMemoryAccess)
}
vm.memory[vm.fetchBaseAddr()] = v
}
func (vm *VM) i64Store16() {
v := uint16(vm.popUint64())
if !vm.inBounds(1) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint16(vm.curMem(), v)
}
func (vm *VM) i64Store32() {
v := uint32(vm.popUint64())
if !vm.inBounds(3) {
panic(ErrOutOfBoundsMemoryAccess)
}
endianess.PutUint32(vm.curMem(), v)
}
func (vm *VM) currentMemory() {
_ = vm.fetchInt8() // reserved (https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/BinaryEncoding.md#memory-related-operators-described-here)
vm.pushInt32(int32(len(vm.memory) / wasmPageSize))
}
func (vm *VM) growMemory() {
_ = vm.fetchInt8() // reserved (https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/BinaryEncoding.md#memory-related-operators-described-here)
curLen := len(vm.memory) / wasmPageSize
n := vm.popInt32()
vm.memory = append(vm.memory, make([]byte, n*wasmPageSize)...)
vm.pushInt32(int32(curLen))
}

View file

@ -1,508 +0,0 @@
// Copyright 2017 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 (
"math"
"math/bits"
)
// int32 operators
func (vm *VM) i32Clz() {
vm.pushUint64(uint64(bits.LeadingZeros32(vm.popUint32())))
}
func (vm *VM) i32Ctz() {
vm.pushUint64(uint64(bits.TrailingZeros32(vm.popUint32())))
}
func (vm *VM) i32Popcnt() {
vm.pushUint64(uint64(bits.OnesCount32(vm.popUint32())))
}
func (vm *VM) i32Add() {
vm.pushUint32(vm.popUint32() + vm.popUint32())
}
func (vm *VM) i32Mul() {
vm.pushUint32(vm.popUint32() * vm.popUint32())
}
func (vm *VM) i32DivS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushInt32(v1 / v2)
}
func (vm *VM) i32DivU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(v1 / v2)
}
func (vm *VM) i32RemS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushInt32(v1 % v2)
}
func (vm *VM) i32RemU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(v1 % v2)
}
func (vm *VM) i32Sub() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(v1 - v2)
}
func (vm *VM) i32And() {
vm.pushUint32(vm.popUint32() & vm.popUint32())
}
func (vm *VM) i32Or() {
vm.pushUint32(vm.popUint32() | vm.popUint32())
}
func (vm *VM) i32Xor() {
vm.pushUint32(vm.popUint32() ^ vm.popUint32())
}
func (vm *VM) i32Shl() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(v1 << v2)
}
func (vm *VM) i32ShrU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(v1 >> v2)
}
func (vm *VM) i32ShrS() {
v2 := vm.popUint32()
v1 := vm.popInt32()
vm.pushInt32(v1 >> v2)
}
func (vm *VM) i32Rotl() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(bits.RotateLeft32(v1, int(v2)))
}
func (vm *VM) i32Rotr() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushUint32(bits.RotateLeft32(v1, -int(v2)))
}
func (vm *VM) i32LeS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushBool(v1 <= v2)
}
func (vm *VM) i32LeU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushBool(v1 <= v2)
}
func (vm *VM) i32LtS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushBool(v1 < v2)
}
func (vm *VM) i32LtU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushBool(v1 < v2)
}
func (vm *VM) i32GtS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushBool(v1 > v2)
}
func (vm *VM) i32GeS() {
v2 := vm.popInt32()
v1 := vm.popInt32()
vm.pushBool(v1 >= v2)
}
func (vm *VM) i32GtU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushBool(v1 > v2)
}
func (vm *VM) i32GeU() {
v2 := vm.popUint32()
v1 := vm.popUint32()
vm.pushBool(v1 >= v2)
}
func (vm *VM) i32Eqz() {
vm.pushBool(vm.popUint32() == 0)
}
func (vm *VM) i32Eq() {
vm.pushBool(vm.popUint32() == vm.popUint32())
}
func (vm *VM) i32Ne() {
vm.pushBool(vm.popUint32() != vm.popUint32())
}
// int64 operators
func (vm *VM) i64Clz() {
vm.pushUint64(uint64(bits.LeadingZeros64(vm.popUint64())))
}
func (vm *VM) i64Ctz() {
vm.pushUint64(uint64(bits.TrailingZeros64(vm.popUint64())))
}
func (vm *VM) i64Popcnt() {
vm.pushUint64(uint64(bits.OnesCount64(vm.popUint64())))
}
func (vm *VM) i64Add() {
vm.pushUint64(vm.popUint64() + vm.popUint64())
}
func (vm *VM) i64Sub() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushUint64(v1 - v2)
}
func (vm *VM) i64Mul() {
vm.pushUint64(vm.popUint64() * vm.popUint64())
}
func (vm *VM) i64DivS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushInt64(v1 / v2)
}
func (vm *VM) i64DivU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushUint64(v1 / v2)
}
func (vm *VM) i64RemS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushInt64(v1 % v2)
}
func (vm *VM) i64RemU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushUint64(v1 % v2)
}
func (vm *VM) i64And() {
vm.pushUint64(vm.popUint64() & vm.popUint64())
}
func (vm *VM) i64Or() {
vm.pushUint64(vm.popUint64() | vm.popUint64())
}
func (vm *VM) i64Xor() {
vm.pushUint64(vm.popUint64() ^ vm.popUint64())
}
func (vm *VM) i64Shl() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushUint64(v1 << v2)
}
func (vm *VM) i64ShrS() {
v2 := vm.popUint64()
v1 := vm.popInt64()
vm.pushInt64(v1 >> v2)
}
func (vm *VM) i64ShrU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushUint64(v1 >> v2)
}
func (vm *VM) i64Rotl() {
v2 := vm.popInt64()
v1 := vm.popUint64()
vm.pushUint64(bits.RotateLeft64(v1, int(v2)))
}
func (vm *VM) i64Rotr() {
v2 := vm.popInt64()
v1 := vm.popUint64()
vm.pushUint64(bits.RotateLeft64(v1, -int(v2)))
}
func (vm *VM) i64Eq() {
vm.pushBool(vm.popUint64() == vm.popUint64())
}
func (vm *VM) i64Eqz() {
vm.pushBool(vm.popUint64() == 0)
}
func (vm *VM) i64Ne() {
vm.pushBool(vm.popUint64() != vm.popUint64())
}
func (vm *VM) i64LtS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushBool(v1 < v2)
}
func (vm *VM) i64LtU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushBool(v1 < v2)
}
func (vm *VM) i64GtS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushBool(v1 > v2)
}
func (vm *VM) i64GtU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushBool(v1 > v2)
}
func (vm *VM) i64LeU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushBool(v1 <= v2)
}
func (vm *VM) i64LeS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushBool(v1 <= v2)
}
func (vm *VM) i64GeS() {
v2 := vm.popInt64()
v1 := vm.popInt64()
vm.pushBool(v1 >= v2)
}
func (vm *VM) i64GeU() {
v2 := vm.popUint64()
v1 := vm.popUint64()
vm.pushBool(v1 >= v2)
}
// float32 operators
func (vm *VM) f32Abs() {
vm.pushFloat32(float32(math.Abs(float64(vm.popFloat32()))))
}
func (vm *VM) f32Neg() {
vm.pushFloat32(-vm.popFloat32())
}
func (vm *VM) f32Ceil() {
vm.pushFloat32(float32(math.Ceil(float64(vm.popFloat32()))))
}
func (vm *VM) f32Floor() {
vm.pushFloat32(float32(math.Floor(float64(vm.popFloat32()))))
}
func (vm *VM) f32Trunc() {
vm.pushFloat32(float32(math.Trunc(float64(vm.popFloat32()))))
}
func (vm *VM) f32Nearest() {
f := vm.popFloat32()
vm.pushFloat32(float32(int32(f + float32(math.Copysign(0.5, float64(f))))))
}
func (vm *VM) f32Sqrt() {
vm.pushFloat32(float32(math.Sqrt(float64(vm.popFloat32()))))
}
func (vm *VM) f32Add() {
vm.pushFloat32(vm.popFloat32() + vm.popFloat32())
}
func (vm *VM) f32Sub() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushFloat32(v1 - v2)
}
func (vm *VM) f32Mul() {
vm.pushFloat32(vm.popFloat32() * vm.popFloat32())
}
func (vm *VM) f32Div() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushFloat32(v1 / v2)
}
func (vm *VM) f32Min() {
vm.pushFloat32(float32(math.Min(float64(vm.popFloat32()), float64(vm.popFloat32()))))
}
func (vm *VM) f32Max() {
vm.pushFloat32(float32(math.Max(float64(vm.popFloat32()), float64(vm.popFloat32()))))
}
func (vm *VM) f32Copysign() {
vm.pushFloat32(float32(math.Copysign(float64(vm.popFloat32()), float64(vm.popFloat32()))))
}
func (vm *VM) f32Eq() {
vm.pushBool(vm.popFloat32() == vm.popFloat32())
}
func (vm *VM) f32Ne() {
vm.pushBool(vm.popFloat32() != vm.popFloat32())
}
func (vm *VM) f32Lt() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushBool(v1 < v2)
}
func (vm *VM) f32Gt() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushBool(v1 > v2)
}
func (vm *VM) f32Le() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushBool(v1 <= v2)
}
func (vm *VM) f32Ge() {
v2 := vm.popFloat32()
v1 := vm.popFloat32()
vm.pushBool(v1 >= v2)
}
// float64 operators
func (vm *VM) f64Abs() {
vm.pushFloat64(math.Abs(vm.popFloat64()))
}
func (vm *VM) f64Neg() {
vm.pushFloat64(-vm.popFloat64())
}
func (vm *VM) f64Ceil() {
vm.pushFloat64(math.Ceil(vm.popFloat64()))
}
func (vm *VM) f64Floor() {
vm.pushFloat64(math.Floor(vm.popFloat64()))
}
func (vm *VM) f64Trunc() {
vm.pushFloat64(math.Trunc(vm.popFloat64()))
}
func (vm *VM) f64Nearest() {
f := vm.popFloat64()
vm.pushFloat64(float64(int64(f + math.Copysign(0.5, f))))
}
func (vm *VM) f64Sqrt() {
vm.pushFloat64(math.Sqrt(vm.popFloat64()))
}
func (vm *VM) f64Add() {
vm.pushFloat64(vm.popFloat64() + vm.popFloat64())
}
func (vm *VM) f64Sub() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushFloat64(v1 - v2)
}
func (vm *VM) f64Mul() {
vm.pushFloat64(vm.popFloat64() * vm.popFloat64())
}
func (vm *VM) f64Div() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushFloat64(v1 / v2)
}
func (vm *VM) f64Min() {
vm.pushFloat64(math.Min(vm.popFloat64(), vm.popFloat64()))
}
func (vm *VM) f64Max() {
vm.pushFloat64(math.Max(vm.popFloat64(), vm.popFloat64()))
}
func (vm *VM) f64Copysign() {
vm.pushFloat64(math.Copysign(vm.popFloat64(), vm.popFloat64()))
}
func (vm *VM) f64Eq() {
vm.pushBool(vm.popFloat64() == vm.popFloat64())
}
func (vm *VM) f64Ne() {
vm.pushBool(vm.popFloat64() != vm.popFloat64())
}
func (vm *VM) f64Lt() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushBool(v1 < v2)
}
func (vm *VM) f64Gt() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushBool(v1 > v2)
}
func (vm *VM) f64Le() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushBool(v1 <= v2)
}
func (vm *VM) f64Ge() {
v2 := vm.popFloat64()
v1 := vm.popFloat64()
vm.pushBool(v1 >= v2)
}

View file

@ -1,21 +0,0 @@
// Copyright 2017 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
func (vm *VM) drop() {
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-1]
}
func (vm *VM) selectOp() {
c := vm.popUint32()
val2 := vm.popUint64()
val1 := vm.popUint64()
if c != 0 {
vm.pushUint64(val1)
} else {
vm.pushUint64(val2)
}
}

View file

@ -1,29 +0,0 @@
// Copyright 2017 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 (
"math"
)
// these operations are essentially no-ops.
// TODO(vibhavp): Add optimisations to package compiles that
// removes them from the original bytecode.
func (vm *VM) i32ReinterpretF32() {
vm.pushUint32(math.Float32bits(vm.popFloat32()))
}
func (vm *VM) i64ReinterpretF64() {
vm.pushUint64(math.Float64bits(vm.popFloat64()))
}
func (vm *VM) f32ReinterpretI32() {
vm.pushFloat32(math.Float32frombits(vm.popUint32()))
}
func (vm *VM) f64ReinterpretI64() {
vm.pushFloat64(math.Float64frombits(vm.popUint64()))
}

View file

@ -1,31 +0,0 @@
// Copyright 2017 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
func (vm *VM) getLocal() {
index := vm.fetchUint32()
vm.pushUint64(vm.ctx.locals[int(index)])
}
func (vm *VM) setLocal() {
index := vm.fetchUint32()
vm.ctx.locals[int(index)] = vm.popUint64()
}
func (vm *VM) teeLocal() {
index := vm.fetchUint32()
val := vm.ctx.stack[len(vm.ctx.stack)-1]
vm.ctx.locals[int(index)] = val
}
func (vm *VM) getGlobal() {
index := vm.fetchUint32()
vm.pushUint64(vm.globals[int(index)])
}
func (vm *VM) setGlobal() {
index := vm.fetchUint32()
vm.globals[int(index)] = vm.popUint64()
}

View file

@ -1,462 +0,0 @@
// Copyright 2017 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 provides functions for executing WebAssembly bytecode.
package exec
import (
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"github.com/go-interpreter/wagon/disasm"
"github.com/go-interpreter/wagon/exec/internal/compile"
"github.com/go-interpreter/wagon/wasm"
ops "github.com/go-interpreter/wagon/wasm/operators"
)
var (
// ErrMultipleLinearMemories is returned by (*VM).NewVM when the module
// has more then one entries in the linear memory space.
ErrMultipleLinearMemories = errors.New("exec: more than one linear memories in module")
// ErrInvalidArgumentCount is returned by (*VM).ExecCode when an invalid
// number of arguments to the WebAssembly function are passed to it.
ErrInvalidArgumentCount = errors.New("exec: invalid number of arguments to function")
)
// InvalidReturnTypeError is returned by (*VM).ExecCode when the module
// specifies an invalid return type value for the executed function.
type InvalidReturnTypeError int8
func (e InvalidReturnTypeError) Error() string {
return fmt.Sprintf("Function has invalid return value_type: %d", int8(e))
}
// InvalidFunctionIndexError is returned by (*VM).ExecCode when the function
// index provided is invalid.
type InvalidFunctionIndexError int64
func (e InvalidFunctionIndexError) Error() string {
return fmt.Sprintf("Invalid index to function index space: %d", int64(e))
}
type context struct {
stack []uint64
locals []uint64
code []byte
pc int64
curFunc int64
}
// VM is the execution context for executing WebAssembly bytecode.
type VM struct {
ctx context
module *wasm.Module
globals []uint64
memory []byte
funcs []function
funcTable [256]func()
// RecoverPanic controls whether the `ExecCode` method
// recovers from a panic and returns it as an error
// instead.
// A panic can occur either when executing an invalid VM
// or encountering an invalid instruction, e.g. `unreachable`.
RecoverPanic bool
abort bool // Flag for host functions to terminate execution
}
// As per the WebAssembly spec: https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/Semantics.md#linear-memory
const wasmPageSize = 65536 // (64 KB)
var endianess = binary.LittleEndian
// NewVM creates a new VM from a given module. If the module defines a
// start function, it will be executed.
func NewVM(module *wasm.Module) (*VM, error) {
var vm VM
if module.Memory != nil && len(module.Memory.Entries) != 0 {
if len(module.Memory.Entries) > 1 {
return nil, ErrMultipleLinearMemories
}
vm.memory = make([]byte, uint(module.Memory.Entries[0].Limits.Initial)*wasmPageSize)
copy(vm.memory, module.LinearMemoryIndexSpace[0])
}
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
}
totalLocalVars := 0
totalLocalVars += len(fn.Sig.ParamTypes)
for _, entry := range fn.Body.Locals {
totalLocalVars += int(entry.Count)
}
code, table := compile.Compile(disassembly.Code)
vm.funcs[i] = compiledFunction{
code: code,
branchTables: table,
maxDepth: disassembly.MaxDepth,
totalLocalVars: totalLocalVars,
args: len(fn.Sig.ParamTypes),
returns: len(fn.Sig.ReturnTypes) != 0,
}
}
for i, global := range module.GlobalIndexSpace {
val, err := module.ExecInitExpr(global.Init)
if err != nil {
return nil, err
}
switch v := val.(type) {
case int32:
vm.globals[i] = uint64(v)
case int64:
vm.globals[i] = uint64(v)
case float32:
vm.globals[i] = uint64(math.Float32bits(v))
case float64:
vm.globals[i] = uint64(math.Float64bits(v))
}
}
if module.Start != nil {
_, err := vm.ExecCode(int64(module.Start.Index))
if err != nil {
return nil, err
}
}
return &vm, nil
}
// Memory returns the linear memory space for the VM.
func (vm *VM) Memory() []byte {
return vm.memory
}
func (vm *VM) pushBool(v bool) {
if v {
vm.pushUint64(1)
} else {
vm.pushUint64(0)
}
}
func (vm *VM) fetchBool() bool {
return vm.fetchInt8() != 0
}
func (vm *VM) fetchInt8() int8 {
i := int8(vm.ctx.code[vm.ctx.pc])
vm.ctx.pc++
return i
}
func (vm *VM) fetchUint32() uint32 {
v := endianess.Uint32(vm.ctx.code[vm.ctx.pc:])
vm.ctx.pc += 4
return v
}
func (vm *VM) fetchInt32() int32 {
return int32(vm.fetchUint32())
}
func (vm *VM) fetchFloat32() float32 {
return math.Float32frombits(vm.fetchUint32())
}
func (vm *VM) fetchUint64() uint64 {
v := endianess.Uint64(vm.ctx.code[vm.ctx.pc:])
vm.ctx.pc += 8
return v
}
func (vm *VM) fetchInt64() int64 {
return int64(vm.fetchUint64())
}
func (vm *VM) fetchFloat64() float64 {
return math.Float64frombits(vm.fetchUint64())
}
func (vm *VM) popUint64() uint64 {
i := vm.ctx.stack[len(vm.ctx.stack)-1]
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-1]
return i
}
func (vm *VM) popInt64() int64 {
return int64(vm.popUint64())
}
func (vm *VM) popFloat64() float64 {
return math.Float64frombits(vm.popUint64())
}
func (vm *VM) popUint32() uint32 {
return uint32(vm.popUint64())
}
func (vm *VM) popInt32() int32 {
return int32(vm.popUint32())
}
func (vm *VM) popFloat32() float32 {
return math.Float32frombits(vm.popUint32())
}
func (vm *VM) pushUint64(i uint64) {
vm.ctx.stack = append(vm.ctx.stack, i)
}
func (vm *VM) pushInt64(i int64) {
vm.pushUint64(uint64(i))
}
func (vm *VM) pushFloat64(f float64) {
vm.pushUint64(math.Float64bits(f))
}
func (vm *VM) pushUint32(i uint32) {
vm.pushUint64(uint64(i))
}
func (vm *VM) pushInt32(i int32) {
vm.pushUint64(uint64(i))
}
func (vm *VM) pushFloat32(f float32) {
vm.pushUint32(math.Float32bits(f))
}
// ExecCode calls the function with the given index and arguments.
// fnIndex should be a valid index into the function index space of
// the VM's module.
func (vm *VM) ExecCode(fnIndex int64, args ...uint64) (rtrn interface{}, err error) {
// If used as a library, client code should set vm.RecoverPanic to true
// in order to have an error returned.
if vm.RecoverPanic {
defer func() {
if r := recover(); r != nil {
switch e := r.(type) {
case error:
err = e
default:
err = fmt.Errorf("exec: %v", e)
}
}
}()
}
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, 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
vm.ctx.curFunc = fnIndex
for i, arg := range args {
vm.ctx.locals[i] = arg
}
res := vm.execCode(compiled)
if compiled.returns {
rtrnType := vm.module.GetFunction(int(fnIndex)).Sig.ReturnTypes[0]
switch rtrnType {
case wasm.ValueTypeI32:
rtrn = uint32(res)
case wasm.ValueTypeI64:
rtrn = uint64(res)
case wasm.ValueTypeF32:
rtrn = math.Float32frombits(uint32(res))
case wasm.ValueTypeF64:
rtrn = math.Float64frombits(res)
default:
return nil, InvalidReturnTypeError(rtrnType)
}
}
return rtrn, nil
}
func (vm *VM) execCode(compiled compiledFunction) uint64 {
outer:
for int(vm.ctx.pc) < len(vm.ctx.code) && !vm.abort {
op := vm.ctx.code[vm.ctx.pc]
vm.ctx.pc++
switch op {
case ops.Return:
break outer
case compile.OpJmp:
vm.ctx.pc = vm.fetchInt64()
continue
case compile.OpJmpZ:
target := vm.fetchInt64()
if vm.popUint32() == 0 {
vm.ctx.pc = target
continue
}
case compile.OpJmpNz:
target := vm.fetchInt64()
preserveTop := vm.fetchBool()
discard := vm.fetchInt64()
if vm.popUint32() != 0 {
vm.ctx.pc = target
var top uint64
if preserveTop {
top = vm.ctx.stack[len(vm.ctx.stack)-1]
}
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-int(discard)]
if preserveTop {
vm.pushUint64(top)
}
continue
}
case ops.BrTable:
index := vm.fetchInt64()
label := vm.popInt32()
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)]
} else {
target = table.DefaultTarget
}
if target.Return {
break outer
}
vm.ctx.pc = target.Addr
var top uint64
if target.PreserveTop {
top = vm.ctx.stack[len(vm.ctx.stack)-1]
}
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-int(target.Discard)]
if target.PreserveTop {
vm.pushUint64(top)
}
continue
case compile.OpDiscard:
place := vm.fetchInt64()
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-int(place)]
case compile.OpDiscardPreserveTop:
top := vm.ctx.stack[len(vm.ctx.stack)-1]
place := vm.fetchInt64()
vm.ctx.stack = vm.ctx.stack[:len(vm.ctx.stack)-int(place)]
vm.pushUint64(top)
default:
vm.funcTable[op]()
}
}
if compiled.returns {
return vm.ctx.stack[len(vm.ctx.stack)-1]
}
return 0
}
// Process is a proxy passed to host functions in order to access
// things such as memory and control.
type Process struct {
vm *VM
}
// NewProcess creates a VM interface object for host functions
func NewProcess(vm *VM) *Process {
return &Process{vm: vm}
}
// ReadAt implements the ReaderAt interface: it copies into p
// the content of memory at offset off.
func (proc *Process) ReadAt(p []byte, off int64) (int, error) {
mem := proc.vm.Memory()
var length int
if len(mem) < len(p)+int(off) {
length = len(mem) - int(off)
} else {
length = len(p)
}
copy(p, mem[off:off+int64(length)])
var err error
if length < len(p) {
err = io.ErrShortBuffer
}
return length, err
}
// WriteAt implements the WriterAt interface: it writes the content of p
// into the VM memory at offset off.
func (proc *Process) WriteAt(p []byte, off int64) (int, error) {
mem := proc.vm.Memory()
var length int
if len(mem) < len(p)+int(off) {
length = len(mem) - int(off)
} else {
length = len(p)
}
copy(mem[off:], p[:length])
var err error
if length < len(p) {
err = io.ErrShortWrite
}
return length, err
}
// Terminate stops the execution of the current module.
func (proc *Process) Terminate() {
proc.vm.abort = true
}

View file

@ -1 +0,0 @@
module github.com/go-interpreter/wagon

View file

@ -1,40 +0,0 @@
// Copyright 2017 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 stack implements a growable uint64 stack
package stack
type Stack struct {
slice []uint64
}
func (s *Stack) Push(b uint64) {
s.slice = append(s.slice, b)
}
func (s *Stack) Pop() uint64 {
v := s.Top()
s.slice = s.slice[:len(s.slice)-1]
return v
}
func (s *Stack) SetTop(v uint64) {
s.slice[len(s.slice)-1] = v
}
func (s *Stack) Top() uint64 {
return s.slice[len(s.slice)-1]
}
func (s *Stack) Get(i int) uint64 {
return s.slice[i]
}
func (s *Stack) Set(i int, v uint64) {
s.slice[i] = v
}
func (s *Stack) Len() int {
return len(s.slice)
}

View file

@ -1,6 +0,0 @@
// Copyright 2017 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 wasm provides functions for reading and parsing WebAssembly modules.
package wasm

View file

@ -1,70 +0,0 @@
// 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 wasm
import (
"bytes"
"encoding/binary"
"io"
"github.com/go-interpreter/wagon/wasm/leb128"
)
const currentVersion = 0x01
// EncodeModule writes a provided module to w using WASM binary encoding.
func EncodeModule(w io.Writer, m *Module) error {
if err := writeU32(w, Magic); err != nil {
return err
}
if err := writeU32(w, currentVersion); err != nil {
return err
}
sections := m.Sections
buf := new(bytes.Buffer)
for _, s := range sections {
if _, err := leb128.WriteVarUint32(w, uint32(s.SectionID())); err != nil {
return err
}
buf.Reset()
if err := s.WritePayload(buf); err != nil {
return err
}
if _, err := leb128.WriteVarUint32(w, uint32(buf.Len())); err != nil {
return err
}
if _, err := buf.WriteTo(w); err != nil {
return err
}
}
return nil
}
func writeStringUint(w io.Writer, s string) error {
return writeBytesUint(w, []byte(s))
}
func writeBytesUint(w io.Writer, p []byte) error {
_, err := leb128.WriteVarUint32(w, uint32(len(p)))
if err != nil {
return err
}
_, err = w.Write(p)
return err
}
func writeU32(w io.Writer, n uint32) error {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], n)
_, err := w.Write(buf[:])
return err
}
func writeU64(w io.Writer, n uint64) error {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], n)
_, err := w.Write(buf[:])
return err
}

View file

@ -1,200 +0,0 @@
// Copyright 2017 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 wasm
import (
"errors"
"fmt"
"io"
"github.com/go-interpreter/wagon/wasm/leb128"
)
// Import is an interface implemented by types that can be imported by a WebAssembly module.
type Import interface {
Kind() External
Marshaler
isImport()
}
// ImportEntry describes an import statement in a Wasm module.
type ImportEntry struct {
ModuleName string // module name string
FieldName string // field name string
// If Kind is Function, Type is a FuncImport containing the type index of the function signature
// If Kind is Table, Type is a TableImport containing the type of the imported table
// If Kind is Memory, Type is a MemoryImport containing the type of the imported memory
// If the Kind is Global, Type is a GlobalVarImport
Type Import
}
type FuncImport struct {
Type uint32
}
func (FuncImport) isImport() {}
func (FuncImport) Kind() External {
return ExternalFunction
}
func (f FuncImport) MarshalWASM(w io.Writer) error {
_, err := leb128.WriteVarUint32(w, uint32(f.Type))
return err
}
type TableImport struct {
Type Table
}
func (TableImport) isImport() {}
func (TableImport) Kind() External {
return ExternalTable
}
func (t TableImport) MarshalWASM(w io.Writer) error {
return t.Type.MarshalWASM(w)
}
type MemoryImport struct {
Type Memory
}
func (MemoryImport) isImport() {}
func (MemoryImport) Kind() External {
return ExternalMemory
}
func (t MemoryImport) MarshalWASM(w io.Writer) error {
return t.Type.MarshalWASM(w)
}
type GlobalVarImport struct {
Type GlobalVar
}
func (GlobalVarImport) isImport() {}
func (GlobalVarImport) Kind() External {
return ExternalGlobal
}
func (t GlobalVarImport) MarshalWASM(w io.Writer) error {
return t.Type.MarshalWASM(w)
}
var (
ErrImportMutGlobal = errors.New("wasm: cannot import global mutable variable")
ErrNoExportsInImportedModule = errors.New("wasm: imported module has no exports")
)
type InvalidExternalError uint8
func (e InvalidExternalError) Error() string {
return fmt.Sprintf("wasm: invalid external_kind value %d", uint8(e))
}
type ExportNotFoundError struct {
ModuleName string
FieldName string
}
type KindMismatchError struct {
ModuleName string
FieldName string
Import External
Export External
}
func (e KindMismatchError) Error() string {
return fmt.Sprintf("wasm: Mismatching import and export external kind values for %s.%s (%v, %v)", e.FieldName, e.ModuleName, e.Import, e.Export)
}
func (e ExportNotFoundError) Error() string {
return fmt.Sprintf("wasm: couldn't find export with name %s in module %s", e.FieldName, e.ModuleName)
}
type InvalidFunctionIndexError uint32
func (e InvalidFunctionIndexError) Error() string {
return fmt.Sprintf("wasm: Invalid index to function index space: %#x", uint32(e))
}
func (module *Module) resolveImports(resolve ResolveFunc) error {
if module.Import == nil {
return nil
}
modules := make(map[string]*Module)
var funcs uint32
for _, importEntry := range module.Import.Entries {
importedModule, ok := modules[importEntry.ModuleName]
if !ok {
var err error
importedModule, err = resolve(importEntry.ModuleName)
if err != nil {
return err
}
modules[importEntry.ModuleName] = importedModule
}
if importedModule.Export == nil {
return ErrNoExportsInImportedModule
}
exportEntry, ok := importedModule.Export.Entries[importEntry.FieldName]
if !ok {
return ExportNotFoundError{importEntry.ModuleName, importEntry.FieldName}
}
if exportEntry.Kind != importEntry.Type.Kind() {
return KindMismatchError{
FieldName: importEntry.FieldName,
ModuleName: importEntry.ModuleName,
Import: importEntry.Type.Kind(),
Export: exportEntry.Kind,
}
}
index := exportEntry.Index
switch exportEntry.Kind {
case ExternalFunction:
fn := importedModule.GetFunction(int(index))
if fn == nil {
return InvalidFunctionIndexError(index)
}
module.FunctionIndexSpace = append(module.FunctionIndexSpace, *fn)
module.Code.Bodies = append(module.Code.Bodies, *fn.Body)
module.imports.Funcs = append(module.imports.Funcs, funcs)
funcs++
case ExternalGlobal:
glb := importedModule.GetGlobal(int(index))
if glb == nil {
return InvalidGlobalIndexError(index)
}
if glb.Type.Mutable {
return ErrImportMutGlobal
}
module.GlobalIndexSpace = append(module.GlobalIndexSpace, *glb)
module.imports.Globals++
// In both cases below, index should be always 0 (according to the MVP)
// We check it against the length of the index space anyway.
case ExternalTable:
if int(index) >= len(importedModule.TableIndexSpace) {
return InvalidTableIndexError(index)
}
module.TableIndexSpace[0] = importedModule.TableIndexSpace[0]
module.imports.Tables++
case ExternalMemory:
if int(index) >= len(importedModule.LinearMemoryIndexSpace) {
return InvalidLinearMemoryIndexError(index)
}
module.LinearMemoryIndexSpace[0] = importedModule.LinearMemoryIndexSpace[0]
module.imports.Memories++
default:
return InvalidExternalError(exportEntry.Kind)
}
}
return nil
}

View file

@ -1,180 +0,0 @@
// Copyright 2017 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 wasm
import (
"fmt"
"reflect"
)
type InvalidTableIndexError uint32
func (e InvalidTableIndexError) Error() string {
return fmt.Sprintf("wasm: Invalid table to table index space: %d", uint32(e))
}
type InvalidValueTypeInitExprError struct {
Wanted reflect.Kind
Got reflect.Kind
}
func (e InvalidValueTypeInitExprError) Error() string {
return fmt.Sprintf("wasm: Wanted initializer expression to return %v value, got %v", e.Wanted, e.Got)
}
type InvalidLinearMemoryIndexError uint32
func (e InvalidLinearMemoryIndexError) Error() string {
return fmt.Sprintf("wasm: Invalid linear memory index: %d", uint32(e))
}
// Functions for populating and looking up entries in a module's index space.
// More info: http://webassembly.org/docs/modules/#function-index-space
func (m *Module) populateFunctions() error {
if m.Types == nil || m.Function == nil {
return nil
}
for codeIndex, typeIndex := range m.Function.Types {
if int(typeIndex) >= len(m.Types.Entries) {
return InvalidFunctionIndexError(typeIndex)
}
fn := Function{
Sig: &m.Types.Entries[typeIndex],
Body: &m.Code.Bodies[codeIndex],
}
m.FunctionIndexSpace = append(m.FunctionIndexSpace, fn)
}
funcs := make([]uint32, 0, len(m.Function.Types)+len(m.imports.Funcs))
funcs = append(funcs, m.imports.Funcs...)
funcs = append(funcs, m.Function.Types...)
m.Function.Types = funcs
return nil
}
// GetFunction returns a *Function, based on the function's index in
// the function index space. Returns nil when the index is invalid
func (m *Module) GetFunction(i int) *Function {
if i >= len(m.FunctionIndexSpace) || i < 0 {
return nil
}
return &m.FunctionIndexSpace[i]
}
func (m *Module) populateGlobals() error {
if m.Global == nil {
return nil
}
m.GlobalIndexSpace = append(m.GlobalIndexSpace, m.Global.Globals...)
logger.Printf("There are %d entries in the global index spaces.", len(m.GlobalIndexSpace))
return nil
}
// GetGlobal returns a *GlobalEntry, based on the global index space.
// Returns nil when the index is invalid
func (m *Module) GetGlobal(i int) *GlobalEntry {
if i >= len(m.GlobalIndexSpace) || i < 0 {
return nil
}
return &m.GlobalIndexSpace[i]
}
func (m *Module) populateTables() error {
if m.Table == nil || len(m.Table.Entries) == 0 || m.Elements == nil || len(m.Elements.Entries) == 0 {
return nil
}
for _, elem := range m.Elements.Entries {
// the MVP dictates that index should always be zero, we shuold
// probably check this
if int(elem.Index) >= len(m.TableIndexSpace) {
return InvalidTableIndexError(elem.Index)
}
val, err := m.ExecInitExpr(elem.Offset)
if err != nil {
return err
}
offset, ok := val.(int32)
if !ok {
return InvalidValueTypeInitExprError{reflect.Int32, reflect.TypeOf(val).Kind()}
}
table := m.TableIndexSpace[int(elem.Index)]
if int(offset)+len(elem.Elems) > len(table) {
data := make([]uint32, int(offset)+len(elem.Elems))
copy(data[offset:], elem.Elems)
copy(data, table)
m.TableIndexSpace[int(elem.Index)] = data
} else {
copy(table[int(offset):], elem.Elems)
m.TableIndexSpace[int(elem.Index)] = table
}
}
logger.Printf("There are %d entries in the table index space.", len(m.TableIndexSpace))
return nil
}
// GetTableElement returns an element from the tableindex space indexed
// by the integer index. It returns an error if index is invalid.
func (m *Module) GetTableElement(index int) (uint32, error) {
if index >= len(m.TableIndexSpace[0]) {
return 0, InvalidTableIndexError(index)
}
return m.TableIndexSpace[0][index], nil
}
func (m *Module) populateLinearMemory() error {
if m.Data == nil || len(m.Data.Entries) == 0 {
return nil
}
// each module can only have a single linear memory in the MVP
for _, entry := range m.Data.Entries {
if entry.Index != 0 {
return InvalidLinearMemoryIndexError(entry.Index)
}
val, err := m.ExecInitExpr(entry.Offset)
if err != nil {
return err
}
offset, ok := val.(int32)
if !ok {
return InvalidValueTypeInitExprError{reflect.Int32, reflect.TypeOf(val).Kind()}
}
memory := m.LinearMemoryIndexSpace[int(entry.Index)]
if int(offset)+len(entry.Data) > len(memory) {
data := make([]byte, int(offset)+len(entry.Data))
copy(data[offset:], entry.Data)
copy(data, memory)
m.LinearMemoryIndexSpace[int(entry.Index)] = data
} else {
copy(memory[int(offset):], entry.Data)
m.LinearMemoryIndexSpace[int(entry.Index)] = memory
}
}
return nil
}
func (m *Module) GetLinearMemoryData(index int) (byte, error) {
if index >= len(m.LinearMemoryIndexSpace[0]) {
return 0, InvalidLinearMemoryIndexError(uint32(index))
}
return m.LinearMemoryIndexSpace[0][index], nil
}

View file

@ -1,172 +0,0 @@
// Copyright 2017 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 wasm
import (
"bytes"
"errors"
"fmt"
"io"
"math"
"github.com/go-interpreter/wagon/wasm/leb128"
)
const (
i32Const byte = 0x41
i64Const byte = 0x42
f32Const byte = 0x43
f64Const byte = 0x44
getGlobal byte = 0x23
end byte = 0x0b
)
var ErrEmptyInitExpr = errors.New("wasm: Initializer expression produces no value")
type InvalidInitExprOpError byte
func (e InvalidInitExprOpError) Error() string {
return fmt.Sprintf("wasm: Invalid opcode in initializer expression: %#x", byte(e))
}
type InvalidGlobalIndexError uint32
func (e InvalidGlobalIndexError) Error() string {
return fmt.Sprintf("wasm: Invalid index to global index space: %#x", uint32(e))
}
func readInitExpr(r io.Reader) ([]byte, error) {
b := make([]byte, 1)
buf := new(bytes.Buffer)
r = io.TeeReader(r, buf)
outer:
for {
_, err := io.ReadFull(r, b)
if err != nil {
return nil, err
}
switch b[0] {
case i32Const:
_, err := leb128.ReadVarint32(r)
if err != nil {
return nil, err
}
case i64Const:
_, err := leb128.ReadVarint64(r)
if err != nil {
return nil, err
}
case f32Const:
if _, err := readU32(r); err != nil {
return nil, err
}
case f64Const:
if _, err := readU64(r); err != nil {
return nil, err
}
case getGlobal:
_, err := leb128.ReadVarUint32(r)
if err != nil {
return nil, err
}
case end:
break outer
default:
return nil, InvalidInitExprOpError(b[0])
}
}
if buf.Len() == 0 {
return nil, ErrEmptyInitExpr
}
return buf.Bytes(), nil
}
// ExecInitExpr executes an initializer expression and returns an interface{} value
// which can either be int32, int64, float32 or float64.
// It returns an error if the expression is invalid, and nil when the expression
// yields no value.
func (m *Module) ExecInitExpr(expr []byte) (interface{}, error) {
var stack []uint64
var lastVal ValueType
r := bytes.NewReader(expr)
if r.Len() == 0 {
return nil, ErrEmptyInitExpr
}
for {
b, err := r.ReadByte()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
switch b {
case i32Const:
i, err := leb128.ReadVarint32(r)
if err != nil {
return nil, err
}
stack = append(stack, uint64(i))
lastVal = ValueTypeI32
case i64Const:
i, err := leb128.ReadVarint64(r)
if err != nil {
return nil, err
}
stack = append(stack, uint64(i))
lastVal = ValueTypeI64
case f32Const:
i, err := readU32(r)
if err != nil {
return nil, err
}
stack = append(stack, uint64(i))
lastVal = ValueTypeF32
case f64Const:
i, err := readU64(r)
if err != nil {
return nil, err
}
stack = append(stack, i)
lastVal = ValueTypeF64
case getGlobal:
index, err := leb128.ReadVarUint32(r)
if err != nil {
return nil, err
}
globalVar := m.GetGlobal(int(index))
if globalVar == nil {
return nil, InvalidGlobalIndexError(index)
}
lastVal = globalVar.Type.Type
case end:
break
default:
return nil, InvalidInitExprOpError(b)
}
}
if len(stack) == 0 {
return nil, nil
}
v := stack[len(stack)-1]
switch lastVal {
case ValueTypeI32:
return int32(v), nil
case ValueTypeI64:
return int64(v), nil
case ValueTypeF32:
return math.Float32frombits(uint32(v)), nil
case ValueTypeF64:
return math.Float64frombits(uint64(v)), nil
default:
panic(fmt.Sprintf("Invalid value type produced by initializer expression: %d", int8(lastVal)))
}
}

View file

@ -1,30 +0,0 @@
// Copyright 2017 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 readpos
import (
"io"
)
// ReadPos implements io.Reader and stores the current number of bytes read from
// the reader
type ReadPos struct {
R io.Reader
CurPos int64
}
// Read implements the io.Reader interface
func (r *ReadPos) Read(p []byte) (int, error) {
n, err := r.R.Read(p)
r.CurPos += int64(n)
return n, err
}
// ReadByte implements the io.ByteReader interface
func (r *ReadPos) ReadByte() (byte, error) {
p := make([]byte, 1)
_, err := r.R.Read(p)
return p[0], err
}

View file

@ -1,92 +0,0 @@
// Copyright 2017 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 leb128 provides functions for reading integer values encoded in the
// Little Endian Base 128 (LEB128) format: https://en.wikipedia.org/wiki/LEB128
package leb128
import (
"io"
)
// ReadVarUint32Size reads a LEB128 encoded unsigned 32-bit integer from r.
// It returns the integer value, the size of the encoded value (in bytes), and
// the error (if any).
func ReadVarUint32Size(r io.Reader) (res uint32, size uint, err error) {
b := make([]byte, 1)
var shift uint
for {
if _, err = io.ReadFull(r, b); err != nil {
return
}
size++
cur := uint32(b[0])
res |= (cur & 0x7f) << (shift)
if cur&0x80 == 0 {
return res, size, nil
}
shift += 7
}
}
// ReadVarUint32 reads a LEB128 encoded unsigned 32-bit integer from r, and
// returns the integer value, and the error (if any).
func ReadVarUint32(r io.Reader) (uint32, error) {
n, _, err := ReadVarUint32Size(r)
return n, err
}
// ReadVarint32Size reads a LEB128 encoded signed 32-bit integer from r, and
// returns the integer value, the size of the encoded value, and the error
// (if any)
func ReadVarint32Size(r io.Reader) (res int32, size uint, err error) {
res64, size, err := ReadVarint64Size(r)
res = int32(res64)
return
}
// ReadVarint32 reads a LEB128 encoded signed 32-bit integer from r, and
// returns the integer value, and the error (if any).
func ReadVarint32(r io.Reader) (int32, error) {
n, _, err := ReadVarint32Size(r)
return n, err
}
// ReadVarint64Size reads a LEB128 encoded signed 64-bit integer from r, and
// returns the integer value, the size of the encoded value, and the error
// (if any)
func ReadVarint64Size(r io.Reader) (res int64, size uint, err error) {
var shift uint
var sign int64 = -1
b := make([]byte, 1)
for {
if _, err = io.ReadFull(r, b); err != nil {
return
}
size++
cur := int64(b[0])
res |= (cur & 0x7f) << shift
shift += 7
sign <<= 7
if cur&0x80 == 0 {
break
}
}
if ((sign >> 1) & res) != 0 {
res |= sign
}
return res, size, nil
}
// ReadVarint64 reads a LEB128 encoded signed 64-bit integer from r, and
// returns the integer value, and the error (if any).
func ReadVarint64(r io.Reader) (int64, error) {
n, _, err := ReadVarint64Size(r)
return n, err
}

View file

@ -1,60 +0,0 @@
// 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 leb128
import "io"
// Copied from cmd/internal/dwarf/dwarf.go
// AppendUleb128 appends v to b using unsigned LEB128 encoding.
func AppendUleb128(b []byte, v uint64) []byte {
for {
c := uint8(v & 0x7f)
v >>= 7
if v != 0 {
c |= 0x80
}
b = append(b, c)
if c&0x80 == 0 {
break
}
}
return b
}
// AppendSleb128 appends v to b using signed LEB128 encoding.
func AppendSleb128(b []byte, v int64) []byte {
for {
c := uint8(v & 0x7f)
s := uint8(v & 0x40)
v >>= 7
if (v != -1 || s == 0) && (v != 0 || s != 0) {
c |= 0x80
}
b = append(b, c)
if c&0x80 == 0 {
break
}
}
return b
}
// WriteVarUint32 writes a LEB128 encoded unsigned 32-bit integer to w.
// It returns the integer value, the size of the encoded value (in bytes), and
// the error (if any).
func WriteVarUint32(w io.Writer, cur uint32) (int, error) {
var buf []byte
buf = AppendUleb128(buf, uint64(cur))
return w.Write(buf)
}
// WriteVarint64 writes a LEB128 encoded signed 64-bit integer to w, and
// returns the integer value, the size of the encoded value, and the error
// (if any)
func WriteVarint64(w io.Writer, cur int64) (int, error) {
var buf []byte
buf = AppendSleb128(buf, cur)
return w.Write(buf)
}

View file

@ -1,25 +0,0 @@
// Copyright 2017 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 wasm
import (
"io/ioutil"
"log"
"os"
)
var logger *log.Logger
func init() {
SetDebugMode(false)
}
func SetDebugMode(dbg bool) {
w := ioutil.Discard
if dbg {
w = os.Stderr
}
logger = log.New(w, "", log.Lshortfile)
}

View file

@ -1,163 +0,0 @@
// Copyright 2017 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 wasm
import (
"errors"
"io"
"reflect"
"github.com/go-interpreter/wagon/wasm/internal/readpos"
)
var ErrInvalidMagic = errors.New("wasm: Invalid magic number")
const (
Magic uint32 = 0x6d736100
Version uint32 = 0x1
)
// Function represents an entry in the function index space of a module.
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:
// http://webassembly.org/docs/modules/
type Module struct {
Version uint32
Sections []Section
Types *SectionTypes
Import *SectionImports
Function *SectionFunctions
Table *SectionTables
Memory *SectionMemories
Global *SectionGlobals
Export *SectionExports
Start *SectionStartFunction
Elements *SectionElements
Code *SectionCode
Data *SectionData
Customs []*SectionCustom
// 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
LinearMemoryIndexSpace [][]byte
imports struct {
Funcs []uint32
Globals int
Tables int
Memories int
}
}
// Custom returns a custom section with a specific name, if it exists.
func (m *Module) Custom(name string) *SectionCustom {
for _, s := range m.Customs {
if s.Name == name {
return s
}
}
return nil
}
// 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)
// DecodeModule is the same as ReadModule, but it only decodes the module without
// initializing the index space or resolving imports.
func DecodeModule(r io.Reader) (*Module, error) {
reader := &readpos.ReadPos{
R: r,
CurPos: 0,
}
m := &Module{}
magic, err := readU32(reader)
if err != nil {
return nil, err
}
if magic != Magic {
return nil, ErrInvalidMagic
}
if m.Version, err = readU32(reader); err != nil {
return nil, err
}
for {
done, err := m.readSection(reader)
if err != nil {
return nil, err
} else if done {
return m, nil
}
}
}
// ReadModule reads a module from the reader r. resolvePath must take a string
// and a return a reader to the module pointed to by the string.
func ReadModule(r io.Reader, resolvePath ResolveFunc) (*Module, error) {
m, err := DecodeModule(r)
if err != nil {
return nil, err
}
m.LinearMemoryIndexSpace = make([][]byte, 1)
if m.Table != nil {
m.TableIndexSpace = make([][]uint32, int(len(m.Table.Entries)))
}
if m.Import != nil && resolvePath != nil {
err := m.resolveImports(resolvePath)
if err != nil {
return nil, err
}
}
for _, fn := range []func() error{
m.populateGlobals,
m.populateFunctions,
m.populateTables,
m.populateLinearMemory,
} {
if err := fn(); err != nil {
return nil, err
}
}
logger.Printf("There are %d entries in the function index space.", len(m.FunctionIndexSpace))
return m, nil
}

View file

@ -1,10 +0,0 @@
// Copyright 2017 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 operators
var (
Call = newPolymorphicOp(0x10, "call")
CallIndirect = newPolymorphicOp(0x11, "call_indirect")
)

View file

@ -1,46 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
I32Eqz = newOp(0x45, "i32.eqz", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Eq = newOp(0x46, "i32.eq", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Ne = newOp(0x47, "i32.ne", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32LtS = newOp(0x48, "i32.lt_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32LtU = newOp(0x49, "i32.lt_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32GtS = newOp(0x4a, "i32.gt_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32GtU = newOp(0x4b, "i32.gt_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32LeS = newOp(0x4c, "i32.le_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32LeU = newOp(0x4d, "i32.le_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32GeS = newOp(0x4e, "i32.ge_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32GeU = newOp(0x4f, "i32.ge_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I64Eqz = newOp(0x50, "i64.eqz", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64Eq = newOp(0x51, "i64.eq", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64Ne = newOp(0x52, "i64.ne", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64LtS = newOp(0x53, "i64.lt_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64LtU = newOp(0x54, "i64.lt_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64GtS = newOp(0x55, "i64.gt_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64GtU = newOp(0x56, "i64.gt_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64LeS = newOp(0x57, "i64.le_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64LeU = newOp(0x58, "i64.le_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64GeS = newOp(0x59, "i64.ge_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
I64GeU = newOp(0x5a, "i64.ge_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI32)
F32Eq = newOp(0x5b, "f32.eq", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F32Ne = newOp(0x5c, "f32.ne", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F32Lt = newOp(0x5d, "f32.lt", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F32Gt = newOp(0x5e, "f32.gt", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F32Le = newOp(0x5f, "f32.le", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F32Ge = newOp(0x60, "f32.ge", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeI32)
F64Eq = newOp(0x61, "f64.eq", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
F64Ne = newOp(0x62, "f64.ne", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
F64Lt = newOp(0x63, "f64.lt", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
F64Gt = newOp(0x64, "f64.gt", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
F64Le = newOp(0x65, "f64.le", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
F64Ge = newOp(0x66, "f64.ge", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeI32)
)

View file

@ -1,16 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
I32Const = newOp(0x41, "i32.const", nil, wasm.ValueTypeI32)
I64Const = newOp(0x42, "i64.const", nil, wasm.ValueTypeI64)
F32Const = newOp(0x43, "f32.const", nil, wasm.ValueTypeF32)
F64Const = newOp(0x44, "f64.const", nil, wasm.ValueTypeF64)
)

View file

@ -1,23 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
Unreachable = newOp(0x00, "unreachable", nil, noReturn)
Nop = newOp(0x01, "nop", nil, noReturn)
Block = newOp(0x02, "block", nil, noReturn)
Loop = newOp(0x03, "loop", nil, noReturn)
If = newOp(0x04, "if", []wasm.ValueType{wasm.ValueTypeI32}, noReturn)
Else = newOp(0x05, "else", nil, noReturn)
End = newOp(0x0b, "end", nil, noReturn)
Br = newPolymorphicOp(0x0c, "br")
BrIf = newOp(0x0d, "br_if", []wasm.ValueType{wasm.ValueTypeI32}, noReturn)
BrTable = newPolymorphicOp(0x0e, "br_table")
Return = newPolymorphicOp(0x0f, "return")
)

View file

@ -1,64 +0,0 @@
// Copyright 2017 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 operators
import (
"regexp"
"github.com/go-interpreter/wagon/wasm"
)
var reCvrtOp = regexp.MustCompile(`(.+)\.(?:[a-z]|\_)+\/(.+)`)
func valType(s string) wasm.ValueType {
switch s {
case "i32":
return wasm.ValueTypeI32
case "i64":
return wasm.ValueTypeI64
case "f32":
return wasm.ValueTypeF32
case "f64":
return wasm.ValueTypeF64
default:
panic("Invalid value type string: " + s)
}
}
func newConversionOp(code byte, name string) byte {
matches := reCvrtOp.FindStringSubmatch(name)
if len(matches) == 0 {
panic(name + " is not a conversion operator")
}
returns := valType(matches[1])
param := valType(matches[2])
return newOp(code, name, []wasm.ValueType{param}, returns)
}
var (
I32WrapI64 = newConversionOp(0xa7, "i32.wrap/i64")
I32TruncSF32 = newConversionOp(0xa8, "i32.trunc_s/f32")
I32TruncUF32 = newConversionOp(0xa9, "i32.trunc_u/f32")
I32TruncSF64 = newConversionOp(0xaa, "i32.trunc_s/f64")
I32TruncUF64 = newConversionOp(0xab, "i32.trunc_u/f64")
I64ExtendSI32 = newConversionOp(0xac, "i64.extend_s/i32")
I64ExtendUI32 = newConversionOp(0xad, "i64.extend_u/i32")
I64TruncSF32 = newConversionOp(0xae, "i64.trunc_s/f32")
I64TruncUF32 = newConversionOp(0xaf, "i64.trunc_u/f32")
I64TruncSF64 = newConversionOp(0xb0, "i64.trunc_s/f64")
I64TruncUF64 = newConversionOp(0xb1, "i64.trunc_u/f64")
F32ConvertSI32 = newConversionOp(0xb2, "f32.convert_s/i32")
F32ConvertUI32 = newConversionOp(0xb3, "f32.convert_u/i32")
F32ConvertSI64 = newConversionOp(0xb4, "f32.convert_s/i64")
F32ConvertUI64 = newConversionOp(0xb5, "f32.convert_u/i64")
F32DemoteF64 = newConversionOp(0xb6, "f32.demote/f64")
F64ConvertSI32 = newConversionOp(0xb7, "f64.convert_s/i32")
F64ConvertUI32 = newConversionOp(0xb8, "f64.convert_u/i32")
F64ConvertSI64 = newConversionOp(0xb9, "f64.convert_s/i64")
F64ConvertUI64 = newConversionOp(0xba, "f64.convert_u/i64")
F64PromoteF32 = newConversionOp(0xbb, "f64.promote/f32")
)

View file

@ -1,39 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
I32Load = newOp(0x28, "i32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I64Load = newOp(0x29, "i64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
F32Load = newOp(0x2a, "f32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF32)
F64Load = newOp(0x2b, "f64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF64)
I32Load8s = newOp(0x2c, "i32.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Load8u = newOp(0x2d, "i32.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Load16s = newOp(0x2e, "i32.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Load16u = newOp(0x2f, "i32.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I64Load8s = newOp(0x30, "i64.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I64Load8u = newOp(0x31, "i64.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I64Load16s = newOp(0x32, "i64.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I64Load16u = newOp(0x33, "i64.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I64Load32s = newOp(0x34, "i64.load32_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I64Load32u = newOp(0x35, "i64.load32_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64)
I32Store = newOp(0x36, "i32.store", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn)
I64Store = newOp(0x37, "i64.store", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn)
F32Store = newOp(0x38, "f32.store", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeI32}, noReturn)
F64Store = newOp(0x39, "f64.store", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeI32}, noReturn)
I32Store8 = newOp(0x3a, "i32.store8", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn)
I32Store16 = newOp(0x3b, "i32.store16", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn)
I64Store8 = newOp(0x3c, "i64.store8", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn)
I64Store16 = newOp(0x3d, "i64.store16", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn)
I64Store32 = newOp(0x3e, "i64.store32", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn)
CurrentMemory = newOp(0x3f, "current_memory", nil, wasm.ValueTypeI32)
GrowMemory = newOp(0x40, "grow_memory", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
)

View file

@ -1,76 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
I32Clz = newOp(0x67, "i32.clz", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Ctz = newOp(0x68, "i32.ctz", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Popcnt = newOp(0x69, "i32.popcnt", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Add = newOp(0x6a, "i32.add", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Sub = newOp(0x6b, "i32.sub", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Mul = newOp(0x6c, "i32.mul", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32DivS = newOp(0x6d, "i32.div_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32DivU = newOp(0x6e, "i32.div_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32RemS = newOp(0x6f, "i32.rem_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32RemU = newOp(0x70, "i32.rem_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32And = newOp(0x71, "i32.and", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Or = newOp(0x72, "i32.or", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Xor = newOp(0x73, "i32.xor", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Shl = newOp(0x74, "i32.shl", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32ShrS = newOp(0x75, "i32.shr_s", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32ShrU = newOp(0x76, "i32.shr_u", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Rotl = newOp(0x77, "i32.rotl", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I32Rotr = newOp(0x78, "i32.rotr", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, wasm.ValueTypeI32)
I64Clz = newOp(0x79, "i64.clz", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Ctz = newOp(0x7a, "i64.ctz", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Popcnt = newOp(0x7b, "i64.popcnt", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Add = newOp(0x7c, "i64.add", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Sub = newOp(0x7d, "i64.sub", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Mul = newOp(0x7e, "i64.mul", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64DivS = newOp(0x7f, "i64.div_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64DivU = newOp(0x80, "i64.div_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64RemS = newOp(0x81, "i64.div_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64RemU = newOp(0x82, "i64.rem_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64And = newOp(0x83, "i64.and", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Or = newOp(0x84, "i64.or", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Xor = newOp(0x85, "i64.xor", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Shl = newOp(0x86, "i64.shl", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64ShrS = newOp(0x87, "i64.shr_s", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64ShrU = newOp(0x88, "i64.shr_u", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Rotl = newOp(0x89, "i64.rotl", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
I64Rotr = newOp(0x8a, "i64.rotr", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI64}, wasm.ValueTypeI64)
F32Abs = newOp(0x8b, "f32.abs", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Neg = newOp(0x8c, "f32.neg", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Ceil = newOp(0x8d, "f32.ceil", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Floor = newOp(0x8e, "f32.floor", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Trunc = newOp(0x8f, "f32.trunc", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Nearest = newOp(0x90, "f32.nearest", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Sqrt = newOp(0x91, "f32.sqrt", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Add = newOp(0x92, "f32.add", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Sub = newOp(0x93, "f32.sub", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Mul = newOp(0x94, "f32.mul", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Div = newOp(0x95, "f32.div", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Min = newOp(0x96, "f32.min", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Max = newOp(0x97, "f32.max", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F32Copysign = newOp(0x98, "f32.copysign", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeF32}, wasm.ValueTypeF32)
F64Abs = newOp(0x99, "f64.abs", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Neg = newOp(0x9a, "f64.neg", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Ceil = newOp(0x9b, "f64.ceil", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Floor = newOp(0x9c, "f64.floor", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Trunc = newOp(0x9d, "f64.trunc", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Nearest = newOp(0x9e, "f64.nearest", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Sqrt = newOp(0x9f, "f64.sqrt", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Add = newOp(0xa0, "f64.add", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Sub = newOp(0xa1, "f64.sub", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Mul = newOp(0xa2, "f64.mul", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Div = newOp(0xa3, "f64.div", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Min = newOp(0xa4, "f64.min", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Max = newOp(0xa5, "f64.max", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
F64Copysign = newOp(0xa6, "f64.copysign", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeF64}, wasm.ValueTypeF64)
)

View file

@ -1,86 +0,0 @@
// Copyright 2017 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 operators provides all operators used by WebAssembly bytecode,
// together with their parameter and return type(s).
package operators
import (
"fmt"
"github.com/go-interpreter/wagon/wasm"
)
var (
ops [256]Op // an array of Op values mapped by wasm opcodes, used by New().
noReturn = wasm.ValueType(wasm.BlockTypeEmpty)
)
// Op describes a WASM operator.
type Op struct {
Code byte // The single-byte opcode
Name string // The name of the operator
// Whether this operator is polymorphic.
// A polymorphic operator has a variable arity. call, call_indirect, and
// drop are examples of polymorphic operators.
Polymorphic bool
Args []wasm.ValueType // an array of value types used by the operator as arguments, is nil for polymorphic operators
Returns wasm.ValueType // the value returned (pushed) by the operator, is 0 for polymorphic operators
}
func (o Op) IsValid() bool {
return o.Name != ""
}
func newOp(code byte, name string, args []wasm.ValueType, returns wasm.ValueType) byte {
if ops[code].IsValid() {
panic(fmt.Errorf("Opcode %#x is already assigned to %s", code, ops[code].Name))
}
op := Op{
Code: code,
Name: name,
Polymorphic: false,
Args: args,
Returns: returns,
}
ops[code] = op
return code
}
func newPolymorphicOp(code byte, name string) byte {
if ops[code].IsValid() {
panic(fmt.Errorf("Opcode %#x is already assigned to %s", code, ops[code].Name))
}
op := Op{
Code: code,
Name: name,
Polymorphic: true,
}
ops[code] = op
return code
}
type InvalidOpcodeError byte
func (e InvalidOpcodeError) Error() string {
return fmt.Sprintf("Invalid opcode: %#x", byte(e))
}
// New returns the Op object for a valid given opcode.
// If code is invalid, an ErrInvalidOpcode is returned.
func New(code byte) (Op, error) {
var op Op
if int(code) >= len(ops) {
return op, InvalidOpcodeError(code)
}
op = ops[code]
if !op.IsValid() {
return op, InvalidOpcodeError(code)
}
return op, nil
}

View file

@ -1,10 +0,0 @@
// Copyright 2017 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 operators
var (
Drop = newPolymorphicOp(0x1a, "drop")
Select = newPolymorphicOp(0x1b, "select")
)

View file

@ -1,16 +0,0 @@
// Copyright 2017 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 operators
import (
"github.com/go-interpreter/wagon/wasm"
)
var (
I32ReinterpretF32 = newOp(0xbc, "i32.reinterpret/f32", []wasm.ValueType{wasm.ValueTypeF32}, wasm.ValueTypeI32)
I64ReinterpretF64 = newOp(0xbd, "i64.reinterpret/f64", []wasm.ValueType{wasm.ValueTypeF64}, wasm.ValueTypeI64)
F32ReinterpretI32 = newOp(0xbe, "f32.reinterpret/i32", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF32)
F64ReinterpretI64 = newOp(0xbf, "f64.reinterpret/i64", []wasm.ValueType{wasm.ValueTypeI64}, wasm.ValueTypeF64)
)

View file

@ -1,13 +0,0 @@
// Copyright 2017 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 operators
var (
GetLocal = newPolymorphicOp(0x20, "get_local")
SetLocal = newPolymorphicOp(0x21, "set_local")
TeeLocal = newPolymorphicOp(0x22, "tee_local")
GetGlobal = newPolymorphicOp(0x23, "get_global")
SetGlobal = newPolymorphicOp(0x24, "set_global")
)

View file

@ -1,64 +0,0 @@
// Copyright 2017 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 wasm
import (
"encoding/binary"
"io"
"github.com/go-interpreter/wagon/wasm/leb128"
)
func readBytes(r io.Reader, n int) ([]byte, error) {
bytes := make([]byte, n)
_, err := io.ReadFull(r, bytes)
if err != nil {
return bytes, err
}
return bytes, nil
}
func readBytesUint(r io.Reader) ([]byte, error) {
n, err := leb128.ReadVarUint32(r)
if err != nil {
return nil, err
}
return readBytes(r, int(n))
}
func readString(r io.Reader, n int) (string, error) {
bytes, err := readBytes(r, n)
if err != nil {
return "", err
}
return string(bytes), nil
}
func readStringUint(r io.Reader) (string, error) {
n, err := leb128.ReadVarUint32(r)
if err != nil {
return "", err
}
return readString(r, int(n))
}
func readU32(r io.Reader) (uint32, error) {
var buf [4]byte
_, err := io.ReadFull(r, buf[:])
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint32(buf[:]), nil
}
func readU64(r io.Reader) (uint64, error) {
var buf [8]byte
_, err := io.ReadFull(r, buf[:])
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint64(buf[:]), nil
}

File diff suppressed because it is too large Load diff

View file

@ -1,352 +0,0 @@
// Copyright 2017 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 wasm
import (
"fmt"
"io"
"github.com/go-interpreter/wagon/wasm/leb128"
)
type Marshaler interface {
// MarshalWASM encodes an object into w using WASM binary encoding.
MarshalWASM(w io.Writer) error
}
type Unmarshaler interface {
// UnmarshalWASM decodes an object from r using WASM binary encoding.
UnmarshalWASM(r io.Reader) error
}
// ValueType represents the type of a valid value in Wasm
type ValueType int8
const (
ValueTypeI32 ValueType = -0x01
ValueTypeI64 ValueType = -0x02
ValueTypeF32 ValueType = -0x03
ValueTypeF64 ValueType = -0x04
)
var valueTypeStrMap = map[ValueType]string{
ValueTypeI32: "i32",
ValueTypeI64: "i64",
ValueTypeF32: "f32",
ValueTypeF64: "f64",
}
func (t ValueType) String() string {
str, ok := valueTypeStrMap[t]
if !ok {
str = fmt.Sprintf("<unknown value_type %d>", int8(t))
}
return str
}
// TypeFunc represents the value type of a function
const TypeFunc int = -0x20
func (t *ValueType) UnmarshalWASM(r io.Reader) error {
v, err := leb128.ReadVarint32(r)
if err != nil {
return err
}
*t = ValueType(v)
return nil
}
func (t ValueType) MarshalWASM(w io.Writer) error {
_, err := leb128.WriteVarint64(w, int64(t))
return err
}
// BlockType represents the signature of a structured block
type BlockType ValueType // varint7
const BlockTypeEmpty BlockType = -0x40
func (b BlockType) String() string {
if b == BlockTypeEmpty {
return "<empty block>"
}
return ValueType(b).String()
}
// ElemType describes the type of a table's elements
type ElemType int // varint7
// ElemTypeAnyFunc descibres an any_func value
const ElemTypeAnyFunc ElemType = -0x10
func (t *ElemType) UnmarshalWASM(r io.Reader) error {
b, err := leb128.ReadVarint32(r)
if err != nil {
return err
}
*t = ElemType(b)
return nil
}
func (t ElemType) MarshalWASM(w io.Writer) error {
_, err := leb128.WriteVarint64(w, int64(t))
return err
}
func (t ElemType) String() string {
if t == ElemTypeAnyFunc {
return "anyfunc"
}
return "<unknown elem_type>"
}
// FunctionSig describes the signature of a declared function in a WASM module
type FunctionSig struct {
// value for the 'func` type constructor
Form int8
// The parameter types of the function
ParamTypes []ValueType
ReturnTypes []ValueType
}
func (f FunctionSig) String() string {
return fmt.Sprintf("<func %v -> %v>", f.ParamTypes, f.ReturnTypes)
}
type InvalidTypeConstructorError struct {
Wanted int
Got int
}
func (e InvalidTypeConstructorError) Error() string {
return fmt.Sprintf("wasm: invalid type constructor: wanted %d, got %d", e.Wanted, e.Got)
}
func (f *FunctionSig) UnmarshalWASM(r io.Reader) error {
form, err := leb128.ReadVarint32(r)
if err != nil {
return err
}
f.Form = int8(form)
paramCount, err := leb128.ReadVarUint32(r)
if err != nil {
return err
}
f.ParamTypes = make([]ValueType, paramCount)
for i := range f.ParamTypes {
err = f.ParamTypes[i].UnmarshalWASM(r)
if err != nil {
return err
}
}
returnCount, err := leb128.ReadVarUint32(r)
if err != nil {
return err
}
f.ReturnTypes = make([]ValueType, returnCount)
for i := range f.ReturnTypes {
err = f.ReturnTypes[i].UnmarshalWASM(r)
if err != nil {
return err
}
}
return nil
}
func (f *FunctionSig) MarshalWASM(w io.Writer) error {
_, err := leb128.WriteVarint64(w, int64(f.Form))
if err != nil {
return err
}
_, err = leb128.WriteVarUint32(w, uint32(len(f.ParamTypes)))
if err != nil {
return err
}
for _, p := range f.ParamTypes {
err = p.MarshalWASM(w)
if err != nil {
return err
}
}
_, err = leb128.WriteVarUint32(w, uint32(len(f.ReturnTypes)))
if err != nil {
return err
}
for _, p := range f.ReturnTypes {
err = p.MarshalWASM(w)
if err != nil {
return err
}
}
return nil
}
// GlobalVar describes the type and mutability of a declared global variable
type GlobalVar struct {
Type ValueType // Type of the value stored by the variable
Mutable bool // Whether the value of the variable can be changed by the set_global operator
}
func (g *GlobalVar) UnmarshalWASM(r io.Reader) error {
*g = GlobalVar{}
err := g.Type.UnmarshalWASM(r)
if err != nil {
return err
}
m, err := leb128.ReadVarUint32(r)
if err != nil {
return err
}
g.Mutable = m == 1
return nil
}
func (g *GlobalVar) MarshalWASM(w io.Writer) error {
if err := g.Type.MarshalWASM(w); err != nil {
return err
}
var m uint32
if g.Mutable {
m = 1
}
if _, err := leb128.WriteVarUint32(w, m); err != nil {
return err
}
return nil
}
// Table describes a table in a Wasm module.
type Table struct {
// The type of elements
ElementType ElemType
Limits ResizableLimits
}
func (t *Table) UnmarshalWASM(r io.Reader) error {
err := t.ElementType.UnmarshalWASM(r)
if err != nil {
return err
}
err = t.Limits.UnmarshalWASM(r)
if err != nil {
return err
}
return err
}
func (t *Table) MarshalWASM(w io.Writer) error {
if err := t.ElementType.MarshalWASM(w); err != nil {
return err
}
if err := t.Limits.MarshalWASM(w); err != nil {
return err
}
return nil
}
type Memory struct {
Limits ResizableLimits
}
func (m *Memory) UnmarshalWASM(r io.Reader) error {
return m.Limits.UnmarshalWASM(r)
}
func (m *Memory) MarshalWASM(w io.Writer) error {
return m.Limits.MarshalWASM(w)
}
// External describes the kind of the entry being imported or exported.
type External uint8
const (
ExternalFunction External = 0
ExternalTable External = 1
ExternalMemory External = 2
ExternalGlobal External = 3
)
func (e External) String() string {
switch e {
case ExternalFunction:
return "function"
case ExternalTable:
return "table"
case ExternalMemory:
return "memory"
case ExternalGlobal:
return "global"
default:
return "<unknown external_kind>"
}
}
func (e *External) UnmarshalWASM(r io.Reader) error {
bytes, err := readBytes(r, 1)
if err != nil {
return err
}
*e = External(bytes[0])
return nil
}
func (e External) MarshalWASM(w io.Writer) error {
_, err := w.Write([]byte{byte(e)})
return err
}
// ResizableLimits describe the limit of a table or linear memory.
type ResizableLimits struct {
Flags uint32 // 1 if the Maximum field is valid
Initial uint32 // initial length (in units of table elements or wasm pages)
Maximum uint32 // If flags is 1, it describes the maximum size of the table or memory
}
func (lim *ResizableLimits) UnmarshalWASM(r io.Reader) error {
*lim = ResizableLimits{}
f, err := leb128.ReadVarUint32(r)
if err != nil {
return err
}
lim.Flags = f
lim.Initial, err = leb128.ReadVarUint32(r)
if err != nil {
return err
}
if lim.Flags&0x1 != 0 {
m, err := leb128.ReadVarUint32(r)
if err != nil {
return err
}
lim.Maximum = m
}
return nil
}
func (lim *ResizableLimits) MarshalWASM(w io.Writer) error {
if _, err := leb128.WriteVarUint32(w, uint32(lim.Flags)); err != nil {
return err
}
if _, err := leb128.WriteVarUint32(w, uint32(lim.Initial)); err != nil {
return err
}
if lim.Flags&0x1 != 0 {
if _, err := leb128.WriteVarUint32(w, uint32(lim.Maximum)); err != nil {
return err
}
}
return nil
}

6
vendor/vendor.json vendored
View file

@ -128,12 +128,6 @@
"revision": "991cd3d3809135dc24daf6188dc6edcaf3d7d2d9", "revision": "991cd3d3809135dc24daf6188dc6edcaf3d7d2d9",
"revisionTime": "2017-01-17T22:23:42Z" "revisionTime": "2017-01-17T22:23:42Z"
}, },
{
"checksumSHA1": "O8hj2TjWxwLgPDVJeEwvuQFm664=",
"path": "github.com/go-interpreter/wagon",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{ {
"checksumSHA1": "zMJhaMOsF8AoZD1nF/geiD0EEiU=", "checksumSHA1": "zMJhaMOsF8AoZD1nF/geiD0EEiU=",
"path": "github.com/go-interpreter/wagon/disasm", "path": "github.com/go-interpreter/wagon/disasm",