core/vm: Add wagon/wasm as an interpreter

* Implement the ewasm module and the EEI.
 * Add an ewasm standalone binary to test standalone files
 * use reflection to declare EEI module exports
 * check the module name when importing EEI functions
 * make eeiFuncs a function to avoid an init loop issue in call
 * Add a reference to the ewasm tests
 * wagon implementation of the Interpreter interface
 * update to wagon's PR #59 and uses the Process structure
 * Adapt to endianness change
 * All tests but 1 passing
 * CanRun only checks for the header
 * Add a "debug" module to support "printHex" in contracts

Notes:
  * There are very little checks, e.g. of
     the call depths and so on.
This commit is contained in:
Guillaume Ballet 2018-06-04 15:06:24 +02:00
parent 5d3b7bb023
commit 9927596822
57 changed files with 7176 additions and 63 deletions

2
.gitmodules vendored
View file

@ -1,3 +1,3 @@
[submodule "tests"]
path = tests/testdata
url = https://github.com/ethereum/tests
url = https://github.com/ewasm/tests

125
cmd/ewasm/main.go Normal file
View file

@ -0,0 +1,125 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// ewasm executes eWASM modules.
package main
import (
"errors"
"fmt"
"math/big"
"math/rand"
"os"
"strconv"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state"
coreVM "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"gopkg.in/urfave/cli.v1"
)
var (
gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags)
app = utils.NewApp(gitCommit, "the ewasm command line interface")
)
var runCommand = cli.Command{
Action: runCmd,
Name: "run",
Usage: "run and arbitrary eWASM module",
ArgsUsage: "<module name> <input> <gas>",
Description: `The run command runs an arbitrary eWASM module.`,
}
func runCmd(ctx *cli.Context) error {
args := ctx.Args()
statedb, err := state.New(common.Hash{}, state.NewDatabase(ethdb.NewMemDatabase()))
if err != nil {
utils.Fatalf("Could not create the state database: %v", err)
}
if args.Present() {
filename := args.First()
input := args.Get(1)
gas, err := strconv.ParseUint(args.Get(2), 10, 64)
if err != nil {
return fmt.Errorf("Error parsing gas number: %v", err)
}
if fd, err := os.Open(filename); err == nil {
fi, _ := fd.Stat()
code := make([]byte, fi.Size())
n, err := fd.Read(code)
if n != len(code) || err != nil {
return fmt.Errorf("Read %d bytes out of %d, err: %v", n, len(code), err)
}
randomContractAddress := make([]byte, common.HashLength)
rand.Read(randomContractAddress)
contractAddr := common.BytesToAddress(randomContractAddress)
randomCallerAddress := make([]byte, common.HashLength)
rand.Read(randomContractAddress)
callerAddr := common.BytesToAddress(randomCallerAddress)
contract := coreVM.NewContract(coreVM.AccountRef(callerAddr), coreVM.AccountRef(contractAddr), big.NewInt(100), gas)
contract.Code = code
evm := coreVM.NewEVM(coreVM.Context{}, statedb, &params.ChainConfig{}, coreVM.Config{EnableWagon: true})
// &ewasm.Contract{
// StateDB: statedb,
// Gas: 100,
// Address: &contractAddr,
// CodeAddr: &codeAddr,
// Module: m,
// VM: vm,
// }
output, err := evm.Interpreter().Run(contract, []byte(input))
if err != nil {
return err
}
fmt.Printf("Output: %v\n", output)
return nil
}
return fmt.Errorf("Error opening module file: %v", err)
}
return errors.New("You need to specify a module name")
}
func init() {
app.Commands = []cli.Command{
runCommand,
}
}
func main() {
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

View file

@ -157,12 +157,12 @@ func (c *Contract) UseGas(gas uint64) (ok bool) {
return true
}
// Address returns the contracts address
// Address returns the contract's address
func (c *Contract) Address() common.Address {
return c.self.Address()
}
// Value returns the contracts value (sent to it from it's caller)
// Value returns the contracts value (sent to it from its caller)
func (c *Contract) Value() *big.Int {
return c.value
}

View file

@ -22,3 +22,12 @@ over a set of bytes and executes them according to the set of rules defined
in the Ethereum yellow paper.
*/
package vm
// Size (in bytes) of a u256
const u256Len = 32
// Size (in bytes) of a u128
const u128Len = 16
// Max recursion depth for contracts
const maxCallDepth = 4

View file

@ -150,7 +150,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
// }
// evm.interpreters = append(evm.interpreters, NewEVMVCInterpreter(evm, vmConfig, options))
// } else {
// evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
evm.interpreters = append(evm.interpreters, NewEWASMInterpreter(evm, vmConfig))
// }
panic("No supported ewasm interpreter yet.")
}
@ -158,7 +158,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
// as we always want to have the built-in EVM as the failover option.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
evm.interpreter = evm.interpreters[0]
evm.interpreter = evm.interpreters[len(evm.interpreters)-1]
return evm
}

141
core/vm/ewasm.go Normal file
View file

