core/vm: Add support for AoT compilation of WASM precompiles

This commit is contained in:
Guillaume Ballet 2019-03-13 11:56:49 +01:00
parent 94db4dc329
commit cc5bed4b21
6 changed files with 504 additions and 8 deletions

233
core/vm/aot.go Normal file
View file

@ -0,0 +1,233 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Copyright (c) 2018 Timo Savola. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vm
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"os"
"reflect"
"syscall"
"unsafe"
"github.com/ethereum/go-ethereum/core/vm/aot"
"github.com/tsavola/wag"
"github.com/tsavola/wag/buffer"
"github.com/tsavola/wag/compile"
"github.com/tsavola/wag/object/debug/dump"
"github.com/tsavola/wag/wa"
)
const linearMemoryAddressSpace = 8 * 1024 * 1024 * 1024
const signalStackReserve = 8192
const goStackVectorOffset = -4 * 8
type importFunc struct {
index int
params int
}
func makeMem(size int, prot, extraFlags int) (mem []byte, err error) {
if size > 0 {
mem, err = syscall.Mmap(-1, 0, size, prot, syscall.MAP_PRIVATE|syscall.MAP_ANONYMOUS|extraFlags)
}
return
}
func memAddr(mem []byte) uintptr {
return (*reflect.SliceHeader)(unsafe.Pointer(&mem)).Data
}
func alignSize(size, alignment int) int {
return (size + (alignment - 1)) &^ (alignment - 1)
}
func GrowMemory(size int32) {
}
// AoTContract
type AoTContract struct {
Code []byte
Num int
ImportVector []byte
ImportFuncs map[string]importFunc
}
func NewAoTContract(code []byte, num int) *AoTContract {
ret := &AoTContract{Code: code, Num: num}
ret.ImportFuncs = make(map[string]importFunc)
ret.ImportVector = make([]byte, (5+4)*8)
binary.LittleEndian.PutUint64(ret.ImportVector[0:], aot.ImportUseGas())
binary.LittleEndian.PutUint64(ret.ImportVector[8:], aot.ImportCallDataCopy())
binary.LittleEndian.PutUint64(ret.ImportVector[16:], aot.ImportGetCallDataSize())
binary.LittleEndian.PutUint64(ret.ImportVector[24:], aot.ImportFinish())
binary.LittleEndian.PutUint64(ret.ImportVector[32:], aot.ImportRevert())
ret.ImportFuncs["useGas"] = importFunc{-9, 1}
ret.ImportFuncs["callDataCopy"] = importFunc{-8, 3}
ret.ImportFuncs["getCallDataSize"] = importFunc{-7, 0}
ret.ImportFuncs["finish"] = importFunc{-6, 2}
ret.ImportFuncs["revert"] = importFunc{-5, 2}
binary.LittleEndian.PutUint64(ret.ImportVector[56:], aot.ImportGrowMemoryHandler())
return ret
}
func (ac *AoTContract) ResolveFunc(module, field string, sig wa.FuncType) (index int, err error) {
if module != "ethereum" {
err = fmt.Errorf("import function's module is unknown: %s %s", module, field)
return
}
i := ac.ImportFuncs[field]
if i.index == 0 {
err = fmt.Errorf("import function not supported: %s", field)
return
}
if len(sig.Params) != i.params {
err = fmt.Errorf("%s: import function has wrong number of parameters: import signature has %d, syscall wrapper has %d", field, len(sig.Params), i.params)
return
}
index = i.index
return
}
func (ac *AoTContract) ResolveGlobal(module, field string, t wa.Type) (init uint64, err error) {
err = fmt.Errorf("imported global not supported: %s %s", module, field)
return
}
func (ac *AoTContract) RequiredGas(code []byte) uint64 {
return 0
}
func (ac *AoTContract) Run(input []byte, contract *Contract) ([]byte, error) {
var (
textSize = compile.DefaultMaxTextSize
stackSize = wa.PageSize
entry = "main"
dumpText = false
)
progReader := bytes.NewReader(input)
vecSize := alignSize(len(ac.ImportVector), os.Getpagesize())
vecTextMem, err := makeMem(vecSize+textSize, syscall.PROT_READ|syscall.PROT_WRITE, 0)
if err != nil {
log.Fatal(err)
}
vecMem := vecTextMem[:vecSize]
copy(vecMem[vecSize-len(ac.ImportVector):], ac.ImportVector)
contractData := make([]byte, 8+8+8+len(input) /* original rsp + gas + size + data */)
binary.LittleEndian.PutUint64(contractData[8:], contract.Gas)
binary.LittleEndian.PutUint64(contractData[16:], uint64(len(input)))
copy(contractData[24:], input)
cdAddr := uint64(memAddr(contractData))
binary.LittleEndian.PutUint64(vecTextMem[vecSize-4*8:], cdAddr)
textMem := vecTextMem[vecSize:]
textAddr := memAddr(textMem)
textBuf := buffer.NewStatic(textMem[:0], len(textMem))
config := &wag.Config{
Text: textBuf,
MemoryAlignment: os.Getpagesize(),
Entry: entry,
}
obj, err := wag.Compile(config, progReader, ac)
if dumpText && len(obj.Text) > 0 {
e := dump.Text(os.Stdout, obj.Text, textAddr, obj.FuncAddrs, &obj.Names)
if err == nil {
err = e
}
}
if err != nil {
log.Fatal(err)
}
binary.LittleEndian.PutUint64(ac.ImportVector[40:], uint64(obj.InitialMemorySize))
globalsMemory, err := makeMem(obj.MemoryOffset+linearMemoryAddressSpace, syscall.PROT_NONE, 0)
if err != nil {
log.Fatal(err)
}
err = syscall.Mprotect(globalsMemory[:obj.MemoryOffset+obj.InitialMemorySize], syscall.PROT_READ|syscall.PROT_WRITE)
if err != nil {
log.Fatal(err)
}
copy(globalsMemory, obj.GlobalsMemory)
memoryAddr := memAddr(globalsMemory) + uintptr(obj.MemoryOffset)
if err := syscall.Mprotect(vecMem, syscall.PROT_READ); err != nil {
log.Fatal(err)
}
if err := syscall.Mprotect(textMem, syscall.PROT_READ|syscall.PROT_EXEC); err != nil {
log.Fatal(err)
}
stackMem, err := makeMem(stackSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_STACK)
if err != nil {
log.Fatal(err)
}
stackOffset := stackSize - len(obj.StackFrame)
copy(stackMem[stackOffset:], obj.StackFrame)
stackAddr := memAddr(stackMem)
stackLimit := stackAddr + signalStackReserve
stackPtr := stackAddr + uintptr(stackOffset)
if stackLimit >= stackPtr {
log.Fatal("stack is too small for starting program")
}
retaddr, retsize := aot.Exec(textAddr, stackLimit, memoryAddr, stackPtr)
gasLeft := binary.LittleEndian.Uint64(contractData[8:])
if gasLeft == 0xffffffffffffffff {
fmt.Println("Out of gas")
return nil, ErrOutOfGas
} else {
fmt.Println("gas left: ", gasLeft, "result: ", globalsMemory[retaddr+obj.MemoryOffset:retaddr+obj.MemoryOffset+retsize])
contract.Gas = gasLeft
if retsize == 0 {
return nil, nil
}
retData := make([]byte, retsize)
copy(retData, globalsMemory[retaddr+obj.MemoryOffset:retaddr+obj.MemoryOffset+retsize])
return retData, nil
}
// TODO cleanup all protected memory areas
}

