From cc5bed4b21377dfe3cf9b4e465ebd9168ef74bfe Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Wed, 13 Mar 2019 11:56:49 +0100 Subject: [PATCH] core/vm: Add support for AoT compilation of WASM precompiles --- core/vm/aot.go | 233 +++++++++++++++++++++++++++++++++++++++ core/vm/aot/eei.go | 24 ++++ core/vm/aot/eei_amd64.s | 153 +++++++++++++++++++++++++ core/vm/aot/exec.go | 9 ++ core/vm/aot/exec_amd64.s | 77 +++++++++++++ core/vm/contracts.go | 16 +-- 6 files changed, 504 insertions(+), 8 deletions(-) create mode 100644 core/vm/aot.go create mode 100644 core/vm/aot/eei.go create mode 100644 core/vm/aot/eei_amd64.s create mode 100644 core/vm/aot/exec.go create mode 100644 core/vm/aot/exec_amd64.s diff --git a/core/vm/aot.go b/core/vm/aot.go new file mode 100644 index 0000000000..97b066230a --- /dev/null +++ b/core/vm/aot.go @@ -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 . + +// 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 +} diff --git a/core/vm/aot/eei.go b/core/vm/aot/eei.go new file mode 100644 index 0000000000..ab0f6e53fe --- /dev/null +++ b/core/vm/aot/eei.go @@ -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 . + +package aot + +func ImportGetCallDataSize() uint64 +func ImportUseGas() uint64 +func ImportCallDataCopy() uint64 +func ImportFinish() uint64 +func ImportRevert() uint64 +func ImportGrowMemoryHandler() uint64 diff --git a/core/vm/aot/eei_amd64.s b/core/vm/aot/eei_amd64.s new file mode 100644 index 0000000000..722d3543d5 --- /dev/null +++ b/core/vm/aot/eei_amd64.s @@ -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 . + +#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 diff --git a/core/vm/aot/exec.go b/core/vm/aot/exec.go new file mode 100644 index 0000000000..4f92bb3270 --- /dev/null +++ b/core/vm/aot/exec.go @@ -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 diff --git a/core/vm/aot/exec_amd64.s b/core/vm/aot/exec_amd64.s new file mode 100644 index 0000000000..d138d6e91e --- /dev/null +++ b/core/vm/aot/exec_amd64.s @@ -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 . + +#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 diff --git a/core/vm/contracts.go b/core/vm/contracts.go index f4cb4b2305..5affac0279 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -67,14 +67,14 @@ var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ // PrecompiledContractsEWASM contains the default set of pre-compiled Ethereum // contracts used for Ethereum 1.x release. var PrecompiledContractsEWASM = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): newEWASMPrecompile(ewasmEcrecoverCode, 1), - common.BytesToAddress([]byte{2}): newEWASMPrecompile(ewasmSha256HashCode, 2), - common.BytesToAddress([]byte{3}): newEWASMPrecompile(ewasmRipemd160hashCode, 3), - common.BytesToAddress([]byte{4}): newEWASMPrecompile(ewasmIdentityCode, 4), - common.BytesToAddress([]byte{5}): newEWASMPrecompile(ewasmExpmodCode, 5), - common.BytesToAddress([]byte{6}): newEWASMPrecompile(ewasmEcaddCode, 6), - common.BytesToAddress([]byte{7}): newEWASMPrecompile(ewasmEcmulCode, 7), - common.BytesToAddress([]byte{8}): newEWASMPrecompile(ewasmEcpairingCode, 8), + common.BytesToAddress([]byte{1}): NewAoTContract(ewasmEcrecoverCode, 1), + common.BytesToAddress([]byte{2}): NewAoTContract(ewasmSha256HashCode, 2), + common.BytesToAddress([]byte{3}): NewAoTContract(ewasmRipemd160hashCode, 3), + common.BytesToAddress([]byte{4}): NewAoTContract(ewasmIdentityCode, 4), + common.BytesToAddress([]byte{5}): NewAoTContract(ewasmExpmodCode, 5), + common.BytesToAddress([]byte{6}): NewAoTContract(ewasmEcaddCode, 6), + common.BytesToAddress([]byte{7}): NewAoTContract(ewasmEcmulCode, 7), + common.BytesToAddress([]byte{8}): NewAoTContract(ewasmEcpairingCode, 8), } // RunPrecompiledContract runs and evaluates the output of a precompiled contract.