diff options
author | dm4 <dm4@skymizer.com> | 2019-03-21 02:46:07 +0800 |
---|---|---|
committer | hydai <hydai@skymizer.com> | 2019-04-11 18:16:52 +0800 |
commit | 8d0abcaefbd5f6ae5c8a3eae7446aaabcade11bf (patch) | |
tree | 67e0a39e7263a43edced3665059690b08de78018 | |
parent | 27b5f88f0f8d3d6e3e478e43180ed79e3f8ae28e (diff) | |
download | dexon-8d0abcaefbd5f6ae5c8a3eae7446aaabcade11bf.tar.gz dexon-8d0abcaefbd5f6ae5c8a3eae7446aaabcade11bf.tar.zst dexon-8d0abcaefbd5f6ae5c8a3eae7446aaabcade11bf.zip |
core/vm: add dvm, a wasm-based virtual machine
DVM follows DEXON VM interface. It checks bytecode and executes it in a
WASM VM.
56 files changed, 8838 insertions, 1 deletions
diff --git a/core/vm/dvm/dvm.go b/core/vm/dvm/dvm.go new file mode 100644 index 000000000..44f5434da --- /dev/null +++ b/core/vm/dvm/dvm.go @@ -0,0 +1,606 @@ +// 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 dvm + +import ( + "bytes" + "errors" + "fmt" + "math/big" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" + "github.com/dexon-foundation/dexon/params" + + "github.com/go-interpreter/wagon/exec" + "github.com/go-interpreter/wagon/wasm" +) + +type terminationType int + +// List of termination reasons +const ( + TerminateFinish = iota + TerminateRevert + TerminateSuicide + TerminateInvalid +) + +// emptyCodeHash is used by create to ensure deployment is disallowed to already +// deployed contract addresses (relevant after the account abstraction). +var ( + emptyCodeHash = crypto.Keccak256Hash(nil) + errExecutionReverted = errors.New("dvm: execution reverted") + errMaxCodeSizeExceeded = errors.New("dvm: max code size exceeded") + u256Len = 32 + u128Len = 16 + maxCallDepth = 1024 + sentinelContractAddress = "0x000000000000000000000000000000000000000a" +) + +type DVM struct { + // Context provides auxiliary blockchain related information + vm.Context + // StateDB gives access to the underlying state + statedb vm.StateDB + // Depth is the current call stack + depth int + + // chainConfig contains information about the current chain + chainConfig *params.ChainConfig + // chain rules contains the chain rules for the current epoch + chainRules params.Rules + // abort is used to abort the SQLVM calling operations + // NOTE: must be set atomically + abort int32 + // callGasTemp holds the gas available for the current call. This is needed because the + // available gas is calculated in gasCall* according to the 63/64 rule and later + // applied in opCall*. + callGasTemp uint64 + + // vm is wasm VM to execute bytecode + vm *exec.VM + + gasTable params.GasTable + contract *vm.Contract + returnData []byte + terminationType terminationType + staticMode bool + + metering bool + meteringContract *vm.Contract + meteringModule *wasm.Module + meteringStartIndex int64 +} + +type Config struct { + Metering bool +} + +func init() { + vm.Register(vm.DVM, &DVM{}) +} + +// NewDVM creates a new wagon-based ewasm vm. +func NewDVM(statedb vm.StateDB, config Config) *DVM { + ctx := vm.Context{ + CanTransfer: core.CanTransfer, + Transfer: core.Transfer, + } + dvm := &DVM{ + Context: ctx, + statedb: statedb, + metering: config.Metering, + } + + if dvm.metering { + meteringContractAddress := common.HexToAddress(sentinelContractAddress) + meteringCode := dvm.StateDB().GetCode(meteringContractAddress) + + var err error + dvm.meteringModule, err = wasm.ReadModule(bytes.NewReader(meteringCode), WrappedModuleResolver(dvm)) + if err != nil { + panic(fmt.Sprintf("Error loading the metering contract: %v", err)) + } + // TODO when the metering contract abides by that rule, check that it + // only exports "main" and "memory". + dvm.meteringStartIndex = int64(dvm.meteringModule.Export.Entries["main"].Index) + mainSig := dvm.meteringModule.FunctionIndexSpace[dvm.meteringStartIndex].Sig + if len(mainSig.ParamTypes) != 0 || len(mainSig.ReturnTypes) != 0 { + panic(fmt.Sprintf("Invalid main function for the metering contract: index=%d sig=%v", dvm.meteringStartIndex, mainSig)) + } + } + + return dvm +} + +func (dvm *DVM) StateDB() vm.StateDB { + return dvm.statedb +} + +func (dvm *DVM) Create(caller vm.ContractRef, code []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, common.Address, uint64, error) { + contractAddr := crypto.CreateAddress(caller.Address(), in.(*DVM).statedb.GetNonce(caller.Address())) + return in.(*DVM).create(caller, &vm.CodeAndHash{Code: code}, gas, value, contractAddr) +} + +func (dvm *DVM) Create2(caller vm.ContractRef, code []byte, gas uint64, endowment *big.Int, salt *big.Int, in vm.Interpreter) ([]byte, common.Address, uint64, error) { + codeAndHash := &vm.CodeAndHash{Code: code} + contractAddr := crypto.CreateAddress2(caller.Address(), common.BigToHash(salt), codeAndHash.Hash().Bytes()) + return dvm.create(caller, codeAndHash, gas, endowment, contractAddr) +} + +// Call executes the contract associated with the addr with the given input as +// parameters. It also handles any necessary value transfer required and takes +// the necessary steps to create accounts and reverses the state in case of an +// execution error or failed value transfer. +func (dvm *DVM) Call(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !dvm.Context.CanTransfer(dvm.StateDB(), caller.Address(), value) { + return nil, gas, vm.ErrInsufficientBalance + } + + var ( + to = vm.AccountRef(addr) + snapshot = dvm.StateDB().Snapshot() + ) + if !dvm.StateDB().Exist(addr) { + precompiles := vm.PrecompiledContractsByzantium + if precompiles[addr] == nil && value.Sign() == 0 { + return nil, gas, nil + } + dvm.StateDB().CreateAccount(addr) + } + dvm.Transfer(dvm.StateDB(), caller.Address(), to.Address(), value) + + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + contract := vm.NewContract(caller, to, value, gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && code[0] == vm.DVM && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + // Even if the account has no code, we need to continue because it might be a precompile + ret, err := dvm.run(contract, input, false) + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// CallCode executes the contract associated with the addr with the given input +// as parameters. It also handles any necessary value transfer required and takes +// the necessary steps to create accounts and reverses the state in case of an +// execution error or failed value transfer. +// +// CallCode differs from Call in the sense that it executes the given address' +// code with the caller as context. +func (dvm *DVM) CallCode(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + // Fail if we're trying to transfer more than the available balance + if !dvm.Context.CanTransfer(dvm.StateDB(), caller.Address(), value) { + return nil, gas, vm.ErrInsufficientBalance + } + + var ( + snapshot = dvm.StateDB().Snapshot() + to = vm.AccountRef(caller.Address()) + ) + + // initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, to, value, gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + ret, err := dvm.run(contract, input, false) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// DelegateCall executes the contract associated with the addr with the given input +// as parameters. It reverses the state in case of an execution error. +// +// DelegateCall differs from CallCode in the sense that it executes the given address' +// code with the caller as context and the caller is set to the caller of the caller. +func (dvm *DVM) DelegateCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + + var ( + snapshot = dvm.StateDB().Snapshot() + to = vm.AccountRef(caller.Address()) + ) + + // Initialise a new contract and make initialise the delegate values + contract := vm.NewContract(caller, to, nil, gas).AsDelegate() + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + ret, err := dvm.run(contract, input, false) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +// StaticCall executes the contract associated with the addr with the given input +// as parameters while disallowing any modifications to the state during the call. +// Opcodes that attempt to perform such modifications will result in exceptions +// instead of performing the modifications. +func (dvm *DVM) StaticCall(caller vm.ContractRef, addr common.Address, input []byte, gas uint64, in vm.Interpreter) ([]byte, uint64, error) { + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, gas, nil + // } + + // Fail if we're trying to execute above the call depth limit + if dvm.depth > int(params.CallCreateDepth) { + return nil, gas, vm.ErrDepth + } + + var ( + to = vm.AccountRef(addr) + snapshot = dvm.StateDB().Snapshot() + ) + + // Initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, to, new(big.Int), gas) + code := dvm.StateDB().GetCode(addr) + if len(code) > 0 && vm.MULTIVM { + code = code[1:] + } + codeAndHash := vm.CodeAndHash{Code: code} + contract.SetCodeOptionalHash(&addr, &codeAndHash) + + // We do an AddBalance of zero here, just in order to trigger a touch. + // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, + // but is the correct thing to do and matters on other networks, in tests, and potential + // future scenarios + dvm.StateDB().AddBalance(addr, new(big.Int)) + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in Homestead this also counts for code storage gas errors. + ret, err := dvm.run(contract, input, true) + if err != nil { + dvm.StateDB().RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + return ret, contract.Gas, err +} + +func (dvm *DVM) create(caller vm.ContractRef, codeAndHash *vm.CodeAndHash, gas uint64, value *big.Int, address common.Address) ([]byte, common.Address, uint64, error) { + // Depth check execution. Fail if we're trying to execute above the + // limit. + if dvm.depth > int(params.CallCreateDepth) { + return nil, common.Address{}, gas, vm.ErrDepth + } + if !dvm.Context.CanTransfer(dvm.statedb, caller.Address(), value) { + return nil, common.Address{}, gas, vm.ErrInsufficientBalance + } + nonce := dvm.statedb.GetNonce(caller.Address()) + dvm.statedb.SetNonce(caller.Address(), nonce+1) + + // Ensure there's no existing contract already at the designated address + contractHash := dvm.statedb.GetCodeHash(address) + if dvm.statedb.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + return nil, common.Address{}, 0, vm.ErrContractAddressCollision + } + + // Create a new account on the state + snapshot := dvm.statedb.Snapshot() + dvm.statedb.CreateAccount(address) + dvm.statedb.SetNonce(address, 1) + dvm.Context.Transfer(dvm.statedb, caller.Address(), address, value) + + // initialise a new contract and set the code that is to be used by the + // DVM. The contract is a scoped environment for this execution context + // only. + contract := vm.NewContract(caller, vm.AccountRef(address), value, gas) + meteredCode, err := dvm.PreContractCreation(codeAndHash.Code, contract) + if err != nil { + return nil, address, gas, nil + } + codeAndHash.Code = meteredCode + contract.SetCodeOptionalHash(&address, codeAndHash) + + // TODO: do we need these checks? + // if evm.vmConfig.NoRecursion && evm.depth > 0 { + // return nil, address, gas, nil + // } + + ret, err := dvm.run(contract, nil, false) + + // The new contract needs to be metered after it has executed the constructor + if err != nil { + if dvm.CanRun(contract.Code) { + ret, err = dvm.PostContractCreation(ret) + } + } + + // check whether the max code size has been exceeded + maxCodeSizeExceeded := len(ret) > params.MaxCodeSize + // if the contract creation ran successfully and no errors were returned + // calculate the gas required to store the code. If the code could not + // be stored due to not enough gas set an error and let it be handled + // by the error checking condition below. + if err == nil && !maxCodeSizeExceeded { + createDataGas := uint64(len(ret)) * params.CreateDataGas + if contract.UseGas(createDataGas) { + dvm.statedb.SetCode(address, ret) + } else { + err = vm.ErrCodeStoreOutOfGas + } + } + + // When an error was returned by the DVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if maxCodeSizeExceeded || err != nil { + dvm.statedb.RevertToSnapshot(snapshot) + if err != errExecutionReverted { + contract.UseGas(contract.Gas) + } + } + // Assign err if contract code size exceeds the max while the err is still empty. + if maxCodeSizeExceeded && err == nil { + err = errMaxCodeSizeExceeded + } + + return ret, address, contract.Gas, err +} + +// 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 (dvm *DVM) run(contract *vm.Contract, input []byte, ro bool) ([]byte, error) { + // Take care of running precompile contracts + if contract.CodeAddr != nil { + precompiles := vm.PrecompiledContractsByzantium + if p := precompiles[*contract.CodeAddr]; p != nil { + return vm.RunPrecompiledContract(p, input, contract) + } + } + + // Increment the call depth which is restricted to 1024 + dvm.depth++ + defer func() { dvm.depth-- }() + + fmt.Printf("contract.Code %v\n", contract.Code) + dvm.contract = contract + dvm.contract.Input = input + + module, err := wasm.ReadModule(bytes.NewReader(contract.Code), WrappedModuleResolver(dvm)) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, fmt.Errorf("Error decoding module at address %s: %v", contract.Address().Hex(), err) + } + + wavm, err := exec.NewVM(module) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, fmt.Errorf("could not create the vm: %v", err) + } + wavm.RecoverPanic = true + dvm.vm = wavm + + mainIndex, err := validateModule(module) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, err + } + + // Check input and output types + sig := module.FunctionIndexSpace[mainIndex].Sig + if len(sig.ParamTypes) == 0 && len(sig.ReturnTypes) == 0 { + _, err = wavm.ExecCode(int64(mainIndex)) + + if err != nil && err != errExecutionReverted { + dvm.terminationType = TerminateInvalid + } + + if dvm.StateDB().HasSuicided(contract.Address()) { + dvm.StateDB().AddRefund(params.SuicideRefundGas) + err = nil + } + + return dvm.returnData, err + } + + dvm.terminationType = TerminateInvalid + return nil, errors.New("Could not find a suitable 'main' function in that contract") +} + +func validateModule(m *wasm.Module) (int, error) { + // A module should not have a start section + if m.Start != nil { + return -1, fmt.Errorf("Module has a start section") + } + + // Only two exports are authorized: "main" and "memory" + if m.Export == nil { + return -1, fmt.Errorf("Module has no exports instead of 2") + } + if len(m.Export.Entries) != 2 { + return -1, fmt.Errorf("Module has %d exports instead of 2", len(m.Export.Entries)) + } + + mainIndex := -1 + for name, entry := range m.Export.Entries { + switch name { + case "main": + if entry.Kind != wasm.ExternalFunction { + return -1, fmt.Errorf("Main is not a function in module") + } + mainIndex = int(entry.Index) + case "memory": + if entry.Kind != wasm.ExternalMemory { + return -1, fmt.Errorf("'memory' is not a memory in module") + } + default: + return -1, fmt.Errorf("A symbol named %s has been exported. Only main and memory should exist", name) + } + } + + if m.Import != nil { + OUTER: + for _, entry := range m.Import.Entries { + if entry.ModuleName == "ethereum" { + if entry.Type.Kind() == wasm.ExternalFunction { + for _, name := range eeiFunctionList { + if name == entry.FieldName { + continue OUTER + } + } + return -1, fmt.Errorf("%s could not be found in the list of ethereum-provided functions", entry.FieldName) + } + } + } + } + + return mainIndex, nil +} + +// CanRun checks the binary for a WASM header and accepts the binary blob +// if it matches. +func (dvm *DVM) CanRun(file []byte) bool { + // Check the header + if len(file) < 4 || string(file[:4]) != "\000asm" { + return false + } + + return true +} + +// PreContractCreation meters the contract's its init code before it +// is run. +func (dvm *DVM) PreContractCreation(code []byte, contract *vm.Contract) ([]byte, error) { + savedContract := dvm.contract + dvm.contract = contract + + defer func() { + dvm.contract = savedContract + }() + + if dvm.metering { + metered, _, err := sentinel(dvm, code) + if len(metered) < 5 || err != nil { + return nil, fmt.Errorf("Error metering the init contract code, err=%v", err) + } + return metered, nil + } + return code, nil +} + +// PostContractCreation meters the contract once its init code has +// been run. It also validates the module's format before it is to +// be committed to disk. +func (dvm *DVM) PostContractCreation(code []byte) ([]byte, error) { + // If a REVERT has been encountered, then return the code and + if dvm.terminationType == TerminateRevert { + return nil, errExecutionReverted + } + + if dvm.CanRun(code) { + if dvm.metering { + meteredCode, _, err := sentinel(dvm, code) + code = meteredCode + if len(code) < 5 || err != nil { + return nil, fmt.Errorf("Error metering the generated contract code, err=%v", err) + } + + if len(code) < 8 { + return nil, fmt.Errorf("Invalid contract code") + } + } + + if len(code) > 8 { + // Check the validity of the module + m, err := wasm.DecodeModule(bytes.NewReader(code)) + if err != nil { + return nil, fmt.Errorf("Error decoding the module produced by init code: %v", err) + } + + _, err = validateModule(m) + if err != nil { + dvm.terminationType = TerminateInvalid + return nil, err + } + } + } + + return code, nil +} diff --git a/core/vm/dvm/eei.go b/core/vm/dvm/eei.go new file mode 100644 index 000000000..df97438b6 --- /dev/null +++ b/core/vm/dvm/eei.go @@ -0,0 +1,1007 @@ +// 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 dvm + +import ( + "fmt" + "math/big" + "reflect" + + "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/core/types" + "github.com/dexon-foundation/dexon/core/vm" + "github.com/dexon-foundation/dexon/crypto" + + "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 + GasCostNewAccount = 25000 + 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 *DVM) gasAccounting(cost uint64) { + if in.contract == nil { + panic("nil contract") + } + if cost > in.contract.Gas { + panic(fmt.Sprintf("out of gas %d > %d", cost, in.contract.Gas)) + } + in.contract.Gas -= cost +} + +func getDebugFuncs(in *DVM) []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 *DVM, 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 *DVM, 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 *DVM) []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[1], + 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[1], + 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[7], + 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 useGas(p *exec.Process, in *DVM, amount int64) { + in.gasAccounting(uint64(amount)) +} + +func getAddress(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + contractBytes := in.contract.CodeAddr.Bytes() + p.WriteAt(contractBytes, int64(resultOffset)) +} + +func getExternalBalance(p *exec.Process, in *DVM, 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 *DVM, number int64, resultOffset int32) int32 { + in.gasAccounting(GasCostBlockHash) + n := big.NewInt(number) + n.Sub(in.Context.BlockNumber, n) + if n.Cmp(big.NewInt(256)) > 0 || n.Cmp(big.NewInt(0)) <= 0 { + return 1 + } + h := in.GetHash(uint64(number)) + p.WriteAt(h.Bytes(), int64(resultOffset)) + return 0 +} + +func callCommon(in *DVM, contract, targetContract *vm.Contract, input []byte, value *big.Int, snapshot int, gas int64, ro bool) int32 { + if in.depth > maxCallDepth { + return ErrEEICallFailure + } + + 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 *DVM, gas int64, addressOffset int32, valueOffset int32, dataOffset int32, dataLength int32) int32 { + 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. + // To be compatible with hera, one must read a u256 value, then check that this is a u128. + value := big.NewInt(0).SetBytes(swapEndian(readSize(p, valueOffset, u256Len))) + check128bits := big.NewInt(1) + check128bits.Lsh(check128bits, 128) + if value.Cmp(check128bits) > 0 { + return ErrEEICallFailure + } + + // Fail if the account's balance is greater than 128bits as discussed + // in https://github.com/ewasm/hera/issues/456 + if in.StateDB().GetBalance(contract.Address()).Cmp(check128bits) > 0 { + in.gasAccounting(contract.Gas) + return ErrEEICallRevert + } + + if in.staticMode && value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(in.contract.Gas) + return ErrEEICallFailure + } + + in.gasAccounting(GasCostCall) + + if in.depth > maxCallDepth { + return ErrEEICallFailure + } + + if value.Cmp(big.NewInt(0)) != 0 { + in.gasAccounting(GasCostCallValue) + } + + // 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(contract.Address()).Cmp(value) < 0 { + return ErrEEICallFailure + } + + // Check that the contract exists + if !in.StateDB().Exist(addr) { + in.gasAccounting(GasCostNewAccount) + in.StateDB().CreateAccount(addr) + } + + var calleeGas uint64 + if uint64(gas) > ((63 * contract.Gas) / 64) { + calleeGas = contract.Gas - (contract.Gas / 64) + } else { + calleeGas = uint64(gas) + } + in.gasAccounting(calleeGas) + + if value.Cmp(big.NewInt(0)) != 0 { + calleeGas += GasCostCallStipend + } + + // TODO tracing + + // Add amount to recipient + in.Transfer(in.StateDB(), contract.Address(), addr, value) + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(contract, vm.AccountRef(addr), value, calleeGas) + code := in.StateDB().GetCode(addr) + if len(code) == 0 { + in.contract.Gas += calleeGas + return EEICallSuccess + } + targetContract.SetCallCode(&addr, in.StateDB().GetCodeHash(addr), code) + + savedVM := in.vm + + in.run(targetContract, input, false) + + in.vm = savedVM + in.contract = contract + + // Add leftover gas + in.contract.Gas += targetContract.Gas + defer func() { in.terminationType = TerminateFinish }() + + 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 callDataCopy(p *exec.Process, in *DVM, 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 *DVM) int32 { + in.gasAccounting(GasCostBase) + return int32(len(in.contract.Input)) +} + +func callCode(p *exec.Process, in *DVM, 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 := vm.NewContract(vm.AccountRef(contract.Caller()), vm.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 *DVM, 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 := vm.NewContract(vm.AccountRef(contract.Address()), vm.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 *DVM, gas int64, addressOffset int32, dataOffset int32, dataLength int32) int32 { + 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() + + in.gasAccounting(GasCostCall) + + if in.depth > maxCallDepth { + return ErrEEICallFailure + } + + // Check that the contract exists + if !in.StateDB().Exist(addr) { + in.gasAccounting(GasCostNewAccount) + in.StateDB().CreateAccount(addr) + } + + calleeGas := uint64(gas) + if calleeGas > ((63 * contract.Gas) / 64) { + calleeGas -= ((63 * contract.Gas) / 64) + } + in.gasAccounting(calleeGas) + + // TODO tracing + + // Add amount to recipient + in.Transfer(in.StateDB(), contract.Address(), addr, value) + + // Load the contract code in a new VM structure + targetContract := vm.NewContract(contract, vm.AccountRef(addr), value, calleeGas) + code := in.StateDB().GetCode(addr) + if len(code) == 0 { + in.contract.Gas += calleeGas + return EEICallSuccess + } + targetContract.SetCallCode(&addr, in.StateDB().GetCodeHash(addr), code) + + savedVM := in.vm + saveStatic := in.staticMode + in.staticMode = true + defer func() { in.staticMode = saveStatic }() + + in.run(targetContract, input, false) + + in.vm = savedVM + in.contract = contract + + // Add leftover gas + in.contract.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 storageStore(p *exec.Process, interpreter *DVM, pathOffset int32, valueOffset int32) { + if interpreter.staticMode { + panic("Static mode violation in storageStore") + } + + loc := common.BytesToHash(readSize(p, pathOffset, u256Len)) + val := common.BytesToHash(readSize(p, valueOffset, u256Len)) + + 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 *DVM, pathOffset int32, resultOffset int32) { + interpreter.gasAccounting(interpreter.gasTable.SLoad) + loc := common.BytesToHash(readSize(p, pathOffset, u256Len)) + valBytes := interpreter.StateDB().GetState(interpreter.contract.Address(), loc).Bytes() + p.WriteAt(valBytes, int64(resultOffset)) +} + +func getCaller(p *exec.Process, in *DVM, resultOffset int32) { + callerAddress := in.contract.CallerAddress + in.gasAccounting(GasCostBase) + p.WriteAt(callerAddress.Bytes(), int64(resultOffset)) +} + +func getCallValue(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(swapEndian(in.contract.Value.Bytes()), int64(resultOffset)) +} + +func codeCopy(p *exec.Process, in *DVM, 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 *DVM) int32 { + in.gasAccounting(GasCostBase) + code := in.StateDB().GetCode(*in.contract.CodeAddr) + return int32(len(code)) +} + +func getBlockCoinbase(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.Coinbase.Bytes(), int64(resultOffset)) +} + +func sentinel(in *DVM, input []byte) ([]byte, uint64, error) { + savedContract := in.contract + savedVM := in.vm + defer func() { + in.contract = savedContract + in.vm = savedVM + }() + meteringContractAddress := common.HexToAddress(sentinelContractAddress) + meteringCode := in.StateDB().GetCode(meteringContractAddress) + in.contract = vm.NewContract(in.contract, vm.AccountRef(meteringContractAddress), &big.Int{}, in.contract.Gas) + in.contract.SetCallCode(&meteringContractAddress, crypto.Keccak256Hash(meteringCode), meteringCode) + vm, err := exec.NewVM(in.meteringModule) + vm.RecoverPanic = true + in.vm = vm + if err != nil { + panic(fmt.Sprintf("Error allocating metering VM: %v", err)) + } + in.contract.Input = input + meteredCode, err := in.vm.ExecCode(in.meteringStartIndex) + if meteredCode == nil { + meteredCode = in.returnData + } + + var asBytes []byte + if err == nil { + asBytes = meteredCode.([]byte) + } + + return asBytes, savedContract.Gas - in.contract.Gas, err +} + +func create(p *exec.Process, in *DVM, valueOffset uint32, codeOffset uint32, length uint32, resultOffset uint32) int32 { + in.gasAccounting(GasCostCreate) + savedVM := in.vm + savedContract := in.contract + defer func() { + in.vm = savedVM + in.contract = savedContract + }() + 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)) + + in.terminationType = TerminateFinish + + // 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) + + /* Meter the contract code if metering is enabled */ + if in.metering { + input, _, _ = sentinel(in, input) + if len(input) < 5 { + return ErrEEICallFailure + } + } + + _, addr, gasLeft, _ := in.Create(in.contract, input, gas, big.NewInt(0).SetBytes(value), in) + + switch in.terminationType { + case TerminateFinish: + savedContract.Gas += gasLeft + p.WriteAt(addr.Bytes(), int64(resultOffset)) + return EEICallSuccess + case TerminateRevert: + savedContract.Gas += gas + return ErrEEICallRevert + default: + savedContract.Gas += gasLeft + return ErrEEICallFailure + } +} + +func getBlockDifficulty(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(swapEndian(in.Difficulty.Bytes()), int64(resultOffset)) +} + +func externalCodeCopy(p *exec.Process, in *DVM, 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 *DVM, 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 *DVM) int64 { + in.gasAccounting(GasCostBase) + return int64(in.contract.Gas) +} + +func getBlockGasLimit(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return int64(in.GasLimit) +} + +func getTxGasPrice(p *exec.Process, in *DVM, valueOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.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 *DVM, 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(readSize(p, topic1, u256Len))) + } + if numberOfTopics > 1 { + if uint64(len(in.vm.Memory())) <= uint64(topic2) { + panic("out of memory") + } + topics[1] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic2, u256Len))) + } + if numberOfTopics > 2 { + if uint64(len(in.vm.Memory())) <= uint64(topic3) { + panic("out of memory") + } + topics[2] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic3, u256Len))) + } + if numberOfTopics > 3 { + if uint64(len(in.vm.Memory())) <= uint64(topic3) { + panic("out of memory") + } + topics[3] = common.BigToHash(big.NewInt(0).SetBytes(readSize(p, topic4, u256Len))) + } + + in.StateDB().AddLog(&types.Log{ + Address: in.contract.Address(), + Topics: topics, + Data: data, + BlockNumber: in.BlockNumber.Uint64(), + }) +} + +func getBlockNumber(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return in.BlockNumber.Int64() +} + +func getTxOrigin(p *exec.Process, in *DVM, resultOffset int32) { + in.gasAccounting(GasCostBase) + p.WriteAt(in.Origin.Big().Bytes(), int64(resultOffset)) +} + +func unWindContract(p *exec.Process, in *DVM, dataOffset int32, length int32) { + in.returnData = make([]byte, length) + p.ReadAt(in.returnData, int64(dataOffset)) +} + +func finish(p *exec.Process, in *DVM, dataOffset int32, length int32) { + unWindContract(p, in, dataOffset, length) + + in.terminationType = TerminateFinish + p.Terminate() +} + +func revert(p *exec.Process, in *DVM, dataOffset int32, length int32) { + unWindContract(p, in, dataOffset, length) + + in.terminationType = TerminateRevert + p.Terminate() +} + +func getReturnDataSize(p *exec.Process, in *DVM) int32 { + in.gasAccounting(GasCostBase) + return int32(len(in.returnData)) +} + +func returnDataCopy(p *exec.Process, in *DVM, 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 *DVM, addressOffset int32) { + contract := in.contract + mem := in.vm.Memory() + + balance := in.StateDB().GetBalance(contract.Address()) + + addr := common.BytesToAddress(mem[addressOffset : addressOffset+common.AddressLength]) + + 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().AddBalance(addr, balance) + in.StateDB().Suicide(contract.Address()) + + // Same as for `revert` and `return`, I need to forcefully terminate + // the execution of the contract. + in.terminationType = TerminateSuicide + p.Terminate() +} + +func getBlockTimestamp(p *exec.Process, in *DVM) int64 { + in.gasAccounting(GasCostBase) + return in.Time.Int64() +} diff --git a/core/vm/dvm/linker.go b/core/vm/dvm/linker.go new file mode 100644 index 000000000..eb57f8b79 --- /dev/null +++ b/core/vm/dvm/linker.go @@ -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 dvm + +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(dvm *DVM, name string) (*wasm.Module, error) { + if name == "debug" { + debugModule := wasm.NewModule() + debugModule.Types = eeiTypes + debugModule.FunctionIndexSpace = getDebugFuncs(dvm) + 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 != "ethereum" { + return nil, fmt.Errorf("Unknown module name: %s", name) + } + + m := wasm.NewModule() + m.Types = eeiTypes + m.FunctionIndexSpace = eeiFuncs(dvm) + + 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(dvm *DVM) wasm.ResolveFunc { + return func(name string) (*wasm.Module, error) { + return ModuleResolver(dvm, name) + } +} diff --git a/core/vm/vm.go b/core/vm/vm.go index 99ab2ed90..04e1b1520 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -9,6 +9,7 @@ import ( const ( EVM = byte(iota) SQLVM + DVM ) var ( @@ -93,7 +94,7 @@ func StaticCall(caller ContractRef, addr common.Address, input []byte, func getVMAndCode(code []byte) (byte, []byte) { if MULTIVM && len(code) > 0 { switch code[0] { - case EVM, SQLVM: + case EVM, SQLVM, DVM: return code[0], code[1:] default: return EVM, code diff --git a/vendor/github.com/go-interpreter/wagon/LICENSE b/vendor/github.com/go-interpreter/wagon/LICENSE new file mode 100644 index 000000000..e46a6de2c --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/LICENSE @@ -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. + diff --git a/vendor/github.com/go-interpreter/wagon/README.md b/vendor/github.com/go-interpreter/wagon/README.md new file mode 100644 index 000000000..3d2558e3d --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/README.md @@ -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`. diff --git a/vendor/github.com/go-interpreter/wagon/disasm/asm.go b/vendor/github.com/go-interpreter/wagon/disasm/asm.go new file mode 100644 index 000000000..2f888c724 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/disasm/asm.go @@ -0,0 +1,63 @@ +// 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" + "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.Immediates[0].(wasm.BlockType))) + 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 +} diff --git a/vendor/github.com/go-interpreter/wagon/disasm/disasm.go b/vendor/github.com/go-interpreter/wagon/disasm/disasm.go new file mode 100644 index 000000000..dffe23732 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/disasm/disasm.go @@ -0,0 +1,492 @@ +// 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") + +// NewDisassembly disassembles the given function. It also takes the function's +// parent module as an argument for locating any other functions referenced by +// fn. +func NewDisassembly(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { + code := fn.Body.Code + instrs, err := Disassemble(code) + if err != nil { + return nil, err + } + 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 _, instr := range instrs { + logger.Printf("stack top is %d", stackDepths.Top()) + opStr := instr.Op + op := opStr.Code + 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(stackDepths.Top()) + blockIndices.Push(uint64(curIndex)) + if !instr.Unreachable { + blockPolymorphicOps = append(blockPolymorphicOps, []int{}) + } + } + + case ops.Block, ops.Loop, ops.If: + sig := uint32(instr.Immediates[0].(wasm.BlockType)) + 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)) + case ops.Br, ops.BrIf: + depth := instr.Immediates[0].(uint32) + 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 := instr.Immediates[0].(uint32) + for i := uint32(0); i < targetCount; i++ { + entry := instr.Immediates[i+1].(uint32) + + 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 := instr.Immediates[targetCount+1].(uint32) + + 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 := instr.Immediates[0].(uint32) + 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: + 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 + } + } + } + + 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 +} + +// Disassemble disassembles a given function body into a set of instructions. It won't check operations for validity. +func Disassemble(code []byte) ([]Instr, error) { + reader := bytes.NewReader(code) + var out []Instr + for { + op, err := reader.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return nil, err + } + + opStr, err := ops.New(op) + if err != nil { + return nil, err + } + instr := Instr{ + Op: opStr, + } + + switch op { + case ops.Block, ops.Loop, ops.If: + sig, err := leb128.ReadVarint32(reader) + if err != nil { + return nil, err + } + 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) + case ops.BrTable: + 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) + } + + defaultTarget, err := leb128.ReadVarUint32(reader) + if err != nil { + return nil, err + } + instr.Immediates = append(instr.Immediates, defaultTarget) + 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) + } + 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) + 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)) + } + out = append(out, instr) + } + return out, nil +} diff --git a/vendor/github.com/go-interpreter/wagon/disasm/log.go b/vendor/github.com/go-interpreter/wagon/disasm/log.go new file mode 100644 index 000000000..4b6912ccf --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/disasm/log.go @@ -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) +} diff --git a/vendor/github.com/go-interpreter/wagon/doc.go b/vendor/github.com/go-interpreter/wagon/doc.go new file mode 100644 index 000000000..cdcbfc75c --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/doc.go @@ -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 diff --git a/vendor/github.com/go-interpreter/wagon/exec/call.go b/vendor/github.com/go-interpreter/wagon/exec/call.go new file mode 100644 index 000000000..0250364e3 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/call.go @@ -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)) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/const.go b/vendor/github.com/go-interpreter/wagon/exec/const.go new file mode 100644 index 000000000..08ebe2ba5 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/const.go @@ -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()) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/control.go b/vendor/github.com/go-interpreter/wagon/exec/control.go new file mode 100644 index 000000000..89545a3c6 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/control.go @@ -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() {} diff --git a/vendor/github.com/go-interpreter/wagon/exec/conv.go b/vendor/github.com/go-interpreter/wagon/exec/conv.go new file mode 100644 index 000000000..ce7e704a8 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/conv.go @@ -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())) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/func.go b/vendor/github.com/go-interpreter/wagon/exec/func.go new file mode 100644 index 000000000..3aa5974af --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/func.go @@ -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) + } +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/func_table.go b/vendor/github.com/go-interpreter/wagon/exec/func_table.go new file mode 100644 index 000000000..d265671b8 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/func_table.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/internal/compile/compile.go b/vendor/github.com/go-interpreter/wagon/exec/internal/compile/compile.go new file mode 100644 index 000000000..3441cbe3a --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/internal/compile/compile.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/memory.go b/vendor/github.com/go-interpreter/wagon/exec/memory.go new file mode 100644 index 000000000..cc8ab7454 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/memory.go @@ -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)) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/num.go b/vendor/github.com/go-interpreter/wagon/exec/num.go new file mode 100644 index 000000000..b069d0cf3 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/num.go @@ -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) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/parametric.go b/vendor/github.com/go-interpreter/wagon/exec/parametric.go new file mode 100644 index 000000000..f29c63c91 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/parametric.go @@ -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) + } +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/reinterp.go b/vendor/github.com/go-interpreter/wagon/exec/reinterp.go new file mode 100644 index 000000000..888c02589 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/reinterp.go @@ -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())) +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/var.go b/vendor/github.com/go-interpreter/wagon/exec/var.go new file mode 100644 index 000000000..fe761984d --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/var.go @@ -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() +} diff --git a/vendor/github.com/go-interpreter/wagon/exec/vm.go b/vendor/github.com/go-interpreter/wagon/exec/vm.go new file mode 100644 index 000000000..04b56adc1 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/exec/vm.go @@ -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.NewDisassembly(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 +} diff --git a/vendor/github.com/go-interpreter/wagon/go.mod b/vendor/github.com/go-interpreter/wagon/go.mod new file mode 100644 index 000000000..280ba086d --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/go.mod @@ -0,0 +1 @@ +module github.com/go-interpreter/wagon diff --git a/vendor/github.com/go-interpreter/wagon/internal/stack/stack.go b/vendor/github.com/go-interpreter/wagon/internal/stack/stack.go new file mode 100644 index 000000000..4481da022 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/internal/stack/stack.go @@ -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) +} diff --git a/vendor/github.com/go-interpreter/wagon/validate/error.go b/vendor/github.com/go-interpreter/wagon/validate/error.go new file mode 100644 index 000000000..3bbe1eb2b --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/validate/error.go @@ -0,0 +1,74 @@ +// 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 validate + +import ( + "errors" + "fmt" + + "github.com/go-interpreter/wagon/wasm" + ops "github.com/go-interpreter/wagon/wasm/operators" +) + +type Error struct { + Offset int // Byte offset in the bytecode vector where the error occurs. + Function int // Index into the function index space for the offending function. + Err error +} + +func (e Error) Error() string { + return fmt.Sprintf("error while validating function %d at offset %d: %v", e.Function, e.Offset, e.Err) +} + +var ErrStackUnderflow = errors.New("validate: stack underflow") + +type InvalidImmediateError struct { + ImmType string + OpName string +} + +func (e InvalidImmediateError) Error() string { + return fmt.Sprintf("invalid immediate for op %s at (should be %s)", e.OpName, e.ImmType) +} + +type UnmatchedOpError byte + +func (e UnmatchedOpError) Error() string { + n1, _ := ops.New(byte(e)) + return fmt.Sprintf("encountered unmatched %s", n1.Name) +} + +type InvalidLabelError uint32 + +func (e InvalidLabelError) Error() string { + return fmt.Sprintf("invalid nesting depth %d", uint32(e)) +} + +type InvalidLocalIndexError uint32 + +func (e InvalidLocalIndexError) Error() string { + return fmt.Sprintf("invalid index for local variable %d", uint32(e)) +} + +type InvalidTypeError struct { + Wanted wasm.ValueType + Got wasm.ValueType +} + +func (e InvalidTypeError) Error() string { + return fmt.Sprintf("invalid type, got: %v, wanted: %v", e.Got, e.Wanted) +} + +type InvalidElementIndexError uint32 + +func (e InvalidElementIndexError) Error() string { + return fmt.Sprintf("invalid element index %d", uint32(e)) +} + +type NoSectionError wasm.SectionID + +func (e NoSectionError) Error() string { + return fmt.Sprintf("reference to non existant section (id %d) in module", wasm.SectionID(e)) +} diff --git a/vendor/github.com/go-interpreter/wagon/validate/log.go b/vendor/github.com/go-interpreter/wagon/validate/log.go new file mode 100644 index 000000000..447166624 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/validate/log.go @@ -0,0 +1,26 @@ +// 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 validate + +import ( + "io/ioutil" + "log" + "os" +) + +var PrintDebugInfo = false + +var logger *log.Logger + +func init() { + w := ioutil.Discard + + if PrintDebugInfo { + w = os.Stderr + } + + logger = log.New(w, "", log.Lshortfile) + log.SetFlags(log.Lshortfile) +} diff --git a/vendor/github.com/go-interpreter/wagon/validate/operand.go b/vendor/github.com/go-interpreter/wagon/validate/operand.go new file mode 100644 index 000000000..6b951211c --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/validate/operand.go @@ -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 validate + +import ( + "github.com/go-interpreter/wagon/wasm" +) + +type operand struct { + Type wasm.ValueType +} diff --git a/vendor/github.com/go-interpreter/wagon/validate/validate.go b/vendor/github.com/go-interpreter/wagon/validate/validate.go new file mode 100644 index 000000000..dac219bb1 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/validate/validate.go @@ -0,0 +1,374 @@ +// 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 validate provides functions for validating WebAssembly modules. +package validate + +import ( + "bytes" + "io" + + "github.com/go-interpreter/wagon/wasm" + ops "github.com/go-interpreter/wagon/wasm/operators" +) + +// vibhavp: TODO: We do not verify whether blocks don't access for the parent block, do that. +func verifyBody(fn *wasm.FunctionSig, body *wasm.FunctionBody, module *wasm.Module) (*mockVM, error) { + vm := &mockVM{ + stack: []operand{}, + stackTop: 0, + + code: bytes.NewReader(body.Code), + origLength: len(body.Code), + + polymorphic: false, + blocks: []block{}, + curFunc: fn, + } + + localVariables := []operand{} + + // Paramters count as local variables too + // This comment explains how local variables work: https://github.com/WebAssembly/design/issues/1037#issuecomment-293505798 + for _, entry := range fn.ParamTypes { + localVariables = append(localVariables, operand{entry}) + } + + for _, entry := range body.Locals { + vars := make([]operand, entry.Count) + for i := uint32(0); i < entry.Count; i++ { + vars[i].Type = entry.Type + logger.Printf("Var %v", entry.Type) + } + localVariables = append(localVariables, vars...) + } + + for { + op, err := vm.code.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return vm, err + } + + opStruct, err := ops.New(op) + if err != nil { + return vm, err + } + + logger.Printf("PC: %d OP: %s polymorphic: %v", vm.pc(), opStruct.Name, vm.isPolymorphic()) + + if !opStruct.Polymorphic { + if err := vm.adjustStack(opStruct); err != nil { + return vm, err + } + } + + switch op { + case ops.If, ops.Block, ops.Loop: + sig, err := vm.fetchVarInt() + if err != nil { + return vm, err + } + + switch wasm.ValueType(sig) { + case wasm.ValueTypeI32, wasm.ValueTypeI64, wasm.ValueTypeF32, wasm.ValueTypeF64, wasm.ValueType(wasm.BlockTypeEmpty): + vm.pushBlock(op, wasm.BlockType(sig)) + default: + if !vm.isPolymorphic() { + return vm, InvalidImmediateError{"block_type", opStruct.Name} + } + } + + case ops.Else: + block := vm.topBlock() + if block == nil || block.op != ops.If { + return vm, UnmatchedOpError(op) + } + + if block.blockType != wasm.BlockTypeEmpty { + top, under := vm.topOperand() + if !vm.isPolymorphic() && (under || top.Type != wasm.ValueType(block.blockType)) { + return vm, InvalidTypeError{wasm.ValueType(block.blockType), top.Type} + } + vm.pushOperand(wasm.ValueType(block.blockType)) + } + vm.stackTop = block.stackTop + case ops.End: + isPolymorphic := vm.isPolymorphic() + + block := vm.popBlock() + if block == nil { + return vm, UnmatchedOpError(op) + } + + if block.blockType != wasm.BlockTypeEmpty { + top, under := vm.topOperand() + if !isPolymorphic && (under || top.Type != wasm.ValueType(block.blockType)) { + return vm, InvalidTypeError{wasm.ValueType(block.blockType), top.Type} + } + vm.stackTop = block.stackTop + vm.pushOperand(wasm.ValueType(block.blockType)) + vm.stackTop = block.stackTop + 1 // as we pushed an element + } else { + vm.stackTop = block.stackTop + } + + case ops.BrIf, ops.Br: + depth, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + if err = vm.canBranch(int(depth)); !vm.isPolymorphic() && err != nil { + return vm, err + } + if op == ops.Br { + vm.setPolymorphic() + } + case ops.BrTable: + operand, under := vm.popOperand() + if !vm.isPolymorphic() && (under || operand.Type != wasm.ValueTypeI32) { + return vm, InvalidTypeError{wasm.ValueTypeI32, operand.Type} + } + // read table entries + targetCount, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + + var targetTable []uint32 + for i := uint32(0); i < targetCount; i++ { + entry, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + if err = vm.canBranch(int(entry)); !vm.isPolymorphic() && err != nil { + return vm, err + } + targetTable = append(targetTable, entry) + } + + defaultTarget, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + if err = vm.canBranch(int(defaultTarget)); !vm.isPolymorphic() && err != nil { + return vm, err + } + vm.setPolymorphic() + + case ops.Return: + if len(fn.ReturnTypes) > 1 { + panic("not implemented") + } + if len(fn.ReturnTypes) != 0 { + // only single returns supported for now + top, under := vm.popOperand() + if !vm.isPolymorphic() && (under || top.Type != fn.ReturnTypes[0]) { + return vm, InvalidTypeError{fn.ReturnTypes[0], top.Type} + } + } + vm.setPolymorphic() + + case ops.Unreachable: + vm.setPolymorphic() + + case ops.I32Const: + _, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + case ops.I64Const: + _, err := vm.fetchVarInt64() + if err != nil { + return vm, err + } + case ops.F32Const: + _, err := vm.fetchUint32() + if err != nil { + return vm, err + } + case ops.F64Const: + _, err := vm.fetchUint64() + if err != nil { + return vm, err + } + case ops.GetLocal, ops.SetLocal, ops.TeeLocal: + i, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + if int(i) >= len(localVariables) { + return vm, InvalidLocalIndexError(i) + } + + v := localVariables[i] + + if op == ops.GetLocal { + vm.pushOperand(v.Type) + } else { // == set_local or tee_local + top, under := vm.popOperand() + if !vm.isPolymorphic() && (under || top.Type != v.Type) { + return vm, InvalidTypeError{v.Type, top.Type} + } + if op == ops.TeeLocal { + vm.pushOperand(v.Type) + } + } + + case ops.GetGlobal, ops.SetGlobal: + index, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + + gv := module.GetGlobal(int(index)) + if gv == nil { + return vm, wasm.InvalidGlobalIndexError(index) + } + if op == ops.GetGlobal { + vm.pushOperand(gv.Type.Type) + } else { + val, under := vm.popOperand() + if !vm.isPolymorphic() && (under || val.Type != gv.Type.Type) { + return vm, InvalidTypeError{gv.Type.Type, val.Type} + } + } + + 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 := vm.fetchVarUint() + if err != nil { + return vm, err + } + // offset + _, err = vm.fetchVarUint() + if err != nil { + return vm, err + } + case ops.CurrentMemory, ops.GrowMemory: + _, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + + case ops.Call: + index, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + + fn := module.GetFunction(int(index)) + if fn == nil { + return vm, wasm.InvalidFunctionIndexError(index) + } + + logger.Printf("Function being called: %v", fn) + for index := range fn.Sig.ParamTypes { + argType := fn.Sig.ParamTypes[len(fn.Sig.ParamTypes)-index-1] + operand, under := vm.popOperand() + if !vm.isPolymorphic() && (under || operand.Type != argType) { + return vm, InvalidTypeError{argType, operand.Type} + } + } + + if len(fn.Sig.ReturnTypes) > 0 { + vm.pushOperand(fn.Sig.ReturnTypes[0]) + } + + case ops.CallIndirect: + if module.Table == nil || len(module.Table.Entries) == 0 { + return vm, NoSectionError(wasm.SectionIDTable) + } + // The call_indirect process consists of getting two i32 values + // off (first from the bytecode stream, and the second from + // the stack) and using first as an index into the "Types" section + // of the module, while the the second one into the function index + // space. The signature of the two elements are then compared + // to see if they match, and the call proceeds as normal if they + // do. + // Since this is possible only during program execution, we only + // perform the static check for the function index mentioned + // in the bytecode stream here. + + // type index + index, err := vm.fetchVarUint() + if err != nil { + return vm, err + } + + fnExpectSig := module.Types.Entries[index] + + if operand, under := vm.popOperand(); !vm.isPolymorphic() && (under || operand.Type != wasm.ValueTypeI32) { + return vm, InvalidTypeError{wasm.ValueTypeI32, operand.Type} + } + + for index := range fnExpectSig.ParamTypes { + argType := fnExpectSig.ParamTypes[len(fnExpectSig.ParamTypes)-index-1] + operand, under := vm.popOperand() + if !vm.isPolymorphic() && (under || (operand.Type != argType)) { + return vm, InvalidTypeError{argType, operand.Type} + } + } + + if len(fnExpectSig.ReturnTypes) > 0 { + vm.pushOperand(fnExpectSig.ReturnTypes[0]) + } + + case ops.Drop: + if _, under := vm.popOperand(); !vm.isPolymorphic() && under { + return vm, ErrStackUnderflow + } + + case ops.Select: + if vm.isPolymorphic() { + continue + } + operands := make([]operand, 2) + c, under := vm.popOperand() + if under || c.Type != wasm.ValueTypeI32 { + return vm, InvalidTypeError{wasm.ValueTypeI32, c.Type} + } + + for i := 0; i < 2; i++ { + operand, under := vm.popOperand() + if !vm.isPolymorphic() && under { + return vm, ErrStackUnderflow + } + operands[i] = operand + } + + // last 2 popped values should be of the same type + if operands[0].Type != operands[1].Type { + return vm, InvalidTypeError{operands[1].Type, operands[2].Type} + } + + vm.pushOperand(operands[1].Type) + } + } + + return vm, nil +} + +// VerifyModule verifies the given module according to WebAssembly verification +// specs. +func VerifyModule(module *wasm.Module) error { + if module.Function == nil || module.Types == nil || len(module.Types.Entries) == 0 { + return nil + } + if module.Code == nil { + return NoSectionError(wasm.SectionIDCode) + } + + logger.Printf("There are %d functions", len(module.Function.Types)) + for i, fn := range module.FunctionIndexSpace { + if vm, err := verifyBody(fn.Sig, fn.Body, module); err != nil { + return Error{vm.pc(), i, err} + } + logger.Printf("No errors in function %d", i) + } + + return nil +} diff --git a/vendor/github.com/go-interpreter/wagon/validate/vm.go b/vendor/github.com/go-interpreter/wagon/validate/vm.go new file mode 100644 index 000000000..fec06068e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/validate/vm.go @@ -0,0 +1,222 @@ +// 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 validate + +import ( + "bytes" + "encoding/binary" + "io" + + "github.com/go-interpreter/wagon/wasm" + "github.com/go-interpreter/wagon/wasm/leb128" + ops "github.com/go-interpreter/wagon/wasm/operators" +) + +// mockVM is a minimal implementation of a virtual machine to +// validate WebAssembly code +type mockVM struct { + stack []operand + stackTop int // the top of the operand stack + origLength int // the original length of the bytecode stream + + code *bytes.Reader + + polymorphic bool // whether the base implict block has a polymorphic stack + blocks []block // a stack of encountered blocks + + curFunc *wasm.FunctionSig +} + +// a block reprsents an instruction sequence preceeded by a control flow operator +// it is used to verify that the block signature set by the operator is the correct +// one when the block ends +type block struct { + pc int // the pc where the control flow operator starting the block is located + stackTop int // stack top when the block started + blockType wasm.BlockType // block_type signature of the control operator + op byte // opcode for the operator starting the new block + polymorphic bool // whether the block has a polymorphic stack + loop bool // whether the block is the body of a loop instruction +} + +func (vm *mockVM) fetchVarUint() (uint32, error) { + return leb128.ReadVarUint32(vm.code) +} + +func (vm *mockVM) fetchVarInt() (int32, error) { + return leb128.ReadVarint32(vm.code) +} + +func (vm *mockVM) fetchVarInt64() (int64, error) { + return leb128.ReadVarint64(vm.code) +} + +func (vm *mockVM) fetchUint32() (uint32, error) { + var buf [4]byte + _, err := io.ReadFull(vm.code, buf[:]) + if err != nil { + return 0, err + } + return binary.LittleEndian.Uint32(buf[:]), nil +} + +func (vm *mockVM) fetchUint64() (uint64, error) { + var buf [8]byte + _, err := io.ReadFull(vm.code, buf[:]) + if err != nil { + return 0, err + } + return binary.LittleEndian.Uint64(buf[:]), nil +} + +func (vm *mockVM) pushBlock(op byte, blockType wasm.BlockType) { + logger.Printf("Pushing block %v", blockType) + vm.blocks = append(vm.blocks, block{ + pc: vm.pc(), + stackTop: vm.stackTop, + blockType: blockType, + polymorphic: vm.isPolymorphic(), + op: op, + loop: op == ops.Loop, + }) +} + +// Get a block from it's relative nesting depth +func (vm *mockVM) getBlockFromDepth(depth int) *block { + if depth >= len(vm.blocks) { + return nil + } + + return &vm.blocks[len(vm.blocks)-1-depth] +} + +// Returns nil if depth is a valid nesting depth value that can be +// branched to. +func (vm *mockVM) canBranch(depth int) error { + blockType := wasm.BlockTypeEmpty + + block := vm.getBlockFromDepth(depth) + // jumping to the start of a loop block doesn't push a value + // on the stack. + if block == nil { + if depth == len(vm.blocks) { + //equivalent to a `return', as the function + //body is an "implicit" block + if len(vm.curFunc.ReturnTypes) != 0 { + blockType = wasm.BlockType(vm.curFunc.ReturnTypes[0]) + } + } else { + return InvalidLabelError(uint32(depth)) + } + } else if !block.loop { + blockType = block.blockType + } + + if blockType != wasm.BlockTypeEmpty { + top, under := vm.topOperand() + if under || top.Type != wasm.ValueType(blockType) { + return InvalidTypeError{wasm.ValueType(blockType), top.Type} + } + } + + return nil +} + +// returns nil in case of an underflow +func (vm *mockVM) popBlock() *block { + if len(vm.blocks) == 0 { + return nil + } + + stackTop := len(vm.blocks) - 1 + block := vm.blocks[stackTop] + vm.blocks = append(vm.blocks[:stackTop], vm.blocks[stackTop+1:]...) + + return &block +} + +func (vm *mockVM) topBlock() *block { + if len(vm.blocks) == 0 { + return nil + } + + return &vm.blocks[len(vm.blocks)-1] +} + +func (vm *mockVM) topOperand() (o operand, under bool) { + stackTop := vm.stackTop - 1 + if stackTop == -1 { + under = true + return + } + o = vm.stack[stackTop] + return +} + +func (vm *mockVM) popOperand() (operand, bool) { + var o operand + stackTop := vm.stackTop - 1 + if stackTop == -1 { + return o, true + } + o = vm.stack[stackTop] + vm.stackTop-- + + logger.Printf("Stack after pop is %v. Popped %v", vm.stack[:vm.stackTop], o) + return o, false +} + +func (vm *mockVM) pushOperand(t wasm.ValueType) { + o := operand{t} + logger.Printf("Stack top: %d, Len of stack :%d", vm.stackTop, len(vm.stack)) + if vm.stackTop == len(vm.stack) { + vm.stack = append(vm.stack, o) + } else { + vm.stack[vm.stackTop] = o + } + vm.stackTop++ + + logger.Printf("Stack after push is %v. Pushed %v", vm.stack[:vm.stackTop], o) +} + +func (vm *mockVM) adjustStack(op ops.Op) error { + for _, t := range op.Args { + op, under := vm.popOperand() + if !vm.isPolymorphic() && (under || op.Type != t) { + return InvalidTypeError{t, op.Type} + } + } + + if op.Returns != wasm.ValueType(wasm.BlockTypeEmpty) { + vm.pushOperand(op.Returns) + } + + return nil +} + +// setPolymorphic sets the current block as having a polymorphic stack +// blocks created under it will be polymorphic too. All type-checking +// is ignored in a polymorhpic stack. +// (See https://github.com/WebAssembly/design/blob/27ac254c854994103c24834a994be16f74f54186/Semantics.md#validation) +func (vm *mockVM) setPolymorphic() { + if len(vm.blocks) == 0 { + vm.polymorphic = true + } else { + + vm.blocks[len(vm.blocks)-1].polymorphic = true + } +} + +func (vm *mockVM) isPolymorphic() bool { + if len(vm.blocks) == 0 { + return vm.polymorphic + } + + return vm.topBlock().polymorphic +} + +func (vm *mockVM) pc() int { + return vm.origLength - vm.code.Len() +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/doc.go b/vendor/github.com/go-interpreter/wagon/wasm/doc.go new file mode 100644 index 000000000..c782f9b6e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/doc.go @@ -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 diff --git a/vendor/github.com/go-interpreter/wagon/wasm/encode.go b/vendor/github.com/go-interpreter/wagon/wasm/encode.go new file mode 100644 index 000000000..be54869e8 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/encode.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/imports.go b/vendor/github.com/go-interpreter/wagon/wasm/imports.go new file mode 100644 index 000000000..95c2db04e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/imports.go @@ -0,0 +1,226 @@ +// 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)) +} + +// InvalidImportError is returned when the export of a resolved module doesn't +// match the signature of its import declaration. +type InvalidImportError struct { + ModuleName string + FieldName string + TypeIndex uint32 +} + +func (e InvalidImportError) Error() string { + return fmt.Sprintf("wasm: invalid signature for import %#x with name '%s' in module %s", e.TypeIndex, e.FieldName, e.ModuleName) +} + +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) + } + + importIndex := importEntry.Type.(FuncImport).Type + if len(fn.Sig.ReturnTypes) != len(module.Types.Entries[importIndex].ReturnTypes) || len(fn.Sig.ParamTypes) != len(module.Types.Entries[importIndex].ParamTypes) { + return InvalidImportError{importEntry.ModuleName, importEntry.FieldName, importIndex} + } + for i, typ := range fn.Sig.ReturnTypes { + if typ != module.Types.Entries[importIndex].ReturnTypes[i] { + return InvalidImportError{importEntry.ModuleName, importEntry.FieldName, importIndex} + } + } + for i, typ := range fn.Sig.ParamTypes { + if typ != module.Types.Entries[importIndex].ParamTypes[i] { + return InvalidImportError{importEntry.ModuleName, importEntry.FieldName, importIndex} + } + } + 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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/index.go b/vendor/github.com/go-interpreter/wagon/wasm/index.go new file mode 100644 index 000000000..d2b22ec0f --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/index.go @@ -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, memory) + copy(data[offset:], entry.Data) + 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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/init_expr.go b/vendor/github.com/go-interpreter/wagon/wasm/init_expr.go new file mode 100644 index 000000000..3a59fb3a3 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/init_expr.go @@ -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))) + } +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/internal/readpos/readpos.go b/vendor/github.com/go-interpreter/wagon/wasm/internal/readpos/readpos.go new file mode 100644 index 000000000..709fbe18f --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/internal/readpos/readpos.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/leb128/read.go b/vendor/github.com/go-interpreter/wagon/wasm/leb128/read.go new file mode 100644 index 000000000..2584da37e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/leb128/read.go @@ -0,0 +1,72 @@ +// 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" +) + +// 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) { + var ( + b = make([]byte, 1) + shift uint + res uint32 + err error + ) + for { + if _, err = io.ReadFull(r, b); err != nil { + return res, err + } + + cur := uint32(b[0]) + res |= (cur & 0x7f) << (shift) + if cur&0x80 == 0 { + return res, nil + } + shift += 7 + } +} + +// 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 := ReadVarint64(r) + return int32(n), err +} + +// 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) { + var ( + b = make([]byte, 1) + shift uint + sign int64 = -1 + res int64 + err error + ) + + for { + if _, err = io.ReadFull(r, b); err != nil { + return res, err + } + + 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, nil +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/leb128/write.go b/vendor/github.com/go-interpreter/wagon/wasm/leb128/write.go new file mode 100644 index 000000000..9e96cb0f5 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/leb128/write.go @@ -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) +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/log.go b/vendor/github.com/go-interpreter/wagon/wasm/log.go new file mode 100644 index 000000000..bbc9d938c --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/log.go @@ -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) +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/module.go b/vendor/github.com/go-interpreter/wagon/wasm/module.go new file mode 100644 index 000000000..b17e50a53 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/module.go @@ -0,0 +1,167 @@ +// 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 { + if m.Code == nil { + m.Code = &SectionCode{} + } + + 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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/call.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/call.go new file mode 100644 index 000000000..bd4c3399a --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/call.go @@ -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") +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/comp.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/comp.go new file mode 100644 index 000000000..850e7f264 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/comp.go @@ -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) +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/const.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/const.go new file mode 100644 index 000000000..8bbc0367f --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/const.go @@ -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) +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/control.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/control.go new file mode 100644 index 000000000..b37822773 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/control.go @@ -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") +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/conv.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/conv.go new file mode 100644 index 000000000..7c252fe3a --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/conv.go @@ -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") +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/memory.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/memory.go new file mode 100644 index 000000000..f0a53cb5e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/memory.go @@ -0,0 +1,41 @@ +// 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) + + // TODO: rename operations accordingly + + CurrentMemory = newOp(0x3f, "memory.size", nil, wasm.ValueTypeI32) + GrowMemory = newOp(0x40, "memory.grow", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/num.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/num.go new file mode 100644 index 000000000..4f604dd33 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/num.go @@ -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.rem_s", []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) +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/op.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/op.go new file mode 100644 index 000000000..9a8c263ad --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/op.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/parametric.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/parametric.go new file mode 100644 index 000000000..ded32da3e --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/parametric.go @@ -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") +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/reinterp.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/reinterp.go new file mode 100644 index 000000000..1b4dd377f --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/reinterp.go @@ -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) +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/operators/var.go b/vendor/github.com/go-interpreter/wagon/wasm/operators/var.go new file mode 100644 index 000000000..b3cb54937 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/operators/var.go @@ -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") +) diff --git a/vendor/github.com/go-interpreter/wagon/wasm/read.go b/vendor/github.com/go-interpreter/wagon/wasm/read.go new file mode 100644 index 000000000..4625b05fd --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/read.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/section.go b/vendor/github.com/go-interpreter/wagon/wasm/section.go new file mode 100644 index 000000000..ac6acafc1 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/section.go @@ -0,0 +1,1205 @@ +// 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" + "io/ioutil" + "sort" + + "github.com/go-interpreter/wagon/wasm/internal/readpos" + "github.com/go-interpreter/wagon/wasm/leb128" +) + +// Section is a generic WASM section interface. +type Section interface { + // SectionID returns a section ID for WASM encoding. Should be unique across types. + SectionID() SectionID + // GetRawSection Returns an embedded RawSection pointer to populate generic fields. + GetRawSection() *RawSection + // ReadPayload reads a section payload, assuming the size was already read, and reader is limited to it. + ReadPayload(r io.Reader) error + // WritePayload writes a section payload without the size. + // Caller should calculate written size and add it before the payload. + WritePayload(w io.Writer) error +} + +// SectionID is a 1-byte code that encodes the section code of both known and custom sections. +type SectionID uint8 + +const ( + SectionIDCustom SectionID = 0 + SectionIDType SectionID = 1 + SectionIDImport SectionID = 2 + SectionIDFunction SectionID = 3 + SectionIDTable SectionID = 4 + SectionIDMemory SectionID = 5 + SectionIDGlobal SectionID = 6 + SectionIDExport SectionID = 7 + SectionIDStart SectionID = 8 + SectionIDElement SectionID = 9 + SectionIDCode SectionID = 10 + SectionIDData SectionID = 11 +) + +func (s SectionID) String() string { + n, ok := map[SectionID]string{ + SectionIDCustom: "custom", + SectionIDType: "type", + SectionIDImport: "import", + SectionIDFunction: "function", + SectionIDTable: "table", + SectionIDMemory: "memory", + SectionIDGlobal: "global", + SectionIDExport: "export", + SectionIDStart: "start", + SectionIDElement: "element", + SectionIDCode: "code", + SectionIDData: "data", + }[s] + if !ok { + return "unknown" + } + return n +} + +// RawSection is a declared section in a WASM module. +type RawSection struct { + Start int64 + End int64 + + ID SectionID + Bytes []byte +} + +func (s *RawSection) SectionID() SectionID { + return s.ID +} + +func (s *RawSection) GetRawSection() *RawSection { + return s +} + +type InvalidSectionIDError SectionID + +func (e InvalidSectionIDError) Error() string { + return fmt.Sprintf("wasm: invalid section ID %d", e) +} + +type InvalidCodeIndexError int + +func (e InvalidCodeIndexError) Error() string { + return fmt.Sprintf("wasm: invalid index to code section: %d", int(e)) +} + +var ErrUnsupportedSection = errors.New("wasm: unsupported section") + +type MissingSectionError SectionID + +func (e MissingSectionError) Error() string { + return fmt.Sprintf("wasm: missing section %s", SectionID(e).String()) +} + +// reads a valid section from r. The first return value is true if and only if +// the module has been completely read. +func (m *Module) readSection(r *readpos.ReadPos) (bool, error) { + var err error + var id uint32 + + logger.Println("Reading section ID") + id, err = leb128.ReadVarUint32(r) + if err == io.EOF { + return true, nil + } else if err != nil { + return false, err + } + s := RawSection{ID: SectionID(id)} + + logger.Println("Reading payload length") + + payloadDataLen, err := leb128.ReadVarUint32(r) + if err != nil { + return false, err + } + + logger.Printf("Section payload length: %d", payloadDataLen) + + s.Start = r.CurPos + + sectionBytes := new(bytes.Buffer) + sectionBytes.Grow(int(payloadDataLen)) + sectionReader := io.LimitReader(io.TeeReader(r, sectionBytes), int64(payloadDataLen)) + + var sec Section + switch s.ID { + case SectionIDCustom: + logger.Println("section custom") + cs := &SectionCustom{} + m.Customs = append(m.Customs, cs) + sec = cs + case SectionIDType: + logger.Println("section type") + m.Types = &SectionTypes{} + sec = m.Types + case SectionIDImport: + logger.Println("section import") + m.Import = &SectionImports{} + sec = m.Import + case SectionIDFunction: + logger.Println("section function") + m.Function = &SectionFunctions{} + sec = m.Function + case SectionIDTable: + logger.Println("section table") + m.Table = &SectionTables{} + sec = m.Table + case SectionIDMemory: + logger.Println("section memory") + m.Memory = &SectionMemories{} + sec = m.Memory + case SectionIDGlobal: + logger.Println("section global") + m.Global = &SectionGlobals{} + sec = m.Global + case SectionIDExport: + logger.Println("section export") + m.Export = &SectionExports{} + sec = m.Export + case SectionIDStart: + logger.Println("section start") + m.Start = &SectionStartFunction{} + sec = m.Start + case SectionIDElement: + logger.Println("section element") + m.Elements = &SectionElements{} + sec = m.Elements + case SectionIDCode: + logger.Println("section code") + m.Code = &SectionCode{} + sec = m.Code + case SectionIDData: + logger.Println("section data") + m.Data = &SectionData{} + sec = m.Data + default: + return false, InvalidSectionIDError(s.ID) + } + err = sec.ReadPayload(sectionReader) + if err != nil { + logger.Println(err) + return false, err + } + s.End = r.CurPos + s.Bytes = sectionBytes.Bytes() + *sec.GetRawSection() = s + switch s.ID { + case SectionIDCode: + s := m.Code + if m.Function == nil || len(m.Function.Types) == 0 { + return false, MissingSectionError(SectionIDFunction) + } + if len(m.Function.Types) != len(s.Bodies) { + return false, errors.New("The number of entries in the function and code section are unequal") + } + if m.Types == nil { + return false, MissingSectionError(SectionIDType) + } + for i := range s.Bodies { + s.Bodies[i].Module = m + } + } + m.Sections = append(m.Sections, sec) + return false, nil +} + +var _ Section = (*SectionCustom)(nil) + +type SectionCustom struct { + RawSection + Name string + Data []byte +} + +func (s *SectionCustom) SectionID() SectionID { + return SectionIDCustom +} + +func (s *SectionCustom) ReadPayload(r io.Reader) error { + var err error + s.Name, err = readStringUint(r) + if err != nil { + return err + } + data, err := ioutil.ReadAll(r) + if err != nil { + return err + } + s.Data = data + return nil +} + +func (s *SectionCustom) WritePayload(w io.Writer) error { + if err := writeStringUint(w, s.Name); err != nil { + return err + } + _, err := w.Write(s.Data) + return err +} + +var _ Section = (*SectionTypes)(nil) + +// SectionTypes declares all function signatures that will be used in a module. +type SectionTypes struct { + RawSection + Entries []FunctionSig +} + +func (*SectionTypes) SectionID() SectionID { + return SectionIDType +} + +func (s *SectionTypes) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]FunctionSig, int(count)) + for i := range s.Entries { + if err = s.Entries[i].UnmarshalWASM(r); err != nil { + return err + } + } + return nil +} + +func (s *SectionTypes) WritePayload(w io.Writer) error { + _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))) + if err != nil { + return err + } + for _, f := range s.Entries { + if err = f.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +var _ Section = (*SectionImports)(nil) + +// SectionImports declares all imports that will be used in the module. +type SectionImports struct { + RawSection + Entries []ImportEntry +} + +func (*SectionImports) SectionID() SectionID { + return SectionIDImport +} + +func (s *SectionImports) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]ImportEntry, count) + for i := range s.Entries { + err = s.Entries[i].UnmarshalWASM(r) + if err != nil { + return err + } + } + return nil +} + +func (s *SectionImports) WritePayload(w io.Writer) error { + _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))) + if err != nil { + return err + } + for _, e := range s.Entries { + err = writeImportEntry(w, e) + if err != nil { + return err + } + } + return nil +} + +func (i *ImportEntry) UnmarshalWASM(r io.Reader) error { + var err error + i.ModuleName, err = readStringUint(r) + if err != nil { + return err + } + i.FieldName, err = readStringUint(r) + if err != nil { + return err + } + var kind External + err = kind.UnmarshalWASM(r) + if err != nil { + return err + } + + switch kind { + case ExternalFunction: + logger.Println("importing function") + var t uint32 + t, err = leb128.ReadVarUint32(r) + i.Type = FuncImport{t} + case ExternalTable: + logger.Println("importing table") + var table Table + + err = table.UnmarshalWASM(r) + if err == nil { + i.Type = TableImport{table} + } + case ExternalMemory: + logger.Println("importing memory") + var mem Memory + + err = mem.UnmarshalWASM(r) + if err == nil { + i.Type = MemoryImport{mem} + } + case ExternalGlobal: + logger.Println("importing global var") + var gl GlobalVar + + err = gl.UnmarshalWASM(r) + if err == nil { + i.Type = GlobalVarImport{gl} + } + default: + return InvalidExternalError(kind) + } + + return err +} + +func writeImportEntry(w io.Writer, i ImportEntry) error { + if err := writeStringUint(w, i.ModuleName); err != nil { + return err + } + if err := writeStringUint(w, i.FieldName); err != nil { + return err + } + if err := i.Type.Kind().MarshalWASM(w); err != nil { + return err + } + return i.Type.MarshalWASM(w) +} + +// SectionFunction declares the signature of all functions defined in the module (in the code section) +type SectionFunctions struct { + RawSection + // Sequences of indices into (FunctionSignatues).Entries + Types []uint32 +} + +func (*SectionFunctions) SectionID() SectionID { + return SectionIDFunction +} + +func (s *SectionFunctions) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Types = make([]uint32, count) + for i := range s.Types { + t, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Types[i] = t + } + return nil +} + +func (s *SectionFunctions) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Types))); err != nil { + return err + } + for _, t := range s.Types { + if _, err := leb128.WriteVarUint32(w, uint32(t)); err != nil { + return err + } + } + return nil +} + +// SectionTables describes all tables declared by a module. +type SectionTables struct { + RawSection + Entries []Table +} + +func (*SectionTables) SectionID() SectionID { + return SectionIDTable +} + +func (s *SectionTables) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]Table, count) + for i := range s.Entries { + err = s.Entries[i].UnmarshalWASM(r) + if err != nil { + return err + } + } + return nil +} + +func (s *SectionTables) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))); err != nil { + return err + } + for _, e := range s.Entries { + if err := e.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +// SectionMemories describes all linaer memories used by a module. +type SectionMemories struct { + RawSection + Entries []Memory +} + +func (*SectionMemories) SectionID() SectionID { + return SectionIDMemory +} + +func (s *SectionMemories) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]Memory, count) + for i := range s.Entries { + err = s.Entries[i].UnmarshalWASM(r) + if err != nil { + return err + } + } + return nil +} + +func (s *SectionMemories) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))); err != nil { + return err + } + for _, e := range s.Entries { + if err := e.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +// SectionGlobals defines the value of all global variables declared in a module. +type SectionGlobals struct { + RawSection + Globals []GlobalEntry +} + +func (*SectionGlobals) SectionID() SectionID { + return SectionIDGlobal +} + +func (s *SectionGlobals) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Globals = make([]GlobalEntry, count) + logger.Printf("%d global entries\n", count) + for i := range s.Globals { + err = s.Globals[i].UnmarshalWASM(r) + if err != nil { + return err + } + } + return nil +} + +func (s *SectionGlobals) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Globals))); err != nil { + return err + } + for _, g := range s.Globals { + if err := g.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +// GlobalEntry declares a global variable. +type GlobalEntry struct { + Type GlobalVar // Type holds information about the value type and mutability of the variable + Init []byte // Init is an initializer expression that computes the initial value of the variable +} + +func (g *GlobalEntry) UnmarshalWASM(r io.Reader) error { + err := g.Type.UnmarshalWASM(r) + if err != nil { + return err + } + + // init_expr is delimited by opcode "end" (0x0b) + g.Init, err = readInitExpr(r) + return err +} + +func (g *GlobalEntry) MarshalWASM(w io.Writer) error { + if err := g.Type.MarshalWASM(w); err != nil { + return err + } + _, err := w.Write(g.Init) + return err +} + +// SectionExports declares the export section of a module +type SectionExports struct { + RawSection + Entries map[string]ExportEntry + Names []string +} + +func (*SectionExports) SectionID() SectionID { + return SectionIDExport +} + +func (s *SectionExports) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make(map[string]ExportEntry, count) + for i := uint32(0); i < count; i++ { + var entry ExportEntry + err = entry.UnmarshalWASM(r) + if err != nil { + return err + } + + if _, exists := s.Entries[entry.FieldStr]; exists { + return DuplicateExportError(entry.FieldStr) + } + s.Entries[entry.FieldStr] = entry + s.Names = append(s.Names, entry.FieldStr) + } + return nil +} + +func (s *SectionExports) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))); err != nil { + return err + } + entries := make([]ExportEntry, 0, len(s.Entries)) + for _, e := range s.Entries { + entries = append(entries, e) + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].Index < entries[j].Index + }) + for _, e := range entries { + if err := e.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +type DuplicateExportError string + +func (e DuplicateExportError) Error() string { + return fmt.Sprintf("Duplicate export entry: %s", e) +} + +// ExportEntry represents an exported entry by the module +type ExportEntry struct { + FieldStr string + Kind External + Index uint32 +} + +func (e *ExportEntry) UnmarshalWASM(r io.Reader) error { + var err error + e.FieldStr, err = readStringUint(r) + if err != nil { + return err + } + + if err := e.Kind.UnmarshalWASM(r); err != nil { + return err + } + + e.Index, err = leb128.ReadVarUint32(r) + + return err +} + +func (e *ExportEntry) MarshalWASM(w io.Writer) error { + if err := writeStringUint(w, e.FieldStr); err != nil { + return err + } + if err := e.Kind.MarshalWASM(w); err != nil { + return err + } + if _, err := leb128.WriteVarUint32(w, e.Index); err != nil { + return err + } + return nil +} + +// SectionStartFunction represents the start function section. +type SectionStartFunction struct { + RawSection + Index uint32 // The index of the start function into the global index space. +} + +func (*SectionStartFunction) SectionID() SectionID { + return SectionIDStart +} + +func (s *SectionStartFunction) ReadPayload(r io.Reader) error { + var err error + s.Index, err = leb128.ReadVarUint32(r) + return err +} + +func (s *SectionStartFunction) WritePayload(w io.Writer) error { + _, err := leb128.WriteVarUint32(w, s.Index) + return err +} + +// SectionElements describes the initial contents of a table's elements. +type SectionElements struct { + RawSection + Entries []ElementSegment +} + +func (*SectionElements) SectionID() SectionID { + return SectionIDElement +} + +func (s *SectionElements) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]ElementSegment, count) + for i := range s.Entries { + err = s.Entries[i].UnmarshalWASM(r) + if err != nil { + return err + } + } + return nil +} + +func (s *SectionElements) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))); err != nil { + return err + } + for _, e := range s.Entries { + if err := e.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +// ElementSegment describes a group of repeated elements that begin at a specified offset +type ElementSegment struct { + Index uint32 // The index into the global table space, should always be 0 in the MVP. + Offset []byte // initializer expression for computing the offset for placing elements, should return an i32 value + Elems []uint32 +} + +func (s *ElementSegment) UnmarshalWASM(r io.Reader) error { + var err error + + if s.Index, err = leb128.ReadVarUint32(r); err != nil { + return err + } + if s.Offset, err = readInitExpr(r); err != nil { + return err + } + + numElems, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Elems = make([]uint32, numElems) + + for i := range s.Elems { + e, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Elems[i] = e + } + + return nil +} + +func (s *ElementSegment) MarshalWASM(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, s.Index); err != nil { + return err + } + if _, err := w.Write(s.Offset); err != nil { + return err + } + + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Elems))); err != nil { + return err + } + for _, e := range s.Elems { + if _, err := leb128.WriteVarUint32(w, e); err != nil { + return err + } + } + return nil +} + +// SectionCode describes the body for every function declared inside a module. +type SectionCode struct { + RawSection + Bodies []FunctionBody +} + +func (*SectionCode) SectionID() SectionID { + return SectionIDCode +} + +func (s *SectionCode) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Bodies = make([]FunctionBody, count) + logger.Printf("%d function bodies\n", count) + + for i := range s.Bodies { + logger.Printf("Reading function %d\n", i) + if err = s.Bodies[i].UnmarshalWASM(r); err != nil { + return err + } + } + return nil +} + +func (s *SectionCode) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Bodies))); err != nil { + return err + } + for _, b := range s.Bodies { + if err := b.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +var ErrFunctionNoEnd = errors.New("Function body does not end with 0x0b (end)") + +type FunctionBody struct { + Module *Module // The parent module containing this function body, for execution purposes + Locals []LocalEntry + Code []byte +} + +func (f *FunctionBody) UnmarshalWASM(r io.Reader) error { + + bodySize, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + + body := make([]byte, bodySize) + + if _, err = io.ReadFull(r, body); err != nil { + return err + } + + bytesReader := bytes.NewBuffer(body) + + localCount, err := leb128.ReadVarUint32(bytesReader) + if err != nil { + return err + } + f.Locals = make([]LocalEntry, localCount) + + for i := range f.Locals { + if err = f.Locals[i].UnmarshalWASM(bytesReader); err != nil { + return err + } + } + + logger.Printf("bodySize: %d, localCount: %d\n", bodySize, localCount) + + code := bytesReader.Bytes() + logger.Printf("Read %d bytes for function body", len(code)) + + if code[len(code)-1] != end { + return ErrFunctionNoEnd + } + + f.Code = code[:len(code)-1] + + return nil +} + +func (f *FunctionBody) MarshalWASM(w io.Writer) error { + body := new(bytes.Buffer) + if _, err := leb128.WriteVarUint32(body, uint32(len(f.Locals))); err != nil { + return err + } + for _, l := range f.Locals { + if err := l.MarshalWASM(body); err != nil { + return err + } + } + if _, err := body.Write(f.Code); err != nil { + return err + } + body.WriteByte(end) + return writeBytesUint(w, body.Bytes()) +} + +type LocalEntry struct { + Count uint32 // The total number of local variables of the given Type used in the function body + Type ValueType // The type of value stored by the variable +} + +func (l *LocalEntry) UnmarshalWASM(r io.Reader) error { + var err error + + l.Count, err = leb128.ReadVarUint32(r) + if err != nil { + return err + } + + err = l.Type.UnmarshalWASM(r) + if err != nil { + return err + } + + return nil +} + +func (l *LocalEntry) MarshalWASM(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, l.Count); err != nil { + return err + } + if err := l.Type.MarshalWASM(w); err != nil { + return err + } + return nil +} + +// SectionData describes the intial values of a module's linear memory +type SectionData struct { + RawSection + Entries []DataSegment +} + +func (*SectionData) SectionID() SectionID { + return SectionIDData +} + +func (s *SectionData) ReadPayload(r io.Reader) error { + count, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + s.Entries = make([]DataSegment, count) + for i := range s.Entries { + if err = s.Entries[i].UnmarshalWASM(r); err != nil { + return err + } + } + return nil +} + +func (s *SectionData) WritePayload(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, uint32(len(s.Entries))); err != nil { + return err + } + for _, e := range s.Entries { + if err := e.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +// DataSegment describes a group of repeated elements that begin at a specified offset in the linear memory +type DataSegment struct { + Index uint32 // The index into the global linear memory space, should always be 0 in the MVP. + Offset []byte // initializer expression for computing the offset for placing elements, should return an i32 value + Data []byte +} + +func (s *DataSegment) UnmarshalWASM(r io.Reader) error { + var err error + + if s.Index, err = leb128.ReadVarUint32(r); err != nil { + return err + } + if s.Offset, err = readInitExpr(r); err != nil { + return err + } + s.Data, err = readBytesUint(r) + return err +} + +func (s *DataSegment) MarshalWASM(w io.Writer) error { + if _, err := leb128.WriteVarUint32(w, s.Index); err != nil { + return err + } + if _, err := w.Write(s.Offset); err != nil { + return err + } + return writeBytesUint(w, s.Data) +} + +// A list of well-known custom sections +const ( + CustomSectionName = "name" +) + +var ( + _ Marshaler = (*NameSection)(nil) + _ Unmarshaler = (*NameSection)(nil) +) + +// NameType is the type of name subsection. +type NameType byte + +const ( + NameModule = NameType(0) + NameFunction = NameType(1) + NameLocal = NameType(2) +) + +// NameSection is a custom section that stores names of modules, functions and locals for debugging purposes. +// See https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md#name-section for more details. +type NameSection struct { + Types map[NameType][]byte +} + +func (s *NameSection) UnmarshalWASM(r io.Reader) error { + s.Types = make(map[NameType][]byte) + for { + typ, err := leb128.ReadVarUint32(r) + if err == io.EOF { + return nil + } else if err != nil { + return err + } + data, err := readBytesUint(r) + if err != nil { + return err + } + s.Types[NameType(typ)] = data + } +} + +func (s *NameSection) MarshalWASM(w io.Writer) error { + keys := make([]NameType, 0, len(s.Types)) + for k := range s.Types { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + for _, k := range keys { + data := s.Types[k] + if _, err := leb128.WriteVarUint32(w, uint32(k)); err != nil { + return err + } + if err := writeBytesUint(w, data); err != nil { + return err + } + } + return nil +} + +// Decode finds a specific subsection type and decodes it. +func (s *NameSection) Decode(typ NameType) (NameSubsection, error) { + var sub NameSubsection + switch typ { + case NameModule: + sub = &ModuleName{} + case NameFunction: + sub = &FunctionNames{} + case NameLocal: + sub = &LocalNames{} + default: + return nil, fmt.Errorf("unsupported name subsection: %x", typ) + } + data, ok := s.Types[typ] + if !ok { + return nil, nil + } + if err := sub.UnmarshalWASM(bytes.NewReader(data)); err != nil { + return nil, err + } + return sub, nil +} + +// NameSubsection is an interface for subsections of NameSection. +// +// Valid types: +// * ModuleName +// * FunctionNames +// * LocalNames +type NameSubsection interface { + Marshaler + Unmarshaler + isNameSubsection() +} + +// ModuleName is the name of a module. +type ModuleName struct { + Name string +} + +func (*ModuleName) isNameSubsection() {} + +func (s *ModuleName) UnmarshalWASM(r io.Reader) error { + var err error + s.Name, err = readStringUint(r) + return err +} + +func (s *ModuleName) MarshalWASM(w io.Writer) error { + return writeStringUint(w, s.Name) +} + +// FunctionNames is a set of names for functions. +type FunctionNames struct { + Names NameMap +} + +func (*FunctionNames) isNameSubsection() {} + +func (s *FunctionNames) UnmarshalWASM(r io.Reader) error { + s.Names = make(NameMap) + return s.Names.UnmarshalWASM(r) +} + +func (s *FunctionNames) MarshalWASM(w io.Writer) error { + return s.Names.MarshalWASM(w) +} + +// LocalNames is a set of local variable names for functions. +type LocalNames struct { + // Funcs maps a function index to a set of variable names. + Funcs map[uint32]NameMap +} + +func (*LocalNames) isNameSubsection() {} + +func (s *LocalNames) UnmarshalWASM(r io.Reader) error { + s.Funcs = make(map[uint32]NameMap) + size, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + for i := 0; i < int(size); i++ { + ind, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + m := make(NameMap) + if err := m.UnmarshalWASM(r); err != nil { + return err + } + s.Funcs[ind] = m + } + return nil +} + +func (s *LocalNames) MarshalWASM(w io.Writer) error { + keys := make([]uint32, 0, len(s.Funcs)) + for k := range s.Funcs { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + for _, k := range keys { + m := s.Funcs[k] + if _, err := leb128.WriteVarUint32(w, k); err != nil { + return err + } + if err := m.MarshalWASM(w); err != nil { + return err + } + } + return nil +} + +var ( + _ Marshaler = (NameMap)(nil) + _ Unmarshaler = (NameMap)(nil) +) + +// NameMap maps an index of the entry to a name. +type NameMap map[uint32]string + +func (m NameMap) UnmarshalWASM(r io.Reader) error { + size, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + for i := 0; i < int(size); i++ { + ind, err := leb128.ReadVarUint32(r) + if err != nil { + return err + } + name, err := readStringUint(r) + if err != nil { + return err + } + m[ind] = name + } + return nil +} +func (m NameMap) MarshalWASM(w io.Writer) error { + keys := make([]uint32, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + for _, k := range keys { + name := m[k] + if _, err := leb128.WriteVarUint32(w, k); err != nil { + return err + } + if err := writeStringUint(w, name); err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/go-interpreter/wagon/wasm/types.go b/vendor/github.com/go-interpreter/wagon/wasm/types.go new file mode 100644 index 000000000..e73f45393 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wasm/types.go @@ -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 +} diff --git a/vendor/github.com/go-interpreter/wagon/wast/write.go b/vendor/github.com/go-interpreter/wagon/wast/write.go new file mode 100644 index 000000000..6c3b521c0 --- /dev/null +++ b/vendor/github.com/go-interpreter/wagon/wast/write.go @@ -0,0 +1,493 @@ +// 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 wast implements a WebAssembly text format. +package wast + +// See https://webassembly.github.io/spec/core/text/ + +import ( + "bufio" + "bytes" + "fmt" + "io" + "math" + "strconv" + + "github.com/go-interpreter/wagon/disasm" + "github.com/go-interpreter/wagon/wasm" + "github.com/go-interpreter/wagon/wasm/operators" +) + +const tab = ` ` + +// WriteTo writes a WASM module in a text representation. +func WriteTo(w io.Writer, m *wasm.Module) error { + wr, err := newWriter(w, m) + if err != nil { + return err + } + return wr.writeModule() +} + +type writer struct { + bw *bufio.Writer + m *wasm.Module + + fnames wasm.NameMap + funcOff int + err error +} + +func newWriter(w io.Writer, m *wasm.Module) (*writer, error) { + wr := &writer{bw: bufio.NewWriter(w), m: m} + if s := m.Custom(wasm.CustomSectionName); s != nil { + var names wasm.NameSection + _ = names.UnmarshalWASM(bytes.NewReader(s.Data)) + sub, _ := names.Decode(wasm.NameFunction) + funcs, ok := sub.(*wasm.FunctionNames) + if ok { + wr.fnames = funcs.Names + } + } + return wr, nil +} + +func (w *writer) writeModule() error { + bw := w.bw + bw.WriteString("(module") + + w.writeTypes() + w.writeImports() + w.writeFunctions() + w.writeGlobals() + w.writeTables() + w.writeMemory() + w.writeExports() + w.writeElements() + w.writeData() + + bw.WriteString(")\n") + if err := bw.Flush(); err != nil { + return err + } + return w.err +} + +func (w *writer) writeTypes() { + if w.m.Types == nil { + return + } + w.WriteString("\n") + for i, t := range w.m.Types.Entries { + if i != 0 { + w.WriteString("\n") + } + w.Print(tab+"(type (;%d;) ", i) + w.writeFuncSignature(t) + w.WriteString(")") + } +} + +func (w *writer) writeFuncSignature(t wasm.FunctionSig) error { + w.WriteString("(func") + defer w.WriteString(")") + return w.writeFuncType(t) +} + +func (w *writer) writeFuncType(t wasm.FunctionSig) error { + if len(t.ParamTypes) != 0 { + w.WriteString(" (param") + for _, p := range t.ParamTypes { + w.WriteString(" ") + w.WriteString(p.String()) + } + w.WriteString(")") + } + if len(t.ReturnTypes) != 0 { + w.WriteString(" (result") + for _, p := range t.ReturnTypes { + w.WriteString(" ") + w.WriteString(p.String()) + } + w.WriteString(")") + } + return nil +} + +func (w *writer) writeImports() { + w.funcOff = 0 + if w.m.Import == nil { + return + } + w.WriteString("\n") + for i, e := range w.m.Import.Entries { + if i != 0 { + w.WriteString("\n") + } + w.WriteString(tab + "(import ") + w.Print("%q %q ", e.ModuleName, e.FieldName) + switch im := e.Type.(type) { + case wasm.FuncImport: + w.Print("(func (;%d;) (type %d))", w.funcOff, im.Type) + if w.fnames == nil { + w.fnames = make(wasm.NameMap) + } + w.fnames[uint32(w.funcOff)] = e.ModuleName + "." + e.FieldName + w.funcOff++ + case wasm.TableImport: + // TODO + case wasm.MemoryImport: + // TODO + case wasm.GlobalVarImport: + // TODO + } + w.WriteString(")") + } +} + +func (w *writer) writeFunctions() { + if w.m.Function == nil { + return + } + w.WriteString("\n") + for i, t := range w.m.Function.Types { + if i != 0 { + w.WriteString("\n") + } + ind := w.funcOff + i + w.bw.WriteString(tab + "(func ") + if name, ok := w.fnames[uint32(ind)]; ok { + w.bw.WriteString("$" + name) + } else { + fmt.Fprintf(w.bw, "(;%d;)", ind) + } + fmt.Fprintf(w.bw, " (type %d)", int(t)) + if int(t) < len(w.m.Types.Entries) { + sig := w.m.Types.Entries[t] + w.writeFuncType(sig) + } + if w.m.Code != nil && i < len(w.m.Code.Bodies) { + b := w.m.Code.Bodies[i] + if len(b.Locals) > 0 { + w.WriteString("\n" + tab + tab + "(local") + for _, l := range b.Locals { + for i := 0; i < int(l.Count); i++ { + w.WriteString(" ") + w.WriteString(l.Type.String()) + } + } + w.WriteString(")") + } + w.writeCode(b.Code, false) + } + w.WriteString(")") + } +} + +func (w *writer) writeGlobals() { + if w.m.Global == nil { + return + } + for i, e := range w.m.Global.Globals { + w.WriteString("\n") + w.WriteString(tab + "(global ") + w.Print("(;%d;)", i) + if e.Type.Mutable { + w.WriteString(" (mut") + } + w.Print(" %v", e.Type.Type) + if e.Type.Mutable { + w.WriteString(")") + } + w.WriteString(" (") + w.writeCode(e.Init, true) + w.WriteString("))") + } +} + +func (w *writer) writeTables() { + if w.m.Table == nil { + return + } + w.WriteString("\n") + for i, t := range w.m.Table.Entries { + w.WriteString(tab + "(table ") + w.Print("(;%d;)", i) + w.Print(" %d %d ", t.Limits.Initial, t.Limits.Maximum) + switch t.ElementType { + case wasm.ElemTypeAnyFunc: + w.WriteString("anyfunc") + } + w.WriteString(")") + } +} + +func (w *writer) writeMemory() { + if w.m.Memory == nil { + return + } + w.WriteString("\n") + for i, e := range w.m.Memory.Entries { + w.WriteString(tab + "(memory ") + w.Print("(;%d;)", i) + w.Print(" %d", e.Limits.Initial) + if e.Limits.Maximum != 0 { + w.Print(" %d", e.Limits.Initial) + } + w.WriteString(")") + } +} + +func (w *writer) writeExports() { + if w.m.Export == nil { + return + } + w.WriteString("\n") + for i, k := range w.m.Export.Names { + e := w.m.Export.Entries[k] + if i != 0 { + w.WriteString("\n") + } + w.Print(tab+"(export %q (", e.FieldStr) + switch e.Kind { + case wasm.ExternalFunction: + w.WriteString("func") + case wasm.ExternalMemory: + w.WriteString("memory") + case wasm.ExternalTable: + w.WriteString("table") + case wasm.ExternalGlobal: + w.WriteString("global") + } + w.Print(" %d))", e.Index) + } +} + +func (w *writer) writeElements() { + if w.m.Elements == nil { + return + } + for _, d := range w.m.Elements.Entries { + w.WriteString("\n") + w.WriteString(tab + "(elem") + if d.Index != 0 { + w.Print(" %d", d.Index) + } + w.WriteString(" (") + w.writeCode(d.Offset, true) + w.WriteString(")") + for _, v := range d.Elems { + w.Print(" %d", v) + } + w.WriteString(")") + } +} + +func (w *writer) writeData() { + if w.m.Data == nil { + return + } + for _, d := range w.m.Data.Entries { + w.WriteString("\n") + w.WriteString(tab + "(data") + if d.Index != 0 { + w.Print(" %d", d.Index) + } + w.WriteString(" (") + w.writeCode(d.Offset, true) + w.Print(") %s)", quoteData(d.Data)) + } +} + +func (w *writer) WriteString(s string) { + if w.err != nil { + return + } + _, w.err = w.bw.WriteString(s) +} + +func (w *writer) Print(format string, args ...interface{}) { + if w.err != nil { + return + } + _, w.err = fmt.Fprintf(w.bw, format, args...) +} + +func quoteData(p []byte) string { + buf := new(bytes.Buffer) + buf.WriteRune('"') + for _, b := range p { + if strconv.IsGraphic(rune(b)) && b < 0xa0 && b != '"' && b != '\\' { + buf.WriteByte(b) + } else { + s := strconv.FormatInt(int64(b), 16) + if len(s) == 1 { + s = "0" + s + } + buf.WriteString(`\` + s) + } + } + buf.WriteRune('"') + return buf.String() +} + +func (w *writer) writeCode(code []byte, isInit bool) { + if w.err != nil { + return + } + instr, err := disasm.Disassemble(code) + if err != nil { + w.err = err + return + } + tabs := 2 + block := 0 + writeBlock := func(d int) { + w.Print(" %d (;@%d;)", d, block-d) + } + hadEnd := false + for i, ins := range instr { + if !isInit { + w.WriteString("\n") + } + switch ins.Op.Code { + case operators.End, operators.Else: + tabs-- + block-- + } + if isInit && !hadEnd && ins.Op.Code == operators.End { + hadEnd = true + continue + } + if isInit { + if i > 0 { + w.WriteString(" ") + } + } else { + for i := 0; i < tabs; i++ { + w.WriteString(tab) + } + } + w.WriteString(ins.Op.Name) + switch ins.Op.Code { + case operators.Else: + tabs++ + block++ + case operators.Block, operators.Loop, operators.If: + tabs++ + block++ + b := ins.Immediates[0].(wasm.BlockType) + if b != wasm.BlockTypeEmpty { + w.WriteString(" (result ") + w.WriteString(b.String()) + w.WriteString(")") + } + w.Print(" ;; label = @%d", block) + continue + case operators.F32Const: + i1 := ins.Immediates[0].(float32) + w.WriteString(" " + formatFloat32(i1)) + continue + case operators.F64Const: + i1 := ins.Immediates[0].(float64) + w.WriteString(" " + formatFloat64(i1)) + continue + case operators.BrIf, operators.Br: + i1 := ins.Immediates[0].(uint32) + writeBlock(int(i1)) + continue + case operators.BrTable: + n := ins.Immediates[0].(uint32) + for i := 0; i < int(n); i++ { + v := ins.Immediates[i+1].(uint32) + writeBlock(int(v)) + } + def := ins.Immediates[n+1].(uint32) + writeBlock(int(def)) + continue + case operators.Call: + i1 := ins.Immediates[0].(uint32) + if name, ok := w.fnames[i1]; ok { + w.WriteString(" $") + w.WriteString(name) + } else { + w.Print(" %v", i1) + } + continue + case operators.CallIndirect: + i1 := ins.Immediates[0].(uint32) + w.Print(" (type %d)", i1) + continue + case operators.CurrentMemory, operators.GrowMemory: + r := ins.Immediates[0].(uint8) + if r == 0 { + continue + } + case operators.I32Store, operators.I64Store, + operators.I32Store8, operators.I64Store8, + operators.I32Store16, operators.I64Store16, + operators.I64Store32, + operators.F32Store, operators.F64Store, + operators.I32Load, operators.I64Load, + operators.I32Load8u, operators.I32Load8s, + operators.I32Load16u, operators.I32Load16s, + operators.I64Load8u, operators.I64Load8s, + operators.I64Load16u, operators.I64Load16s, + operators.I64Load32u, operators.I64Load32s, + operators.F32Load, operators.F64Load: + + i1 := ins.Immediates[0].(uint32) + i2 := ins.Immediates[1].(uint32) + dst := 0 // in log 2 (i8) + switch ins.Op.Code { + case operators.I64Load, operators.I64Store, + operators.F64Load, operators.F64Store: + dst = 3 + case operators.I32Load, operators.I64Load32s, operators.I64Load32u, + operators.I32Store, operators.I64Store32, + operators.F32Load, operators.F32Store: + dst = 2 + case operators.I32Load16u, operators.I32Load16s, operators.I64Load16u, operators.I64Load16s, + operators.I32Store16, operators.I64Store16: + dst = 1 + case operators.I32Load8u, operators.I32Load8s, operators.I64Load8u, operators.I64Load8s, + operators.I32Store8, operators.I64Store8: + dst = 0 + } + if i2 != 0 { + w.Print(" offset=%d", i2) + } + if int(i1) != dst { + w.Print(" align=%d", 1<<i1) + } + continue + } + for _, a := range ins.Immediates { + w.WriteString(" ") + w.Print("%v", a) + } + } +} + +func formatFloat32(v float32) string { + s := "" + if v == float32(int32(v)) { + s = strconv.FormatInt(int64(v), 10) + } else { + s = strconv.FormatFloat(float64(v), 'g', -1, 32) + } + return fmt.Sprintf("%#0x (;=%s;)", math.Float32bits(v), s) +} + +func formatFloat64(v float64) string { + // TODO: https://github.com/WebAssembly/wabt/blob/master/src/literal.cc (FloatWriter<T>::WriteHex) + s := "" + if v == float64(int64(v)) { + s = strconv.FormatInt(int64(v), 10) + } else { + s = strconv.FormatFloat(float64(v), 'g', -1, 64) + } + return fmt.Sprintf("%#0x (;=%v;)", math.Float64bits(v), s) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index fc554bfce..2b38e5487 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -293,6 +293,66 @@ "revisionTime": "2017-01-17T22:23:42Z" }, { + "checksumSHA1": "4gQMKF4uGPHsuitzRwSxBo7/EBA=", + "path": "github.com/go-interpreter/wagon/disasm", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "+X9ssBMp1fralbG8OjI7Vc8zoE4=", + "path": "github.com/go-interpreter/wagon/exec", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "4yRxeKi6k5yIf87EwOXzbSTlTRU=", + "path": "github.com/go-interpreter/wagon/exec/internal/compile", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "6zjjuEauIj/855KLJGKT2cgjVio=", + "path": "github.com/go-interpreter/wagon/internal/stack", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "nKawEPA06hZUgof0kPD3pwkdDPo=", + "path": "github.com/go-interpreter/wagon/validate", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "NUIMs+zfyuHDzStjUq0qCr0uKcs=", + "path": "github.com/go-interpreter/wagon/wasm", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "kRHgYIzF9xTBn1Ai9h5H0wcPfp8=", + "path": "github.com/go-interpreter/wagon/wasm/internal/readpos", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "yRQe75b3JNu76jw2fHHaQzGAOEA=", + "path": "github.com/go-interpreter/wagon/wasm/leb128", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "Ic6GhILuFEnGnMq8pOM2iUrJq54=", + "path": "github.com/go-interpreter/wagon/wasm/operators", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { + "checksumSHA1": "Jvjgz1mb1a/sdGK1K1lWEuElGV4=", + "path": "github.com/go-interpreter/wagon/wast", + "revision": "a76146c83d210a8893392bcde44420be18db39f5", + "revisionTime": "2019-03-11T15:10:31Z" + }, + { "checksumSHA1": "gxV/cPPLkByTdY8y172t7v4qcZA=", "path": "github.com/go-ole/go-ole", "revision": "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506", |