24
core/vm/aot/eei.go Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package aot
func ImportGetCallDataSize() uint64
func ImportUseGas() uint64
func ImportCallDataCopy() uint64
func ImportFinish() uint64
func ImportRevert() uint64
func ImportGrowMemoryHandler() uint64

153
core/vm/aot/eei_amd64.s Normal file
View file

@ -0,0 +1,153 @@
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
#include "textflag.h"
TEXT ·ImportGetCallDataSize(SB),$0-8
LEAQ ethereumGetCallDataSize<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT ethereumGetCallDataSize<>(SB),NOSPLIT,$0
// Get pointer to the contract info area into r13
MOVQ -0x20(R15), R13
MOVQ 0x10(R13), AX
RET
TEXT ·ImportUseGas(SB),$0-8
LEAQ ethereumUseGas<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT ethereumUseGas<>(SB),NOSPLIT,$0
// Get pointer to the contract info area into r13
MOVQ -0x20(R15), R13
MOVQ 8(SP), AX // Gas required
MOVQ 0x8(R13), CX // Gas left
CMPQ AX, CX
JA oog
SUBQ AX, CX
MOVQ CX, 0x8(R13)
XORQ AX, AX
XORQ CX, CX
RET
oog:
// Set gas value to -1
XORQ AX, AX
NOTQ AX
MOVQ AX, 0x8(R13)
// Recover the saved value of the stack
MOVQ -0x20(R15), SI
MOVQ (SI), SP
RET
TEXT ·ImportCallDataCopy(SB),$0-8
LEAQ ethereumCallDataCopy<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT ethereumCallDataCopy<>(SB),NOSPLIT,$0
// Get pointer to the contract info area into r13
MOVQ -0x20(R15), R13
// Get pointer to input data
MOVQ R13, SI
ADDQ $0x18, SI // start of input buffer
MOVQ 0x10(SP), AX // rax = input data offset
ADDQ AX, SI
// Load and check the size of data to be
// copied to the destination buffer
MOVQ 0x8(SP), CX // rcx = number of bytes
ADDQ CX, AX // rax = input buffer + nbytes
MOVQ 0x10(R13), R12 // r12 = max size
CMPQ AX, R12
JA eei_error
// Load address of the destination buffer
MOVQ 0x18(SP), DI
ADDQ R14, DI
copy:
MOVB (SI), AX
MOVB AX, (DI)
ADDQ $1, SI
ADDQ $1, DI
LOOP copy
RET
eei_error:
// Recover the saved value of the stack
MOVQ -0x20(R15), SI
MOVQ (SI), SP
RET
TEXT ·ImportFinish(SB),$0-8
LEAQ ethereumFinish<>(SB), AX
MOVQ AX, ret+0(FP)
RET
// Voir si je peux mettre $0-16 et ret+0(FP), ret+8(FP)
TEXT ethereumFinish<>(SB),NOSPLIT,$0
// Get both arguments from the stack, before it
// is changed back to its previous value
MOVQ 8(SP), AX
MOVQ 16(SP), CX
// Recover the saved value of the stack
MOVQ -0x20(R15), SI
MOVQ (SI), SP
// Store the buffer addresses and size at
// the location where go expects both parameters
// to be stored, on the initial stack.
MOVQ CX, 0x28(SP)
MOVQ AX, 0x30(SP)
RET
TEXT ·ImportRevert(SB),$0-8
LEAQ ethereumRevert<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT ethereumRevert<>(SB),NOSPLIT,$0
// Get both arguments from the stack, before it
// is changed back to its previous value
MOVQ 8(SP), AX
MOVQ 16(SP), CX
// Recover the saved value of the stack
MOVQ -0x20(R15), SI
MOVQ (SI), SP
// Store the buffer addresses and size at
// the location where go expects both parameters
// to be stored, on the initial stack.
MOVQ CX, 0x28(SP)
MOVQ AX, 0x30(SP)
RET
TEXT ·ImportGrowMemoryHandler(SB),$0-8
LEAQ growMemoryHandler<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT growMemoryHandler<>(SB),NOSPLIT,$0-8
//CALL main·GrowMemory+0(FP)
RET