@ -0,0 +1,141 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// This file lists the EEI functions, so that they can be bound to any
// eWASM-compatible module, as well as the types of these functions
package vm
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"github.com/ethereum/go-ethereum/params"
"github.com/go-interpreter/wagon/wasm"
"github.com/go-interpreter/wagon/exec"
)
type terminationType int
// List of termination reasons
const (
TerminateFinish = iota
TerminateRevert
TerminateSuicide
TerminateInvalid
)
// InterpreterEWASM implements the Interpreter interface for eWASM.
type InterpreterEWASM struct {
vm *exec.VM
evm *EVM
StateDB StateDB
gasTable params.GasTable
contract *Contract
returnData []byte
terminationType terminationType
}
// NewEWASMInterpreter creates a new wagon-based eWASM interpreter. It
// currently takes a *vm.EVM pointer as a proxy to the client's internal
// state; this will be fixed in subsequent updates.
func NewEWASMInterpreter(evm *EVM, cfg Config) Interpreter {
return &InterpreterEWASM{StateDB: evm.StateDB, evm: evm, gasTable: evm.chainConfig.GasTable(evm.BlockNumber)}
}
// Run loops and evaluates the contract's code with the given input data and returns
// the return byte-slice and an error if one occurred.
func (in *InterpreterEWASM) Run(contract *Contract, input []byte, ro bool) ([]byte, error) {
in.contract = contract
in.contract.Input = input
initialGas := contract.Gas
module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(in))
if err != nil {
return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address(), err)
}
// The module should not have any start function
if module.Start != nil {
return nil, fmt.Errorf("A contract should not have a start function: found #%d", module.Start.Index)
}
vm, err := exec.NewVM(module)
if err != nil {
return nil, fmt.Errorf("could not create the vm: %v", err)
}
vm.RecoverPanic = true
in.vm = vm
// Look for the "main" function and execute it after checking it
// has the right kind of signature.
for name, entry := range module.Export.Entries {
if name == "main" && entry.Kind == wasm.ExternalFunction {
// Check input and output types
sig := module.FunctionIndexSpace[entry.Index].Sig
if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 {
_, err = vm.ExecCode(int64(entry.Index))
if in.StateDB.HasSuicided(contract.Address()) {
if initialGas-contract.Gas-params.TxGas < 2*params.SuicideRefundGas {
in.StateDB.AddRefund((initialGas - contract.Gas - params.TxGas) / 2)
} else {
in.StateDB.AddRefund(params.SuicideRefundGas)
}
err = nil
}
if err != nil {
in.terminationType = TerminateInvalid
}
return in.returnData, err
}
// Found a main but it doesn't have the right signature - fail
break
}
}
return nil, errors.New("Could not find a suitable 'main' function in that contract")
}
// CanRun checks the binary for a WASM header and accepts the binary blob
// if it matches.
func (in *InterpreterEWASM) CanRun(file []byte) bool {
// Check the header
if len(file) <= 8 || string(file[:4]) != "\000asm" {
return false
}
// Check the version
ver := binary.LittleEndian.Uint32(file[4:])
if ver != 1 {
return false
}
return true
}

874
core/vm/ewasm_eei.go Normal file
View file

