From d93fb1b015e5dd25c5b854bbbbc9792fcf830c02 Mon Sep 17 00:00:00 2001 From: Mikhail Sherstennikov Date: Fri, 15 Mar 2024 20:07:59 +0200 Subject: [PATCH] Check CI Signed-off-by: Mikhail Sherstennikov --- clang/include/clang/Basic/BuiltinsEVM.def | 7 +- llvm/.gitignore | 3 +- llvm/include/llvm/IR/IntrinsicsEVM.td | 5 +- llvm/lib/Target/EVM/EVMAsmPrinter.cpp | 13 +- llvm/lib/Target/EVM/EVMConvertRegToStack.cpp | 62 ++-- llvm/lib/Target/EVM/EVMFinalization.cpp | 54 ++-- llvm/lib/Target/EVM/EVMISelLowering.cpp | 42 ++- llvm/lib/Target/EVM/EVMISelLowering.h | 1 + llvm/lib/Target/EVM/EVMInstrInfo.td | 6 + llvm/lib/Target/EVM/EVMMachineFunctionInfo.h | 14 +- llvm/projects/evm-sdk/ecc_gen.rb | 169 ----------- .../evm-sdk/evm-stdlib/include/evm_sdk.h | 126 +++++++- .../evm-sdk/evm-stdlib/include/storage.h | 106 ++++++- .../evm-tests/common_tests/common_test.cpp | 4 +- .../evm-tests/common_tests/return_data.cpp | 29 ++ .../projects/evm-tests/dynamic_stackalloc.cpp | 39 +++ llvm/projects/evm-tests/evm_test.rb | 16 +- llvm/projects/evm-tests/storage_iter_map.cpp | 16 +- llvm/projects/evm-tests/storage_map.cpp | 22 +- llvm/projects/evm-tests/storage_struct.cpp | 16 +- llvm/projects/evm-tests/storage_vector.cpp | 18 +- llvm/projects/evm-tools/CMakeLists.txt | 10 + llvm/projects/evm-tools/ecc_tool.rb | 272 ++++++++++++++++++ .../projects/evm-tools/templates/contract.erb | 83 ++++++ llvm/projects/evm-tools/templates/host.erb | 40 +++ llvm/projects/evm-tools/templates/python.erb | 79 +++++ llvm/tools/evm-linker/evm_common.h | 3 + llvm/tools/evm-linker/evm_linker.cpp | 21 +- 28 files changed, 971 insertions(+), 305 deletions(-) delete mode 100755 llvm/projects/evm-sdk/ecc_gen.rb create mode 100644 llvm/projects/evm-tests/common_tests/return_data.cpp create mode 100644 llvm/projects/evm-tests/dynamic_stackalloc.cpp create mode 100644 llvm/projects/evm-tools/CMakeLists.txt create mode 100644 llvm/projects/evm-tools/ecc_tool.rb create mode 100644 llvm/projects/evm-tools/templates/contract.erb create mode 100644 llvm/projects/evm-tools/templates/host.erb create mode 100644 llvm/projects/evm-tools/templates/python.erb diff --git a/clang/include/clang/Basic/BuiltinsEVM.def b/clang/include/clang/Basic/BuiltinsEVM.def index 132b1a9cf119..e6e3894d0d0d 100644 --- a/clang/include/clang/Basic/BuiltinsEVM.def +++ b/clang/include/clang/Basic/BuiltinsEVM.def @@ -314,7 +314,7 @@ BUILTIN(__builtin_evm_gas, "LLLLi", "nc") //- **Return:** *None* //- **Codegen details:** // - LOG1 opcode: `0xa1` -BUILTIN(__builtin_evm_log1, "vcC*LLLLiLLLLi", "nc") +BUILTIN(__builtin_evm_log1, "vvC*LLLLiLLLLi", "nc") // @brief Log2 // @@ -469,7 +469,7 @@ BUILTIN(__builtin_evm_staticcall, "LLLLiLLLLiLLLLiLLLLiLLLLiLLLLiLLLLi", "nc") //- **Return:** *Ends execution* //- **Codegen details** // - REVERT opcode: `0xfd` -BUILTIN(__builtin_evm_revert, "vLLLLiLLLLi", "nc") +BUILTIN(__builtin_evm_revert, "vvC*LLLLi", "nc") // @brief SelfDestruct // @@ -513,5 +513,8 @@ BUILTIN(__builtin_evm_printf, "vcC*.", "nc") // the frame buffer, which will be passed to SHA3 instruction. BUILTIN(__builtin_evm_sha3_vargs, "LLLLi.", "nc") +// Load config parameter. +BUILTIN(__builtin_evm_cload, "LLLLiLLLLi", "nc") + #undef BUILTIN #undef TARGET_BUILTIN diff --git a/llvm/.gitignore b/llvm/.gitignore index 2c29eb345d49..c36e6de8aff8 100644 --- a/llvm/.gitignore +++ b/llvm/.gitignore @@ -31,7 +31,7 @@ autoconf/autom4te.cache # Visual Studio built-in CMake configuration /CMakeSettings.json # CLion project configuration -/.idea +.idea # Qt Creator project configuration /CMakeLists.txt.user @@ -43,6 +43,7 @@ projects/* !projects/*.* !projects/evm-sdk !projects/evm-tests +!projects/evm-tools !projects/Makefile runtimes/* !runtimes/*.* diff --git a/llvm/include/llvm/IR/IntrinsicsEVM.td b/llvm/include/llvm/IR/IntrinsicsEVM.td index dd7342147524..78e9eca4e9d3 100644 --- a/llvm/include/llvm/IR/IntrinsicsEVM.td +++ b/llvm/include/llvm/IR/IntrinsicsEVM.td @@ -143,7 +143,7 @@ let TargetPrefix = "evm" in { // All intrinsics start with "llvm.evm." [IntrWriteMem, IntrHasSideEffects]>; def int_evm_revert : ClangBuiltin<"__builtin_evm_revert">, - Intrinsic<[], [llvm_i256_ty, llvm_i256_ty], [IntrWriteMem, IntrHasSideEffects, IntrNoReturn]>; + Intrinsic<[], [llvm_ptr_ty, llvm_i256_ty], [IntrWriteMem, IntrHasSideEffects, IntrNoReturn]>; def int_evm_return : ClangBuiltin<"__builtin_evm_return">, Intrinsic<[], [llvm_i256_ty, llvm_i256_ty], [IntrWriteMem, IntrHasSideEffects, IntrNoReturn]>; @@ -169,4 +169,7 @@ let TargetPrefix = "evm" in { // All intrinsics start with "llvm.evm." Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; def int_evm_printf : Intrinsic<[], [llvm_ptr_ty, llvm_vararg_ty], []>; + + def int_evm_cload : ClangBuiltin<"__builtin_evm_cload">, + Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem, IntrHasSideEffects]>; } diff --git a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp index a923c4a59509..e700061a818e 100644 --- a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp +++ b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp @@ -203,11 +203,16 @@ void EVMAsmPrinter::emitConstantPool() { Signature->Params.push_back(valTypeFromMVT(RegisterVT)); } - ComputeValueVTs(TLI, DL, F.getFunctionType()->getReturnType(), EResults); - for (EVT VT : EResults) { - auto RegisterVT = TLI.getRegisterType(F.getContext(), VT); - Signature->Returns.push_back(valTypeFromMVT(RegisterVT)); + if (F.getFunctionType()->getReturnType()->isPointerTy()) { + Signature->Returns.push_back(ValType::PTR); + } else { + ComputeValueVTs(TLI, DL, F.getFunctionType()->getReturnType(), EResults); + for (EVT VT : EResults) { + auto RegisterVT = TLI.getRegisterType(F.getContext(), VT); + Signature->Returns.push_back(valTypeFromMVT(RegisterVT)); + } } + Streamer->emitFunctionType(Sym); } diff --git a/llvm/lib/Target/EVM/EVMConvertRegToStack.cpp b/llvm/lib/Target/EVM/EVMConvertRegToStack.cpp index de32a1e8265e..5242a57ee367 100644 --- a/llvm/lib/Target/EVM/EVMConvertRegToStack.cpp +++ b/llvm/lib/Target/EVM/EVMConvertRegToStack.cpp @@ -146,13 +146,14 @@ static unsigned getSlotMemOpcode(bool IsStore) { } int EVMConvertRegToStack::convertCall(MachineInstr &MI, MachineBasicBlock &MBB) const { - int StackOpcode = -1; - auto& MF = *MBB.getParent(); EVMMachineFunctionInfo *MFI = MF.getInfo(); unsigned FrameSize = MFI->getFrameSizeInBytes(); assert(FrameSize % MFI->getStackSlotSizeInBytes() == 0); + if (MFI->hasDynamicAlloc()) { + FrameSize += MFI->getStackSlotSizeInBytes(); + } auto* InsertBefore = &MI; auto MakeInst = [&](unsigned Opcode, Imms... imms) { @@ -167,22 +168,45 @@ int EVMConvertRegToStack::convertCall(MachineInstr &MI, MachineBasicBlock &MBB) using namespace EVM; auto LoadOpc = getSlotMemOpcode(false); auto StoreOpc = getSlotMemOpcode(true); + auto SlotSize = MFI->getStackSlotSizeInBytes(); + + // Update FP for the callee frame + if (MFI->hasDynamicAlloc()) { + // Function has dynamic stack allocation, so we need to read frame size + // from the special slot in the frame. + auto Offset = MF.getFrameInfo().getObjectOffset(MFI->getDynSizeIndex()); + MakeInst(PUSH32, FpAddress) // fpaddr + .addComment(MF, "Call start"); + MakeInst(MLOAD); // fp + MakeInst(DUP1); // fp, fp + MakeInst(PUSH32, Offset); // fp, fp, dyn_size_offset + MakeInst(ADD); // fp, new_fp_slot + MakeInst(MLOAD); // fp, new_fp + MakeInst(PUSH32, SlotSize); // fp, new_fp, slot_size + MakeInst(ADD); // fp, new_fp + slot_size + MakeInst(SWAP1); // new_fp, fp + MakeInst(DUP2); // new_fp, fp, new_fp + MakeInst(PUSH32, SlotSize); // new_fp, fp, new_fp, slot_size + MakeInst(SWAP1); // new_fp, fp, slot_size, new_fp + MakeInst(SUB); // new_fp, fp, new_fp - slot_size + MakeInst(StoreOpc); // new_fp + } else { + // Save current FP in the slot after current frame + MakeInst(PUSH32, FpAddress) // fpaddr + .addComment(MF, "Call start"); + MakeInst(MLOAD); // fp + MakeInst(DUP1); // fp, fp + MakeInst(PUSH32, FrameSize); // fp, fp, frame_size + MakeInst(ADD); // fp, fp + frame_size + MakeInst(StoreOpc); // - + + MakeInst(PUSH32, FpAddress); // fpaddr + MakeInst(MLOAD); // fp + MakeInst(PUSH32, FrameSize + SlotSize); + // fp, frame_size + MakeInst(ADD); // new_fp + } - // Save current FP in the slot after current frame - MakeInst(PUSH32, FpAddress).addComment(MF, "Call start"); - // fpaddr - MakeInst(MLOAD); // fp - MakeInst(DUP1); // fp, fp - MakeInst(PUSH32, FrameSize); // fp, fp, frame_size - MakeInst(ADD); // fp, fp + frame_size - MakeInst(StoreOpc); // - - - // Update FP to the callee frame - MakeInst(PUSH32, FpAddress); // fpaddr - MakeInst(MLOAD); // fp - MakeInst(PUSH32, FrameSize + MFI->getStackSlotSizeInBytes()); - // fp, frame_size - MakeInst(ADD); // new_fp MakeInst(DUP1); // new_fp, new_fp MakeInst(PUSH32, FpAddress); // new_fp, new_fp, fpaddr MakeInst(MSTORE); // new_fp @@ -192,6 +216,7 @@ int EVMConvertRegToStack::convertCall(MachineInstr &MI, MachineBasicBlock &MBB) MakeInst(PUSH32, SpAddress); // new_fp, spaddr MakeInst(MSTORE); // - + int StackOpcode; if (MF.getSubtarget().hasSubroutine()) { // With subroutine support we do not push return address on to stack StackOpcode = EVM::JUMPSUB; @@ -219,8 +244,7 @@ int EVMConvertRegToStack::convertCall(MachineInstr &MI, MachineBasicBlock &MBB) MakeInst(JUMPDEST).addComment(MF, "Call continuation"); MakeInst(PUSH32, FpAddress); // fpaddr MakeInst(MLOAD); // fp - MakeInst(PUSH32, MFI->getStackSlotSizeInBytes()); - // fp, slot_size + MakeInst(PUSH32, SlotSize); // fp, slot_size MakeInst(SWAP1); // slot_size, fp MakeInst(SUB); // fp - slot_size MakeInst(LoadOpc); // caller_fp diff --git a/llvm/lib/Target/EVM/EVMFinalization.cpp b/llvm/lib/Target/EVM/EVMFinalization.cpp index 06813bc275f0..853449307111 100644 --- a/llvm/lib/Target/EVM/EVMFinalization.cpp +++ b/llvm/lib/Target/EVM/EVMFinalization.cpp @@ -139,7 +139,6 @@ bool EVMFinalization::runOnMachineFunction(MachineFunction &MF) { BuildMI(MBB, begin, begin->getDebugLoc(), TII->get(EVM::BEGINSUB)); } - // use iterator since we need to remove pseudo instructions for (MachineBasicBlock::iterator I = MBB.begin(), E = MBB.end(); I != E;) { @@ -161,39 +160,26 @@ bool EVMFinalization::runOnMachineFunction(MachineFunction &MF) { } } - - // Emit final code to separate file. It is better than disassembling of the result binary, since it contains basic - // blocks and some additional information. - // TODO(EVM): this is kept in the repository solely for debug purposes. Once the EVM matures, we need to rework it. - if (1) { - unsigned Pc = 89; - std::error_code EC; - raw_fd_ostream OS("code.ir", EC); - OS << "Function: " << MF.getName() << '\n'; - for (MachineBasicBlock &MBB : MF) { - OS << "\nbb." << MBB.getNumber()<< '.' << MBB.getName() << ":\n"; - OS << "; predecessors: "; - if (!MBB.pred_empty()) { - ListSeparator LS; - for (auto *Pred : MBB.predecessors()) - OS << LS << printMBBReference(*Pred); - OS << '\n'; - } - if (!MBB.succ_empty()) { - OS.indent(2) << "successors: "; - ListSeparator LS; - for (auto I = MBB.succ_begin(), E = MBB.succ_end(); I != E; ++I) { - OS << LS << printMBBReference(**I); - } - OS << '\n'; - } - for (auto &MI : MBB) { - OS << " " << Pc << " " << MI; - Pc += 1; - // TSFlags contains immediate size - Pc += MI.getDesc().TSFlags; - } - } + auto& MFI = *MF.getInfo(); + if (MFI.hasDynamicAlloc()) { + auto I = ++(MF.begin()->instr_begin()); //->getNextNode(); + auto &MBB = *MF.begin(); + auto Offset = MF.getFrameInfo().getObjectOffset(MFI.getDynSizeIndex()); + unsigned FpAddress = MF.getSubtarget().getFramePointer(); + + auto TII = MF.getSubtarget().getInstrInfo(); + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::PUSH4)) + .addImm(FpAddress); + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::MLOAD)); // fp + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::DUP1)); // fp, fp + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::PUSH4)) + .addImm(MFI.getFrameSizeInBytes()); // fp, fp, frame_size + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::ADD)); // fp, new_fp = fp + frame_size + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::SWAP1)); // new_fp, fp + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::PUSH4)) + .addImm(Offset); // new_fp, fp, dyn_size_offset + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::ADD)); // new_fp, dyn_size_slot + BuildMI(MBB, *I, DebugLoc(), TII->get(EVM::MSTORE)); } return Changed; diff --git a/llvm/lib/Target/EVM/EVMISelLowering.cpp b/llvm/lib/Target/EVM/EVMISelLowering.cpp index 53da2257bb23..c19b932c3b76 100644 --- a/llvm/lib/Target/EVM/EVMISelLowering.cpp +++ b/llvm/lib/Target/EVM/EVMISelLowering.cpp @@ -113,8 +113,7 @@ EVMTargetLowering::EVMTargetLowering(const TargetMachine &TM, setOperationAction(ISD::FrameIndex, VT, Custom); setOperationAction(ISD::TargetFrameIndex, VT, Custom); - // FIXME: DYNAMIC_STACKALLOC - setOperationAction(ISD::DYNAMIC_STACKALLOC, VT, Expand); + setOperationAction(ISD::DYNAMIC_STACKALLOC, VT, Custom); } setOperationAction(ISD::BR_CC, MVT::i256, Custom); setOperationAction(ISD::BR_JT, MVT::Other, Expand); @@ -382,6 +381,8 @@ SDValue EVMTargetLowering::LowerOperation(SDValue Op, // arithmetic instructions. return DAG.getNode(ISD::TRUNCATE, SDLoc(Op), Op.getValueType(), Res); } + case ISD::DYNAMIC_STACKALLOC: + return LowerDYNAMIC_STACKALLOC(Op, DAG); case ISD::CTLZ: case ISD::CTTZ: case ISD::CTLZ_ZERO_UNDEF: @@ -433,6 +434,32 @@ SDValue EVMTargetLowering::LowerCtxz(SDValue Op, SelectionDAG &DAG) const { return ResNode; } +SDValue EVMTargetLowering::LowerDYNAMIC_STACKALLOC(SDValue Op, SelectionDAG &DAG) const { + SDLoc DL(Op); + SDValue Chain = Op.getOperand(0); + SDValue Size = Op.getOperand(1); + + MachineFunction &MF = DAG.getMachineFunction(); + auto& MFI = *MF.getInfo(); + + if (!MFI.hasDynamicAlloc()) { + auto Ty = IntegerType::get(MF.getFunction().getContext(), 32); + auto Align = MF.getDataLayout().getPrefTypeAlign(Ty); + auto Index = MF.getFrameInfo().CreateStackObject(32, Align, false); + MFI.setDynSizeIndex(Index); + } + + auto FI = DAG.getTargetFrameIndex(MFI.getDynSizeIndex(), {MVT::i256}); + auto LD = DAG.getLoad(MVT::i256, DL, Chain, FI, MachinePointerInfo()); + if (Size.getValueSizeInBits() < 256) { + Size = DAG.getNode(ISD::ZERO_EXTEND, DL, {MVT::i256}, Size); + } + auto NewDynSize = DAG.getNode(ISD::ADD, DL, MVT::i256, {LD, Size}); + Chain = DAG.getStore(Chain, DL, NewDynSize, FI, MachinePointerInfo()); + + return DAG.getMergeValues({LD, Chain}, DL); +} + SDValue EVMTargetLowering::LowerCopyToReg(SDValue Op, SelectionDAG &DAG) const { SDValue Src = Op.getOperand(2); @@ -457,11 +484,14 @@ void EVMTargetLowering::ReplaceNodeResults(SDNode *N, SelectionDAG &DAG) const { SDLoc DL(N); - assert(N->getNumValues() == 1); - auto Result = LowerOperation(SDValue(N, 0), DAG); - - Results.push_back(Result); + if (Result.getOpcode() == ISD::MERGE_VALUES) { + for (unsigned i = 0; i < Result.getNumOperands(); i++) { + Results.push_back(Result.getOperand(i)); + } + } else { + Results.push_back(Result); + } } MachineBasicBlock * diff --git a/llvm/lib/Target/EVM/EVMISelLowering.h b/llvm/lib/Target/EVM/EVMISelLowering.h index 7764e1b7af10..404c1e3807de 100644 --- a/llvm/lib/Target/EVM/EVMISelLowering.h +++ b/llvm/lib/Target/EVM/EVMISelLowering.h @@ -87,6 +87,7 @@ class EVMTargetLowering : public TargetLowering { SDValue LowerBasicBlock(SDValue Op, SelectionDAG &DAG) const; SDValue LowerCopyToReg(SDValue Op, SelectionDAG &DAG) const; SDValue LowerCtxz(SDValue Op, SelectionDAG &DAG) const; + SDValue LowerDYNAMIC_STACKALLOC(SDValue Op, SelectionDAG &DAG) const; // This method returns the name of a target specific DAG node. const char *getTargetNodeName(unsigned Opcode) const override; diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 09adf78ea54f..0eafb92ba8e3 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -359,6 +359,10 @@ defm SLOAD : Inst_1_1<"SLOAD", defm SSTORE : Inst_2_0<"SSTORE", [(int_evm_sstore GPR:$src1, GPR:$src2)], 0x55, 200000>; + +defm CLOAD : Inst_1_1<"CLOAD", + [(set GPR:$dst, (int_evm_cload GPR:$src))], + 0xc4, 200>; } let isBranch = 1, isTerminator = 1, isIndirectBranch = 1 in { @@ -699,6 +703,8 @@ def : Pat<(i256 (int_evm_sload GPR:$addr)), (i256 (SLOAD_r GPR:$addr))>; def : Pat<(int_evm_sstore GPR:$offset, GPR:$value), (SSTORE_r GPR:$offset, GPR:$value)>; +def : Pat<(i256 (int_evm_cload GPR:$addr)), + (i256 (CLOAD_r GPR:$addr))>; def : Pat<(int_evm_mload GPR:$addr), (MLOAD_r GPR:$addr)>; def : Pat<(int_evm_mstore GPR:$offset, GPR:$value), diff --git a/llvm/lib/Target/EVM/EVMMachineFunctionInfo.h b/llvm/lib/Target/EVM/EVMMachineFunctionInfo.h index 339450d4cce6..a66f40a0ad20 100644 --- a/llvm/lib/Target/EVM/EVMMachineFunctionInfo.h +++ b/llvm/lib/Target/EVM/EVMMachineFunctionInfo.h @@ -42,6 +42,18 @@ class EVMMachineFunctionInfo : public MachineFunctionInfo { return SpillsCount * getStackSlotSizeInBytes() + LocalsSize; } + bool hasDynamicAlloc() const { + return DynSizeIndex != -1; + } + + void setDynSizeIndex(int I) { + DynSizeIndex = I; + } + + int getDynSizeIndex() const { + return DynSizeIndex; + } + static unsigned getStackSlotSizeInBytes() { return 8; } @@ -50,7 +62,7 @@ class EVMMachineFunctionInfo : public MachineFunctionInfo { MachineFunction &MF; unsigned NumStackArgs{0}; unsigned SpillsCount{0}; - + int DynSizeIndex{-1}; }; } // end namespace llvm diff --git a/llvm/projects/evm-sdk/ecc_gen.rb b/llvm/projects/evm-sdk/ecc_gen.rb deleted file mode 100755 index ec2ca7889b49..000000000000 --- a/llvm/projects/evm-sdk/ecc_gen.rb +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -require 'erb' -require 'optparse' -require 'ostruct' -require 'yaml' - -$options = OpenStruct.new -OptionParser.new do |opts| - opts.banner = 'Usage: ecc_gen.rb [options] FILE' - opts.on('--output=PATH', 'Path to output file') - opts.on('-c', '--cpp-input', 'Input file is a c++ file with embedded storage description') { $options.cpp_input = _1 } - opts.on('-v', '--verbose', 'Verbose logging') -end.parse!(into: $options) - -def options; $options; end - -INPUT_FILE=ARGV.pop -raise "Input file must be provided" unless INPUT_FILE - -options.output = File.basename(INPUT_FILE, '.*') + '_gen.h' unless options.output - -$primitive_types = %w(uint8_t uint16_t uint32_t uint64_t uint128_t uint256_t - int8_t int16_t int32_t int64_t int128_t int256_t - bool int unsigned) - -def to_recursive_ostruct(value) - if value.is_a?(Array) - value.each_with_object([]) do |val, memo| - memo << ((val.is_a?(Hash) || val.is_a?(Array)) ? to_recursive_ostruct(val) : val) - end - else - OpenStruct.new(value.each_with_object({}) do |(key, val), memo| - memo[key] = (val.is_a?(Hash) || val.is_a?(Array)) ? to_recursive_ostruct(val) : val - end) - end -end - -def load_input_file(file) - return File.read(file) unless options.cpp_input - m = File.read(file).match(/STORAGE_YAML *= *R"\((.*?)\)"/m) - m ? m[1] : nil -end - -def get_cpp_type_static(type, i) - if $primitive_types.include?(type) - "SlotStatic<#{type}, Key + #{i}>" - else - m = type.match /(\w+)(\<(\w+)\>)?/ - raise "Invalid type: #{type}" unless m - base_type, sub_type = m[1], m[3] - sub_type = "#{sub_type}, " if sub_type - "#{base_type}Static<#{sub_type}Key + #{i}>" - end -end - -def get_cpp_type(type, field_pos) - if $primitive_types.include?(type) - "Slot<#{type}>" - else - m = type.match /(\w+)(\<(\w+)\>)?/ - raise "Invalid type: #{type}" unless m - base_type, sub_type = m[1], m[3] - if sub_type - "#{base_type}<#{sub_type}>" - else - base_type - end - end -end - -def parse_type(type) - m = type.match /(\w+)(\<(\w+)\>)?/ - raise "Invalid type: #{var.type}" unless m - [m[1], m[3]] -end - -def calculate_total_size(type, types) - return type.total_size if type.total_size - type.total_size = type.fields.reduce(0) { |sum, x| - base_type, _ = parse_type(x.type) - raise "Unknown type: #{base_type}" unless types[base_type] - sum + calculate_total_size(types[base_type], types) - } -end - -def main - data = load_input_file(INPUT_FILE) - data = data ? to_recursive_ostruct(YAML.load(data)) : OpenStruct.new - data.types ||= [] - - types = Hash[data.types.map{ [_1.name, _1]}] - types["Map"] = OpenStruct.new({name: "Map", total_size: 1}) - types["Vector"] = OpenStruct.new({name: "Vector", total_size: 1}) - types["IterableMap"] = OpenStruct.new({name: "IterableMap", total_size: 4}) - - $primitive_types.each do |type| - types[type] = OpenStruct.new({name: type, total_size: 1}) - end - - types.each do |name, type| - type.total_size = calculate_total_size(type, types) - if type.fields - type.fields.each_with_index do |field, i| - field.cpp_name_static = get_cpp_type_static(field.type, i) - field.cpp_name = get_cpp_type(field.type, i) - field.ctor_initializer = "key + #{i}" - end - type.ctor_initializer = type.fields.map{ |x| "#{x.name}(#{x.ctor_initializer})" }.join(', ') - end - end - - slot = 0 - data.variables&.each_with_index do |var, i| - var.slot = slot - if $primitive_types.include?(var.type) - var.cpp_type = "SlotStatic<#{var.type}, #{slot}>" - slot += 1 - else - type, subtype = parse_type(var.type) - var.cpp_type = "#{type}Static<#{subtype ? subtype + ', ' : ''}#{slot}>" - raise "Invlid type: #{type.name}" unless types[type].total_size - slot += types[type].total_size - end - end - - t = ERB.new($template, nil, '%-') - out = t.result(binding) - if !options.output - $stdout.write(out) - else - File.write(options.output, out) - end -end - -$template = < { - static constexpr unsigned FIELDS_NUM = <%= type.total_size %>; - - <%= type.name %>(__int256_t key): <%= type.ctor_initializer %> {} -% type.fields.each do |field| - <%= field.cpp_name %> <%= field.name %>; -% end -}; - -template<__uint256_t Key> -struct <%= type.name %>Static { - static constexpr unsigned FIELDS_NUM = <%= type.total_size %>; -% type.fields&.each do |field| - <%= field.cpp_name_static %> <%= field.name %>; -% end -}; -% end - -% data.variables&.each do |var| -<%= var.cpp_type %> <%= var.name %>; -% end - -} // namespace evm::stor -EOC - -main diff --git a/llvm/projects/evm-sdk/evm-stdlib/include/evm_sdk.h b/llvm/projects/evm-sdk/evm-stdlib/include/evm_sdk.h index 4607ebefde52..5eca96f58b0f 100644 --- a/llvm/projects/evm-sdk/evm-stdlib/include/evm_sdk.h +++ b/llvm/projects/evm-sdk/evm-stdlib/include/evm_sdk.h @@ -24,6 +24,7 @@ #ifndef LLVM_EVM_SDK_H #define LLVM_EVM_SDK_H +#include #include extern "C" __uint256_t __evm_builtin_modpow(__uint256_t, __uint256_t, __uint256_t); @@ -33,21 +34,136 @@ using int256_t = __int256_t; #define evm_printf __builtin_evm_printf +#define DVM_PACKED __attribute__((packed)) + +constexpr uint256_t operator "" _gwei(unsigned long long v) { + constexpr unsigned GWEI_VALUE = 1'000'000'000; + return uint256_t(static_cast(v * GWEI_VALUE)); +} + namespace evm { -using Address = __uint256_t; -using Gwei = __uint256_t; +struct DVM_PACKED Address { + int8_t workchain_id; + uint256_t address; +}; -void require(bool expr) { +void require(bool expr, uint256_t code = 0) { if (!expr) { - __builtin_evm_revert(0, 0); + __builtin_evm_revert((void*)&code, sizeof(code)); } } uint256_t now() { - return __builtin_evm_timestamp(); + return uint256_t(__builtin_evm_timestamp()); +} + +Address caller() { + return {-1, uint256_t(__builtin_evm_caller())}; +} + +Address my_address() { + return {-1, uint256_t(__builtin_evm_address())}; +} + +uint256_t callvalue() { + return __builtin_evm_callvalue(); } +void send_message(const void* data, unsigned size) { + __builtin_evm_log1(data, size, 0x0ec3c86d); +} + +void* return_data(void* data, unsigned size) { + static constexpr unsigned RETURN_DATA_SIZE_SLOT_ADDRESS = 32; + __uint256_t* p = (__uint256_t*)RETURN_DATA_SIZE_SLOT_ADDRESS; + // For arbitrary-sized data returned from the contract, we save its size in + // memory address 32. Linker then reads it and properly returns to the host. + *p = size; + return data; +} + +template +auto get_config_param(uint256_t id, uint256_t sub_id) { + static constexpr uint256_t PARAMS_BASE_KEY = 0x1000000000000000000000000000000000000000000000000000000000000000X; + static constexpr uint256_t PARAM_SIZE = 0x1000; + if constexpr (std::is_integral_v) { + return __builtin_evm_sload(PARAMS_BASE_KEY + id * PARAM_SIZE + sub_id); + } else { + return T(PARAMS_BASE_KEY + id * PARAM_SIZE + sub_id); + } +} + +struct Signature { + uint256_t hi; + uint256_t lo; + + bool check(uint256_t pubkey, void* data, unsigned size) { + // TODO: Implement, looks like precompiled contract #1 is suitable for this. + return true; + } +}; + +/** + * This is common header for all messages passing between contracts. + */ +struct DVM_PACKED MessageHeader { + uint256_t address_from; + uint256_t address_to; + uint256_t amount; +}; + +/** + * This is a dynamic array. It cannot grow, it has precomputed size, stored in + * the first word, followed by array's elements. + * @tparam T type of element + */ +template +class DVM_PACKED Vector { +public: + static Vector& create_in_buffer(void* data, unsigned size) { + Vector* self = reinterpret_cast(data); + self->size_ = size; + return *self; + } + + static unsigned required_size(unsigned num) { + return sizeof(size_) + sizeof(T) * num; + } + + bool empty() const { + return size() == 0; + } + + unsigned size() const { + return size_; + } + + const T* data() const { + return data_; + } + + T* data() { + return data_; + } + + T& operator[](unsigned index) { + return data_[index]; + } + + template + void foreach(Func func) { + T* current = data(); + for (unsigned i = 0; i < size(); i++) { + func(current); + current++; + } + } +private: + uint32_t size_; + T data_[0]; +}; + } // namespace evm #endif // LLVM_EVM_SDK_H diff --git a/llvm/projects/evm-sdk/evm-stdlib/include/storage.h b/llvm/projects/evm-sdk/evm-stdlib/include/storage.h index 6f7e463fb604..bbb9bf0223b4 100644 --- a/llvm/projects/evm-sdk/evm-stdlib/include/storage.h +++ b/llvm/projects/evm-sdk/evm-stdlib/include/storage.h @@ -1,3 +1,6 @@ +#ifndef _STORAGE_H +#define _STORAGE_H + #include "evm_sdk.h" #include @@ -11,7 +14,8 @@ void remove_sequence(uint256_t slot, (__builtin_evm_sstore(slot + Slots, 0), ...); } -template void clear(uint256_t slot) { +template +void clear(uint256_t slot) { if constexpr (std::is_integral_v) { __builtin_evm_sstore(slot, 0); } else { @@ -20,16 +24,44 @@ template void clear(uint256_t slot) { } } -template +struct ConfigAccessor { + static constexpr uint256_t load(uint256_t slot) { + return __builtin_evm_cload(slot); + } + + static constexpr void store(uint256_t slot, uint256_t v) { + __builtin_evm_revert(0, 0); + } +}; + +struct StorageAccessor { + static constexpr uint256_t load(uint256_t slot) { + return __builtin_evm_sload(slot); + } + + static constexpr void store(uint256_t slot, uint256_t v) { + __builtin_evm_sstore(slot, v); + } +}; + +template struct SlotStaticImpl { using ValueType = T; static constexpr uint256_t slot() { return slot_; } + + uint256_t load() const { + return Accessor::load(slot_); + } + + void store(uint256_t v) const { + Accessor::store(slot_, v); + } }; -template +template struct SlotImpl { using ValueType = T; @@ -39,6 +71,14 @@ struct SlotImpl { return slot_; } + uint256_t load() const { + return Accessor::load(slot_); + } + + void store(uint256_t v) const { + Accessor::store(slot_, v); + } + uint256_t slot_; }; @@ -50,11 +90,15 @@ struct SlotBase: private Impl { using Impl::Impl; void set(const ValueType &v) { - __builtin_evm_sstore(Impl::slot(), v); + Impl::store(v); + } + + uint256_t slot() const { + return Impl::slot(); } ValueType get() const { - return __builtin_evm_sload(Impl::slot()); + return Impl::load(); } SlotBase& operator=(const ValueType &v) { @@ -68,10 +112,10 @@ struct SlotBase: private Impl { }; template -using Slot = SlotBase>; +using Slot = SlotBase>; template -using SlotStatic = SlotBase>; +using SlotStatic = SlotBase>; /******************************************************************************* * Dynamic array. @@ -95,7 +139,7 @@ class VectorBase: private Impl { } unsigned size() const { - return __builtin_evm_sload(Impl::slot()); + return Impl::load(); } bool empty() const { @@ -105,7 +149,7 @@ class VectorBase: private Impl { auto push() { auto sz = size(); uint256_t new_size = sz + 1; - __builtin_evm_sstore(Impl::slot(), new_size); + Impl::store(new_size); return operator[](sz); } @@ -113,7 +157,7 @@ class VectorBase: private Impl { auto sz = size(); require(sz != 0); details::clear(get_storage_key() + sz - 1); - __builtin_evm_sstore(Impl::slot(), sz - 1); + Impl::store(sz - 1); } private: @@ -123,10 +167,10 @@ class VectorBase: private Impl { }; template -using Vector = VectorBase>; +using Vector = VectorBase>; template -using VectorStatic = VectorBase>; +using VectorStatic = VectorBase>; /******************************************************************************* * Basic map. @@ -158,10 +202,10 @@ struct MapBase: private Impl { }; template -using Map = MapBase>; +using Map = MapBase>; template -using MapStatic = MapBase>; +using MapStatic = MapBase>; /******************************************************************************* * Iterable map. @@ -194,6 +238,18 @@ class IterableMapBase: private Impl { return Impl::keys[index].get(); } + auto get_at_index(uint256_t index) { + auto key = get_key_at(index); + return operator[](key); + } + + void clear() { + for (unsigned i = 0; i < size(); i++) { + auto key = Impl::keys[i].get(); + remove(key); + } + } + void remove(uint256_t key) { if (!contains(key)) { return; @@ -255,3 +311,25 @@ template using IterableMapStatic = IterableMapBase>; } // namespace evm::stor + +namespace evm::config { + +template +using Slot = evm::stor::SlotBase>; +template +using SlotStatic = evm::stor::SlotBase>; + +template +using Vector = evm::stor::VectorBase>; +template +using VectorStatic = evm::stor::VectorBase>; + +template +using Map = evm::stor::MapBase>; +template +using MapStatic = evm::stor::MapBase>; + +} // namespace evm::config + + +#endif // _STORAGE_H diff --git a/llvm/projects/evm-tests/common_tests/common_test.cpp b/llvm/projects/evm-tests/common_tests/common_test.cpp index d7d84cb15cf0..6b481b86b93e 100644 --- a/llvm/projects/evm-tests/common_tests/common_test.cpp +++ b/llvm/projects/evm-tests/common_tests/common_test.cpp @@ -62,7 +62,7 @@ int return_void_value; } //! DEPLOY -//! CALL function: :test, expect_fail: true -[[evm]] void test() { +//! CALL function: :test_assert, expect_fail: true +[[evm]] void test_assert() { assert(false); } diff --git a/llvm/projects/evm-tests/common_tests/return_data.cpp b/llvm/projects/evm-tests/common_tests/return_data.cpp new file mode 100644 index 000000000000..68eff72a1ad5 --- /dev/null +++ b/llvm/projects/evm-tests/common_tests/return_data.cpp @@ -0,0 +1,29 @@ +#include "evm_sdk.h" + +//! DEPLOY +//! def calc(n); (0..n-1).reduce("") { |res, x| res += "%02X"%((x + 1) % 256) }; end +//! CALL function: :test_return_dyn, input: [0], no_result: true +//! CALL function: :test_return_dyn, input: [1], result: 1 +//! CALL function: :test_return_dyn, input: [25], result: calc(25).to_i(16) +//! CALL function: :test_return_dyn, input: [500], result: calc(500) + +[[evm]] void* test_return_dyn(unsigned size) { + unsigned char buf[size]; + for (unsigned i = 0; i < size; i++) { + buf[i] = i + 1; + } + return evm::return_data(buf, size); +} + +//! def calc64(n); (0..n-1).reduce("") { |res, x| res += "%016X"%(x + 1) }; end +//! CALL function: :test_return_dyn_i64, input: [1], result: 1 +//! CALL function: :test_return_dyn_i64, input: [25], result: calc64(25) +//! CALL function: :test_return_dyn_i64, input: [500], result: calc64(500) + +[[evm]] void* test_return_dyn_i64(unsigned size) { + uint64_t buf[size]; + for (unsigned i = 0; i < size; i++) { + buf[i] = i + 1; + } + return evm::return_data(buf, size * sizeof(uint64_t)); +} diff --git a/llvm/projects/evm-tests/dynamic_stackalloc.cpp b/llvm/projects/evm-tests/dynamic_stackalloc.cpp new file mode 100644 index 000000000000..98aad24e3694 --- /dev/null +++ b/llvm/projects/evm-tests/dynamic_stackalloc.cpp @@ -0,0 +1,39 @@ + +struct Array { + __attribute__ ((noinline)) Array(unsigned char* p, unsigned sz): buf(p), size(sz) {} + + void __attribute__ ((noinline)) fill(unsigned char addend) { + for (unsigned i = 0; i < size; i++) { + buf[i] = i + addend; + } + } + + unsigned char *buf = nullptr; + unsigned size = 0; +}; + +int __attribute__ ((noinline)) nested_alloc(Array& a1, Array& a2, unsigned i) { + auto size = a1.size; + unsigned char buf[size]; + Array a3(buf, size); + for (unsigned i = 0; i < size; i++) { + a3.buf[i] = a1.buf[i] + a2.buf[i]; + } + return a3.buf[i]; +} + +//! DEPLOY +//! def calc(n, i); (0..n).map{ _1 + _1 + 10 }[i]; end +//! CALL function: :test, input: [4, 3], result: calc(4, 3) +//! CALL function: :test, input: [35, 13], result: calc(35, 13) +//! CALL function: :test, input: [150, 48], result: calc(150, 48) +//! CALL function: :test, input: [150, 0], result: calc(150, 0) +[[evm]] int test(unsigned size, unsigned i) { + unsigned char buf1[size]; + unsigned char buf2[size]; + Array a1(buf1, size); + Array a2(buf2, size); + a1.fill(0); + a2.fill(10); + return nested_alloc(a1, a2, i); +} diff --git a/llvm/projects/evm-tests/evm_test.rb b/llvm/projects/evm-tests/evm_test.rb index af0510c5050d..abbb3f9ff95e 100755 --- a/llvm/projects/evm-tests/evm_test.rb +++ b/llvm/projects/evm-tests/evm_test.rb @@ -31,8 +31,8 @@ def options; $options; end -VM_RUN = options.vmrun # "/home/mike/bld/dbms-nix/bin/vmrun/vm_run" -ECC_GEN = "#{__dir__}/../evm-sdk/ecc_gen.rb" +VM_RUN = options.vmrun +ECC_TOOL = "#{__dir__}/../evm-tools/ecc_tool.rb" options.src_dir = File.realpath(__dir__ + '/../../../') unless options.src_dir @@ -207,7 +207,7 @@ def let(name, value) def compile - command("ruby #{ECC_GEN} -c #{@source_file}") + command("ruby #{ECC_TOOL} #{@source_file} --generate=contract") clang_cmd = "#{@bindir}/ecc #{@source_file} -o #{@codefile} " clang_cmd += ' -v ' if options.verbose @@ -236,6 +236,7 @@ def CALL(**args) function = args[:function]&.to_s address = args[:address] || 0 result = args[:result] + no_result = args[:no_result] input = args[:input] raise "Function name must be specified" unless function @@ -263,8 +264,12 @@ def CALL(**args) m = output.match /Result stack: (.*)/ raise "Invalid vm_run output: #{output}" unless m stack = eval(m[1]) - raise "Must be only one stack value" unless stack.size == 1 - raise "Result mismatch: expected(#{result}) != real(#{stack[0]})" if result != stack[0] + if no_result + raise "Method return non empty stack for test with `no_result`" unless stack.empty? + else + raise "Must be only one stack value" unless stack.size == 1 + raise "Result mismatch: expected(#{result}) != real(#{stack[0]})" if result != stack[0] + end end end end @@ -276,7 +281,6 @@ def CHECK_STOR(**args) raise "Key must be specified" unless key def calc_key(key, hex_out=true) - # return key if key.is_a? Integer s = key.reduce('') do |acc, key| acc + (key.is_a?(Array) ? calc_key(key, false) : pack_i256(key)) end diff --git a/llvm/projects/evm-tests/storage_iter_map.cpp b/llvm/projects/evm-tests/storage_iter_map.cpp index 2927d0cbc6ef..f3a5967d2351 100644 --- a/llvm/projects/evm-tests/storage_iter_map.cpp +++ b/llvm/projects/evm-tests/storage_iter_map.cpp @@ -7,15 +7,15 @@ static constexpr const char* STORAGE_YAML = R"( types: - name: Foo fields: - - { name: a, type: uint256_t } - - { name: b, type: uint256_t } - - { name: c, type: uint256_t } - - { name: map, type: IterableMap } + - a: u256 + - b: u256 + - c: u256 + - map: IterableMap variables: -- { name: dummy, type: Foo } -- { name: map, type: IterableMap } -- { name: map_foo, type: IterableMap } -- { name: flag, type: bool } +- dummy: Foo +- map: IterableMap +- map_foo: IterableMap +- flag: bool )"; //! DEPLOY diff --git a/llvm/projects/evm-tests/storage_map.cpp b/llvm/projects/evm-tests/storage_map.cpp index 69d31a0a995e..2d6e65a78bd5 100644 --- a/llvm/projects/evm-tests/storage_map.cpp +++ b/llvm/projects/evm-tests/storage_map.cpp @@ -5,23 +5,23 @@ using namespace evm; static constexpr const char* STORAGE_YAML = R"( variables: -- { name: gv, type: int } -- { name: map, type: Map } -- { name: foo, type: Foo } +- gv: i32 +- map: Map +- foo: Foo types: - name: Foo fields: - - { name: a, type: int } - - { name: b, type: int } - - { name: c, type: Map } - - { name: d, type: Map } - - { name: bar, type: Bar } + - a: i32 + - b: i32 + - c: Map + - d: Map + - bar: Bar - name: Bar fields: - - { name: a, type: int } - - { name: b, type: int } - - { name: d, type: Map } + - a: i32 + - b: i32 + - d: Map )"; //! let :value, 123456 diff --git a/llvm/projects/evm-tests/storage_struct.cpp b/llvm/projects/evm-tests/storage_struct.cpp index 7f49463b8c8b..10b3db767523 100644 --- a/llvm/projects/evm-tests/storage_struct.cpp +++ b/llvm/projects/evm-tests/storage_struct.cpp @@ -7,17 +7,17 @@ static constexpr const char* STORAGE_YAML = R"( types: - name: Foo fields: - - { name: a, type: uint256_t } - - { name: b, type: uint256_t } - - { name: c, type: uint256_t } + - a: u256 + - b: u256 + - c: u256 - name: Bar fields: - - { name: a, type: uint256_t } - - { name: b, type: uint256_t } - - { name: c, type: uint256_t } + - a: u256 + - b: u256 + - c: u256 variables: -- { name: foo, type: Foo } -- { name: bar, type: Bar } +- foo: Foo +- bar: Bar )"; //! DEPLOY diff --git a/llvm/projects/evm-tests/storage_vector.cpp b/llvm/projects/evm-tests/storage_vector.cpp index 568d6a22b13a..4aad3aa81e9e 100644 --- a/llvm/projects/evm-tests/storage_vector.cpp +++ b/llvm/projects/evm-tests/storage_vector.cpp @@ -7,18 +7,18 @@ static constexpr const char* STORAGE_YAML = R"( types: - name: Foo fields: - - { name: a, type: int } - - { name: b, type: Vector } - - { name: c, type: Vector } + - a: i32 + - b: Vector + - c: Vector - name: Bar fields: - - { name: a, type: int } - - { name: b, type: int } - - { name: c, type: Vector } + - a: i32 + - b: i32 + - c: Vector variables: -- { name: arr, type: Vector } -- { name: arr_foo, type: Vector } -- { name: map_arr, type: Map } +- arr: Vector +- arr_foo: Vector +- map_arr: Map )"; //! DEPLOY diff --git a/llvm/projects/evm-tools/CMakeLists.txt b/llvm/projects/evm-tools/CMakeLists.txt new file mode 100644 index 000000000000..bb83d8a110de --- /dev/null +++ b/llvm/projects/evm-tools/CMakeLists.txt @@ -0,0 +1,10 @@ + +install(DIRECTORY templates + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT evm-tools +) + +install(FILES ecc_tool.rb + DESTINATION ${CMAKE_INSTALL_BINDIR} + COMPONENT evm-tools +) \ No newline at end of file diff --git a/llvm/projects/evm-tools/ecc_tool.rb b/llvm/projects/evm-tools/ecc_tool.rb new file mode 100644 index 000000000000..693627facc05 --- /dev/null +++ b/llvm/projects/evm-tools/ecc_tool.rb @@ -0,0 +1,272 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'erb' +require 'open3' +require 'optparse' +require 'ostruct' +require 'pathname' +require 'yaml' + +$options = OpenStruct.new +OptionParser.new do |opts| + opts.banner = 'Usage: ecc_gen.rb [options] FILE' + opts.on('--output=PATH', 'Path to output file') + opts.on('--ecc=PATH', 'Path to ecc compiler') + opts.on('--compile', 'Compile file') + opts.on('--generate=STRING', 'Select component to generate: host, contract, python') + opts.on('--data-file=PATH', 'Path to the contract data file in yaml format') { $options.data_file = _1 } + opts.on('--clang-args=ARGS', 'Command line arguments for clang') { $options.clang_args = _1 } + opts.on('--linker-args=ARGS', 'Command line arguments for linker') { $options.linker_args = _1 } + opts.on('--no-ctor', 'Do not generate EVM constructor') { $options.no_ctor = true } + opts.on('-c', '--cpp-input', 'Input file is a c++ file with embedded storage description') { $options.cpp_input = _1 } + opts.on('-v', '--verbose', 'Verbose logging') +end.parse!(into: $options) +def options; $options; end + +INPUT_FILE=ARGV.pop +raise "Input file must be provided" unless INPUT_FILE + +def command(cmd) + puts "COMMAND: #{cmd}" if options.verbose + output, status = Open3.capture2e(cmd.to_s) + if status.signaled? || status.exitstatus != 0 + raise "Command failed '#{cmd}':\n#{output}" + end + puts output if options.verbose + output +end + +def cpp_type(type) + @type_aliases ||= {"u8" => "uint8_t", "i8" => "int8_t", "u16" => "uint16_t", "i16" => "int16_t", + "u32" => "uint32_t", "i32" => "int32_t", "u64" => "uint64_t", "i64" => "int64_t", + "u128" => "uint128_t", "i128" => "int128_t", "u256" => "uint256_t", "i256" => "int256_t"} + @type_aliases[type] || type +end + +def canonicalize(data) + def fix_field(field) + if field['name'] + raise "type must be specified for field: #{field['name']}" unless field['type'] + else + # raise "Exact one type specifier is allowed per field" unless field.keys.size == 1 + field['name'], field['type'] = field.first + end + end + data['types']&.each { _1['fields']&.each { |field| fix_field(field) } } + data['variables']&.each { fix_field(_1) } + data['messages']&.each { _1['fields']&.each { |field| fix_field(field) } } +end + +class Type + attr_reader :name + + def initialize(name) + @name = name + end + + def bytes_size + raise "Abstract type hasn't bytes size" + end + + def to_s; @name; end + def cpp_name; cpp_type(@name); end + def host_name; cpp_type(@name); end + def python_name + @name =~ /[iu]\d+/ ? 'int' : @name + end +end + +class PrimitiveType < Type + def initialize(name) + super(name) + end + + def bits + return 8 if @name == "bool" + m = @name.match /[ui](\d+)/ + raise "type_bits: can't parse type: #{@name}" unless m + m[1].to_i + end + + def bytes_size + bs = bits + raise "bytes_size: bits is not multiple of 8: #{bs}" unless bs % 8 == 0 + bs / 8 + end + + def cpp_name + "Slot<#{cpp_type(@name)}>" + end + + def cpp_name_static(index) + index = index.call if index.is_a?(Proc) + "SlotStatic<#{cpp_type(@name)}, #{index}>" + end + + def slots_size + 1 + end +end + +class StructType < Type + attr_reader :fields, :descr + + def initialize(name, descr, fields) + super(name) + @fields = fields + @descr = descr + end + + def bytes_size + @fields.sum do |field| + sz = field.type.slots_size + return nil if sz.nil? + sz + end + end + + def to_s + "#{@name}: #{@fields.map(&:name).join(', ')}" + end + + def slots_size + @size ||= @fields.sum do |field| + field.type.is_a?(StructType) ? field.type.slots_size : 1 + end + end + + def cpp_name + @name + end + + def cpp_name_static(index) + index = index.call if index.is_a?(Proc) + "#{@name}Static<#{index}>" + end +end + + +class ContainerType < Type + attr_reader :base_type_name, :sub_type + + def initialize(name, base_type_name, sub_type) + super(name) + @base_type_name = base_type_name + @sub_type = sub_type + end + + def slots_size + 1 + end + + def cpp_name + @name + end + + def cpp_name_static(index) + index = index.call if index.is_a?(Proc) + "#{@base_type_name}Static<#{@sub_type.cpp_name}, #{index}>" + end +end + +class Ir + attr_reader :types, :variables, :namespace, :data + + PRIMITIVE_TYPES = %w(u8 i8 u16 i16 u32 i32 u64 i64 u128 i128 u256 i256 bool) + + def initialize(data) + @types = {} + @data = data + @variables = nil + @namespace = nil + end + + def load() + @namespace = 'evm::' + (@data.dig('contract', 'storage_namespace') || 'stor') + PRIMITIVE_TYPES.each { |type| @types[type] = PrimitiveType.new(type) } + + @data['types'].each { |type| get_or_create_type(type) } + # @data['messages'].each { |type| get_or_create_type(type) } + + fields = @data['variables']&.map { |var| + OpenStruct.new({'name' => var['name'], 'type' => get_or_create_type(var['type']), 'data' => var}) + } || [] + @variables = StructType.new("variables", @data['variables'], fields) + end + + def get_or_create_type(str) + str = str['name'] if str.is_a?(Hash) + existing_type = @types[str] + return existing_type if existing_type + m = str.match /(\w+)(\<(\w+)\>)?/ + base_type_name, sub_type = m[1], m[3] + if sub_type + raise "Unknown container type" unless %(Vector Map IterableMap).include? base_type_name + sub_type = get_or_create_type(sub_type) + @types[str] = ContainerType.new(str, base_type_name, sub_type) + else + type_dscr = @data['types'].find { _1['name'] == str } + raise "Type not found: #{str}" unless type_dscr + fields = [] + type_dscr['fields'].each do |field| + type = get_or_create_type(field['type']) + fields << OpenStruct.new({'name' => field['name'], 'type' => type, 'descr' => field}) + end + descr = @data['types'].find{ _1['name'] == str } + @types[str] = StructType.new(str, descr, fields) + end + end +end + +def generate + if options.data_file + data = YAML.load_file(options.data_file) + else + m = File.read(INPUT_FILE).match(/STORAGE_YAML *= *R"\((.*?)\)"/m) + begin + data = YAML.load(m ? m[1] : File.read(INPUT_FILE)) + rescue + return + end + end + data['types'] ||= [] + canonicalize(data) + + ir = Ir.new(data) + ir.load() + + filename = "#{__dir__}/templates/#{options.generate}.erb" + t = ERB.new(File.read(filename), nil, '%-') + t.filename = filename + res = t.result(binding) + if options.output + Pathname(options.output).dirname.mkpath + File.write(options.output, res) + # puts res + else + puts res + end +end + +def compile + raise "Path to `ecc` compiler must be specified" unless options.ecc + clang_cmd = "#{options.ecc} #{INPUT_FILE} -o #{options.output} " + clang_cmd += ' -v ' if options.verbose + clang_cmd += ' -std=c++17 ' + clang_cmd += ' -O3 ' + clang_cmd += ' -Xlinker --no-ctor ' if options.no_ctor + clang_cmd += " -Xlinker #{options.linker_args}" if options.linker_args + clang_cmd += " #{options.clang_args}" if options.clang_args + clang_cmd += " -nostdlib" if options.no_stdlib + clang_cmd += " -save-temps" if options.save_temps + + command(clang_cmd) +end + +def main + generate if options.generate + compile if options.compile +end + +main diff --git a/llvm/projects/evm-tools/templates/contract.erb b/llvm/projects/evm-tools/templates/contract.erb new file mode 100644 index 000000000000..d2722760fc1b --- /dev/null +++ b/llvm/projects/evm-tools/templates/contract.erb @@ -0,0 +1,83 @@ +#include "evm_sdk.h" +#include "storage.h" + +namespace <%= ir.namespace %> { + +% ir.types.select{ _2.is_a? (StructType) }.each do |name, type| +struct <%= type.name %> { + static constexpr unsigned FIELDS_NUM = <%= type.slots_size %>; + + <%= type.name %>(uint256_t key): <%= type.fields.map.with_index{ |x, i| x.name + '(key + ' + i.to_s + ')'}.join(', ')%> {} + +% # <%= type['name'] %>(__int256_t key): <%= type['ctor_initializer'] %> {} +% type.fields&.each do |field| + <%= field.type.cpp_name %> <%= field.name %>; +% end +}; + +template<__uint256_t Key> +struct <%= type.name %>Static { + static constexpr unsigned FIELDS_NUM = <%= type.slots_size %>; +% i = 0 +% type.fields&.each do |field| + <%= field.type.cpp_name_static(lambda{'Key + ' + i.to_s}) %> <%= field.name %>; +% i += field.type.slots_size +% end +}; + +% end + +% i = 0 +% ir.variables&.fields.each do |field| +<%= field.type.cpp_name_static(i) %> <%= field.name %>; +% i += field.type.slots_size +% end + +} // namespace <%= ir.namespace %> + +% ir.variables&.fields&.select{ _1.data['getter'] }.each do |field| +[[evm]] uint256_t get_<%= field.name %>() { + return <%= ir.namespace %>::<%= field.name %>.get(); +} + +% end + +namespace evm::constants { + +% ir.data['constants']&.each do |constant| +% type = constant['type'] +% if constant.include?('constants') + +namespace <%= constant['name'] %> { +% constant['constants'].each do |constant| + static constexpr uint256_t <%= constant['name'] %> = <%= constant['value'] %>; +% end +} +% else +static constexpr uint256_t <%= constant['name'] %> = <%= constant['value'] %>; +% end +% end + +} // namespace evm::constants + +namespace evm::ser { + +% ir.types.each do |name, type| +% next unless type.is_a?(StructType) +% next if type.descr['flags']&.include?("storage") +struct DVM_PACKED <%= name %> { +% if type.descr['flags']&.include?("message") + MessageHeader header; +% end +% type.fields.each do |field| +% init = field.descr.include?('constant') ? 'evm::constants::' + field.descr['constant'] : '' + <%= field.type.host_name %> <%= field.name %>{<%= init %>}; +% end + static constexpr unsigned size() { + return sizeof(<%= name %>); + } +}; + +% end + +} // namespace evm::ser \ No newline at end of file diff --git a/llvm/projects/evm-tools/templates/host.erb b/llvm/projects/evm-tools/templates/host.erb new file mode 100644 index 000000000000..06866e996d9f --- /dev/null +++ b/llvm/projects/evm-tools/templates/host.erb @@ -0,0 +1,40 @@ + +namespace dvm::gen { + +namespace config_slots { +enum { +% slot = 0 +% ir.variables.fields.each do |var| + <%= var.name %> = <%= slot %>, +% if var.type.is_a?(StructType) +% var.type.fields.each do |field| +% raise "Unsupported nested struct type" if field.type.is_a?(StructType) + <%= var.name %>_<%= field.name %> = <%= slot %>, +% slot += field.type.slots_size +% end +% else +% slot += var.type.slots_size +% end +% end +}; + +} + +namespace config_sizes { +enum { +% ir.variables.fields.each do |var| +% if var.type.is_a?(StructType) +% var.type.fields.each do |field| +% next unless field.type.is_a?(PrimitiveType) + <%= var.name %>_<%= field.name %> = <%= field.type.bytes_size %>, +% slot += field.type.slots_size +% end +% elsif var.type.is_a?(PrimitiveType) + <%= var.name %> = <%= var.type.bytes_size %>, +% slot += var.type.slots_size +% end +% end +}; +} + +} // namespace dvm::gen \ No newline at end of file diff --git a/llvm/projects/evm-tools/templates/python.erb b/llvm/projects/evm-tools/templates/python.erb new file mode 100644 index 000000000000..1948df7b5b02 --- /dev/null +++ b/llvm/projects/evm-tools/templates/python.erb @@ -0,0 +1,79 @@ +import re, struct +from dataclasses import dataclass, fields, field + +def unpack(type, data: str | bytes): + if isinstance(data, str): + data = bytes.fromhex(data) + assert (len(data) >= type.SIZE) + formats = type.FORMAT.split("-") + assert len(formats) == len(fields(type)) + + obj = type() + pos = 0 + for i, field in enumerate(fields(type)): + m = re.match(r"[iu](\d+)$", formats[i]) + if not m: + cls = globals()[formats[i]] + val = unpack(cls, data) + size = cls.SIZE + #raise Exception(f"Unknown format: {formats[i]}") + else: + size = int(m.group(1)) // 8 + val = int.from_bytes(data[pos:size], "big", signed=False) + setattr(obj, field.name, val) + data = data[size:] + return obj + + +def unpack_array(type, data: str | bytes): + if isinstance(data, str): + data = bytes.fromhex(data) + size = struct.unpack(">I", data[:4])[0] + items = [] + data = data[4:] + while len(data) != 0: + assert (len(data) >= type.SIZE) + items.append(unpack(type, data)) + data = data[type.SIZE:] + assert size == len(items) + return items + + +def pack(obj): + formats = obj.FORMAT.split("-") + data = b"" + for i, field in enumerate(fields(obj.__class__)): + val = getattr(obj, field.name) + if m := re.match(r"[iu](\d+)$", formats[i]): + size = int(m.group(1)) // 8 + data += val.to_bytes(size, byteorder="big", signed=False) + else: + data += pack(val) + return data + + +@dataclass +class Message: + addr_from: int | None = None + addr_to: int | None = None + amount: int | None = None + FORMAT = "u256-u256-u256" + SIZE = 32 * 3 + +% ir.types.select{ _2.is_a?(StructType) && !_2.descr['flags']&.include?("storage") }.each do |name, type| + +@dataclass +class <%= name %>: +% if type.descr['flags']&.include?("message") +% msg = "Message-" + message_header: Message | None = None +% end +% type.fields.each do |field| + <%= field.name %>: <%= field.type.python_name() %> | None = None +% end + FORMAT = "<%= msg %><%= type.fields.map(&:type).map(&:name).join('-') %>" + SIZE = <%= type.fields.map(&:type).sum(&:bytes_size) %> + + def pack(self): + return pack(self) +% end \ No newline at end of file diff --git a/llvm/tools/evm-linker/evm_common.h b/llvm/tools/evm-linker/evm_common.h index 8e1f16f4e467..9e81c51a534c 100644 --- a/llvm/tools/evm-linker/evm_common.h +++ b/llvm/tools/evm-linker/evm_common.h @@ -30,6 +30,9 @@ // Offset to the data where init data will be located. static constexpr unsigned InitDataOffset = 32 * 2; +// Slot of the size of the arbitrary-sized return data. +static constexpr unsigned ReturnSizeOffset = 32 * 1; + // Default size of memory chunk to be fixed by resolved symbol relocation. static constexpr unsigned SymbolRelocSizeInBytes = 4; diff --git a/llvm/tools/evm-linker/evm_linker.cpp b/llvm/tools/evm-linker/evm_linker.cpp index a0677cd30872..4f69bf0d1125 100644 --- a/llvm/tools/evm-linker/evm_linker.cpp +++ b/llvm/tools/evm-linker/evm_linker.cpp @@ -307,13 +307,24 @@ void EvmLinker::buildDispatcher() { Dispatcher.inst(opcodes::JUMP); Dispatcher.jump_dest(Func->Name + "#return"); if (!Func->Outputs.empty()) { - // TODO: Should read function's abi to determine return size. - assert(Func->Outputs.size() == 1); + assert(Func->Outputs.size() == 1 && "Multiple return is not supported"); + auto& Output = Func->Outputs.front(); + if (Output == EVM::ValType::PTR) { + // Function returns data with arbitrary size. Size is stored in the memory at slot + // `ReturnSizeOffset`. Let's read it and return to the caller as a size. + Dispatcher.inst(opcodes::PUSH1, ReturnSizeOffset); + Dispatcher.inst(opcodes::MLOAD); + Dispatcher.inst(opcodes::SWAP1); + } else { + Dispatcher.inst(opcodes::PUSH1, 0); + Dispatcher.inst(opcodes::MSTORE); + Dispatcher.inst(opcodes::PUSH1, 0x20); + Dispatcher.inst(opcodes::PUSH1, 0); + } + } else { + Dispatcher.inst(opcodes::PUSH1, 0); Dispatcher.inst(opcodes::PUSH1, 0); - Dispatcher.inst(opcodes::MSTORE); } - Dispatcher.inst(opcodes::PUSH1, 0x20); - Dispatcher.inst(opcodes::PUSH1, 0); Dispatcher.inst(opcodes::RETURN); } }