9
core/vm/aot/exec.go Normal file
View file

@ -0,0 +1,9 @@
// Copyright (c) 2018 Timo Savola. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package aot
func Exec(textBase, stackLimit, memoryBase, stackPtr uintptr) (int, int)
func ImportTrapHandler() uint64
func ImportGrowMemory() uint64

77
core/vm/aot/exec_amd64.s Normal file
View file

@ -0,0 +1,77 @@
// Copyright (c) 2018 Timo Savola. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Copyright 2019 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
#include "textflag.h"
// func exec(textBase, stackLimit, memoryBase, stackPtr uintptr)
TEXT ·Exec(SB),NOSPLIT,$0-48
MOVQ textBase+0(FP), R15
MOVQ stackLimit+8(FP), BX
MOVQ memoryBase+16(FP), R14
MOVQ stackPtr+24(FP), CX
MOVQ -0x20(R15), DI
MOVQ SP, (DI)
MOVQ CX, SP // stack ptr
XORL AX, AX
XORL CX, CX
XORL BP, BP
XORL SI, SI
XORL DI, DI
XORL R8, R8
XORL R9, R9
XORL R10, R10
XORL R11, R11
XORL R12, R12
XORL R13, R13
MOVQ R15, DX
ADDQ $32, DX // init routine
JMP DX
// func importTrapHandler() uint64
TEXT ·ImportTrapHandler(SB),$0-8
LEAQ trapHandler<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT trapHandler<>(SB),NOSPLIT,$0
CMPL AX, $0 // exit trap (lower 32 bits)
JE exittrap
ADDL $100, AX // 100 + trap id
JMP sysexit
exittrap:
SHRQ $32, AX // exit code (higher 32 bits)
sysexit:
MOVL AX, DI
MOVL $231, AX // exit_group syscall
SYSCALL
// func importGrowMemory() uint64
TEXT ·ImportGrowMemory(SB),$0-8
LEAQ growMemory<>(SB), AX
MOVQ AX, ret+0(FP)
RET
TEXT growMemory<>(SB),NOSPLIT,$0
HLT // TODO: implementation