@ -0,0 +1,874 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// This file lists the EEI functions, so that they can be bound to any
// eWASM-compatible module, as well as the types of these functions
package vm
import (
"fmt"
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/go-interpreter/wagon/exec"
"github.com/go-interpreter/wagon/wasm"
)
const (
// EEICallSuccess is the return value in case of a successful contract execution
EEICallSuccess = 0
// ErrEEICallFailure is the return value in case of a contract execution failture
ErrEEICallFailure = 1
// ErrEEICallRevert is the return value in case a contract calls `revert`
ErrEEICallRevert = 2
)
// List of gas costs
const (
GasCostZero = 0
GasCostBase = 2
GasCostVeryLow = 3
GasCostLow = 5
GasCostMid = 8
GasCostHigh = 10
GasCostExtCode = 700
GasCostBalance = 400
GasCostSLoad = 200
GasCostJumpDest = 1
GasCostSSet = 20000
GasCostSReset = 5000
GasRefundSClear = 15000
GasRefundSelfDestruct = 24000
GasCostCreate = 32000
GasCostCall = 700
GasCostCallValue = 9000
GasCostCallStipend = 2300
GasCostLog = 375
GasCostLogData = 8
GasCostLogTopic = 375
GasCostCopy = 3
GasCostBlockHash = 800
)
var eeiTypes = &wasm.SectionTypes{
Entries: []wasm.FunctionSig{
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI64},
ReturnTypes: []wasm.ValueType{},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{},
},
{
ParamTypes: []wasm.ValueType{},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI32},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{},
},
{
ParamTypes: []wasm.ValueType{},
ReturnTypes: []wasm.ValueType{wasm.ValueTypeI64},
},
{
ParamTypes: []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32, wasm.ValueTypeI32},
ReturnTypes: []wasm.ValueType{},
},
},
}
func swapEndian(src []byte) []byte {
ret := make([]byte, len(src))
for i, v := range src {
ret[len(src)-i-1] = v
}
return ret
}
func (in *InterpreterEWASM) gasAccounting(cost uint64) {
if in.contract == nil {
panic("nil contract")
}
if cost > in.contract.Gas {
panic("out of gas")
}
in.contract.Gas -= cost
}
func getDebugFuncs(in *InterpreterEWASM) []wasm.Function {
return []wasm.Function{
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, o, l int32) { printMemHex(p, in, o, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, o int32) { printStorageHex(p, in, o) }),
Body: &wasm.FunctionBody{},
},
}
}
func printMemHex(p *exec.Process, in *InterpreterEWASM, offset, length int32) {
data := readSize(p, offset, int(length))
for _, v := range data {
fmt.Printf("%02x", v)
}
fmt.Println("")
}
func printStorageHex(p *exec.Process, in *InterpreterEWASM, pathOffset int32) {
path := common.BytesToHash(readSize(p, pathOffset, common.HashLength))
val := in.StateDB.GetState(in.contract.Address(), path)
for v := range val {
fmt.Printf("%02x", v)
}
fmt.Println("")
}
// Return the list of function descriptors. This is a function instead of
// a variable in order to avoid an initialization loop.
func eeiFuncs(in *InterpreterEWASM) []wasm.Function {
return []wasm.Function{
{
Sig: &eeiTypes.Entries[0], // TODO use constants or find the right entry in the list
Host: reflect.ValueOf(func(p *exec.Process, a int64) { useGas(p, in, a) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getAddress(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, a, r int32) { getExternalBalance(p, in, a, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[3],
Host: reflect.ValueOf(func(p *exec.Process, n int64, r int32) int32 { return getBlockHash(p, in, n, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[4],
Host: reflect.ValueOf(func(p *exec.Process, g int64, a, v, d, l int32) int32 { return call(p, in, g, a, v, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[6],
Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) { callDataCopy(p, in, r, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[7],
Host: reflect.ValueOf(func(p *exec.Process) int32 { return getCallDataSize(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[9],
Host: reflect.ValueOf(func(p *exec.Process, g int64, a, v, d, l int32) int32 { return callCode(p, in, g, a, v, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[8],
Host: reflect.ValueOf(func(p *exec.Process, g int64, a, d, l int32) int32 { return callDelegate(p, in, g, a, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[8],
Host: reflect.ValueOf(func(p *exec.Process, g int64, a, d, l int32) int32 { return callStatic(p, in, g, a, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(pr *exec.Process, p, v int32) { storageStore(pr, in, p, v) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(pr *exec.Process, p, r int32) { storageLoad(pr, in, p, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getCaller(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getCallValue(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[6],
Host: reflect.ValueOf(func(p *exec.Process, r, c, l int32) { codeCopy(p, in, r, c, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[7],
Host: reflect.ValueOf(func(p *exec.Process) int32 { return getCodeSize(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getBlockCoinbase(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[10],
Host: reflect.ValueOf(func(p *exec.Process, v, d, l, r uint32) int32 { return create(p, in, v, d, l, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getBlockDifficulty(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[11],
Host: reflect.ValueOf(func(p *exec.Process, a, r, c, l int32) { externalCodeCopy(p, in, a, r, c, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[5],
Host: reflect.ValueOf(func(p *exec.Process, a int32) int32 { return getExternalCodeSize(p, in, a) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[12],
Host: reflect.ValueOf(func(p *exec.Process) int64 { return getGasLeft(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[12],
Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockGasLimit(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[5],
Host: reflect.ValueOf(func(p *exec.Process, v int32) { getTxGasPrice(p, in, v) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[13],
Host: reflect.ValueOf(func(p *exec.Process, d, l, n, t1, t2, t3, t4 int32) { log(p, in, d, l, n, t1, t2, t3, t4) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[12],
Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockNumber(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, r int32) { getTxOrigin(p, in, r) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, d, l int32) { finish(p, in, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[2],
Host: reflect.ValueOf(func(p *exec.Process, d, l int32) { revert(p, in, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[5],
Host: reflect.ValueOf(func(p *exec.Process) int32 { return getReturnDataSize(p, in) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[6],
Host: reflect.ValueOf(func(p *exec.Process, r, d, l int32) { returnDataCopy(p, in, r, d, l) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[1],
Host: reflect.ValueOf(func(p *exec.Process, a int32) { selfDestruct(p, in, a) }),
Body: &wasm.FunctionBody{},
},
{
Sig: &eeiTypes.Entries[12],
Host: reflect.ValueOf(func(p *exec.Process) int64 { return getBlockTimestamp(p, in) }),
Body: &wasm.FunctionBody{},
},
}
}
func readSize(p *exec.Process, offset int32, size int) []byte {
// TODO modify the process interface to find out how much memory is
// available on the system.
val := make([]byte, size)
p.ReadAt(val, int64(offset))
return val
}
func readU256(p *exec.Process, offset int32) []byte {
return readSize(p, offset, u256Len)
}
func useGas(p *exec.Process, in *InterpreterEWASM, amount int64) {
in.gasAccounting(uint64(amount))
}
func getAddress(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
in.gasAccounting(GasCostBase)
contractBytes := in.contract.CodeAddr.Bytes()
p.WriteAt(contractBytes, int64(resultOffset))
}
func getExternalBalance(p *exec.Process, in *InterpreterEWASM, addressOffset int32, resultOffset int32) {
in.gasAccounting(in.gasTable.Balance)
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
balance := swapEndian(in.StateDB.GetBalance(addr).Bytes())
p.WriteAt(balance, int64(resultOffset))
}
func getBlockHash(p *exec.Process, in *InterpreterEWASM, number int64, resultOffset int32) int32 {
in.gasAccounting(GasCostBlockHash)
n := big.NewInt(number)
fmt.Println(n)
n.Sub(in.evm.Context.BlockNumber, n)
fmt.Println(n, n.Cmp(big.NewInt(256)), n.Cmp(big.NewInt(0)))
if n.Cmp(big.NewInt(256)) > 0 || n.Cmp(big.NewInt(0)) <= 0 {
return 1
}
h := in.evm.GetHash(uint64(number))
p.WriteAt(h.Bytes(), int64(resultOffset))
return 0
}
func callCommon(in *InterpreterEWASM, contract, targetContract *Contract, input []byte, value *big.Int, snapshot int, gas int64, ro bool) int32 {
savedVM := in.vm
in.Run(targetContract, input, ro)
in.vm = savedVM
in.contract = contract
if value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(uint64(gas) - targetContract.Gas - GasCostCallStipend)
} else {
in.gasAccounting(uint64(gas) - targetContract.Gas)
}
switch in.terminationType {
case TerminateFinish:
return EEICallSuccess
case TerminateRevert:
in.StateDB.RevertToSnapshot(snapshot)
return ErrEEICallRevert
default:
in.StateDB.RevertToSnapshot(snapshot)
contract.UseGas(targetContract.Gas)
return ErrEEICallFailure
}
}
func call(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract
// Get the address of the contract to call
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
// Get the value. The [spec](https://github.com/ewasm/design/blob/master/eth_interface.md#call)
// requires this operation to be U128, which is incompatible with the EVM version that expects
// a u256.
value := big.NewInt(0).SetBytes(swapEndian(readSize(p, valueOffset, u128Len)))
if value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(GasCostCallValue)
gas += GasCostCallStipend
}
// Get the arguments.
// TODO check the need for callvalue (seems not, a lot of that stuff is
// already accounted for in the functions that I already called - need to
// refactor all that)
input := readSize(p, dataOffset, int(dataLength))
snapshot := in.StateDB.Snapshot()
// Check that the contract exists
if !in.StateDB.Exist(addr) {
// TODO check that no new account creation stuff is required
in.StateDB.CreateAccount(addr)
}
// Check that there is enough balance to transfer the value
if in.StateDB.GetBalance(contract.Address()).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
in.contract.Gas += GasCostCallStipend
return ErrEEICallFailure
}
// TODO tracing
// TODO check that EIP-150 is respected
// Add amount to recipient
in.evm.Transfer(in.StateDB, contract.Address(), addr, value)
// Load the contract code in a new VM structure
targetContract := NewContract(contract, AccountRef(addr), value, uint64(gas))
code := in.StateDB.GetCode(addr)
targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code)
return callCommon(in, contract, targetContract, input, value, snapshot, gas, false)
}
func callDataCopy(p *exec.Process, in *InterpreterEWASM, resultOffset int32, dataOffset int32, length int32) {
in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5))
p.WriteAt(in.contract.Input[dataOffset:dataOffset+length], int64(resultOffset))
}
func getCallDataSize(p *exec.Process, in *InterpreterEWASM) int32 {
in.gasAccounting(GasCostBase)
return int32(len(in.contract.Input))
}
func callCode(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract
// Get the address of the contract to call
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
// Get the value. The [spec](https://github.com/ewasm/design/blob/master/eth_interface.md#call)
// requires this operation to be U128, which is incompatible with the EVM version that expects
// a u256.
value := big.NewInt(0).SetBytes(readSize(p, valueOffset, u128Len))
if value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(GasCostCallValue)
gas += GasCostCallStipend
}
// Get the arguments.
// TODO check the need for callvalue (seems not, a lot of that stuff is
// already accounted for in the functions that I already called - need to
// refactor all that)
input := readSize(p, dataOffset, int(dataLength))
snapshot := in.StateDB.Snapshot()
// Check that there is enough balance to transfer the value
if in.StateDB.GetBalance(addr).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
return ErrEEICallFailure
}
// TODO tracing
// TODO check that EIP-150 is respected
// Load the contract code in a new VM structure
targetContract := NewContract(contract.caller, AccountRef(contract.Address()), value, uint64(gas))
code := in.StateDB.GetCode(addr)
targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code)
return callCommon(in, contract, targetContract, input, value, snapshot, gas, false)
}
func callDelegate(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract
// Get the address of the contract to call
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
// Get the value. The [spec](https://github.com/ewasm/design/blob/master/eth_interface.md#call)
// requires this operation to be U128, which is incompatible with the EVM version that expects
// a u256.
value := contract.value
if value.Cmp(big.NewInt(0)) != 0 {
in.gasAccounting(GasCostCallValue)
gas += GasCostCallStipend
}
// Get the arguments.
// TODO check the need for callvalue (seems not, a lot of that stuff is
// already accounted for in the functions that I already called - need to
// refactor all that)
input := readSize(p, dataOffset, int(dataLength))
snapshot := in.StateDB.Snapshot()
// Check that there is enough balance to transfer the value
if in.StateDB.GetBalance(addr).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
return ErrEEICallFailure
}
// TODO tracing
// TODO check that EIP-150 is respected
// Load the contract code in a new VM structure
targetContract := NewContract(AccountRef(contract.Address()), AccountRef(contract.Address()), value, uint64(gas))
code := in.StateDB.GetCode(addr)
caddr := contract.Address()
targetContract.SetCallCode(&caddr, in.StateDB.GetCodeHash(addr), code)
return callCommon(in, contract, targetContract, input, value, snapshot, gas, false)
}
func callStatic(p *exec.Process, in *InterpreterEWASM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 {
in.gasAccounting(GasCostCall)
contract := in.contract
// Get the address of the contract to call
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
value := big.NewInt(0)
// Get the arguments.
// TODO check the need for callvalue (seems not, a lot of that stuff is
// already accounted for in the functions that I already called - need to
// refactor all that)
input := readSize(p, dataOffset, int(dataLength))
snapshot := in.StateDB.Snapshot()
// Check that the contract exists
if !in.StateDB.Exist(addr) {
// TODO check that no new account creation stuff is required
in.StateDB.CreateAccount(addr)
}
// Check that there is enough balance to transfer the value
if in.StateDB.GetBalance(addr).Cmp(value) < 0 {
fmt.Printf("Not enough balance: wanted to use %v, got %v\n", value, in.StateDB.GetBalance(addr))
in.contract.Gas += GasCostCallStipend
return ErrEEICallFailure
}
// TODO tracing
// TODO check that EIP-150 is respected
// Add amount to recipient
in.evm.Transfer(in.StateDB, contract.Address(), addr, value)
// Load the contract code in a new VM structure
targetContract := NewContract(contract, AccountRef(addr), value, uint64(gas))
code := in.StateDB.GetCode(addr)
targetContract.SetCallCode(&addr, in.StateDB.GetCodeHash(addr), code)
return callCommon(in, contract, targetContract, input, value, snapshot, gas, true)
}
func storageStore(p *exec.Process, interpreter *InterpreterEWASM, pathOffset int32, valueOffset int32) {
loc := common.BytesToHash(readU256(p, pathOffset))
val := common.BytesToHash(readU256(p, valueOffset))
fmt.Println(val, loc)
nonZeroBytes := 0
for _, b := range val.Bytes() {
if b != 0 {
nonZeroBytes++
}
}
oldValue := interpreter.StateDB.GetState(interpreter.contract.Address(), loc)
oldNonZeroBytes := 0
for _, b := range oldValue.Bytes() {
if b != 0 {
oldNonZeroBytes++
}
}
if (nonZeroBytes > 0 && oldNonZeroBytes != nonZeroBytes) || (oldNonZeroBytes != 0 && nonZeroBytes == 0) {
interpreter.gasAccounting(GasCostSSet)
} else {
// Refund for setting one value to 0 or if the "zeroness" remains
// unchanged.
interpreter.gasAccounting(GasCostSReset)
}
interpreter.StateDB.SetState(interpreter.contract.Address(), loc, val)
}
func storageLoad(p *exec.Process, interpreter *InterpreterEWASM, pathOffset int32, resultOffset int32) {
interpreter.gasAccounting(interpreter.gasTable.SLoad)
loc := common.BytesToHash(readU256(p, pathOffset))
valBytes := interpreter.StateDB.GetState(interpreter.contract.Address(), loc).Bytes()
p.WriteAt(valBytes, int64(resultOffset))
}
func getCaller(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
callerAddress := in.contract.CallerAddress
in.gasAccounting(GasCostBase)
p.WriteAt(callerAddress.Bytes(), int64(resultOffset))
}
func getCallValue(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
in.gasAccounting(GasCostBase)
p.WriteAt(swapEndian(in.contract.Value().Bytes()), int64(resultOffset))
}
func codeCopy(p *exec.Process, in *InterpreterEWASM, resultOffset int32, codeOffset int32, length int32) {
in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5))
code := in.contract.Code
p.WriteAt(code[codeOffset:codeOffset+length], int64(resultOffset))
}
func getCodeSize(p *exec.Process, in *InterpreterEWASM) int32 {
in.gasAccounting(GasCostBase)
code := in.StateDB.GetCode(*in.contract.CodeAddr)
return int32(len(code))
}
func getBlockCoinbase(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
in.gasAccounting(GasCostBase)
p.WriteAt(in.evm.Coinbase.Bytes(), int64(resultOffset))
}
func create(p *exec.Process, in *InterpreterEWASM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 {
in.gasAccounting(GasCostCreate)
savedVM := in.vm
savedContract := in.contract
in.terminationType = TerminateInvalid
if int(codeOffset)+int(length) > len(in.vm.Memory()) {
return ErrEEICallFailure
}
input := readSize(p, int32(codeOffset), int(length))
if (int(valueOffset) + u128Len) > len(in.vm.Memory()) {
return ErrEEICallFailure
}
value := swapEndian(readSize(p, int32(valueOffset), u128Len))
// EIP150 says that the calling contract should keep 1/64th of the
// leftover gas.
gas := in.contract.Gas - in.contract.Gas/64
in.gasAccounting(gas)
_, addr, gasLeft, _ := in.evm.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value))
in.vm = savedVM
in.contract = savedContract
in.contract.Gas += gasLeft
p.WriteAt(addr.Bytes(), int64(resultOffset))
switch in.terminationType {
case TerminateFinish:
return EEICallSuccess
case TerminateRevert:
return ErrEEICallRevert
default:
return ErrEEICallFailure
}
}
func getBlockDifficulty(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
in.gasAccounting(GasCostBase)
p.WriteAt(swapEndian(in.evm.Difficulty.Bytes()), int64(resultOffset))
}
func externalCodeCopy(p *exec.Process, in *InterpreterEWASM, addressOffset int32, resultOffset int32, codeOffset int32, length int32) {
in.gasAccounting(in.gasTable.ExtcodeCopy + GasCostCopy*(uint64(length+31)>>5))
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
code := in.StateDB.GetCode(addr)
p.WriteAt(code[codeOffset:codeOffset+length], int64(resultOffset))
}
func getExternalCodeSize(p *exec.Process, in *InterpreterEWASM, addressOffset int32) int32 {
in.gasAccounting(in.gasTable.ExtcodeSize)
addr := common.BytesToAddress(readSize(p, addressOffset, common.AddressLength))
code := in.StateDB.GetCode(addr)
return int32(len(code))
}
func getGasLeft(p *exec.Process, in *InterpreterEWASM) int64 {
in.gasAccounting(GasCostBase)
return int64(in.contract.Gas)
}
func getBlockGasLimit(p *exec.Process, in *InterpreterEWASM) int64 {
in.gasAccounting(GasCostBase)
return int64(in.evm.GasLimit)
}
func getTxGasPrice(p *exec.Process, in *InterpreterEWASM, valueOffset int32) {
in.gasAccounting(GasCostBase)
p.WriteAt(in.evm.GasPrice.Bytes(), int64(valueOffset))
}
// It would be nice to be able to use variadic functions to pass the number of topics,
// however this imposes a change in wagon because the number of arguments is being
// checked when calling a function.
func log(p *exec.Process, in *InterpreterEWASM, dataOffset int32, length int32, numberOfTopics int32, topic1 int32, topic2 int32, topic3 int32, topic4 int32) {
in.gasAccounting(GasCostLog + GasCostLogData*uint64(length) + uint64(numberOfTopics)*GasCostLogTopic)
// TODO need to add some info about the memory boundary on wagon
if uint64(len(in.vm.Memory())) <= uint64(length)+uint64(dataOffset) {
panic("out of memory")
}
data := readSize(p, dataOffset, int(uint32(length)))
topics := make([]common.Hash, numberOfTopics)
if numberOfTopics > 4 || numberOfTopics < 0 {
in.terminationType = TerminateInvalid
p.Terminate()
}
// Variadic functions FTW
if numberOfTopics > 0 {
if uint64(len(in.vm.Memory())) <= uint64(topic1) {
panic("out of memory")
}
topics[0] = common.BigToHash(big.NewInt(0).SetBytes(readU256(p, topic1)))
}
if numberOfTopics > 1 {
if uint64(len(in.vm.Memory())) <= uint64(topic2) {
panic("out of memory")
}
topics[1] = common.BigToHash(big.NewInt(0).SetBytes(readU256(p, topic2)))
}
if numberOfTopics > 2 {
if uint64(len(in.vm.Memory())) <= uint64(topic3) {
panic("out of memory")
}
topics[2] = common.BigToHash(big.NewInt(0).SetBytes(readU256(p, topic3)))
}
if numberOfTopics > 3 {
if uint64(len(in.vm.Memory())) <= uint64(topic3) {
panic("out of memory")
}
topics[3] = common.BigToHash(big.NewInt(0).SetBytes(readU256(p, topic4)))
}
in.StateDB.AddLog(&types.Log{
Address: in.contract.Address(),
Topics: topics,
Data: data,
BlockNumber: in.evm.BlockNumber.Uint64(),
})
}
func getBlockNumber(p *exec.Process, in *InterpreterEWASM) int64 {
in.gasAccounting(GasCostBase)
return in.evm.BlockNumber.Int64()
}
func getTxOrigin(p *exec.Process, in *InterpreterEWASM, resultOffset int32) {
in.gasAccounting(GasCostBase)
p.WriteAt(in.evm.Origin.Big().Bytes(), int64(resultOffset))
}
func unWindContract(p *exec.Process, in *InterpreterEWASM, dataOffset int32, length int32) {
in.returnData = make([]byte, length)
p.ReadAt(in.returnData, int64(dataOffset))
}
func finish(p *exec.Process, in *InterpreterEWASM, dataOffset int32, length int32) {
unWindContract(p, in, dataOffset, length)
in.terminationType = TerminateFinish
p.Terminate()
}
func revert(p *exec.Process, in *InterpreterEWASM, dataOffset int32, length int32) {
unWindContract(p, in, dataOffset, length)
in.terminationType = TerminateRevert
p.Terminate()
}
func getReturnDataSize(p *exec.Process, in *InterpreterEWASM) int32 {
in.gasAccounting(GasCostBase)
return int32(len(in.returnData))
}
func returnDataCopy(p *exec.Process, in *InterpreterEWASM, resultOffset int32, dataOffset int32, length int32) {
in.gasAccounting(GasCostVeryLow + GasCostCopy*(uint64(length+31)>>5))
p.WriteAt(in.returnData[dataOffset:dataOffset+length], int64(resultOffset))
}
func selfDestruct(p *exec.Process, in *InterpreterEWASM, addressOffset int32) {
contract := in.contract
mem := in.vm.Memory()
balance := in.StateDB.GetBalance(contract.Address())
addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength])
in.StateDB.AddBalance(addr, balance)
totalGas := in.gasTable.Suicide
// If the destination address doesn't exist, add the account creation costs
if in.StateDB.Empty(addr) && balance.Sign() != 0 {
totalGas += in.gasTable.CreateBySuicide
}
in.gasAccounting(totalGas)
in.StateDB.Suicide(contract.Address())
// Same as for `revert` and `return`, I need to forcefuly terminate
// the execution of the contract.
in.terminationType = TerminateSuicide
p.Terminate()
}
func getBlockTimestamp(p *exec.Process, in *InterpreterEWASM) int64 {
in.gasAccounting(GasCostBase)
return in.evm.Time.Int64()
}

119
core/vm/ewasm_linker.go Normal file
View file

@ -0,0 +1,119 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
package vm
import (
"fmt"
"github.com/go-interpreter/wagon/wasm"
)
var eeiFunctionList = []string{
"useGas",
"getAddress",
"getExternalBalance",
"getBlockHash",
"call",
"callDataCopy",
"getCallDataSize",
"callCode",
"callDelegate",
"callStatic",
"storageStore",
"storageLoad",
"getCaller",
"getCallValue",
"codeCopy",
"getCodeSize",
"getBlockCoinbase",
"create",
"getBlockDifficulty",
"externalCodeCopy",
"getExternalCodeSize",
"getGasLeft",
"getBlockGasLimit",
"getTxGasPrice",
"log",
"getBlockNumber",
"getTxOrigin",
"finish",
"revert",
"getReturnDataSize",
"returnDataCopy",
"selfDestruct",
"getBlockTimestamp",
}
var debugFunctionList = []string{
"printMemHex",
"printStorageHex",
}
// ModuleResolver matches all EEI functions to native go functions
func ModuleResolver(interpreter *InterpreterEWASM, name string) (*wasm.Module, error) {
if name == "debug" {
debugModule := wasm.NewModule()
debugModule.Types = eeiTypes
debugModule.FunctionIndexSpace = getDebugFuncs(interpreter)
entries := make(map[string]wasm.ExportEntry)
for idx, name := range debugFunctionList {
entries[name] = wasm.ExportEntry{
FieldStr: name,
Kind: wasm.ExternalFunction,
Index: uint32(idx),
}
}
debugModule.Export = &wasm.SectionExports{
Entries: entries,
}
return debugModule, nil
}
if name != "env" && name != "ethereum" {
return nil, fmt.Errorf("Unknown module name: %s", name)
}
m := wasm.NewModule()
m.Types = eeiTypes
m.FunctionIndexSpace = eeiFuncs(interpreter)
entries := make(map[string]wasm.ExportEntry)
for idx, name := range eeiFunctionList {
entries[name] = wasm.ExportEntry{
FieldStr: name,
Kind: wasm.ExternalFunction,
Index: uint32(idx),
}
}
m.Export = &wasm.SectionExports{
Entries: entries,
}
return m, nil
}
// WrappedModuleResolver returns a module resolver function that whose
// EEI functions are closure-bound to a given interpreter.
// This is the first step to closure hell, the plan is to improve PR #59
// in wagon to be able to pass some context.
func WrappedModuleResolver(in *InterpreterEWASM) wasm.ResolveFunc {
return func(name string) (*wasm.Module, error) {
return ModuleResolver(in, name)
}
}

View file

@ -177,7 +177,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
return nil, err
}
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, config.MinerRecommit, config.MinerGasFloor, config.MinerGasCeil, eth.isLocalBlock)
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, config.MinerRecommit, config.MinerGasFloor, config.MinerGasCeil)
eth.miner.SetExtra(makeExtraData(config.MinerExtraData))
eth.APIBackend = &EthAPIBackend{eth, nil}
@ -334,60 +334,6 @@ func (s *Ethereum) Etherbase() (eb common.Address, err error) {
return common.Address{}, fmt.Errorf("etherbase must be explicitly specified")
}
// isLocalBlock checks whether the specified block is mined
// by local miner accounts.
//
// We regard two types of accounts as local miner account: etherbase
// and accounts specified via `txpool.locals` flag.
func (s *Ethereum) isLocalBlock(block *types.Block) bool {
author, err := s.engine.Author(block.Header())
if err != nil {
log.Warn("Failed to retrieve block author", "number", block.NumberU64(), "hash", block.Hash(), "err", err)
return false
}
// Check whether the given address is etherbase.
s.lock.RLock()
etherbase := s.etherbase
s.lock.RUnlock()
if author == etherbase {
return true
}
// Check whether the given address is specified by `txpool.local`
// CLI flag.
for _, account := range s.config.TxPool.Locals {
if account == author {
return true
}
}
return false
}
// shouldPreserve checks whether we should preserve the given block
// during the chain reorg depending on whether the author of block
// is a local account.
func (s *Ethereum) shouldPreserve(block *types.Block) bool {
// The reason we need to disable the self-reorg preserving for clique
// is it can be probable to introduce a deadlock.
//
// e.g. If there are 7 available signers
//
// r1 A
// r2 B
// r3 C
// r4 D
// r5 A [X] F G
// r6 [X]
//
// In the round5, the inturn signer E is offline, so the worst case
// is A, F and G sign the block of round5 and reject the block of opponents
// and in the round6, the last available signer B is offline, the whole
// network is stuck.
if _, ok := s.engine.(*clique.Clique); ok {
return false
}
return s.isLocalBlock(block)
}
// SetEtherbase sets the mining reward address.
func (s *Ethereum) SetEtherbase(etherbase common.Address) {
s.lock.Lock()
@ -420,7 +366,7 @@ func (s *Ethereum) StartMining(threads int) error {
s.lock.RUnlock()
s.txPool.SetGasPrice(price)
// Configure the local mining address
// Configure the local mining addess
eb, err := s.Etherbase()
if err != nil {
log.Error("Cannot start mining without etherbase", "err", err)

46
tests/ewasm_test.go Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tests
import (
"fmt"
"testing"
"github.com/ethereum/go-ethereum/core/vm"
)
func TestEWASMVM(t *testing.T) {
t.Parallel()
st := new(testMatcher)
st.walk(t, stateTestDir+"/stEWASMTests", func(t *testing.T, name string, test *StateTest) {
for _, subtest := range test.Subtests() {
subtest := subtest
key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index)
name := name + "/" + key
t.Run(key, func(t *testing.T) {
if subtest.Fork == "Constantinople" {
t.Skip("constantinople not supported yet")
}
withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error {
vmconfig.EnableWagon = true
_, err := test.Run(subtest, vmconfig)
return st.checkFailure(t, name, err)
})
})
}
})
}

View file

@ -66,7 +66,7 @@ func TestState(t *testing.T) {
}
// Transactions with gasLimit above this value will not get a VM trace on failure.
const traceErrorLimit = 400000
const traceErrorLimit = 5242880
func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) {
err := test(vm.Config{})
@ -75,7 +75,7 @@ func withTrace(t *testing.T, gasLimit uint64, test func(vm.Config) error) {
}
t.Error(err)
if gasLimit > traceErrorLimit {
t.Log("gas limit too high for EVM trace")
t.Log(fmt.Sprintf("gas limit too high for EVM trace %d > %d", gasLimit, traceErrorLimit))
return
}
tracer := vm.NewStructLogger(nil)

24
vendor/github.com/go-interpreter/wagon/LICENSE generated vendored Normal file
View file

@ -0,0 +1,24 @@
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.

28
vendor/github.com/go-interpreter/wagon/README.md generated vendored Normal file
View file

@ -0,0 +1,28 @@
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`.

62
vendor/github.com/go-interpreter/wagon/disasm/asm.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
// 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
}

464
vendor/github.com/go-interpreter/wagon/disasm/disasm.go generated vendored Normal file
View file

@ -0,0 +1,464 @@
// 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
}

33
vendor/github.com/go-interpreter/wagon/disasm/log.go generated vendored Normal file
View file

@ -0,0 +1,33 @@
// 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)
}

6
vendor/github.com/go-interpreter/wagon/doc.go generated vendored Normal file
View file

@ -0,0 +1,6 @@
// 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

57
vendor/github.com/go-interpreter/wagon/exec/call.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
// 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))
}

21
vendor/github.com/go-interpreter/wagon/exec/const.go generated vendored Normal file
View file

@ -0,0 +1,21 @@
// 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())
}

17
vendor/github.com/go-interpreter/wagon/exec/control.go generated vendored Normal file
View file

@ -0,0 +1,17 @@
// 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() {}

93
vendor/github.com/go-interpreter/wagon/exec/conv.go generated vendored Normal file
View file

@ -0,0 +1,93 @@
// 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()))
}

109
vendor/github.com/go-interpreter/wagon/exec/func.go generated vendored Normal file
View file

@ -0,0 +1,109 @@
// 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

@ -0,0 +1,186 @@
// 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

@ -0,0 +1,377 @@
// 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
}

214
vendor/github.com/go-interpreter/wagon/exec/memory.go generated vendored Normal file
View file

@ -0,0 +1,214 @@
// 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))
}

508
vendor/github.com/go-interpreter/wagon/exec/num.go generated vendored Normal file
View file

@ -0,0 +1,508 @@
// 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

@ -0,0 +1,21 @@
// 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

@ -0,0 +1,29 @@
// 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()))
}

31
vendor/github.com/go-interpreter/wagon/exec/var.go generated vendored Normal file
View file

@ -0,0 +1,31 @@
// 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()
}

462
vendor/github.com/go-interpreter/wagon/exec/vm.go generated vendored Normal file
View file

@ -0,0 +1,462 @@
// 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
}

1
vendor/github.com/go-interpreter/wagon/go.mod generated vendored Normal file
View file

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

View file

@ -0,0 +1,40 @@
// 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)
}

6
vendor/github.com/go-interpreter/wagon/wasm/doc.go generated vendored Normal file
View file

@ -0,0 +1,6 @@
// 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

70
vendor/github.com/go-interpreter/wagon/wasm/encode.go generated vendored Normal file
View file

@ -0,0 +1,70 @@
// 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
}

200
vendor/github.com/go-interpreter/wagon/wasm/imports.go generated vendored Normal file
View file

@ -0,0 +1,200 @@
// 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
}

180
vendor/github.com/go-interpreter/wagon/wasm/index.go generated vendored Normal file
View file

@ -0,0 +1,180 @@
// 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

@ -0,0 +1,172 @@
// 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

@ -0,0 +1,30 @@
// 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

@ -0,0 +1,92 @@
// 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

@ -0,0 +1,60 @@
// 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)
}

25
vendor/github.com/go-interpreter/wagon/wasm/log.go generated vendored Normal file
View file

@ -0,0 +1,25 @@
// 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)
}

163
vendor/github.com/go-interpreter/wagon/wasm/module.go generated vendored Normal file
View file

@ -0,0 +1,163 @@
// 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

@ -0,0 +1,10 @@
// 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

@ -0,0 +1,46 @@
// 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

@ -0,0 +1,16 @@
// 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

@ -0,0 +1,23 @@
// 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

@ -0,0 +1,64 @@
// 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

@ -0,0 +1,39 @@
// 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

@ -0,0 +1,76 @@
// 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

@ -0,0 +1,86 @@
// 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

@ -0,0 +1,10 @@
// 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

@ -0,0 +1,16 @@
// 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

@ -0,0 +1,13 @@
// 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")
)

64
vendor/github.com/go-interpreter/wagon/wasm/read.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
// 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
}

1203
vendor/github.com/go-interpreter/wagon/wasm/section.go generated vendored Normal file

File diff suppressed because it is too large Load diff

352
vendor/github.com/go-interpreter/wagon/wasm/types.go generated vendored Normal file
View file

@ -0,0 +1,352 @@
// 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
}

54
vendor/vendor.json vendored
View file

@ -128,6 +128,60 @@
"revision": "991cd3d3809135dc24daf6188dc6edcaf3d7d2d9",
"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=",
"path": "github.com/go-interpreter/wagon/disasm",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "liSTT+2c8gy/07ueALb5+d5+cY8=",
"path": "github.com/go-interpreter/wagon/exec",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "4yRxeKi6k5yIf87EwOXzbSTlTRU=",
"path": "github.com/go-interpreter/wagon/exec/internal/compile",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "6zjjuEauIj/855KLJGKT2cgjVio=",
"path": "github.com/go-interpreter/wagon/internal/stack",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "m8mlG4hbjWqwiwxUijPXDXq9wBc=",
"path": "github.com/go-interpreter/wagon/wasm",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "kRHgYIzF9xTBn1Ai9h5H0wcPfp8=",
"path": "github.com/go-interpreter/wagon/wasm/internal/readpos",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "PwuCAZewDWGOeIK2Ya8vh2ux3ik=",
"path": "github.com/go-interpreter/wagon/wasm/leb128",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "eaISibWdQ9N489Att4HG3Xbg+jw=",
"path": "github.com/go-interpreter/wagon/wasm/operators",
"revision": "876a973424d4032ac0f0af8c78c0d22dc9b0249e",
"revisionTime": "2018-08-07T20:59:56Z"
},
{
"checksumSHA1": "gxV/cPPLkByTdY8y172t7v4qcZA=",
"path": "github.com/go-ole/go-ole",