View file

@ -67,14 +67,14 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
// PrecompiledContractsEWASM contains the default set of pre-compiled Ethereum // PrecompiledContractsEWASM contains the default set of pre-compiled Ethereum
// contracts used for Ethereum 1.x release. // contracts used for Ethereum 1.x release.
var PrecompiledContractsEWASM = map[common.Address]PrecompiledContract{ var PrecompiledContractsEWASM = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{1}): newEWASMPrecompile(ewasmEcrecoverCode, 1), common.BytesToAddress([]byte{1}): NewAoTContract(ewasmEcrecoverCode, 1),
common.BytesToAddress([]byte{2}): newEWASMPrecompile(ewasmSha256HashCode, 2), common.BytesToAddress([]byte{2}): NewAoTContract(ewasmSha256HashCode, 2),
common.BytesToAddress([]byte{3}): newEWASMPrecompile(ewasmRipemd160hashCode, 3), common.BytesToAddress([]byte{3}): NewAoTContract(ewasmRipemd160hashCode, 3),
common.BytesToAddress([]byte{4}): newEWASMPrecompile(ewasmIdentityCode, 4), common.BytesToAddress([]byte{4}): NewAoTContract(ewasmIdentityCode, 4),
common.BytesToAddress([]byte{5}): newEWASMPrecompile(ewasmExpmodCode, 5), common.BytesToAddress([]byte{5}): NewAoTContract(ewasmExpmodCode, 5),
common.BytesToAddress([]byte{6}): newEWASMPrecompile(ewasmEcaddCode, 6), common.BytesToAddress([]byte{6}): NewAoTContract(ewasmEcaddCode, 6),
common.BytesToAddress([]byte{7}): newEWASMPrecompile(ewasmEcmulCode, 7), common.BytesToAddress([]byte{7}): NewAoTContract(ewasmEcmulCode, 7),
common.BytesToAddress([]byte{8}): newEWASMPrecompile(ewasmEcpairingCode, 8), common.BytesToAddress([]byte{8}): NewAoTContract(ewasmEcpairingCode, 8),
} }
// RunPrecompiledContract runs and evaluates the output of a precompiled contract. // RunPrecompiledContract runs and evaluates the output of a precompiled contract.