From fdfc444bd0596c5dba2e6652acf000845d862ee2 Mon Sep 17 00:00:00 2001 From: IgorKoval Date: Thu, 4 Jun 2020 18:26:18 +0300 Subject: [PATCH] 0.25 --- .gitignore | 2 + API.md | 1108 +++++-- README.md | Bin 2956 -> 1753 bytes compiler/CMakeLists.txt | 7 - compiler/libevmasm/Assembly.cpp | 657 ----- compiler/libevmasm/Assembly.h | 179 -- compiler/libevmasm/AssemblyItem.cpp | 283 -- compiler/libevmasm/AssemblyItem.h | 178 -- compiler/libevmasm/BlockDeduplicator.cpp | 146 - compiler/libevmasm/BlockDeduplicator.h | 90 - compiler/libevmasm/CMakeLists.txt | 39 - .../CommonSubexpressionEliminator.cpp | 506 ---- .../libevmasm/CommonSubexpressionEliminator.h | 187 -- compiler/libevmasm/ConstantOptimiser.cpp | 330 --- compiler/libevmasm/ConstantOptimiser.h | 164 -- compiler/libevmasm/ControlFlowGraph.cpp | 373 --- compiler/libevmasm/ControlFlowGraph.h | 127 - compiler/libevmasm/Exceptions.h | 37 - compiler/libevmasm/ExpressionClasses.cpp | 224 -- compiler/libevmasm/ExpressionClasses.h | 128 - compiler/libevmasm/GasMeter.cpp | 287 -- compiler/libevmasm/GasMeter.h | 180 -- compiler/libevmasm/Instruction.cpp | 387 --- compiler/libevmasm/Instruction.h | 318 --- compiler/libevmasm/JumpdestRemover.cpp | 66 - compiler/libevmasm/JumpdestRemover.h | 47 - compiler/libevmasm/KnownState.cpp | 418 --- compiler/libevmasm/KnownState.h | 182 -- compiler/libevmasm/LinkerObject.cpp | 86 - compiler/libevmasm/LinkerObject.h | 65 - compiler/libevmasm/PathGasMeter.cpp | 139 - compiler/libevmasm/PathGasMeter.h | 85 - compiler/libevmasm/PeepholeOptimiser.cpp | 375 --- compiler/libevmasm/PeepholeOptimiser.h | 53 - compiler/libevmasm/RuleList.h | 674 ----- compiler/libevmasm/SemanticInformation.cpp | 318 --- compiler/libevmasm/SemanticInformation.h | 75 - compiler/libevmasm/SimplificationRule.h | 158 - compiler/libevmasm/SimplificationRules.cpp | 233 -- compiler/libevmasm/SimplificationRules.h | 159 -- compiler/liblangutil/CMakeLists.txt | 1 - compiler/liblangutil/EVMVersion.cpp | 28 +- compiler/liblangutil/EVMVersion.h | 4 - compiler/libsolc/libsolc.cpp | 2 - compiler/libsolidity/CMakeLists.txt | 64 +- .../analysis/ControlFlowBuilder.cpp | 12 +- .../libsolidity/analysis/GlobalContext.cpp | 3 +- .../analysis/NameAndTypeResolver.cpp | 16 - .../analysis/ReferencesResolver.cpp | 118 +- .../libsolidity/analysis/ReferencesResolver.h | 11 +- .../libsolidity/analysis/StaticAnalyzer.cpp | 15 +- .../libsolidity/analysis/SyntaxChecker.cpp | 14 +- compiler/libsolidity/analysis/TypeChecker.cpp | 224 +- compiler/libsolidity/analysis/TypeChecker.h | 2 + .../libsolidity/analysis/ViewPureChecker.cpp | 115 +- compiler/libsolidity/ast/AST.cpp | 10 + compiler/libsolidity/ast/AST.h | 20 +- compiler/libsolidity/ast/ASTAnnotations.h | 11 - compiler/libsolidity/ast/ASTJsonConverter.cpp | 40 +- compiler/libsolidity/ast/ASTJsonConverter.h | 1 - compiler/libsolidity/ast/ASTJsonImporter.cpp | 10 +- compiler/libsolidity/ast/AST_accept.h | 3 + compiler/libsolidity/ast/TypeProvider.cpp | 7 +- compiler/libsolidity/ast/TypeProvider.h | 3 +- compiler/libsolidity/ast/Types.cpp | 139 +- compiler/libsolidity/ast/Types.h | 16 +- compiler/libsolidity/codegen/ABIFunctions.cpp | 1532 ---------- compiler/libsolidity/codegen/ABIFunctions.h | 267 -- compiler/libsolidity/codegen/ArrayUtils.cpp | 1209 -------- compiler/libsolidity/codegen/ArrayUtils.h | 120 - compiler/libsolidity/codegen/Compiler.cpp | 63 - compiler/libsolidity/codegen/Compiler.h | 87 - .../libsolidity/codegen/CompilerContext.cpp | 559 ---- .../libsolidity/codegen/CompilerContext.h | 358 --- .../libsolidity/codegen/CompilerUtils.cpp | 1513 ---------- compiler/libsolidity/codegen/CompilerUtils.h | 329 --- .../libsolidity/codegen/ContractCompiler.cpp | 1343 --------- .../libsolidity/codegen/ContractCompiler.h | 161 -- .../codegen/ExpressionCompiler.cpp | 2536 ----------------- .../libsolidity/codegen/ExpressionCompiler.h | 157 - compiler/libsolidity/codegen/LValue.cpp | 534 ---- compiler/libsolidity/codegen/LValue.h | 197 -- .../codegen/MultiUseYulFunctionCollector.cpp | 52 - .../codegen/MultiUseYulFunctionCollector.h | 53 - compiler/libsolidity/codegen/TVM.cpp | 16 +- compiler/libsolidity/codegen/TVM.h | 9 +- compiler/libsolidity/codegen/TVMABI.cpp | 178 +- compiler/libsolidity/codegen/TVMABI.hpp | 17 +- compiler/libsolidity/codegen/TVMCommons.cpp | 57 +- compiler/libsolidity/codegen/TVMCommons.hpp | 34 +- compiler/libsolidity/codegen/TVMCompiler.hpp | 1691 ----------- compiler/libsolidity/codegen/TVMConstants.hpp | 1 + .../codegen/TVMContractCompiler.cpp | 8 +- .../codegen/TVMContractCompiler.hpp | 2 +- .../codegen/TVMExpressionCompiler.cpp | 733 +++-- .../codegen/TVMExpressionCompiler.hpp | 17 +- .../libsolidity/codegen/TVMFunctionCall.cpp | 316 +- .../libsolidity/codegen/TVMFunctionCall.hpp | 3 +- .../codegen/TVMFunctionCompiler.cpp | 98 +- .../codegen/TVMFunctionCompiler.hpp | 3 +- .../libsolidity/codegen/TVMIntrinsics.cpp | 39 - .../libsolidity/codegen/TVMOptimizations.cpp | 80 +- compiler/libsolidity/codegen/TVMPusher.cpp | 720 +++-- compiler/libsolidity/codegen/TVMPusher.hpp | 55 +- .../libsolidity/codegen/TVMStructCompiler.cpp | 271 +- .../libsolidity/codegen/TVMStructCompiler.hpp | 27 +- .../libsolidity/codegen/TVMTypeChecker.cpp | 36 +- .../libsolidity/codegen/TVMTypeChecker.hpp | 2 +- .../libsolidity/codegen/YulUtilFunctions.cpp | 1945 ------------- .../libsolidity/codegen/YulUtilFunctions.h | 307 -- .../codegen/ir/IRGenerationContext.cpp | 170 -- .../codegen/ir/IRGenerationContext.h | 119 - .../libsolidity/codegen/ir/IRGenerator.cpp | 395 --- compiler/libsolidity/codegen/ir/IRGenerator.h | 84 - .../codegen/ir/IRGeneratorForStatements.cpp | 1436 ---------- .../codegen/ir/IRGeneratorForStatements.h | 113 - compiler/libsolidity/codegen/ir/IRLValue.cpp | 228 -- compiler/libsolidity/codegen/ir/IRLValue.h | 129 - compiler/libsolidity/codegen/ir/README.md | 10 - compiler/libsolidity/formal/BMC.cpp | 905 ------ compiler/libsolidity/formal/BMC.h | 188 -- compiler/libsolidity/formal/CHC.cpp | 738 ----- compiler/libsolidity/formal/CHC.h | 217 -- .../formal/CHCSmtLib2Interface.cpp | 168 -- .../libsolidity/formal/CHCSmtLib2Interface.h | 73 - .../libsolidity/formal/CHCSolverInterface.h | 50 - compiler/libsolidity/formal/CVC4Interface.cpp | 246 -- compiler/libsolidity/formal/CVC4Interface.h | 70 - .../libsolidity/formal/EncodingContext.cpp | 263 -- compiler/libsolidity/formal/EncodingContext.h | 186 -- compiler/libsolidity/formal/ModelChecker.cpp | 62 - compiler/libsolidity/formal/ModelChecker.h | 75 - compiler/libsolidity/formal/SMTEncoder.cpp | 1684 ----------- compiler/libsolidity/formal/SMTEncoder.h | 274 -- .../libsolidity/formal/SMTLib2Interface.cpp | 246 -- .../libsolidity/formal/SMTLib2Interface.h | 84 - compiler/libsolidity/formal/SMTPortfolio.cpp | 152 - compiler/libsolidity/formal/SMTPortfolio.h | 68 - compiler/libsolidity/formal/SSAVariable.cpp | 33 - compiler/libsolidity/formal/SSAVariable.h | 48 - compiler/libsolidity/formal/SolverInterface.h | 375 --- compiler/libsolidity/formal/SymbolicTypes.cpp | 354 --- compiler/libsolidity/formal/SymbolicTypes.h | 72 - .../libsolidity/formal/SymbolicVariables.cpp | 264 -- .../libsolidity/formal/SymbolicVariables.h | 253 -- compiler/libsolidity/formal/VariableUsage.cpp | 110 - compiler/libsolidity/formal/VariableUsage.h | 59 - .../libsolidity/formal/Z3CHCInterface.cpp | 111 - compiler/libsolidity/formal/Z3CHCInterface.h | 55 - compiler/libsolidity/formal/Z3Interface.cpp | 234 -- compiler/libsolidity/formal/Z3Interface.h | 68 - compiler/libsolidity/interface/ABI.cpp | 201 -- compiler/libsolidity/interface/ABI.h | 67 - .../libsolidity/interface/CompilerStack.cpp | 457 +-- .../libsolidity/interface/CompilerStack.h | 55 +- .../libsolidity/interface/GasEstimator.cpp | 210 -- compiler/libsolidity/interface/GasEstimator.h | 88 - .../interface/StandardCompiler.cpp | 154 +- .../libsolidity/interface/StorageLayout.cpp | 119 - .../libsolidity/interface/StorageLayout.h | 55 - compiler/libsolidity/parsing/Parser.cpp | 40 +- compiler/libyul/AsmAnalysis.cpp | 672 ----- compiler/libyul/AsmAnalysis.h | 124 - compiler/libyul/AsmAnalysisInfo.h | 43 - compiler/libyul/AsmData.h | 89 - compiler/libyul/AsmDataForward.h | 52 - compiler/libyul/AsmJsonConverter.cpp | 203 -- compiler/libyul/AsmJsonConverter.h | 68 - compiler/libyul/AsmParser.cpp | 561 ---- compiler/libyul/AsmParser.h | 98 - compiler/libyul/AsmPrinter.cpp | 254 -- compiler/libyul/AsmPrinter.h | 66 - compiler/libyul/AsmScope.cpp | 91 - compiler/libyul/AsmScope.h | 93 - compiler/libyul/AsmScopeFiller.cpp | 178 -- compiler/libyul/AsmScopeFiller.h | 80 - compiler/libyul/AssemblyStack.cpp | 248 -- compiler/libyul/AssemblyStack.h | 119 - compiler/libyul/CMakeLists.txt | 162 -- compiler/libyul/CompilabilityChecker.cpp | 79 - compiler/libyul/CompilabilityChecker.h | 53 - compiler/libyul/Dialect.cpp | 53 - compiler/libyul/Dialect.h | 73 - compiler/libyul/Exceptions.h | 38 - compiler/libyul/Object.cpp | 73 - compiler/libyul/Object.h | 79 - compiler/libyul/ObjectParser.cpp | 149 - compiler/libyul/ObjectParser.h | 70 - compiler/libyul/SideEffects.h | 88 - compiler/libyul/Utilities.cpp | 146 - compiler/libyul/Utilities.h | 71 - compiler/libyul/YulString.h | 169 -- .../libyul/backends/evm/AbstractAssembly.h | 127 - compiler/libyul/backends/evm/AsmCodeGen.cpp | 218 -- compiler/libyul/backends/evm/AsmCodeGen.h | 87 - .../libyul/backends/evm/ConstantOptimiser.cpp | 224 -- .../libyul/backends/evm/ConstantOptimiser.h | 108 - compiler/libyul/backends/evm/EVMAssembly.cpp | 224 -- compiler/libyul/backends/evm/EVMAssembly.h | 104 - .../libyul/backends/evm/EVMCodeTransform.cpp | 729 ----- .../libyul/backends/evm/EVMCodeTransform.h | 235 -- compiler/libyul/backends/evm/EVMDialect.cpp | 289 -- compiler/libyul/backends/evm/EVMDialect.h | 108 - compiler/libyul/backends/evm/EVMMetrics.cpp | 118 - compiler/libyul/backends/evm/EVMMetrics.h | 100 - .../libyul/backends/evm/EVMObjectCompiler.cpp | 63 - .../libyul/backends/evm/EVMObjectCompiler.h | 45 - .../libyul/backends/evm/NoOutputAssembly.cpp | 162 -- .../libyul/backends/evm/NoOutputAssembly.h | 89 - .../libyul/backends/wasm/BinaryTransform.cpp | 592 ---- .../libyul/backends/wasm/BinaryTransform.h | 91 - .../backends/wasm/EVMToEwasmTranslator.cpp | 1288 --------- .../backends/wasm/EVMToEwasmTranslator.h | 46 - .../libyul/backends/wasm/TextTransform.cpp | 195 -- compiler/libyul/backends/wasm/TextTransform.h | 68 - compiler/libyul/backends/wasm/WasmAST.h | 102 - .../backends/wasm/WasmCodeTransform.cpp | 393 --- .../libyul/backends/wasm/WasmCodeTransform.h | 101 - compiler/libyul/backends/wasm/WasmDialect.cpp | 202 -- compiler/libyul/backends/wasm/WasmDialect.h | 70 - .../backends/wasm/WasmObjectCompiler.cpp | 57 - .../libyul/backends/wasm/WasmObjectCompiler.h | 52 - .../backends/wasm/WordSizeTransform.cpp | 412 --- .../libyul/backends/wasm/WordSizeTransform.h | 112 - compiler/libyul/optimiser/ASTCopier.cpp | 172 -- compiler/libyul/optimiser/ASTCopier.h | 120 - compiler/libyul/optimiser/ASTWalker.cpp | 180 -- compiler/libyul/optimiser/ASTWalker.h | 104 - compiler/libyul/optimiser/BlockFlattener.cpp | 42 - compiler/libyul/optimiser/BlockFlattener.h | 38 - compiler/libyul/optimiser/BlockHasher.cpp | 193 -- compiler/libyul/optimiser/BlockHasher.h | 109 - .../libyul/optimiser/CallGraphGenerator.cpp | 66 - .../libyul/optimiser/CallGraphGenerator.h | 65 - .../CommonSubexpressionEliminator.cpp | 105 - .../optimiser/CommonSubexpressionEliminator.h | 56 - .../optimiser/ConditionalSimplifier.cpp | 93 - .../libyul/optimiser/ConditionalSimplifier.h | 71 - .../optimiser/ConditionalUnsimplifier.cpp | 99 - .../optimiser/ConditionalUnsimplifier.h | 51 - .../optimiser/ControlFlowSimplifier.cpp | 233 -- .../libyul/optimiser/ControlFlowSimplifier.h | 73 - .../libyul/optimiser/DataFlowAnalyzer.cpp | 403 --- compiler/libyul/optimiser/DataFlowAnalyzer.h | 182 -- .../libyul/optimiser/DeadCodeEliminator.cpp | 66 - .../libyul/optimiser/DeadCodeEliminator.h | 64 - compiler/libyul/optimiser/Disambiguator.cpp | 76 - compiler/libyul/optimiser/Disambiguator.h | 72 - .../optimiser/EquivalentFunctionCombiner.cpp | 40 - .../optimiser/EquivalentFunctionCombiner.h | 53 - .../optimiser/EquivalentFunctionDetector.cpp | 42 - .../optimiser/EquivalentFunctionDetector.h | 56 - .../libyul/optimiser/ExpressionInliner.cpp | 79 - compiler/libyul/optimiser/ExpressionInliner.h | 73 - .../libyul/optimiser/ExpressionJoiner.cpp | 144 - compiler/libyul/optimiser/ExpressionJoiner.h | 99 - .../libyul/optimiser/ExpressionSimplifier.cpp | 55 - .../libyul/optimiser/ExpressionSimplifier.h | 55 - .../libyul/optimiser/ExpressionSplitter.cpp | 112 - .../libyul/optimiser/ExpressionSplitter.h | 87 - .../optimiser/ForLoopConditionIntoBody.cpp | 67 - .../optimiser/ForLoopConditionIntoBody.h | 57 - .../optimiser/ForLoopConditionOutOfBody.cpp | 74 - .../optimiser/ForLoopConditionOutOfBody.h | 74 - .../libyul/optimiser/ForLoopInitRewriter.cpp | 50 - .../libyul/optimiser/ForLoopInitRewriter.h | 46 - compiler/libyul/optimiser/FullInliner.cpp | 263 -- compiler/libyul/optimiser/FullInliner.h | 165 -- compiler/libyul/optimiser/FunctionGrouper.cpp | 61 - compiler/libyul/optimiser/FunctionGrouper.h | 53 - compiler/libyul/optimiser/FunctionHoister.cpp | 49 - compiler/libyul/optimiser/FunctionHoister.h | 54 - .../InlinableExpressionFunctionFinder.cpp | 69 - .../InlinableExpressionFunctionFinder.h | 66 - compiler/libyul/optimiser/KnowledgeBase.cpp | 89 - compiler/libyul/optimiser/KnowledgeBase.h | 58 - compiler/libyul/optimiser/LoadResolver.cpp | 81 - compiler/libyul/optimiser/LoadResolver.h | 72 - .../optimiser/LoopInvariantCodeMotion.cpp | 115 - .../optimiser/LoopInvariantCodeMotion.h | 67 - compiler/libyul/optimiser/MainFunction.cpp | 53 - compiler/libyul/optimiser/MainFunction.h | 46 - compiler/libyul/optimiser/Metrics.cpp | 177 -- compiler/libyul/optimiser/Metrics.h | 109 - compiler/libyul/optimiser/NameCollector.cpp | 109 - compiler/libyul/optimiser/NameCollector.h | 116 - compiler/libyul/optimiser/NameDispenser.cpp | 66 - compiler/libyul/optimiser/NameDispenser.h | 61 - compiler/libyul/optimiser/NameDisplacer.cpp | 85 - compiler/libyul/optimiser/NameDisplacer.h | 73 - compiler/libyul/optimiser/OptimiserStep.h | 65 - .../libyul/optimiser/OptimizerUtilities.cpp | 39 - .../libyul/optimiser/OptimizerUtilities.h | 32 - compiler/libyul/optimiser/README.md | 639 ----- .../optimiser/RedundantAssignEliminator.cpp | 314 -- .../optimiser/RedundantAssignEliminator.h | 195 -- compiler/libyul/optimiser/Rematerialiser.cpp | 117 - compiler/libyul/optimiser/Rematerialiser.h | 109 - compiler/libyul/optimiser/SSAReverser.cpp | 120 - compiler/libyul/optimiser/SSAReverser.h | 85 - compiler/libyul/optimiser/SSATransform.cpp | 385 --- compiler/libyul/optimiser/SSATransform.h | 97 - compiler/libyul/optimiser/SSAValueTracker.cpp | 72 - compiler/libyul/optimiser/SSAValueTracker.h | 63 - compiler/libyul/optimiser/Semantics.cpp | 212 -- compiler/libyul/optimiser/Semantics.h | 204 -- .../libyul/optimiser/SimplificationRules.cpp | 250 -- .../libyul/optimiser/SimplificationRules.h | 138 - compiler/libyul/optimiser/StackCompressor.cpp | 203 -- compiler/libyul/optimiser/StackCompressor.h | 54 - .../libyul/optimiser/StructuralSimplifier.cpp | 131 - .../libyul/optimiser/StructuralSimplifier.h | 57 - compiler/libyul/optimiser/Substitution.cpp | 39 - compiler/libyul/optimiser/Substitution.h | 46 - compiler/libyul/optimiser/Suite.cpp | 439 --- compiler/libyul/optimiser/Suite.h | 85 - .../libyul/optimiser/SyntacticalEquality.cpp | 168 -- .../libyul/optimiser/SyntacticalEquality.h | 87 - compiler/libyul/optimiser/UnusedPruner.cpp | 190 -- compiler/libyul/optimiser/UnusedPruner.h | 128 - .../libyul/optimiser/VarDeclInitializer.cpp | 57 - .../libyul/optimiser/VarDeclInitializer.h | 42 - compiler/libyul/optimiser/VarNameCleaner.cpp | 128 - compiler/libyul/optimiser/VarNameCleaner.h | 97 - compiler/scripts/ASTImportTest.sh | 0 compiler/scripts/build.sh | 0 compiler/scripts/build_emscripten.sh | 0 .../scripts/bytecodecompare/prepare_report.py | 0 .../scripts/bytecodecompare/storebytecode.sh | 0 compiler/scripts/check_style.sh | 0 compiler/scripts/create_source_tarball.sh | 0 compiler/scripts/deps-ppa/static_z3.sh | 0 compiler/scripts/docker_build.sh | 0 compiler/scripts/docker_deploy.sh | 0 compiler/scripts/docker_deploy_manual.sh | 0 compiler/scripts/docs.sh | 0 compiler/scripts/download_ossfuzz_corpus.sh | 0 compiler/scripts/extract_test_cases.py | 0 .../fix_homebrew_paths_in_standalone_zip.py | 0 compiler/scripts/get_version.sh | 0 compiler/scripts/install_cmake.sh | 0 compiler/scripts/install_deps.sh | 0 compiler/scripts/install_lib_variable.sh | 7 + .../scripts/install_obsolete_jsoncpp_1_7_4.sh | 0 compiler/scripts/isolate_tests.py | 0 compiler/scripts/isoltest.sh | 0 compiler/scripts/pylint_all.py | 0 compiler/scripts/regressions.py | 0 compiler/scripts/release.sh | 0 compiler/scripts/release_emscripten.sh | 0 compiler/scripts/release_ppa.sh | 0 compiler/scripts/report_errors.sh | 0 compiler/scripts/run_proofs.sh | 0 compiler/scripts/soltest.sh | 0 compiler/scripts/splitSources.py | 0 compiler/scripts/test_emscripten.sh | 0 compiler/scripts/tests.sh | 0 .../travis-emscripten/build_emscripten.sh | 0 .../scripts/travis-emscripten/install_deps.sh | 0 .../travis-emscripten/publish_binary.sh | 0 compiler/scripts/uniqueErrors.sh | 0 compiler/scripts/update_bugs_by_version.py | 0 compiler/solc/CommandLineInterface.cpp | 1152 +------- compiler/solc/CommandLineInterface.h | 1 - compiler/test/buglistTests.js | 0 compiler/test/cmdlineTests.sh | 0 compiler/test/docsCodeStyle.sh | 0 compiler/test/externalTests.sh | 0 compiler/test/externalTests/colony.sh | 0 compiler/test/externalTests/gnosis.sh | 0 .../test/externalTests/solc-js/solc-js.sh | 0 compiler/test/externalTests/zeppelin.sh | 0 lib/stdlib_sol.tvm | 46 +- 373 files changed, 2719 insertions(+), 60489 deletions(-) create mode 100644 .gitignore delete mode 100644 compiler/libevmasm/Assembly.cpp delete mode 100644 compiler/libevmasm/Assembly.h delete mode 100644 compiler/libevmasm/AssemblyItem.cpp delete mode 100644 compiler/libevmasm/AssemblyItem.h delete mode 100644 compiler/libevmasm/BlockDeduplicator.cpp delete mode 100644 compiler/libevmasm/BlockDeduplicator.h delete mode 100644 compiler/libevmasm/CMakeLists.txt delete mode 100644 compiler/libevmasm/CommonSubexpressionEliminator.cpp delete mode 100644 compiler/libevmasm/CommonSubexpressionEliminator.h delete mode 100644 compiler/libevmasm/ConstantOptimiser.cpp delete mode 100644 compiler/libevmasm/ConstantOptimiser.h delete mode 100644 compiler/libevmasm/ControlFlowGraph.cpp delete mode 100644 compiler/libevmasm/ControlFlowGraph.h delete mode 100644 compiler/libevmasm/Exceptions.h delete mode 100644 compiler/libevmasm/ExpressionClasses.cpp delete mode 100644 compiler/libevmasm/ExpressionClasses.h delete mode 100644 compiler/libevmasm/GasMeter.cpp delete mode 100644 compiler/libevmasm/GasMeter.h delete mode 100644 compiler/libevmasm/Instruction.cpp delete mode 100644 compiler/libevmasm/Instruction.h delete mode 100644 compiler/libevmasm/JumpdestRemover.cpp delete mode 100644 compiler/libevmasm/JumpdestRemover.h delete mode 100644 compiler/libevmasm/KnownState.cpp delete mode 100644 compiler/libevmasm/KnownState.h delete mode 100644 compiler/libevmasm/LinkerObject.cpp delete mode 100644 compiler/libevmasm/LinkerObject.h delete mode 100644 compiler/libevmasm/PathGasMeter.cpp delete mode 100644 compiler/libevmasm/PathGasMeter.h delete mode 100644 compiler/libevmasm/PeepholeOptimiser.cpp delete mode 100644 compiler/libevmasm/PeepholeOptimiser.h delete mode 100644 compiler/libevmasm/RuleList.h delete mode 100644 compiler/libevmasm/SemanticInformation.cpp delete mode 100644 compiler/libevmasm/SemanticInformation.h delete mode 100644 compiler/libevmasm/SimplificationRule.h delete mode 100644 compiler/libevmasm/SimplificationRules.cpp delete mode 100644 compiler/libevmasm/SimplificationRules.h delete mode 100644 compiler/libsolidity/codegen/ABIFunctions.cpp delete mode 100644 compiler/libsolidity/codegen/ABIFunctions.h delete mode 100644 compiler/libsolidity/codegen/ArrayUtils.cpp delete mode 100644 compiler/libsolidity/codegen/ArrayUtils.h delete mode 100644 compiler/libsolidity/codegen/Compiler.cpp delete mode 100644 compiler/libsolidity/codegen/Compiler.h delete mode 100644 compiler/libsolidity/codegen/CompilerContext.cpp delete mode 100644 compiler/libsolidity/codegen/CompilerContext.h delete mode 100644 compiler/libsolidity/codegen/CompilerUtils.cpp delete mode 100644 compiler/libsolidity/codegen/CompilerUtils.h delete mode 100644 compiler/libsolidity/codegen/ContractCompiler.cpp delete mode 100644 compiler/libsolidity/codegen/ContractCompiler.h delete mode 100644 compiler/libsolidity/codegen/ExpressionCompiler.cpp delete mode 100644 compiler/libsolidity/codegen/ExpressionCompiler.h delete mode 100644 compiler/libsolidity/codegen/LValue.cpp delete mode 100644 compiler/libsolidity/codegen/LValue.h delete mode 100644 compiler/libsolidity/codegen/MultiUseYulFunctionCollector.cpp delete mode 100644 compiler/libsolidity/codegen/MultiUseYulFunctionCollector.h delete mode 100644 compiler/libsolidity/codegen/TVMCompiler.hpp delete mode 100644 compiler/libsolidity/codegen/YulUtilFunctions.cpp delete mode 100644 compiler/libsolidity/codegen/YulUtilFunctions.h delete mode 100644 compiler/libsolidity/codegen/ir/IRGenerationContext.cpp delete mode 100644 compiler/libsolidity/codegen/ir/IRGenerationContext.h delete mode 100644 compiler/libsolidity/codegen/ir/IRGenerator.cpp delete mode 100644 compiler/libsolidity/codegen/ir/IRGenerator.h delete mode 100644 compiler/libsolidity/codegen/ir/IRGeneratorForStatements.cpp delete mode 100644 compiler/libsolidity/codegen/ir/IRGeneratorForStatements.h delete mode 100644 compiler/libsolidity/codegen/ir/IRLValue.cpp delete mode 100644 compiler/libsolidity/codegen/ir/IRLValue.h delete mode 100644 compiler/libsolidity/codegen/ir/README.md delete mode 100644 compiler/libsolidity/formal/BMC.cpp delete mode 100644 compiler/libsolidity/formal/BMC.h delete mode 100644 compiler/libsolidity/formal/CHC.cpp delete mode 100644 compiler/libsolidity/formal/CHC.h delete mode 100644 compiler/libsolidity/formal/CHCSmtLib2Interface.cpp delete mode 100644 compiler/libsolidity/formal/CHCSmtLib2Interface.h delete mode 100644 compiler/libsolidity/formal/CHCSolverInterface.h delete mode 100644 compiler/libsolidity/formal/CVC4Interface.cpp delete mode 100644 compiler/libsolidity/formal/CVC4Interface.h delete mode 100644 compiler/libsolidity/formal/EncodingContext.cpp delete mode 100644 compiler/libsolidity/formal/EncodingContext.h delete mode 100644 compiler/libsolidity/formal/ModelChecker.cpp delete mode 100644 compiler/libsolidity/formal/ModelChecker.h delete mode 100644 compiler/libsolidity/formal/SMTEncoder.cpp delete mode 100644 compiler/libsolidity/formal/SMTEncoder.h delete mode 100644 compiler/libsolidity/formal/SMTLib2Interface.cpp delete mode 100644 compiler/libsolidity/formal/SMTLib2Interface.h delete mode 100644 compiler/libsolidity/formal/SMTPortfolio.cpp delete mode 100644 compiler/libsolidity/formal/SMTPortfolio.h delete mode 100644 compiler/libsolidity/formal/SSAVariable.cpp delete mode 100644 compiler/libsolidity/formal/SSAVariable.h delete mode 100644 compiler/libsolidity/formal/SolverInterface.h delete mode 100644 compiler/libsolidity/formal/SymbolicTypes.cpp delete mode 100644 compiler/libsolidity/formal/SymbolicTypes.h delete mode 100644 compiler/libsolidity/formal/SymbolicVariables.cpp delete mode 100644 compiler/libsolidity/formal/SymbolicVariables.h delete mode 100644 compiler/libsolidity/formal/VariableUsage.cpp delete mode 100644 compiler/libsolidity/formal/VariableUsage.h delete mode 100644 compiler/libsolidity/formal/Z3CHCInterface.cpp delete mode 100644 compiler/libsolidity/formal/Z3CHCInterface.h delete mode 100644 compiler/libsolidity/formal/Z3Interface.cpp delete mode 100644 compiler/libsolidity/formal/Z3Interface.h delete mode 100644 compiler/libsolidity/interface/ABI.cpp delete mode 100644 compiler/libsolidity/interface/ABI.h delete mode 100644 compiler/libsolidity/interface/GasEstimator.cpp delete mode 100644 compiler/libsolidity/interface/GasEstimator.h delete mode 100644 compiler/libsolidity/interface/StorageLayout.cpp delete mode 100644 compiler/libsolidity/interface/StorageLayout.h delete mode 100644 compiler/libyul/AsmAnalysis.cpp delete mode 100644 compiler/libyul/AsmAnalysis.h delete mode 100644 compiler/libyul/AsmAnalysisInfo.h delete mode 100644 compiler/libyul/AsmData.h delete mode 100644 compiler/libyul/AsmDataForward.h delete mode 100644 compiler/libyul/AsmJsonConverter.cpp delete mode 100644 compiler/libyul/AsmJsonConverter.h delete mode 100644 compiler/libyul/AsmParser.cpp delete mode 100644 compiler/libyul/AsmParser.h delete mode 100644 compiler/libyul/AsmPrinter.cpp delete mode 100644 compiler/libyul/AsmPrinter.h delete mode 100644 compiler/libyul/AsmScope.cpp delete mode 100644 compiler/libyul/AsmScope.h delete mode 100644 compiler/libyul/AsmScopeFiller.cpp delete mode 100644 compiler/libyul/AsmScopeFiller.h delete mode 100644 compiler/libyul/AssemblyStack.cpp delete mode 100644 compiler/libyul/AssemblyStack.h delete mode 100644 compiler/libyul/CMakeLists.txt delete mode 100644 compiler/libyul/CompilabilityChecker.cpp delete mode 100644 compiler/libyul/CompilabilityChecker.h delete mode 100644 compiler/libyul/Dialect.cpp delete mode 100644 compiler/libyul/Dialect.h delete mode 100644 compiler/libyul/Exceptions.h delete mode 100644 compiler/libyul/Object.cpp delete mode 100644 compiler/libyul/Object.h delete mode 100644 compiler/libyul/ObjectParser.cpp delete mode 100644 compiler/libyul/ObjectParser.h delete mode 100644 compiler/libyul/SideEffects.h delete mode 100644 compiler/libyul/Utilities.cpp delete mode 100644 compiler/libyul/Utilities.h delete mode 100644 compiler/libyul/YulString.h delete mode 100644 compiler/libyul/backends/evm/AbstractAssembly.h delete mode 100644 compiler/libyul/backends/evm/AsmCodeGen.cpp delete mode 100644 compiler/libyul/backends/evm/AsmCodeGen.h delete mode 100644 compiler/libyul/backends/evm/ConstantOptimiser.cpp delete mode 100644 compiler/libyul/backends/evm/ConstantOptimiser.h delete mode 100644 compiler/libyul/backends/evm/EVMAssembly.cpp delete mode 100644 compiler/libyul/backends/evm/EVMAssembly.h delete mode 100644 compiler/libyul/backends/evm/EVMCodeTransform.cpp delete mode 100644 compiler/libyul/backends/evm/EVMCodeTransform.h delete mode 100644 compiler/libyul/backends/evm/EVMDialect.cpp delete mode 100644 compiler/libyul/backends/evm/EVMDialect.h delete mode 100644 compiler/libyul/backends/evm/EVMMetrics.cpp delete mode 100644 compiler/libyul/backends/evm/EVMMetrics.h delete mode 100644 compiler/libyul/backends/evm/EVMObjectCompiler.cpp delete mode 100644 compiler/libyul/backends/evm/EVMObjectCompiler.h delete mode 100644 compiler/libyul/backends/evm/NoOutputAssembly.cpp delete mode 100644 compiler/libyul/backends/evm/NoOutputAssembly.h delete mode 100644 compiler/libyul/backends/wasm/BinaryTransform.cpp delete mode 100644 compiler/libyul/backends/wasm/BinaryTransform.h delete mode 100644 compiler/libyul/backends/wasm/EVMToEwasmTranslator.cpp delete mode 100644 compiler/libyul/backends/wasm/EVMToEwasmTranslator.h delete mode 100644 compiler/libyul/backends/wasm/TextTransform.cpp delete mode 100644 compiler/libyul/backends/wasm/TextTransform.h delete mode 100644 compiler/libyul/backends/wasm/WasmAST.h delete mode 100644 compiler/libyul/backends/wasm/WasmCodeTransform.cpp delete mode 100644 compiler/libyul/backends/wasm/WasmCodeTransform.h delete mode 100644 compiler/libyul/backends/wasm/WasmDialect.cpp delete mode 100644 compiler/libyul/backends/wasm/WasmDialect.h delete mode 100644 compiler/libyul/backends/wasm/WasmObjectCompiler.cpp delete mode 100644 compiler/libyul/backends/wasm/WasmObjectCompiler.h delete mode 100644 compiler/libyul/backends/wasm/WordSizeTransform.cpp delete mode 100644 compiler/libyul/backends/wasm/WordSizeTransform.h delete mode 100644 compiler/libyul/optimiser/ASTCopier.cpp delete mode 100644 compiler/libyul/optimiser/ASTCopier.h delete mode 100644 compiler/libyul/optimiser/ASTWalker.cpp delete mode 100644 compiler/libyul/optimiser/ASTWalker.h delete mode 100644 compiler/libyul/optimiser/BlockFlattener.cpp delete mode 100644 compiler/libyul/optimiser/BlockFlattener.h delete mode 100644 compiler/libyul/optimiser/BlockHasher.cpp delete mode 100644 compiler/libyul/optimiser/BlockHasher.h delete mode 100644 compiler/libyul/optimiser/CallGraphGenerator.cpp delete mode 100644 compiler/libyul/optimiser/CallGraphGenerator.h delete mode 100644 compiler/libyul/optimiser/CommonSubexpressionEliminator.cpp delete mode 100644 compiler/libyul/optimiser/CommonSubexpressionEliminator.h delete mode 100644 compiler/libyul/optimiser/ConditionalSimplifier.cpp delete mode 100644 compiler/libyul/optimiser/ConditionalSimplifier.h delete mode 100644 compiler/libyul/optimiser/ConditionalUnsimplifier.cpp delete mode 100644 compiler/libyul/optimiser/ConditionalUnsimplifier.h delete mode 100644 compiler/libyul/optimiser/ControlFlowSimplifier.cpp delete mode 100644 compiler/libyul/optimiser/ControlFlowSimplifier.h delete mode 100644 compiler/libyul/optimiser/DataFlowAnalyzer.cpp delete mode 100644 compiler/libyul/optimiser/DataFlowAnalyzer.h delete mode 100644 compiler/libyul/optimiser/DeadCodeEliminator.cpp delete mode 100644 compiler/libyul/optimiser/DeadCodeEliminator.h delete mode 100644 compiler/libyul/optimiser/Disambiguator.cpp delete mode 100644 compiler/libyul/optimiser/Disambiguator.h delete mode 100644 compiler/libyul/optimiser/EquivalentFunctionCombiner.cpp delete mode 100644 compiler/libyul/optimiser/EquivalentFunctionCombiner.h delete mode 100644 compiler/libyul/optimiser/EquivalentFunctionDetector.cpp delete mode 100644 compiler/libyul/optimiser/EquivalentFunctionDetector.h delete mode 100644 compiler/libyul/optimiser/ExpressionInliner.cpp delete mode 100644 compiler/libyul/optimiser/ExpressionInliner.h delete mode 100644 compiler/libyul/optimiser/ExpressionJoiner.cpp delete mode 100644 compiler/libyul/optimiser/ExpressionJoiner.h delete mode 100644 compiler/libyul/optimiser/ExpressionSimplifier.cpp delete mode 100644 compiler/libyul/optimiser/ExpressionSimplifier.h delete mode 100644 compiler/libyul/optimiser/ExpressionSplitter.cpp delete mode 100644 compiler/libyul/optimiser/ExpressionSplitter.h delete mode 100644 compiler/libyul/optimiser/ForLoopConditionIntoBody.cpp delete mode 100644 compiler/libyul/optimiser/ForLoopConditionIntoBody.h delete mode 100644 compiler/libyul/optimiser/ForLoopConditionOutOfBody.cpp delete mode 100644 compiler/libyul/optimiser/ForLoopConditionOutOfBody.h delete mode 100644 compiler/libyul/optimiser/ForLoopInitRewriter.cpp delete mode 100644 compiler/libyul/optimiser/ForLoopInitRewriter.h delete mode 100644 compiler/libyul/optimiser/FullInliner.cpp delete mode 100644 compiler/libyul/optimiser/FullInliner.h delete mode 100644 compiler/libyul/optimiser/FunctionGrouper.cpp delete mode 100644 compiler/libyul/optimiser/FunctionGrouper.h delete mode 100644 compiler/libyul/optimiser/FunctionHoister.cpp delete mode 100644 compiler/libyul/optimiser/FunctionHoister.h delete mode 100644 compiler/libyul/optimiser/InlinableExpressionFunctionFinder.cpp delete mode 100644 compiler/libyul/optimiser/InlinableExpressionFunctionFinder.h delete mode 100644 compiler/libyul/optimiser/KnowledgeBase.cpp delete mode 100644 compiler/libyul/optimiser/KnowledgeBase.h delete mode 100644 compiler/libyul/optimiser/LoadResolver.cpp delete mode 100644 compiler/libyul/optimiser/LoadResolver.h delete mode 100644 compiler/libyul/optimiser/LoopInvariantCodeMotion.cpp delete mode 100644 compiler/libyul/optimiser/LoopInvariantCodeMotion.h delete mode 100644 compiler/libyul/optimiser/MainFunction.cpp delete mode 100644 compiler/libyul/optimiser/MainFunction.h delete mode 100644 compiler/libyul/optimiser/Metrics.cpp delete mode 100644 compiler/libyul/optimiser/Metrics.h delete mode 100644 compiler/libyul/optimiser/NameCollector.cpp delete mode 100644 compiler/libyul/optimiser/NameCollector.h delete mode 100644 compiler/libyul/optimiser/NameDispenser.cpp delete mode 100644 compiler/libyul/optimiser/NameDispenser.h delete mode 100644 compiler/libyul/optimiser/NameDisplacer.cpp delete mode 100644 compiler/libyul/optimiser/NameDisplacer.h delete mode 100644 compiler/libyul/optimiser/OptimiserStep.h delete mode 100644 compiler/libyul/optimiser/OptimizerUtilities.cpp delete mode 100644 compiler/libyul/optimiser/OptimizerUtilities.h delete mode 100644 compiler/libyul/optimiser/README.md delete mode 100644 compiler/libyul/optimiser/RedundantAssignEliminator.cpp delete mode 100644 compiler/libyul/optimiser/RedundantAssignEliminator.h delete mode 100644 compiler/libyul/optimiser/Rematerialiser.cpp delete mode 100644 compiler/libyul/optimiser/Rematerialiser.h delete mode 100644 compiler/libyul/optimiser/SSAReverser.cpp delete mode 100644 compiler/libyul/optimiser/SSAReverser.h delete mode 100644 compiler/libyul/optimiser/SSATransform.cpp delete mode 100644 compiler/libyul/optimiser/SSATransform.h delete mode 100644 compiler/libyul/optimiser/SSAValueTracker.cpp delete mode 100644 compiler/libyul/optimiser/SSAValueTracker.h delete mode 100644 compiler/libyul/optimiser/Semantics.cpp delete mode 100644 compiler/libyul/optimiser/Semantics.h delete mode 100644 compiler/libyul/optimiser/SimplificationRules.cpp delete mode 100644 compiler/libyul/optimiser/SimplificationRules.h delete mode 100644 compiler/libyul/optimiser/StackCompressor.cpp delete mode 100644 compiler/libyul/optimiser/StackCompressor.h delete mode 100644 compiler/libyul/optimiser/StructuralSimplifier.cpp delete mode 100644 compiler/libyul/optimiser/StructuralSimplifier.h delete mode 100644 compiler/libyul/optimiser/Substitution.cpp delete mode 100644 compiler/libyul/optimiser/Substitution.h delete mode 100644 compiler/libyul/optimiser/Suite.cpp delete mode 100644 compiler/libyul/optimiser/Suite.h delete mode 100644 compiler/libyul/optimiser/SyntacticalEquality.cpp delete mode 100644 compiler/libyul/optimiser/SyntacticalEquality.h delete mode 100644 compiler/libyul/optimiser/UnusedPruner.cpp delete mode 100644 compiler/libyul/optimiser/UnusedPruner.h delete mode 100644 compiler/libyul/optimiser/VarDeclInitializer.cpp delete mode 100644 compiler/libyul/optimiser/VarDeclInitializer.h delete mode 100644 compiler/libyul/optimiser/VarNameCleaner.cpp delete mode 100644 compiler/libyul/optimiser/VarNameCleaner.h mode change 100644 => 100755 compiler/scripts/ASTImportTest.sh mode change 100644 => 100755 compiler/scripts/build.sh mode change 100644 => 100755 compiler/scripts/build_emscripten.sh mode change 100644 => 100755 compiler/scripts/bytecodecompare/prepare_report.py mode change 100644 => 100755 compiler/scripts/bytecodecompare/storebytecode.sh mode change 100644 => 100755 compiler/scripts/check_style.sh mode change 100644 => 100755 compiler/scripts/create_source_tarball.sh mode change 100644 => 100755 compiler/scripts/deps-ppa/static_z3.sh mode change 100644 => 100755 compiler/scripts/docker_build.sh mode change 100644 => 100755 compiler/scripts/docker_deploy.sh mode change 100644 => 100755 compiler/scripts/docker_deploy_manual.sh mode change 100644 => 100755 compiler/scripts/docs.sh mode change 100644 => 100755 compiler/scripts/download_ossfuzz_corpus.sh mode change 100644 => 100755 compiler/scripts/extract_test_cases.py mode change 100644 => 100755 compiler/scripts/fix_homebrew_paths_in_standalone_zip.py mode change 100644 => 100755 compiler/scripts/get_version.sh mode change 100644 => 100755 compiler/scripts/install_cmake.sh mode change 100644 => 100755 compiler/scripts/install_deps.sh create mode 100755 compiler/scripts/install_lib_variable.sh mode change 100644 => 100755 compiler/scripts/install_obsolete_jsoncpp_1_7_4.sh mode change 100644 => 100755 compiler/scripts/isolate_tests.py mode change 100644 => 100755 compiler/scripts/isoltest.sh mode change 100644 => 100755 compiler/scripts/pylint_all.py mode change 100644 => 100755 compiler/scripts/regressions.py mode change 100644 => 100755 compiler/scripts/release.sh mode change 100644 => 100755 compiler/scripts/release_emscripten.sh mode change 100644 => 100755 compiler/scripts/release_ppa.sh mode change 100644 => 100755 compiler/scripts/report_errors.sh mode change 100644 => 100755 compiler/scripts/run_proofs.sh mode change 100644 => 100755 compiler/scripts/soltest.sh mode change 100644 => 100755 compiler/scripts/splitSources.py mode change 100644 => 100755 compiler/scripts/test_emscripten.sh mode change 100644 => 100755 compiler/scripts/tests.sh mode change 100644 => 100755 compiler/scripts/travis-emscripten/build_emscripten.sh mode change 100644 => 100755 compiler/scripts/travis-emscripten/install_deps.sh mode change 100644 => 100755 compiler/scripts/travis-emscripten/publish_binary.sh mode change 100644 => 100755 compiler/scripts/uniqueErrors.sh mode change 100644 => 100755 compiler/scripts/update_bugs_by_version.py mode change 100644 => 100755 compiler/test/buglistTests.js mode change 100644 => 100755 compiler/test/cmdlineTests.sh mode change 100644 => 100755 compiler/test/docsCodeStyle.sh mode change 100644 => 100755 compiler/test/externalTests.sh mode change 100644 => 100755 compiler/test/externalTests/colony.sh mode change 100644 => 100755 compiler/test/externalTests/gnosis.sh mode change 100644 => 100755 compiler/test/externalTests/solc-js/solc-js.sh mode change 100644 => 100755 compiler/test/externalTests/zeppelin.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..30023e62 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +build/ diff --git a/API.md b/API.md index 5c8c1498..0cbe378e 100644 --- a/API.md +++ b/API.md @@ -1,23 +1,90 @@ # **TON Solidity API** + TON Solidity compiler expands Solidity language with different API functions to facilitate TON contract development. ## Table of Contents + * [TON specific types](#ton-specific-types) * [TvmCell](#tvmcell) + * [TvmCell.toSlice()](#tvmcelltoslice) * [TvmSlice](#tvmslice) + * [TvmSlice.size()](#tvmslicesize) + * [TvmSlice.bits()](#tvmslicebits) + * [TvmSlice.refs()](#tvmslicerefs) + * [TvmSlice.decode()](#tvmslicedecode) + * [TvmSlice.loadRef()](#tvmsliceloadref) + * [TvmSlice.loadRefAsSlice()](#tvmsliceloadrefasslice) + * [TvmSlice.loadSigned()](#tvmsliceloadsigned) + * [TvmSlice.loadUnsigned()](#tvmsliceloadunsigned) + * [TvmSlice.decodeFunctionParams()](#tvmslicedecodefunctionparams) * [TvmBuilder](#tvmbuilder) + * [TvmBuilder.toSlice()](#tvmbuildertoslice) + * [TvmBuilder.toCell()](#tvmbuildertocell) + * [TvmBuilder.bits()](#tvmbuilderbits) + * [TvmBuilder.refs()](#tvmbuilderrefs) + * [TvmBuilder.bitsAndRefs()](#tvmbuilderbitsandrefs) + * [TvmBuilder.remBits()](#tvmbuilderrembits) + * [TvmBuilder.remRefs()](#tvmbuilderremrefs) + * [TvmBuilder.remBitsAndRefs()](#tvmbuilderrembitsandrefs) + * [TvmBuilder.store()](#tvmbuilderstore) + * [TvmBuilder.storeSigned()](#tvmbuilderstoresigned) + * [TvmBuilder.storeUnsigned()](#tvmbuilderstoreunsigned) + * [TvmBuilder.storeRef()](#tvmbuilderstoreref) + * Changes in + * [string](#string) + * [string.byteLength()](#stringbytelength) + * [string.substr()](#stringsubstr) + * [bytes](#bytes) + * [operator[]](#operator) + * [bytes.length](#byteslength) + * [address](#address) + * [Object creating](#object-creating) + * [constructor()](#constructor) + * [address.makeAddrStd()](#addressmakeaddrstd) + * [address.makeAddrNone()](#addressmakeaddrnone) + * [address.makeAddrExtern()](#addressmakeaddrextern) + * [Members](#members) + * [address.wid](#addresswid) + * [address.value](#addressvalue) + * [address.currencies](#addresscurrencies) + * [Functions](#functions) + * [address.getType()](#addressgettype) + * [address.isStdZero()](#addressisstdzero) + * [address.isExternZero()](#addressisexternzero) + * [address.isNone()](#addressisnone) + * [address.unpack()](#addressunpack) + * [address.transfer()](#addresstransfer) + * [mapping](#mapping) + * [mapping.min() and mapping.max()](#mappingmin-and-mappingmax) + * [mapping.next() and mapping.prev()](#mappingnext-and-mappingprev) + * [mapping.nextOrEq() and mapping.prevOrEq()](#mappingnextoreq-and-mappingprevoreq) + * [mapping.delMin() and mapping.delMax()](#mappingdelmin-and-mappingdelmax) + * [mapping.fetch()](#mappingfetch) + * [mapping.exists()](#mappingexists) + * [mapping.empty()](#mappingempty) + * [mapping.replace()](#mappingreplace) + * [mapping.add()](#mappingadd) + * [mapping.getSet()](#mappinggetset) + * [mapping.getAdd()](#mappinggetadd) + * [mapping.getReplace()](#mappinggetreplace) * [ExtraCurrencyCollection](#extracurrencycollection) - * Changes in [string](#string), [address](#address) and [mapping](#mapping) + * [require, revert](#require-revert) + * [require](#require) + * [revert](#revert) * [Pragmas](#pragmas) * [ignoreIntOverflow](#ignoreintoverflow) * [AbiHeader](#abiheader) -* [Contract functions](#contract-functions) +* [Special contract functions](#special-contract-functions) + * [receive](#receive) + * [fallback](#fallback) * [onBounce](#onbounce) * [onCodeUpgrade](#oncodeupgrade) -* [Function specificators](#function-specificators) + * [afterSignatureCheck](#aftersignaturecheck) +* [Function specifiers](#function-specifiers) * [functionID()](#functionid) -* [Events](#events) +* [Events and return](#events-and-return) * [extAddr](#extaddr) + * [return](#return) * [API functions and members](#api-functions-and-members) * [**msg** namespace](#msg-namespace) * [msg.pubkey()](#msgpubkey) @@ -41,684 +108,1119 @@ TON Solidity compiler expands Solidity language with different API functions to * [tvm.deploy()](#tvmdeploy) * [tvm.deployAndCallConstructor()](#tvmdeployandcallconstructor) * [tvm.deployAndCallConstructorWithFlag()](#tvmdeployandcallconstructorwithflag) + * [Deploy via new](#deploy-via-new) * [Others](#others) * [tvm.pubkey()](#tvmpubkey) * [tvm.setCurrentCode()](#tvmsetcurrentcode) - * [tvm.sendMsg()](#tvmsendmsg) * [tvm.resetStorage()](#tvmresetstorage) - * [tvm.setExtDestAddr()](#tvmsetextdestaddr) * [tvm.functionId()](#tvmfunctionid) - * [tvm.min()](#tvmmin) - * [tvm.max()](#tvmmax) + * [tvm.encodeBody()](#tvmencodebody) + * [tvm.min() and tvm.max()](#tvmmin-and-tvmmax) ## Detailed description ### TON specific types + TON Solidity compiler expands functionality of some existing types and adds several new TVM specific types: TvmCell, TvmSlice, TvmBuilder and ExtraCurrencyCollection. Full description of this types can be found in [TVM][1] and [TON Blockchain][2] specifications. #### TvmCell + TvmCell represents TVM type Cell. TON Solidity compiler defines the following functions to work with this type: -##### 1. TvmCell.toSlice() -``` -TvmCell.toSlice() -> returns (TvmSlice) +##### TvmCell.toSlice() + +```TVMSolidity +TvmCell cell; +TvmSlice slice = cell.toSlice(); ``` + This function converts cell to slice. #### TvmSlice + TvmSlice represents TVM type Slice. TON Solidity compiler defines the following functions to work with this type: -##### 1. TvmSlice.size() -``` -TvmSlice.size() -> returns (uint16, uint8) +##### TvmSlice.size() + +```TVMSolidity +TvmSlice slice; +(uint16 bits, uint8 refs) = slice.size(); ``` + This function returns number of data bits and references in the slice. -##### 2. TvmSlice.bits() -``` -TvmSlice.bits() -> returns (uint16) +##### TvmSlice.bits() + +```TVMSolidity +TvmSlice slice; +uint16 bits = slice.bits(); ``` + This function returns number of data bits in the slice. -##### 3. TvmSlice.refs() -``` -TvmSlice.refs() -> returns (uint8) +##### TvmSlice.refs() + +```TVMSolidity +TvmSlice slice; +uint8 refs = slice.refs(); ``` + This function returns number of references in the slice. -##### 4. TvmSlice.decode() -``` -TvmSlice.decode() -> returns () +##### TvmSlice.decode() + +```TVMSolidity +TvmSlice slice; +(TypeA a, TypeB b, ...) = slice.decode(TypeA, TypeB, ...); ``` + This function loads given types from the slice. Example: -``` + +```TVMSolidity +TvmSlice slice; (uint8 a, uint16 b) = slice.decode(uint8, uint16); (uint16 num0, uint32 num1, address addr) = slice.decode(uint16, uint32, address); ``` -##### 5. TvmSlice.loadRef() -``` -TvmSlice.loadRef() -> returns (TvmCell) +##### TvmSlice.loadRef() + +```TVMSolidity +TvmSlice slice; +TvmCell cell = slice.loadRef(); ``` + This function loads a cell from the slice reference. -##### 6. TvmSlice.loadRefAsSlice() -``` -TvmSlice.loadRefAsSlice() -> returns (TvmSlice) +##### TvmSlice.loadRefAsSlice() + +```TVMSolidity +TvmSlice slice; +TvmSlice refSlice = slice.loadRefAsSlice(); ``` + This function loads a cell from the slice reference and converts it into a slice. -##### 7. TvmSlice.loadSigned() -``` -TvmSlice.loadSigned(uint16 bitsize) -> returns (int) -``` -This function loads a signed integer with the given bitsize from the slice. +##### TvmSlice.loadSigned() -##### 8. TvmSlice.loadUnsigned() +```TVMSolidity +TvmSlice slice; +uint16 bitSize; +int number = slice.loadSigned(bitSize) ``` -TvmSlice.loadUnsigned(uint16 bitsize) -> returns (uint) -``` -This function loads an unsigned integer with the given bitsize from the slice. -##### 9. TvmSlice.decodeFunctionParams() +This function loads a signed integer with the given **bitSize** from the slice. + +##### TvmSlice.loadUnsigned() + +```TVMSolidity +TvmSlice slice; +uint16 bitSize; +uint number = slice.loadUnsigned(bitSize); ``` -TvmSlice.decodeFunctionParams(function_name) -> returns () + +This function loads an unsigned integer with the given **bitSize** from the slice. + +##### TvmSlice.decodeFunctionParams() + +```TVMSolidity +TvmSlice slice; +(TypeA a, TypeB b, ...) = slice.decodeFunctionParams(function_name); ``` + This function decodes parameters of function with given name. It's very convenient if there are many params and they don't fit in one cell. This function is usually used in **[onBounce](#onbounce)** function. See example of how to use **onBounce** function: -- [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) + +* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) #### TvmBuilder + TvmBuilder represents TVM type Builder. TON Solidity compiler defines the following functions to work with this type: -##### 1. TvmBuilder.toSlice() -``` -TvmBuilder.toSlice() -> returns (TvmSlice) +##### TvmBuilder.toSlice() + +```TVMSolidity +TvmBuilder builder; +TvmSlice slice = builder.toSlice(); ``` + This function converts the builder into a Slice. -##### 2. TvmBuilder.toCell() -``` -TvmBuilder.toCell() -> returns (TvmCell) +##### TvmBuilder.toCell() + +```TVMSolidity +TvmBuilder builder; +TvmCell cell = builder.toCell(); ``` + This function converts the builder into a Cell. -##### 3. TvmBuilder.bits() -``` -TvmBuilder.bits() -> returns (uint16) +##### TvmBuilder.bits() + +```TVMSolidity +TvmBuilder builder; +uint16 bits = builder.bits(); ``` + This function returns the number of data bits already stored in the builder. -##### 4. TvmBuilder.refs() -``` -TvmBuilder.refs() -> returns (uint8) +##### TvmBuilder.refs() + +```TVMSolidity +TvmBuilder builder; +uint8 refs = builder.refs(); ``` + This function returns the number of references already stored in the builder. -##### 5. TvmBuilder.bitsAndRefs() -``` -TvmBuilder.bitsAndRefs() -> returns (uint16, uint8) +##### TvmBuilder.bitsAndRefs() + +```TVMSolidity +TvmBuilder builder; +(uint16 bits, uint8 refs) = builder.bitsAndRefs(); ``` + This function returns the number of data bits and references already stored in the builder. -##### 6. TvmBuilder.remBits() -``` -TvmBuilder.remBits() -> returns (uint16) +##### TvmBuilder.remBits() + +```TVMSolidity +TvmBuilder builder; +uint16 remBits = builder.remBits(); ``` + This function returns the number of data bits that can still be stored in the builder. -##### 7. TvmBuilder.remRefs() -``` -TvmBuilder.remRefs() -> returns (uint8) +##### TvmBuilder.remRefs() + +```TVMSolidity +TvmBuilder builder; +uint8 refRefs = builder.remRefs(); ``` + This function returns the number of references that can still be stored in the builder. -##### 8. TvmBuilder.remBitsAndRefs() -``` -TvmBuilder.remBitsAndRefs() -> returns (uint16, uint8) +##### TvmBuilder.remBitsAndRefs() + +```TVMSolidity +TvmBuilder builder; +(uint16 remBits, uint8 remRefs) = builder.remBitsAndRefs(); ``` + This function returns the number of data bits and references that can still be stored in the builder. -##### 9. TvmBuilder.store() -``` -TvmBuilder.store() +##### TvmBuilder.store() + +```TVMSolidity +TvmBuilder builder; +builder.store(/*list_of_variables*/); ``` + This function stores variables in the builder. Available types: -- integer -- address -- TvmSlice -- TvmBuilder -- TvmCell -- mapping -- struct -- array + +* integer +* address +* TvmSlice +* TvmBuilder +* TvmCell +* mapping +* struct +* array Example: -``` + +```TVMSolidity uint8 a; int16 b; TvmBuilder builder; builder.store(a, b, uint(123132)); ``` -##### 10. TvmBuilder.storeSigned() -``` -TvmBuilder.storeSigned(int256, uint16) -``` -This function stores a signed integer with given bitsize in the builder. +##### TvmBuilder.storeSigned() -##### 11. TvmBuilder.storeUnsigned() -``` -TvmBuilder.storeUnsigned(uint256, uint8) +```TVMSolidity +TvmBuilder builder; +int256 value; +uint16 bitSize; +builder.storeSigned(value, bitSize); ``` -This function stores an unsigned integer with given bitsize in the builder. -##### 12. TvmBuilder.storeRef() +This function stores a signed integer with given **bitSize** in the builder. + +##### TvmBuilder.storeUnsigned() + +```TVMSolidity +TvmBuilder builder; +uint256 value; +uint16 bitSize; +builder.storeUnsigned(value, bitSize); ``` -TvmBuilder.storeRef(TvmBuilder) + +This function stores an unsigned integer with given **bitSize** in the builder. + +##### TvmBuilder.storeRef() + +```TVMSolidity +TvmBuilder builder; +TvmBuilder builder2; +builder.storeRef(builder2) ``` -This function converts the argument builder to a cell and stores it in a reference of the builder. + +This function converts the argument builder into a cell and stores it in a reference of the builder. See example of how to work with TVM specific types: -- [Message_construction](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageSender.sol) -- [Message_parsing](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageReceiver.sol) +* [Message_construction](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageSender.sol) +* [Message_parsing](https://github.com/tonlabs/samples/blob/master/solidity/15_MessageReceiver.sol) -#### string -TON Solidity compiler expands **string** type with the following functions: +#### bytes -##### 1. string.byteLength() +##### operator[] + +```TVMSolidity +bytes byteArray = "abba"; +int index = 0; +byte a0 = byteArray[index]; ``` -string.byteLength() -> returns (uint8) + +Operator **[]** returns a byte located at position **index**. +Warring: **index** must be in range 0 to 203 include. + +##### bytes.length + +```TVMSolidity +bytes byteArray = "abba"; +uint l = byteArray.length; ``` -This function returns byte length of the string data. -##### 2. string.substr() +Member **length** returns length of byte array. +Warring: if length of array is bigger than 204 than this function return 204. + +#### string + +TON Solidity compiler expands **string** type with the following functions: + +##### string.byteLength() + +```TVMSolidity +string str +uint8 len = str.byteLength(); ``` -string.substr(uint8 from, uint8 count) -> returns (string) + +This function returns byte length of the string data. +Warring: if length of string is bigger than 204 than this function return 204. + +##### string.substr() + +```TVMSolidity +string str; +uint8 from; +uint8 count; +string substr = str.substr(from, count); ``` -This function returns a substring starting from the byte with number with byte length . + +This function returns a substring starting from the byte with number **from** with byte length **count**. +Warring: **from** must be in range 0 to 203 include and **from + count** must be in range 1 to 204 include. #### address + **address** represents different types of TVM addresses: **addr_none**, **addr_extern** and **addr_std**. TON Solidity compiler expands **address** type with the following members and functions: ##### Object creating -##### 1. constructor() -``` -address addrStd = address(uint256 address_value); +##### constructor() + +```TVMSolidity +uint address_value; +address addrStd = address(address_value); ``` + This function constructs **address** of type **addr_std** with zero workchain id and given address value. -##### 2. address.makeAddrStd() -``` -address addrStd = address.makeAddrStd(int8 wid, uint address); -``` -This function constructs **address** of type **addr_std** with given workchain id *wid* and value *address_value*. +##### address.makeAddrStd() -##### 3. address.makeAddrNone() +```TVMSolidity +int8 wid; +uint address; +address addrStd = address.makeAddrStd(wid, address); ``` + +This function constructs **address** of type **addr_std** with given workchain id **wid** and value **address_value**. + +##### address.makeAddrNone() + +```TVMSolidity address addrNone = address.makeAddrNone(); ``` + This function constructs **address** of type **addr_none**. -##### 4. address.makeAddrExtern() -``` -address addrExtern = address.makeAddrExtern(uint addrNumber, uint bitCnt); +##### address.makeAddrExtern() + +```TVMSolidity +uint addrNumber; +uint bitCnt; +address addrExtern = address.makeAddrExtern(addrNumber, bitCnt); ``` -This function constructs **address** of type **addr_extern** with given *value* with *bitCnt* bit length. + +This function constructs **address** of type **addr_extern** with given **value** with **bitCnt** bit length. ##### Members -##### 1. address.wid -``` -address.wid -> returns (int8 workchain_id) +##### address.wid + +```TVMSolidity +address addr; +int8 workchain_id = addr.wid; ``` + This member of **address** type allows to obtain the workchain id of **addr_std**. -##### 2. address.value -``` -address.value -> returns (uint256 address_value) +##### address.value + +```TVMSolidity +address addr; +uint address_value = addr.value; ``` + This member of **address** type allows to obtain the address value of **addr_std**. -##### 3. address.currencies -``` -address(this).currencies -> return ExtraCurrencyCollection +##### address.currencies + +```TVMSolidity +ExtraCurrencyCollection cur = address(this).currencies; ``` + This member returns currencies on the balance of **this** contract. ##### Functions -##### 1. address.getType() -``` -address.getType() -> (uint8 address_type) +##### address.getType() + +```TVMSolidity +address addr; +uint8 address_type = addr.getType(); ``` + This function returns type of the **address**: 0 - addr_none 1 - addr_extern 2 - addr_std -##### 2. address.isStdZero() -``` -address.isStdZero() -> returns (bool status) +##### address.isStdZero() + +```TVMSolidity +address addr; +bool status = addr.isStdZero(); ``` + This function compares **address** with zero **address** of type **addr_std**. -##### 3. address.isExternZero() -``` -address.isExternZero() -> returns (bool status) +##### address.isExternZero() + +```TVMSolidity +address addr; +bool status = addr.isExternZero(); ``` + This function compares **address** with zero **address** of type **addr_extern**. -##### 4. address.isNone() -``` -address.isNone() -> returns (bool status) +##### address.isNone() + +```TVMSolidity +address addr; +bool status = addr.isNone(); ``` + This function returns true for **address** of type **addr_none**, otherwise returns false. -##### 5. address.unpack() -``` -address.unpack() -> returns (int8 wid, uint256 value) -``` -This function unpacks **addr_std** and returns workchain id *wid* and address *value*. +##### address.unpack() -##### 6. address.transfer() +```TVMSolidity +address addr; +(int8 wid, uint256 value) = addr.unpack(); ``` -address.transfer(uint128 grams_value, bool bounce, uint16 sendrawmsg_flag, TvmCell body, ExtraCurrencyCollection c) + +This function unpacks **addr_std** and returns workchain id **wid** and address **value**. + +##### address.transfer() + +```TVMSolidity +address addr; +uint128 value; +bool bounce; +uint16 sendrawmsg_flag; +TvmCell body; +ExtraCurrencyCollection c; +addr.transfer(value, bounce, sendrawmsg_flag, body, c); ``` -Ths function is an overload of "address.transfer(uint128 value)" function. It allows to make a currency transfer with arbitrary settings. Unlike usual "address.transfer(uint128 value)" (which has bounce flag set to true) it can be used to send grams to a non-existing address. + +Ths function is an overload of "address.transfer(uint128 value)" function. It allows to make a currency transfer with arbitrary settings. Unlike usual "address.transfer(uint128 value)" (which has bounce flag set to true) it can be used to send value to a non-existing address. Example: -``` + +```TVMSolidity TvmCell cell = getCell(); -ExtraCurrencyCollection c = getCurrence(); +ExtraCurrencyCollection c = getCurrency(); address destination = msg.sender; destination.transfer({value:122, bounce:false, flag:128, body:cell, currencies:c}); destination.transfer({value:122, bounce:false, flag:128, body:cell}); destination.transfer({value:122, bounce:false, flag:128}); destination.transfer(10000, true, 1) destination.transfer(10000, false, 128, cell) - ``` + Some named parameters can be omitted. If parameter is omitted than default value is used. Default values: -value = 0 -bounce = true -flag = 1 -body = TvmCell -currencies = ExtraCurrencyCollection +``value`` = 0 +``bounce`` = true +``flag`` = 1 +``body`` = empty TvmCell +``currencies`` = empty ExtraCurrencyCollection See example of how to use address.transfer(): -- [giver](https://github.com/tonlabs/samples/blob/master/solidity/7_Giver.sol) + +* [giver](https://github.com/tonlabs/samples/blob/master/solidity/7_Giver.sol) #### mapping -TON Solidity compiler expands **mapping** type with the following functions: -##### 1. mapping.min() -``` -mapping.min() -> returns (KeyType key, ValueType value, bool have_value) -``` -This function computes the minimal key of mapping and returns that key, associated value and true for *have_value*. If mapping is empty, this function returns default values and false for *have_value*. +See example of how to work with mapping: -##### 2. mapping.max() -``` -mapping.max() -> returns (KeyType key, ValueType value, bool have_value) -``` -This function computes the maximal key of mapping and returns that key, associated value and true for *have_value*. If mapping is empty, this function returns default values and false for *have_value*. +* [database](https://github.com/tonlabs/samples/blob/master/solidity/13_BankCollector.sol) +* [client](https://github.com/tonlabs/samples/blob/master/solidity/13_BankCollectorClient.sol) -##### 3. mapping.next() -``` -mapping.next(KeyType prev_key) -> returns (KeyType nextkey, ValueType next_value, bool have_value) -``` -This function computes the minimal key in mapping that is lexicographically greater than *prev_key* and returns that key, associated value and status flag. +TON Solidity compiler expands **mapping** type with the following functions. +In code examples below identifier **map** defines object of **mapping(KeyType => ValueType)** type. -##### 4. mapping.prev() -``` -mapping.prev(KeyType prev_key) -> returns (KeyType nextkey, ValueType next_value, bool have_value) +##### mapping.min() and mapping.max() + +```TVMSolidity +(KeyType key, ValueType value, bool haveValue) = map.min(); +(KeyType key, ValueType value, bool haveValue) = map.max(); ``` -This function computes the maximal key in mapping that is lexicographically less than *prev_key* and returns that key, associated value and status flag. -##### 5. mapping.delMin() +This function computes the minimal (maximal) key of mapping and returns that key, associated value and true for **haveValue**. If mapping is empty, this function returns default values and false for **haveValue**. + +##### mapping.next() and mapping.prev() + +```TVMSolidity +KeyType key; +(KeyType nextKey, ValueType nextValue, bool haveValue) = map.next(key); +(KeyType prevKey, ValueType prevValue, bool haveValue) = map.prev(key); ``` -mapping.delMin() -> returns (KeyType firstKey, ValueType value) + +This function computes the minimal (maximal) key in mapping that is lexicographically greater (less) than **key** and returns that key, associated value and status flag. + +##### mapping.nextOrEq() and mapping.prevOrEq() + +```TVMSolidity +KeyType key; +(KeyType nextKey, ValueType nextValue, bool haveValue) = map.nextOrEq(key); +(KeyType prevKey, ValueType prevValue, bool haveValue) = map.prevOrEq(key); ``` -This function computes the minimal key of mapping, deletes that key and associated value from the mapping and returns that key and associated value. -##### 6. mapping.fetch() +This function computes the minimal (maximal) key in mapping that is lexicographically greater than or equal to (less than or equal to) **key** and returns that key, associated value and status flag. + +##### mapping.delMin() and mapping.delMax() + +```TVMSolidity +(KeyType firstKey, ValueType value) = map.delMin(); +(KeyType lastKey, ValueType value) = map.delMax(); ``` -mapping.fetch(KeyType key) -> returns (bool exists, ValueType value) + + If mapping is not empty than this function computes the minimal (maximum) key of mapping, deletes that key and associated value from the mapping and returns that key and associated value. + Else exception is thrown. + +##### mapping.fetch() + +```TVMSolidity +KeyType key; +(bool exists, ValueType value) = map.fetch(key); ``` -This function checks whether *key* presents in the mapping and returns status flag and associated value(if it exists). + +This function checks whether **key** presents in the mapping and returns status flag and associated value(if it exists). If the value has struct type and key doesn't present in the mapping than value is NULL. Use value only in such constructions: -``` + +```TVMSolidity (bool ok, MyStruct value) = map.fetch(key); if (ok) { - // use value only after this check + // use value here } ``` -##### 7. mapping.exists() +##### mapping.exists() + +```TVMSolidity +KeyType key; +bool exists = map.exists(key); ``` -mapping.exists(KeyType key) -> returns (bool exists) + +This function checks whether **key** presents in the mapping and returns status flag. + +##### mapping.empty() + +```TVMSolidity +bool isEmpty = map.empty(); ``` -This function checks whether *key* presents in the mapping and returns status flag. -##### 8. mapping.empty() +This function checks whether the mapping is empty and returns status flag. + +##### mapping.replace() + +```TVMSolidity +KeyType key; +ValueType value; +bool success = map.replace(key, value); ``` -mapping.empty() -> returns (bool isEmpty) + +This function sets the value associated with **key** only if the **key** is present in mapping. + +##### mapping.add() + +```TVMSolidity +KeyType key; +ValueType value; +bool success = map.add(key, value); ``` -This function checks whether the mapping is empty and returns status flag. -See example of how to work with mapping: -- [database](https://github.com/tonlabs/samples/blob/master/solidity/13_BankCollector.sol) -- [client](https://github.com/tonlabs/samples/blob/master/solidity/13_BankCollectorClient.sol) +This function sets the value associated with **key** only if the **key** is not present in mapping. + +##### mapping.getSet() + +```TVMSolidity +KeyType key; +ValueType value; +(ValueType oldValue, bool haveOldValue) = map.getSet(key, value); +``` + +This function sets the **value** associated with **key**, but also +returns the **oldValue** associated with the **key**, if present. Else it returns default value. + +##### mapping.getAdd() + +```TVMSolidity +KeyType key; +ValueType value; +(ValueType oldValue, bool haveOldValue) = map.getAdd(key, value); +``` + +This function sets the **value** associated with **key**, but only if **key** is not present in mapping. Otherwise, just returns the **oldValue** without changing the dictionary. +##### mapping.getReplace() + +```TVMSolidity +KeyType key; +ValueType value; +(ValueType oldValue, bool haveOldValue) = map.getReplace(key, value); +``` + +This function sets the **value** associated with **key**, but only if **key** is present in mapping. On success, returns the **oldValue** associated with the **key**. Otherwise, returns default value. #### ExtraCurrencyCollection + ExtraCurrencyCollection represents TVM type ExtraCurrencyCollection. It has the same functions as **mapping(uint32 => uint256)**: + +```TVMSolidity +ExtraCurrencyCollection curCol; +uint32 key; +uint256 value; +(uint32 minKey, uint256 minKeyValue, bool haveValue) = curCol.min(); +(uint32 nextKey, uint256 nextValue, bool haveValue) = curCol.next(key); +(uint32 prevKey, uint256 prevValue, bool haveValue) = curCol.prev(key); +(uint32 firstKey, uint256 value) = curCol.delMin(); +(uint32 firstKey, uint256 value) = curCol.delMax(); +(bool exists, uint256 value) = curCol.fetch(key); +bool exists = curCol.exists(key); +bool isEmpty = curCol.empty(); +bool success = curCol.replace(key, value); +bool success = curCol.add(key, value); +(uint32 oldValue, bool haveOldValue) = curCol.getSet(key, value); +(uint32 oldValue, bool haveOldValue) = curCol.getAdd(key, value); +(uint32 oldValue, bool haveOldValue) = curCol.getReplace(key, value); +uint256 value = curCol[index]; ``` -ExtraCurrencyCollection CurCol; -CurCol.min() -> returns (uint32 key, uint256 value, bool have_value) -CurCol.next(uint32 prev_key) -> returns (uint32 nextkey, uint256 next_value, bool have_value) -CurCol.delMin() -> returns (uint32 firstKey, uint256 value) -CurCol.fetch(uint32 key) -> returns (bool exists, uint256 value) -CurCol.exists(uint32 key) -> returns (bool exists) -CurCol.empty() -> returns (bool isEmpty) -CurCol[index] -> returns (uint256 value) + +#### require, revert + +On exception state variables of the contract are reverted to state before +[tvm.commit()](#tvmcommit) or to state before contract was called. + +##### require + +```TVMSolidity +uint a = 5; + +require(a == 5); // ok +require(a == 6); // throw exception 100 +require(a == 6, 101); // throw exception 101 +require(a == 6, 101, "a is not equal to six"); // throw exception 101 and string +require(a == 6, 101, a); // throw exception 101 and number a ``` +The **require** function can be used to check for conditions and throw an exception if the condition is not met. The function takes condition and optional parameters: error code (unsigned integer) and any object. + +##### revert + +```TVMSolidity +uint a = 5; +revert(); // throw exception 100 +revert(101); // throw exception 101 +revert(102, "We have a some problem"); // throw exception 102 and string +revert(101, a); // throw exception 101 and number a +``` + +The **revert** function are used to trigger exceptions. The function takes an optional error code (unsigned integer) and some object. + ### Pragmas #### ignoreIntOverflow -``` + +```TVMSolidity pragma ignoreIntOverflow; ``` + This pragma turns off binary operation result overflow check. #### AbiHeader -``` + +```TVMSolidity pragma AbiHeader time; -pragma AbiHeader pubkey +pragma AbiHeader pubkey; pragma AbiHeader expire; ``` -This pragma can tells the compiler that message contains an approriate field in the header. -See example of [message expiration time](https://docs.ton.dev/86757ecb2/p/88321a-message-expiration-time). -### Contract functions +This pragmas force message forming utility to fill an appropriate fields in the header of message to be sent to this contract: -#### onBounce +* **pubkey** - public key by which the message was signed; +* **time** - local time at what message was created; +* **expire** - time at which message should be meant as expired. + +**pubkey** field is necessary for the contract to be able to check message signature which was generated with +public key that is different from what is stored in this contract data. +**time** and **expire** fields can be used for replay protection and if set they should be read in [afterSignatureCheck](#aftersignaturecheck) in case of not default replay protection. +To read more about this and ABI follow this [link](https://docs.ton.dev/86757ecb2/p/40ba94-abi-specification-v2). +Here is example of [message expiration time](https://docs.ton.dev/86757ecb2/p/88321a-message-expiration-time) usage. + +### Special contract functions + +##### receive + +```TVMSolidity +contract Sink { + uint counter = 0; + receive() external payable { + ++counter; + } ``` + +On plain value transfer **receive** function is +called. See [address.transfer()](#addresstransfer) +If there is no **receive** function and **fallback** function +**fallback** function is called. +If there are no **receive** and **fallback** functions, contract +does nothing on plain value transfer. +If you don't want the contract to receive plain value transfers, define + **receive** function and throw exception in that function. + +##### fallback + +```TVMSolidity +contract Contr { + uint counter = 0; + fallback() external { + ++counter; + } +``` + +**fallback** function is called when body of the inbound message has invalid +function id. +If no **receive** function is defined and **fallback** function exists, +**fallback** function is called on plain value transfer. See [address.transfer()](#addresstransfer). + + +#### onBounce + +```TVMSolidity onBounce(TvmSlice slice) external override { - /*...*/ + /*...*/ } ``` -**onBounce** function is executed if inbound internal message has bounced flag set. Parameter slice of onBounce function contains body of the message. + +**onBounce** function is executed if inbound internal message has bounced flag set. Parameter slice of onBounce function contains truncated body of the message (it's truncated by the network). If this function it not defined than on inbound internal message contract do nothing. See example of how to use **onBounce** function: -- [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) + +* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) #### onCodeUpgrade -``` + +```TVMSolidity function onCodeUpgrade() private { - /*...*/ + /*...*/ } ``` -**onCodeUpgrade** function can have arbuitrary set of arguments and should be executed after [tvm.setcode()](#tvmsetcode) function call. In this function [tvm.resetStorage()](#tvmresetstorage) should be called if the set of state variables is changed in the new version of the contract. This function implicitly calls [tvm.commit()](#tvmcommit). After return from **onCodeUpgrade** TVM execution is finished with exit code 0. + +**onCodeUpgrade** function can have arbitrary set of arguments and should be executed after [tvm.setcode()](#tvmsetcode) function call. In this function [tvm.resetStorage()](#tvmresetstorage) should be called if the set of state variables is changed in the new version of the contract. This function implicitly calls [tvm.commit()](#tvmcommit). After return from **onCodeUpgrade** TVM execution is finished with exit code 0. See example of how to upgrade code: -- [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) -- [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) +* [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) +* [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) #### afterSignatureCheck -``` + +```TVMSolidity function afterSignatureCheck(TvmSlice body, TvmCell message) private inline returns (TvmSlice) { - /*...*/ + /*...*/ } ``` + Developer can define **afterSignatureCheck** function to create his own replay protection function instead of default one. See example of how to define this function: -- [Custom replay protection](https://github.com/tonlabs/samples/blob/master/solidity/14_CustomReplayProtection.sol) +* [Custom replay protection](https://github.com/tonlabs/samples/blob/master/solidity/14_CustomReplayProtection.sol) -### Function specificators +### Function specifiers #### functionID() - ``` + +```TVMSolidity function functionName() public pure functionID(123) { - /*...*/ + /*...*/ } ``` -This keyword allows to set identificator of the function manually. -### Events +This keyword allows to set identifier of the function manually. + +### Events and return #### extAddr + +```TVMSolidity +emit EventName(arguments).extAddr(address); +emit EventName(arguments); ``` -emit EventName(arguments).extAddr(address) + +TON Solidity compiler allows to specify destination address of the message sent via event emitting using suffix **extAddr**. If **extAddr** suffix is not used, external address is set to **addr_none**. + +#### return + +```TVMSolidity +function f(uint n) public pure { + return n <= 1? 1 : n * f(n - 1); +} ``` -TON Solidity compiler allows to specify destination address of the message sent via event emitting using suffix **extAddr**. + +Public or external functions (called by external message) send an external message on return. Destination address of that message is the source address of the inbound external message. +For example, if function **f** above was called with **n** = 5 by external message, only one external message is sent. Internal call of public or external function doesn't generate external message. ### API functions and members #### **msg** namespace ##### msg.pubkey() + +```TVMSolidity +uint256 pubkey = msg.pubkey(); ``` -msg.pubkey() -> returns (uint256 pubkey) -``` + This function returns sender's public key, obtained from the body if the external inbound message. If message is not signed function returns 0. If message is signed and message header ([AbiHeader](#abiheader)) does not contain pubkey than msg.pubkey() is equal to tvm.pubkey(). ##### msg.createdAt + +```TVMSolidity +uint32 created_at = msg.createdAt; ``` -msg.createdAt -> returns (uint32 created_at) -``` -This member is a field *created_at* of the external inbound message. + +This member is a field **created_at** of the external inbound message. ##### msg.currencies + +```TVMSolidity +ExtraCurrencyCollection c = msg.currencies; ``` -msg.currencies -> returns ExtraCurrencyCollection -``` -This member is a field *CurrencyCollection:other* of the external inbound message. +This member is a field of the internal inbound message. #### **tvm** namespace ##### TVM instructions ##### tvm.accept() -``` + +```TVMSolidity tvm.accept() ``` + This function executes TVM instruction "ACCEPT" ([TVM][1] - A.11.2. - F800). This instruction sets current gas limit to its maximal allowed value. This action is required to process external messages, which bring no value. See example of how to use this function: -- [accumulator](https://github.com/tonlabs/samples/blob/master/solidity/1_Accumulator.sol) + +* [accumulator](https://github.com/tonlabs/samples/blob/master/solidity/1_Accumulator.sol) ##### tvm.commit() -``` + +```TVMSolidity tvm.commit() ``` + This function executes TVM instruction "COMMIT" ([TVM][1] - A.11.2. - F80F). This instruction commits the current state of registers c4 and c5 so that the current execution is considered “successful” with the saved values even if an exception is thrown later. ##### tvm.log() -``` + +```TVMSolidity tvm.log(string) logtvm(string) ``` + This function executes TVM instruction "PRINTSTR" ([TVM][1] - A.12.2. - FEFn01ssss). This command may be ignored if --without-logstr flag is presented in command line for compiler. **logtvm** is an alias for tvm.log(string). ##### tvm.setcode() + +```TVMSolidity +tvm.setcode(TvmCell newCode) ``` -tvm.setcode(TvmCell newcode) -``` -This function executes TVM instruction "SETCODE" ([TVM][1] - A.11.9. - FB04). This command creates an output action that would change this smart contract code to that given by Cell *newcode* (this change will take effect only after the successful termination of the current run of the smart contract). + +This function executes TVM instruction "SETCODE" ([TVM][1] - A.11.9. - FB04). This command creates an output action that would change this smart contract code to that given by Cell **newCode** (this change will take effect only after the successful termination of the current run of the smart contract). See example of how to use this function: -- [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) -- [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) + +* [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) +* [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) ##### tvm.cdatasize() + +```TVMSolidity +TvmCell cell; +uint totalNumOfCells; +(uint cells, uint bits, uint refs) = tvm.cdatasize(cell, totalNumOfCells); ``` -tvm.cdatasize(TvmCell cell, uint totalNumOfCells) -> returns (uint cells, uint bits, uint refs) -``` + This function executes TVM instruction "CDATASIZE" ([TVM][1] - A.11.7. - F941). This command recursively computes the count of distinct cells, data bits and cell references. ##### tvm.transLT() + +```TVMSolidity +uint64 time = tvm.transLT(); ``` -tvm.transLT() -``` + This function executes TVM instruction "LTIME" ([TVM][1] - A.11.4. - F825). This command returns the logical time of the current transaction. ##### tvm.configParam() + +```TVMSolidity +(TypeA a, TypeB b, ...) = tvm.configParam(uint8(paramNumber)); ``` -tvm.configParam(uint8 paramNumber) -``` -This function executes TVM instruction "CONFIGPARAM" ([TVM][1] - A.11.4. - F832). This command returns the value of the global configuration parameter with integer index paramNumber. Argument should be an integer literal. Supported paramNumbers: 1, 15, 17, 34. +This function executes TVM instruction "CONFIGPARAM" ([TVM][1] - A.11.4. - F832). This command returns the value of the global configuration parameter with integer index paramNumber. Argument should be an integer literal. Supported paramNumbers: 1, 15, 17, 34. ##### Hashing and cryptography ##### tvm.hash() + +```TVMSolidity +uint256 hash = tvm.hash(TvmCell cellTree); +uint256 hash = tvm.hash(string); +uint256 hash = tvm.hash(bytes); ``` -tvm.hash(TvmCell cellTree) -> returns (uint256) -tvm.hash(string) -> returns (uint256) -tvm.hash(bytes) -> returns (uint256) -``` + This function executes TVM instruction "HASHCU" ([TVM][1] - A.11.6. - F900). It computes the representation hash of a given argument and returns it as a 256-bit unsigned integer ##### tvm.checkSign() + +```TVMSolidity +uint256 hash; +uint256 SignHighPart; +uint256 SignLowPart; +uint256 pubkey; +bool signatureIsValid = tvm.checkSign(hash, SignHighPart, SignLowPart, pubkey); ``` -tvm.checkSign(uint256 /*hash*/, uint256 /*SignHighPart*/, uint256 /*SignLowPart*/, uint256 /*pubkey*/) ->returns (bool) -``` -This function executes TVM instruction "CHKSIGNU" ([TVM][1] - A.11.6. - F910). This command checks the Ed25519-signature of a hash using public key pubKey. Signature is represented by two uint256 SignHighPart and SignLowPart. +This function executes TVM instruction "CHKSIGNU" ([TVM][1] - A.11.6. - F910). This command checks the Ed25519-signature of a **hash** using public key **pubKey**. Signature is represented by two uint256 **SignHighPart** and **SignLowPart**. ##### Deploy contract from contract ##### tvm.insertPubkey() + +```TVMSolidity +TvmCell stateInit; +uint256 pubkey; +TvmCell stateInitWithKey = tvm.insertPubkey(stateInit, pubkey); ``` -tvm.insertPubkey(TvmCell stateInit, uint256 pubkey) -> returns(TvmCell stateInitWithKey) -``` + This function inserts a public key into contract's data field. ##### tvm.buildStateInit() + +```TVMSolidity +TvmCell code; +TvmCell data; +TvmCell StateInit = tvm.buildStateInit(code, data); ``` -tvm.buildStateInit(TvmCell code, TvmCell data) -> returns (TvmCell StateInit) -``` + This function generates a StateInit ([TBLKCH][2] - 3.1.7.) from code and data cells. ##### tvm.deploy() + +```TVMSolidity +TvmCell stateInit; +address addr; +uint128 value; +TvmCell payload; +tvm.deploy(stateInit, addr, value, payload); ``` -tvm.deploy(TvmCell stateInit, address addr, uint128 grams, TvmCell payload) -``` + This function implements "Create smart contract by a smart contract" functionality. It generates and sends a constructor message to create a new contract. Arguments: stateInit - contract's StateInit; addr - address of the contract; -gram - amount of currency in nanograms that will be sent to the new contract address; +value - amount of currency in nano tons that will be sent to the new contract address; payload - encoded message of constructor call. ##### tvm.deployAndCallConstructor() + +```TVMSolidity +TvmCell stateInit; +address addr; +uint128 value; +uint32 constructor_id; +tvm.deployAndCallConstructor(stateInit, addr, value, constructor_id[, ]) ``` -tvm.deployAndCallConstructor(TvmCell stateInit, address addr, uint128 grams, uint32 constructor_id, ) -``` + This function is equal to tvm.deploy() but it takes not body of a constructor call but builds it and then attaches to the constructor message. Arguments: stateInit - contract's StateInit; addr - address of the contract; -grams - amount of currency in nanograms that will be sent to the new contract address; -constructor_id - identificator of constructor function; +value - amount of currency in nano tons that will be sent to the new contract address; +constructor_id - identifier of constructor function; \. ##### tvm.deployAndCallConstructorWithFlag() + +```TVMSolidity +TvmCell stateInit; +address addr; +uint128 value; +uint8 flag; +uint32 constructor_id; +tvm.deployAndCallConstructorWithFlag(stateInit, addr, value, flag, constructor_id[, ]) ``` -tvm.deployAndCallConstructorWithFlag(TvmCell my_contract, address addr, uint128 gram, uint8 flag, uint32 constructor_id, ) -``` + This function is equal to tvm.deployAndCallConstructor() but sends the message with an appropriate flag. See example of how to deploy contract from contract: -- [Contract_deployer](https://github.com/tonlabs/samples/blob/master/solidity/11_ContractDeployer.sol) + +* [Contract_deployer](https://github.com/tonlabs/samples/blob/master/solidity/11_ContractDeployer.sol) + +##### Deploy via new + +```TVMSolidity +TvmCell stateInitWithKey; +uint initialValue; +uint8 sendRawMsgFlag; +address newWallet = new SimpleWallet{stateInit:stateInitWithKey, value:initialValue, flag:sendRawMsgFlag}(); +``` + +Developer can deploy contract from contract using **new** expression. Detailed description can be found in [doc](https://github.com/tonlabs/samples/blob/master/solidity/17_ContractProducer.md). +Example can be found in the samples repo: + +* [Wallet_producer](https://github.com/tonlabs/samples/blob/master/solidity/17_ContractProducer.sol) ##### Others ##### tvm.pubkey() + +```TVMSolidity +uint256 pubkey = tvm.pubkey(); ``` -tvm.pubkey() -> returns (uint256 pubkey) -``` + This function returns contract's public key, stored in contract data. If key is not set function returns 0. ##### tvm.setCurrentCode() + +```TVMSolidity +TvmCell newCode; +tvm.setCurrentCode(newCode); ``` -tvm.setCurrentCode(TvmCell newcode) -``` -This function changes this smart contract current code to that given by Cell *newcode*. + +This function changes this smart contract current code to that given by Cell **newCode**. See example of how to use this function: -- [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) -- [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) -##### tvm.sendMsg() -``` -tvm.sendMsg(uint256 dest_addr_literal, uint32 funcID_literal, uint8 flag_literal, uint120 value = 10_000_000) -tvm.sendMsg(address dest_addr, uint32 funcID_literal, uint8 flag_literal, uint120 value = 10_000_000) -``` -This function allows to call remote contract with address dest_addr function. Remote contract function is defined with identificator funcID_literal. Function generates msg and sends it via SENDRAWMSG instruction. If not passed value is set to 10000000 (10000 gas * 1000 gas price). +* [old contract](https://github.com/tonlabs/samples/blob/master/solidity/12_BadContract.sol) +* [new contract](https://github.com/tonlabs/samples/blob/master/solidity/12_NewVersion.sol) ##### tvm.resetStorage() -``` -tvm.resetStorage() -``` -This function resets all state variables to their default values. -##### tvm.setExtDestAddr() -``` -tvm.setExtDestAddr(address destAddr) +```TVMSolidity +tvm.resetStorage(); ``` -This function sets destination address for outbound external messages sent on emit or return command. + +This function resets all state variables to their default values. ##### tvm.functionId() + +```TVMSolidity +uint32 funcID = tvm.functionId(function_name); ``` -tvm.functionId(function_name) -> returns (uint32) -``` + This function returns function id (uint32) for public/external function. Example: -``` + +```TVMSolidity function f() public pure returns (uint) { - /*...*/ + /*...*/ } function getFuncID() public pure returns (uint32) { - uint32 functionId == tvm.functionId(f); - return functionId; + uint32 functionId == tvm.functionId(f); + return functionId; } ``` See example of how to use this function: -- [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) -##### tvm.min() -``` -tvm.min(int a, int b, ...) -> returns (int) -tvm.min(uint a, uint b, ...) -> returns (uint) +* [onBounceHandler](https://github.com/tonlabs/samples/blob/master/solidity/16_onBounceHandler.sol) + +##### tvm.encodeBody() + +```TVMSolidity +TvmCell body = tvm.encodeBody(function_name, arg0, arg1, arg2, ...); ``` -This function returns the minimal value of the passed arguments. -##### tvm.max() +This function constructs a function call message body that can be used +as the [transfer](#addresstransfer) payload. +Example: + +```TVMSolidity +contract Remote { + function func(uint256 num, address a, int64 num2) public pure {} +} + +contract Caller { + function test() public pure { + TvmCell body = tvm.encodeBody(Remote.func, 123, address.makeAddrStd(22, 333), -654); + msg.sender.transfer({value:1e10, body:body}); + } +} ``` -tvm.max(int a, int b, ...) -> returns (int) -tvm.max(uint a, uint b, ...) -> returns (uint) + +##### tvm.min() and tvm.max() + +```TVMSolidity +int min = tvm.min(int a, int b, ...); +uint max = tvm.min(uint a, uint b, ...); +int min = tvm.max(int a, int b, ...); +uint max = tvm.max(uint a, uint b, ...); ``` -This function returns the maximal value of the passed arguments. +This function returns the minimal (maximal) value of the passed arguments. [1]: https://ton.org/tvm.pdf "TVM" [2]: https://ton.org/tblkch.pdf "TBLKCH" diff --git a/README.md b/README.md index b93c319db8340ded29c4eb5117b715dd69676186..147e477faaec8132dba49e7c47d3f7c285402137 100644 GIT binary patch literal 1753 zcmcgt+in{-5Pj!Y44{_;@b0#09|{8jVoOSD#1}!59kfm%lgrUABQCk%MUMYI!@Wo` z;^wUd1Za0TGn_f+@NfbvtKY2eu3&1LR%vvMEvyS*w-9Pft4gZy1is-eWTiF1@iM@D zIfVc7st%#`r$tezP)Cu|LV=X(FgArB(qBV`1|1K|RFHNPPXS9S5w_OB`sNyhw&kv@ zxiUE{G;)vNaElIth1mMwkTHc)*V z_XpUdEn|ZwbX^{?8ga>KEJ=-1n9r3h&=i^n^r09wCA_jja2*;w>H12ax6U@S7F~(2 zyFn*#E20SzE|rO28GCqm@HNu7#0-?$8U%WJcjW0bSfjb{1rd;qEoY+;atdUnOzix) zSd8;CUqfE_(y2E1vFnY**5|%v%}y%ULzzl-y|c36JA^!kY<9bvZ>D$C-{0Q6e?Pqb z{oC-ep0C!c;dy#>_Hn*BzrDDeZPtG+=5J>xV#T^b*_S`ksEn;GY6W5HAS8R>UdS)ak!nbyPc za0J_)li~&WqEqNkKyASwN|dC;=O*VBId^jHOL7mmPCmYxgH&YGO2p2PIT`72G0-Hr s#C_4@vcxhSEV6o2p=WH2dSUdJori7;F#<6vvY2+|w5>LjL>C162KY*36aWAK literal 2956 zcmdUx+fEZv6o%JzpJI~UNMfg+E)0o@K+zCU8j2VNgY;aXhqN;wFRy<8+8w6TR&BU5 zO*@^n_d5UU@at#UB5T;#uI-tf*r~neeUJB{O>6+QV^b^fsY9Du!s*z${3h1tEaM$m zo7;)qL7BmO>31vE;`Rvo6iUmv&z!a>{i{7fR>Efj@3vL>^juosQfpc~=cuADXr1ye zMN5_YvOL$mh5jC=Bfip^a5jPx-TT(W#)MN@*5(=EjXcwhBm2O!iNrDISA6@(kc1d| z-J@}dPI1YGE?;pb+>fwG_RH4}Ttg_wwr@LDBc}UE7@{xNCaFQQq9r>ODMeSmS#+;C zll>`A`4w?5aSN{Djy*GFD?{Jqj`AfP;?eVfr!2aJe$Cw@Y#Sg)x<$)xOyO33f)z8D z(ZrS%T0|7acFQyP*<7PZ5_3(t{Q+5dej<rl0fmUy14Hk?9pkTxT(ii7Yr;#WBkV&q@dZOT2Bg*~fqR>WcKh#taQ z#;+_h+3hZRRbhcI<*kMMt%ZD+@%y85%Bz&OL)V#VDFWukf!* z5L#Z(pT%n0=0x}8T@(AObNp9UqFh<&2k~5D32n+#sGLnWt8gs$7I_ijyQ;b62%TV? zEGnYv0$m-iv6$07Q5aIA>N~2zJj$6>MT)BI%A*paDiG8Mf>~p(@1+uH>)@K$~?7O|Z&#Jnu zYFZfhh+fdI zE^-X7YVFT*BDehp`-QIwPenaU3P}nTTZbC*o80^JFO+W9`yYD%L1cfnOKCe9dFN5Rtg~<%`a^3#`&tG6a~;yX14VscjE$OWnP$c52HQ1L)D6U? zO>F>u=3QaHRuMs!pX#>ld9V1}gjTe}CY5bahe*{-iP)#IElZs?;-31HA~|sEWE+Qe zmau6Q_rOr~vCF?9){7gDkfpg9ya~ISrm=j{j4VAks@elLn`U8GY_4EpdCFDkl-ebO Qed?e+p9|6yHp7zr24yz`kpKVy diff --git a/compiler/CMakeLists.txt b/compiler/CMakeLists.txt index 98318361..f168de85 100644 --- a/compiler/CMakeLists.txt +++ b/compiler/CMakeLists.txt @@ -51,16 +51,9 @@ configure_project(TESTS) add_subdirectory(libsolutil) add_subdirectory(liblangutil) -add_subdirectory(libevmasm) -add_subdirectory(libyul) add_subdirectory(libsolidity) add_subdirectory(libsolc) -# add_subdirectory(tools) if (NOT EMSCRIPTEN) add_subdirectory(solc) endif() - -if (TESTS AND NOT EMSCRIPTEN) - add_subdirectory(test) -endif() diff --git a/compiler/libevmasm/Assembly.cpp b/compiler/libevmasm/Assembly.cpp deleted file mode 100644 index 10503c48..00000000 --- a/compiler/libevmasm/Assembly.cpp +++ /dev/null @@ -1,657 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file Assembly.cpp - * @author Gav Wood - * @date 2014 - */ - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::langutil; -using namespace solidity::util; - -AssemblyItem const& Assembly::append(AssemblyItem const& _i) -{ - assertThrow(m_deposit >= 0, AssemblyException, "Stack underflow."); - m_deposit += _i.deposit(); - m_items.emplace_back(_i); - if (!m_items.back().location().isValid() && m_currentSourceLocation.isValid()) - m_items.back().setLocation(m_currentSourceLocation); - m_items.back().m_modifierDepth = m_currentModifierDepth; - return m_items.back(); -} - -unsigned Assembly::bytesRequired(unsigned subTagSize) const -{ - for (unsigned tagSize = subTagSize; true; ++tagSize) - { - unsigned ret = 1; - for (auto const& i: m_data) - ret += i.second.size(); - - for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(tagSize); - if (util::bytesRequired(ret) <= tagSize) - return ret; - } -} - -namespace -{ - -string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) -{ - if (!_location.hasText() || _sourceCodes.empty()) - return ""; - - auto it = _sourceCodes.find(_location.source->name()); - if (it == _sourceCodes.end()) - return ""; - - string const& source = it->second; - if (size_t(_location.start) >= source.size()) - return ""; - - string cut = source.substr(_location.start, _location.end - _location.start); - auto newLinePos = cut.find_first_of("\n"); - if (newLinePos != string::npos) - cut = cut.substr(0, newLinePos) + "..."; - - return cut; -} - -class Functionalizer -{ -public: - Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes): - m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes) - {} - - void feed(AssemblyItem const& _item) - { - if (_item.location().isValid() && _item.location() != m_location) - { - flush(); - m_location = _item.location(); - printLocation(); - } - if (!( - _item.canBeFunctional() && - _item.returnValues() <= 1 && - _item.arguments() <= int(m_pending.size()) - )) - { - flush(); - m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl; - return; - } - string expression = _item.toAssemblyText(); - if (_item.arguments() > 0) - { - expression += "("; - for (int i = 0; i < _item.arguments(); ++i) - { - expression += m_pending.back(); - m_pending.pop_back(); - if (i + 1 < _item.arguments()) - expression += ", "; - } - expression += ")"; - } - - m_pending.push_back(expression); - if (_item.returnValues() != 1) - flush(); - } - - void flush() - { - for (string const& expression: m_pending) - m_out << m_prefix << " " << expression << endl; - m_pending.clear(); - } - - void printLocation() - { - if (!m_location.isValid()) - return; - m_out << m_prefix << " /*"; - if (m_location.source) - m_out << " \"" + m_location.source->name() + "\""; - if (m_location.hasText()) - m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end); - m_out << " " << locationFromSources(m_sourceCodes, m_location); - m_out << " */" << endl; - } - -private: - strings m_pending; - SourceLocation m_location; - - ostream& m_out; - string const& m_prefix; - StringMap const& m_sourceCodes; -}; - -} - -void Assembly::assemblyStream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const -{ - Functionalizer f(_out, _prefix, _sourceCodes); - - for (auto const& i: m_items) - f.feed(i); - f.flush(); - - if (!m_data.empty() || !m_subs.empty()) - { - _out << _prefix << "stop" << endl; - for (auto const& i: m_data) - if (u256(i.first) >= m_subs.size()) - _out << _prefix << "data_" << toHex(u256(i.first)) << " " << toHex(i.second) << endl; - - for (size_t i = 0; i < m_subs.size(); ++i) - { - _out << endl << _prefix << "sub_" << i << ": assembly {\n"; - m_subs[i]->assemblyStream(_out, _prefix + " ", _sourceCodes); - _out << _prefix << "}" << endl; - } - } - - if (m_auxiliaryData.size() > 0) - _out << endl << _prefix << "auxdata: 0x" << toHex(m_auxiliaryData) << endl; -} - -string Assembly::assemblyString(StringMap const& _sourceCodes) const -{ - ostringstream tmp; - assemblyStream(tmp, "", _sourceCodes); - return tmp.str(); -} - -Json::Value Assembly::createJsonValue(string _name, int _begin, int _end, string _value, string _jumpType) -{ - Json::Value value; - value["name"] = _name; - value["begin"] = _begin; - value["end"] = _end; - if (!_value.empty()) - value["value"] = _value; - if (!_jumpType.empty()) - value["jumpType"] = _jumpType; - return value; -} - -string Assembly::toStringInHex(u256 _value) -{ - std::stringstream hexStr; - hexStr << std::uppercase << hex << _value; - return hexStr.str(); -} - -Json::Value Assembly::assemblyJSON(StringMap const& _sourceCodes) const -{ - Json::Value root; - - Json::Value& collection = root[".code"] = Json::arrayValue; - for (AssemblyItem const& i: m_items) - { - switch (i.type()) - { - case Operation: - collection.append( - createJsonValue(instructionInfo(i.instruction()).name, i.location().start, i.location().end, i.getJumpTypeAsString())); - break; - case Push: - collection.append( - createJsonValue("PUSH", i.location().start, i.location().end, toStringInHex(i.data()), i.getJumpTypeAsString())); - break; - case PushString: - collection.append( - createJsonValue("PUSH tag", i.location().start, i.location().end, m_strings.at((h256)i.data()))); - break; - case PushTag: - if (i.data() == 0) - collection.append( - createJsonValue("PUSH [ErrorTag]", i.location().start, i.location().end, "")); - else - collection.append( - createJsonValue("PUSH [tag]", i.location().start, i.location().end, toString(i.data()))); - break; - case PushSub: - collection.append( - createJsonValue("PUSH [$]", i.location().start, i.location().end, toString(h256(i.data())))); - break; - case PushSubSize: - collection.append( - createJsonValue("PUSH #[$]", i.location().start, i.location().end, toString(h256(i.data())))); - break; - case PushProgramSize: - collection.append( - createJsonValue("PUSHSIZE", i.location().start, i.location().end)); - break; - case PushLibraryAddress: - collection.append( - createJsonValue("PUSHLIB", i.location().start, i.location().end, m_libraries.at(h256(i.data()))) - ); - break; - case PushDeployTimeAddress: - collection.append( - createJsonValue("PUSHDEPLOYADDRESS", i.location().start, i.location().end) - ); - break; - case Tag: - collection.append( - createJsonValue("tag", i.location().start, i.location().end, toString(i.data()))); - collection.append( - createJsonValue("JUMPDEST", i.location().start, i.location().end)); - break; - case PushData: - collection.append(createJsonValue("PUSH data", i.location().start, i.location().end, toStringInHex(i.data()))); - break; - default: - assertThrow(false, InvalidOpcode, ""); - } - } - - if (!m_data.empty() || !m_subs.empty()) - { - Json::Value& data = root[".data"] = Json::objectValue; - for (auto const& i: m_data) - if (u256(i.first) >= m_subs.size()) - data[toStringInHex((u256)i.first)] = toHex(i.second); - - for (size_t i = 0; i < m_subs.size(); ++i) - { - std::stringstream hexStr; - hexStr << hex << i; - data[hexStr.str()] = m_subs[i]->assemblyJSON(_sourceCodes); - } - } - - if (m_auxiliaryData.size() > 0) - root[".auxdata"] = toHex(m_auxiliaryData); - - return root; -} - -AssemblyItem Assembly::namedTag(string const& _name) -{ - assertThrow(!_name.empty(), AssemblyException, "Empty named tag."); - if (!m_namedTags.count(_name)) - m_namedTags[_name] = size_t(newTag().data()); - return AssemblyItem{Tag, m_namedTags.at(_name)}; -} - -AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier) -{ - h256 h(util::keccak256(_identifier)); - m_libraries[h] = _identifier; - return AssemblyItem{PushLibraryAddress, h}; -} - -Assembly& Assembly::optimise(bool _enable, EVMVersion _evmVersion, bool _isCreation, size_t _runs) -{ - OptimiserSettings settings; - settings.isCreation = _isCreation; - settings.runJumpdestRemover = true; - settings.runPeephole = true; - if (_enable) - { - settings.runDeduplicate = true; - settings.runCSE = true; - settings.runConstantOptimiser = true; - } - settings.evmVersion = _evmVersion; - settings.expectedExecutionsPerDeployment = _runs; - optimise(settings); - return *this; -} - - -Assembly& Assembly::optimise(OptimiserSettings const& _settings) -{ - optimiseInternal(_settings, {}); - return *this; -} - -map Assembly::optimiseInternal( - OptimiserSettings const& _settings, - std::set _tagsReferencedFromOutside -) -{ - // Run optimisation for sub-assemblies. - for (size_t subId = 0; subId < m_subs.size(); ++subId) - { - OptimiserSettings settings = _settings; - // Disable creation mode for sub-assemblies. - settings.isCreation = false; - map subTagReplacements = m_subs[subId]->optimiseInternal( - settings, - JumpdestRemover::referencedTags(m_items, subId) - ); - // Apply the replacements (can be empty). - BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); - } - - map tagReplacements; - // Iterate until no new optimisation possibilities are found. - for (unsigned count = 1; count > 0;) - { - count = 0; - - if (_settings.runJumpdestRemover) - { - JumpdestRemover jumpdestOpt{m_items}; - if (jumpdestOpt.optimise(_tagsReferencedFromOutside)) - count++; - } - - if (_settings.runPeephole) - { - PeepholeOptimiser peepOpt{m_items}; - while (peepOpt.optimise()) - { - count++; - assertThrow(count < 64000, OptimizerException, "Peephole optimizer seems to be stuck."); - } - } - - // This only modifies PushTags, we have to run again to actually remove code. - if (_settings.runDeduplicate) - { - BlockDeduplicator dedup{m_items}; - if (dedup.deduplicate()) - { - for (auto const& replacement: dedup.replacedTags()) - { - assertThrow( - replacement.first <= size_t(-1) && replacement.second <= size_t(-1), - OptimizerException, - "Invalid tag replacement." - ); - assertThrow( - !tagReplacements.count(replacement.first), - OptimizerException, - "Replacement already known." - ); - tagReplacements[replacement.first] = replacement.second; - if (_tagsReferencedFromOutside.erase(size_t(replacement.first))) - _tagsReferencedFromOutside.insert(size_t(replacement.second)); - } - count++; - } - } - - if (_settings.runCSE) - { - // Control flow graph optimization has been here before but is disabled because it - // assumes we only jump to tags that are pushed. This is not the case anymore with - // function types that can be stored in storage. - AssemblyItems optimisedItems; - - bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem{Instruction::MSIZE}) != m_items.end()); - - auto iter = m_items.begin(); - while (iter != m_items.end()) - { - KnownState emptyState; - CommonSubexpressionEliminator eliminator{emptyState}; - auto orig = iter; - iter = eliminator.feedItems(iter, m_items.end(), usesMSize); - bool shouldReplace = false; - AssemblyItems optimisedChunk; - try - { - optimisedChunk = eliminator.getOptimizedItems(); - shouldReplace = (optimisedChunk.size() < size_t(iter - orig)); - } - catch (StackTooDeepException const&) - { - // This might happen if the opcode reconstruction is not as efficient - // as the hand-crafted code. - } - catch (ItemNotAvailableException const&) - { - // This might happen if e.g. associativity and commutativity rules - // reorganise the expression tree, but not all leaves are available. - } - - if (shouldReplace) - { - count++; - optimisedItems += optimisedChunk; - } - else - copy(orig, iter, back_inserter(optimisedItems)); - } - if (optimisedItems.size() < m_items.size()) - { - m_items = move(optimisedItems); - count++; - } - } - } - - if (_settings.runConstantOptimiser) - ConstantOptimisationMethod::optimiseConstants( - _settings.isCreation, - _settings.isCreation ? 1 : _settings.expectedExecutionsPerDeployment, - _settings.evmVersion, - *this - ); - - return tagReplacements; -} - -LinkerObject const& Assembly::assemble() const -{ - // Return the already assembled object, if present. - if (!m_assembledObject.bytecode.empty()) - return m_assembledObject; - // Otherwise ensure the object is actually clear. - assertThrow(m_assembledObject.linkReferences.empty(), AssemblyException, "Unexpected link references."); - - size_t subTagSize = 1; - for (auto const& sub: m_subs) - { - sub->assemble(); - for (size_t tagPos: sub->m_tagPositionsInBytecode) - if (tagPos != size_t(-1) && tagPos > subTagSize) - subTagSize = tagPos; - } - - LinkerObject& ret = m_assembledObject; - - size_t bytesRequiredForCode = bytesRequired(subTagSize); - m_tagPositionsInBytecode = vector(m_usedTags, -1); - map> tagRef; - multimap dataRef; - multimap subRef; - vector sizeRef; ///< Pointers to code locations where the size of the program is inserted - unsigned bytesPerTag = util::bytesRequired(bytesRequiredForCode); - uint8_t tagPush = (uint8_t)Instruction::PUSH1 - 1 + bytesPerTag; - - unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1 + m_auxiliaryData.size(); - for (auto const& sub: m_subs) - bytesRequiredIncludingData += sub->assemble().bytecode.size(); - - unsigned bytesPerDataRef = util::bytesRequired(bytesRequiredIncludingData); - uint8_t dataRefPush = (uint8_t)Instruction::PUSH1 - 1 + bytesPerDataRef; - ret.bytecode.reserve(bytesRequiredIncludingData); - - for (AssemblyItem const& i: m_items) - { - // store position of the invalid jump destination - if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1)) - m_tagPositionsInBytecode[0] = ret.bytecode.size(); - - switch (i.type()) - { - case Operation: - ret.bytecode.push_back((uint8_t)i.instruction()); - break; - case PushString: - { - ret.bytecode.push_back((uint8_t)Instruction::PUSH32); - unsigned ii = 0; - for (auto j: m_strings.at((h256)i.data())) - if (++ii > 32) - break; - else - ret.bytecode.push_back((uint8_t)j); - while (ii++ < 32) - ret.bytecode.push_back(0); - break; - } - case Push: - { - uint8_t b = max(1, util::bytesRequired(i.data())); - ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b); - ret.bytecode.resize(ret.bytecode.size() + b); - bytesRef byr(&ret.bytecode.back() + 1 - b, b); - toBigEndian(i.data(), byr); - break; - } - case PushTag: - { - ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); - ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); - break; - } - case PushData: - ret.bytecode.push_back(dataRefPush); - dataRef.insert(make_pair((h256)i.data(), ret.bytecode.size())); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - case PushSub: - assertThrow(i.data() <= size_t(-1), AssemblyException, ""); - ret.bytecode.push_back(dataRefPush); - subRef.insert(make_pair(size_t(i.data()), ret.bytecode.size())); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - case PushSubSize: - { - assertThrow(i.data() <= size_t(-1), AssemblyException, ""); - auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); - i.setPushedValue(u256(s)); - uint8_t b = max(1, util::bytesRequired(s)); - ret.bytecode.push_back((uint8_t)Instruction::PUSH1 - 1 + b); - ret.bytecode.resize(ret.bytecode.size() + b); - bytesRef byr(&ret.bytecode.back() + 1 - b, b); - toBigEndian(s, byr); - break; - } - case PushProgramSize: - { - ret.bytecode.push_back(dataRefPush); - sizeRef.push_back(ret.bytecode.size()); - ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef); - break; - } - case PushLibraryAddress: - ret.bytecode.push_back(uint8_t(Instruction::PUSH20)); - ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data()); - ret.bytecode.resize(ret.bytecode.size() + 20); - break; - case PushDeployTimeAddress: - ret.bytecode.push_back(uint8_t(Instruction::PUSH20)); - ret.bytecode.resize(ret.bytecode.size() + 20); - break; - case Tag: - assertThrow(i.data() != 0, AssemblyException, "Invalid tag position."); - assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag."); - assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); - assertThrow(m_tagPositionsInBytecode[size_t(i.data())] == size_t(-1), AssemblyException, "Duplicate tag position."); - m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size(); - ret.bytecode.push_back((uint8_t)Instruction::JUMPDEST); - break; - default: - assertThrow(false, InvalidOpcode, "Unexpected opcode while assembling."); - } - } - - if (!m_subs.empty() || !m_data.empty() || !m_auxiliaryData.empty()) - // Append an INVALID here to help tests find miscompilation. - ret.bytecode.push_back(uint8_t(Instruction::INVALID)); - - for (size_t i = 0; i < m_subs.size(); ++i) - { - auto references = subRef.equal_range(i); - if (references.first == references.second) - continue; - for (auto ref = references.first; ref != references.second; ++ref) - { - bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } - ret.append(m_subs[i]->assemble()); - } - for (auto const& i: tagRef) - { - size_t subId; - size_t tagId; - tie(subId, tagId) = i.second; - assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id"); - std::vector const& tagPositions = - subId == size_t(-1) ? - m_tagPositionsInBytecode : - m_subs[subId]->m_tagPositionsInBytecode; - assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); - size_t pos = tagPositions[tagId]; - assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); - assertThrow(util::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); - bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); - toBigEndian(pos, r); - } - for (auto const& dataItem: m_data) - { - auto references = dataRef.equal_range(dataItem.first); - if (references.first == references.second) - continue; - for (auto ref = references.first; ref != references.second; ++ref) - { - bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } - ret.bytecode += dataItem.second; - } - - ret.bytecode += m_auxiliaryData; - - for (unsigned pos: sizeRef) - { - bytesRef r(ret.bytecode.data() + pos, bytesPerDataRef); - toBigEndian(ret.bytecode.size(), r); - } - return ret; -} diff --git a/compiler/libevmasm/Assembly.h b/compiler/libevmasm/Assembly.h deleted file mode 100644 index e859951b..00000000 --- a/compiler/libevmasm/Assembly.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ - -#pragma once - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include -#include - -namespace solidity::evmasm -{ - -using AssemblyPointer = std::shared_ptr; - -class Assembly -{ -public: - AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); } - AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); } - /// Returns a tag identified by the given name. Creates it if it does not yet exist. - AssemblyItem namedTag(std::string const& _name); - AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } - bytes const& data(util::h256 const& _i) const { return m_data.at(_i); } - AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } - Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } - Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } - AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } - AssemblyItem newPushLibraryAddress(std::string const& _identifier); - - AssemblyItem const& append(AssemblyItem const& _i); - AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } - - template Assembly& operator<<(T const& _d) { append(_d); return *this; } - - /// Pushes the final size of the current assembly itself. Use this when the code is modified - /// after compilation and CODESIZE is not an option. - void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } - void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); } - - AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } - AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } - AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; } - AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; } - - /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) - /// on the stack. @returns the pushsub assembly item. - AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; } - void pushSubroutineSize(size_t _subRoutine) { append(newPushSubSize(_subRoutine)); } - /// Pushes the offset of the subroutine. - void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); } - - /// Appends @a _data literally to the very end of the bytecode. - void appendAuxiliaryDataToEnd(bytes const& _data) { m_auxiliaryData += _data; } - - /// Returns the assembly items. - AssemblyItems const& items() const { return m_items; } - - /// Returns the mutable assembly items. Use with care! - AssemblyItems& items() { return m_items; } - - int deposit() const { return m_deposit; } - void adjustDeposit(int _adjustment) { m_deposit += _adjustment; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } - void setDeposit(int _deposit) { m_deposit = _deposit; assertThrow(m_deposit >= 0, InvalidDeposit, ""); } - - /// Changes the source location used for each appended item. - void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; } - - /// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached. - LinkerObject const& assemble() const; - - struct OptimiserSettings - { - bool isCreation = false; - bool runJumpdestRemover = false; - bool runPeephole = false; - bool runDeduplicate = false; - bool runCSE = false; - bool runConstantOptimiser = false; - langutil::EVMVersion evmVersion; - /// This specifies an estimate on how often each opcode in this assembly will be executed, - /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage. - size_t expectedExecutionsPerDeployment = 200; - }; - - /// Modify and return the current assembly such that creation and execution gas usage - /// is optimised according to the settings in @a _settings. - Assembly& optimise(OptimiserSettings const& _settings); - - /// Modify (if @a _enable is set) and return the current assembly such that creation and - /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. - /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, - /// i.e. use a small value to optimise for size and a large value to optimise for runtime. - /// If @a _enable is not set, will perform some simple peephole optimizations. - Assembly& optimise(bool _enable, langutil::EVMVersion _evmVersion, bool _isCreation, size_t _runs); - - /// Create a text representation of the assembly. - std::string assemblyString( - StringMap const& _sourceCodes = StringMap() - ) const; - void assemblyStream( - std::ostream& _out, - std::string const& _prefix = "", - StringMap const& _sourceCodes = StringMap() - ) const; - - /// Create a JSON representation of the assembly. - Json::Value assemblyJSON( - StringMap const& _sourceCodes = StringMap() - ) const; - -protected: - /// Does the same operations as @a optimise, but should only be applied to a sub and - /// returns the replaced tags. Also takes an argument containing the tags of this assembly - /// that are referenced in a super-assembly. - std::map optimiseInternal(OptimiserSettings const& _settings, std::set _tagsReferencedFromOutside); - - unsigned bytesRequired(unsigned subTagSize) const; - -private: - static Json::Value createJsonValue(std::string _name, int _begin, int _end, std::string _value = std::string(), std::string _jumpType = std::string()); - static std::string toStringInHex(u256 _value); - -protected: - /// 0 is reserved for exception - unsigned m_usedTags = 1; - std::map m_namedTags; - AssemblyItems m_items; - std::map m_data; - /// Data that is appended to the very end of the contract. - bytes m_auxiliaryData; - std::vector> m_subs; - std::map m_strings; - std::map m_libraries; ///< Identifiers of libraries to be linked. - - mutable LinkerObject m_assembledObject; - mutable std::vector m_tagPositionsInBytecode; - - int m_deposit = 0; - - langutil::SourceLocation m_currentSourceLocation; -public: - size_t m_currentModifierDepth = 0; -}; - -inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) -{ - _a.assemblyStream(_out); - return _out; -} - -} diff --git a/compiler/libevmasm/AssemblyItem.cpp b/compiler/libevmasm/AssemblyItem.cpp deleted file mode 100644 index b9b6c4ac..00000000 --- a/compiler/libevmasm/AssemblyItem.cpp +++ /dev/null @@ -1,283 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ - -#include - -#include -#include - -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide"); - -AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const -{ - assertThrow(data() < (u256(1) << 64), util::Exception, "Tag already has subassembly set."); - assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); - size_t tag = size_t(u256(data()) & 0xffffffffffffffffULL); - AssemblyItem r = *this; - r.m_type = PushTag; - r.setPushTagSubIdAndTag(_subId, tag); - return r; -} - -pair AssemblyItem::splitForeignPushTag() const -{ - assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); - u256 combined = u256(data()); - size_t subId = size_t((combined >> 64) - 1); - size_t tag = size_t(combined & 0xffffffffffffffffULL); - return make_pair(subId, tag); -} - -void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) -{ - assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); - u256 data = _tag; - if (_subId != size_t(-1)) - data |= (u256(_subId) + 1) << 64; - setData(data); -} - -unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const -{ - switch (m_type) - { - case Operation: - case Tag: // 1 byte for the JUMPDEST - return 1; - case PushString: - return 1 + 32; - case Push: - return 1 + max(1, util::bytesRequired(data())); - case PushSubSize: - case PushProgramSize: - return 1 + 4; // worst case: a 16MB program - case PushTag: - case PushData: - case PushSub: - return 1 + _addressLength; - case PushLibraryAddress: - case PushDeployTimeAddress: - return 1 + 20; - default: - break; - } - assertThrow(false, InvalidOpcode, ""); -} - -int AssemblyItem::arguments() const -{ - if (type() == Operation) - return instructionInfo(instruction()).args; - else - return 0; -} - -int AssemblyItem::returnValues() const -{ - switch (m_type) - { - case Operation: - return instructionInfo(instruction()).ret; - case Push: - case PushString: - case PushTag: - case PushData: - case PushSub: - case PushSubSize: - case PushProgramSize: - case PushLibraryAddress: - case PushDeployTimeAddress: - return 1; - case Tag: - return 0; - default: - break; - } - return 0; -} - -bool AssemblyItem::canBeFunctional() const -{ - if (m_jumpType != JumpType::Ordinary) - return false; - switch (m_type) - { - case Operation: - return !isDupInstruction(instruction()) && !isSwapInstruction(instruction()); - case Push: - case PushString: - case PushTag: - case PushData: - case PushSub: - case PushSubSize: - case PushProgramSize: - case PushLibraryAddress: - case PushDeployTimeAddress: - return true; - case Tag: - return false; - default: - break; - } - return false; -} - -string AssemblyItem::getJumpTypeAsString() const -{ - switch (m_jumpType) - { - case JumpType::IntoFunction: - return "[in]"; - case JumpType::OutOfFunction: - return "[out]"; - case JumpType::Ordinary: - default: - return ""; - } -} - -string AssemblyItem::toAssemblyText() const -{ - string text; - switch (type()) - { - case Operation: - { - assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction."); - string name = instructionInfo(instruction()).name; - transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); - text = name; - break; - } - case Push: - text = toHex(util::toCompactBigEndian(data(), 1), util::HexPrefix::Add); - break; - case PushString: - text = string("data_") + util::toHex(data()); - break; - case PushTag: - { - size_t sub{0}; - size_t tag{0}; - tie(sub, tag) = splitForeignPushTag(); - if (sub == size_t(-1)) - text = string("tag_") + to_string(tag); - else - text = string("tag_") + to_string(sub) + "_" + to_string(tag); - break; - } - case Tag: - assertThrow(data() < 0x10000, AssemblyException, "Declaration of sub-assembly tag."); - text = string("tag_") + to_string(size_t(data())) + ":"; - break; - case PushData: - text = string("data_") + util::toHex(data()); - break; - case PushSub: - text = string("dataOffset(sub_") + to_string(size_t(data())) + ")"; - break; - case PushSubSize: - text = string("dataSize(sub_") + to_string(size_t(data())) + ")"; - break; - case PushProgramSize: - text = string("bytecodeSize"); - break; - case PushLibraryAddress: - text = string("linkerSymbol(\"") + util::toHex(data()) + string("\")"); - break; - case PushDeployTimeAddress: - text = string("deployTimeAddress()"); - break; - case UndefinedItem: - assertThrow(false, AssemblyException, "Invalid assembly item."); - break; - default: - assertThrow(false, InvalidOpcode, ""); - } - if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) - { - text += "\t//"; - if (m_jumpType == JumpType::IntoFunction) - text += " in"; - else - text += " out"; - } - return text; -} - -ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item) -{ - switch (_item.type()) - { - case Operation: - _out << " " << instructionInfo(_item.instruction()).name; - if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) - _out << "\t" << _item.getJumpTypeAsString(); - break; - case Push: - _out << " PUSH " << hex << _item.data() << dec; - break; - case PushString: - _out << " PushString" << hex << (unsigned)_item.data() << dec; - break; - case PushTag: - { - size_t subId = _item.splitForeignPushTag().first; - if (subId == size_t(-1)) - _out << " PushTag " << _item.splitForeignPushTag().second; - else - _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; - break; - } - case Tag: - _out << " Tag " << _item.data(); - break; - case PushData: - _out << " PushData " << hex << (unsigned)_item.data() << dec; - break; - case PushSub: - _out << " PushSub " << hex << size_t(_item.data()) << dec; - break; - case PushSubSize: - _out << " PushSubSize " << hex << size_t(_item.data()) << dec; - break; - case PushProgramSize: - _out << " PushProgramSize"; - break; - case PushLibraryAddress: - { - string hash(util::h256((_item.data())).hex()); - _out << " PushLibraryAddress " << hash.substr(0, 8) + "..." + hash.substr(hash.length() - 8); - break; - } - case PushDeployTimeAddress: - _out << " PushDeployTimeAddress"; - break; - case UndefinedItem: - _out << " ???"; - break; - default: - assertThrow(false, InvalidOpcode, ""); - } - return _out; -} diff --git a/compiler/libevmasm/AssemblyItem.h b/compiler/libevmasm/AssemblyItem.h deleted file mode 100644 index 31f17510..00000000 --- a/compiler/libevmasm/AssemblyItem.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file AssemblyItem.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace solidity::evmasm -{ - -enum AssemblyItemType { - UndefinedItem, - Operation, - Push, - PushString, - PushTag, - PushSub, - PushSubSize, - PushProgramSize, - Tag, - PushData, - PushLibraryAddress, ///< Push a currently unknown address of another (library) contract. - PushDeployTimeAddress ///< Push an address to be filled at deploy time. Should not be touched by the optimizer. -}; - -class Assembly; - -class AssemblyItem -{ -public: - enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; - - AssemblyItem(u256 _push, langutil::SourceLocation _location = langutil::SourceLocation()): - AssemblyItem(Push, std::move(_push), std::move(_location)) { } - AssemblyItem(Instruction _i, langutil::SourceLocation _location = langutil::SourceLocation()): - m_type(Operation), - m_instruction(_i), - m_location(std::move(_location)) - {} - AssemblyItem(AssemblyItemType _type, u256 _data = 0, langutil::SourceLocation _location = langutil::SourceLocation()): - m_type(_type), - m_location(std::move(_location)) - { - if (m_type == Operation) - m_instruction = Instruction(uint8_t(_data)); - else - m_data = std::make_shared(std::move(_data)); - } - AssemblyItem(AssemblyItem const&) = default; - AssemblyItem(AssemblyItem&&) = default; - AssemblyItem& operator=(AssemblyItem const&) = default; - AssemblyItem& operator=(AssemblyItem&&) = default; - - AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(Tag, data()); } - AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); return AssemblyItem(PushTag, data()); } - /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. - /// @param _subId the identifier of the subassembly the tag is taken from. - AssemblyItem toSubAssemblyTag(size_t _subId) const; - /// @returns splits the data of the push tag into sub assembly id and actual tag id. - /// The sub assembly id of non-foreign push tags is -1. - std::pair splitForeignPushTag() const; - /// Sets sub-assembly part and tag for a push tag. - void setPushTagSubIdAndTag(size_t _subId, size_t _tag); - - AssemblyItemType type() const { return m_type; } - u256 const& data() const { assertThrow(m_type != Operation, util::Exception, ""); return *m_data; } - void setData(u256 const& _data) { assertThrow(m_type != Operation, util::Exception, ""); m_data = std::make_shared(_data); } - - /// @returns the instruction of this item (only valid if type() == Operation) - Instruction instruction() const { assertThrow(m_type == Operation, util::Exception, ""); return m_instruction; } - - /// @returns true if the type and data of the items are equal. - bool operator==(AssemblyItem const& _other) const - { - if (type() != _other.type()) - return false; - if (type() == Operation) - return instruction() == _other.instruction(); - else - return data() == _other.data(); - } - bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); } - /// Less-than operator compatible with operator==. - bool operator<(AssemblyItem const& _other) const - { - if (type() != _other.type()) - return type() < _other.type(); - else if (type() == Operation) - return instruction() < _other.instruction(); - else - return data() < _other.data(); - } - - /// Shortcut that avoids constructing an AssemblyItem just to perform the comparison. - bool operator==(Instruction _instr) const - { - return type() == Operation && instruction() == _instr; - } - bool operator!=(Instruction _instr) const { return !operator==(_instr); } - - /// @returns an upper bound for the number of bytes required by this item, assuming that - /// the value of a jump tag takes @a _addressLength bytes. - unsigned bytesRequired(unsigned _addressLength) const; - int arguments() const; - int returnValues() const; - int deposit() const { return returnValues() - arguments(); } - - /// @returns true if the assembly item can be used in a functional context. - bool canBeFunctional() const; - - void setLocation(langutil::SourceLocation const& _location) { m_location = _location; } - langutil::SourceLocation const& location() const { return m_location; } - - void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } - JumpType getJumpType() const { return m_jumpType; } - std::string getJumpTypeAsString() const; - - void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared(_value); } - u256 const* pushedValue() const { return m_pushedValue.get(); } - - std::string toAssemblyText() const; - - size_t m_modifierDepth = 0; - -private: - AssemblyItemType m_type; - Instruction m_instruction; ///< Only valid if m_type == Operation - std::shared_ptr m_data; ///< Only valid if m_type != Operation - langutil::SourceLocation m_location; - JumpType m_jumpType = JumpType::Ordinary; - /// Pushed value for operations with data to be determined during assembly stage, - /// e.g. PushSubSize, PushTag, PushSub, etc. - mutable std::shared_ptr m_pushedValue; -}; - -using AssemblyItems = std::vector; - -inline size_t bytesRequired(AssemblyItems const& _items, size_t _addressLength) -{ - size_t size = 0; - for (AssemblyItem const& item: _items) - size += item.bytesRequired(_addressLength); - return size; -} - -std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item); -inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _items) -{ - for (AssemblyItem const& item: _items) - _out << item; - return _out; -} - -} diff --git a/compiler/libevmasm/BlockDeduplicator.cpp b/compiler/libevmasm/BlockDeduplicator.cpp deleted file mode 100644 index 9a26db8c..00000000 --- a/compiler/libevmasm/BlockDeduplicator.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file BlockDeduplicator.cpp - * @author Christian - * @date 2015 - * Unifies basic blocks that share content. - */ - -#include - -#include -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - - -bool BlockDeduplicator::deduplicate() -{ - // Compares indices based on the suffix that starts there, ignoring tags and stopping at - // opcodes that stop the control flow. - - // Virtual tag that signifies "the current block" and which is used to optimise loops. - // We abort if this virtual tag actually exists. - AssemblyItem pushSelf{PushTag, u256(-4)}; - if ( - std::count(m_items.cbegin(), m_items.cend(), pushSelf.tag()) || - std::count(m_items.cbegin(), m_items.cend(), pushSelf.pushTag()) - ) - return false; - - function comparator = [&](size_t _i, size_t _j) - { - if (_i == _j) - return false; - - // To compare recursive loops, we have to already unify PushTag opcodes of the - // block's own tag. - AssemblyItem pushFirstTag{pushSelf}; - AssemblyItem pushSecondTag{pushSelf}; - - if (_i < m_items.size() && m_items.at(_i).type() == Tag) - pushFirstTag = m_items.at(_i).pushTag(); - if (_j < m_items.size() && m_items.at(_j).type() == Tag) - pushSecondTag = m_items.at(_j).pushTag(); - - BlockIterator first{m_items.begin() + _i, m_items.end(), &pushFirstTag, &pushSelf}; - BlockIterator second{m_items.begin() + _j, m_items.end(), &pushSecondTag, &pushSelf}; - BlockIterator end{m_items.end(), m_items.end()}; - - if (first != end && (*first).type() == Tag) - ++first; - if (second != end && (*second).type() == Tag) - ++second; - - return std::lexicographical_compare(first, end, second, end); - }; - - size_t iterations = 0; - for (; ; ++iterations) - { - //@todo this should probably be optimized. - set> blocksSeen(comparator); - for (size_t i = 0; i < m_items.size(); ++i) - { - if (m_items.at(i).type() != Tag) - continue; - auto it = blocksSeen.find(i); - if (it == blocksSeen.end()) - blocksSeen.insert(i); - else - m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data(); - } - - if (!applyTagReplacement(m_items, m_replacedTags)) - break; - } - return iterations > 0; -} - -bool BlockDeduplicator::applyTagReplacement( - AssemblyItems& _items, - map const& _replacements, - size_t _subId -) -{ - bool changed = false; - for (AssemblyItem& item: _items) - if (item.type() == PushTag) - { - size_t subId; - size_t tagId; - tie(subId, tagId) = item.splitForeignPushTag(); - if (subId != _subId) - continue; - auto it = _replacements.find(tagId); - if (it != _replacements.end()) - { - changed = true; - item.setPushTagSubIdAndTag(subId, size_t(it->second)); - } - } - return changed; -} - -BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() -{ - if (it == end) - return *this; - if (SemanticInformation::altersControlFlow(*it) && *it != AssemblyItem{Instruction::JUMPI}) - it = end; - else - { - ++it; - while (it != end && it->type() == Tag) - ++it; - } - return *this; -} - -AssemblyItem const& BlockDeduplicator::BlockIterator::operator*() const -{ - if (replaceItem && replaceWith && *it == *replaceItem) - return *replaceWith; - else - return *it; -} diff --git a/compiler/libevmasm/BlockDeduplicator.h b/compiler/libevmasm/BlockDeduplicator.h deleted file mode 100644 index ce12b4bf..00000000 --- a/compiler/libevmasm/BlockDeduplicator.h +++ /dev/null @@ -1,90 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file BlockDeduplicator.h - * @author Christian - * @date 2015 - * Unifies basic blocks that share content. - */ - -#pragma once - -#include - -#include -#include -#include -#include - -namespace solidity::evmasm -{ - -class AssemblyItem; -using AssemblyItems = std::vector; - -/** - * Optimizer class to be used to unify blocks that share content. - * Modifies the passed vector in place. - */ -class BlockDeduplicator -{ -public: - explicit BlockDeduplicator(AssemblyItems& _items): m_items(_items) {} - /// @returns true if something was changed - bool deduplicate(); - /// @returns the tags that were replaced. - std::map const& replacedTags() const { return m_replacedTags; } - - /// Replaces all PushTag operations insied @a _items that match a key in - /// @a _replacements by the respective value. If @a _subID is not -1, only - /// apply the replacement for foreign tags from this sub id. - /// @returns true iff a replacement was performed. - static bool applyTagReplacement( - AssemblyItems& _items, - std::map const& _replacements, - size_t _subID = size_t(-1) - ); - -private: - /// Iterator that skips tags and skips to the end if (all branches of) the control - /// flow does not continue to the next instruction. - /// If the arguments are supplied to the constructor, replaces items on the fly. - struct BlockIterator: std::iterator - { - public: - BlockIterator( - AssemblyItems::const_iterator _it, - AssemblyItems::const_iterator _end, - AssemblyItem const* _replaceItem = nullptr, - AssemblyItem const* _replaceWith = nullptr - ): - it(_it), end(_end), replaceItem(_replaceItem), replaceWith(_replaceWith) {} - BlockIterator& operator++(); - bool operator==(BlockIterator const& _other) const { return it == _other.it; } - bool operator!=(BlockIterator const& _other) const { return it != _other.it; } - AssemblyItem const& operator*() const; - AssemblyItems::const_iterator it; - AssemblyItems::const_iterator end; - AssemblyItem const* replaceItem; - AssemblyItem const* replaceWith; - }; - - std::map m_replacedTags; - AssemblyItems& m_items; -}; - -} diff --git a/compiler/libevmasm/CMakeLists.txt b/compiler/libevmasm/CMakeLists.txt deleted file mode 100644 index 9ef622f0..00000000 --- a/compiler/libevmasm/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -set(sources - Assembly.cpp - Assembly.h - AssemblyItem.cpp - AssemblyItem.h - BlockDeduplicator.cpp - BlockDeduplicator.h - CommonSubexpressionEliminator.cpp - CommonSubexpressionEliminator.h - ConstantOptimiser.cpp - ConstantOptimiser.h - ControlFlowGraph.cpp - ControlFlowGraph.h - Exceptions.h - ExpressionClasses.cpp - ExpressionClasses.h - GasMeter.cpp - GasMeter.h - Instruction.cpp - Instruction.h - JumpdestRemover.cpp - JumpdestRemover.h - KnownState.cpp - KnownState.h - LinkerObject.cpp - LinkerObject.h - PathGasMeter.cpp - PathGasMeter.h - PeepholeOptimiser.cpp - PeepholeOptimiser.h - SemanticInformation.cpp - SemanticInformation.h - SimplificationRule.h - SimplificationRules.cpp - SimplificationRules.h -) - -add_library(evmasm ${sources}) -target_link_libraries(evmasm PUBLIC solutil) diff --git a/compiler/libevmasm/CommonSubexpressionEliminator.cpp b/compiler/libevmasm/CommonSubexpressionEliminator.cpp deleted file mode 100644 index 747f78f2..00000000 --- a/compiler/libevmasm/CommonSubexpressionEliminator.cpp +++ /dev/null @@ -1,506 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file CommonSubexpressionEliminator.cpp - * @author Christian - * @date 2015 - * Optimizer step for common subexpression elimination and stack reorganisation. - */ - -#include -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::langutil; - -vector CommonSubexpressionEliminator::getOptimizedItems() -{ - optimizeBreakingItem(); - - KnownState nextInitialState = m_state; - if (m_breakingItem) - nextInitialState.feedItem(*m_breakingItem); - KnownState nextState = nextInitialState; - - ScopeGuard reset([&]() - { - m_breakingItem = nullptr; - m_storeOperations.clear(); - m_initialState = move(nextInitialState); - m_state = move(nextState); - }); - - map initialStackContents; - map targetStackContents; - int minHeight = m_state.stackHeight() + 1; - if (!m_state.stackElements().empty()) - minHeight = min(minHeight, m_state.stackElements().begin()->first); - for (int height = minHeight; height <= m_initialState.stackHeight(); ++height) - initialStackContents[height] = m_initialState.stackElement(height, SourceLocation()); - for (int height = minHeight; height <= m_state.stackHeight(); ++height) - targetStackContents[height] = m_state.stackElement(height, SourceLocation()); - - AssemblyItems items = CSECodeGenerator(m_state.expressionClasses(), m_storeOperations).generateCode( - m_initialState.sequenceNumber(), - m_initialState.stackHeight(), - initialStackContents, - targetStackContents - ); - if (m_breakingItem) - items.push_back(*m_breakingItem); - - return items; -} - -void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _copyItem) -{ - StoreOperation op = m_state.feedItem(_item, _copyItem); - if (op.isValid()) - m_storeOperations.push_back(op); -} - -void CommonSubexpressionEliminator::optimizeBreakingItem() -{ - if (!m_breakingItem) - return; - - ExpressionClasses& classes = m_state.expressionClasses(); - SourceLocation const& itemLocation = m_breakingItem->location(); - if (*m_breakingItem == AssemblyItem(Instruction::JUMPI)) - { - AssemblyItem::JumpType jumpType = m_breakingItem->getJumpType(); - - Id condition = m_state.stackElement(m_state.stackHeight() - 1, itemLocation); - if (classes.knownNonZero(condition)) - { - feedItem(AssemblyItem(Instruction::SWAP1, itemLocation), true); - feedItem(AssemblyItem(Instruction::POP, itemLocation), true); - - AssemblyItem item(Instruction::JUMP, itemLocation); - item.setJumpType(jumpType); - m_breakingItem = classes.storeItem(item); - } - else if (classes.knownZero(condition)) - { - AssemblyItem it(Instruction::POP, itemLocation); - feedItem(it, true); - feedItem(it, true); - m_breakingItem = nullptr; - } - } - else if (*m_breakingItem == AssemblyItem(Instruction::RETURN)) - { - Id size = m_state.stackElement(m_state.stackHeight() - 1, itemLocation); - if (classes.knownZero(size)) - { - feedItem(AssemblyItem(Instruction::POP, itemLocation), true); - feedItem(AssemblyItem(Instruction::POP, itemLocation), true); - AssemblyItem item(Instruction::STOP, itemLocation); - m_breakingItem = classes.storeItem(item); - } - } -} - -CSECodeGenerator::CSECodeGenerator( - ExpressionClasses& _expressionClasses, - vector const& _storeOperations -): - m_expressionClasses(_expressionClasses) -{ - for (auto const& store: _storeOperations) - m_storeOperations[make_pair(store.target, store.slot)].push_back(store); -} - -AssemblyItems CSECodeGenerator::generateCode( - unsigned _initialSequenceNumber, - int _initialStackHeight, - map const& _initialStack, - map const& _targetStackContents -) -{ - m_stackHeight = _initialStackHeight; - m_stack = _initialStack; - m_targetStack = _targetStackContents; - for (auto const& item: m_stack) - m_classPositions[item.second].insert(item.first); - - // generate the dependency graph starting from final storage and memory writes and target stack contents - for (auto const& p: m_storeOperations) - addDependencies(p.second.back().expression); - for (auto const& targetItem: m_targetStack) - { - m_finalClasses.insert(targetItem.second); - addDependencies(targetItem.second); - } - - // store all needed sequenced expressions - set> sequencedExpressions; - for (auto const& p: m_neededBy) - for (auto id: {p.first, p.second}) - if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) - { - // Invalid sequenced operation. - // @todo quick fix for now. Proper fix needs to choose representative with higher - // sequence number during dependency analysis. - assertThrow(seqNr >= _initialSequenceNumber, StackTooDeepException, ""); - sequencedExpressions.insert(make_pair(seqNr, id)); - } - - // Perform all operations on storage and memory in order, if they are needed. - for (auto const& seqAndId: sequencedExpressions) - if (!m_classPositions.count(seqAndId.second)) - generateClassElement(seqAndId.second, true); - - // generate the target stack elements - for (auto const& targetItem: m_targetStack) - { - if (m_stack.count(targetItem.first) && m_stack.at(targetItem.first) == targetItem.second) - continue; // already there - generateClassElement(targetItem.second); - assertThrow(!m_classPositions[targetItem.second].empty(), OptimizerException, ""); - if (m_classPositions[targetItem.second].count(targetItem.first)) - continue; - SourceLocation sourceLocation; - if (m_expressionClasses.representative(targetItem.second).item) - sourceLocation = m_expressionClasses.representative(targetItem.second).item->location(); - int position = classElementPosition(targetItem.second); - if (position < targetItem.first) - // it is already at its target, we need another copy - appendDup(position, sourceLocation); - else - appendOrRemoveSwap(position, sourceLocation); - appendOrRemoveSwap(targetItem.first, sourceLocation); - } - - // remove surplus elements - while (removeStackTopIfPossible()) - { - // no-op - } - - // check validity - int finalHeight = 0; - if (!m_targetStack.empty()) - // have target stack, so its height should be the final height - finalHeight = (--m_targetStack.end())->first; - else if (!_initialStack.empty()) - // no target stack, only erase the initial stack - finalHeight = _initialStack.begin()->first - 1; - else - // neither initial no target stack, no change in height - finalHeight = _initialStackHeight; - assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height."); - - return m_generatedItems; -} - -void CSECodeGenerator::addDependencies(Id _c) -{ - if (m_classPositions.count(_c)) - return; // it is already on the stack - if (m_neededBy.count(_c)) - return; // we already computed the dependencies for _c - ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); - assertThrow(expr.item, OptimizerException, ""); - // If this exception happens, we need to find a different way to generate the - // compound expression. - assertThrow(expr.item->type() != UndefinedItem, ItemNotAvailableException, "Undefined item requested but not available."); - for (Id argument: expr.arguments) - { - addDependencies(argument); - m_neededBy.insert(make_pair(argument, _c)); - } - if (expr.item && expr.item->type() == Operation && ( - expr.item->instruction() == Instruction::SLOAD || - expr.item->instruction() == Instruction::MLOAD || - expr.item->instruction() == Instruction::KECCAK256 - )) - { - // this loads an unknown value from storage or memory and thus, in addition to its - // arguments, depends on all store operations to addresses where we do not know that - // they are different that occur before this load - StoreOperation::Target target = expr.item->instruction() == Instruction::SLOAD ? - StoreOperation::Storage : StoreOperation::Memory; - Id slotToLoadFrom = expr.arguments.at(0); - for (auto const& p: m_storeOperations) - { - if (p.first.first != target) - continue; - Id slot = p.first.second; - StoreOperations const& storeOps = p.second; - if (storeOps.front().sequenceNumber > expr.sequenceNumber) - continue; - bool knownToBeIndependent = false; - switch (expr.item->instruction()) - { - case Instruction::SLOAD: - knownToBeIndependent = m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom); - break; - case Instruction::MLOAD: - knownToBeIndependent = m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom); - break; - case Instruction::KECCAK256: - { - Id length = expr.arguments.at(1); - AssemblyItem offsetInstr(Instruction::SUB, expr.item->location()); - Id offsetToStart = m_expressionClasses.find(offsetInstr, {slot, slotToLoadFrom}); - u256 const* o = m_expressionClasses.knownConstant(offsetToStart); - u256 const* l = m_expressionClasses.knownConstant(length); - if (l && *l == 0) - knownToBeIndependent = true; - else if (o) - { - // We could get problems here if both *o and *l are larger than 2**254 - // but it is probably ok for the optimizer to produce wrong code for such cases - // which cannot be executed anyway because of the non-payable price. - if (u2s(*o) <= -32) - knownToBeIndependent = true; - else if (l && u2s(*o) >= 0 && *o >= *l) - knownToBeIndependent = true; - } - break; - } - default: - break; - } - if (knownToBeIndependent) - continue; - - // note that store and load never have the same sequence number - Id latestStore = storeOps.front().expression; - for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) - if (it->sequenceNumber < expr.sequenceNumber) - latestStore = it->expression; - addDependencies(latestStore); - m_neededBy.insert(make_pair(latestStore, _c)); - } - } -} - -void CSECodeGenerator::generateClassElement(Id _c, bool _allowSequenced) -{ - for (auto it: m_classPositions) - for (auto p: it.second) - if (p > m_stackHeight) - { - assertThrow(false, OptimizerException, ""); - } - // do some cleanup - removeStackTopIfPossible(); - - if (m_classPositions.count(_c)) - { - assertThrow( - !m_classPositions[_c].empty(), - OptimizerException, - "Element already removed but still needed." - ); - return; - } - ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c); - assertThrow( - _allowSequenced || expr.sequenceNumber == 0, - OptimizerException, - "Sequence constrained operation requested out of sequence." - ); - assertThrow(expr.item, OptimizerException, "Non-generated expression without item."); - assertThrow( - expr.item->type() != UndefinedItem, - OptimizerException, - "Undefined item requested but not available." - ); - vector const& arguments = expr.arguments; - for (Id arg: boost::adaptors::reverse(arguments)) - generateClassElement(arg); - - SourceLocation const& itemLocation = expr.item->location(); - // The arguments are somewhere on the stack now, so it remains to move them at the correct place. - // This is quite difficult as sometimes, the values also have to removed in this process - // (if canBeRemoved() returns true) and the two arguments can be equal. For now, this is - // implemented for every single case for combinations of up to two arguments manually. - if (arguments.size() == 1) - { - if (canBeRemoved(arguments[0], _c)) - appendOrRemoveSwap(classElementPosition(arguments[0]), itemLocation); - else - appendDup(classElementPosition(arguments[0]), itemLocation); - } - else if (arguments.size() == 2) - { - if (canBeRemoved(arguments[1], _c)) - { - appendOrRemoveSwap(classElementPosition(arguments[1]), itemLocation); - if (arguments[0] == arguments[1]) - appendDup(m_stackHeight, itemLocation); - else if (canBeRemoved(arguments[0], _c)) - { - appendOrRemoveSwap(m_stackHeight - 1, itemLocation); - appendOrRemoveSwap(classElementPosition(arguments[0]), itemLocation); - } - else - appendDup(classElementPosition(arguments[0]), itemLocation); - } - else - { - if (arguments[0] == arguments[1]) - { - appendDup(classElementPosition(arguments[0]), itemLocation); - appendDup(m_stackHeight, itemLocation); - } - else if (canBeRemoved(arguments[0], _c)) - { - appendOrRemoveSwap(classElementPosition(arguments[0]), itemLocation); - appendDup(classElementPosition(arguments[1]), itemLocation); - appendOrRemoveSwap(m_stackHeight - 1, itemLocation); - } - else - { - appendDup(classElementPosition(arguments[1]), itemLocation); - appendDup(classElementPosition(arguments[0]), itemLocation); - } - } - } - else - assertThrow( - arguments.size() <= 2, - OptimizerException, - "Opcodes with more than two arguments not implemented yet." - ); - for (size_t i = 0; i < arguments.size(); ++i) - assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." ); - - while (SemanticInformation::isCommutativeOperation(*expr.item) && - !m_generatedItems.empty() && - m_generatedItems.back() == AssemblyItem(Instruction::SWAP1)) - // this will not append a swap but remove the one that is already there - appendOrRemoveSwap(m_stackHeight - 1, itemLocation); - for (size_t i = 0; i < arguments.size(); ++i) - { - m_classPositions[m_stack[m_stackHeight - i]].erase(m_stackHeight - i); - m_stack.erase(m_stackHeight - i); - } - appendItem(*expr.item); - if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1) - { - m_stack[m_stackHeight] = _c; - m_classPositions[_c].insert(m_stackHeight); - } - else - { - assertThrow( - instructionInfo(expr.item->instruction()).ret == 0, - OptimizerException, - "Invalid number of return values." - ); - m_classPositions[_c]; // ensure it is created to mark the expression as generated - } -} - -int CSECodeGenerator::classElementPosition(Id _id) const -{ - assertThrow( - m_classPositions.count(_id) && !m_classPositions.at(_id).empty(), - OptimizerException, - "Element requested but is not present." - ); - return *max_element(m_classPositions.at(_id).begin(), m_classPositions.at(_id).end()); -} - -bool CSECodeGenerator::canBeRemoved(Id _element, Id _result, int _fromPosition) -{ - // Default for _fromPosition is the canonical position of the element. - if (_fromPosition == c_invalidPosition) - _fromPosition = classElementPosition(_element); - - bool haveCopy = m_classPositions.at(_element).size() > 1; - if (m_finalClasses.count(_element)) - // It is part of the target stack. It can be removed if it is a copy that is not in the target position. - return haveCopy && (!m_targetStack.count(_fromPosition) || m_targetStack[_fromPosition] != _element); - else if (!haveCopy) - { - // Can be removed unless it is needed by a class that has not been computed yet. - // Note that m_classPositions also includes classes that were deleted in the meantime. - auto range = m_neededBy.equal_range(_element); - for (auto it = range.first; it != range.second; ++it) - if (it->second != _result && !m_classPositions.count(it->second)) - return false; - } - return true; -} - -bool CSECodeGenerator::removeStackTopIfPossible() -{ - if (m_stack.empty()) - return false; - assertThrow(m_stack.count(m_stackHeight) > 0, OptimizerException, ""); - Id top = m_stack[m_stackHeight]; - if (!canBeRemoved(top, Id(-1), m_stackHeight)) - return false; - m_classPositions[m_stack[m_stackHeight]].erase(m_stackHeight); - m_stack.erase(m_stackHeight); - appendItem(AssemblyItem(Instruction::POP)); - return true; -} - -void CSECodeGenerator::appendDup(int _fromPosition, SourceLocation const& _location) -{ - assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); - int instructionNum = 1 + m_stackHeight - _fromPosition; - assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables."); - assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); - appendItem(AssemblyItem(dupInstruction(instructionNum), _location)); - m_stack[m_stackHeight] = m_stack[_fromPosition]; - m_classPositions[m_stack[m_stackHeight]].insert(m_stackHeight); -} - -void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition, SourceLocation const& _location) -{ - assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); - if (_fromPosition == m_stackHeight) - return; - int instructionNum = m_stackHeight - _fromPosition; - assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables."); - assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); - appendItem(AssemblyItem(swapInstruction(instructionNum), _location)); - - if (m_stack[m_stackHeight] != m_stack[_fromPosition]) - { - m_classPositions[m_stack[m_stackHeight]].erase(m_stackHeight); - m_classPositions[m_stack[m_stackHeight]].insert(_fromPosition); - m_classPositions[m_stack[_fromPosition]].erase(_fromPosition); - m_classPositions[m_stack[_fromPosition]].insert(m_stackHeight); - swap(m_stack[m_stackHeight], m_stack[_fromPosition]); - } - if (m_generatedItems.size() >= 2 && - SemanticInformation::isSwapInstruction(m_generatedItems.back()) && - *(m_generatedItems.end() - 2) == m_generatedItems.back()) - { - m_generatedItems.pop_back(); - m_generatedItems.pop_back(); - } -} - -void CSECodeGenerator::appendItem(AssemblyItem const& _item) -{ - m_generatedItems.push_back(_item); - m_stackHeight += _item.deposit(); -} diff --git a/compiler/libevmasm/CommonSubexpressionEliminator.h b/compiler/libevmasm/CommonSubexpressionEliminator.h deleted file mode 100644 index 0e6f6ee3..00000000 --- a/compiler/libevmasm/CommonSubexpressionEliminator.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file CommonSubexpressionEliminator.h - * @author Christian - * @date 2015 - * Optimizer step for common subexpression elimination and stack reorganisation. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace langutil -{ -struct SourceLocation; -} - -namespace solidity::evmasm -{ - -class AssemblyItem; -using AssemblyItems = std::vector; - -/** - * Optimizer step that performs common subexpression elimination and stack reorganisation, - * i.e. it tries to infer equality among expressions and compute the values of two expressions - * known to be equal only once. - * - * The general workings are that for each assembly item that is fed into the eliminator, an - * equivalence class is derived from the operation and the equivalence class of its arguments. - * DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these - * classes are determined. - * - * When the list of optimized items is requested, they are generated in a bottom-up fashion, - * adding code for equivalence classes that were not yet computed. - */ -class CommonSubexpressionEliminator -{ -public: - using Id = ExpressionClasses::Id; - using StoreOperation = KnownState::StoreOperation; - - explicit CommonSubexpressionEliminator(KnownState const& _state): m_initialState(_state), m_state(_state) {} - - /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first - /// item that must be fed into a new instance of the eliminator. - /// @param _msizeImportant if false, do not consider modification of MSIZE a side-effect - template - _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end, bool _msizeImportant); - - /// @returns the resulting items after optimization. - AssemblyItems getOptimizedItems(); - -private: - /// Feeds the item into the system for analysis. - void feedItem(AssemblyItem const& _item, bool _copyItem = false); - - /// Tries to optimize the item that breaks the basic block at the end. - void optimizeBreakingItem(); - - KnownState m_initialState; - KnownState m_state; - /// Keeps information about which storage or memory slots were written to at which sequence - /// number with what instruction. - std::vector m_storeOperations; - - /// The item that breaks the basic block, can be nullptr. - /// It is usually appended to the block but can be optimized in some cases. - AssemblyItem const* m_breakingItem = nullptr; -}; - -/** - * Unit that generates code from current stack layout, target stack layout and information about - * the equivalence classes. - */ -class CSECodeGenerator -{ -public: - using StoreOperation = CommonSubexpressionEliminator::StoreOperation; - using StoreOperations = std::vector; - using Id = ExpressionClasses::Id; - - /// Initializes the code generator with the given classes and store operations. - /// The store operations have to be sorted by sequence number in ascending order. - CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); - - /// @returns the assembly items generated from the given requirements - /// @param _initialSequenceNumber starting sequence number, do not generate sequenced operations - /// before this number. - /// @param _initialStack current contents of the stack (up to stack height of zero) - /// @param _targetStackContents final contents of the stack, by stack height relative to initial - /// @note should only be called once on each object. - AssemblyItems generateCode( - unsigned _initialSequenceNumber, - int _initialStackHeight, - std::map const& _initialStack, - std::map const& _targetStackContents - ); - -private: - /// Recursively discovers all dependencies to @a m_requests. - void addDependencies(Id _c); - - /// Produce code that generates the given element if it is not yet present. - /// @param _allowSequenced indicates that sequence-constrained operations are allowed - void generateClassElement(Id _c, bool _allowSequenced = false); - /// @returns the position of the representative of the given id on the stack. - /// @note throws an exception if it is not on the stack. - int classElementPosition(Id _id) const; - - /// @returns true if the copy of @a _element can be removed from stack position _fromPosition - /// - in general or, if given, while computing @a _result. - bool canBeRemoved(Id _element, Id _result = Id(-1), int _fromPosition = c_invalidPosition); - - /// Appends code to remove the topmost stack element if it can be removed. - bool removeStackTopIfPossible(); - - /// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position. - void appendDup(int _fromPosition, langutil::SourceLocation const& _location); - /// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position. - /// @note this might also remove the last item if it exactly the same swap instruction. - void appendOrRemoveSwap(int _fromPosition, langutil::SourceLocation const& _location); - /// Appends the given assembly item. - void appendItem(AssemblyItem const& _item); - - static int const c_invalidPosition = -0x7fffffff; - - AssemblyItems m_generatedItems; - /// Current height of the stack relative to the start. - int m_stackHeight = 0; - /// If (b, a) is in m_requests then b is needed to compute a. - std::multimap m_neededBy; - /// Current content of the stack. - std::map m_stack; - /// Current positions of equivalence classes, equal to the empty set if already deleted. - std::map> m_classPositions; - - /// The actual equivalence class items and how to compute them. - ExpressionClasses& m_expressionClasses; - /// Keeps information about which storage or memory slots were written to by which operations. - /// The operations are sorted ascendingly by sequence number. - std::map, StoreOperations> m_storeOperations; - /// The set of equivalence classes that should be present on the stack at the end. - std::set m_finalClasses; - std::map m_targetStack; -}; - -template -_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( - _AssemblyItemIterator _iterator, - _AssemblyItemIterator _end, - bool _msizeImportant -) -{ - assertThrow(!m_breakingItem, OptimizerException, "Invalid use of CommonSubexpressionEliminator."); - for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator, _msizeImportant); ++_iterator) - feedItem(*_iterator); - if (_iterator != _end) - m_breakingItem = &(*_iterator++); - return _iterator; -} - -} diff --git a/compiler/libevmasm/ConstantOptimiser.cpp b/compiler/libevmasm/ConstantOptimiser.cpp deleted file mode 100644 index 8e6d991a..00000000 --- a/compiler/libevmasm/ConstantOptimiser.cpp +++ /dev/null @@ -1,330 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file ConstantOptimiser.cpp - * @author Christian - * @date 2015 - */ - -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -unsigned ConstantOptimisationMethod::optimiseConstants( - bool _isCreation, - size_t _runs, - langutil::EVMVersion _evmVersion, - Assembly& _assembly -) -{ - // TODO: design the optimiser in a way this is not needed - AssemblyItems& _items = _assembly.items(); - - unsigned optimisations = 0; - map pushes; - for (AssemblyItem const& item: _items) - if (item.type() == Push) - pushes[item]++; - map pendingReplacements; - for (auto it: pushes) - { - AssemblyItem const& item = it.first; - if (item.data() < 0x100) - continue; - Params params; - params.multiplicity = it.second; - params.isCreation = _isCreation; - params.runs = _runs; - params.evmVersion = _evmVersion; - LiteralMethod lit(params, item.data()); - bigint literalGas = lit.gasNeeded(); - CodeCopyMethod copy(params, item.data()); - bigint copyGas = copy.gasNeeded(); - ComputeMethod compute(params, item.data()); - bigint computeGas = compute.gasNeeded(); - AssemblyItems replacement; - if (copyGas < literalGas && copyGas < computeGas) - { - replacement = copy.execute(_assembly); - optimisations++; - } - else if (computeGas < literalGas && computeGas <= copyGas) - { - replacement = compute.execute(_assembly); - optimisations++; - } - if (!replacement.empty()) - pendingReplacements[item.data()] = replacement; - } - if (!pendingReplacements.empty()) - replaceConstants(_items, pendingReplacements); - return optimisations; -} - -bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items) -{ - bigint gas = 0; - for (AssemblyItem const& item: _items) - if (item.type() == Push) - gas += GasMeter::runGas(Instruction::PUSH1); - else if (item.type() == Operation) - { - if (item.instruction() == Instruction::EXP) - gas += GasCosts::expGas; - else - gas += GasMeter::runGas(item.instruction()); - } - return gas; -} - -bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const -{ - assertThrow(_data.size() > 0, OptimizerException, "Empty bytecode generated."); - return bigint(GasMeter::dataGas(_data, m_params.isCreation, m_params.evmVersion)); -} - -size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items) -{ - return evmasm::bytesRequired(_items, 3); // assume 3 byte addresses -} - -void ConstantOptimisationMethod::replaceConstants( - AssemblyItems& _items, - map const& _replacements -) -{ - AssemblyItems replaced; - for (AssemblyItem const& item: _items) - { - if (item.type() == Push) - { - auto it = _replacements.find(item.data()); - if (it != _replacements.end()) - { - replaced += it->second; - continue; - } - } - replaced.push_back(item); - } - _items = std::move(replaced); -} - -bigint LiteralMethod::gasNeeded() const -{ - return combineGas( - simpleRunGas({Instruction::PUSH1}), - // PUSHX plus data - (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas) + dataGas(util::toCompactBigEndian(m_value, 1)), - 0 - ); -} - -bigint CodeCopyMethod::gasNeeded() const -{ - return combineGas( - // Run gas: we ignore memory increase costs - simpleRunGas(copyRoutine()) + GasCosts::copyGas, - // Data gas for copy routines: Some bytes are zero, but we ignore them. - bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), - // Data gas for data itself - dataGas(util::toBigEndian(m_value)) - ); -} - -AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const -{ - bytes data = util::toBigEndian(m_value); - assertThrow(data.size() == 32, OptimizerException, "Invalid number encoding."); - AssemblyItems actualCopyRoutine = copyRoutine(); - actualCopyRoutine[4] = _assembly.newData(data); - return actualCopyRoutine; -} - -AssemblyItems const& CodeCopyMethod::copyRoutine() -{ - AssemblyItems static copyRoutine{ - // constant to be reused 3+ times - u256(0), - - // back up memory - // mload(0) - Instruction::DUP1, - Instruction::MLOAD, - - // codecopy(0, , 32) - u256(32), - AssemblyItem(PushData, u256(1) << 16), // replaced above in actualCopyRoutine[4] - Instruction::DUP4, - Instruction::CODECOPY, - - // mload(0) - Instruction::DUP2, - Instruction::MLOAD, - - // restore original memory - Instruction::SWAP2, - Instruction::MSTORE - }; - return copyRoutine; -} - -AssemblyItems ComputeMethod::findRepresentation(u256 const& _value) -{ - if (_value < 0x10000) - // Very small value, not worth computing - return AssemblyItems{_value}; - else if (util::bytesRequired(~_value) < util::bytesRequired(_value)) - // Negated is shorter to represent - return findRepresentation(~_value) + AssemblyItems{Instruction::NOT}; - else - { - // Decompose value into a * 2**k + b where abs(b) << 2**k - // Is not always better, try literal and decomposition method. - AssemblyItems routine{u256(_value)}; - bigint bestGas = gasNeeded(routine); - for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits) - { - unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff); - if (gapDetector != 0xff && gapDetector != 0x100) - continue; - - u256 powerOfTwo = u256(1) << bits; - u256 upperPart = _value >> bits; - bigint lowerPart = _value & (powerOfTwo - 1); - if ((powerOfTwo - lowerPart) < lowerPart) - { - lowerPart = lowerPart - powerOfTwo; // make it negative - upperPart++; - } - if (upperPart == 0) - continue; - if (abs(lowerPart) >= (powerOfTwo >> 8)) - continue; - - AssemblyItems newRoutine; - if (lowerPart != 0) - newRoutine += findRepresentation(u256(abs(lowerPart))); - if (m_params.evmVersion.hasBitwiseShifting()) - { - newRoutine += findRepresentation(upperPart); - newRoutine += AssemblyItems{u256(bits), Instruction::SHL}; - } - else - { - newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP}; - if (upperPart != 1) - newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL}; - } - if (lowerPart > 0) - newRoutine += AssemblyItems{Instruction::ADD}; - else if (lowerPart < 0) - newRoutine.push_back(Instruction::SUB); - - if (m_maxSteps > 0) - m_maxSteps--; - bigint newGas = gasNeeded(newRoutine); - if (newGas < bestGas) - { - bestGas = move(newGas); - routine = move(newRoutine); - } - } - return routine; - } -} - -bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const -{ - // This is a tiny EVM that can only evaluate some instructions. - vector stack; - for (AssemblyItem const& item: _routine) - { - switch (item.type()) - { - case Operation: - { - if (stack.size() < size_t(item.arguments())) - return false; - u256* sp = &stack.back(); - switch (item.instruction()) - { - case Instruction::MUL: - sp[-1] = sp[0] * sp[-1]; - break; - case Instruction::EXP: - if (sp[-1] > 0xff) - return false; - sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1])); - break; - case Instruction::ADD: - sp[-1] = sp[0] + sp[-1]; - break; - case Instruction::SUB: - sp[-1] = sp[0] - sp[-1]; - break; - case Instruction::NOT: - sp[0] = ~sp[0]; - break; - case Instruction::SHL: - assertThrow( - m_params.evmVersion.hasBitwiseShifting(), - OptimizerException, - "Shift generated for invalid EVM version." - ); - assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated."); - sp[-1] = u256(bigint(sp[-1]) << unsigned(sp[0])); - break; - case Instruction::SHR: - assertThrow( - m_params.evmVersion.hasBitwiseShifting(), - OptimizerException, - "Shift generated for invalid EVM version." - ); - assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated."); - sp[-1] = sp[-1] >> unsigned(sp[0]); - break; - default: - return false; - } - stack.resize(stack.size() + item.deposit()); - break; - } - case Push: - stack.push_back(item.data()); - break; - default: - return false; - } - } - return stack.size() == 1 && stack.front() == _value; -} - -bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const -{ - size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP); - return combineGas( - simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)), - // Data gas for routine: Some bytes are zero, but we ignore them. - bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas(m_params.evmVersion) : GasCosts::createDataGas), - 0 - ); -} diff --git a/compiler/libevmasm/ConstantOptimiser.h b/compiler/libevmasm/ConstantOptimiser.h deleted file mode 100644 index a9db47ab..00000000 --- a/compiler/libevmasm/ConstantOptimiser.h +++ /dev/null @@ -1,164 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file ConstantOptimiser.cpp - * @author Christian - * @date 2015 - */ - -#pragma once - -#include - -#include - -#include -#include -#include - -#include - -namespace solidity::evmasm -{ - -class AssemblyItem; -using AssemblyItems = std::vector; -class Assembly; - -/** - * Abstract base class for one way to change how constants are represented in the code. - */ -class ConstantOptimisationMethod -{ -public: - /// Tries to optimised how constants are represented in the source code and modifies - /// @a _assembly. - /// @returns zero if no optimisations could be performed. - static unsigned optimiseConstants( - bool _isCreation, - size_t _runs, - langutil::EVMVersion _evmVersion, - Assembly& _assembly - ); - -protected: - /// This is the public API for the optimiser methods, but it doesn't need to be exposed to the caller. - - struct Params - { - bool isCreation; ///< Whether this is called during contract creation or runtime. - size_t runs; ///< Estimated number of calls per opcode oven the lifetime of the contract. - size_t multiplicity; ///< Number of times the constant appears in the code. - langutil::EVMVersion evmVersion; ///< Version of the EVM - }; - - explicit ConstantOptimisationMethod(Params const& _params, u256 const& _value): - m_params(_params), m_value(_value) {} - virtual ~ConstantOptimisationMethod() = default; - virtual bigint gasNeeded() const = 0; - /// Executes the method, potentially appending to the assembly and returns a vector of - /// assembly items the constant should be relpaced with in one sweep. - /// If the vector is empty, the constants will not be deleted. - virtual AssemblyItems execute(Assembly& _assembly) const = 0; - -protected: - /// @returns the run gas for the given items ignoring special gas costs - static bigint simpleRunGas(AssemblyItems const& _items); - /// @returns the gas needed to store the given data literally - bigint dataGas(bytes const& _data) const; - static size_t bytesRequired(AssemblyItems const& _items); - /// @returns the combined estimated gas usage taking @a m_params into account. - bigint combineGas( - bigint const& _runGas, - bigint const& _repeatedDataGas, - bigint const& _uniqueDataGas - ) const - { - // _runGas is not multiplied by _multiplicity because the runs are "per opcode" - return m_params.runs * _runGas + m_params.multiplicity * _repeatedDataGas + _uniqueDataGas; - } - - /// Replaces all constants i by the code given in @a _replacement[i]. - static void replaceConstants(AssemblyItems& _items, std::map const& _replacements); - - Params m_params; - u256 const& m_value; -}; - -/** - * Optimisation method that pushes the constant to the stack literally. This is the default method, - * i.e. executing it does not alter the Assembly. - */ -class LiteralMethod: public ConstantOptimisationMethod -{ -public: - explicit LiteralMethod(Params const& _params, u256 const& _value): - ConstantOptimisationMethod(_params, _value) {} - bigint gasNeeded() const override; - AssemblyItems execute(Assembly&) const override { return AssemblyItems{}; } -}; - -/** - * Method that stores the data in the .data section of the code and copies it to the stack. - */ -class CodeCopyMethod: public ConstantOptimisationMethod -{ -public: - explicit CodeCopyMethod(Params const& _params, u256 const& _value): - ConstantOptimisationMethod(_params, _value) {} - bigint gasNeeded() const override; - AssemblyItems execute(Assembly& _assembly) const override; - -protected: - static AssemblyItems const& copyRoutine(); -}; - -/** - * Method that tries to compute the constant. - */ -class ComputeMethod: public ConstantOptimisationMethod -{ -public: - explicit ComputeMethod(Params const& _params, u256 const& _value): - ConstantOptimisationMethod(_params, _value) - { - m_routine = findRepresentation(m_value); - assertThrow( - checkRepresentation(m_value, m_routine), - OptimizerException, - "Invalid constant expression created." - ); - } - - bigint gasNeeded() const override { return gasNeeded(m_routine); } - AssemblyItems execute(Assembly&) const override - { - return m_routine; - } - -protected: - /// Tries to recursively find a way to compute @a _value. - AssemblyItems findRepresentation(u256 const& _value); - /// Recomputes the value from the calculated representation and checks for correctness. - bool checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const; - bigint gasNeeded(AssemblyItems const& _routine) const; - - /// Counter for the complexity of optimization, will stop when it reaches zero. - size_t m_maxSteps = 10000; - AssemblyItems m_routine; -}; - -} diff --git a/compiler/libevmasm/ControlFlowGraph.cpp b/compiler/libevmasm/ControlFlowGraph.cpp deleted file mode 100644 index 4c9d59c8..00000000 --- a/compiler/libevmasm/ControlFlowGraph.cpp +++ /dev/null @@ -1,373 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file ControlFlowGraph.cpp - * @author Christian - * @date 2015 - * Control flow analysis for the optimizer. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -BlockId::BlockId(u256 const& _id): - m_id(unsigned(_id)) -{ - assertThrow( _id < initial().m_id, OptimizerException, "Tag number too large."); -} - -BasicBlocks ControlFlowGraph::optimisedBlocks() -{ - if (m_items.empty()) - return BasicBlocks(); - - findLargestTag(); - splitBlocks(); - resolveNextLinks(); - removeUnusedBlocks(); - setPrevLinks(); - gatherKnowledge(); - - return rebuildCode(); -} - -void ControlFlowGraph::findLargestTag() -{ - m_lastUsedId = 0; - for (auto const& item: m_items) - if (item.type() == Tag || item.type() == PushTag) - { - // Assert that it can be converted. - BlockId(item.data()); - m_lastUsedId = max(unsigned(item.data()), m_lastUsedId); - } -} - -void ControlFlowGraph::splitBlocks() -{ - m_blocks.clear(); - BlockId id = BlockId::initial(); - m_blocks[id].begin = 0; - for (size_t index = 0; index < m_items.size(); ++index) - { - AssemblyItem const& item = m_items.at(index); - if (item.type() == Tag) - { - if (id) - m_blocks[id].end = index; - id = BlockId::invalid(); - } - if (!id) - { - id = item.type() == Tag ? BlockId(item.data()) : generateNewId(); - m_blocks[id].begin = index; - } - if (item.type() == PushTag) - m_blocks[id].pushedTags.emplace_back(item.data()); - if (SemanticInformation::altersControlFlow(item)) - { - m_blocks[id].end = index + 1; - if (item == Instruction::JUMP) - m_blocks[id].endType = BasicBlock::EndType::JUMP; - else if (item == Instruction::JUMPI) - m_blocks[id].endType = BasicBlock::EndType::JUMPI; - else - m_blocks[id].endType = BasicBlock::EndType::STOP; - id = BlockId::invalid(); - } - } - if (id) - { - m_blocks[id].end = m_items.size(); - if (m_blocks[id].endType == BasicBlock::EndType::HANDOVER) - m_blocks[id].endType = BasicBlock::EndType::STOP; - } -} - -void ControlFlowGraph::resolveNextLinks() -{ - map blockByBeginPos; - for (auto const& idAndBlock: m_blocks) - if (idAndBlock.second.begin != idAndBlock.second.end) - blockByBeginPos[idAndBlock.second.begin] = idAndBlock.first; - - for (auto& idAndBlock: m_blocks) - { - BasicBlock& block = idAndBlock.second; - switch (block.endType) - { - case BasicBlock::EndType::JUMPI: - case BasicBlock::EndType::HANDOVER: - assertThrow( - blockByBeginPos.count(block.end), - OptimizerException, - "Successor block not found." - ); - block.next = blockByBeginPos.at(block.end); - break; - default: - break; - } - } -} - -void ControlFlowGraph::removeUnusedBlocks() -{ - vector blocksToProcess{BlockId::initial()}; - set neededBlocks{BlockId::initial()}; - while (!blocksToProcess.empty()) - { - BasicBlock const& block = m_blocks.at(blocksToProcess.back()); - blocksToProcess.pop_back(); - for (BlockId tag: block.pushedTags) - if (!neededBlocks.count(tag) && m_blocks.count(tag)) - { - neededBlocks.insert(tag); - blocksToProcess.push_back(tag); - } - if (block.next && !neededBlocks.count(block.next)) - { - neededBlocks.insert(block.next); - blocksToProcess.push_back(block.next); - } - } - for (auto it = m_blocks.begin(); it != m_blocks.end();) - if (neededBlocks.count(it->first)) - ++it; - else - m_blocks.erase(it++); -} - -void ControlFlowGraph::setPrevLinks() -{ - for (auto& idAndBlock: m_blocks) - { - BasicBlock& block = idAndBlock.second; - switch (block.endType) - { - case BasicBlock::EndType::JUMPI: - case BasicBlock::EndType::HANDOVER: - assertThrow( - !m_blocks.at(block.next).prev, - OptimizerException, - "Successor already has predecessor." - ); - m_blocks[block.next].prev = idAndBlock.first; - break; - default: - break; - } - } - // If block ends with jump to not yet linked block, link them removing the jump - for (auto& idAndBlock: m_blocks) - { - BlockId blockId = idAndBlock.first; - BasicBlock& block = idAndBlock.second; - if (block.endType != BasicBlock::EndType::JUMP || block.end - block.begin < 2) - continue; - AssemblyItem const& push = m_items.at(block.end - 2); - if (push.type() != PushTag) - continue; - BlockId nextId(push.data()); - if (m_blocks.count(nextId) && m_blocks.at(nextId).prev) - continue; - bool hasLoop = false; - for (BlockId id = nextId; id && m_blocks.count(id) && !hasLoop; id = m_blocks.at(id).next) - hasLoop = (id == blockId); - if (hasLoop || !m_blocks.count(nextId)) - continue; - - m_blocks[nextId].prev = blockId; - block.next = nextId; - block.end -= 2; - assertThrow( - !block.pushedTags.empty() && block.pushedTags.back() == nextId, - OptimizerException, - "Last pushed tag not at end of pushed list." - ); - block.pushedTags.pop_back(); - block.endType = BasicBlock::EndType::HANDOVER; - } -} - -void ControlFlowGraph::gatherKnowledge() -{ - // @todo actually we know that memory is filled with zeros at the beginning, - // we could make use of that. - KnownStatePointer emptyState = make_shared(); - bool unknownJumpEncountered = false; - - struct WorkQueueItem { - BlockId blockId; - KnownStatePointer state; - set blocksSeen; - }; - - vector workQueue{WorkQueueItem{BlockId::initial(), emptyState->copy(), set()}}; - auto addWorkQueueItem = [&](WorkQueueItem const& _currentItem, BlockId _to, KnownStatePointer const& _state) - { - WorkQueueItem item; - item.blockId = _to; - item.state = _state->copy(); - item.blocksSeen = _currentItem.blocksSeen; - item.blocksSeen.insert(_currentItem.blockId); - workQueue.push_back(move(item)); - }; - - while (!workQueue.empty()) - { - WorkQueueItem item = move(workQueue.back()); - workQueue.pop_back(); - //@todo we might have to do something like incrementing the sequence number for each JUMPDEST - assertThrow(!!item.blockId, OptimizerException, ""); - if (!m_blocks.count(item.blockId)) - continue; // too bad, we do not know the tag, probably an invalid jump - BasicBlock& block = m_blocks.at(item.blockId); - KnownStatePointer state = item.state; - if (block.startState) - { - // We call reduceToCommonKnowledge even in the non-join setting to get the correct - // sequence number - if (!m_joinKnowledge) - state->reset(); - state->reduceToCommonKnowledge(*block.startState, !item.blocksSeen.count(item.blockId)); - if (*state == *block.startState) - continue; - } - - block.startState = state->copy(); - - // Feed all items except for the final jump yet because it will erase the target tag. - unsigned pc = block.begin; - while (pc < block.end && !SemanticInformation::altersControlFlow(m_items.at(pc))) - state->feedItem(m_items.at(pc++)); - - if ( - block.endType == BasicBlock::EndType::JUMP || - block.endType == BasicBlock::EndType::JUMPI - ) - { - assertThrow(block.begin <= pc && pc == block.end - 1, OptimizerException, ""); - //@todo in the case of JUMPI, add knowledge about the condition to the state - // (for both values of the condition) - set tags = state->tagsInExpression( - state->stackElement(state->stackHeight(), langutil::SourceLocation{}) - ); - state->feedItem(m_items.at(pc++)); - - if (tags.empty()) - { - if (!unknownJumpEncountered) - { - // We do not know the target of this jump, so we have to reset the states of all - // JUMPDESTs. - unknownJumpEncountered = true; - for (auto const& it: m_blocks) - if (it.second.begin < it.second.end && m_items[it.second.begin].type() == Tag) - workQueue.push_back(WorkQueueItem{it.first, emptyState->copy(), set()}); - } - } - else - for (auto tag: tags) - addWorkQueueItem(item, BlockId(tag), state); - } - else if (block.begin <= pc && pc < block.end) - state->feedItem(m_items.at(pc++)); - assertThrow(block.end <= block.begin || pc == block.end, OptimizerException, ""); - - block.endState = state; - - if ( - block.endType == BasicBlock::EndType::HANDOVER || - block.endType == BasicBlock::EndType::JUMPI - ) - addWorkQueueItem(item, block.next, state); - } - - // Remove all blocks we never visited here. This might happen because a tag is pushed but - // never used for a JUMP. - // Note that this invalidates some contents of pushedTags - for (auto it = m_blocks.begin(); it != m_blocks.end();) - if (!it->second.startState) - it = m_blocks.erase(it); - else - it++; -} - -BasicBlocks ControlFlowGraph::rebuildCode() -{ - map pushes; - for (auto& idAndBlock: m_blocks) - for (BlockId ref: idAndBlock.second.pushedTags) - if (m_blocks.count(ref)) - pushes[ref]++; - - set blocksToAdd; - for (auto it: m_blocks) - blocksToAdd.insert(it.first); - set blocksAdded; - BasicBlocks blocks; - - for ( - BlockId blockId = BlockId::initial(); - blockId; - blockId = blocksToAdd.empty() ? BlockId::invalid() : *blocksToAdd.begin() - ) - { - bool previousHandedOver = (blockId == BlockId::initial()); - while (m_blocks.at(blockId).prev) - blockId = m_blocks.at(blockId).prev; - for (; blockId; blockId = m_blocks.at(blockId).next) - { - BasicBlock& block = m_blocks.at(blockId); - blocksToAdd.erase(blockId); - blocksAdded.insert(blockId); - - if (block.begin == block.end) - continue; - // If block starts with unused tag, skip it. - if (previousHandedOver && !pushes[blockId] && m_items[block.begin].type() == Tag) - ++block.begin; - if (block.begin < block.end) - { - blocks.push_back(block); - blocks.back().startState->clearTagUnions(); - blocks.back().endState->clearTagUnions(); - } - previousHandedOver = (block.endType == BasicBlock::EndType::HANDOVER); - } - } - - return blocks; -} - -BlockId ControlFlowGraph::generateNewId() -{ - BlockId id = BlockId(++m_lastUsedId); - assertThrow(id < BlockId::initial(), OptimizerException, "Out of block IDs."); - return id; -} diff --git a/compiler/libevmasm/ControlFlowGraph.h b/compiler/libevmasm/ControlFlowGraph.h deleted file mode 100644 index a0c2b963..00000000 --- a/compiler/libevmasm/ControlFlowGraph.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file ControlFlowGraph.h - * @author Christian - * @date 2015 - * Control flow analysis for the optimizer. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace solidity::evmasm -{ - -class KnownState; -using KnownStatePointer = std::shared_ptr; - -/** - * Identifier for a block, coincides with the tag number of an AssemblyItem but adds a special - * ID for the initial block. - */ -class BlockId -{ -public: - BlockId() { *this = invalid(); } - explicit BlockId(unsigned _id): m_id(_id) {} - explicit BlockId(u256 const& _id); - static BlockId initial() { return BlockId(-2); } - static BlockId invalid() { return BlockId(-1); } - - bool operator==(BlockId const& _other) const { return m_id == _other.m_id; } - bool operator!=(BlockId const& _other) const { return m_id != _other.m_id; } - bool operator<(BlockId const& _other) const { return m_id < _other.m_id; } - explicit operator bool() const { return *this != invalid(); } - -private: - unsigned m_id; -}; - -/** - * Control flow block inside which instruction counter is always incremented by one - * (except for possibly the last instruction). - */ -struct BasicBlock -{ - /// Start index into assembly item list. - unsigned begin = 0; - /// End index (excluded) inte assembly item list. - unsigned end = 0; - /// Tags pushed inside this block, with multiplicity. - std::vector pushedTags; - /// ID of the block that always follows this one (either non-branching part of JUMPI or flow - /// into new block), or BlockId::invalid() otherwise - BlockId next = BlockId::invalid(); - /// ID of the block that has to precede this one (because control flows into it). - BlockId prev = BlockId::invalid(); - - enum class EndType { JUMP, JUMPI, STOP, HANDOVER }; - EndType endType = EndType::HANDOVER; - - /// Knowledge about the state when this block is entered. Intersection of all possible ways - /// to enter this block. - KnownStatePointer startState; - /// Knowledge about the state at the end of this block. - KnownStatePointer endState; -}; - -using BasicBlocks = std::vector; - -/** - * Control flow graph optimizer. - * ASSUMES THAT WE ONLY JUMP TO TAGS THAT WERE PREVIOUSLY PUSHED. THIS IS NOT TRUE ANYMORE - * NOW THAT FUNCTION TAGS CAN BE STORED IN STORAGE. - */ -class ControlFlowGraph -{ -public: - /// Initializes the control flow graph. - /// @a _items has to persist across the usage of this class. - /// @a _joinKnowledge if true, reduces state knowledge to common base at the join of two paths - explicit ControlFlowGraph(AssemblyItems const& _items, bool _joinKnowledge = true): - m_items(_items), - m_joinKnowledge(_joinKnowledge) - {} - /// @returns vector of basic blocks in the order they should be used in the final code. - /// Should be called only once. - BasicBlocks optimisedBlocks(); - -private: - void findLargestTag(); - void splitBlocks(); - void resolveNextLinks(); - void removeUnusedBlocks(); - void gatherKnowledge(); - void setPrevLinks(); - BasicBlocks rebuildCode(); - - BlockId generateNewId(); - - unsigned m_lastUsedId = 0; - AssemblyItems const& m_items; - bool m_joinKnowledge = true; - std::map m_blocks; -}; - - -} diff --git a/compiler/libevmasm/Exceptions.h b/compiler/libevmasm/Exceptions.h deleted file mode 100644 index e4fadb53..00000000 --- a/compiler/libevmasm/Exceptions.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file Exceptions.h - * @author Christian - * @date 2014 - */ - -#pragma once - -#include - -namespace solidity::evmasm -{ - -struct AssemblyException: virtual util::Exception {}; -struct OptimizerException: virtual AssemblyException {}; -struct StackTooDeepException: virtual OptimizerException {}; -struct ItemNotAvailableException: virtual OptimizerException {}; - -DEV_SIMPLE_EXCEPTION(InvalidDeposit); -DEV_SIMPLE_EXCEPTION(InvalidOpcode); - -} diff --git a/compiler/libevmasm/ExpressionClasses.cpp b/compiler/libevmasm/ExpressionClasses.cpp deleted file mode 100644 index 644f4a27..00000000 --- a/compiler/libevmasm/ExpressionClasses.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file ExpressionClasses.cpp - * @author Christian - * @date 2015 - * Container for equivalence classes of expressions for use in common subexpression elimination. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::langutil; - -bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression const& _other) const -{ - assertThrow(!!item && !!_other.item, OptimizerException, ""); - auto type = item->type(); - auto otherType = _other.item->type(); - if (type != otherType) - return type < otherType; - else if (type == Operation) - { - auto instr = item->instruction(); - auto otherInstr = _other.item->instruction(); - return std::tie(instr, arguments, sequenceNumber) < - std::tie(otherInstr, _other.arguments, _other.sequenceNumber); - } - else - return std::tie(item->data(), arguments, sequenceNumber) < - std::tie(_other.item->data(), _other.arguments, _other.sequenceNumber); -} - -ExpressionClasses::Id ExpressionClasses::find( - AssemblyItem const& _item, - Ids const& _arguments, - bool _copyItem, - unsigned _sequenceNumber -) -{ - Expression exp; - exp.id = Id(-1); - exp.item = &_item; - exp.arguments = _arguments; - exp.sequenceNumber = _sequenceNumber; - - if (SemanticInformation::isCommutativeOperation(_item)) - sort(exp.arguments.begin(), exp.arguments.end()); - - if (SemanticInformation::isDeterministic(_item)) - { - auto it = m_expressions.find(exp); - if (it != m_expressions.end()) - return it->id; - } - - if (_copyItem) - exp.item = storeItem(_item); - - ExpressionClasses::Id id = tryToSimplify(exp); - if (id < m_representatives.size()) - exp.id = id; - else - { - exp.id = m_representatives.size(); - m_representatives.push_back(exp); - } - m_expressions.insert(exp); - return exp.id; -} - -void ExpressionClasses::forceEqual( - ExpressionClasses::Id _id, - AssemblyItem const& _item, - ExpressionClasses::Ids const& _arguments, - bool _copyItem -) -{ - Expression exp; - exp.id = _id; - exp.item = &_item; - exp.arguments = _arguments; - - if (SemanticInformation::isCommutativeOperation(_item)) - sort(exp.arguments.begin(), exp.arguments.end()); - - if (_copyItem) - exp.item = storeItem(_item); - - m_expressions.insert(exp); -} - -ExpressionClasses::Id ExpressionClasses::newClass(SourceLocation const& _location) -{ - Expression exp; - exp.id = m_representatives.size(); - exp.item = storeItem(AssemblyItem(UndefinedItem, (u256(1) << 255) + exp.id, _location)); - m_representatives.push_back(exp); - m_expressions.insert(exp); - return exp.id; -} - -bool ExpressionClasses::knownToBeDifferent(ExpressionClasses::Id _a, ExpressionClasses::Id _b) -{ - // Try to simplify "_a - _b" and return true iff the value is a non-zero constant. - return knownNonZero(find(Instruction::SUB, {_a, _b})); -} - -bool ExpressionClasses::knownToBeDifferentBy32(ExpressionClasses::Id _a, ExpressionClasses::Id _b) -{ - // Try to simplify "_a - _b" and return true iff the value is at least 32 away from zero. - u256 const* v = knownConstant(find(Instruction::SUB, {_a, _b})); - // forbidden interval is ["-31", 31] - return v && *v + 31 > u256(62); -} - -bool ExpressionClasses::knownZero(Id _c) -{ - return Pattern(u256(0)).matches(representative(_c), *this); -} - -bool ExpressionClasses::knownNonZero(Id _c) -{ - return Pattern(u256(0)).matches(representative(find(Instruction::ISZERO, {_c})), *this); -} - -u256 const* ExpressionClasses::knownConstant(Id _c) -{ - map matchGroups; - Pattern constant(Push); - constant.setMatchGroup(1, matchGroups); - if (!constant.matches(representative(_c), *this)) - return nullptr; - return &constant.d(); -} - -AssemblyItem const* ExpressionClasses::storeItem(AssemblyItem const& _item) -{ - m_spareAssemblyItems.push_back(make_shared(_item)); - return m_spareAssemblyItems.back().get(); -} - -string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const -{ - Expression const& expr = representative(_id); - stringstream str; - str << dec << expr.id << ":"; - if (expr.item) - { - str << *expr.item << "("; - for (Id arg: expr.arguments) - str << fullDAGToString(arg) << ","; - str << ")"; - } - else - str << " UNIQUE"; - return str.str(); -} - -ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr) -{ - static Rules rules; - assertThrow(rules.isInitialized(), OptimizerException, "Rule list not properly initialized."); - - if ( - !_expr.item || - _expr.item->type() != Operation || - !SemanticInformation::isDeterministic(*_expr.item) - ) - return -1; - - if (auto match = rules.findFirstMatch(_expr, *this)) - { - // Debug info - if (false) - { - cout << "Simplifying " << *_expr.item << "("; - for (Id arg: _expr.arguments) - cout << fullDAGToString(arg) << ", "; - cout << ")" << endl; - cout << "with rule " << match->pattern.toString() << endl; - cout << "to " << match->action().toString() << endl; - } - - return rebuildExpression(ExpressionTemplate(match->action(), _expr.item->location())); - } - - return -1; -} - -ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate const& _template) -{ - if (_template.hasId) - return _template.id; - - Ids arguments; - for (ExpressionTemplate const& t: _template.arguments) - arguments.push_back(rebuildExpression(t)); - return find(_template.item, arguments); -} diff --git a/compiler/libevmasm/ExpressionClasses.h b/compiler/libevmasm/ExpressionClasses.h deleted file mode 100644 index f9eebabd..00000000 --- a/compiler/libevmasm/ExpressionClasses.h +++ /dev/null @@ -1,128 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file ExpressionClasses.h - * @author Christian - * @date 2015 - * Container for equivalence classes of expressions for use in common subexpression elimination. - */ - -#pragma once - -#include -#include - -#include -#include -#include -#include - -namespace solidity::langutil -{ -struct SourceLocation; -} - -namespace solidity::evmasm -{ - -class Pattern; -struct ExpressionTemplate; - -/** - * Collection of classes of equivalent expressions that can also determine the class of an expression. - * Identifiers are contiguously assigned to new classes starting from zero. - */ -class ExpressionClasses -{ -public: - using Id = unsigned; - using Ids = std::vector; - - struct Expression - { - Id id; - AssemblyItem const* item = nullptr; - Ids arguments; - /// Storage modification sequence, only used for storage and memory operations. - unsigned sequenceNumber = 0; - /// Behaves as if this was a tuple of (item->type(), item->data(), arguments, sequenceNumber). - bool operator<(Expression const& _other) const; - }; - - /// Retrieves the id of the expression equivalence class resulting from the given item applied to the - /// given classes, might also create a new one. - /// @param _copyItem if true, copies the assembly item to an internal storage instead of just - /// keeping a pointer. - /// The @a _sequenceNumber indicates the current storage or memory access sequence. - Id find( - AssemblyItem const& _item, - Ids const& _arguments = {}, - bool _copyItem = true, - unsigned _sequenceNumber = 0 - ); - /// @returns the canonical representative of an expression class. - Expression const& representative(Id _id) const { return m_representatives.at(_id); } - /// @returns the number of classes. - Id size() const { return m_representatives.size(); } - - /// Forces the given @a _item with @a _arguments to the class @a _id. This can be used to - /// add prior knowledge e.g. about CALLDATA, but has to be used with caution. Will not work as - /// expected if @a _item applied to @a _arguments already exists. - void forceEqual(Id _id, AssemblyItem const& _item, Ids const& _arguments, bool _copyItem = true); - - /// @returns the id of a new class which is different to all other classes. - Id newClass(langutil::SourceLocation const& _location); - - /// @returns true if the values of the given classes are known to be different (on every input). - /// @note that this function might still return false for some different inputs. - bool knownToBeDifferent(Id _a, Id _b); - /// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32. - bool knownToBeDifferentBy32(Id _a, Id _b); - /// @returns true if the value of the given class is known to be zero. - /// @note that this is not the negation of knownNonZero - bool knownZero(Id _c); - /// @returns true if the value of the given class is known to be nonzero. - /// @note that this is not the negation of knownZero - bool knownNonZero(Id _c); - /// @returns a pointer to the value if the given class is known to be a constant, - /// and a nullptr otherwise. - u256 const* knownConstant(Id _c); - - /// Stores a copy of the given AssemblyItem and returns a pointer to the copy that is valid for - /// the lifetime of the ExpressionClasses object. - AssemblyItem const* storeItem(AssemblyItem const& _item); - - std::string fullDAGToString(Id _id) const; - -private: - /// Tries to simplify the given expression. - /// @returns its class if it possible or Id(-1) otherwise. - Id tryToSimplify(Expression const& _expr); - - /// Rebuilds an expression from a (matched) pattern. - Id rebuildExpression(ExpressionTemplate const& _template); - - std::vector>> createRules() const; - - /// Expression equivalence class representatives - we only store one item of an equivalence. - std::vector m_representatives; - /// All expression ever encountered. - std::set m_expressions; - std::vector> m_spareAssemblyItems; -}; - -} diff --git a/compiler/libevmasm/GasMeter.cpp b/compiler/libevmasm/GasMeter.cpp deleted file mode 100644 index 6a07e990..00000000 --- a/compiler/libevmasm/GasMeter.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ - -#include - -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; - -GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption const& _other) -{ - if (_other.isInfinite && !isInfinite) - *this = infinite(); - if (isInfinite) - return *this; - bigint v = bigint(value) + _other.value; - if (v > numeric_limits::max()) - *this = infinite(); - else - value = u256(v); - return *this; -} - -GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _includeExternalCosts) -{ - GasConsumption gas; - switch (_item.type()) - { - case Push: - case PushTag: - case PushData: - case PushString: - case PushSub: - case PushSubSize: - case PushProgramSize: - case PushLibraryAddress: - case PushDeployTimeAddress: - gas = runGas(Instruction::PUSH1); - break; - case Tag: - gas = runGas(Instruction::JUMPDEST); - break; - case Operation: - { - ExpressionClasses& classes = m_state->expressionClasses(); - switch (_item.instruction()) - { - case Instruction::SSTORE: - { - ExpressionClasses::Id slot = m_state->relativeStackElement(0); - ExpressionClasses::Id value = m_state->relativeStackElement(-1); - if (classes.knownZero(value) || ( - m_state->storageContent().count(slot) && - classes.knownNonZero(m_state->storageContent().at(slot)) - )) - gas = GasCosts::sstoreResetGas; //@todo take refunds into account - else - gas = GasCosts::sstoreSetGas; - break; - } - case Instruction::SLOAD: - gas = GasCosts::sloadGas(m_evmVersion); - break; - case Instruction::RETURN: - case Instruction::REVERT: - gas = runGas(_item.instruction()); - gas += memoryGas(0, -1); - break; - case Instruction::MLOAD: - case Instruction::MSTORE: - gas = runGas(_item.instruction()); - gas += memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(0), - classes.find(AssemblyItem(32)) - })); - break; - case Instruction::MSTORE8: - gas = runGas(_item.instruction()); - gas += memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(0), - classes.find(AssemblyItem(1)) - })); - break; - case Instruction::KECCAK256: - gas = GasCosts::keccak256Gas; - gas += memoryGas(0, -1); - gas += wordGas(GasCosts::keccak256WordGas, m_state->relativeStackElement(-1)); - break; - case Instruction::CALLDATACOPY: - case Instruction::CODECOPY: - case Instruction::RETURNDATACOPY: - gas = runGas(_item.instruction()); - gas += memoryGas(0, -2); - gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-2)); - break; - case Instruction::EXTCODESIZE: - gas = GasCosts::extCodeGas(m_evmVersion); - break; - case Instruction::EXTCODEHASH: - gas = GasCosts::balanceGas(m_evmVersion); - break; - case Instruction::EXTCODECOPY: - gas = GasCosts::extCodeGas(m_evmVersion); - gas += memoryGas(-1, -3); - gas += wordGas(GasCosts::copyGas, m_state->relativeStackElement(-3)); - break; - case Instruction::LOG0: - case Instruction::LOG1: - case Instruction::LOG2: - case Instruction::LOG3: - case Instruction::LOG4: - { - gas = GasCosts::logGas + GasCosts::logTopicGas * getLogNumber(_item.instruction()); - gas += memoryGas(0, -1); - if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) - gas += GasCosts::logDataGas * (*value); - else - gas = GasConsumption::infinite(); - break; - } - case Instruction::CALL: - case Instruction::CALLCODE: - case Instruction::DELEGATECALL: - case Instruction::STATICCALL: - { - if (_includeExternalCosts) - // We assume that we do not know the target contract and thus, the consumption is infinite. - gas = GasConsumption::infinite(); - else - { - gas = GasCosts::callGas(m_evmVersion); - if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) - gas += (*value); - else - gas = GasConsumption::infinite(); - if (_item.instruction() == Instruction::CALL) - gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. - int valueSize = 1; - if (_item.instruction() == Instruction::DELEGATECALL || _item.instruction() == Instruction::STATICCALL) - valueSize = 0; - else if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) - gas += GasCosts::callValueTransferGas; - gas += memoryGas(-2 - valueSize, -3 - valueSize); - gas += memoryGas(-4 - valueSize, -5 - valueSize); - } - break; - } - case Instruction::SELFDESTRUCT: - gas = GasCosts::selfdestructGas(m_evmVersion); - gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. - break; - case Instruction::CREATE: - case Instruction::CREATE2: - if (_includeExternalCosts) - // We assume that we do not know the target contract and thus, the consumption is infinite. - gas = GasConsumption::infinite(); - else - { - gas = GasCosts::createGas; - gas += memoryGas(-1, -2); - } - break; - case Instruction::EXP: - gas = GasCosts::expGas; - if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) - { - if (*value) - { - // Note: msb() counts from 0 and throws on 0 as input. - unsigned const significantByteCount = (boost::multiprecision::msb(*value) + 1 + 7) / 8; - gas += GasCosts::expByteGas(m_evmVersion) * significantByteCount; - } - } - else - gas += GasCosts::expByteGas(m_evmVersion) * 32; - break; - case Instruction::BALANCE: - gas = GasCosts::balanceGas(m_evmVersion); - break; - case Instruction::CHAINID: - gas = runGas(Instruction::CHAINID); - break; - case Instruction::SELFBALANCE: - gas = runGas(Instruction::SELFBALANCE); - break; - default: - gas = runGas(_item.instruction()); - break; - } - break; - } - default: - gas = GasConsumption::infinite(); - break; - } - - m_state->feedItem(_item); - return gas; -} - -GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _value) -{ - u256 const* value = m_state->expressionClasses().knownConstant(_value); - if (!value) - return GasConsumption::infinite(); - return GasConsumption(_multiplier * ((*value + 31) / 32)); -} - -GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _position) -{ - u256 const* value = m_state->expressionClasses().knownConstant(_position); - if (!value) - return GasConsumption::infinite(); - if (*value < m_largestMemoryAccess) - return GasConsumption(0); - u256 previous = m_largestMemoryAccess; - m_largestMemoryAccess = *value; - auto memGas = [=](u256 const& pos) -> u256 - { - u256 size = (pos + 31) / 32; - return GasCosts::memoryGas * size + size * size / GasCosts::quadCoeffDiv; - }; - return memGas(*value) - memGas(previous); -} - -GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosSize) -{ - ExpressionClasses& classes = m_state->expressionClasses(); - if (classes.knownZero(m_state->relativeStackElement(_stackPosSize))) - return GasConsumption(0); - else - return memoryGas(classes.find(Instruction::ADD, { - m_state->relativeStackElement(_stackPosOffset), - m_state->relativeStackElement(_stackPosSize) - })); -} - -unsigned GasMeter::runGas(Instruction _instruction) -{ - if (_instruction == Instruction::JUMPDEST) - return 1; - - switch (instructionInfo(_instruction).gasPriceTier) - { - case Tier::Zero: return GasCosts::tier0Gas; - case Tier::Base: return GasCosts::tier1Gas; - case Tier::VeryLow: return GasCosts::tier2Gas; - case Tier::Low: return GasCosts::tier3Gas; - case Tier::Mid: return GasCosts::tier4Gas; - case Tier::High: return GasCosts::tier5Gas; - case Tier::Ext: return GasCosts::tier6Gas; - default: break; - } - assertThrow(false, OptimizerException, "Invalid gas tier for instruction " + instructionInfo(_instruction).name); - return 0; -} - -u256 GasMeter::dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion) -{ - bigint gas = 0; - if (_inCreation) - { - for (auto b: _data) - gas += (b != 0) ? GasCosts::txDataNonZeroGas(_evmVersion) : GasCosts::txDataZeroGas; - } - else - gas = bigint(GasCosts::createDataGas) * _data.size(); - assertThrow(gas < bigint(u256(-1)), OptimizerException, "Gas cost exceeds 256 bits."); - return u256(gas); -} diff --git a/compiler/libevmasm/GasMeter.h b/compiler/libevmasm/GasMeter.h deleted file mode 100644 index 64b772c6..00000000 --- a/compiler/libevmasm/GasMeter.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file GasMeter.cpp - * @author Christian - * @date 2015 - */ - -#pragma once - -#include -#include - -#include - -#include -#include - -namespace solidity::evmasm -{ - -class KnownState; - -namespace GasCosts -{ - static unsigned const stackLimit = 1024; - static unsigned const tier0Gas = 0; - static unsigned const tier1Gas = 2; - static unsigned const tier2Gas = 3; - static unsigned const tier3Gas = 5; - static unsigned const tier4Gas = 8; - static unsigned const tier5Gas = 10; - static unsigned const tier6Gas = 20; - static unsigned const tier7Gas = 0; - inline unsigned extCodeGas(langutil::EVMVersion _evmVersion) - { - return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 700 : 20; - } - inline unsigned balanceGas(langutil::EVMVersion _evmVersion) - { - if (_evmVersion >= langutil::EVMVersion::istanbul()) - return 700; - else if (_evmVersion >= langutil::EVMVersion::tangerineWhistle()) - return 400; - else - return 20; - } - static unsigned const expGas = 10; - inline unsigned expByteGas(langutil::EVMVersion _evmVersion) - { - return _evmVersion >= langutil::EVMVersion::spuriousDragon() ? 50 : 10; - } - static unsigned const keccak256Gas = 30; - static unsigned const keccak256WordGas = 6; - inline unsigned sloadGas(langutil::EVMVersion _evmVersion) - { - if (_evmVersion >= langutil::EVMVersion::istanbul()) - return 800; - else if (_evmVersion >= langutil::EVMVersion::tangerineWhistle()) - return 200; - else - return 50; - } - static unsigned const sstoreSetGas = 20000; - static unsigned const sstoreResetGas = 5000; - static unsigned const sstoreRefundGas = 15000; - static unsigned const jumpdestGas = 1; - static unsigned const logGas = 375; - static unsigned const logDataGas = 8; - static unsigned const logTopicGas = 375; - static unsigned const createGas = 32000; - inline unsigned callGas(langutil::EVMVersion _evmVersion) - { - return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 700 : 40; - } - static unsigned const callStipend = 2300; - static unsigned const callValueTransferGas = 9000; - static unsigned const callNewAccountGas = 25000; - inline unsigned selfdestructGas(langutil::EVMVersion _evmVersion) - { - return _evmVersion >= langutil::EVMVersion::tangerineWhistle() ? 5000 : 0; - } - static unsigned const selfdestructRefundGas = 24000; - static unsigned const memoryGas = 3; - static unsigned const quadCoeffDiv = 512; - static unsigned const createDataGas = 200; - static unsigned const txGas = 21000; - static unsigned const txCreateGas = 53000; - static unsigned const txDataZeroGas = 4; - inline unsigned txDataNonZeroGas(langutil::EVMVersion _evmVersion) - { - return _evmVersion >= langutil::EVMVersion::istanbul() ? 16 : 68; - } - static unsigned const copyGas = 3; -} - -/** - * Class that helps computing the maximum gas consumption for instructions. - * Has to be initialized with a certain known state that will be automatically updated for - * each call to estimateMax. These calls have to supply strictly subsequent AssemblyItems. - * A new gas meter has to be constructed (with a new state) for control flow changes. - */ -class GasMeter -{ -public: - struct GasConsumption - { - GasConsumption(unsigned _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {} - GasConsumption(u256 _value, bool _infinite = false): value(_value), isInfinite(_infinite) {} - static GasConsumption infinite() { return GasConsumption(0, true); } - - GasConsumption& operator+=(GasConsumption const& _other); - bool operator<(GasConsumption const& _other) const - { - return std::make_pair(isInfinite, value) < std::make_pair(_other.isInfinite, _other.value); - } - - u256 value; - bool isInfinite; - }; - - /// Constructs a new gas meter given the current state. - GasMeter(std::shared_ptr const& _state, langutil::EVMVersion _evmVersion, u256 const& _largestMemoryAccess = 0): - m_state(_state), m_evmVersion(_evmVersion), m_largestMemoryAccess(_largestMemoryAccess) {} - - /// @returns an upper bound on the gas consumed by the given instruction and updates - /// the state. - /// @param _inculdeExternalCosts if true, include costs caused by other contracts in calls. - GasConsumption estimateMax(AssemblyItem const& _item, bool _includeExternalCosts = true); - - u256 const& largestMemoryAccess() const { return m_largestMemoryAccess; } - - /// @returns gas costs for simple instructions with constant gas costs (that do not - /// change with EVM versions) - static unsigned runGas(Instruction _instruction); - - /// @returns the gas cost of the supplied data, depending whether it is in creation code, or not. - /// In case of @a _inCreation, the data is only sent as a transaction and is not stored, whereas - /// otherwise code will be stored and have to pay "createDataGas" cost. - static u256 dataGas(bytes const& _data, bool _inCreation, langutil::EVMVersion _evmVersion); - -private: - /// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise. - GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value); - /// @returns the gas needed to access the given memory position. - /// @todo this assumes that memory was never accessed before and thus over-estimates gas usage. - GasConsumption memoryGas(ExpressionClasses::Id _position); - /// @returns the memory gas for accessing the memory at a specific offset for a number of bytes - /// given as values on the stack at the given relative positions. - GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize); - - std::shared_ptr m_state; - langutil::EVMVersion m_evmVersion; - /// Largest point where memory was accessed since the creation of this object. - u256 m_largestMemoryAccess; -}; - -inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption) -{ - if (_consumption.isInfinite) - return _str << "[???]"; - else - return _str << std::dec << _consumption.value; -} - - -} diff --git a/compiler/libevmasm/Instruction.cpp b/compiler/libevmasm/Instruction.cpp deleted file mode 100644 index 34fef164..00000000 --- a/compiler/libevmasm/Instruction.cpp +++ /dev/null @@ -1,387 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file Instruction.cpp - * @author Gav Wood - * @date 2014 - */ - -#include - -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; - -std::map const solidity::evmasm::c_instructions = -{ - { "STOP", Instruction::STOP }, - { "ADD", Instruction::ADD }, - { "SUB", Instruction::SUB }, - { "MUL", Instruction::MUL }, - { "DIV", Instruction::DIV }, - { "SDIV", Instruction::SDIV }, - { "MOD", Instruction::MOD }, - { "SMOD", Instruction::SMOD }, - { "EXP", Instruction::EXP }, - { "NOT", Instruction::NOT }, - { "LT", Instruction::LT }, - { "GT", Instruction::GT }, - { "SLT", Instruction::SLT }, - { "SGT", Instruction::SGT }, - { "EQ", Instruction::EQ }, - { "ISZERO", Instruction::ISZERO }, - { "AND", Instruction::AND }, - { "OR", Instruction::OR }, - { "XOR", Instruction::XOR }, - { "BYTE", Instruction::BYTE }, - { "SHL", Instruction::SHL }, - { "SHR", Instruction::SHR }, - { "SAR", Instruction::SAR }, - { "ADDMOD", Instruction::ADDMOD }, - { "MULMOD", Instruction::MULMOD }, - { "SIGNEXTEND", Instruction::SIGNEXTEND }, - { "KECCAK256", Instruction::KECCAK256 }, - { "ADDRESS", Instruction::ADDRESS }, - { "BALANCE", Instruction::BALANCE }, - { "ORIGIN", Instruction::ORIGIN }, - { "CALLER", Instruction::CALLER }, - { "CALLVALUE", Instruction::CALLVALUE }, - { "CALLDATALOAD", Instruction::CALLDATALOAD }, - { "CALLDATASIZE", Instruction::CALLDATASIZE }, - { "CALLDATACOPY", Instruction::CALLDATACOPY }, - { "CODESIZE", Instruction::CODESIZE }, - { "CODECOPY", Instruction::CODECOPY }, - { "GASPRICE", Instruction::GASPRICE }, - { "EXTCODESIZE", Instruction::EXTCODESIZE }, - { "EXTCODECOPY", Instruction::EXTCODECOPY }, - { "RETURNDATASIZE", Instruction::RETURNDATASIZE }, - { "RETURNDATACOPY", Instruction::RETURNDATACOPY }, - { "EXTCODEHASH", Instruction::EXTCODEHASH }, - { "BLOCKHASH", Instruction::BLOCKHASH }, - { "COINBASE", Instruction::COINBASE }, - { "TIMESTAMP", Instruction::TIMESTAMP }, - { "NUMBER", Instruction::NUMBER }, - { "DIFFICULTY", Instruction::DIFFICULTY }, - { "GASLIMIT", Instruction::GASLIMIT }, - { "CHAINID", Instruction::CHAINID }, - { "SELFBALANCE", Instruction::SELFBALANCE }, - { "POP", Instruction::POP }, - { "MLOAD", Instruction::MLOAD }, - { "MSTORE", Instruction::MSTORE }, - { "MSTORE8", Instruction::MSTORE8 }, - { "SLOAD", Instruction::SLOAD }, - { "SSTORE", Instruction::SSTORE }, - { "JUMP", Instruction::JUMP }, - { "JUMPI", Instruction::JUMPI }, - { "PC", Instruction::PC }, - { "MSIZE", Instruction::MSIZE }, - { "GAS", Instruction::GAS }, - { "JUMPDEST", Instruction::JUMPDEST }, - { "PUSH1", Instruction::PUSH1 }, - { "PUSH2", Instruction::PUSH2 }, - { "PUSH3", Instruction::PUSH3 }, - { "PUSH4", Instruction::PUSH4 }, - { "PUSH5", Instruction::PUSH5 }, - { "PUSH6", Instruction::PUSH6 }, - { "PUSH7", Instruction::PUSH7 }, - { "PUSH8", Instruction::PUSH8 }, - { "PUSH9", Instruction::PUSH9 }, - { "PUSH10", Instruction::PUSH10 }, - { "PUSH11", Instruction::PUSH11 }, - { "PUSH12", Instruction::PUSH12 }, - { "PUSH13", Instruction::PUSH13 }, - { "PUSH14", Instruction::PUSH14 }, - { "PUSH15", Instruction::PUSH15 }, - { "PUSH16", Instruction::PUSH16 }, - { "PUSH17", Instruction::PUSH17 }, - { "PUSH18", Instruction::PUSH18 }, - { "PUSH19", Instruction::PUSH19 }, - { "PUSH20", Instruction::PUSH20 }, - { "PUSH21", Instruction::PUSH21 }, - { "PUSH22", Instruction::PUSH22 }, - { "PUSH23", Instruction::PUSH23 }, - { "PUSH24", Instruction::PUSH24 }, - { "PUSH25", Instruction::PUSH25 }, - { "PUSH26", Instruction::PUSH26 }, - { "PUSH27", Instruction::PUSH27 }, - { "PUSH28", Instruction::PUSH28 }, - { "PUSH29", Instruction::PUSH29 }, - { "PUSH30", Instruction::PUSH30 }, - { "PUSH31", Instruction::PUSH31 }, - { "PUSH32", Instruction::PUSH32 }, - { "DUP1", Instruction::DUP1 }, - { "DUP2", Instruction::DUP2 }, - { "DUP3", Instruction::DUP3 }, - { "DUP4", Instruction::DUP4 }, - { "DUP5", Instruction::DUP5 }, - { "DUP6", Instruction::DUP6 }, - { "DUP7", Instruction::DUP7 }, - { "DUP8", Instruction::DUP8 }, - { "DUP9", Instruction::DUP9 }, - { "DUP10", Instruction::DUP10 }, - { "DUP11", Instruction::DUP11 }, - { "DUP12", Instruction::DUP12 }, - { "DUP13", Instruction::DUP13 }, - { "DUP14", Instruction::DUP14 }, - { "DUP15", Instruction::DUP15 }, - { "DUP16", Instruction::DUP16 }, - { "SWAP1", Instruction::SWAP1 }, - { "SWAP2", Instruction::SWAP2 }, - { "SWAP3", Instruction::SWAP3 }, - { "SWAP4", Instruction::SWAP4 }, - { "SWAP5", Instruction::SWAP5 }, - { "SWAP6", Instruction::SWAP6 }, - { "SWAP7", Instruction::SWAP7 }, - { "SWAP8", Instruction::SWAP8 }, - { "SWAP9", Instruction::SWAP9 }, - { "SWAP10", Instruction::SWAP10 }, - { "SWAP11", Instruction::SWAP11 }, - { "SWAP12", Instruction::SWAP12 }, - { "SWAP13", Instruction::SWAP13 }, - { "SWAP14", Instruction::SWAP14 }, - { "SWAP15", Instruction::SWAP15 }, - { "SWAP16", Instruction::SWAP16 }, - { "LOG0", Instruction::LOG0 }, - { "LOG1", Instruction::LOG1 }, - { "LOG2", Instruction::LOG2 }, - { "LOG3", Instruction::LOG3 }, - { "LOG4", Instruction::LOG4 }, - { "CREATE", Instruction::CREATE }, - { "CALL", Instruction::CALL }, - { "CALLCODE", Instruction::CALLCODE }, - { "STATICCALL", Instruction::STATICCALL }, - { "RETURN", Instruction::RETURN }, - { "DELEGATECALL", Instruction::DELEGATECALL }, - { "CREATE2", Instruction::CREATE2 }, - { "REVERT", Instruction::REVERT }, - { "INVALID", Instruction::INVALID }, - { "SELFDESTRUCT", Instruction::SELFDESTRUCT } -}; - -static std::map const c_instructionInfo = -{ // Add, Args, Ret, SideEffects, GasPriceTier - { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } }, - { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::Low } }, - { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::Low } }, - { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::Low } }, - { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::Low } }, - { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::Low } }, - { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::Special } }, - { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLow } }, - { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLow } }, - { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SHL, { "SHL", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SHR, { "SHR", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::SAR, { "SAR", 0, 2, 1, false, Tier::VeryLow } }, - { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } }, - { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } }, - { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } }, - { Instruction::KECCAK256, { "KECCAK256", 0, 2, 1, true, Tier::Special } }, - { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } }, - { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Balance } }, - { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } }, - { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } }, - { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } }, - { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLow } }, - { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::Base } }, - { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLow } }, - { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } }, - { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } }, - { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } }, - { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::ExtCode } }, - { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::ExtCode } }, - { Instruction::RETURNDATASIZE, {"RETURNDATASIZE", 0, 0, 1, false, Tier::Base } }, - { Instruction::RETURNDATACOPY, {"RETURNDATACOPY", 0, 3, 0, true, Tier::VeryLow } }, - { Instruction::EXTCODEHASH, { "EXTCODEHASH", 0, 1, 1, false, Tier::Balance } }, - { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } }, - { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } }, - { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } }, - { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } }, - { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } }, - { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } }, - { Instruction::CHAINID, { "CHAINID", 0, 0, 1, false, Tier::Base } }, - { Instruction::SELFBALANCE, { "SELFBALANCE", 0, 0, 1, false, Tier::Low } }, - { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } }, - { Instruction::MLOAD, { "MLOAD", 0, 1, 1, true, Tier::VeryLow } }, - { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } }, - { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLow } }, - { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::Special } }, - { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::Special } }, - { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::Mid } }, - { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::High } }, - { Instruction::PC, { "PC", 0, 0, 1, false, Tier::Base } }, - { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::Base } }, - { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::Base } }, - { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::Special } }, - { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLow } }, - { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLow } }, - { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLow } }, - { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLow } }, - { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLow } }, - { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLow } }, - { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLow } }, - { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLow } }, - { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLow } }, - { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLow } }, - { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLow } }, - { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLow } }, - { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLow } }, - { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLow } }, - { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLow } }, - { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLow } }, - { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLow } }, - { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLow } }, - { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLow } }, - { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLow } }, - { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLow } }, - { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLow } }, - { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLow } }, - { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLow } }, - { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLow } }, - { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLow } }, - { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLow } }, - { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLow } }, - { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLow } }, - { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLow } }, - { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLow } }, - { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLow } }, - { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLow } }, - { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLow } }, - { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::Special } }, - { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::Special } }, - { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } }, - { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } }, - { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } }, - { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } }, - { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } }, - { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } }, - { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } }, - { Instruction::DELEGATECALL, { "DELEGATECALL", 0, 6, 1, true, Tier::Special } }, - { Instruction::STATICCALL, { "STATICCALL", 0, 6, 1, true, Tier::Special } }, - { Instruction::CREATE2, { "CREATE2", 0, 4, 1, true, Tier::Special } }, - { Instruction::REVERT, { "REVERT", 0, 2, 0, true, Tier::Zero } }, - { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } }, - { Instruction::SELFDESTRUCT, { "SELFDESTRUCT", 0, 1, 0, true, Tier::Special } } -}; - -void solidity::evmasm::eachInstruction( - bytes const& _mem, - function const& _onInstruction -) -{ - for (auto it = _mem.begin(); it < _mem.end(); ++it) - { - Instruction instr = Instruction(*it); - size_t additional = 0; - if (isValidInstruction(instr)) - additional = instructionInfo(instr).additional; - - u256 data; - - // fill the data with the additional data bytes from the instruction stream - while (additional > 0 && std::next(it) < _mem.end()) - { - data <<= 8; - data |= *++it; - --additional; - } - - // pad the remaining number of additional octets with zeros - data <<= 8 * additional; - - _onInstruction(instr, data); - } -} - -string solidity::evmasm::disassemble(bytes const& _mem) -{ - stringstream ret; - eachInstruction(_mem, [&](Instruction _instr, u256 const& _data) { - if (!isValidInstruction(_instr)) - ret << "0x" << std::uppercase << std::hex << int(_instr) << " "; - else - { - InstructionInfo info = instructionInfo(_instr); - ret << info.name << " "; - if (info.additional) - ret << "0x" << std::uppercase << std::hex << _data << " "; - } - }); - return ret.str(); -} - -InstructionInfo solidity::evmasm::instructionInfo(Instruction _inst) -{ - try - { - return c_instructionInfo.at(_inst); - } - catch (...) - { - return InstructionInfo({"", 0, 0, 0, false, Tier::Invalid}); - } -} - -bool solidity::evmasm::isValidInstruction(Instruction _inst) -{ - return !!c_instructionInfo.count(_inst); -} diff --git a/compiler/libevmasm/Instruction.h b/compiler/libevmasm/Instruction.h deleted file mode 100644 index 511e3cf1..00000000 --- a/compiler/libevmasm/Instruction.h +++ /dev/null @@ -1,318 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file Instruction.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include - -namespace solidity::evmasm -{ - -/// Virtual machine bytecode instruction. -enum class Instruction: uint8_t -{ - STOP = 0x00, ///< halts execution - ADD, ///< addition operation - MUL, ///< multiplication operation - SUB, ///< subtraction operation - DIV, ///< integer division operation - SDIV, ///< signed integer division operation - MOD, ///< modulo remainder operation - SMOD, ///< signed modulo remainder operation - ADDMOD, ///< unsigned modular addition - MULMOD, ///< unsigned modular multiplication - EXP, ///< exponential operation - SIGNEXTEND, ///< extend length of signed integer - - LT = 0x10, ///< less-than comparison - GT, ///< greater-than comparison - SLT, ///< signed less-than comparison - SGT, ///< signed greater-than comparison - EQ, ///< equality comparison - ISZERO, ///< simple not operator - AND, ///< bitwise AND operation - OR, ///< bitwise OR operation - XOR, ///< bitwise XOR operation - NOT, ///< bitwise NOT operation - BYTE, ///< retrieve single byte from word - SHL, ///< bitwise SHL operation - SHR, ///< bitwise SHR operation - SAR, ///< bitwise SAR operation - - KECCAK256 = 0x20, ///< compute KECCAK-256 hash - - ADDRESS = 0x30, ///< get address of currently executing account - BALANCE, ///< get balance of the given account - ORIGIN, ///< get execution origination address - CALLER, ///< get caller address - CALLVALUE, ///< get deposited value by the instruction/transaction responsible for this execution - CALLDATALOAD, ///< get input data of current environment - CALLDATASIZE, ///< get size of input data in current environment - CALLDATACOPY, ///< copy input data in current environment to memory - CODESIZE, ///< get size of code running in current environment - CODECOPY, ///< copy code running in current environment to memory - GASPRICE, ///< get price of gas in current environment - EXTCODESIZE, ///< get external code size (from another contract) - EXTCODECOPY, ///< copy external code (from another contract) - RETURNDATASIZE = 0x3d, ///< get size of return data buffer - RETURNDATACOPY = 0x3e, ///< copy return data in current environment to memory - EXTCODEHASH = 0x3f, ///< get external code hash (from another contract) - - BLOCKHASH = 0x40, ///< get hash of most recent complete block - COINBASE, ///< get the block's coinbase address - TIMESTAMP, ///< get the block's timestamp - NUMBER, ///< get the block's number - DIFFICULTY, ///< get the block's difficulty - GASLIMIT, ///< get the block's gas limit - CHAINID, ///< get the config's chainid param - SELFBALANCE, ///< get balance of the current account - - POP = 0x50, ///< remove item from stack - MLOAD, ///< load word from memory - MSTORE, ///< save word to memory - MSTORE8, ///< save byte to memory - SLOAD, ///< load word from storage - SSTORE, ///< save word to storage - JUMP, ///< alter the program counter - JUMPI, ///< conditionally alter the program counter - PC, ///< get the program counter - MSIZE, ///< get the size of active memory - GAS, ///< get the amount of available gas - JUMPDEST, ///< set a potential jump destination - - PUSH1 = 0x60, ///< place 1 byte item on stack - PUSH2, ///< place 2 byte item on stack - PUSH3, ///< place 3 byte item on stack - PUSH4, ///< place 4 byte item on stack - PUSH5, ///< place 5 byte item on stack - PUSH6, ///< place 6 byte item on stack - PUSH7, ///< place 7 byte item on stack - PUSH8, ///< place 8 byte item on stack - PUSH9, ///< place 9 byte item on stack - PUSH10, ///< place 10 byte item on stack - PUSH11, ///< place 11 byte item on stack - PUSH12, ///< place 12 byte item on stack - PUSH13, ///< place 13 byte item on stack - PUSH14, ///< place 14 byte item on stack - PUSH15, ///< place 15 byte item on stack - PUSH16, ///< place 16 byte item on stack - PUSH17, ///< place 17 byte item on stack - PUSH18, ///< place 18 byte item on stack - PUSH19, ///< place 19 byte item on stack - PUSH20, ///< place 20 byte item on stack - PUSH21, ///< place 21 byte item on stack - PUSH22, ///< place 22 byte item on stack - PUSH23, ///< place 23 byte item on stack - PUSH24, ///< place 24 byte item on stack - PUSH25, ///< place 25 byte item on stack - PUSH26, ///< place 26 byte item on stack - PUSH27, ///< place 27 byte item on stack - PUSH28, ///< place 28 byte item on stack - PUSH29, ///< place 29 byte item on stack - PUSH30, ///< place 30 byte item on stack - PUSH31, ///< place 31 byte item on stack - PUSH32, ///< place 32 byte item on stack - - DUP1 = 0x80, ///< copies the highest item in the stack to the top of the stack - DUP2, ///< copies the second highest item in the stack to the top of the stack - DUP3, ///< copies the third highest item in the stack to the top of the stack - DUP4, ///< copies the 4th highest item in the stack to the top of the stack - DUP5, ///< copies the 5th highest item in the stack to the top of the stack - DUP6, ///< copies the 6th highest item in the stack to the top of the stack - DUP7, ///< copies the 7th highest item in the stack to the top of the stack - DUP8, ///< copies the 8th highest item in the stack to the top of the stack - DUP9, ///< copies the 9th highest item in the stack to the top of the stack - DUP10, ///< copies the 10th highest item in the stack to the top of the stack - DUP11, ///< copies the 11th highest item in the stack to the top of the stack - DUP12, ///< copies the 12th highest item in the stack to the top of the stack - DUP13, ///< copies the 13th highest item in the stack to the top of the stack - DUP14, ///< copies the 14th highest item in the stack to the top of the stack - DUP15, ///< copies the 15th highest item in the stack to the top of the stack - DUP16, ///< copies the 16th highest item in the stack to the top of the stack - - SWAP1 = 0x90, ///< swaps the highest and second highest value on the stack - SWAP2, ///< swaps the highest and third highest value on the stack - SWAP3, ///< swaps the highest and 4th highest value on the stack - SWAP4, ///< swaps the highest and 5th highest value on the stack - SWAP5, ///< swaps the highest and 6th highest value on the stack - SWAP6, ///< swaps the highest and 7th highest value on the stack - SWAP7, ///< swaps the highest and 8th highest value on the stack - SWAP8, ///< swaps the highest and 9th highest value on the stack - SWAP9, ///< swaps the highest and 10th highest value on the stack - SWAP10, ///< swaps the highest and 11th highest value on the stack - SWAP11, ///< swaps the highest and 12th highest value on the stack - SWAP12, ///< swaps the highest and 13th highest value on the stack - SWAP13, ///< swaps the highest and 14th highest value on the stack - SWAP14, ///< swaps the highest and 15th highest value on the stack - SWAP15, ///< swaps the highest and 16th highest value on the stack - SWAP16, ///< swaps the highest and 17th highest value on the stack - - LOG0 = 0xa0, ///< Makes a log entry; no topics. - LOG1, ///< Makes a log entry; 1 topic. - LOG2, ///< Makes a log entry; 2 topics. - LOG3, ///< Makes a log entry; 3 topics. - LOG4, ///< Makes a log entry; 4 topics. - - JUMPTO = 0xb0, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp - JUMPIF, ///< conditionally alter the program counter -- not part of Instructions.cpp - JUMPV, ///< alter the program counter to a jumpdest -- not part of Instructions.cpp - JUMPSUB, ///< alter the program counter to a beginsub -- not part of Instructions.cpp - JUMPSUBV, ///< alter the program counter to a beginsub -- not part of Instructions.cpp - BEGINSUB, ///< set a potential jumpsub destination -- not part of Instructions.cpp - BEGINDATA, ///< begin the data section -- not part of Instructions.cpp - RETURNSUB, ///< return to subroutine jumped from -- not part of Instructions.cpp - PUTLOCAL, ///< pop top of stack to local variable -- not part of Instructions.cpp - GETLOCAL, ///< push local variable to top of stack -- not part of Instructions.cpp - - CREATE = 0xf0, ///< create a new account with associated code - CALL, ///< message-call into an account - CALLCODE, ///< message-call with another account's code only - RETURN, ///< halt execution returning output data - DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender - CREATE2 = 0xf5, ///< create new account with associated code at address `sha3(0xff + sender + salt + init code) % 2**160` - STATICCALL = 0xfa, ///< like CALL but disallow state modifications - - REVERT = 0xfd, ///< halt execution, revert state and return output data - INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero) - SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion -}; - -/// @returns true if the instruction is a PUSH -inline bool isPushInstruction(Instruction _inst) -{ - return Instruction::PUSH1 <= _inst && _inst <= Instruction::PUSH32; -} - -/// @returns true if the instruction is a DUP -inline bool isDupInstruction(Instruction _inst) -{ - return Instruction::DUP1 <= _inst && _inst <= Instruction::DUP16; -} - -/// @returns true if the instruction is a SWAP -inline bool isSwapInstruction(Instruction _inst) -{ - return Instruction::SWAP1 <= _inst && _inst <= Instruction::SWAP16; -} - -/// @returns true if the instruction is a LOG -inline bool isLogInstruction(Instruction _inst) -{ - return Instruction::LOG0 <= _inst && _inst <= Instruction::LOG4; -} - -/// @returns the number of PUSH Instruction _inst -inline unsigned getPushNumber(Instruction _inst) -{ - return (uint8_t)_inst - unsigned(Instruction::PUSH1) + 1; -} - -/// @returns the number of DUP Instruction _inst -inline unsigned getDupNumber(Instruction _inst) -{ - return (uint8_t)_inst - unsigned(Instruction::DUP1) + 1; -} - -/// @returns the number of SWAP Instruction _inst -inline unsigned getSwapNumber(Instruction _inst) -{ - return (uint8_t)_inst - unsigned(Instruction::SWAP1) + 1; -} - -/// @returns the number of LOG Instruction _inst -inline unsigned getLogNumber(Instruction _inst) -{ - return (uint8_t)_inst - unsigned(Instruction::LOG0); -} - -/// @returns the PUSH<_number> instruction -inline Instruction pushInstruction(unsigned _number) -{ - assertThrow(1 <= _number && _number <= 32, InvalidOpcode, std::string("Invalid PUSH instruction requested (") + std::to_string(_number) + ")."); - return Instruction(unsigned(Instruction::PUSH1) + _number - 1); -} - -/// @returns the DUP<_number> instruction -inline Instruction dupInstruction(unsigned _number) -{ - assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid DUP instruction requested (") + std::to_string(_number) + ")."); - return Instruction(unsigned(Instruction::DUP1) + _number - 1); -} - -/// @returns the SWAP<_number> instruction -inline Instruction swapInstruction(unsigned _number) -{ - assertThrow(1 <= _number && _number <= 16, InvalidOpcode, std::string("Invalid SWAP instruction requested (") + std::to_string(_number) + ")."); - return Instruction(unsigned(Instruction::SWAP1) + _number - 1); -} - -/// @returns the LOG<_number> instruction -inline Instruction logInstruction(unsigned _number) -{ - assertThrow(_number <= 4, InvalidOpcode, std::string("Invalid LOG instruction requested (") + std::to_string(_number) + ")."); - return Instruction(unsigned(Instruction::LOG0) + _number); -} - -enum class Tier : unsigned -{ - Zero = 0, // 0, Zero - Base, // 2, Quick - VeryLow, // 3, Fastest - Low, // 5, Fast - Mid, // 8, Mid - High, // 10, Slow - Ext, // 20, Ext - ExtCode, // 700, Extcode - Balance, // 400, Balance - Special, // multiparam or otherwise special - Invalid // Invalid. -}; - -/// Information structure for a particular instruction. -struct InstructionInfo -{ - std::string name; ///< The name of the instruction. - int additional; ///< Additional items required in memory for this instructions (only for PUSH). - int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack). - int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed. - bool sideEffects; ///< false if the only effect on the execution environment (apart from gas usage) is a change to a topmost segment of the stack - Tier gasPriceTier; ///< Tier for gas pricing. -}; - -/// Information on all the instructions. -InstructionInfo instructionInfo(Instruction _inst); - -/// check whether instructions exists. -bool isValidInstruction(Instruction _inst); - -/// Convert from string mnemonic to Instruction type. -extern const std::map c_instructions; - -/// Iterate through EVM code and call a function on each instruction. -void eachInstruction(bytes const& _mem, std::function const& _onInstruction); - -/// Convert from EVM code to simple EVM assembly language. -std::string disassemble(bytes const& _mem); - -} diff --git a/compiler/libevmasm/JumpdestRemover.cpp b/compiler/libevmasm/JumpdestRemover.cpp deleted file mode 100644 index 6be05b19..00000000 --- a/compiler/libevmasm/JumpdestRemover.cpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * Removes unused JUMPDESTs. - */ - -#include - -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; - -bool JumpdestRemover::optimise(set const& _tagsReferencedFromOutside) -{ - set references{referencedTags(m_items, -1)}; - references.insert(_tagsReferencedFromOutside.begin(), _tagsReferencedFromOutside.end()); - - size_t initialSize = m_items.size(); - /// Remove tags which are never referenced. - auto pend = remove_if( - m_items.begin(), - m_items.end(), - [&](AssemblyItem const& _item) - { - if (_item.type() != Tag) - return false; - auto asmIdAndTag = _item.splitForeignPushTag(); - assertThrow(asmIdAndTag.first == size_t(-1), OptimizerException, "Sub-assembly tag used as label."); - size_t tag = asmIdAndTag.second; - return !references.count(tag); - } - ); - m_items.erase(pend, m_items.end()); - return m_items.size() != initialSize; -} - -set JumpdestRemover::referencedTags(AssemblyItems const& _items, size_t _subId) -{ - set ret; - for (auto const& item: _items) - if (item.type() == PushTag) - { - auto subAndTag = item.splitForeignPushTag(); - if (subAndTag.first == _subId) - ret.insert(subAndTag.second); - } - return ret; -} diff --git a/compiler/libevmasm/JumpdestRemover.h b/compiler/libevmasm/JumpdestRemover.h deleted file mode 100644 index 80b546d7..00000000 --- a/compiler/libevmasm/JumpdestRemover.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Alex Beregszaszi - * Removes unused JUMPDESTs. - */ -#pragma once - -#include -#include -#include - -namespace solidity::evmasm -{ -class AssemblyItem; -using AssemblyItems = std::vector; - -class JumpdestRemover -{ -public: - explicit JumpdestRemover(AssemblyItems& _items): m_items(_items) {} - - bool optimise(std::set const& _tagsReferencedFromOutside); - - /// @returns a set of all tags from the given sub-assembly that are referenced - /// from the given list of items. - static std::set referencedTags(AssemblyItems const& _items, size_t _subId); - -private: - AssemblyItems& m_items; -}; - -} diff --git a/compiler/libevmasm/KnownState.cpp b/compiler/libevmasm/KnownState.cpp deleted file mode 100644 index b6198e99..00000000 --- a/compiler/libevmasm/KnownState.cpp +++ /dev/null @@ -1,418 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file KnownState.cpp - * @author Christian - * @date 2015 - * Contains knowledge about the state of the virtual machine at a specific instruction. - */ - -#include -#include -#include - -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::langutil; - -ostream& KnownState::stream(ostream& _out) const -{ - auto streamExpressionClass = [this](ostream& _out, Id _id) - { - auto const& expr = m_expressionClasses->representative(_id); - _out << " " << dec << _id << ": "; - if (!expr.item) - _out << " no item"; - else if (expr.item->type() == UndefinedItem) - _out << " unknown " << int(expr.item->data()); - else - _out << *expr.item; - if (expr.sequenceNumber) - _out << "@" << dec << expr.sequenceNumber; - _out << "("; - for (Id arg: expr.arguments) - _out << dec << arg << ","; - _out << ")" << endl; - }; - - _out << "=== State ===" << endl; - _out << "Stack height: " << dec << m_stackHeight << endl; - _out << "Equivalence classes:" << endl; - for (Id eqClass = 0; eqClass < m_expressionClasses->size(); ++eqClass) - streamExpressionClass(_out, eqClass); - - _out << "Stack:" << endl; - for (auto const& it: m_stackElements) - { - _out << " " << dec << it.first << ": "; - streamExpressionClass(_out, it.second); - } - _out << "Storage:" << endl; - for (auto const& it: m_storageContent) - { - _out << " "; - streamExpressionClass(_out, it.first); - _out << ": "; - streamExpressionClass(_out, it.second); - } - _out << "Memory:" << endl; - for (auto const& it: m_memoryContent) - { - _out << " "; - streamExpressionClass(_out, it.first); - _out << ": "; - streamExpressionClass(_out, it.second); - } - - return _out; -} - -KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool _copyItem) -{ - StoreOperation op; - if (_item.type() == Tag) - { - // can be ignored - } - else if (_item.type() != Operation) - { - assertThrow(_item.deposit() == 1, InvalidDeposit, ""); - if (_item.pushedValue()) - // only available after assembly stage, should not be used for optimisation - setStackElement(++m_stackHeight, m_expressionClasses->find(*_item.pushedValue())); - else - setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem)); - } - else - { - Instruction instruction = _item.instruction(); - InstructionInfo info = instructionInfo(instruction); - if (SemanticInformation::isDupInstruction(_item)) - setStackElement( - m_stackHeight + 1, - stackElement( - m_stackHeight - int(instruction) + int(Instruction::DUP1), - _item.location() - ) - ); - else if (SemanticInformation::isSwapInstruction(_item)) - swapStackElements( - m_stackHeight, - m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1), - _item.location() - ); - else if (instruction != Instruction::POP) - { - vector arguments(info.args); - for (int i = 0; i < info.args; ++i) - arguments[i] = stackElement(m_stackHeight - i, _item.location()); - switch (_item.instruction()) - { - case Instruction::SSTORE: - op = storeInStorage(arguments[0], arguments[1], _item.location()); - break; - case Instruction::SLOAD: - setStackElement( - m_stackHeight + _item.deposit(), - loadFromStorage(arguments[0], _item.location()) - ); - break; - case Instruction::MSTORE: - op = storeInMemory(arguments[0], arguments[1], _item.location()); - break; - case Instruction::MLOAD: - setStackElement( - m_stackHeight + _item.deposit(), - loadFromMemory(arguments[0], _item.location()) - ); - break; - case Instruction::KECCAK256: - setStackElement( - m_stackHeight + _item.deposit(), - applyKeccak256(arguments.at(0), arguments.at(1), _item.location()) - ); - break; - default: - bool invMem = SemanticInformation::invalidatesMemory(_item.instruction()); - bool invStor = SemanticInformation::invalidatesStorage(_item.instruction()); - // We could be a bit more fine-grained here (CALL only invalidates part of - // memory, etc), but we do not for now. - if (invMem) - resetMemory(); - if (invStor) - resetStorage(); - if (invMem || invStor) - m_sequenceNumber += 2; // Increment by two because it can read and write - assertThrow(info.ret <= 1, InvalidDeposit, ""); - if (info.ret == 1) - setStackElement( - m_stackHeight + _item.deposit(), - m_expressionClasses->find(_item, arguments, _copyItem) - ); - } - } - m_stackElements.erase( - m_stackElements.upper_bound(m_stackHeight + _item.deposit()), - m_stackElements.end() - ); - m_stackHeight += _item.deposit(); - } - return op; -} - -/// Helper function for KnownState::reduceToCommonKnowledge, removes everything from -/// _this which is not in or not equal to the value in _other. -template void intersect(_Mapping& _this, _Mapping const& _other) -{ - for (auto it = _this.begin(); it != _this.end();) - if (_other.count(it->first) && _other.at(it->first) == it->second) - ++it; - else - it = _this.erase(it); -} - -void KnownState::reduceToCommonKnowledge(KnownState const& _other, bool _combineSequenceNumbers) -{ - int stackDiff = m_stackHeight - _other.m_stackHeight; - for (auto it = m_stackElements.begin(); it != m_stackElements.end();) - if (_other.m_stackElements.count(it->first - stackDiff)) - { - Id other = _other.m_stackElements.at(it->first - stackDiff); - if (it->second == other) - ++it; - else - { - set theseTags = tagsInExpression(it->second); - set otherTags = tagsInExpression(other); - if (!theseTags.empty() && !otherTags.empty()) - { - theseTags.insert(otherTags.begin(), otherTags.end()); - it->second = tagUnion(theseTags); - ++it; - } - else - it = m_stackElements.erase(it); - } - } - else - it = m_stackElements.erase(it); - - // Use the smaller stack height. Essential to terminate in case of loops. - if (m_stackHeight > _other.m_stackHeight) - { - map shiftedStack; - for (auto const& stackElement: m_stackElements) - shiftedStack[stackElement.first - stackDiff] = stackElement.second; - m_stackElements = move(shiftedStack); - m_stackHeight = _other.m_stackHeight; - } - - intersect(m_storageContent, _other.m_storageContent); - intersect(m_memoryContent, _other.m_memoryContent); - if (_combineSequenceNumbers) - m_sequenceNumber = max(m_sequenceNumber, _other.m_sequenceNumber); -} - -bool KnownState::operator==(KnownState const& _other) const -{ - if (m_storageContent != _other.m_storageContent || m_memoryContent != _other.m_memoryContent) - return false; - int stackDiff = m_stackHeight - _other.m_stackHeight; - auto thisIt = m_stackElements.cbegin(); - auto otherIt = _other.m_stackElements.cbegin(); - for (; thisIt != m_stackElements.cend() && otherIt != _other.m_stackElements.cend(); ++thisIt, ++otherIt) - if (thisIt->first - stackDiff != otherIt->first || thisIt->second != otherIt->second) - return false; - return (thisIt == m_stackElements.cend() && otherIt == _other.m_stackElements.cend()); -} - -ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation const& _location) -{ - if (m_stackElements.count(_stackHeight)) - return m_stackElements.at(_stackHeight); - // Stack element not found (not assigned yet), create new unknown equivalence class. - return m_stackElements[_stackHeight] = - m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location)); -} - -KnownState::Id KnownState::relativeStackElement(int _stackOffset, SourceLocation const& _location) -{ - return stackElement(m_stackHeight + _stackOffset, _location); -} - -void KnownState::clearTagUnions() -{ - for (auto it = m_stackElements.begin(); it != m_stackElements.end();) - if (m_tagUnions.left.count(it->second)) - it = m_stackElements.erase(it); - else - ++it; -} - -void KnownState::setStackElement(int _stackHeight, Id _class) -{ - m_stackElements[_stackHeight] = _class; -} - -void KnownState::swapStackElements( - int _stackHeightA, - int _stackHeightB, - SourceLocation const& _location -) -{ - assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements."); - // ensure they are created - stackElement(_stackHeightA, _location); - stackElement(_stackHeightB, _location); - - swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]); -} - -KnownState::StoreOperation KnownState::storeInStorage( - Id _slot, - Id _value, - SourceLocation const& _location) -{ - if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value) - // do not execute the storage if we know that the value is already there - return StoreOperation(); - m_sequenceNumber++; - decltype(m_storageContent) storageContents; - // Copy over all values (i.e. retain knowledge about them) where we know that this store - // operation will not destroy the knowledge. Specifically, we copy storage locations we know - // are different from _slot or locations where we know that the stored value is equal to _value. - for (auto const& storageItem: m_storageContent) - if (m_expressionClasses->knownToBeDifferent(storageItem.first, _slot) || storageItem.second == _value) - storageContents.insert(storageItem); - m_storageContent = move(storageContents); - - AssemblyItem item(Instruction::SSTORE, _location); - Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); - StoreOperation operation{StoreOperation::Storage, _slot, m_sequenceNumber, id}; - m_storageContent[_slot] = _value; - // increment a second time so that we get unique sequence numbers for writes - m_sequenceNumber++; - - return operation; -} - -ExpressionClasses::Id KnownState::loadFromStorage(Id _slot, SourceLocation const& _location) -{ - if (m_storageContent.count(_slot)) - return m_storageContent.at(_slot); - - AssemblyItem item(Instruction::SLOAD, _location); - return m_storageContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber); -} - -KnownState::StoreOperation KnownState::storeInMemory(Id _slot, Id _value, SourceLocation const& _location) -{ - if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) - // do not execute the store if we know that the value is already there - return StoreOperation(); - m_sequenceNumber++; - decltype(m_memoryContent) memoryContents; - // copy over values at points where we know that they are different from _slot by at least 32 - for (auto const& memoryItem: m_memoryContent) - if (m_expressionClasses->knownToBeDifferentBy32(memoryItem.first, _slot)) - memoryContents.insert(memoryItem); - m_memoryContent = move(memoryContents); - - AssemblyItem item(Instruction::MSTORE, _location); - Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); - StoreOperation operation{StoreOperation::Memory, _slot, m_sequenceNumber, id}; - m_memoryContent[_slot] = _value; - // increment a second time so that we get unique sequence numbers for writes - m_sequenceNumber++; - return operation; -} - -ExpressionClasses::Id KnownState::loadFromMemory(Id _slot, SourceLocation const& _location) -{ - if (m_memoryContent.count(_slot)) - return m_memoryContent.at(_slot); - - AssemblyItem item(Instruction::MLOAD, _location); - return m_memoryContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber); -} - -KnownState::Id KnownState::applyKeccak256( - Id _start, - Id _length, - SourceLocation const& _location -) -{ - AssemblyItem keccak256Item(Instruction::KECCAK256, _location); - // Special logic if length is a short constant, otherwise we cannot tell. - u256 const* l = m_expressionClasses->knownConstant(_length); - // unknown or too large length - if (!l || *l > 128) - return m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber); - - vector arguments; - for (u256 i = 0; i < *l; i += 32) - { - Id slot = m_expressionClasses->find( - AssemblyItem(Instruction::ADD, _location), - {_start, m_expressionClasses->find(i)} - ); - arguments.push_back(loadFromMemory(slot, _location)); - } - if (m_knownKeccak256Hashes.count(arguments)) - return m_knownKeccak256Hashes.at(arguments); - Id v; - // If all arguments are known constants, compute the Keccak-256 here - if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses->knownConstant(_a); })) - { - bytes data; - for (Id a: arguments) - data += util::toBigEndian(*m_expressionClasses->knownConstant(a)); - data.resize(size_t(*l)); - v = m_expressionClasses->find(AssemblyItem(u256(util::keccak256(data)), _location)); - } - else - v = m_expressionClasses->find(keccak256Item, {_start, _length}, true, m_sequenceNumber); - return m_knownKeccak256Hashes[arguments] = v; -} - -set KnownState::tagsInExpression(KnownState::Id _expressionId) -{ - if (m_tagUnions.left.count(_expressionId)) - return m_tagUnions.left.at(_expressionId); - // Might be a tag, then return the set of itself. - ExpressionClasses::Expression expr = m_expressionClasses->representative(_expressionId); - if (expr.item && expr.item->type() == PushTag) - return set({expr.item->data()}); - else - return set(); -} - -KnownState::Id KnownState::tagUnion(set _tags) -{ - if (m_tagUnions.right.count(_tags)) - return m_tagUnions.right.at(_tags); - else - { - Id id = m_expressionClasses->newClass(SourceLocation()); - m_tagUnions.right.insert(make_pair(_tags, id)); - return id; - } -} - diff --git a/compiler/libevmasm/KnownState.h b/compiler/libevmasm/KnownState.h deleted file mode 100644 index a9cc4f20..00000000 --- a/compiler/libevmasm/KnownState.h +++ /dev/null @@ -1,182 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file KnownState.h - * @author Christian - * @date 2015 - * Contains knowledge about the state of the virtual machine at a specific instruction. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wredeclared-class-member" -#endif // defined(__clang__) - -#include - -#if defined(__clang__) -#pragma clang diagnostic pop -#endif // defined(__clang__) - -#include -#include -#include -#include - -namespace solidity::langutil -{ -struct SourceLocation; -} - -namespace solidity::evmasm -{ - -class AssemblyItem; -using AssemblyItems = std::vector; - -/** - * Class to infer and store knowledge about the state of the virtual machine at a specific - * instruction. - * - * The general workings are that for each assembly item that is fed, an equivalence class is - * derived from the operation and the equivalence class of its arguments. DUPi, SWAPi and some - * arithmetic instructions are used to infer equivalences while these classes are determined. - */ -class KnownState -{ -public: - using Id = ExpressionClasses::Id; - struct StoreOperation - { - enum Target { Invalid, Memory, Storage }; - - bool isValid() const { return target != Invalid; } - - Target target{Invalid}; - Id slot{std::numeric_limits::max()}; - unsigned sequenceNumber{std::numeric_limits::max()}; - Id expression{std::numeric_limits::max()}; - }; - - explicit KnownState( - std::shared_ptr _expressionClasses = std::make_shared() - ): m_expressionClasses(_expressionClasses) - { - } - - /// Streams debugging information to @a _out. - std::ostream& stream(std::ostream& _out) const; - - /// Feeds the item into the system for analysis. - /// @returns a possible store operation - StoreOperation feedItem(AssemblyItem const& _item, bool _copyItem = false); - - /// Resets any knowledge about storage. - void resetStorage() { m_storageContent.clear(); } - /// Resets any knowledge about storage. - void resetMemory() { m_memoryContent.clear(); } - /// Resets any knowledge about the current stack. - void resetStack() { m_stackElements.clear(); m_stackHeight = 0; } - /// Resets any knowledge. - void reset() { resetStorage(); resetMemory(); resetStack(); } - - unsigned sequenceNumber() const { return m_sequenceNumber; } - - /// Replaces the state by the intersection with _other, i.e. only equal knowledge is retained. - /// If the stack heighht is different, the smaller one is used and the stack is compared - /// relatively. - /// @param _combineSequenceNumbers if true, sets the sequence number to the maximum of both - void reduceToCommonKnowledge(KnownState const& _other, bool _combineSequenceNumbers); - - /// @returns a shared pointer to a copy of this state. - std::shared_ptr copy() const { return std::make_shared(*this); } - - /// @returns true if the knowledge about the state of both objects is (known to be) equal. - bool operator==(KnownState const& _other) const; - - /// Retrieves the current equivalence class fo the given stack element (or generates a new - /// one if it does not exist yet). - Id stackElement(int _stackHeight, langutil::SourceLocation const& _location); - /// @returns the stackElement relative to the current stack height. - Id relativeStackElement(int _stackOffset, langutil::SourceLocation const& _location = {}); - - /// @returns its set of tags if the given expression class is a known tag union; returns a set - /// containing the tag if it is a PushTag expression and the empty set otherwise. - std::set tagsInExpression(Id _expressionId); - /// During analysis, different tags on the stack are partially treated as the same class. - /// This removes such classes not to confuse later analyzers. - void clearTagUnions(); - - int stackHeight() const { return m_stackHeight; } - std::map const& stackElements() const { return m_stackElements; } - ExpressionClasses& expressionClasses() const { return *m_expressionClasses; } - - std::map const& storageContent() const { return m_storageContent; } - -private: - /// Assigns a new equivalence class to the next sequence number of the given stack element. - void setStackElement(int _stackHeight, Id _class); - /// Swaps the given stack elements in their next sequence number. - void swapStackElements(int _stackHeightA, int _stackHeightB, langutil::SourceLocation const& _location); - - /// Increments the sequence number, deletes all storage information that might be overwritten - /// and stores the new value at the given slot. - /// @returns the store operation, which might be invalid if storage was not modified - StoreOperation storeInStorage(Id _slot, Id _value, langutil::SourceLocation const& _location); - /// Retrieves the current value at the given slot in storage or creates a new special sload class. - Id loadFromStorage(Id _slot, langutil::SourceLocation const& _location); - /// Increments the sequence number, deletes all memory information that might be overwritten - /// and stores the new value at the given slot. - /// @returns the store operation, which might be invalid if memory was not modified - StoreOperation storeInMemory(Id _slot, Id _value, langutil::SourceLocation const& _location); - /// Retrieves the current value at the given slot in memory or creates a new special mload class. - Id loadFromMemory(Id _slot, langutil::SourceLocation const& _location); - /// Finds or creates a new expression that applies the Keccak-256 hash function to the contents in memory. - Id applyKeccak256(Id _start, Id _length, langutil::SourceLocation const& _location); - - /// @returns a new or already used Id representing the given set of tags. - Id tagUnion(std::set _tags); - - /// Current stack height, can be negative. - int m_stackHeight = 0; - /// Current stack layout, mapping stack height -> equivalence class - std::map m_stackElements; - /// Current sequence number, this is incremented with each modification to storage or memory. - unsigned m_sequenceNumber = 1; - /// Knowledge about storage content. - std::map m_storageContent; - /// Knowledge about memory content. Keys are memory addresses, note that the values overlap - /// and are not contained here if they are not completely known. - std::map m_memoryContent; - /// Keeps record of all Keccak-256 hashes that are computed. - std::map, Id> m_knownKeccak256Hashes; - /// Structure containing the classes of equivalent expressions. - std::shared_ptr m_expressionClasses; - /// Container for unions of tags stored on the stack. - boost::bimap> m_tagUnions; -}; - -} diff --git a/compiler/libevmasm/LinkerObject.cpp b/compiler/libevmasm/LinkerObject.cpp deleted file mode 100644 index 21b21310..00000000 --- a/compiler/libevmasm/LinkerObject.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file LinkerObject.cpp - * @author Christian R - * @date 2015 - */ - -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; - -void LinkerObject::append(LinkerObject const& _other) -{ - for (auto const& ref: _other.linkReferences) - linkReferences[ref.first + bytecode.size()] = ref.second; - bytecode += _other.bytecode; -} - -void LinkerObject::link(map const& _libraryAddresses) -{ - std::map remainingRefs; - for (auto const& linkRef: linkReferences) - if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses)) - copy(address->data(), address->data() + 20, bytecode.begin() + linkRef.first); - else - remainingRefs.insert(linkRef); - linkReferences.swap(remainingRefs); -} - -string LinkerObject::toHex() const -{ - string hex = solidity::util::toHex(bytecode); - for (auto const& ref: linkReferences) - { - size_t pos = ref.first * 2; - string hash = libraryPlaceholder(ref.second); - hex[pos] = hex[pos + 1] = hex[pos + 38] = hex[pos + 39] = '_'; - for (size_t i = 0; i < 36; ++i) - hex[pos + 2 + i] = hash.at(i); - } - return hex; -} - -string LinkerObject::libraryPlaceholder(string const& _libraryName) -{ - return "$" + keccak256(_libraryName).hex().substr(0, 34) + "$"; -} - -h160 const* -LinkerObject::matchLibrary( - string const& _linkRefName, - map const& _libraryAddresses -) -{ - auto it = _libraryAddresses.find(_linkRefName); - if (it != _libraryAddresses.end()) - return &it->second; - // If the user did not supply a fully qualified library name, - // try to match only the simple library name - size_t colon = _linkRefName.find(':'); - if (colon == string::npos) - return nullptr; - it = _libraryAddresses.find(_linkRefName.substr(colon + 1)); - if (it != _libraryAddresses.end()) - return &it->second; - return nullptr; -} diff --git a/compiler/libevmasm/LinkerObject.h b/compiler/libevmasm/LinkerObject.h deleted file mode 100644 index e7a1e966..00000000 --- a/compiler/libevmasm/LinkerObject.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file Assembly.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include - -namespace solidity::evmasm -{ - -/** - * Binary object that potentially still needs to be linked (i.e. addresses of other contracts - * need to be filled in). - */ -struct LinkerObject -{ - /// The bytecode. - bytes bytecode; - - /// Map from offsets in bytecode to library identifiers. The addresses starting at those offsets - /// need to be replaced by the actual addresses by the linker. - std::map linkReferences; - - /// Appends the bytecode of @a _other and incorporates its link references. - void append(LinkerObject const& _other); - - /// Links the given libraries by replacing their uses in the code and removes them from the references. - void link(std::map const& _libraryAddresses); - - /// @returns a hex representation of the bytecode of the given object, replacing unlinked - /// addresses by placeholders. This output is lowercase. - std::string toHex() const; - - /// @returns a 36 character string that is used as a placeholder for the library - /// address (enclosed by `__` on both sides). The placeholder is the hex representation - /// of the first 18 bytes of the keccak-256 hash of @a _libraryName. - static std::string libraryPlaceholder(std::string const& _libraryName); - -private: - static util::h160 const* matchLibrary( - std::string const& _linkRefName, - std::map const& _libraryAddresses - ); -}; - -} diff --git a/compiler/libevmasm/PathGasMeter.cpp b/compiler/libevmasm/PathGasMeter.cpp deleted file mode 100644 index 749cc7bb..00000000 --- a/compiler/libevmasm/PathGasMeter.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file PathGasMeter.cpp - * @author Christian - * @date 2015 - */ - -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -PathGasMeter::PathGasMeter(AssemblyItems const& _items, langutil::EVMVersion _evmVersion): - m_items(_items), m_evmVersion(_evmVersion) -{ - for (size_t i = 0; i < m_items.size(); ++i) - if (m_items[i].type() == Tag) - m_tagPositions[m_items[i].data()] = i; -} - -GasMeter::GasConsumption PathGasMeter::estimateMax( - size_t _startIndex, - shared_ptr const& _state -) -{ - auto path = make_unique(); - path->index = _startIndex; - path->state = _state->copy(); - queue(move(path)); - - GasMeter::GasConsumption gas; - while (!m_queue.empty() && !gas.isInfinite) - gas = max(gas, handleQueueItem()); - return gas; -} - -void PathGasMeter::queue(std::unique_ptr&& _newPath) -{ - if ( - m_highestGasUsagePerJumpdest.count(_newPath->index) && - _newPath->gas < m_highestGasUsagePerJumpdest.at(_newPath->index) - ) - return; - m_highestGasUsagePerJumpdest[_newPath->index] = _newPath->gas; - m_queue[_newPath->index] = move(_newPath); -} - -GasMeter::GasConsumption PathGasMeter::handleQueueItem() -{ - assertThrow(!m_queue.empty(), OptimizerException, ""); - - unique_ptr path = move(m_queue.rbegin()->second); - m_queue.erase(--m_queue.end()); - - shared_ptr state = path->state; - GasMeter meter(state, m_evmVersion, path->largestMemoryAccess); - ExpressionClasses& classes = state->expressionClasses(); - GasMeter::GasConsumption gas = path->gas; - size_t index = path->index; - - if (index >= m_items.size() || (index > 0 && m_items.at(index).type() != Tag)) - // Invalid jump usually provokes an out-of-gas exception, but we want to give an upper - // bound on the gas that is needed without changing the behaviour, so it is fine to - // return the current gas value. - return gas; - - set jumpTags; - for (; index < m_items.size() && !gas.isInfinite; ++index) - { - bool branchStops = false; - jumpTags.clear(); - AssemblyItem const& item = m_items.at(index); - if (item.type() == Tag || item == AssemblyItem(Instruction::JUMPDEST)) - { - // Do not allow any backwards jump. This is quite restrictive but should work for - // the simplest things. - if (path->visitedJumpdests.count(index)) - return GasMeter::GasConsumption::infinite(); - path->visitedJumpdests.insert(index); - } - else if (item == AssemblyItem(Instruction::JUMP)) - { - branchStops = true; - jumpTags = state->tagsInExpression(state->relativeStackElement(0)); - if (jumpTags.empty()) // unknown jump destination - return GasMeter::GasConsumption::infinite(); - } - else if (item == AssemblyItem(Instruction::JUMPI)) - { - ExpressionClasses::Id condition = state->relativeStackElement(-1); - if (classes.knownNonZero(condition) || !classes.knownZero(condition)) - { - jumpTags = state->tagsInExpression(state->relativeStackElement(0)); - if (jumpTags.empty()) // unknown jump destination - return GasMeter::GasConsumption::infinite(); - } - branchStops = classes.knownNonZero(condition); - } - else if (SemanticInformation::altersControlFlow(item)) - branchStops = true; - - gas += meter.estimateMax(item); - - for (u256 const& tag: jumpTags) - { - auto newPath = make_unique(); - newPath->index = m_items.size(); - if (m_tagPositions.count(tag)) - newPath->index = m_tagPositions.at(tag); - newPath->gas = gas; - newPath->largestMemoryAccess = meter.largestMemoryAccess(); - newPath->state = state->copy(); - newPath->visitedJumpdests = path->visitedJumpdests; - queue(move(newPath)); - } - - if (branchStops) - break; - } - - return gas; -} diff --git a/compiler/libevmasm/PathGasMeter.h b/compiler/libevmasm/PathGasMeter.h deleted file mode 100644 index 38a19797..00000000 --- a/compiler/libevmasm/PathGasMeter.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** @file PathGasMeter.cpp - * @author Christian - * @date 2015 - */ - -#pragma once - -#include - -#include - -#include -#include -#include - -namespace solidity::evmasm -{ - -class KnownState; - -struct GasPath -{ - size_t index = 0; - std::shared_ptr state; - u256 largestMemoryAccess; - GasMeter::GasConsumption gas; - std::set visitedJumpdests; -}; - -/** - * Computes an upper bound on the gas usage of a computation starting at a certain position in - * a list of AssemblyItems in a given state until the computation stops. - * Can be used to estimate the gas usage of functions on any given input. - */ -class PathGasMeter -{ -public: - explicit PathGasMeter(AssemblyItems const& _items, langutil::EVMVersion _evmVersion); - - GasMeter::GasConsumption estimateMax(size_t _startIndex, std::shared_ptr const& _state); - - static GasMeter::GasConsumption estimateMax( - AssemblyItems const& _items, - langutil::EVMVersion _evmVersion, - size_t _startIndex, - std::shared_ptr const& _state - ) - { - return PathGasMeter(_items, _evmVersion).estimateMax(_startIndex, _state); - } - -private: - /// Adds a new path item to the queue, but only if we do not already have - /// a higher gas usage at that point. - /// This is not exact as different state might influence higher gas costs at a later - /// point in time, but it greatly reduces computational overhead. - void queue(std::unique_ptr&& _newPath); - GasMeter::GasConsumption handleQueueItem(); - - /// Map of jumpdest -> gas path, so not really a queue. We only have one queued up - /// item per jumpdest, because of the behaviour of `queue` above. - std::map> m_queue; - std::map m_highestGasUsagePerJumpdest; - std::map m_tagPositions; - AssemblyItems const& m_items; - langutil::EVMVersion m_evmVersion; -}; - -} diff --git a/compiler/libevmasm/PeepholeOptimiser.cpp b/compiler/libevmasm/PeepholeOptimiser.cpp deleted file mode 100644 index e05abd3e..00000000 --- a/compiler/libevmasm/PeepholeOptimiser.cpp +++ /dev/null @@ -1,375 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file PeepholeOptimiser.cpp - * Performs local optimising code changes to assembly. - */ - -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -// TODO: Extend this to use the tools from ExpressionClasses.cpp - -namespace -{ - -struct OptimiserState -{ - AssemblyItems const& items; - size_t i; - std::back_insert_iterator out; -}; - -template -struct ApplyRule -{ -}; -template -struct ApplyRule -{ - static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) - { - return Method::applySimple(_in[0], _in[1], _in[2], _in[3], _out); - } -}; -template -struct ApplyRule -{ - static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) - { - return Method::applySimple(_in[0], _in[1], _in[2], _out); - } -}; -template -struct ApplyRule -{ - static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) - { - return Method::applySimple(_in[0], _in[1], _out); - } -}; -template -struct ApplyRule -{ - static bool applyRule(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) - { - return Method::applySimple(_in[0], _out); - } -}; - -template -struct SimplePeepholeOptimizerMethod -{ - static bool apply(OptimiserState& _state) - { - if ( - _state.i + WindowSize <= _state.items.size() && - ApplyRule::applyRule(_state.items.begin() + _state.i, _state.out) - ) - { - _state.i += WindowSize; - return true; - } - else - return false; - } -}; - -struct Identity: SimplePeepholeOptimizerMethod -{ - static bool applySimple(AssemblyItem const& _item, std::back_insert_iterator _out) - { - *_out = _item; - return true; - } -}; - -struct PushPop: SimplePeepholeOptimizerMethod -{ - static bool applySimple(AssemblyItem const& _push, AssemblyItem const& _pop, std::back_insert_iterator) - { - auto t = _push.type(); - return _pop == Instruction::POP && ( - SemanticInformation::isDupInstruction(_push) || - t == Push || t == PushString || t == PushTag || t == PushSub || - t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress - ); - } -}; - -struct OpPop: SimplePeepholeOptimizerMethod -{ - static bool applySimple( - AssemblyItem const& _op, - AssemblyItem const& _pop, - std::back_insert_iterator _out - ) - { - if (_pop == Instruction::POP && _op.type() == Operation) - { - Instruction instr = _op.instruction(); - if (instructionInfo(instr).ret == 1 && !instructionInfo(instr).sideEffects) - { - for (int j = 0; j < instructionInfo(instr).args; j++) - *_out = {Instruction::POP, _op.location()}; - return true; - } - } - return false; - } -}; - -struct DoubleSwap: SimplePeepholeOptimizerMethod -{ - static size_t applySimple(AssemblyItem const& _s1, AssemblyItem const& _s2, std::back_insert_iterator) - { - return _s1 == _s2 && SemanticInformation::isSwapInstruction(_s1); - } -}; - -struct DoublePush: SimplePeepholeOptimizerMethod -{ - static bool applySimple(AssemblyItem const& _push1, AssemblyItem const& _push2, std::back_insert_iterator _out) - { - if (_push1.type() == Push && _push2.type() == Push && _push1.data() == _push2.data()) - { - *_out = _push1; - *_out = {Instruction::DUP1, _push2.location()}; - return true; - } - else - return false; - } -}; - -struct CommutativeSwap: SimplePeepholeOptimizerMethod -{ - static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator _out) - { - // Remove SWAP1 if following instruction is commutative - if ( - _swap == Instruction::SWAP1 && - SemanticInformation::isCommutativeOperation(_op) - ) - { - *_out = _op; - return true; - } - else - return false; - } -}; - -struct SwapComparison: SimplePeepholeOptimizerMethod -{ - static bool applySimple(AssemblyItem const& _swap, AssemblyItem const& _op, std::back_insert_iterator _out) - { - static map const swappableOps{ - { Instruction::LT, Instruction::GT }, - { Instruction::GT, Instruction::LT }, - { Instruction::SLT, Instruction::SGT }, - { Instruction::SGT, Instruction::SLT } - }; - - if ( - _swap == Instruction::SWAP1 && - _op.type() == Operation && - swappableOps.count(_op.instruction()) - ) - { - *_out = swappableOps.at(_op.instruction()); - return true; - } - else - return false; - } -}; - -struct IsZeroIsZeroJumpI: SimplePeepholeOptimizerMethod -{ - static size_t applySimple( - AssemblyItem const& _iszero1, - AssemblyItem const& _iszero2, - AssemblyItem const& _pushTag, - AssemblyItem const& _jumpi, - std::back_insert_iterator _out - ) - { - if ( - _iszero1 == Instruction::ISZERO && - _iszero2 == Instruction::ISZERO && - _pushTag.type() == PushTag && - _jumpi == Instruction::JUMPI - ) - { - *_out = _pushTag; - *_out = _jumpi; - return true; - } - else - return false; - } -}; - -struct JumpToNext: SimplePeepholeOptimizerMethod -{ - static size_t applySimple( - AssemblyItem const& _pushTag, - AssemblyItem const& _jump, - AssemblyItem const& _tag, - std::back_insert_iterator _out - ) - { - if ( - _pushTag.type() == PushTag && - (_jump == Instruction::JUMP || _jump == Instruction::JUMPI) && - _tag.type() == Tag && - _pushTag.data() == _tag.data() - ) - { - if (_jump == Instruction::JUMPI) - *_out = AssemblyItem(Instruction::POP, _jump.location()); - *_out = _tag; - return true; - } - else - return false; - } -}; - -struct TagConjunctions: SimplePeepholeOptimizerMethod -{ - static bool applySimple( - AssemblyItem const& _pushTag, - AssemblyItem const& _pushConstant, - AssemblyItem const& _and, - std::back_insert_iterator _out - ) - { - if ( - _pushTag.type() == PushTag && - _and == Instruction::AND && - _pushConstant.type() == Push && - (_pushConstant.data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF) - ) - { - *_out = _pushTag; - return true; - } - else - return false; - } -}; - -struct TruthyAnd: SimplePeepholeOptimizerMethod -{ - static bool applySimple( - AssemblyItem const& _push, - AssemblyItem const& _not, - AssemblyItem const& _and, - std::back_insert_iterator - ) - { - return ( - _push.type() == Push && _push.data() == 0 && - _not == Instruction::NOT && - _and == Instruction::AND - ); - } -}; - -/// Removes everything after a JUMP (or similar) until the next JUMPDEST. -struct UnreachableCode -{ - static bool apply(OptimiserState& _state) - { - auto it = _state.items.begin() + _state.i; - auto end = _state.items.end(); - if (it == end) - return false; - if ( - it[0] != Instruction::JUMP && - it[0] != Instruction::RETURN && - it[0] != Instruction::STOP && - it[0] != Instruction::INVALID && - it[0] != Instruction::SELFDESTRUCT && - it[0] != Instruction::REVERT - ) - return false; - - size_t i = 1; - while (it + i != end && it[i].type() != Tag) - i++; - if (i > 1) - { - *_state.out = it[0]; - _state.i += i; - return true; - } - else - return false; - } -}; - -void applyMethods(OptimiserState&) -{ - assertThrow(false, OptimizerException, "Peephole optimizer failed to apply identity."); -} - -template -void applyMethods(OptimiserState& _state, Method, OtherMethods... _other) -{ - if (!Method::apply(_state)) - applyMethods(_state, _other...); -} - -size_t numberOfPops(AssemblyItems const& _items) -{ - return std::count(_items.begin(), _items.end(), Instruction::POP); -} - -} - -bool PeepholeOptimiser::optimise() -{ - OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; - while (state.i < m_items.size()) - applyMethods( - state, - PushPop(), OpPop(), DoublePush(), DoubleSwap(), CommutativeSwap(), SwapComparison(), - IsZeroIsZeroJumpI(), JumpToNext(), UnreachableCode(), - TagConjunctions(), TruthyAnd(), Identity() - ); - if (m_optimisedItems.size() < m_items.size() || ( - m_optimisedItems.size() == m_items.size() && ( - evmasm::bytesRequired(m_optimisedItems, 3) < evmasm::bytesRequired(m_items, 3) || - numberOfPops(m_optimisedItems) > numberOfPops(m_items) - ) - )) - { - m_items = std::move(m_optimisedItems); - return true; - } - else - return false; -} diff --git a/compiler/libevmasm/PeepholeOptimiser.h b/compiler/libevmasm/PeepholeOptimiser.h deleted file mode 100644 index a260d314..00000000 --- a/compiler/libevmasm/PeepholeOptimiser.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file PeepholeOptimiser.h - * Performs local optimising code changes to assembly. - */ -#pragma once - -#include -#include -#include - -namespace solidity::evmasm -{ -class AssemblyItem; -using AssemblyItems = std::vector; - -class PeepholeOptimisationMethod -{ -public: - virtual ~PeepholeOptimisationMethod() = default; - virtual size_t windowSize() const; - virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out); -}; - -class PeepholeOptimiser -{ -public: - explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} - virtual ~PeepholeOptimiser() = default; - - bool optimise(); - -private: - AssemblyItems& m_items; - AssemblyItems m_optimisedItems; -}; - -} diff --git a/compiler/libevmasm/RuleList.h b/compiler/libevmasm/RuleList.h deleted file mode 100644 index 812c6700..00000000 --- a/compiler/libevmasm/RuleList.h +++ /dev/null @@ -1,674 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @date 2018 - * Templatized list of simplification rules. - */ - -#pragma once - - -#include -#include - -#include - -#include - -#include -#include - -namespace solidity::evmasm -{ - -template S divWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) / bigint(_b)); -} - -template S modWorkaround(S const& _a, S const& _b) -{ - return (S)(bigint(_a) % bigint(_b)); -} - -// This works around a bug fixed with Boost 1.64. -// https://www.boost.org/doc/libs/1_68_0/libs/multiprecision/doc/html/boost_multiprecision/map/hist.html#boost_multiprecision.map.hist.multiprecision_2_3_1_boost_1_64 -template S shlWorkaround(S const& _x, unsigned _amount) -{ - return u256((bigint(_x) << _amount) & u256(-1)); -} - -// simplificationRuleList below was split up into parts to prevent -// stack overflows in the JavaScript optimizer for emscripten builds -// that affected certain browser versions. -template -std::vector> simplificationRuleListPart1( - Pattern A, - Pattern B, - Pattern C, - Pattern, - Pattern -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - return std::vector> { - // arithmetic on constants - {Builtins::ADD(A, B), [=]{ return A.d() + B.d(); }, false}, - {Builtins::MUL(A, B), [=]{ return A.d() * B.d(); }, false}, - {Builtins::SUB(A, B), [=]{ return A.d() - B.d(); }, false}, - {Builtins::DIV(A, B), [=]{ return B.d() == 0 ? 0 : divWorkaround(A.d(), B.d()); }, false}, - {Builtins::SDIV(A, B), [=]{ return B.d() == 0 ? 0 : s2u(divWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, - {Builtins::MOD(A, B), [=]{ return B.d() == 0 ? 0 : modWorkaround(A.d(), B.d()); }, false}, - {Builtins::SMOD(A, B), [=]{ return B.d() == 0 ? 0 : s2u(modWorkaround(u2s(A.d()), u2s(B.d()))); }, false}, - {Builtins::EXP(A, B), [=]{ return Word(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << Pattern::WordSize)); }, false}, - {Builtins::NOT(A), [=]{ return ~A.d(); }, false}, - {Builtins::LT(A, B), [=]() -> Word { return A.d() < B.d() ? 1 : 0; }, false}, - {Builtins::GT(A, B), [=]() -> Word { return A.d() > B.d() ? 1 : 0; }, false}, - {Builtins::SLT(A, B), [=]() -> Word { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }, false}, - {Builtins::SGT(A, B), [=]() -> Word { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }, false}, - {Builtins::EQ(A, B), [=]() -> Word { return A.d() == B.d() ? 1 : 0; }, false}, - {Builtins::ISZERO(A), [=]() -> Word { return A.d() == 0 ? 1 : 0; }, false}, - {Builtins::AND(A, B), [=]{ return A.d() & B.d(); }, false}, - {Builtins::OR(A, B), [=]{ return A.d() | B.d(); }, false}, - {Builtins::XOR(A, B), [=]{ return A.d() ^ B.d(); }, false}, - {Builtins::BYTE(A, B), [=]{ - return - A.d() >= Pattern::WordSize / 8 ? - 0 : - (B.d() >> unsigned(8 * (Pattern::WordSize / 8 - 1 - A.d()))) & 0xff; - }, false}, - {Builtins::ADDMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) + bigint(B.d())) % C.d()); }, false}, - {Builtins::MULMOD(A, B, C), [=]{ return C.d() == 0 ? 0 : Word((bigint(A.d()) * bigint(B.d())) % C.d()); }, false}, - {Builtins::SIGNEXTEND(A, B), [=]() -> Word { - if (A.d() >= Pattern::WordSize / 8 - 1) - return B.d(); - unsigned testBit = unsigned(A.d()) * 8 + 7; - Word mask = (Word(1) << testBit) - 1; - return boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask; - }, false}, - {Builtins::SHL(A, B), [=]{ - if (A.d() >= Pattern::WordSize) - return Word(0); - return shlWorkaround(B.d(), unsigned(A.d())); - }, false}, - {Builtins::SHR(A, B), [=]{ - if (A.d() >= Pattern::WordSize) - return Word(0); - return B.d() >> unsigned(A.d()); - }, false} - }; -} - - -template -std::vector> simplificationRuleListPart2( - Pattern, - Pattern, - Pattern, - Pattern X, - Pattern Y -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - return std::vector> { - // invariants involving known constants - {Builtins::ADD(X, 0), [=]{ return X; }, false}, - {Builtins::ADD(0, X), [=]{ return X; }, false}, - {Builtins::SUB(X, 0), [=]{ return X; }, false}, - {Builtins::SUB(~Word(0), X), [=]() -> Pattern { return Builtins::NOT(X); }, false}, - {Builtins::MUL(X, 0), [=]{ return Word(0); }, true}, - {Builtins::MUL(0, X), [=]{ return Word(0); }, true}, - {Builtins::MUL(X, 1), [=]{ return X; }, false}, - {Builtins::MUL(1, X), [=]{ return X; }, false}, - {Builtins::MUL(X, Word(-1)), [=]() -> Pattern { return Builtins::SUB(0, X); }, false}, - {Builtins::MUL(Word(-1), X), [=]() -> Pattern { return Builtins::SUB(0, X); }, false}, - {Builtins::DIV(X, 0), [=]{ return Word(0); }, true}, - {Builtins::DIV(0, X), [=]{ return Word(0); }, true}, - {Builtins::DIV(X, 1), [=]{ return X; }, false}, - {Builtins::SDIV(X, 0), [=]{ return Word(0); }, true}, - {Builtins::SDIV(0, X), [=]{ return Word(0); }, true}, - {Builtins::SDIV(X, 1), [=]{ return X; }, false}, - {Builtins::AND(X, ~Word(0)), [=]{ return X; }, false}, - {Builtins::AND(~Word(0), X), [=]{ return X; }, false}, - {Builtins::AND(X, 0), [=]{ return Word(0); }, true}, - {Builtins::AND(0, X), [=]{ return Word(0); }, true}, - {Builtins::OR(X, 0), [=]{ return X; }, false}, - {Builtins::OR(0, X), [=]{ return X; }, false}, - {Builtins::OR(X, ~Word(0)), [=]{ return ~Word(0); }, true}, - {Builtins::OR(~Word(0), X), [=]{ return ~Word(0); }, true}, - {Builtins::XOR(X, 0), [=]{ return X; }, false}, - {Builtins::XOR(0, X), [=]{ return X; }, false}, - {Builtins::MOD(X, 0), [=]{ return Word(0); }, true}, - {Builtins::MOD(0, X), [=]{ return Word(0); }, true}, - {Builtins::EQ(X, 0), [=]() -> Pattern { return Builtins::ISZERO(X); }, false }, - {Builtins::EQ(0, X), [=]() -> Pattern { return Builtins::ISZERO(X); }, false }, - {Builtins::SHL(0, X), [=]{ return X; }, false}, - {Builtins::SHR(0, X), [=]{ return X; }, false}, - {Builtins::SHL(X, 0), [=]{ return Word(0); }, true}, - {Builtins::SHR(X, 0), [=]{ return Word(0); }, true}, - {Builtins::GT(X, 0), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false}, - {Builtins::LT(0, X), [=]() -> Pattern { return Builtins::ISZERO(Builtins::ISZERO(X)); }, false}, - {Builtins::GT(X, ~Word(0)), [=]{ return Word(0); }, true}, - {Builtins::LT(~Word(0), X), [=]{ return Word(0); }, true}, - {Builtins::GT(0, X), [=]{ return Word(0); }, true}, - {Builtins::LT(X, 0), [=]{ return Word(0); }, true}, - {Builtins::AND(Builtins::BYTE(X, Y), Word(0xff)), [=]() -> Pattern { return Builtins::BYTE(X, Y); }, false}, - {Builtins::BYTE(Word(Pattern::WordSize / 8 - 1), X), [=]() -> Pattern { return Builtins::AND(X, Word(0xff)); }, false} - }; -} - -template -std::vector> simplificationRuleListPart3( - Pattern, - Pattern, - Pattern, - Pattern X, - Pattern -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - return std::vector> { - // operations involving an expression and itself - {Builtins::AND(X, X), [=]{ return X; }, true}, - {Builtins::OR(X, X), [=]{ return X; }, true}, - {Builtins::XOR(X, X), [=]{ return Word(0); }, true}, - {Builtins::SUB(X, X), [=]{ return Word(0); }, true}, - {Builtins::EQ(X, X), [=]{ return Word(1); }, true}, - {Builtins::LT(X, X), [=]{ return Word(0); }, true}, - {Builtins::SLT(X, X), [=]{ return Word(0); }, true}, - {Builtins::GT(X, X), [=]{ return Word(0); }, true}, - {Builtins::SGT(X, X), [=]{ return Word(0); }, true}, - {Builtins::MOD(X, X), [=]{ return Word(0); }, true} - }; -} - -template -std::vector> simplificationRuleListPart4( - Pattern, - Pattern, - Pattern, - Pattern X, - Pattern Y -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - return std::vector> { - // logical instruction combinations - {Builtins::NOT(Builtins::NOT(X)), [=]{ return X; }, false}, - {Builtins::XOR(X, Builtins::XOR(X, Y)), [=]{ return Y; }, true}, - {Builtins::XOR(X, Builtins::XOR(Y, X)), [=]{ return Y; }, true}, - {Builtins::XOR(Builtins::XOR(X, Y), X), [=]{ return Y; }, true}, - {Builtins::XOR(Builtins::XOR(Y, X), X), [=]{ return Y; }, true}, - {Builtins::OR(X, Builtins::AND(X, Y)), [=]{ return X; }, true}, - {Builtins::OR(X, Builtins::AND(Y, X)), [=]{ return X; }, true}, - {Builtins::OR(Builtins::AND(X, Y), X), [=]{ return X; }, true}, - {Builtins::OR(Builtins::AND(Y, X), X), [=]{ return X; }, true}, - {Builtins::AND(X, Builtins::OR(X, Y)), [=]{ return X; }, true}, - {Builtins::AND(X, Builtins::OR(Y, X)), [=]{ return X; }, true}, - {Builtins::AND(Builtins::OR(X, Y), X), [=]{ return X; }, true}, - {Builtins::AND(Builtins::OR(Y, X), X), [=]{ return X; }, true}, - {Builtins::AND(X, Builtins::NOT(X)), [=]{ return Word(0); }, true}, - {Builtins::AND(Builtins::NOT(X), X), [=]{ return Word(0); }, true}, - {Builtins::OR(X, Builtins::NOT(X)), [=]{ return ~Word(0); }, true}, - {Builtins::OR(Builtins::NOT(X), X), [=]{ return ~Word(0); }, true}, - }; -} - - -template -std::vector> simplificationRuleListPart5( - Pattern A, - Pattern, - Pattern, - Pattern X, - Pattern -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - - std::vector> rules; - - // Replace MOD X, with AND X, - 1 - for (size_t i = 0; i < Pattern::WordSize; ++i) - { - Word value = Word(1) << i; - rules.push_back({ - Builtins::MOD(X, value), - [=]() -> Pattern { return Builtins::AND(X, value - 1); }, - false - }); - } - - // Replace SHL >=256, X with 0 - rules.push_back({ - Builtins::SHL(A, X), - [=]() -> Pattern { return Word(0); }, - true, - [=]() { return A.d() >= Pattern::WordSize; } - }); - - // Replace SHR >=256, X with 0 - rules.push_back({ - Builtins::SHR(A, X), - [=]() -> Pattern { return Word(0); }, - true, - [=]() { return A.d() >= Pattern::WordSize; } - }); - - // Replace BYTE(A, X), A >= 32 with 0 - rules.push_back({ - Builtins::BYTE(A, X), - [=]() -> Pattern { return Word(0); }, - true, - [=]() { return A.d() >= Pattern::WordSize / 8; } - }); - - for (auto instr: { - Instruction::ADDRESS, - Instruction::CALLER, - Instruction::ORIGIN, - Instruction::COINBASE - }) - { - assertThrow(Pattern::WordSize > 160, OptimizerException, ""); - Word const mask = (Word(1) << 160) - 1; - rules.push_back({ - Builtins::AND(Pattern{instr}, mask), - [=]() -> Pattern { return {instr}; }, - false - }); - rules.push_back({ - Builtins::AND(mask, Pattern{instr}), - [=]() -> Pattern { return {instr}; }, - false - }); - } - - return rules; -} - -template -std::vector> simplificationRuleListPart6( - Pattern, - Pattern, - Pattern, - Pattern X, - Pattern Y -) -{ - using Builtins = typename Pattern::Builtins; - - std::vector> rules; - // Double negation of opcodes with boolean result - for (auto instr: { - Instruction::EQ, - Instruction::LT, - Instruction::SLT, - Instruction::GT, - Instruction::SGT - }) - { - typename Builtins::PatternGeneratorInstance op{instr}; - rules.push_back({ - Builtins::ISZERO(Builtins::ISZERO(op(X, Y))), - [=]() -> Pattern { return op(X, Y); }, - false - }); - } - - rules.push_back({ - Builtins::ISZERO(Builtins::ISZERO(Builtins::ISZERO(X))), - [=]() -> Pattern { return Builtins::ISZERO(X); }, - false - }); - - rules.push_back({ - Builtins::ISZERO(Builtins::XOR(X, Y)), - [=]() -> Pattern { return Builtins::EQ(X, Y); }, - false - }); - - return rules; -} - -template -std::vector> simplificationRuleListPart7( - Pattern A, - Pattern B, - Pattern, - Pattern X, - Pattern Y -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - - std::vector> rules; - // Associative operations - for (auto&& instrAndFunc: std::vector>>{ - {Instruction::ADD, std::plus()}, - {Instruction::MUL, std::multiplies()}, - {Instruction::AND, std::bit_and()}, - {Instruction::OR, std::bit_or()}, - {Instruction::XOR, std::bit_xor()} - }) - { - typename Builtins::PatternGeneratorInstance op{instrAndFunc.first}; - std::function fun = instrAndFunc.second; - // Moving constants to the outside, order matters here - we first add rules - // for constants and then for non-constants. - // xa can be (X, A) or (A, X) - for (auto const& opXA: {op(X, A), op(A, X)}) - { - rules += std::vector>{{ - // (X+A)+B -> X+(A+B) - op(opXA, B), - [=]() -> Pattern { return op(X, fun(A.d(), B.d())); }, - false - }, { - // (X+A)+Y -> (X+Y)+A - op(opXA, Y), - [=]() -> Pattern { return op(op(X, Y), A); }, - false - }, { - // B+(X+A) -> X+(A+B) - op(B, opXA), - [=]() -> Pattern { return op(X, fun(A.d(), B.d())); }, - false - }, { - // Y+(X+A) -> (Y+X)+A - op(Y, opXA), - [=]() -> Pattern { return op(op(Y, X), A); }, - false - }}; - } - } - - // Combine two SHL by constant - rules.push_back({ - // SHL(B, SHL(A, X)) -> SHL(min(A+B, 256), X) - Builtins::SHL(B, Builtins::SHL(A, X)), - [=]() -> Pattern { - bigint sum = bigint(A.d()) + B.d(); - if (sum >= Pattern::WordSize) - return Builtins::AND(X, Word(0)); - else - return Builtins::SHL(Word(sum), X); - }, - false - }); - - // Combine two SHR by constant - rules.push_back({ - // SHR(B, SHR(A, X)) -> SHR(min(A+B, 256), X) - Builtins::SHR(B, Builtins::SHR(A, X)), - [=]() -> Pattern { - bigint sum = bigint(A.d()) + B.d(); - if (sum >= Pattern::WordSize) - return Builtins::AND(X, Word(0)); - else - return Builtins::SHR(Word(sum), X); - }, - false - }); - - // Combine SHL-SHR by constant - rules.push_back({ - // SHR(B, SHL(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) - Builtins::SHR(B, Builtins::SHL(A, X)), - [=]() -> Pattern { - Word mask = shlWorkaround(~Word(0), unsigned(A.d())) >> unsigned(B.d()); - - if (A.d() > B.d()) - return Builtins::AND(Builtins::SHL(A.d() - B.d(), X), mask); - else if (B.d() > A.d()) - return Builtins::AND(Builtins::SHR(B.d() - A.d(), X), mask); - else - return Builtins::AND(X, mask); - }, - false, - [=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; } - }); - - // Combine SHR-SHL by constant - rules.push_back({ - // SHL(B, SHR(A, X)) -> AND(SH[L/R]([B - A / A - B], X), Mask) - Builtins::SHL(B, Builtins::SHR(A, X)), - [=]() -> Pattern { - Word mask = shlWorkaround((~Word(0)) >> unsigned(A.d()), unsigned(B.d())); - - if (A.d() > B.d()) - return Builtins::AND(Builtins::SHR(A.d() - B.d(), X), mask); - else if (B.d() > A.d()) - return Builtins::AND(Builtins::SHL(B.d() - A.d(), X), mask); - else - return Builtins::AND(X, mask); - }, - false, - [=] { return A.d() < Pattern::WordSize && B.d() < Pattern::WordSize; } - }); - - // Move AND with constant across SHL and SHR by constant - for (auto instr: {Instruction::SHL, Instruction::SHR}) - { - typename Builtins::PatternGeneratorInstance shiftOp{instr}; - auto replacement = [=]() -> Pattern { - Word mask = - instr == Instruction::SHL ? - shlWorkaround(A.d(), unsigned(B.d())) : - A.d() >> unsigned(B.d()); - return Builtins::AND(shiftOp(B.d(), X), std::move(mask)); - }; - rules.push_back({ - // SH[L/R](B, AND(X, A)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) - shiftOp(B, Builtins::AND(X, A)), - replacement, - false, - [=] { return B.d() < Pattern::WordSize; } - }); - rules.push_back({ - // SH[L/R](B, AND(A, X)) -> AND(SH[L/R](B, X), [ A << B / A >> B ]) - shiftOp(B, Builtins::AND(A, X)), - replacement, - false, - [=] { return B.d() < Pattern::WordSize; } - }); - } - - rules.push_back({ - // MUL(X, SHL(Y, 1)) -> SHL(Y, X) - Builtins::MUL(X, Builtins::SHL(Y, Word(1))), - [=]() -> Pattern { - return Builtins::SHL(Y, X); - }, - // Actually only changes the order, does not remove. - true - }); - rules.push_back({ - // MUL(SHL(X, 1), Y) -> SHL(X, Y) - Builtins::MUL(Builtins::SHL(X, Word(1)), Y), - [=]() -> Pattern { - return Builtins::SHL(X, Y); - }, - false - }); - - rules.push_back({ - // DIV(X, SHL(Y, 1)) -> SHR(Y, X) - Builtins::DIV(X, Builtins::SHL(Y, Word(1))), - [=]() -> Pattern { - return Builtins::SHR(Y, X); - }, - // Actually only changes the order, does not remove. - true - }); - - std::function feasibilityFunction = [=]() { - if (B.d() > Pattern::WordSize) - return false; - unsigned bAsUint = static_cast(B.d()); - return (A.d() & ((~Word(0)) >> bAsUint)) == ((~Word(0)) >> bAsUint); - }; - - rules.push_back({ - // AND(A, SHR(B, X)) -> A & ((2^256-1) >> B) == ((2^256-1) >> B) - Builtins::AND(A, Builtins::SHR(B, X)), - [=]() -> Pattern { return Builtins::SHR(B, X); }, - false, - feasibilityFunction - }); - - rules.push_back({ - // AND(SHR(B, X), A) -> ((2^256-1) >> B) & A == ((2^256-1) >> B) - Builtins::AND(Builtins::SHR(B, X), A), - [=]() -> Pattern { return Builtins::SHR(B, X); }, - false, - feasibilityFunction - }); - - return rules; -} - -template -std::vector> simplificationRuleListPart8( - Pattern A, - Pattern, - Pattern, - Pattern X, - Pattern Y -) -{ - using Builtins = typename Pattern::Builtins; - std::vector> rules; - - // move constants across subtractions - rules += std::vector>{ - { - // X - A -> X + (-A) - Builtins::SUB(X, A), - [=]() -> Pattern { return Builtins::ADD(X, 0 - A.d()); }, - false - }, { - // (X + A) - Y -> (X - Y) + A - Builtins::SUB(Builtins::ADD(X, A), Y), - [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }, - false - }, { - // (A + X) - Y -> (X - Y) + A - Builtins::SUB(Builtins::ADD(A, X), Y), - [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), A); }, - false - }, { - // X - (Y + A) -> (X - Y) + (-A) - Builtins::SUB(X, Builtins::ADD(Y, A)), - [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }, - false - }, { - // X - (A + Y) -> (X - Y) + (-A) - Builtins::SUB(X, Builtins::ADD(A, Y)), - [=]() -> Pattern { return Builtins::ADD(Builtins::SUB(X, Y), 0 - A.d()); }, - false - } - }; - return rules; -} - -template -std::vector> simplificationRuleListPart9( - Pattern, - Pattern, - Pattern, - Pattern W, - Pattern X, - Pattern Y, - Pattern Z -) -{ - using Word = typename Pattern::Word; - using Builtins = typename Pattern::Builtins; - std::vector> rules; - - assertThrow(Pattern::WordSize > 160, OptimizerException, ""); - Word const mask = (Word(1) << 160) - 1; - // CREATE - rules.push_back({ - Builtins::AND(Builtins::CREATE(W, X, Y), mask), - [=]() -> Pattern { return Builtins::CREATE(W, X, Y); }, - false - }); - rules.push_back({ - Builtins::AND(mask, Builtins::CREATE(W, X, Y)), - [=]() -> Pattern { return Builtins::CREATE(W, X, Y); }, - false - }); - // CREATE2 - rules.push_back({ - Builtins::AND(Builtins::CREATE2(W, X, Y, Z), mask), - [=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); }, - false - }); - rules.push_back({ - Builtins::AND(mask, Builtins::CREATE2(W, X, Y, Z)), - [=]() -> Pattern { return Builtins::CREATE2(W, X, Y, Z); }, - false - }); - - return rules; -} - -/// @returns a list of simplification rules given certain match placeholders. -/// A, B and C should represent constants, W, X, Y, and Z arbitrary expressions. -/// The simplifications should never change the order of evaluation of -/// arbitrary operations. -template -std::vector> simplificationRuleList( - Pattern A, - Pattern B, - Pattern C, - Pattern W, - Pattern X, - Pattern Y, - Pattern Z -) -{ - using Word = typename Pattern::Word; - // Some sanity checks - assertThrow(Pattern::WordSize % 8 == 0, OptimizerException, ""); - assertThrow(Pattern::WordSize >= 8, OptimizerException, ""); - assertThrow(Pattern::WordSize <= 256, OptimizerException, ""); - assertThrow(Word(-1) == ~Word(0), OptimizerException, ""); - assertThrow(Word(-1) + 1 == Word(0), OptimizerException, ""); - - std::vector> rules; - rules += simplificationRuleListPart1(A, B, C, W, X); - rules += simplificationRuleListPart2(A, B, C, W, X); - rules += simplificationRuleListPart3(A, B, C, W, X); - rules += simplificationRuleListPart4(A, B, C, W, X); - rules += simplificationRuleListPart5(A, B, C, W, X); - rules += simplificationRuleListPart6(A, B, C, W, X); - rules += simplificationRuleListPart7(A, B, C, W, X); - rules += simplificationRuleListPart8(A, B, C, W, X); - rules += simplificationRuleListPart9(A, B, C, W, X, Y, Z); - return rules; -} - -} diff --git a/compiler/libevmasm/SemanticInformation.cpp b/compiler/libevmasm/SemanticInformation.cpp deleted file mode 100644 index b7fa069d..00000000 --- a/compiler/libevmasm/SemanticInformation.cpp +++ /dev/null @@ -1,318 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file SemanticInformation.cpp - * @author Christian - * @date 2015 - * Helper to provide semantic information about assembly items. - */ - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; - -bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant) -{ - switch (_item.type()) - { - default: - case UndefinedItem: - case Tag: - case PushDeployTimeAddress: - return true; - case Push: - case PushString: - case PushTag: - case PushSub: - case PushSubSize: - case PushProgramSize: - case PushData: - case PushLibraryAddress: - return false; - case Operation: - { - if (isSwapInstruction(_item) || isDupInstruction(_item)) - return false; - if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) - return true; // GAS and PC assume a specific order of opcodes - if (_item.instruction() == Instruction::MSIZE) - return true; // msize is modified already by memory access, avoid that for now - InstructionInfo info = instructionInfo(_item.instruction()); - if (_item.instruction() == Instruction::SSTORE) - return false; - if (_item.instruction() == Instruction::MSTORE) - return false; - if (!_msizeImportant && ( - _item.instruction() == Instruction::MLOAD || - _item.instruction() == Instruction::KECCAK256 - )) - return false; - //@todo: We do not handle the following memory instructions for now: - // calldatacopy, codecopy, extcodecopy, mstore8, - // msize (note that msize also depends on memory read access) - - // the second requirement will be lifted once it is implemented - return info.sideEffects || info.args > 2; - } - } -} - -bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return false; - switch (_item.instruction()) - { - case Instruction::ADD: - case Instruction::MUL: - case Instruction::EQ: - case Instruction::AND: - case Instruction::OR: - case Instruction::XOR: - return true; - default: - return false; - } -} - -bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return false; - return evmasm::isDupInstruction(_item.instruction()); -} - -bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return false; - return evmasm::isSwapInstruction(_item.instruction()); -} - -bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) -{ - return _item == Instruction::JUMP || _item == Instruction::JUMPI; -} - -bool SemanticInformation::altersControlFlow(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return false; - switch (_item.instruction()) - { - // note that CALL, CALLCODE and CREATE do not really alter the control flow, because we - // continue on the next instruction - case Instruction::JUMP: - case Instruction::JUMPI: - case Instruction::RETURN: - case Instruction::SELFDESTRUCT: - case Instruction::STOP: - case Instruction::INVALID: - case Instruction::REVERT: - return true; - default: - return false; - } -} - -bool SemanticInformation::terminatesControlFlow(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return false; - else - return terminatesControlFlow(_item.instruction()); -} - -bool SemanticInformation::terminatesControlFlow(Instruction _instruction) -{ - switch (_instruction) - { - case Instruction::RETURN: - case Instruction::SELFDESTRUCT: - case Instruction::STOP: - case Instruction::INVALID: - case Instruction::REVERT: - return true; - default: - return false; - } -} - -bool SemanticInformation::isDeterministic(AssemblyItem const& _item) -{ - if (_item.type() != Operation) - return true; - - switch (_item.instruction()) - { - case Instruction::CALL: - case Instruction::CALLCODE: - case Instruction::DELEGATECALL: - case Instruction::STATICCALL: - case Instruction::CREATE: - case Instruction::CREATE2: - case Instruction::GAS: - case Instruction::PC: - case Instruction::MSIZE: // depends on previous writes and reads, not only on content - case Instruction::BALANCE: // depends on previous calls - case Instruction::SELFBALANCE: // depends on previous calls - case Instruction::EXTCODESIZE: - case Instruction::EXTCODEHASH: - case Instruction::RETURNDATACOPY: // depends on previous calls - case Instruction::RETURNDATASIZE: - return false; - default: - return true; - } -} - -bool SemanticInformation::movable(Instruction _instruction) -{ - // These are not really functional. - if (isDupInstruction(_instruction) || isSwapInstruction(_instruction)) - return false; - InstructionInfo info = instructionInfo(_instruction); - if (info.sideEffects) - return false; - switch (_instruction) - { - case Instruction::KECCAK256: - case Instruction::BALANCE: - case Instruction::SELFBALANCE: - case Instruction::EXTCODESIZE: - case Instruction::EXTCODEHASH: - case Instruction::RETURNDATASIZE: - case Instruction::SLOAD: - case Instruction::PC: - case Instruction::MSIZE: - case Instruction::GAS: - return false; - default: - return true; - } - return true; -} - -bool SemanticInformation::sideEffectFree(Instruction _instruction) -{ - // These are not really functional. - assertThrow(!isDupInstruction(_instruction) && !isSwapInstruction(_instruction), AssemblyException, ""); - - return !instructionInfo(_instruction).sideEffects; -} - -bool SemanticInformation::sideEffectFreeIfNoMSize(Instruction _instruction) -{ - if (_instruction == Instruction::KECCAK256 || _instruction == Instruction::MLOAD) - return true; - else - return sideEffectFree(_instruction); -} - -bool SemanticInformation::invalidatesMemory(Instruction _instruction) -{ - switch (_instruction) - { - case Instruction::CALLDATACOPY: - case Instruction::CODECOPY: - case Instruction::EXTCODECOPY: - case Instruction::RETURNDATACOPY: - case Instruction::MSTORE: - case Instruction::MSTORE8: - case Instruction::CALL: - case Instruction::CALLCODE: - case Instruction::DELEGATECALL: - case Instruction::STATICCALL: - return true; - default: - return false; - } -} - -bool SemanticInformation::invalidatesStorage(Instruction _instruction) -{ - switch (_instruction) - { - case Instruction::CALL: - case Instruction::CALLCODE: - case Instruction::DELEGATECALL: - case Instruction::CREATE: - case Instruction::CREATE2: - case Instruction::SSTORE: - return true; - default: - return false; - } -} - -bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) -{ - switch (_instruction) - { - case Instruction::ADDRESS: - case Instruction::SELFBALANCE: - case Instruction::BALANCE: - case Instruction::ORIGIN: - case Instruction::CALLER: - case Instruction::CALLVALUE: - case Instruction::GAS: - case Instruction::GASPRICE: - case Instruction::EXTCODESIZE: - case Instruction::EXTCODECOPY: - case Instruction::EXTCODEHASH: - case Instruction::BLOCKHASH: - case Instruction::COINBASE: - case Instruction::TIMESTAMP: - case Instruction::NUMBER: - case Instruction::DIFFICULTY: - case Instruction::GASLIMIT: - case Instruction::STATICCALL: - case Instruction::SLOAD: - return true; - default: - break; - } - return invalidInViewFunctions(_instruction); -} - -bool SemanticInformation::invalidInViewFunctions(Instruction _instruction) -{ - switch (_instruction) - { - case Instruction::SSTORE: - case Instruction::JUMP: - case Instruction::JUMPI: - case Instruction::LOG0: - case Instruction::LOG1: - case Instruction::LOG2: - case Instruction::LOG3: - case Instruction::LOG4: - case Instruction::CREATE: - case Instruction::CALL: - case Instruction::CALLCODE: - case Instruction::DELEGATECALL: - case Instruction::CREATE2: - case Instruction::SELFDESTRUCT: - return true; - default: - break; - } - return false; -} diff --git a/compiler/libevmasm/SemanticInformation.h b/compiler/libevmasm/SemanticInformation.h deleted file mode 100644 index f08c3d73..00000000 --- a/compiler/libevmasm/SemanticInformation.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file SemanticInformation.h - * @author Christian - * @date 2015 - * Helper to provide semantic information about assembly items. - */ - -#pragma once - -#include - -namespace solidity::evmasm -{ - -class AssemblyItem; - -/** - * Helper functions to provide context-independent information about assembly items. - */ -struct SemanticInformation -{ - /// @returns true if the given items starts a new block for common subexpression analysis. - /// @param _msizeImportant if false, consider an operation non-breaking if its only side-effect is that it modifies msize. - static bool breaksCSEAnalysisBlock(AssemblyItem const& _item, bool _msizeImportant); - /// @returns true if the item is a two-argument operation whose value does not depend on the - /// order of its arguments. - static bool isCommutativeOperation(AssemblyItem const& _item); - static bool isDupInstruction(AssemblyItem const& _item); - static bool isSwapInstruction(AssemblyItem const& _item); - static bool isJumpInstruction(AssemblyItem const& _item); - static bool altersControlFlow(AssemblyItem const& _item); - static bool terminatesControlFlow(AssemblyItem const& _item); - static bool terminatesControlFlow(Instruction _instruction); - /// @returns false if the value put on the stack by _item depends on anything else than - /// the information in the current block header, memory, storage or stack. - static bool isDeterministic(AssemblyItem const& _item); - /// @returns true if the instruction can be moved or copied (together with its arguments) - /// without altering the semantics. This means it cannot depend on storage or memory, - /// cannot have any side-effects, but it can depend on a call-constant state of the blockchain. - static bool movable(Instruction _instruction); - /// @returns true if the instruction can be removed without changing the semantics. - /// This does not mean that it has to be deterministic or retrieve information from - /// somewhere else than purely the values of its arguments. - static bool sideEffectFree(Instruction _instruction); - /// @returns true if the instruction can be removed without changing the semantics. - /// This does not mean that it has to be deterministic or retrieve information from - /// somewhere else than purely the values of its arguments. - /// If true, the instruction is still allowed to influence the value returned by the - /// msize instruction. - static bool sideEffectFreeIfNoMSize(Instruction _instruction); - /// @returns true if the given instruction modifies memory. - static bool invalidatesMemory(Instruction _instruction); - /// @returns true if the given instruction modifies storage (even indirectly). - static bool invalidatesStorage(Instruction _instruction); - static bool invalidInPureFunctions(Instruction _instruction); - static bool invalidInViewFunctions(Instruction _instruction); -}; - -} diff --git a/compiler/libevmasm/SimplificationRule.h b/compiler/libevmasm/SimplificationRule.h deleted file mode 100644 index 1fa1d8ea..00000000 --- a/compiler/libevmasm/SimplificationRule.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * Expression simplification pattern. - */ - -#pragma once - -#include -#include -#include - -namespace solidity::evmasm -{ - -/** - * Rule that contains a pattern, an action that can be applied - * after the pattern has matched and a bool that indicates - * whether the action would remove something from the expression - * than is not a constant literal. - */ -template -struct SimplificationRule -{ - SimplificationRule( - Pattern _pattern, - std::function _action, - bool _removesNonConstants, - std::function _feasible = {} - ): - pattern(std::move(_pattern)), - action(std::move(_action)), - removesNonConstants(_removesNonConstants), - feasible(std::move(_feasible)) - {} - - Pattern pattern; - std::function action; - bool removesNonConstants; - std::function feasible; -}; - -template -struct EVMBuiltins -{ - using InstrType = Instruction; - - template - struct PatternGenerator - { - template constexpr Pattern operator()(Args&&... _args) const - { - return {inst, {std::forward(_args)...}}; - } - }; - - struct PatternGeneratorInstance - { - Instruction instruction; - template constexpr Pattern operator()(Args&&... _args) const - { - return {instruction, {std::forward(_args)...}}; - } - }; - - - static auto constexpr STOP = PatternGenerator{}; - static auto constexpr ADD = PatternGenerator{}; - static auto constexpr SUB = PatternGenerator{}; - static auto constexpr MUL = PatternGenerator{}; - static auto constexpr DIV = PatternGenerator{}; - static auto constexpr SDIV = PatternGenerator{}; - static auto constexpr MOD = PatternGenerator{}; - static auto constexpr SMOD = PatternGenerator{}; - static auto constexpr EXP = PatternGenerator{}; - static auto constexpr NOT = PatternGenerator{}; - static auto constexpr LT = PatternGenerator{}; - static auto constexpr GT = PatternGenerator{}; - static auto constexpr SLT = PatternGenerator{}; - static auto constexpr SGT = PatternGenerator{}; - static auto constexpr EQ = PatternGenerator{}; - static auto constexpr ISZERO = PatternGenerator{}; - static auto constexpr AND = PatternGenerator{}; - static auto constexpr OR = PatternGenerator{}; - static auto constexpr XOR = PatternGenerator{}; - static auto constexpr BYTE = PatternGenerator{}; - static auto constexpr SHL = PatternGenerator{}; - static auto constexpr SHR = PatternGenerator{}; - static auto constexpr SAR = PatternGenerator{}; - static auto constexpr ADDMOD = PatternGenerator{}; - static auto constexpr MULMOD = PatternGenerator{}; - static auto constexpr SIGNEXTEND = PatternGenerator{}; - static auto constexpr KECCAK256 = PatternGenerator{}; - static auto constexpr ADDRESS = PatternGenerator{}; - static auto constexpr BALANCE = PatternGenerator{}; - static auto constexpr ORIGIN = PatternGenerator{}; - static auto constexpr CALLER = PatternGenerator{}; - static auto constexpr CALLVALUE = PatternGenerator{}; - static auto constexpr CALLDATALOAD = PatternGenerator{}; - static auto constexpr CALLDATASIZE = PatternGenerator{}; - static auto constexpr CALLDATACOPY = PatternGenerator{}; - static auto constexpr CODESIZE = PatternGenerator{}; - static auto constexpr CODECOPY = PatternGenerator{}; - static auto constexpr GASPRICE = PatternGenerator{}; - static auto constexpr EXTCODESIZE = PatternGenerator{}; - static auto constexpr EXTCODECOPY = PatternGenerator{}; - static auto constexpr RETURNDATASIZE = PatternGenerator{}; - static auto constexpr RETURNDATACOPY = PatternGenerator{}; - static auto constexpr EXTCODEHASH = PatternGenerator{}; - static auto constexpr BLOCKHASH = PatternGenerator{}; - static auto constexpr COINBASE = PatternGenerator{}; - static auto constexpr TIMESTAMP = PatternGenerator{}; - static auto constexpr NUMBER = PatternGenerator{}; - static auto constexpr DIFFICULTY = PatternGenerator{}; - static auto constexpr GASLIMIT = PatternGenerator{}; - static auto constexpr CHAINID = PatternGenerator{}; - static auto constexpr SELFBALANCE = PatternGenerator{}; - static auto constexpr POP = PatternGenerator{}; - static auto constexpr MLOAD = PatternGenerator{}; - static auto constexpr MSTORE = PatternGenerator{}; - static auto constexpr MSTORE8 = PatternGenerator{}; - static auto constexpr SLOAD = PatternGenerator{}; - static auto constexpr SSTORE = PatternGenerator{}; - static auto constexpr PC = PatternGenerator{}; - static auto constexpr MSIZE = PatternGenerator{}; - static auto constexpr GAS = PatternGenerator{}; - static auto constexpr LOG0 = PatternGenerator{}; - static auto constexpr LOG1 = PatternGenerator{}; - static auto constexpr LOG2 = PatternGenerator{}; - static auto constexpr LOG3 = PatternGenerator{}; - static auto constexpr LOG4 = PatternGenerator{}; - static auto constexpr CREATE = PatternGenerator{}; - static auto constexpr CALL = PatternGenerator{}; - static auto constexpr CALLCODE = PatternGenerator{}; - static auto constexpr STATICCALL = PatternGenerator{}; - static auto constexpr RETURN = PatternGenerator{}; - static auto constexpr DELEGATECALL = PatternGenerator{}; - static auto constexpr CREATE2 = PatternGenerator{}; - static auto constexpr REVERT = PatternGenerator{}; - static auto constexpr INVALID = PatternGenerator{}; - static auto constexpr SELFDESTRUCT = PatternGenerator{}; -}; - -} diff --git a/compiler/libevmasm/SimplificationRules.cpp b/compiler/libevmasm/SimplificationRules.cpp deleted file mode 100644 index 751153b0..00000000 --- a/compiler/libevmasm/SimplificationRules.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file ExpressionClasses.cpp - * @author Christian - * @date 2015 - * Container for equivalence classes of expressions for use in common subexpression elimination. - */ - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::langutil; - -SimplificationRule const* Rules::findFirstMatch( - Expression const& _expr, - ExpressionClasses const& _classes -) -{ - resetMatchGroups(); - - assertThrow(_expr.item, OptimizerException, ""); - for (auto const& rule: m_rules[uint8_t(_expr.item->instruction())]) - { - if (rule.pattern.matches(_expr, _classes)) - if (!rule.feasible || rule.feasible()) - return &rule; - - resetMatchGroups(); - } - return nullptr; -} - -bool Rules::isInitialized() const -{ - return !m_rules[uint8_t(Instruction::ADD)].empty(); -} - -void Rules::addRules(std::vector> const& _rules) -{ - for (auto const& r: _rules) - addRule(r); -} - -void Rules::addRule(SimplificationRule const& _rule) -{ - m_rules[uint8_t(_rule.pattern.instruction())].push_back(_rule); -} - -Rules::Rules() -{ - // Multiple occurrences of one of these inside one rule must match the same equivalence class. - // Constants. - Pattern A(Push); - Pattern B(Push); - Pattern C(Push); - // Anything. - Pattern W; - Pattern X; - Pattern Y; - Pattern Z; - A.setMatchGroup(1, m_matchGroups); - B.setMatchGroup(2, m_matchGroups); - C.setMatchGroup(3, m_matchGroups); - W.setMatchGroup(4, m_matchGroups); - X.setMatchGroup(5, m_matchGroups); - Y.setMatchGroup(6, m_matchGroups); - Z.setMatchGroup(7, m_matchGroups); - - addRules(simplificationRuleList(A, B, C, W, X, Y, Z)); - assertThrow(isInitialized(), OptimizerException, "Rule list not properly initialized."); -} - -Pattern::Pattern(Instruction _instruction, std::initializer_list _arguments): - m_type(Operation), - m_instruction(_instruction), - m_arguments(_arguments) -{ -} - -void Pattern::setMatchGroup(unsigned _group, map& _matchGroups) -{ - m_matchGroup = _group; - m_matchGroups = &_matchGroups; -} - -bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const -{ - if (!matchesBaseItem(_expr.item)) - return false; - if (m_matchGroup) - { - if (!m_matchGroups->count(m_matchGroup)) - (*m_matchGroups)[m_matchGroup] = &_expr; - else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) - return false; - } - assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); - for (size_t i = 0; i < m_arguments.size(); ++i) - if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) - return false; - return true; -} - -AssemblyItem Pattern::toAssemblyItem(SourceLocation const& _location) const -{ - if (m_type == Operation) - return AssemblyItem(m_instruction, _location); - else - return AssemblyItem(m_type, data(), _location); -} - -string Pattern::toString() const -{ - stringstream s; - switch (m_type) - { - case Operation: - s << instructionInfo(m_instruction).name; - break; - case Push: - if (m_data) - s << "PUSH " << hex << data(); - else - s << "PUSH "; - break; - case UndefinedItem: - s << "ANY"; - break; - default: - if (m_data) - s << "t=" << dec << m_type << " d=" << hex << data(); - else - s << "t=" << dec << m_type << " d: nullptr"; - break; - } - if (!m_requireDataMatch) - s << " ~"; - if (m_matchGroup) - s << "[" << dec << m_matchGroup << "]"; - s << "("; - for (Pattern const& p: m_arguments) - s << p.toString() << ", "; - s << ")"; - return s.str(); -} - -bool Pattern::matchesBaseItem(AssemblyItem const* _item) const -{ - if (m_type == UndefinedItem) - return true; - if (!_item) - return false; - if (m_type != _item->type()) - return false; - else if (m_type == Operation) - return m_instruction == _item->instruction(); - else if (m_requireDataMatch) - return data() == _item->data(); - return true; -} - -Pattern::Expression const& Pattern::matchGroupValue() const -{ - assertThrow(m_matchGroup > 0, OptimizerException, ""); - assertThrow(!!m_matchGroups, OptimizerException, ""); - assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); - return *(*m_matchGroups)[m_matchGroup]; -} - -u256 const& Pattern::data() const -{ - assertThrow(m_data, OptimizerException, ""); - return *m_data; -} - -ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern, SourceLocation const& _location) -{ - if (_pattern.matchGroup()) - { - hasId = true; - id = _pattern.id(); - } - else - { - hasId = false; - item = _pattern.toAssemblyItem(_location); - } - for (auto const& arg: _pattern.arguments()) - arguments.emplace_back(arg, _location); -} - -string ExpressionTemplate::toString() const -{ - stringstream s; - if (hasId) - s << id; - else - s << item; - s << "("; - for (auto const& arg: arguments) - s << arg.toString(); - s << ")"; - return s.str(); -} diff --git a/compiler/libevmasm/SimplificationRules.h b/compiler/libevmasm/SimplificationRules.h deleted file mode 100644 index 72884d32..00000000 --- a/compiler/libevmasm/SimplificationRules.h +++ /dev/null @@ -1,159 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @file SimplificationRules - * @author Christian - * @date 2017 - * Module for applying replacement rules against Expressions. - */ - -#pragma once - -#include -#include - -#include - -#include - -#include -#include - -namespace solidity::langutil -{ -struct SourceLocation; -} - -namespace solidity::evmasm -{ - -class Pattern; - -/** - * Container for all simplification rules. - */ -class Rules: public boost::noncopyable -{ -public: - using Expression = ExpressionClasses::Expression; - - Rules(); - - /// @returns a pointer to the first matching pattern and sets the match - /// groups accordingly. - SimplificationRule const* findFirstMatch( - Expression const& _expr, - ExpressionClasses const& _classes - ); - - /// Checks whether the rulelist is non-empty. This is usually enforced - /// by the constructor, but we had some issues with static initialization. - bool isInitialized() const; - -private: - void addRules(std::vector> const& _rules); - void addRule(SimplificationRule const& _rule); - - void resetMatchGroups() { m_matchGroups.clear(); } - - std::map m_matchGroups; - /// Pattern to match, replacement to be applied and flag indicating whether - /// the replacement might remove some elements (except constants). - std::vector> m_rules[256]; -}; - -/** - * Pattern to match against an expression. - * Also stores matched expressions to retrieve them later, for constructing new expressions using - * ExpressionTemplate. - */ -class Pattern -{ -public: - using Expression = ExpressionClasses::Expression; - using Id = ExpressionClasses::Id; - - using Builtins = evmasm::EVMBuiltins; - static constexpr size_t WordSize = 256; - using Word = u256; - - // Matches a specific constant value. - Pattern(unsigned _value): Pattern(u256(_value)) {} - Pattern(int _value): Pattern(u256(_value)) {} - Pattern(long unsigned _value): Pattern(u256(_value)) {} - // Matches a specific constant value. - Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(std::make_shared(_value)) {} - // Matches a specific assembly item type or anything if not given. - Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} - // Matches a given instruction with given arguments - Pattern(Instruction _instruction, std::initializer_list _arguments = {}); - /// Sets this pattern to be part of the match group with the identifier @a _group. - /// Inside one rule, all patterns in the same match group have to match expressions from the - /// same expression equivalence class. - void setMatchGroup(unsigned _group, std::map& _matchGroups); - unsigned matchGroup() const { return m_matchGroup; } - bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; - - AssemblyItem toAssemblyItem(langutil::SourceLocation const& _location) const; - std::vector arguments() const { return m_arguments; } - - /// @returns the id of the matched expression if this pattern is part of a match group. - Id id() const { return matchGroupValue().id; } - /// @returns the data of the matched expression if this pattern is part of a match group. - u256 const& d() const { return matchGroupValue().item->data(); } - - std::string toString() const; - - AssemblyItemType type() const { return m_type; } - Instruction instruction() const - { - assertThrow(type() == Operation, OptimizerException, ""); - return m_instruction; - } - -private: - bool matchesBaseItem(AssemblyItem const* _item) const; - Expression const& matchGroupValue() const; - u256 const& data() const; - - AssemblyItemType m_type; - bool m_requireDataMatch = false; - Instruction m_instruction; ///< Only valid if m_type is Operation - std::shared_ptr m_data; ///< Only valid if m_type is not Operation - std::vector m_arguments; - unsigned m_matchGroup = 0; - std::map* m_matchGroups = nullptr; -}; - -/** - * Template for a new expression that can be built from matched patterns. - */ -struct ExpressionTemplate -{ - using Expression = ExpressionClasses::Expression; - using Id = ExpressionClasses::Id; - explicit ExpressionTemplate(Pattern const& _pattern, langutil::SourceLocation const& _location); - std::string toString() const; - bool hasId = false; - /// Id of the matched expression, if available. - Id id = Id(-1); - // Otherwise, assembly item. - AssemblyItem item = UndefinedItem; - std::vector arguments; -}; - -} diff --git a/compiler/liblangutil/CMakeLists.txt b/compiler/liblangutil/CMakeLists.txt index ab30ac34..573a7c10 100644 --- a/compiler/liblangutil/CMakeLists.txt +++ b/compiler/liblangutil/CMakeLists.txt @@ -6,7 +6,6 @@ set(sources ErrorReporter.cpp ErrorReporter.h EVMVersion.h - EVMVersion.cpp Exceptions.cpp Exceptions.h ParserBase.cpp diff --git a/compiler/liblangutil/EVMVersion.cpp b/compiler/liblangutil/EVMVersion.cpp index fa100c09..8188ce4c 100644 --- a/compiler/liblangutil/EVMVersion.cpp +++ b/compiler/liblangutil/EVMVersion.cpp @@ -21,32 +21,6 @@ #include using namespace solidity; -using namespace solidity::evmasm; using namespace solidity::langutil; -bool EVMVersion::hasOpcode(Instruction _opcode) const -{ - switch (_opcode) - { - case Instruction::RETURNDATACOPY: - case Instruction::RETURNDATASIZE: - return supportsReturndata(); - case Instruction::STATICCALL: - return hasStaticCall(); - case Instruction::SHL: - case Instruction::SHR: - case Instruction::SAR: - return hasBitwiseShifting(); - case Instruction::CREATE2: - return hasCreate2(); - case Instruction::EXTCODEHASH: - return hasExtCodeHash(); - case Instruction::CHAINID: - return hasChainID(); - case Instruction::SELFBALANCE: - return hasSelfBalance(); - default: - return true; - } -} - +// TODO: delete this file diff --git a/compiler/liblangutil/EVMVersion.h b/compiler/liblangutil/EVMVersion.h index 2c9e87ef..08cfd58c 100644 --- a/compiler/liblangutil/EVMVersion.h +++ b/compiler/liblangutil/EVMVersion.h @@ -20,8 +20,6 @@ #pragma once -#include - #include #include @@ -87,8 +85,6 @@ class EVMVersion: bool hasChainID() const { return *this >= istanbul(); } bool hasSelfBalance() const { return *this >= istanbul(); } - bool hasOpcode(evmasm::Instruction _opcode) const; - /// Whether we have to retain the costs for the call opcode itself (false), /// or whether we can just forward easily all remaining gas (true). bool canOverchargeGasForCall() const { return *this >= tangerineWhistle(); } diff --git a/compiler/libsolc/libsolc.cpp b/compiler/libsolc/libsolc.cpp index d216ae8a..c42f4f75 100644 --- a/compiler/libsolc/libsolc.cpp +++ b/compiler/libsolc/libsolc.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -155,7 +154,6 @@ extern void solidity_reset() noexcept { // This is called right before each compilation, but not at the end, so additional memory // can be freed here. - yul::YulStringRepository::reset(); solidityAllocations.clear(); } } diff --git a/compiler/libsolidity/CMakeLists.txt b/compiler/libsolidity/CMakeLists.txt index ffed4d9d..408504f8 100644 --- a/compiler/libsolidity/CMakeLists.txt +++ b/compiler/libsolidity/CMakeLists.txt @@ -39,8 +39,6 @@ set(sources ast/ASTAnnotations.h ast/ASTEnums.h ast/ASTForward.h - ast/AsmJsonImporter.cpp - ast/AsmJsonImporter.h ast/ASTJsonConverter.cpp ast/ASTJsonConverter.h ast/ASTUtils.cpp @@ -53,75 +51,15 @@ set(sources ast/Types.h ast/TypeProvider.cpp ast/TypeProvider.h - codegen/ABIFunctions.cpp - codegen/ABIFunctions.h - codegen/ArrayUtils.cpp - codegen/ArrayUtils.h - codegen/Compiler.cpp - codegen/Compiler.h - codegen/CompilerContext.cpp - codegen/CompilerContext.h - codegen/CompilerUtils.cpp - codegen/CompilerUtils.h - codegen/ContractCompiler.cpp - codegen/ContractCompiler.h - codegen/ExpressionCompiler.cpp - codegen/ExpressionCompiler.h - codegen/LValue.cpp - codegen/LValue.h - codegen/MultiUseYulFunctionCollector.h - codegen/MultiUseYulFunctionCollector.cpp - codegen/YulUtilFunctions.h - codegen/YulUtilFunctions.cpp - codegen/ir/IRGenerator.cpp - codegen/ir/IRGenerator.h - codegen/ir/IRGeneratorForStatements.cpp - codegen/ir/IRGeneratorForStatements.h - codegen/ir/IRGenerationContext.cpp - codegen/ir/IRGenerationContext.h - codegen/ir/IRLValue.cpp - codegen/ir/IRLValue.h - formal/BMC.cpp - formal/BMC.h - formal/CHC.cpp - formal/CHC.h - formal/CHCSmtLib2Interface.cpp - formal/CHCSmtLib2Interface.h - formal/CHCSolverInterface.h - formal/EncodingContext.cpp - formal/EncodingContext.h - formal/ModelChecker.cpp - formal/ModelChecker.h - formal/SMTEncoder.cpp - formal/SMTEncoder.h - formal/SMTLib2Interface.cpp - formal/SMTLib2Interface.h - formal/SMTPortfolio.cpp - formal/SMTPortfolio.h - formal/SolverInterface.h - formal/SSAVariable.cpp - formal/SSAVariable.h - formal/SymbolicTypes.cpp - formal/SymbolicTypes.h - formal/SymbolicVariables.cpp - formal/SymbolicVariables.h - formal/VariableUsage.cpp - formal/VariableUsage.h - interface/ABI.cpp - interface/ABI.h interface/CompilerStack.cpp interface/CompilerStack.h interface/DebugSettings.h - interface/GasEstimator.cpp - interface/GasEstimator.h interface/Natspec.cpp interface/Natspec.h interface/OptimiserSettings.h interface/ReadFile.h interface/StandardCompiler.cpp interface/StandardCompiler.h - interface/StorageLayout.cpp - interface/StorageLayout.h interface/Version.cpp interface/Version.h parsing/DocStringParser.cpp @@ -186,7 +124,7 @@ if (NOT (${Z3_FOUND} OR ${CVC4_FOUND})) endif() add_library(solidity ${sources} ${z3_SRCS} ${cvc4_SRCS}) -target_link_libraries(solidity PUBLIC yul evmasm langutil solutil Boost::boost Boost::filesystem Boost::system) +target_link_libraries(solidity PUBLIC langutil solutil Boost::boost Boost::filesystem Boost::system) if (${Z3_FOUND}) target_link_libraries(solidity PUBLIC z3::libz3) diff --git a/compiler/libsolidity/analysis/ControlFlowBuilder.cpp b/compiler/libsolidity/analysis/ControlFlowBuilder.cpp index c7504897..4871e442 100644 --- a/compiler/libsolidity/analysis/ControlFlowBuilder.cpp +++ b/compiler/libsolidity/analysis/ControlFlowBuilder.cpp @@ -361,19 +361,9 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName) return false; } -bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly) +bool ControlFlowBuilder::visit(InlineAssembly const& /*_inlineAssembly*/) { solAssert(!!m_currentNode, ""); - visitNode(_inlineAssembly); - for (auto const& ref: _inlineAssembly.annotation().externalReferences) - { - if (auto variableDeclaration = dynamic_cast(ref.second.declaration)) - m_currentNode->variableOccurrences.emplace_back( - *variableDeclaration, - VariableOccurrence::Kind::InlineAssembly, - &_inlineAssembly - ); - } return true; } diff --git a/compiler/libsolidity/analysis/GlobalContext.cpp b/compiler/libsolidity/analysis/GlobalContext.cpp index 7199380b..3aa31aff 100644 --- a/compiler/libsolidity/analysis/GlobalContext.cpp +++ b/compiler/libsolidity/analysis/GlobalContext.cpp @@ -95,8 +95,7 @@ inline vector> constructMagicVariable magicVarDecl("mulmod", TypeProvider::function(strings{"uint256", "uint256", "uint256"}, strings{"uint256"}, FunctionType::Kind::MulMod, false, StateMutability::Pure)), magicVarDecl("now", TypeProvider::uint256()), magicVarDecl("require", TypeProvider::function(strings{"bool"}, strings{}, FunctionType::Kind::Require, true, StateMutability::Pure)), - magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), - magicVarDecl("revert", TypeProvider::function(strings{"string memory"}, strings(), FunctionType::Kind::Revert, false, StateMutability::Pure)), + magicVarDecl("revert", TypeProvider::function(strings(), strings(), FunctionType::Kind::Revert, true, StateMutability::Pure)), magicVarDecl("ripemd160", TypeProvider::function(strings{"bytes memory"}, strings{"bytes20"}, FunctionType::Kind::RIPEMD160, false, StateMutability::Pure)), magicVarDecl("selfdestruct", TypeProvider::function(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), magicVarDecl("sha256", TypeProvider::function(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), diff --git a/compiler/libsolidity/analysis/NameAndTypeResolver.cpp b/compiler/libsolidity/analysis/NameAndTypeResolver.cpp index 76c388f3..77bac2f0 100644 --- a/compiler/libsolidity/analysis/NameAndTypeResolver.cpp +++ b/compiler/libsolidity/analysis/NameAndTypeResolver.cpp @@ -242,22 +242,6 @@ vector NameAndTypeResolver::cleanedDeclarations( void NameAndTypeResolver::warnVariablesNamedLikeInstructions() { - for (auto const& instruction: evmasm::c_instructions) - { - string const instructionName{boost::algorithm::to_lower_copy(instruction.first)}; - auto declarations = nameFromCurrentScope(instructionName, true); - for (Declaration const* const declaration: declarations) - { - solAssert(!!declaration, ""); - if (dynamic_cast(declaration)) - // Don't warn the user for what the user did not. - continue; - m_errorReporter.warning( - declaration->location(), - "Variable is shadowed in inline assembly by an instruction of the same name" - ); - } - } } void NameAndTypeResolver::setScope(ASTNode const* _node) diff --git a/compiler/libsolidity/analysis/ReferencesResolver.cpp b/compiler/libsolidity/analysis/ReferencesResolver.cpp index 4fbff93d..5137d8e2 100644 --- a/compiler/libsolidity/analysis/ReferencesResolver.cpp +++ b/compiler/libsolidity/analysis/ReferencesResolver.cpp @@ -26,11 +26,6 @@ #include #include -#include -#include -#include -#include - #include #include @@ -230,15 +225,23 @@ void ReferencesResolver::endVisit(Mapping const& _typeName) TypePointer valueType = _typeName.valueType().annotation().type; // Convert key type to memory. keyType = TypeProvider::withLocationIfReference(DataLocation::Memory, keyType); - + // valueType = ReferenceType::copyForLocationIfReference(_typeName.location(), valueType); // _typeName.annotation().type = make_shared(keyType, valueType, _typeName.location()); // Convert value type to storage reference. // valueType = TypeProvider::withLocationIfReference(DataLocation::Storage, valueType); // _typeName.annotation().type = TypeProvider::mapping(keyType, valueType); - + valueType = TypeProvider::withLocationIfReference(_typeName.location(), valueType); - _typeName.annotation().type = TypeProvider::mapping(keyType, valueType, _typeName.location()); + _typeName.annotation().type = TypeProvider::mapping(keyType, valueType, _typeName.location()); +} + +void ReferencesResolver::endVisit(const ElementaryTypeName &_typeName) { + if (_typeName.typeName().token() == Token::ExtraCurrencyCollection) { + _typeName.annotation().type = TypeProvider::extraCurrencyCollection(_typeName.getLocation()); + } else { + ASTConstVisitor::endVisit(_typeName); + } } void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) @@ -270,27 +273,21 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) // } // else // _typeName.annotation().type = TypeProvider::array(DataLocation::Storage, baseType); - + // _typeName.annotation().type = make_shared(_typeName.location(), baseType, lengthType->literalValue(nullptr)); // } // else // _typeName.annotation().type = make_shared(_typeName.location(), baseType); - + _typeName.annotation().type = TypeProvider::array(_typeName.location(), baseType, lengthType->literalValue(nullptr)); } else _typeName.annotation().type = TypeProvider::array(_typeName.location(), baseType); - + } -bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) +bool ReferencesResolver::visit(InlineAssembly const& /*_inlineAssembly*/) { - m_resolver.warnVariablesNamedLikeInstructions(); - - m_yulAnnotation = &_inlineAssembly.annotation(); - (*this)(_inlineAssembly.operations()); - m_yulAnnotation = nullptr; - return false; } @@ -408,90 +405,6 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable) _variable.annotation().type = type; } -void ReferencesResolver::operator()(yul::FunctionDefinition const& _function) -{ - bool wasInsideFunction = m_yulInsideFunction; - m_yulInsideFunction = true; - this->operator()(_function.body); - m_yulInsideFunction = wasInsideFunction; -} - -void ReferencesResolver::operator()(yul::Identifier const& _identifier) -{ - bool isSlot = boost::algorithm::ends_with(_identifier.name.str(), "_slot"); - bool isOffset = boost::algorithm::ends_with(_identifier.name.str(), "_offset"); - - auto declarations = m_resolver.nameFromCurrentScope(_identifier.name.str()); - if (isSlot || isOffset) - { - // special mode to access storage variables - if (!declarations.empty()) - // the special identifier exists itself, we should not allow that. - return; - string realName = _identifier.name.str().substr(0, _identifier.name.str().size() - ( - isSlot ? - string("_slot").size() : - string("_offset").size() - )); - if (realName.empty()) - { - declarationError(_identifier.location, "In variable names _slot and _offset can only be used as a suffix."); - return; - } - declarations = m_resolver.nameFromCurrentScope(realName); - } - if (declarations.size() > 1) - { - declarationError(_identifier.location, "Multiple matching identifiers. Resolving overloaded identifiers is not supported."); - return; - } - else if (declarations.size() == 0) - return; - if (auto var = dynamic_cast(declarations.front())) - if (var->isLocalVariable() && m_yulInsideFunction) - { - declarationError(_identifier.location, "Cannot access local Solidity variables from inside an inline assembly function."); - return; - } - - m_yulAnnotation->externalReferences[&_identifier].isSlot = isSlot; - m_yulAnnotation->externalReferences[&_identifier].isOffset = isOffset; - m_yulAnnotation->externalReferences[&_identifier].declaration = declarations.front(); -} - -void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl) -{ - for (auto const& identifier: _varDecl.variables) - { - bool isSlot = boost::algorithm::ends_with(identifier.name.str(), "_slot"); - bool isOffset = boost::algorithm::ends_with(identifier.name.str(), "_offset"); - - string namePrefix = identifier.name.str().substr(0, identifier.name.str().find('.')); - if (isSlot || isOffset) - declarationError(identifier.location, "In variable declarations _slot and _offset can not be used as a suffix."); - else if ( - auto declarations = m_resolver.nameFromCurrentScope(namePrefix); - !declarations.empty() - ) - { - SecondarySourceLocation ssl; - for (auto const* decl: declarations) - ssl.append("The shadowed declaration is here:", decl->location()); - if (!ssl.infos.empty()) - declarationError( - identifier.location, - ssl, - namePrefix.size() < identifier.name.str().size() ? - "The prefix of this declaration conflicts with a declaration outside the inline assembly block." : - "This declaration shadows a declaration outside the inline assembly block." - ); - } - } - - if (_varDecl.value) - visit(*_varDecl.value); -} - void ReferencesResolver::typeError(SourceLocation const& _location, string const& _description) { m_errorOccurred = true; @@ -522,4 +435,5 @@ void ReferencesResolver::fatalDeclarationError(SourceLocation const& _location, m_errorReporter.fatalDeclarationError(_location, _description); } + } diff --git a/compiler/libsolidity/analysis/ReferencesResolver.h b/compiler/libsolidity/analysis/ReferencesResolver.h index 2bbce69d..3e29810a 100644 --- a/compiler/libsolidity/analysis/ReferencesResolver.h +++ b/compiler/libsolidity/analysis/ReferencesResolver.h @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -46,7 +45,7 @@ class NameAndTypeResolver; * Resolves references to declarations (of variables and types) and also establishes the link * between a return statement and the return parameter list. */ -class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker +class ReferencesResolver: private ASTConstVisitor { public: ReferencesResolver( @@ -65,9 +64,6 @@ class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker bool resolve(ASTNode const& _root); private: - using yul::ASTWalker::visit; - using yul::ASTWalker::operator(); - bool visit(Block const& _block) override; void endVisit(Block const& _block) override; bool visit(ForStatement const& _for) override; @@ -82,15 +78,12 @@ class ReferencesResolver: private ASTConstVisitor, private yul::ASTWalker void endVisit(UserDefinedTypeName const& _typeName) override; void endVisit(FunctionTypeName const& _typeName) override; void endVisit(Mapping const& _typeName) override; + void endVisit(ElementaryTypeName const& _typeName) override; void endVisit(ArrayTypeName const& _typeName) override; bool visit(InlineAssembly const& _inlineAssembly) override; bool visit(Return const& _return) override; void endVisit(VariableDeclaration const& _variable) override; - void operator()(yul::FunctionDefinition const& _function) override; - void operator()(yul::Identifier const& _identifier) override; - void operator()(yul::VariableDeclaration const& _varDecl) override; - /// Adds a new error to the list of errors. void typeError(langutil::SourceLocation const& _location, std::string const& _description); diff --git a/compiler/libsolidity/analysis/StaticAnalyzer.cpp b/compiler/libsolidity/analysis/StaticAnalyzer.cpp index c53bc03f..31b41ccd 100644 --- a/compiler/libsolidity/analysis/StaticAnalyzer.cpp +++ b/compiler/libsolidity/analysis/StaticAnalyzer.cpp @@ -256,21 +256,8 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) return true; } -bool StaticAnalyzer::visit(InlineAssembly const& _inlineAssembly) +bool StaticAnalyzer::visit(InlineAssembly const& /*_inlineAssembly*/) { - if (!m_currentFunction) - return true; - - for (auto const& ref: _inlineAssembly.annotation().externalReferences) - { - if (auto var = dynamic_cast(ref.second.declaration)) - { - solAssert(!var->name().empty(), ""); - if (var->isLocalVariable()) - m_localVarUseCount[make_pair(var->id(), var)] += 1; - } - } - return true; } diff --git a/compiler/libsolidity/analysis/SyntaxChecker.cpp b/compiler/libsolidity/analysis/SyntaxChecker.cpp index 94e550b7..b40b83d7 100644 --- a/compiler/libsolidity/analysis/SyntaxChecker.cpp +++ b/compiler/libsolidity/analysis/SyntaxChecker.cpp @@ -21,9 +21,6 @@ #include #include -#include -#include - #include #include @@ -269,17 +266,8 @@ bool SyntaxChecker::visit(UnaryOperation const& _operation) return true; } -bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly) +bool SyntaxChecker::visit(InlineAssembly const& /*_inlineAssembly*/) { - if (!m_useYulOptimizer) - return false; - - if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations())) - m_errorReporter.syntaxError( - _inlineAssembly.location(), - "The msize instruction cannot be used when the Yul optimizer is activated because " - "it can change its semantics. Either disable the Yul optimizer or do not use the instruction." - ); return false; } diff --git a/compiler/libsolidity/analysis/TypeChecker.cpp b/compiler/libsolidity/analysis/TypeChecker.cpp index a84afb3f..5228fc62 100644 --- a/compiler/libsolidity/analysis/TypeChecker.cpp +++ b/compiler/libsolidity/analysis/TypeChecker.cpp @@ -25,10 +25,6 @@ #include #include -#include -#include -#include - #include #include @@ -743,133 +739,8 @@ void TypeChecker::endVisit(FunctionTypeName const& _funType) solAssert(fun.interfaceType(false), "External function type uses internal types."); } -bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) +bool TypeChecker::visit(InlineAssembly const& /*_inlineAssembly*/) { - // External references have already been resolved in a prior stage and stored in the annotation. - // We run the resolve step again regardless. - yul::ExternalIdentifierAccess::Resolver identifierAccess = [&]( - yul::Identifier const& _identifier, - yul::IdentifierContext _context, - bool - ) - { - auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); - if (ref == _inlineAssembly.annotation().externalReferences.end()) - return size_t(-1); - Declaration const* declaration = ref->second.declaration; - solAssert(!!declaration, ""); - bool requiresStorage = ref->second.isSlot || ref->second.isOffset; - if (auto var = dynamic_cast(declaration)) - { - solAssert(var->type(), "Expected variable type!"); - if (var->isConstant()) - { - var = rootVariableDeclaration(*var); - - if (!var->value()) - { - m_errorReporter.typeError(_identifier.location, "Constant has no value."); - return size_t(-1); - } - else if (!type(*var)->isValueType() || ( - dynamic_cast(var->value().get()) == nullptr && - type(*var->value())->category() != Type::Category::RationalNumber - )) - { - m_errorReporter.typeError(_identifier.location, "Only direct number constants and references to such constants are supported by inline assembly."); - return size_t(-1); - } - else if (_context == yul::IdentifierContext::LValue) - { - m_errorReporter.typeError(_identifier.location, "Constant variables cannot be assigned to."); - return size_t(-1); - } - else if (requiresStorage) - { - m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on non-constant storage variables."); - return size_t(-1); - } - } - - if (requiresStorage) - { - if (!var->isStateVariable() && !var->type()->dataStoredIn(DataLocation::Storage)) - { - m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); - return size_t(-1); - } - else if (_context != yul::IdentifierContext::RValue) - { - m_errorReporter.typeError(_identifier.location, "Storage variables cannot be assigned to."); - return size_t(-1); - } - } - else if (!var->isConstant() && var->isStateVariable()) - { - m_errorReporter.typeError(_identifier.location, "Only local variables are supported. To access storage variables, use the _slot and _offset suffixes."); - return size_t(-1); - } - else if (var->type()->dataStoredIn(DataLocation::Storage)) - { - m_errorReporter.typeError(_identifier.location, "You have to use the _slot or _offset suffix to access storage reference variables."); - return size_t(-1); - } - else if (var->type()->sizeOnStack() != 1) - { - if (var->type()->dataStoredIn(DataLocation::CallData)) - m_errorReporter.typeError(_identifier.location, "Call data elements cannot be accessed directly. Copy to a local variable first or use \"calldataload\" or \"calldatacopy\" with manually determined offsets and sizes."); - else - m_errorReporter.typeError(_identifier.location, "Only types that use one stack slot are supported."); - return size_t(-1); - } - } - else if (requiresStorage) - { - m_errorReporter.typeError(_identifier.location, "The suffixes _offset and _slot can only be used on storage variables."); - return size_t(-1); - } - else if (_context == yul::IdentifierContext::LValue) - { - if (dynamic_cast(declaration)) - return size_t(-1); - - m_errorReporter.typeError(_identifier.location, "Only local variables can be assigned to in inline assembly."); - return size_t(-1); - } - - if (_context == yul::IdentifierContext::RValue) - { - solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); - if (dynamic_cast(declaration)) - { - } - else if (dynamic_cast(declaration)) - { - } - else if (auto contract = dynamic_cast(declaration)) - { - if (!contract->isLibrary()) - { - m_errorReporter.typeError(_identifier.location, "Expected a library."); - return size_t(-1); - } - } - else - return size_t(-1); - } - ref->second.valueSize = 1; - return size_t(1); - }; - solAssert(!_inlineAssembly.annotation().analysisInfo, ""); - _inlineAssembly.annotation().analysisInfo = make_shared(); - yul::AsmAnalyzer analyzer( - *_inlineAssembly.annotation().analysisInfo, - m_errorReporter, - _inlineAssembly.dialect(), - identifierAccess - ); - if (!analyzer.analyze(_inlineAssembly.operations())) - return false; return true; } @@ -2044,6 +1915,19 @@ TypeChecker::isArgumentAPublicFunction(FunctionCall const& _functionCall) { return functionDeclaration; } +void TypeChecker::areArgumentsValidForFunctionCall(const FunctionCall &_functionCall, const FunctionDefinition *funcDef) +{ + std::vector> params = funcDef->parameters(); + auto arguments = _functionCall.arguments(); + std::vector> args(arguments.begin() + 1, + arguments.end()); + if (params.size() != args.size()) + m_errorReporter.fatalTypeError(_functionCall.location(), "Wrong argument number for a function call."); + + for (size_t i = 0; i < params.size(); i++) + expectType(*args[i], *params[i]->annotation().type); + +} void TypeChecker::typeCheckFunctionGeneralChecks( FunctionCall const& _functionCall, @@ -2309,6 +2193,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) // Determine function call kind and function type for this FunctionCall node FunctionCallAnnotation& funcCallAnno = _functionCall.annotation(); FunctionTypePointer functionType = nullptr; + FunctionDefinition const* funcDef = nullptr; // Determine and assign function call kind, lvalue, purity and function type for this FunctionCall node switch (expressionType->category()) @@ -2449,8 +2334,11 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) case FunctionType::Kind::MetaType: returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall); break; + case FunctionType::Kind::TVMEncodeBody: case FunctionType::Kind::TVMFunctionId: - isArgumentAPublicFunction(_functionCall); + funcDef = isArgumentAPublicFunction(_functionCall); + if (functionType->kind() == FunctionType::Kind::TVMEncodeBody) + areArgumentsValidForFunctionCall(_functionCall, funcDef); [[fallthrough]]; default: { @@ -2499,6 +2387,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) bool setGas = false; bool setFlag = false; bool setCurrencies = false; + bool setStateInit = false; FunctionType::Kind kind = expressionFunctionType->kind(); if ( @@ -2553,9 +2442,19 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) } else if (name == "currencies") { - expectType(*_functionCallOptions.options()[i], *TypeProvider::extraCurrencyCollection()); + expectType(*_functionCallOptions.options()[i], *TypeProvider::extraCurrencyCollection(DataLocation::Memory)); setCheckOption(setCurrencies, "currencies", expressionFunctionType->currenciesSet()); } + else if (name == "stateInit") + { + if (!dynamic_cast(&_functionCallOptions.expression())) + m_errorReporter.typeError( + _functionCallOptions.location(), + "Option \"stateInit\" can be set only for \"new\" expression."); + + expectType(*_functionCallOptions.options()[i], *TypeProvider::tvmcell()); + setCheckOption(setStateInit, "stateInit", expressionFunctionType->currenciesSet()); + } else if (name == "value") { if (kind == FunctionType::Kind::BareDelegateCall) @@ -2568,11 +2467,6 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) _functionCallOptions.location(), "Cannot set option \"value\" for staticcall." ); - else if (!expressionFunctionType->isPayable()) - m_errorReporter.typeError( - _functionCallOptions.location(), - "Cannot set option \"value\" on a non-payable function type." - ); else { expectType(*_functionCallOptions.options()[i], *TypeProvider::uint256()); @@ -2597,7 +2491,7 @@ bool TypeChecker::visit(FunctionCallOptions const& _functionCallOptions) else m_errorReporter.typeError( _functionCallOptions.location(), - "Unknown call option \"" + name + "\". Valid options are \"salt\", \"value\" and \"gas\"." + "Unknown call option \"" + name + "\". Valid options are \"currencies\", \"stateInit\", \"flag\", \"salt\", \"value\" and \"gas\"." ); } @@ -3116,33 +3010,33 @@ void TypeChecker::endVisit(MappingNameExpression const& _expr) void TypeChecker::endVisit(Literal const& _literal) { - if (_literal.looksLikeAddress()) - { - // Assign type here if it even looks like an address. This prevents double errors for invalid addresses - _literal.annotation().type = TypeProvider::payableAddress(); - - string msg; - if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits - // looksLikeAddress enforces that it is a hex literal starting with "0x" - msg = - "This looks like an address but is not exactly 40 hex digits. It is " + - to_string(_literal.valueWithoutUnderscores().length() - 2) + - " hex digits."; - else if (!_literal.passesAddressChecksum()) - { - msg = "This looks like an address but has an invalid checksum."; - if (!_literal.getChecksummedAddress().empty()) - msg += " Correct checksummed address: \"" + _literal.getChecksummedAddress() + "\"."; - } - - if (!msg.empty()) - m_errorReporter.syntaxError( - _literal.location(), - msg + - " If this is not used as an address, please prepend '00'. " + - "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" - ); - } +// if (_literal.looksLikeAddress()) +// { +// // Assign type here if it even looks like an address. This prevents double errors for invalid addresses +// _literal.annotation().type = TypeProvider::payableAddress(); +// +// string msg; +// if (_literal.valueWithoutUnderscores().length() != 42) // "0x" + 40 hex digits +// // looksLikeAddress enforces that it is a hex literal starting with "0x" +// msg = +// "This looks like an address but is not exactly 40 hex digits. It is " + +// to_string(_literal.valueWithoutUnderscores().length() - 2) + +// " hex digits."; +// else if (!_literal.passesAddressChecksum()) +// { +// msg = "This looks like an address but has an invalid checksum."; +// if (!_literal.getChecksummedAddress().empty()) +// msg += " Correct checksummed address: \"" + _literal.getChecksummedAddress() + "\"."; +// } +// +// if (!msg.empty()) +// m_errorReporter.syntaxError( +// _literal.location(), +// msg + +// " If this is not used as an address, please prepend '00'. " + +// "For more information please see https://solidity.readthedocs.io/en/develop/types.html#address-literals" +// ); +// } if (_literal.isHexNumber() && _literal.subDenomination() != Literal::SubDenomination::None) m_errorReporter.fatalTypeError( diff --git a/compiler/libsolidity/analysis/TypeChecker.h b/compiler/libsolidity/analysis/TypeChecker.h index dd9176cd..d99fbe4d 100644 --- a/compiler/libsolidity/analysis/TypeChecker.h +++ b/compiler/libsolidity/analysis/TypeChecker.h @@ -116,6 +116,8 @@ class TypeChecker: private ASTConstVisitor ); FunctionDefinition const* isArgumentAPublicFunction(FunctionCall const& _functionCall); + void areArgumentsValidForFunctionCall(FunctionCall const& _functionCall, + FunctionDefinition const* funcDef); void endVisit(InheritanceSpecifier const& _inheritance) override; void endVisit(UsingForDirective const& _usingFor) override; diff --git a/compiler/libsolidity/analysis/ViewPureChecker.cpp b/compiler/libsolidity/analysis/ViewPureChecker.cpp index 26d57792..8a420e59 100644 --- a/compiler/libsolidity/analysis/ViewPureChecker.cpp +++ b/compiler/libsolidity/analysis/ViewPureChecker.cpp @@ -17,10 +17,7 @@ #include #include -#include -#include #include -#include #include #include @@ -30,100 +27,6 @@ using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; -namespace -{ - -class AssemblyViewPureChecker -{ -public: - explicit AssemblyViewPureChecker( - yul::Dialect const& _dialect, - std::function _reportMutability - ): - m_dialect(_dialect), - m_reportMutability(_reportMutability) {} - - void operator()(yul::Literal const&) {} - void operator()(yul::Identifier const&) {} - void operator()(yul::ExpressionStatement const& _expr) - { - std::visit(*this, _expr.expression); - } - void operator()(yul::Assignment const& _assignment) - { - std::visit(*this, *_assignment.value); - } - void operator()(yul::VariableDeclaration const& _varDecl) - { - if (_varDecl.value) - std::visit(*this, *_varDecl.value); - } - void operator()(yul::FunctionDefinition const& _funDef) - { - (*this)(_funDef.body); - } - void operator()(yul::FunctionCall const& _funCall) - { - if (yul::EVMDialect const* dialect = dynamic_cast(&m_dialect)) - if (yul::BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) - if (fun->instruction) - checkInstruction(_funCall.location, *fun->instruction); - - for (auto const& arg: _funCall.arguments) - std::visit(*this, arg); - } - void operator()(yul::If const& _if) - { - std::visit(*this, *_if.condition); - (*this)(_if.body); - } - void operator()(yul::Switch const& _switch) - { - std::visit(*this, *_switch.expression); - for (auto const& _case: _switch.cases) - { - if (_case.value) - (*this)(*_case.value); - (*this)(_case.body); - } - } - void operator()(yul::ForLoop const& _for) - { - (*this)(_for.pre); - std::visit(*this, *_for.condition); - (*this)(_for.body); - (*this)(_for.post); - } - void operator()(yul::Break const&) - { - } - void operator()(yul::Continue const&) - { - } - void operator()(yul::Leave const&) - { - } - void operator()(yul::Block const& _block) - { - for (auto const& s: _block.statements) - std::visit(*this, s); - } - -private: - void checkInstruction(SourceLocation _location, evmasm::Instruction _instruction) - { - if (evmasm::SemanticInformation::invalidInViewFunctions(_instruction)) - m_reportMutability(StateMutability::NonPayable, _location); - else if (evmasm::SemanticInformation::invalidInPureFunctions(_instruction)) - m_reportMutability(StateMutability::View, _location); - } - - yul::Dialect const& m_dialect; - std::function m_reportMutability; -}; - -} - bool ViewPureChecker::check() { vector contracts; @@ -224,12 +127,8 @@ void ViewPureChecker::endVisit(Identifier const& _identifier) reportMutability(mutability, _identifier.location()); } -void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) +void ViewPureChecker::endVisit(InlineAssembly const& /*_inlineAssembly*/) { - AssemblyViewPureChecker{ - _inlineAssembly.dialect(), - [=](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); } - }(_inlineAssembly.operations()); } void ViewPureChecker::reportMutability( @@ -342,8 +241,15 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) ASTString const& member = _memberAccess.memberName(); switch (_memberAccess.expression().annotation().type->category()) { + case Type::Category::ExtraCurrencyCollection: case Type::Category::Mapping: - if (member == "delMin") { + if (member == "delMin" || + member == "delMax" || + member == "replace" || + member == "add" || + member == "getSet" || + member == "getAdd" || + member == "getReplace") { if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage)) mutability = StateMutability::NonPayable; } @@ -377,11 +283,12 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::TVM, "setcode"}, {MagicType::Kind::TVM, "sendrawmsg"}, {MagicType::Kind::TVM, "setCurrentCode"}, - {MagicType::Kind::TVM, "setExtDestAddr"}, {MagicType::Kind::TVM, "transfer"}, {MagicType::Kind::TVM, "transLT"}, {MagicType::Kind::TVM, "min"}, {MagicType::Kind::TVM, "max"}, + {MagicType::Kind::TVM, "functionId"}, + {MagicType::Kind::TVM, "encodeBody"}, {MagicType::Kind::MetaType, "creationCode"}, {MagicType::Kind::MetaType, "runtimeCode"}, {MagicType::Kind::MetaType, "name"}, diff --git a/compiler/libsolidity/ast/AST.cpp b/compiler/libsolidity/ast/AST.cpp index 2355da31..0442475f 100644 --- a/compiler/libsolidity/ast/AST.cpp +++ b/compiler/libsolidity/ast/AST.cpp @@ -41,6 +41,9 @@ void Mapping::setLocation(DataLocation _location) { map->setLocation(_location); if (auto arr = dynamic_cast(m_valueType.get())) arr->setLocation(_location); + if (auto cc = dynamic_cast(m_valueType.get())) + if (cc->typeName().token() == Token::ExtraCurrencyCollection) + cc->setLocation(_location); } void ArrayTypeName::setLocation(DataLocation _location) { @@ -49,6 +52,13 @@ void ArrayTypeName::setLocation(DataLocation _location) { map->setLocation(_location); if (auto arr = dynamic_cast(m_baseType.get())) arr->setLocation(_location); + if (auto cc = dynamic_cast(m_baseType.get())) + if (cc->typeName().token() == Token::ExtraCurrencyCollection) + cc->setLocation(_location); +} + +void ElementaryTypeName::setLocation(DataLocation _location) { + m_location = _location; } ASTNode::ASTNode(int64_t _id, SourceLocation const& _location): diff --git a/compiler/libsolidity/ast/AST.h b/compiler/libsolidity/ast/AST.h index 7fcb5362..095f2a2a 100644 --- a/compiler/libsolidity/ast/AST.h +++ b/compiler/libsolidity/ast/AST.h @@ -29,7 +29,6 @@ #include #include -#include #include #include @@ -1084,7 +1083,7 @@ class ElementaryTypeName: public TypeName SourceLocation const& _location, ElementaryTypeNameToken const& _elem, std::optional _stateMutability = {} - ): TypeName(_id, _location), m_type(_elem), m_stateMutability(_stateMutability) + ): TypeName(_id, _location), m_type(_elem), m_stateMutability(_stateMutability), m_location(DataLocation::Memory) { solAssert(!_stateMutability.has_value() || _elem.token() == Token::Address, ""); } @@ -1096,9 +1095,13 @@ class ElementaryTypeName: public TypeName std::optional const& stateMutability() const { return m_stateMutability; } + void setLocation(DataLocation _location); + DataLocation getLocation() const { return m_location; } + private: ElementaryTypeNameToken m_type; std::optional m_stateMutability; ///< state mutability for address type + DataLocation m_location; ///< for ExtraCurrencyCollection type }; /** @@ -1245,22 +1248,13 @@ class InlineAssembly: public Statement InlineAssembly( int64_t _id, SourceLocation const& _location, - ASTPointer const& _docString, - yul::Dialect const& _dialect, - std::shared_ptr const& _operations + ASTPointer const& _docString ): - Statement(_id, _location, _docString), m_dialect(_dialect), m_operations(_operations) {} + Statement(_id, _location, _docString) {} void accept(ASTVisitor& _visitor) override; void accept(ASTConstVisitor& _visitor) const override; - yul::Dialect const& dialect() const { return m_dialect; } - yul::Block const& operations() const { return *m_operations; } - InlineAssemblyAnnotation& annotation() const override; - -private: - yul::Dialect const& m_dialect; - std::shared_ptr m_operations; }; /** diff --git a/compiler/libsolidity/ast/ASTAnnotations.h b/compiler/libsolidity/ast/ASTAnnotations.h index 86636684..1d609362 100644 --- a/compiler/libsolidity/ast/ASTAnnotations.h +++ b/compiler/libsolidity/ast/ASTAnnotations.h @@ -32,12 +32,6 @@ #include #include -namespace solidity::yul -{ -struct AsmAnalysisInfo; -struct Identifier; -struct Dialect; -} namespace solidity::frontend { @@ -155,11 +149,6 @@ struct InlineAssemblyAnnotation: StatementAnnotation bool isOffset = false; ///< Whether the intra-slot offset of a storage variable is queried. size_t valueSize = size_t(-1); }; - - /// Mapping containing resolved references to external identifiers and their value size - std::map externalReferences; - /// Information generated during analysis phase. - std::shared_ptr analysisInfo; }; struct BlockAnnotation: StatementAnnotation, ScopableAnnotation diff --git a/compiler/libsolidity/ast/ASTJsonConverter.cpp b/compiler/libsolidity/ast/ASTJsonConverter.cpp index ed3c6fff..9bc4d2e1 100644 --- a/compiler/libsolidity/ast/ASTJsonConverter.cpp +++ b/compiler/libsolidity/ast/ASTJsonConverter.cpp @@ -23,11 +23,6 @@ #include -#include -#include -#include -#include - #include #include @@ -187,17 +182,6 @@ void ASTJsonConverter::appendExpressionAttributes( _attributes += exprAttributes; } -Json::Value ASTJsonConverter::inlineAssemblyIdentifierToJson(pair _info) const -{ - Json::Value tuple(Json::objectValue); - tuple["src"] = sourceLocationToString(_info.first->location); - tuple["declaration"] = idOrNull(_info.second.declaration); - tuple["isSlot"] = Json::Value(_info.second.isSlot); - tuple["isOffset"] = Json::Value(_info.second.isOffset); - tuple["valueSize"] = Json::Value(Json::LargestUInt(_info.second.valueSize)); - return tuple; -} - void ASTJsonConverter::print(ostream& _stream, ASTNode const& _node) { _stream << util::jsonPrettyPrint(toJson(_node)); @@ -491,30 +475,8 @@ bool ASTJsonConverter::visit(ArrayTypeName const& _node) return false; } -bool ASTJsonConverter::visit(InlineAssembly const& _node) +bool ASTJsonConverter::visit(InlineAssembly const& /*_node*/) { - vector> externalReferences; - - for (auto const& it: _node.annotation().externalReferences) - if (it.first) - externalReferences.emplace_back(make_pair( - it.first->name.str(), - inlineAssemblyIdentifierToJson(it) - )); - - Json::Value externalReferencesJson = Json::arrayValue; - - for (auto&& it: boost::range::sort(externalReferences)) - externalReferencesJson.append(std::move(it.second)); - - setJsonNode(_node, "InlineAssembly", { - m_legacy ? - make_pair("operations", Json::Value(yul::AsmPrinter()(_node.operations()))) : - make_pair("AST", Json::Value(yul::AsmJsonConverter(sourceIndexFromLocation(_node.location()))(_node.operations()))), - make_pair("externalReferences", std::move(externalReferencesJson)), - make_pair("evmVersion", dynamic_cast(_node.dialect()).evmVersion().name()) - }); - return false; } diff --git a/compiler/libsolidity/ast/ASTJsonConverter.h b/compiler/libsolidity/ast/ASTJsonConverter.h index a1d3cd4a..c8c7db54 100644 --- a/compiler/libsolidity/ast/ASTJsonConverter.h +++ b/compiler/libsolidity/ast/ASTJsonConverter.h @@ -145,7 +145,6 @@ class ASTJsonConverter: public ASTConstVisitor { return _node ? toJson(*_node) : Json::nullValue; } - Json::Value inlineAssemblyIdentifierToJson(std::pair _info) const; static std::string location(VariableDeclaration::Location _location); static std::string contractKind(ContractKind _kind); static std::string functionCallKind(FunctionCallKind _kind); diff --git a/compiler/libsolidity/ast/ASTJsonImporter.cpp b/compiler/libsolidity/ast/ASTJsonImporter.cpp index aa28e3f1..01a32330 100644 --- a/compiler/libsolidity/ast/ASTJsonImporter.cpp +++ b/compiler/libsolidity/ast/ASTJsonImporter.cpp @@ -21,14 +21,10 @@ */ #include -#include #include -#include #include #include #include -#include -#include #include #include #include @@ -532,13 +528,9 @@ ASTPointer ASTJsonImporter::createInlineAssembly(Json::Value con astAssert(evmVersion.has_value(), "Invalid EVM version!"); astAssert(m_evmVersion == evmVersion, "Imported tree evm version differs from configured evm version!"); - yul::Dialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(evmVersion.value()); - shared_ptr operations = make_shared(AsmJsonImporter(m_currentSourceName).createBlock(member(_node, "AST"))); return createASTNode( _node, - nullOrASTString(_node, "documentation"), - dialect, - operations + nullOrASTString(_node, "documentation") ); } diff --git a/compiler/libsolidity/ast/AST_accept.h b/compiler/libsolidity/ast/AST_accept.h index c4b4ef86..8d7f4996 100644 --- a/compiler/libsolidity/ast/AST_accept.h +++ b/compiler/libsolidity/ast/AST_accept.h @@ -276,6 +276,9 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const if (auto arr = dynamic_cast(m_typeName.get())) if (m_location != Location::Memory) arr->setLocation(DataLocation::Storage); + if (auto currency = dynamic_cast(m_typeName.get())) + if (m_location != Location::Memory) + currency->setLocation(DataLocation::Storage); m_typeName->accept(_visitor); } if (m_overrides) diff --git a/compiler/libsolidity/ast/TypeProvider.cpp b/compiler/libsolidity/ast/TypeProvider.cpp index dcaeb80f..02b76241 100644 --- a/compiler/libsolidity/ast/TypeProvider.cpp +++ b/compiler/libsolidity/ast/TypeProvider.cpp @@ -29,7 +29,6 @@ BoolType const TypeProvider::m_boolean{}; TvmCellType const TypeProvider::m_tvmcell{}; TvmSliceType const TypeProvider::m_tvmslice{}; TvmBuilderType const TypeProvider::m_tvmbuilder{}; -ExtraCurrencyCollectionType const TypeProvider::m_extraCurrencyCollection{}; InaccessibleDynamicType const TypeProvider::m_inaccessibleDynamic{}; /// The string and bytes unique_ptrs are initialized when they are first used because @@ -259,7 +258,7 @@ Type const* TypeProvider::fromElementaryTypeName(ElementaryTypeNameToken const& case Token::TvmBuilder: return tvmbuilder(); case Token::ExtraCurrencyCollection: - return extraCurrencyCollection(); + return extraCurrencyCollection(DataLocation::Memory); case Token::Bytes: return bytesStorage(); case Token::String: @@ -580,3 +579,7 @@ MappingType const* TypeProvider::mapping(Type const* _keyType, Type const* _valu { return createAndGet(_keyType, _valueType, _location); } + +ExtraCurrencyCollectionType const *TypeProvider::extraCurrencyCollection(DataLocation _location) { + return createAndGet(_location); +} diff --git a/compiler/libsolidity/ast/TypeProvider.h b/compiler/libsolidity/ast/TypeProvider.h index db3ed7ea..a5f66bce 100644 --- a/compiler/libsolidity/ast/TypeProvider.h +++ b/compiler/libsolidity/ast/TypeProvider.h @@ -67,7 +67,6 @@ class TypeProvider static TvmSliceType const* tvmslice() noexcept {return &m_tvmslice; } static TvmBuilderType const* tvmbuilder() noexcept { return &m_tvmbuilder; } - static ExtraCurrencyCollectionType const* extraCurrencyCollection() noexcept { return &m_extraCurrencyCollection; } static FixedBytesType const* byte() { return fixedBytes(1); } static FixedBytesType const* fixedBytes(unsigned m) { return m_bytesM.at(m - 1).get(); } @@ -197,6 +196,7 @@ class TypeProvider static MagicType const* meta(Type const* _type); static MappingType const* mapping(Type const* _keyType, Type const* _valueType, DataLocation _location); + static ExtraCurrencyCollectionType const* extraCurrencyCollection(DataLocation _location); private: /// Global TypeProvider instance. @@ -213,7 +213,6 @@ class TypeProvider static TvmCellType const m_tvmcell; static TvmSliceType const m_tvmslice; static TvmBuilderType const m_tvmbuilder; - static ExtraCurrencyCollectionType const m_extraCurrencyCollection; static InaccessibleDynamicType const m_inaccessibleDynamic; /// These are lazy-initialized because they depend on `byte` being available. diff --git a/compiler/libsolidity/ast/Types.cpp b/compiler/libsolidity/ast/Types.cpp index 3a14c872..3381438f 100644 --- a/compiler/libsolidity/ast/Types.cpp +++ b/compiler/libsolidity/ast/Types.cpp @@ -450,7 +450,7 @@ MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) cons { MemberList::MemberMap members = { {"balance", TypeProvider::uint256()}, - {"currencies", TypeProvider::extraCurrencyCollection()}, + {"currencies", TypeProvider::extraCurrencyCollection(DataLocation::Memory)}, {"wid", TypeProvider::integer(8, IntegerType::Modifier::Signed)}, {"value", TypeProvider::uint256()}, {"call", TypeProvider::function(strings{"bytes memory"}, strings{"bool", "bytes memory"}, FunctionType::Kind::BareCall, false, StateMutability::Payable)}, @@ -485,7 +485,7 @@ MemberList::MemberMap AddressType::nativeMembers(ContractDefinition const*) cons TypeProvider::boolean(), TypeProvider::integer(16, IntegerType::Modifier::Unsigned), TypeProvider::tvmcell(), - TypeProvider::extraCurrencyCollection()}, + TypeProvider::extraCurrencyCollection(DataLocation::Memory)}, TypePointers{}, strings{string("value"), string("bounce"), string("flag"), string("body"), string("currencies")}, strings{}, @@ -1470,6 +1470,8 @@ BoolResult ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const &dynamic_cast(_convertTo).contractDefinition() ) != bases.end(); } + if (_convertTo.category() == Category::Address) + return true; return false; } @@ -1834,46 +1836,36 @@ MemberList::MemberMap ArrayType::nativeMembers(ContractDefinition const*) const } static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType, Type const* valueType) { - members.emplace_back("min", TypeProvider::function( - TypePointers{}, - TypePointers{keyType, valueType, TypeProvider::boolean()}, - strings{}, - strings{string(), string(), string()}, - FunctionType::Kind::MappingGetMinKey, - false, StateMutability::Pure - )); - members.emplace_back("max", TypeProvider::function( - TypePointers{}, - TypePointers{keyType, valueType, TypeProvider::boolean()}, - strings{}, - strings{string(), string(), string()}, - FunctionType::Kind::MappingGetMaxKey, - false, StateMutability::Pure - )); - members.emplace_back("delMin", TypeProvider::function( - TypePointers{}, - TypePointers{keyType, valueType}, - strings{}, - strings{string(), string()}, - FunctionType::Kind::MappingDelMin, - false, StateMutability::Pure - )); - members.emplace_back("next", TypeProvider::function( - TypePointers{keyType}, - TypePointers{keyType, valueType, TypeProvider::boolean()}, - strings{string()}, - strings{string(), string(), string()}, - FunctionType::Kind::MappingGetNextKey, - false, StateMutability::Pure - )); - members.emplace_back("prev", TypeProvider::function( - TypePointers{keyType}, - TypePointers{keyType, valueType, TypeProvider::boolean()}, - strings{string()}, - strings{string(), string(), string()}, - FunctionType::Kind::MappingGetPrevKey, - false, StateMutability::Pure - )); + for (const std::string& name : {"min", "max"}) { + members.emplace_back(name, TypeProvider::function( + TypePointers{}, + TypePointers{keyType, valueType, TypeProvider::boolean()}, + strings{}, + strings{string(), string(), string()}, + FunctionType::Kind::MappingGetMinMax, + false, StateMutability::Pure + )); + } + for (const std::string& name : {"delMin", "delMax"}) { + members.emplace_back(name, TypeProvider::function( + TypePointers{}, + TypePointers{keyType, valueType}, + strings{}, + strings{string(), string()}, + FunctionType::Kind::MappingDelMinOrMax, + false, StateMutability::Pure + )); + } + for (const std::string& name : {"next", "prev", "nextOrEq", "prevOrEq"}) { + members.emplace_back(name, TypeProvider::function( + TypePointers{keyType}, + TypePointers{keyType, valueType, TypeProvider::boolean()}, + strings{string()}, + strings{string(), string(), string()}, + FunctionType::Kind::MappingGetNextKey, + false, StateMutability::Pure + )); + } members.emplace_back("fetch", TypeProvider::function( TypePointers{keyType}, TypePointers{TypeProvider::boolean(), valueType}, @@ -1898,6 +1890,26 @@ static void appendMapMethods(MemberList::MemberMap& members, Type const* keyType FunctionType::Kind::MappingEmpty, false, StateMutability::Pure )); + for (const std::string& name : {"replace", "add"}) { + members.emplace_back(name, TypeProvider::function( + TypePointers{keyType, valueType}, + TypePointers{TypeProvider::boolean()}, + strings{string(), string()}, + strings{string()}, + FunctionType::Kind::MappingReplaceOrAdd, + false, StateMutability::Pure + )); + } + for (const std::string& name : {"getSet", "getAdd", "getReplace"}) { + members.emplace_back(name, TypeProvider::function( + TypePointers{keyType, valueType}, + TypePointers{valueType, TypeProvider::boolean()}, + strings{string(), string()}, + strings{string(), string()}, + FunctionType::Kind::MappingGetSet, + false, StateMutability::Pure + )); + } } MemberList::MemberMap MappingType::nativeMembers(ContractDefinition const*) const @@ -2798,7 +2810,6 @@ string FunctionType::richIdentifier() const string id = "t_function_"; switch (m_kind) { - case Kind::TVMDestAddr: id += "tvmsetextdestaddr"; break; case Kind::StringMethod: id += "stringmethod"; break; case Kind::TVMSetcode: id += "tvmsetcode"; break; case Kind::TVMChecksign: id += "tvmchecksign"; break; @@ -2827,17 +2838,19 @@ string FunctionType::richIdentifier() const case Kind::TVMBuilderMethods: id += "tvmbuildermethods"; break; case Kind::TVMLoadRef: id += "tvmloadref"; break; case Kind::TVMFunctionId: id += "tvmfunctionid"; break; + case Kind::TVMEncodeBody: id += "tvmencodebody"; break; case Kind::TVMMaxMin: id += "tvmmaxmin"; break; case Kind::AddressMakeAddrStd: id += "addressmakeaddrstd"; break; case Kind::LogTVM: id += "logtvm"; break; - case Kind::MappingGetMinKey: id += "mapgetmin"; break; - case Kind::MappingGetMaxKey: id += "mapgetmax"; break; + case Kind::MappingGetMinMax: id += "mapgetminmax"; break; case Kind::MappingGetNextKey: id += "mapgetnext"; break; case Kind::MappingGetPrevKey: id += "mapgetprev"; break; case Kind::MappingFetch: id += "mapfetch"; break; case Kind::MappingExists: id += "mapexists"; break; - case Kind::MappingDelMin: id += "mapdelmin"; break; + case Kind::MappingDelMinOrMax: id += "mapdelmin"; break; case Kind::MappingEmpty: id += "mapempty"; break; + case Kind::MappingReplaceOrAdd: id += "mappingreplaceoradd"; break; + case Kind::MappingGetSet: id += "mappingsetget"; break; case Kind::Declaration: id += "declaration"; break; case Kind::Internal: id += "internal"; break; case Kind::External: id += "external"; break; @@ -3560,20 +3573,6 @@ TypeResult MappingType::unaryOperatorResult(Token _operator) const { TypeResult MappingType::interfaceType(bool _inLibrary) const { solAssert(keyType()->interfaceType(_inLibrary).get(), "Must be an elementary type!"); - - if (_inLibrary) - { - auto iType = valueType()->interfaceType(_inLibrary); - - if (!iType.get()) - { - solAssert(!iType.message().empty(), "Expected detailed error message!"); - return iType; - } - } - else - return TypeResult::err("Only libraries are allowed to use the mapping type in public or external functions."); - return this; } @@ -3793,7 +3792,7 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const {"value", TypeProvider::uint256()}, {"data", TypeProvider::array(DataLocation::CallData)}, {"sig", TypeProvider::fixedBytes(4)}, - {"currencies", TypeProvider::extraCurrencyCollection()}, + {"currencies", TypeProvider::extraCurrencyCollection(DataLocation::Memory)}, }); case Kind::TVM: { MemberList::MemberMap members = { @@ -3813,14 +3812,6 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const FunctionType::Kind::TVMSetcode, false, StateMutability::Pure )); - members.emplace_back("setExtDestAddr", TypeProvider::function( - TypePointers{TypeProvider::address()}, - TypePointers{}, - strings{string()}, - strings{}, - FunctionType::Kind::TVMDestAddr, - false, StateMutability::Pure - )); members.emplace_back("setCurrentCode", TypeProvider::function( TypePointers{TypeProvider::tvmcell()}, TypePointers{}, @@ -3967,6 +3958,16 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const FunctionType::Kind::TVMFunctionId, true, StateMutability::Pure )); + + members.emplace_back("encodeBody", TypeProvider::function( + TypePointers{}, + TypePointers{TypeProvider::tvmcell()}, + strings{}, + strings{string()}, + FunctionType::Kind::TVMEncodeBody, + true, StateMutability::Pure + )); + members.emplace_back("max", TypeProvider::function( TypePointers{}, TypePointers{}, diff --git a/compiler/libsolidity/ast/Types.h b/compiler/libsolidity/ast/Types.h index 6e4f5f7f..381e2504 100644 --- a/compiler/libsolidity/ast/Types.h +++ b/compiler/libsolidity/ast/Types.h @@ -516,6 +516,8 @@ class RationalNumberType: public Type /// @returns true if the value is not an integer. bool isFractional() const { return m_value.denominator() != 1; } + bigint numerator() const { return m_value.numerator(); } + /// @returns true if the value is negative. bool isNegative() const { return m_value < 0; } @@ -704,6 +706,7 @@ class VarInteger: public Type class ExtraCurrencyCollectionType: public Type { public: + explicit ExtraCurrencyCollectionType(DataLocation _location) : m_location{_location} {} Category category() const override { return Category::ExtraCurrencyCollection; } bool isValueType() const override { return true; } std::string richIdentifier() const override { return "t_extracurrencycollection"; } @@ -717,6 +720,10 @@ class ExtraCurrencyCollectionType: public Type TypeResult unaryOperatorResult(Token _operator) const override; MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + bool dataStoredIn(DataLocation _location) const override { return m_location == _location; } + +private: + DataLocation m_location; }; /** @@ -1150,8 +1157,8 @@ class FunctionType: public Type TVMCommit, ///< tvm.commit() TVMConfigParam, ///< tvm.configParam() TVMDeploy, ///< functions to deploy contract from contract - TVMDestAddr, ///< tvm.setExtDestAddr() TVMFunctionId, ///< tvm.functionId(function_name) + TVMEncodeBody, ///< tvm.encodeBody() TVMMaxMin, ///< tvm.min(a, b, ...) or tvm.max(a, b, ...) TVMHash, ///< tvm.hash() TVMPubkey, ///< tvm.pubkey() @@ -1165,12 +1172,13 @@ class FunctionType: public Type ByteArrayPush, ///< .push() to a dynamically sized byte array in storage MappingGetNextKey, ///< .next() for a mapping MappingGetPrevKey, ///< .prev() for a mapping - MappingGetMinKey, ///< .min() for a mapping - MappingGetMaxKey, ///< .max() for a mapping - MappingDelMin, ///< .delMin() for a mapping + MappingGetMinMax, ///< .min() or .max() for a mapping + MappingDelMinOrMax, ///< .delMin() or .delMax() for a mapping MappingFetch, ///< .fetch() for a mapping MappingExists, ///< .exists() for a mapping MappingEmpty, ///< .empty() for a mapping + MappingReplaceOrAdd, ///< .replace() or .add() for a mapping + MappingGetSet, ///< .getSet() for a mapping StringMethod, ///< string methods ObjectCreation, ///< array creation using new Assert, ///< assert() diff --git a/compiler/libsolidity/codegen/ABIFunctions.cpp b/compiler/libsolidity/codegen/ABIFunctions.cpp deleted file mode 100644 index 0ad044a5..00000000 --- a/compiler/libsolidity/codegen/ABIFunctions.cpp +++ /dev/null @@ -1,1532 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2017 - * Routines that generate Yul code related to ABI encoding, decoding and type conversions. - */ - -#include - -#include -#include -#include -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::frontend; - -string ABIFunctions::tupleEncoder( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes -) -{ - EncodingOptions options; - options.encodeAsLibraryTypes = _encodeAsLibraryTypes; - options.encodeFunctionFromStack = true; - options.padded = true; - options.dynamicInplace = false; - - string functionName = string("abi_encode_tuple_"); - for (auto const& t: _givenTypes) - functionName += t->identifier() + "_"; - functionName += "_to_"; - for (auto const& t: _targetTypes) - functionName += t->identifier() + "_"; - functionName += options.toFunctionNameSuffix(); - - return createExternallyUsedFunction(functionName, [&]() { - // Note that the values are in reverse due to the difference in calling semantics. - Whiskers templ(R"( - function (headStart ) -> tail { - tail := add(headStart, ) - - } - )"); - templ("functionName", functionName); - size_t const headSize_ = headSize(_targetTypes); - templ("headSize", to_string(headSize_)); - string encodeElements; - size_t headPos = 0; - size_t stackPos = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - solAssert(_givenTypes[i], ""); - solAssert(_targetTypes[i], ""); - size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); - bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl( - dynamic ? - string(R"( - mstore(add(headStart, ), sub(tail, headStart)) - tail := ( tail) - )") : - string(R"( - ( add(headStart, )) - )") - ); - string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); - elementTempl("values", values.empty() ? "" : values + ", "); - elementTempl("pos", to_string(headPos)); - elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); - encodeElements += elementTempl.render(); - headPos += _targetTypes[i]->calldataHeadSize(); - stackPos += sizeOnStack; - } - solAssert(headPos == headSize_, ""); - string valueParams = suffixedVariableNameList("value", stackPos, 0); - templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); - templ("encodeElements", encodeElements); - - return templ.render(); - }); -} - -string ABIFunctions::tupleEncoderPacked( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes -) -{ - EncodingOptions options; - options.encodeAsLibraryTypes = false; - options.encodeFunctionFromStack = true; - options.padded = false; - options.dynamicInplace = true; - - string functionName = string("abi_encode_tuple_packed_"); - for (auto const& t: _givenTypes) - functionName += t->identifier() + "_"; - functionName += "_to_"; - for (auto const& t: _targetTypes) - functionName += t->identifier() + "_"; - functionName += options.toFunctionNameSuffix(); - - return createExternallyUsedFunction(functionName, [&]() { - solAssert(!_givenTypes.empty(), ""); - - // Note that the values are in reverse due to the difference in calling semantics. - Whiskers templ(R"( - function (pos ) -> end { - - end := pos - } - )"); - templ("functionName", functionName); - string encodeElements; - size_t stackPos = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - solAssert(_givenTypes[i], ""); - solAssert(_targetTypes[i], ""); - size_t sizeOnStack = _givenTypes[i]->sizeOnStack(); - bool dynamic = _targetTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl( - dynamic ? - string(R"( - pos := ( pos) - )") : - string(R"( - ( pos) - pos := add(pos, ) - )") - ); - string values = suffixedVariableNameList("value", stackPos, stackPos + sizeOnStack); - elementTempl("values", values.empty() ? "" : values + ", "); - if (!dynamic) - elementTempl("calldataEncodedSize", to_string(_targetTypes[i]->calldataEncodedSize(false))); - elementTempl("abiEncode", abiEncodingFunction(*_givenTypes[i], *_targetTypes[i], options)); - encodeElements += elementTempl.render(); - stackPos += sizeOnStack; - } - string valueParams = suffixedVariableNameList("value", stackPos, 0); - templ("valueParams", valueParams.empty() ? "" : ", " + valueParams); - templ("encodeElements", encodeElements); - - return templ.render(); - }); -} -string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory) -{ - string functionName = string("abi_decode_tuple_"); - for (auto const& t: _types) - functionName += t->identifier(); - if (_fromMemory) - functionName += "_fromMemory"; - - return createExternallyUsedFunction(functionName, [&]() { - TypePointers decodingTypes; - for (auto const& t: _types) - decodingTypes.emplace_back(t->decodingType()); - - Whiskers templ(R"( - function (headStart, dataEnd) { - if slt(sub(dataEnd, headStart), ) { } - - } - )"); - templ("functionName", functionName); - templ("revertString", revertReasonIfDebug("ABI decoding: tuple data too short")); - templ("minimumSize", to_string(headSize(decodingTypes))); - - string decodeElements; - vector valueReturnParams; - size_t headPos = 0; - size_t stackPos = 0; - for (size_t i = 0; i < _types.size(); ++i) - { - solAssert(_types[i], ""); - solAssert(decodingTypes[i], ""); - size_t sizeOnStack = _types[i]->sizeOnStack(); - solAssert(sizeOnStack == decodingTypes[i]->sizeOnStack(), ""); - solAssert(sizeOnStack > 0, ""); - vector valueNamesLocal; - for (size_t j = 0; j < sizeOnStack; j++) - { - valueNamesLocal.emplace_back("value" + to_string(stackPos)); - valueReturnParams.emplace_back("value" + to_string(stackPos)); - stackPos++; - } - bool dynamic = decodingTypes[i]->isDynamicallyEncoded(); - Whiskers elementTempl( - dynamic ? - R"( - { - let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { } - := (add(headStart, offset), dataEnd) - } - )" : - R"( - { - let offset := - := (add(headStart, offset), dataEnd) - } - )" - ); - // TODO add test - elementTempl("revertString", revertReasonIfDebug("ABI decoding: invalid tuple offset")); - elementTempl("load", _fromMemory ? "mload" : "calldataload"); - elementTempl("values", boost::algorithm::join(valueNamesLocal, ", ")); - elementTempl("pos", to_string(headPos)); - elementTempl("abiDecode", abiDecodingFunction(*_types[i], _fromMemory, true)); - decodeElements += elementTempl.render(); - headPos += decodingTypes[i]->calldataHeadSize(); - } - templ("valueReturnParams", boost::algorithm::join(valueReturnParams, ", ")); - templ("arrow", valueReturnParams.empty() ? "" : "->"); - templ("decodeElements", decodeElements); - - return templ.render(); - }); -} - -pair> ABIFunctions::requestedFunctions() -{ - std::set empty; - swap(empty, m_externallyUsedFunctions); - return make_pair(m_functionCollector->requestedFunctions(), std::move(empty)); -} - -string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const -{ - string suffix; - if (!padded) - suffix += "_nonPadded"; - if (dynamicInplace) - suffix += "_inplace"; - if (encodeFunctionFromStack) - suffix += "_fromStack"; - if (encodeAsLibraryTypes) - suffix += "_library"; - return suffix; -} - -string ABIFunctions::abiEncodingFunction( - Type const& _from, - Type const& _to, - EncodingOptions const& _options -) -{ - TypePointer toInterface = _to.fullEncodingType(_options.encodeAsLibraryTypes, true, false); - solUnimplementedAssert(toInterface, "Encoding type \"" + _to.toString() + "\" not yet implemented."); - Type const& to = *toInterface; - - if (_from.category() == Type::Category::StringLiteral) - return abiEncodingFunctionStringLiteral(_from, to, _options); - else if (auto toArray = dynamic_cast(&to)) - { - solAssert(_from.category() == Type::Category::Array, ""); - solAssert(to.dataStoredIn(DataLocation::Memory), ""); - ArrayType const& fromArray = dynamic_cast(_from); - - switch (fromArray.location()) - { - case DataLocation::CallData: - if ( - fromArray.isByteArray() || - *fromArray.baseType() == *TypeProvider::uint256() || - *fromArray.baseType() == FixedBytesType(32) - ) - return abiEncodingFunctionCalldataArrayWithoutCleanup(fromArray, *toArray, _options); - else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); - case DataLocation::Memory: - if (fromArray.isByteArray()) - return abiEncodingFunctionMemoryByteArray(fromArray, *toArray, _options); - else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); - case DataLocation::Storage: - if (fromArray.baseType()->storageBytes() <= 16) - return abiEncodingFunctionCompactStorageArray(fromArray, *toArray, _options); - else - return abiEncodingFunctionSimpleArray(fromArray, *toArray, _options); - default: - solAssert(false, ""); - } - } - else if (auto const* toStruct = dynamic_cast(&to)) - { - StructType const* fromStruct = dynamic_cast(&_from); - solAssert(fromStruct, ""); - return abiEncodingFunctionStruct(*fromStruct, *toStruct, _options); - } - else if (_from.category() == Type::Category::Function) - return abiEncodingFunctionFunctionType( - dynamic_cast(_from), - to, - _options - ); - - solAssert(_from.sizeOnStack() == 1, ""); - solAssert(to.isValueType(), ""); - solAssert(to.calldataEncodedSize() == 32, ""); - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - to.identifier() + - _options.toFunctionNameSuffix(); - return createFunction(functionName, [&]() { - solAssert(!to.isDynamicallyEncoded(), ""); - - Whiskers templ(R"( - function (value, pos) { - mstore(pos, ) - } - )"); - templ("functionName", functionName); - - if (_from.dataStoredIn(DataLocation::Storage)) - { - // special case: convert storage reference type to value type - this is only - // possible for library calls where we just forward the storage reference - solAssert(_options.encodeAsLibraryTypes, ""); - solAssert(_options.padded && !_options.dynamicInplace, "Non-padded / inplace encoding for library call requested."); - solAssert(to == *TypeProvider::uint256(), ""); - templ("cleanupConvert", "value"); - } - else - { - string cleanupConvert; - if (_from == to) - cleanupConvert = m_utils.cleanupFunction(_from) + "(value)"; - else - cleanupConvert = m_utils.conversionFunction(_from, to) + "(value)"; - if (!_options.padded) - cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")"; - templ("cleanupConvert", cleanupConvert); - } - return templ.render(); - }); -} - -string ABIFunctions::abiEncodeAndReturnUpdatedPosFunction( - Type const& _givenType, - Type const& _targetType, - ABIFunctions::EncodingOptions const& _options -) -{ - string functionName = - "abi_encodeUpdatedPos_" + - _givenType.identifier() + - "_to_" + - _targetType.identifier() + - _options.toFunctionNameSuffix(); - return createFunction(functionName, [&]() { - string values = suffixedVariableNameList("value", 0, numVariablesForType(_givenType, _options)); - string encoder = abiEncodingFunction(_givenType, _targetType, _options); - if (_targetType.isDynamicallyEncoded()) - return Whiskers(R"( - function (, pos) -> updatedPos { - updatedPos := (, pos) - } - )") - ("functionName", functionName) - ("encode", encoder) - ("values", values) - .render(); - else - { - unsigned encodedSize = _targetType.calldataEncodedSize(_options.padded); - solAssert(encodedSize != 0, "Invalid encoded size."); - return Whiskers(R"( - function (, pos) -> updatedPos { - (, pos) - updatedPos := add(pos, ) - } - )") - ("functionName", functionName) - ("encode", encoder) - ("encodedSize", toCompactHexWithPrefix(encodedSize)) - ("values", values) - .render(); - } - }); -} - -string ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup( - Type const& _from, - Type const& _to, - EncodingOptions const& _options -) -{ - solAssert(_from.category() == Type::Category::Array, "Unknown dynamic type."); - solAssert(_to.category() == Type::Category::Array, "Unknown dynamic type."); - auto const& fromArrayType = dynamic_cast(_from); - auto const& toArrayType = dynamic_cast(_to); - - solAssert(fromArrayType.location() == DataLocation::CallData, ""); - solAssert( - fromArrayType.isByteArray() || - *fromArrayType.baseType() == *TypeProvider::uint256() || - *fromArrayType.baseType() == FixedBytesType(32), - "" - ); - solAssert(fromArrayType.calldataStride() == toArrayType.memoryStride(), ""); - - solAssert( - *fromArrayType.copyForLocation(DataLocation::Memory, true) == - *toArrayType.copyForLocation(DataLocation::Memory, true), - "" - ); - - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - return createFunction(functionName, [&]() { - bool needsPadding = _options.padded && fromArrayType.isByteArray(); - if (fromArrayType.isDynamicallySized()) - { - Whiskers templ(R"( - // -> - function (start, length, pos) -> end { - pos := (pos, length) - - (start, pos, length) - end := add(pos, ) - } - )"); - templ("storeLength", arrayStoreLengthForEncodingFunction(toArrayType, _options)); - templ("functionName", functionName); - if (fromArrayType.isByteArray() || fromArrayType.calldataStride() == 1) - templ("scaleLengthByStride", ""); - else - templ("scaleLengthByStride", - Whiskers(R"( - if gt(length, ) { } - length := mul(length, ) - )") - ("stride", toCompactHexWithPrefix(fromArrayType.calldataStride())) - ("maxLength", toCompactHexWithPrefix(u256(-1) / fromArrayType.calldataStride())) - ("revertString", revertReasonIfDebug("ABI encoding: array data too long")) - .render() - // TODO add revert test - ); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", m_utils.copyToMemoryFunction(true)); - templ("lengthPadded", needsPadding ? m_utils.roundUpFunction() + "(length)" : "length"); - return templ.render(); - } - else - { - solAssert(fromArrayType.calldataStride() == 32, ""); - Whiskers templ(R"( - // -> - function (start, pos) { - (start, pos, ) - } - )"); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("copyFun", m_utils.copyToMemoryFunction(true)); - templ("byteLength", toCompactHexWithPrefix(fromArrayType.length() * fromArrayType.calldataStride())); - return templ.render(); - } - }); -} - -string ABIFunctions::abiEncodingFunctionSimpleArray( - ArrayType const& _from, - ArrayType const& _to, - EncodingOptions const& _options -) -{ - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - - solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); - solAssert(_from.length() == _to.length(), ""); - solAssert(!_from.isByteArray(), ""); - if (_from.dataStoredIn(DataLocation::Storage)) - solAssert(_from.baseType()->storageBytes() > 16, ""); - - return createFunction(functionName, [&]() { - bool dynamic = _to.isDynamicallyEncoded(); - bool dynamicBase = _to.baseType()->isDynamicallyEncoded(); - bool const usesTail = dynamicBase && !_options.dynamicInplace; - EncodingOptions subOptions(_options); - subOptions.encodeFunctionFromStack = false; - subOptions.padded = true; - string elementValues = suffixedVariableNameList("elementValue", 0, numVariablesForType(*_from.baseType(), subOptions)); - Whiskers templ( - usesTail ? - R"( - // -> - function (value, pos) { - - pos := (pos, length) - let headStart := pos - let tail := add(pos, mul(length, 0x20)) - let baseRef := (value) - let srcPtr := baseRef - for { let i := 0 } lt(i, length) { i := add(i, 1) } - { - mstore(pos, sub(tail, headStart)) - let := - tail := (, tail) - srcPtr := (srcPtr) - pos := add(pos, 0x20) - } - pos := tail - - } - )" : - R"( - // -> - function (value, pos) { - - pos := (pos, length) - let baseRef := (value) - let srcPtr := baseRef - for { let i := 0 } lt(i, length) { i := add(i, 1) } - { - let := - pos := (, pos) - srcPtr := (srcPtr) - } - - } - )" - ); - templ("functionName", functionName); - templ("elementValues", elementValues); - bool lengthAsArgument = _from.dataStoredIn(DataLocation::CallData) && _from.isDynamicallySized(); - if (lengthAsArgument) - { - templ("maybeLength", " length,"); - templ("declareLength", ""); - } - else - { - templ("maybeLength", ""); - templ("declareLength", "let length := " + m_utils.arrayLengthFunction(_from) + "(value)"); - } - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("return", dynamic ? " -> end " : ""); - templ("assignEnd", dynamic ? "end := pos" : ""); - templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from)); - - templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions)); - switch (_from.location()) - { - case DataLocation::Memory: - templ("arrayElementAccess", "mload(srcPtr)"); - break; - case DataLocation::Storage: - if (_from.baseType()->isValueType()) - templ("arrayElementAccess", m_utils.readFromStorage(*_from.baseType(), 0, false) + "(srcPtr)"); - else - templ("arrayElementAccess", "srcPtr"); - break; - case DataLocation::CallData: - templ("arrayElementAccess", calldataAccessFunction(*_from.baseType()) + "(baseRef, srcPtr)"); - break; - default: - solAssert(false, ""); - } - templ("nextArrayElement", m_utils.nextArrayElementFunction(_from)); - return templ.render(); - }); -} - -string ABIFunctions::abiEncodingFunctionMemoryByteArray( - ArrayType const& _from, - ArrayType const& _to, - EncodingOptions const& _options -) -{ - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - - solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); - solAssert(_from.length() == _to.length(), ""); - solAssert(_from.dataStoredIn(DataLocation::Memory), ""); - solAssert(_from.isByteArray(), ""); - - return createFunction(functionName, [&]() { - solAssert(_to.isByteArray(), ""); - Whiskers templ(R"( - function (value, pos) -> end { - let length := (value) - pos := (pos, length) - (add(value, 0x20), pos, length) - end := add(pos, ) - } - )"); - templ("functionName", functionName); - templ("lengthFun", m_utils.arrayLengthFunction(_from)); - templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("copyFun", m_utils.copyToMemoryFunction(false)); - templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length"); - return templ.render(); - }); -} - -string ABIFunctions::abiEncodingFunctionCompactStorageArray( - ArrayType const& _from, - ArrayType const& _to, - EncodingOptions const& _options -) -{ - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - - solAssert(_from.isDynamicallySized() == _to.isDynamicallySized(), ""); - solAssert(_from.length() == _to.length(), ""); - solAssert(_from.dataStoredIn(DataLocation::Storage), ""); - - return createFunction(functionName, [&]() { - if (_from.isByteArray()) - { - solAssert(_to.isByteArray(), ""); - Whiskers templ(R"( - // -> - function (value, pos) -> ret { - let slotValue := sload(value) - switch and(slotValue, 1) - case 0 { - // short byte array - let length := and(div(slotValue, 2), 0x7f) - pos := (pos, length) - mstore(pos, and(slotValue, not(0xff))) - ret := add(pos, ) - } - case 1 { - // long byte array - let length := div(slotValue, 2) - pos := (pos, length) - let dataPos := (value) - let i := 0 - for { } lt(i, length) { i := add(i, 0x20) } { - mstore(add(pos, i), sload(dataPos)) - dataPos := add(dataPos, 1) - } - ret := add(pos, ) - } - } - )"); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("lengthPaddedShort", _options.padded ? "0x20" : "length"); - templ("lengthPaddedLong", _options.padded ? "i" : "length"); - templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from)); - return templ.render(); - } - else - { - // Multiple items per slot - solAssert(_from.baseType()->storageBytes() <= 16, ""); - solAssert(!_from.baseType()->isDynamicallyEncoded(), ""); - solAssert(!_to.baseType()->isDynamicallyEncoded(), ""); - solAssert(_from.baseType()->isValueType(), ""); - bool dynamic = _to.isDynamicallyEncoded(); - size_t storageBytes = _from.baseType()->storageBytes(); - size_t itemsPerSlot = 32 / storageBytes; - solAssert(itemsPerSlot > 0, ""); - // The number of elements we need to handle manually after the loop. - size_t spill = size_t(_from.length() % itemsPerSlot); - Whiskers templ( - R"( - // -> - function (value, pos) { - let length := (value) - pos := (pos, length) - let originalPos := pos - let srcPtr := (value) - let itemCounter := 0 - if { - // Run the loop over all full slots - for { } lt(add(itemCounter, sub(, 1)), length) - { itemCounter := add(itemCounter, ) } - { - let data := sload(srcPtr) - <#items> - ((data), pos) - pos := add(pos, ) - - srcPtr := add(srcPtr, 1) - } - } - // Handle the last (not necessarily full) slot specially - if { - let data := sload(srcPtr) - <#items> - if { - ((data), pos) - pos := add(pos, ) - itemCounter := add(itemCounter, 1) - } - - } - - } - )" - ); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("return", dynamic ? " -> end " : ""); - templ("assignEnd", dynamic ? "end := pos" : ""); - templ("lengthFun", m_utils.arrayLengthFunction(_from)); - templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options)); - templ("dataArea", m_utils.arrayDataAreaFunction(_from)); - // We skip the loop for arrays that fit a single slot. - if (_from.isDynamicallySized() || _from.length() >= itemsPerSlot) - templ("useLoop", "1"); - else - templ("useLoop", "0"); - if (_from.isDynamicallySized() || spill != 0) - templ("useSpill", "1"); - else - templ("useSpill", "0"); - templ("itemsPerSlot", to_string(itemsPerSlot)); - templ("stride", toCompactHexWithPrefix(_to.calldataStride())); - - EncodingOptions subOptions(_options); - subOptions.encodeFunctionFromStack = false; - subOptions.padded = true; - string encodeToMemoryFun = abiEncodingFunction( - *_from.baseType(), - *_to.baseType(), - subOptions - ); - templ("encodeToMemoryFun", encodeToMemoryFun); - std::vector> items(itemsPerSlot); - for (size_t i = 0; i < itemsPerSlot; ++i) - { - if (_from.isDynamicallySized()) - items[i]["inRange"] = "lt(itemCounter, length)"; - else if (i < spill) - items[i]["inRange"] = "1"; - else - items[i]["inRange"] = "0"; - items[i]["extractFromSlot"] = m_utils.extractFromStorageValue(*_from.baseType(), i * storageBytes, false); - } - templ("items", items); - return templ.render(); - } - }); -} - -string ABIFunctions::abiEncodingFunctionStruct( - StructType const& _from, - StructType const& _to, - EncodingOptions const& _options -) -{ - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - - solAssert(&_from.structDefinition() == &_to.structDefinition(), ""); - - return createFunction(functionName, [&]() { - bool dynamic = _to.isDynamicallyEncoded(); - Whiskers templ(R"( - // -> - function (value, pos) { - let tail := add(pos, ) - - <#members> - { - // - - let := - - } - - - } - )"); - templ("functionName", functionName); - templ("readableTypeNameFrom", _from.toString(true)); - templ("readableTypeNameTo", _to.toString(true)); - templ("return", dynamic ? " -> end " : ""); - if (dynamic && _options.dynamicInplace) - templ("assignEnd", "end := pos"); - else if (dynamic && !_options.dynamicInplace) - templ("assignEnd", "end := tail"); - else - templ("assignEnd", ""); - // to avoid multiple loads from the same slot for subsequent members - templ("init", _from.dataStoredIn(DataLocation::Storage) ? "let slotValue := 0" : ""); - u256 previousSlotOffset(-1); - u256 encodingOffset = 0; - vector> members; - for (auto const& member: _to.members(nullptr)) - { - solAssert(member.type, ""); - if (!member.type->canLiveOutsideStorage()) - continue; - TypePointer memberTypeTo = member.type->fullEncodingType(_options.encodeAsLibraryTypes, true, false); - solUnimplementedAssert(memberTypeTo, "Encoding type \"" + member.type->toString() + "\" not yet implemented."); - auto memberTypeFrom = _from.memberType(member.name); - solAssert(memberTypeFrom, ""); - bool dynamicMember = memberTypeTo->isDynamicallyEncoded(); - if (dynamicMember) - solAssert(dynamic, ""); - - members.push_back({}); - members.back()["preprocess"] = ""; - - switch (_from.location()) - { - case DataLocation::Storage: - { - solAssert(memberTypeFrom->isValueType() == memberTypeTo->isValueType(), ""); - u256 storageSlotOffset; - size_t intraSlotOffset; - tie(storageSlotOffset, intraSlotOffset) = _from.storageOffsetsOfMember(member.name); - if (memberTypeFrom->isValueType()) - { - if (storageSlotOffset != previousSlotOffset) - { - members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))"; - previousSlotOffset = storageSlotOffset; - } - members.back()["retrieveValue"] = m_utils.extractFromStorageValue(*memberTypeFrom, intraSlotOffset, false) + "(slotValue)"; - } - else - { - solAssert(memberTypeFrom->dataStoredIn(DataLocation::Storage), ""); - solAssert(intraSlotOffset == 0, ""); - members.back()["retrieveValue"] = "add(value, " + toCompactHexWithPrefix(storageSlotOffset) + ")"; - } - break; - } - case DataLocation::Memory: - { - string sourceOffset = toCompactHexWithPrefix(_from.memoryOffsetOfMember(member.name)); - members.back()["retrieveValue"] = "mload(add(value, " + sourceOffset + "))"; - break; - } - case DataLocation::CallData: - { - string sourceOffset = toCompactHexWithPrefix(_from.calldataOffsetOfMember(member.name)); - members.back()["retrieveValue"] = calldataAccessFunction(*memberTypeFrom) + "(value, add(value, " + sourceOffset + "))"; - break; - } - default: - solAssert(false, ""); - } - - EncodingOptions subOptions(_options); - subOptions.encodeFunctionFromStack = false; - // Like with arrays, struct members are always padded. - subOptions.padded = true; - - string memberValues = suffixedVariableNameList("memberValue", 0, numVariablesForType(*memberTypeFrom, subOptions)); - members.back()["memberValues"] = memberValues; - - string encode; - if (_options.dynamicInplace) - encode = Whiskers{"pos := (, pos)"} - ("encode", abiEncodeAndReturnUpdatedPosFunction(*memberTypeFrom, *memberTypeTo, subOptions)) - ("memberValues", memberValues) - .render(); - else - { - Whiskers encodeTempl( - dynamicMember ? - string(R"( - mstore(add(pos, ), sub(tail, pos)) - tail := (, tail) - )") : - "(, add(pos, ))" - ); - encodeTempl("memberValues", memberValues); - encodeTempl("encodingOffset", toCompactHexWithPrefix(encodingOffset)); - encodingOffset += memberTypeTo->calldataHeadSize(); - encodeTempl("abiEncode", abiEncodingFunction(*memberTypeFrom, *memberTypeTo, subOptions)); - encode = encodeTempl.render(); - } - members.back()["encode"] = encode; - - members.back()["memberName"] = member.name; - } - templ("members", members); - if (_options.dynamicInplace) - solAssert(encodingOffset == 0, "In-place encoding should enforce zero head size."); - templ("headSize", toCompactHexWithPrefix(encodingOffset)); - return templ.render(); - }); -} - -string ABIFunctions::abiEncodingFunctionStringLiteral( - Type const& _from, - Type const& _to, - EncodingOptions const& _options -) -{ - solAssert(_from.category() == Type::Category::StringLiteral, ""); - - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - return createFunction(functionName, [&]() { - auto const& strType = dynamic_cast(_from); - string const& value = strType.value(); - solAssert(_from.sizeOnStack() == 0, ""); - - if (_to.isDynamicallySized()) - { - solAssert(_to.category() == Type::Category::Array, ""); - Whiskers templ(R"( - function (pos) -> end { - pos := (pos, ) - <#word> - mstore(add(pos, ), ) - - end := add(pos, ) - } - )"); - templ("functionName", functionName); - - // TODO this can make use of CODECOPY for large strings once we have that in Yul - size_t words = (value.size() + 31) / 32; - templ("length", to_string(value.size())); - templ("storeLength", arrayStoreLengthForEncodingFunction(dynamic_cast(_to), _options)); - if (_options.padded) - templ("overallSize", to_string(words * 32)); - else - templ("overallSize", to_string(value.size())); - - vector> wordParams(words); - for (size_t i = 0; i < words; ++i) - { - wordParams[i]["offset"] = to_string(i * 32); - wordParams[i]["wordValue"] = formatAsStringOrNumber(value.substr(32 * i, 32)); - } - templ("word", wordParams); - return templ.render(); - } - else - { - solAssert(_to.category() == Type::Category::FixedBytes, ""); - solAssert(value.size() <= 32, ""); - Whiskers templ(R"( - function (pos) { - mstore(pos, ) - } - )"); - templ("functionName", functionName); - templ("wordValue", formatAsStringOrNumber(value)); - return templ.render(); - } - }); -} - -string ABIFunctions::abiEncodingFunctionFunctionType( - FunctionType const& _from, - Type const& _to, - EncodingOptions const& _options -) -{ - solAssert(_from.kind() == FunctionType::Kind::External, ""); - solAssert(_from == _to, ""); - - string functionName = - "abi_encode_" + - _from.identifier() + - "_to_" + - _to.identifier() + - _options.toFunctionNameSuffix(); - - if (_options.encodeFunctionFromStack) - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr, function_id, pos) { - mstore(pos, (addr, function_id)) - } - )") - ("functionName", functionName) - ("combineExtFun", m_utils.combineExternalFunctionIdFunction()) - .render(); - }); - else - return createFunction(functionName, [&]() { - return Whiskers(R"( - function (addr_and_function_id, pos) { - mstore(pos, (addr_and_function_id)) - } - )") - ("functionName", functionName) - ("cleanExtFun", m_utils.cleanupFunction(_to)) - .render(); - }); -} - -string ABIFunctions::abiDecodingFunction(Type const& _type, bool _fromMemory, bool _forUseOnStack) -{ - // The decoding function has to perform bounds checks unless it decodes a value type. - // Conversely, bounds checks have to be performed before the decoding function - // of a value type is called. - - TypePointer decodingType = _type.decodingType(); - solAssert(decodingType, ""); - - if (auto arrayType = dynamic_cast(decodingType)) - { - if (arrayType->dataStoredIn(DataLocation::CallData)) - { - solAssert(!_fromMemory, ""); - return abiDecodingFunctionCalldataArray(*arrayType); - } - else if (arrayType->isByteArray()) - return abiDecodingFunctionByteArray(*arrayType, _fromMemory); - else - return abiDecodingFunctionArray(*arrayType, _fromMemory); - } - else if (auto const* structType = dynamic_cast(decodingType)) - { - if (structType->dataStoredIn(DataLocation::CallData)) - { - solAssert(!_fromMemory, ""); - return abiDecodingFunctionCalldataStruct(*structType); - } - else - return abiDecodingFunctionStruct(*structType, _fromMemory); - } - else if (auto const* functionType = dynamic_cast(decodingType)) - return abiDecodingFunctionFunctionType(*functionType, _fromMemory, _forUseOnStack); - else - return abiDecodingFunctionValueType(_type, _fromMemory); -} - -string ABIFunctions::abiDecodingFunctionValueType(Type const& _type, bool _fromMemory) -{ - TypePointer decodingType = _type.decodingType(); - solAssert(decodingType, ""); - solAssert(decodingType->sizeOnStack() == 1, ""); - solAssert(decodingType->isValueType(), ""); - solAssert(!decodingType->isDynamicallyEncoded(), ""); - solAssert(decodingType->calldataEncodedSize() == 32, ""); - - string functionName = - "abi_decode_" + - _type.identifier() + - (_fromMemory ? "_fromMemory" : ""); - return createFunction(functionName, [&]() { - Whiskers templ(R"( - function (offset, end) -> value { - value := (offset) - (value) - } - )"); - templ("functionName", functionName); - templ("load", _fromMemory ? "mload" : "calldataload"); - // Validation should use the type and not decodingType, because e.g. - // the decoding type of an enum is a plain int. - templ("validator", m_utils.validatorFunction(_type, true)); - return templ.render(); - }); - -} - -string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory) -{ - solAssert(_type.dataStoredIn(DataLocation::Memory), ""); - solAssert(!_type.isByteArray(), ""); - - string functionName = - "abi_decode_" + - _type.identifier() + - (_fromMemory ? "_fromMemory" : ""); - - solAssert(!_type.dataStoredIn(DataLocation::Storage), ""); - - return createFunction(functionName, [&]() { - string load = _fromMemory ? "mload" : "calldataload"; - bool dynamicBase = _type.baseType()->isDynamicallyEncoded(); - Whiskers templ( - R"( - // - function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { } - let length := - array := ((length)) - let dst := array - // might update offset and dst - let src := offset - - for { let i := 0 } lt(i, length) { i := add(i, 1) } - { - let elementPos := - mstore(dst, (elementPos, end)) - dst := add(dst, 0x20) - src := add(src, ) - } - } - )" - ); - // TODO add test - templ("revertString", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); - templ("functionName", functionName); - templ("readableTypeName", _type.toString(true)); - templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)"); - templ("allocate", m_utils.allocationFunction()); - templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); - string calldataStride = toCompactHexWithPrefix(_type.calldataStride()); - templ("stride", calldataStride); - if (_type.isDynamicallySized()) - templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)"); - else - templ("storeLength", ""); - if (dynamicBase) - { - templ("staticBoundsCheck", ""); - templ("retrieveElementPos", "add(offset, " + load + "(src))"); - } - else - { - templ("staticBoundsCheck", "if gt(add(src, mul(length, " + - calldataStride + - ")), end) { " + - revertReasonIfDebug("ABI decoding: invalid calldata array stride") + - " }" - ); - templ("retrieveElementPos", "src"); - } - templ("decodingFun", abiDecodingFunction(*_type.baseType(), _fromMemory, false)); - return templ.render(); - }); -} - -string ABIFunctions::abiDecodingFunctionCalldataArray(ArrayType const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::CallData), ""); - if (!_type.isDynamicallySized()) - solAssert(_type.length() < u256("0xffffffffffffffff"), ""); - solAssert(_type.calldataStride() > 0, ""); - solAssert(_type.calldataStride() < u256("0xffffffffffffffff"), ""); - - string functionName = - "abi_decode_" + - _type.identifier(); - return createFunction(functionName, [&]() { - string templ; - if (_type.isDynamicallySized()) - templ = R"( - // - function (offset, end) -> arrayPos, length { - if iszero(slt(add(offset, 0x1f), end)) { } - length := calldataload(offset) - if gt(length, 0xffffffffffffffff) { } - arrayPos := add(offset, 0x20) - if gt(add(arrayPos, mul(length, )), end) { } - } - )"; - else - templ = R"( - // - function (offset, end) -> arrayPos { - arrayPos := offset - if gt(add(arrayPos, mul(, )), end) { } - } - )"; - Whiskers w{templ}; - // TODO add test - w("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid calldata array offset")); - w("revertStringLength", revertReasonIfDebug("ABI decoding: invalid calldata array length")); - w("revertStringPos", revertReasonIfDebug("ABI decoding: invalid calldata array stride")); - w("functionName", functionName); - w("readableTypeName", _type.toString(true)); - w("stride", toCompactHexWithPrefix(_type.calldataStride())); - if (!_type.isDynamicallySized()) - w("length", toCompactHexWithPrefix(_type.length())); - return w.render(); - }); -} - -string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory) -{ - solAssert(_type.dataStoredIn(DataLocation::Memory), ""); - solAssert(_type.isByteArray(), ""); - - string functionName = - "abi_decode_" + - _type.identifier() + - (_fromMemory ? "_fromMemory" : ""); - - return createFunction(functionName, [&]() { - Whiskers templ( - R"( - function (offset, end) -> array { - if iszero(slt(add(offset, 0x1f), end)) { } - let length := (offset) - array := ((length)) - mstore(array, length) - let src := add(offset, 0x20) - let dst := add(array, 0x20) - if gt(add(src, length), end) { } - (src, dst, length) - } - )" - ); - // TODO add test - templ("revertStringOffset", revertReasonIfDebug("ABI decoding: invalid byte array offset")); - templ("revertStringLength", revertReasonIfDebug("ABI decoding: invalid byte array length")); - templ("functionName", functionName); - templ("load", _fromMemory ? "mload" : "calldataload"); - templ("allocate", m_utils.allocationFunction()); - templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type)); - templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory)); - return templ.render(); - }); -} - -string ABIFunctions::abiDecodingFunctionCalldataStruct(StructType const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::CallData), ""); - string functionName = - "abi_decode_" + - _type.identifier(); - - return createFunction(functionName, [&]() { - Whiskers w{R"( - // - function (offset, end) -> value { - if slt(sub(end, offset), ) { } - value := offset - } - )"}; - // TODO add test - w("revertString", revertReasonIfDebug("ABI decoding: struct calldata too short")); - w("functionName", functionName); - w("readableTypeName", _type.toString(true)); - w("minimumSize", to_string(_type.isDynamicallyEncoded() ? _type.calldataEncodedTailSize() : _type.calldataEncodedSize(true))); - return w.render(); - }); -} - -string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory) -{ - solAssert(!_type.dataStoredIn(DataLocation::CallData), ""); - string functionName = - "abi_decode_" + - _type.identifier() + - (_fromMemory ? "_fromMemory" : ""); - - return createFunction(functionName, [&]() { - Whiskers templ(R"( - // - function (headStart, end) -> value { - if slt(sub(end, headStart), ) { } - value := () - <#members> - { - // - - } - - } - )"); - // TODO add test - templ("revertString", revertReasonIfDebug("ABI decoding: struct data too short")); - templ("functionName", functionName); - templ("readableTypeName", _type.toString(true)); - templ("allocate", m_utils.allocationFunction()); - solAssert(_type.memoryDataSize() < u256("0xffffffffffffffff"), ""); - templ("memorySize", toCompactHexWithPrefix(_type.memoryDataSize())); - size_t headPos = 0; - vector> members; - for (auto const& member: _type.members(nullptr)) - { - solAssert(member.type, ""); - solAssert(member.type->canLiveOutsideStorage(), ""); - auto decodingType = member.type->decodingType(); - solAssert(decodingType, ""); - bool dynamic = decodingType->isDynamicallyEncoded(); - Whiskers memberTempl( - dynamic ? - R"( - let offset := (add(headStart, )) - if gt(offset, 0xffffffffffffffff) { } - mstore(add(value, ), (add(headStart, offset), end)) - )" : - R"( - let offset := - mstore(add(value, ), (add(headStart, offset), end)) - )" - ); - // TODO add test - memberTempl("revertString", revertReasonIfDebug("ABI decoding: invalid struct offset")); - memberTempl("load", _fromMemory ? "mload" : "calldataload"); - memberTempl("pos", to_string(headPos)); - memberTempl("memoryOffset", toCompactHexWithPrefix(_type.memoryOffsetOfMember(member.name))); - memberTempl("abiDecode", abiDecodingFunction(*member.type, _fromMemory, false)); - - members.push_back({}); - members.back()["decode"] = memberTempl.render(); - members.back()["memberName"] = member.name; - headPos += decodingType->calldataHeadSize(); - } - templ("members", members); - templ("minimumSize", toCompactHexWithPrefix(headPos)); - return templ.render(); - }); -} - -string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack) -{ - solAssert(_type.kind() == FunctionType::Kind::External, ""); - - string functionName = - "abi_decode_" + - _type.identifier() + - (_fromMemory ? "_fromMemory" : "") + - (_forUseOnStack ? "_onStack" : ""); - - return createFunction(functionName, [&]() { - if (_forUseOnStack) - { - return Whiskers(R"( - function (offset, end) -> addr, function_selector { - addr, function_selector := ((offset, end)) - } - )") - ("functionName", functionName) - ("decodeFun", abiDecodingFunctionFunctionType(_type, _fromMemory, false)) - ("splitExtFun", m_utils.splitExternalFunctionIdFunction()) - .render(); - } - else - { - return Whiskers(R"( - function (offset, end) -> fun { - fun := (offset) - (fun) - } - )") - ("functionName", functionName) - ("load", _fromMemory ? "mload" : "calldataload") - ("validateExtFun", m_utils.validatorFunction(_type, true)) - .render(); - } - }); -} - -string ABIFunctions::calldataAccessFunction(Type const& _type) -{ - solAssert(_type.isValueType() || _type.dataStoredIn(DataLocation::CallData), ""); - string functionName = "calldata_access_" + _type.identifier(); - return createFunction(functionName, [&]() { - if (_type.isDynamicallyEncoded()) - { - unsigned int tailSize = _type.calldataEncodedTailSize(); - solAssert(tailSize > 1, ""); - Whiskers w(R"( - function (base_ref, ptr) -> { - let rel_offset_of_tail := calldataload(ptr) - if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } - value := add(rel_offset_of_tail, base_ref) - - } - )"); - if (_type.isDynamicallySized()) - { - auto const* arrayType = dynamic_cast(&_type); - solAssert(!!arrayType, ""); - w("handleLength", Whiskers(R"( - length := calldataload(value) - value := add(value, 0x20) - if gt(length, 0xffffffffffffffff) { } - if sgt(base_ref, sub(calldatasize(), mul(length, ))) { } - )") - ("calldataStride", toCompactHexWithPrefix(arrayType->calldataStride())) - // TODO add test - ("revertStringLength", revertReasonIfDebug("Invalid calldata access length")) - // TODO add test - ("revertStringStride", revertReasonIfDebug("Invalid calldata access stride")) - .render()); - w("return", "value, length"); - } - else - { - w("handleLength", ""); - w("return", "value"); - } - w("neededLength", toCompactHexWithPrefix(tailSize)); - w("functionName", functionName); - w("revertStringOffset", revertReasonIfDebug("Invalid calldata access offset")); - return w.render(); - } - else if (_type.isValueType()) - { - string decodingFunction; - if (auto const* functionType = dynamic_cast(&_type)) - decodingFunction = abiDecodingFunctionFunctionType(*functionType, false, false); - else - decodingFunction = abiDecodingFunctionValueType(_type, false); - // Note that the second argument to the decoding function should be discarded after inlining. - return Whiskers(R"( - function (baseRef, ptr) -> value { - value := (ptr, add(ptr, 32)) - } - )") - ("functionName", functionName) - ("decodingFunction", decodingFunction) - .render(); - } - else - { - solAssert( - _type.category() == Type::Category::Array || - _type.category() == Type::Category::Struct, - "" - ); - return Whiskers(R"( - function (baseRef, ptr) -> value { - value := ptr - } - )") - ("functionName", functionName) - .render(); - } - }); -} - -string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options) -{ - string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix(); - return createFunction(functionName, [&]() { - if (_type.isDynamicallySized() && !_options.dynamicInplace) - return Whiskers(R"( - function (pos, length) -> updated_pos { - mstore(pos, length) - updated_pos := add(pos, 0x20) - } - )") - ("functionName", functionName) - .render(); - else - return Whiskers(R"( - function (pos, length) -> updated_pos { - updated_pos := pos - } - )") - ("functionName", functionName) - .render(); - }); -} - -string ABIFunctions::createFunction(string const& _name, function const& _creator) -{ - return m_functionCollector->createFunction(_name, _creator); -} - -string ABIFunctions::createExternallyUsedFunction(string const& _name, function const& _creator) -{ - string name = createFunction(_name, _creator); - m_externallyUsedFunctions.insert(name); - return name; -} - -size_t ABIFunctions::headSize(TypePointers const& _targetTypes) -{ - size_t headSize = 0; - for (auto const& t: _targetTypes) - headSize += t->calldataHeadSize(); - - return headSize; -} - -size_t ABIFunctions::numVariablesForType(Type const& _type, EncodingOptions const& _options) -{ - if (_type.category() == Type::Category::Function && !_options.encodeFunctionFromStack) - return 1; - else - return _type.sizeOnStack(); -} - -std::string ABIFunctions::revertReasonIfDebug(std::string const& _message) -{ - return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); -} diff --git a/compiler/libsolidity/codegen/ABIFunctions.h b/compiler/libsolidity/codegen/ABIFunctions.h deleted file mode 100644 index a0b8333d..00000000 --- a/compiler/libsolidity/codegen/ABIFunctions.h +++ /dev/null @@ -1,267 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2017 - * Routines that generate Yul code related to ABI encoding, decoding and type conversions. - */ - -#pragma once - -#include -#include - -#include - -#include - -#include -#include -#include -#include - -namespace solidity::frontend -{ - -class Type; -class ArrayType; -class StructType; -class FunctionType; -using TypePointer = Type const*; -using TypePointers = std::vector; - -/** - * Class to generate encoding and decoding functions. Also maintains a collection - * of "functions to be generated" in order to avoid generating the same function - * multiple times. - * - * Make sure to include the result of ``requestedFunctions()`` to a block that - * is visible from the code that was generated here, or use named labels. - */ -class ABIFunctions -{ -public: - explicit ABIFunctions( - langutil::EVMVersion _evmVersion, - RevertStrings _revertStrings, - std::shared_ptr _functionCollector = std::make_shared() - ): - m_evmVersion(_evmVersion), - m_revertStrings(_revertStrings), - m_functionCollector(std::move(_functionCollector)), - m_utils(_evmVersion, m_revertStrings, m_functionCollector) - {} - - /// @returns name of an assembly function to ABI-encode values of @a _givenTypes - /// into memory, converting the types to @a _targetTypes on the fly. - /// Parameters are: ... , i.e. - /// the layout on the stack is ... with - /// the top of the stack on the right. - /// The values represent stack slots. If a type occupies more or less than one - /// stack slot, it takes exactly that number of values. - /// Returns a pointer to the end of the area written in memory. - /// Does not allocate memory (does not change the free memory pointer), but writes - /// to memory starting at $headStart and an unrestricted amount after that. - std::string tupleEncoder( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes = false - ); - - /// @returns name of an assembly function to encode values of @a _givenTypes - /// with packed encoding into memory, converting the types to @a _targetTypes on the fly. - /// Parameters are: ... , i.e. - /// the layout on the stack is ... with - /// the top of the stack on the right. - /// The values represent stack slots. If a type occupies more or less than one - /// stack slot, it takes exactly that number of values. - /// Returns a pointer to the end of the area written in memory. - /// Does not allocate memory (does not change the free memory pointer), but writes - /// to memory starting at memPos and an unrestricted amount after that. - std::string tupleEncoderPacked(TypePointers const& _givenTypes, TypePointers const& _targetTypes); - - /// @returns name of an assembly function to ABI-decode values of @a _types - /// into memory. If @a _fromMemory is true, decodes from memory instead of - /// from calldata. - /// Can allocate memory. - /// Inputs: (layout reversed on stack) - /// Outputs: ... - /// The values represent stack slots. If a type occupies more or less than one - /// stack slot, it takes exactly that number of values. - std::string tupleDecoder(TypePointers const& _types, bool _fromMemory = false); - - /// @returns concatenation of all generated functions and a set of the - /// externally used functions. - /// Clears the internal list, i.e. calling it again will result in an - /// empty return value. - std::pair> requestedFunctions(); - -private: - struct EncodingOptions - { - /// Pad/signextend value types and bytes/string to multiples of 32 bytes. - /// If false, data is always left-aligned. - /// Note that this is always re-set to true for the elements of arrays and structs. - bool padded = true; - /// Store arrays and structs in place without "data pointer" and do not store the length. - bool dynamicInplace = false; - /// Only for external function types: The value is a pair of address / function id instead - /// of a memory pointer to the compression representation. - bool encodeFunctionFromStack = false; - /// Encode storage pointers as storage pointers (we are targeting a library call). - bool encodeAsLibraryTypes = false; - - /// @returns a string to uniquely identify the encoding options for the encoding - /// function name. Skips everything that has its default value. - std::string toFunctionNameSuffix() const; - }; - - /// @returns the name of the ABI encoding function with the given type - /// and queues the generation of the function to the requested functions. - /// @param _fromStack if false, the input value was just loaded from storage - /// or memory and thus might be compacted into a single slot (depending on the type). - std::string abiEncodingFunction( - Type const& _givenType, - Type const& _targetType, - EncodingOptions const& _options - ); - /// @returns the name of a function that internally calls `abiEncodingFunction` - /// but always returns the updated encoding position, even if the type is - /// statically encoded. - std::string abiEncodeAndReturnUpdatedPosFunction( - Type const& _givenType, - Type const& _targetType, - EncodingOptions const& _options - ); - /// Part of @a abiEncodingFunction for array target type and given calldata array. - /// Uses calldatacopy and does not perform cleanup or validation and can therefore only - /// be used for byte arrays and arrays with the base type uint256 or bytes32. - std::string abiEncodingFunctionCalldataArrayWithoutCleanup( - Type const& _givenType, - Type const& _targetType, - EncodingOptions const& _options - ); - /// Part of @a abiEncodingFunction for array target type and given memory array or - /// a given storage array with every item occupies one or multiple full slots. - std::string abiEncodingFunctionSimpleArray( - ArrayType const& _givenType, - ArrayType const& _targetType, - EncodingOptions const& _options - ); - std::string abiEncodingFunctionMemoryByteArray( - ArrayType const& _givenType, - ArrayType const& _targetType, - EncodingOptions const& _options - ); - /// Part of @a abiEncodingFunction for array target type and given storage array - /// where multiple items are packed into the same storage slot. - std::string abiEncodingFunctionCompactStorageArray( - ArrayType const& _givenType, - ArrayType const& _targetType, - EncodingOptions const& _options - ); - - /// Part of @a abiEncodingFunction for struct types. - std::string abiEncodingFunctionStruct( - StructType const& _givenType, - StructType const& _targetType, - EncodingOptions const& _options - ); - - // @returns the name of the ABI encoding function with the given type - // and queues the generation of the function to the requested functions. - // Case for _givenType being a string literal - std::string abiEncodingFunctionStringLiteral( - Type const& _givenType, - Type const& _targetType, - EncodingOptions const& _options - ); - - std::string abiEncodingFunctionFunctionType( - FunctionType const& _from, - Type const& _to, - EncodingOptions const& _options - ); - - /// @returns the name of the ABI decoding function for the given type - /// and queues the generation of the function to the requested functions. - /// The caller has to ensure that no out of bounds access (at least to the static - /// part) can happen inside this function. - /// @param _fromMemory if decoding from memory instead of from calldata - /// @param _forUseOnStack if the decoded value is stored on stack or in memory. - std::string abiDecodingFunction( - Type const& _Type, - bool _fromMemory, - bool _forUseOnStack - ); - - /// Part of @a abiDecodingFunction for value types. - std::string abiDecodingFunctionValueType(Type const& _type, bool _fromMemory); - /// Part of @a abiDecodingFunction for "regular" array types. - std::string abiDecodingFunctionArray(ArrayType const& _type, bool _fromMemory); - /// Part of @a abiDecodingFunction for calldata array types. - std::string abiDecodingFunctionCalldataArray(ArrayType const& _type); - /// Part of @a abiDecodingFunction for byte array types. - std::string abiDecodingFunctionByteArray(ArrayType const& _type, bool _fromMemory); - /// Part of @a abiDecodingFunction for calldata struct types. - std::string abiDecodingFunctionCalldataStruct(StructType const& _type); - /// Part of @a abiDecodingFunction for struct types. - std::string abiDecodingFunctionStruct(StructType const& _type, bool _fromMemory); - /// Part of @a abiDecodingFunction for array types. - std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack); - - /// @returns the name of a function that retrieves an element from calldata. - std::string calldataAccessFunction(Type const& _type); - - /// @returns the name of a function used during encoding that stores the length - /// if the array is dynamically sized (and the options do not request in-place encoding). - /// It returns the new encoding position. - /// If the array is not dynamically sized (or in-place encoding was requested), - /// does nothing and just returns the position again. - std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options); - - /// Helper function that uses @a _creator to create a function and add it to - /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both - /// cases. - std::string createFunction(std::string const& _name, std::function const& _creator); - - /// Helper function that uses @a _creator to create a function and add it to - /// @a m_requestedFunctions if it has not been created yet and returns @a _name in both - /// cases. Also adds it to the list of externally used functions. - std::string createExternallyUsedFunction(std::string const& _name, std::function const& _creator); - - /// @returns the size of the static part of the encoding of the given types. - static size_t headSize(TypePointers const& _targetTypes); - - /// @returns the number of variables needed to store a type. - /// This is one for almost all types. The exception being dynamically sized calldata arrays or - /// external function types (if we are encoding from stack, i.e. _options.encodeFunctionFromStack - /// is true), for which it is two. - static size_t numVariablesForType(Type const& _type, EncodingOptions const& _options); - - /// @returns code that stores @param _message for revert reason - /// if m_revertStrings is debug. - std::string revertReasonIfDebug(std::string const& _message = ""); - - langutil::EVMVersion m_evmVersion; - RevertStrings const m_revertStrings; - std::shared_ptr m_functionCollector; - std::set m_externallyUsedFunctions; - YulUtilFunctions m_utils; -}; - -} diff --git a/compiler/libsolidity/codegen/ArrayUtils.cpp b/compiler/libsolidity/codegen/ArrayUtils.cpp deleted file mode 100644 index 5350be62..00000000 --- a/compiler/libsolidity/codegen/ArrayUtils.cpp +++ /dev/null @@ -1,1209 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2015 - * Code generation utils that handle arrays. - */ - -#include - -#include -#include -#include -#include -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::frontend; -using namespace solidity::langutil; - -void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const -{ - // this copies source to target and also clears target if it was larger - // need to leave "target_ref target_byte_off" on the stack at the end - - // stack layout: [source_ref] [source length] target_ref (top) -// solAssert(_targetType.location() == DataLocation::Storage, ""); - - TypePointer uint256 = TypeProvider::uint256(); - TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType(); - TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType(); - - // TODO unroll loop for small sizes - - bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; - bool fromCalldata = _sourceType.location() == DataLocation::CallData; - bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; - bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->storageBytes() <= 16; - bool haveByteOffsetTarget = !directCopy && targetBaseType->storageBytes() <= 16; - unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0); - - // stack: source_ref [source_length] target_ref - // store target_ref - for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); - // stack: target_ref source_ref [source_length] - // stack: target_ref source_ref [source_length] - // retrieve source length - if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) - retrieveLength(_sourceType); // otherwise, length is already there - if (_sourceType.location() == DataLocation::Memory && _sourceType.isDynamicallySized()) - { - // increment source pointer to point to data - m_context << Instruction::SWAP1 << u256(0x20); - m_context << Instruction::ADD << Instruction::SWAP1; - } - - // stack: target_ref source_ref source_length - TypePointer targetType = &_targetType; - TypePointer sourceType = &_sourceType; - m_context.callLowLevelFunction( - "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), - 3, - 1, - [=](CompilerContext& _context) - { - ArrayUtils utils(_context); - ArrayType const& _sourceType = dynamic_cast(*sourceType); - ArrayType const& _targetType = dynamic_cast(*targetType); - // stack: target_ref source_ref source_length - _context << Instruction::DUP3; - // stack: target_ref source_ref source_length target_ref - utils.retrieveLength(_targetType); - // stack: target_ref source_ref source_length target_ref target_length - if (_targetType.isDynamicallySized()) - // store new target length - if (!_targetType.isByteArray()) - // Otherwise, length will be stored below. - _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; - if (sourceBaseType->category() == Type::Category::Mapping) - { - solAssert(targetBaseType->category() == Type::Category::Mapping, ""); - solAssert(_sourceType.location() == DataLocation::Storage, ""); - // nothing to copy - _context - << Instruction::POP << Instruction::POP - << Instruction::POP << Instruction::POP; - return; - } - // stack: target_ref source_ref source_length target_ref target_length - // compute hashes (data positions) - _context << Instruction::SWAP1; - if (_targetType.isDynamicallySized()) - CompilerUtils(_context).computeHashStatic(); - // stack: target_ref source_ref source_length target_length target_data_pos - _context << Instruction::SWAP1; - utils.convertLengthToSize(_targetType); - _context << Instruction::DUP2 << Instruction::ADD; - // stack: target_ref source_ref source_length target_data_pos target_data_end - _context << Instruction::SWAP3; - // stack: target_ref target_data_end source_length target_data_pos source_ref - - evmasm::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); - - // special case for short byte arrays: Store them together with their length. - if (_targetType.isByteArray()) - { - // stack: target_ref target_data_end source_length target_data_pos source_ref - _context << Instruction::DUP3 << u256(31) << Instruction::LT; - evmasm::AssemblyItem longByteArray = _context.appendConditionalJump(); - // store the short byte array - solAssert(_sourceType.isByteArray(), ""); - if (_sourceType.location() == DataLocation::Storage) - { - // just copy the slot, it contains length and data - _context << Instruction::DUP1 << Instruction::SLOAD; - _context << Instruction::DUP6 << Instruction::SSTORE; - } - else - { - _context << Instruction::DUP1; - CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - // stack: target_ref target_data_end source_length target_data_pos source_ref value - // clear the lower-order byte - which will hold the length - _context << u256(0xff) << Instruction::NOT << Instruction::AND; - // fetch the length and shift it left by one - _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD; - // combine value and length and store them - _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE; - } - // end of special case, jump right into cleaning target data area - _context.appendJumpTo(copyLoopEndWithoutByteOffset); - _context << longByteArray; - // Store length (2*length+1) - _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - _context << u256(1) << Instruction::ADD; - _context << Instruction::DUP6 << Instruction::SSTORE; - } - - // skip copying if source length is zero - _context << Instruction::DUP3 << Instruction::ISZERO; - _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); - - if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) - CompilerUtils(_context).computeHashStatic(); - // stack: target_ref target_data_end source_length target_data_pos source_data_pos - _context << Instruction::SWAP2; - utils.convertLengthToSize(_sourceType); - _context << Instruction::DUP3 << Instruction::ADD; - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end - if (haveByteOffsetTarget) - _context << u256(0); - if (haveByteOffsetSource) - _context << u256(0); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - evmasm::AssemblyItem copyLoopStart = _context.newTag(); - _context << copyLoopStart; - // check for loop condition - _context - << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) - << Instruction::GT << Instruction::ISZERO; - evmasm::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // copy - if (sourceBaseType->category() == Type::Category::Array) - { - solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); - auto const& sourceBaseArrayType = dynamic_cast(*sourceBaseType); - _context << Instruction::DUP3; - if (sourceBaseArrayType.location() == DataLocation::Memory) - _context << Instruction::MLOAD; - _context << Instruction::DUP3; - utils.copyArrayToStorage(dynamic_cast(*targetBaseType), sourceBaseArrayType); - _context << Instruction::POP; - } - else if (directCopy) - { - solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); - _context - << Instruction::DUP3 << Instruction::SLOAD - << Instruction::DUP3 << Instruction::SSTORE; - } - else - { - // Note that we have to copy each element on its own in case conversion is involved. - // We might copy too much if there is padding at the last element, but this way end - // checking is easier. - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - _context << dupInstruction(3 + byteOffsetSize); - if (_sourceType.location() == DataLocation::Storage) - { - if (haveByteOffsetSource) - _context << Instruction::DUP2; - else - _context << u256(0); - StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); - } - else if (sourceBaseType->isValueType()) - CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); - else - solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... - solAssert( - 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, - "Stack too deep, try removing local variables." - ); - // fetch target storage reference - _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); - if (haveByteOffsetTarget) - _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); - else - _context << u256(0); - StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); - } - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] - // increment source - if (haveByteOffsetSource) - utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); - else - { - _context << swapInstruction(2 + byteOffsetSize); - if (sourceIsStorage) - _context << sourceBaseType->storageSize(); - else if (_sourceType.location() == DataLocation::Memory) - _context << sourceBaseType->memoryHeadSize(); - else - _context << sourceBaseType->calldataHeadSize(); - _context - << Instruction::ADD - << swapInstruction(2 + byteOffsetSize); - } - // increment target - if (haveByteOffsetTarget) - utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - else - _context - << swapInstruction(1 + byteOffsetSize) - << targetBaseType->storageSize() - << Instruction::ADD - << swapInstruction(1 + byteOffsetSize); - _context.appendJumpTo(copyLoopStart); - _context << copyLoopEnd; - if (haveByteOffsetTarget) - { - // clear elements that might be left over in the current slot in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] - _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; - evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); - _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); - StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); - utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); - _context.appendJumpTo(copyLoopEnd); - - _context << copyCleanupLoopEnd; - _context << Instruction::POP; // might pop the source, but then target is popped next - } - if (haveByteOffsetSource) - _context << Instruction::POP; - _context << copyLoopEndWithoutByteOffset; - - // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end - _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; - // stack: target_ref target_data_end target_data_pos_updated - utils.clearStorageLoop(targetBaseType); - _context << Instruction::POP; - } - ); -} - -void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const -{ - solUnimplementedAssert( - !_sourceType.baseType()->isDynamicallySized(), - "Nested dynamic arrays not implemented here." - ); - CompilerUtils utils(m_context); - - if (_sourceType.location() == DataLocation::CallData) - { - if (!_sourceType.isDynamicallySized()) - m_context << _sourceType.length(); - if (!_sourceType.isByteArray()) - convertLengthToSize(_sourceType); - - string routine = "calldatacopy(target, source, len)\n"; - if (_padToWordBoundaries) - routine += R"( - // Set padding suffix to zero - mstore(add(target, len), 0) - len := and(add(len, 0x1f), not(0x1f)) - )"; - routine += "target := add(target, len)\n"; - m_context.appendInlineAssembly("{" + routine + "}", {"target", "source", "len"}); - m_context << Instruction::POP << Instruction::POP; - } - else if (_sourceType.location() == DataLocation::Memory) - { - retrieveLength(_sourceType); - // stack: target source length - if (!_sourceType.baseType()->isValueType()) - { - // copy using a loop - m_context << u256(0) << Instruction::SWAP3; - // stack: counter source length target - auto repeat = m_context.newTag(); - m_context << repeat; - m_context << Instruction::DUP2 << Instruction::DUP5; - m_context << Instruction::LT << Instruction::ISZERO; - auto loopEnd = m_context.appendConditionalJump(); - m_context << Instruction::DUP3 << Instruction::DUP5; - accessIndex(_sourceType, false); - MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType())) - copyArrayToMemory(*baseArray, _padToWordBoundaries); - else - utils.storeInMemoryDynamic(*_sourceType.baseType()); - m_context << Instruction::SWAP3 << u256(1) << Instruction::ADD; - m_context << Instruction::SWAP3; - m_context.appendJumpTo(repeat); - m_context << loopEnd; - m_context << Instruction::SWAP3; - utils.popStackSlots(3); - // stack: updated_target_pos - return; - } - - // memcpy using the built-in contract - if (_sourceType.isDynamicallySized()) - { - // change pointer to data part - m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; - m_context << Instruction::SWAP1; - } - if (!_sourceType.isByteArray()) - convertLengthToSize(_sourceType); - // stack: - m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; - // We can resort to copying full 32 bytes only if - // - the length is known to be a multiple of 32 or - // - we will pad to full 32 bytes later anyway. - if (!_sourceType.isByteArray() || _padToWordBoundaries) - utils.memoryCopy32(); - else - utils.memoryCopy(); - - m_context << Instruction::SWAP1 << Instruction::POP; - // stack: - - bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArray(); - - if (paddingNeeded) - { - // stack: - m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD; - // stack: - m_context << Instruction::SWAP1 << u256(31) << Instruction::AND; - // stack: - evmasm::AssemblyItem skip = m_context.newTag(); - if (_sourceType.isDynamicallySized()) - { - m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalJumpTo(skip); - } - // round off, load from there. - // stack - m_context << Instruction::DUP1 << Instruction::DUP3; - m_context << Instruction::SUB; - // stack: target+size remainder - m_context << Instruction::DUP1 << Instruction::MLOAD; - // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) - m_context << u256(1); - m_context << Instruction::DUP4 << u256(32) << Instruction::SUB; - // stack: ... 1 <32 - remainder> - m_context << u256(0x100) << Instruction::EXP << Instruction::SUB; - m_context << Instruction::NOT << Instruction::AND; - // stack: target+size remainder target+size-remainder - m_context << Instruction::DUP2 << Instruction::MSTORE; - // stack: target+size remainder target+size-remainder - m_context << u256(32) << Instruction::ADD; - // stack: target+size remainder - m_context << Instruction::SWAP2 << Instruction::POP; - - if (_sourceType.isDynamicallySized()) - m_context << skip.tag(); - // stack - m_context << Instruction::POP; - } - else - // stack: - m_context << Instruction::ADD; - } - else - { - solAssert(_sourceType.location() == DataLocation::Storage, ""); - unsigned storageBytes = _sourceType.baseType()->storageBytes(); - u256 storageSize = _sourceType.baseType()->storageSize(); - solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); - - retrieveLength(_sourceType); - // stack here: memory_offset storage_offset length - // jump to end if length is zero - m_context << Instruction::DUP1 << Instruction::ISZERO; - evmasm::AssemblyItem loopEnd = m_context.appendConditionalJump(); - // Special case for tightly-stored byte arrays - if (_sourceType.isByteArray()) - { - // stack here: memory_offset storage_offset length - m_context << Instruction::DUP1 << u256(31) << Instruction::LT; - evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); - // store the short byte array (discard lower-order byte) - m_context << u256(0x100) << Instruction::DUP1; - m_context << Instruction::DUP4 << Instruction::SLOAD; - m_context << Instruction::DIV << Instruction::MUL; - m_context << Instruction::DUP4 << Instruction::MSTORE; - // stack here: memory_offset storage_offset length - // add 32 or length to memory offset - m_context << Instruction::SWAP2; - if (_padToWordBoundaries) - m_context << u256(32); - else - m_context << Instruction::DUP3; - m_context << Instruction::ADD; - m_context << Instruction::SWAP2; - m_context.appendJumpTo(loopEnd); - m_context << longByteArray; - } - else - // convert length to memory size - m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL; - - m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; - if (_sourceType.isDynamicallySized()) - { - // actual array data is stored at KECCAK256(storage_offset) - m_context << Instruction::SWAP1; - utils.computeHashStatic(); - m_context << Instruction::SWAP1; - } - - // stack here: memory_end_offset storage_data_offset memory_offset - bool haveByteOffset = !_sourceType.isByteArray() && storageBytes <= 16; - if (haveByteOffset) - m_context << u256(0) << Instruction::SWAP1; - // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset - evmasm::AssemblyItem loopStart = m_context.newTag(); - m_context << loopStart; - // load and store - if (_sourceType.isByteArray()) - { - // Packed both in storage and memory. - m_context << Instruction::DUP2 << Instruction::SLOAD; - m_context << Instruction::DUP2 << Instruction::MSTORE; - // increment storage_data_offset by 1 - m_context << Instruction::SWAP1 << u256(1) << Instruction::ADD; - // increment memory offset by 32 - m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; - } - else - { - // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset - if (haveByteOffset) - m_context << Instruction::DUP3 << Instruction::DUP3; - else - m_context << Instruction::DUP2 << u256(0); - StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); - if (auto baseArray = dynamic_cast(_sourceType.baseType())) - copyArrayToMemory(*baseArray, _padToWordBoundaries); - else - utils.storeInMemoryDynamic(*_sourceType.baseType()); - // increment storage_data_offset and byte offset - if (haveByteOffset) - incrementByteOffset(storageBytes, 2, 3); - else - { - m_context << Instruction::SWAP1; - m_context << storageSize << Instruction::ADD; - m_context << Instruction::SWAP1; - } - } - // check for loop condition - m_context << Instruction::DUP1 << dupInstruction(haveByteOffset ? 5 : 4); - m_context << Instruction::GT; - m_context.appendConditionalJumpTo(loopStart); - // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset - if (haveByteOffset) - m_context << Instruction::SWAP1 << Instruction::POP; - if (!_sourceType.isByteArray()) - { - solAssert(_sourceType.calldataStride() % 32 == 0, ""); - solAssert(_sourceType.memoryStride() % 32 == 0, ""); - } - if (_padToWordBoundaries && _sourceType.isByteArray()) - { - // memory_end_offset - start is the actual length (we want to compute the ceil of). - // memory_offset - start is its next multiple of 32, but it might be off by 32. - // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 - m_context << Instruction::DUP3 << Instruction::SWAP1 << Instruction::SUB; - m_context << u256(31) << Instruction::AND; - m_context << Instruction::DUP3 << Instruction::ADD; - m_context << Instruction::SWAP2; - } - m_context << loopEnd << Instruction::POP << Instruction::POP; - } -} - -void ArrayUtils::clearArray(ArrayType const& _typeIn) const -{ - TypePointer type = &_typeIn; - m_context.callLowLevelFunction( - "$clearArray_" + _typeIn.identifier(), - 2, - 0, - [type](CompilerContext& _context) - { - ArrayType const& _type = dynamic_cast(*type); - unsigned stackHeightStart = _context.stackHeight(); - solAssert(_type.location() == DataLocation::Storage, ""); - if (_type.baseType()->storageBytes() < 32) - { - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); - } - if (_type.baseType()->isValueType()) - solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); - - _context << Instruction::POP; // remove byte offset - if (_type.isDynamicallySized()) - ArrayUtils(_context).clearDynamicArray(_type); - else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) - _context << Instruction::POP; - else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) - { - // unroll loop for small arrays @todo choose a good value - // Note that we loop over storage slots here, not elements. - for (unsigned i = 1; i < _type.storageSize(); ++i) - _context - << u256(0) << Instruction::DUP2 << Instruction::SSTORE - << u256(1) << Instruction::ADD; - _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; - } - else if (!_type.baseType()->isValueType() && _type.length() <= 4) - { - // unroll loop for small arrays @todo choose a good value - solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); - for (unsigned i = 1; i < _type.length(); ++i) - { - _context << u256(0); - StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); - _context - << Instruction::POP - << u256(_type.baseType()->storageSize()) << Instruction::ADD; - } - _context << u256(0); - StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); - } - else - { - _context << Instruction::DUP1 << _type.length(); - ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::ADD << Instruction::SWAP1; - if (_type.baseType()->storageBytes() < 32) - ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); - else - ArrayUtils(_context).clearStorageLoop(_type.baseType()); - _context << Instruction::POP; - } - solAssert(_context.stackHeight() == stackHeightStart - 2, ""); - } - ); -} - -void ArrayUtils::clearDynamicArray(ArrayType const& _type) const -{ - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - - // fetch length - retrieveLength(_type); - // set length to zero - m_context << u256(0) << Instruction::DUP3 << Instruction::SSTORE; - // Special case: short byte arrays are stored togeher with their length - evmasm::AssemblyItem endTag = m_context.newTag(); - if (_type.isByteArray()) - { - // stack: ref old_length - m_context << Instruction::DUP1 << u256(31) << Instruction::LT; - evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); - m_context << Instruction::POP; - m_context.appendJumpTo(endTag); - m_context.adjustStackOffset(1); // needed because of jump - m_context << longByteArray; - } - // stack: ref old_length - convertLengthToSize(_type); - // compute data positions - m_context << Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos - m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD - << Instruction::SWAP1; - // stack: data_pos_end data_pos - if (_type.storageStride() < 32) - clearStorageLoop(TypeProvider::uint256()); - else - clearStorageLoop(_type.baseType()); - // cleanup - m_context << endTag; - m_context << Instruction::POP; -} - -void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const -{ - TypePointer type = &_typeIn; - m_context.callLowLevelFunction( - "$resizeDynamicArray_" + _typeIn.identifier(), - 2, - 0, - [type](CompilerContext& _context) - { - ArrayType const& _type = dynamic_cast(*type); - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - unsigned stackHeightStart = _context.stackHeight(); - evmasm::AssemblyItem resizeEnd = _context.newTag(); - - // stack: ref new_length - // fetch old length - ArrayUtils(_context).retrieveLength(_type, 1); - // stack: ref new_length old_length - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); - - // Special case for short byte arrays, they are stored together with their length - if (_type.isByteArray()) - { - evmasm::AssemblyItem regularPath = _context.newTag(); - // We start by a large case-distinction about the old and new length of the byte array. - - _context << Instruction::DUP3 << Instruction::SLOAD; - // stack: ref new_length current_length ref_value - - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context << Instruction::DUP2 << u256(31) << Instruction::LT; - evmasm::AssemblyItem currentIsLong = _context.appendConditionalJump(); - _context << Instruction::DUP3 << u256(31) << Instruction::LT; - evmasm::AssemblyItem newIsLong = _context.appendConditionalJump(); - - // Here: short -> short - - // Compute 1 << (256 - 8 * new_size) - evmasm::AssemblyItem shortToShort = _context.newTag(); - _context << shortToShort; - _context << Instruction::DUP3 << u256(8) << Instruction::MUL; - _context << u256(0x100) << Instruction::SUB; - _context << u256(2) << Instruction::EXP; - // Divide and multiply by that value, clearing bits. - _context << Instruction::DUP1 << Instruction::SWAP2; - _context << Instruction::DIV << Instruction::MUL; - // Insert 2*length. - _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; - _context << Instruction::OR; - // Store. - _context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - _context.appendJumpTo(resizeEnd); - - _context.adjustStackOffset(1); // we have to do that because of the jumps - // Here: short -> long - - _context << newIsLong; - // stack: ref new_length current_length ref_value - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - // Zero out lower-order byte. - _context << u256(0xff) << Instruction::NOT << Instruction::AND; - // Store at data location. - _context << Instruction::DUP4; - CompilerUtils(_context).computeHashStatic(); - _context << Instruction::SSTORE; - // stack: ref new_length current_length - // Store new length: Compule 2*length + 1 and store it. - _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; - _context << u256(1) << Instruction::ADD; - // stack: ref new_length current_length 2*new_length+1 - _context << Instruction::DUP4 << Instruction::SSTORE; - solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); - _context.appendJumpTo(resizeEnd); - - _context.adjustStackOffset(1); // we have to do that because of the jumps - - _context << currentIsLong; - _context << Instruction::DUP3 << u256(31) << Instruction::LT; - _context.appendConditionalJumpTo(regularPath); - - // Here: long -> short - // Read the first word of the data and store it on the stack. Clear the data location and - // then jump to the short -> short case. - - // stack: ref new_length current_length ref_value - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context << Instruction::POP << Instruction::DUP3; - CompilerUtils(_context).computeHashStatic(); - _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location - _context << Instruction::DUP3; - ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; - // stack: ref new_length current_length first_word data_location_end data_location - ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); - _context << Instruction::POP; - // stack: ref new_length current_length first_word - solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); - _context.appendJumpTo(shortToShort); - - _context << regularPath; - // stack: ref new_length current_length ref_value - _context << Instruction::POP; - } - - // Change of length for a regular array (i.e. length at location, data at KECCAK256(location)). - // stack: ref new_length old_length - // store new length - _context << Instruction::DUP2; - if (_type.isByteArray()) - // For a "long" byte array, store length as 2*length+1 - _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; - _context << Instruction::DUP4 << Instruction::SSTORE; - // skip if size is not reduced - _context << Instruction::DUP2 << Instruction::DUP2 - << Instruction::GT << Instruction::ISZERO; - _context.appendConditionalJumpTo(resizeEnd); - - // size reduced, clear the end of the array - // stack: ref new_length old_length - ArrayUtils(_context).convertLengthToSize(_type); - _context << Instruction::DUP2; - ArrayUtils(_context).convertLengthToSize(_type); - // stack: ref new_length old_size new_size - // compute data positions - _context << Instruction::DUP4; - CompilerUtils(_context).computeHashStatic(); - // stack: ref new_length old_size new_size data_pos - _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; - // stack: ref new_length data_pos new_size delete_end - _context << Instruction::SWAP2 << Instruction::ADD; - // stack: ref new_length delete_end delete_start - if (_type.storageStride() < 32) - ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); - else - ArrayUtils(_context).clearStorageLoop(_type.baseType()); - - _context << resizeEnd; - // cleanup - _context << Instruction::POP << Instruction::POP << Instruction::POP; - solAssert(_context.stackHeight() == stackHeightStart - 2, ""); - } - ); -} - -void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const -{ - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - if (_type.isByteArray()) - { - // We almost always just add 2 (length of byte arrays is shifted left by one) - // except for the case where we transition from a short byte array - // to a long byte array, there we have to copy. - // This happens if the length is exactly 31, which means that the - // lowest-order byte (we actually use a mask with fewer bits) must - // be (31*2+0) = 62 - - m_context.appendInlineAssembly(R"({ - let data := sload(ref) - let shifted_length := and(data, 63) - // We have to copy if length is exactly 31, because that marks - // the transition between in-place and out-of-place storage. - switch shifted_length - case 62 - { - mstore(0, ref) - let data_area := keccak256(0, 0x20) - sstore(data_area, and(data, not(0xff))) - // New length is 32, encoded as (32 * 2 + 1) - sstore(ref, 65) - // Replace ref variable by new length - ref := 32 - } - default - { - sstore(ref, add(data, 2)) - // Replace ref variable by new length - if iszero(and(data, 1)) { data := shifted_length } - ref := add(div(data, 2), 1) - } - })", {"ref"}); - } - else - m_context.appendInlineAssembly(R"({ - let new_length := add(sload(ref), 1) - sstore(ref, new_length) - ref := new_length - })", {"ref"}); -} - -void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const -{ - solAssert(_type.location() == DataLocation::Storage, ""); - solAssert(_type.isDynamicallySized(), ""); - if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32) - solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); - - if (_type.isByteArray()) - { - m_context.appendInlineAssembly(R"({ - let slot_value := sload(ref) - switch and(slot_value, 1) - case 0 { - // short byte array - let length := and(div(slot_value, 2), 0x1f) - if iszero(length) { invalid() } - - // Zero-out the suffix including the least significant byte. - let mask := sub(exp(0x100, sub(33, length)), 1) - length := sub(length, 1) - slot_value := or(and(not(mask), slot_value), mul(length, 2)) - sstore(ref, slot_value) - } - case 1 { - // long byte array - mstore(0, ref) - let length := div(slot_value, 2) - let slot := keccak256(0, 0x20) - switch length - case 32 - { - let data := sload(slot) - sstore(slot, 0) - data := and(data, not(0xff)) - sstore(ref, or(data, 62)) - } - default - { - let offset_inside_slot := and(sub(length, 1), 0x1f) - slot := add(slot, div(sub(length, 1), 32)) - let data := sload(slot) - - // Zero-out the suffix of the byte array by masking it. - // ((1<<(8 * (32 - offset))) - 1) - let mask := sub(exp(0x100, sub(32, offset_inside_slot)), 1) - data := and(not(mask), data) - sstore(slot, data) - - // Reduce the length by 1 - slot_value := sub(slot_value, 2) - sstore(ref, slot_value) - } - } - })", {"ref"}); - m_context << Instruction::POP; - } - else - { - // stack: ArrayReference - retrieveLength(_type); - // stack: ArrayReference oldLength - m_context << Instruction::DUP1; - // stack: ArrayReference oldLength oldLength - m_context << Instruction::ISZERO; - m_context.appendConditionalInvalid(); - - // Stack: ArrayReference oldLength - m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; - // Stack ArrayReference newLength - - if (_type.baseType()->category() != Type::Category::Mapping) - { - m_context << Instruction::DUP2 << Instruction::DUP2; - // Stack ArrayReference newLength ArrayReference newLength; - accessIndex(_type, false); - // Stack: ArrayReference newLength storage_slot byte_offset - StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); - } - - // Stack: ArrayReference newLength - m_context << Instruction::SWAP1 << Instruction::SSTORE; - } -} - -void ArrayUtils::clearStorageLoop(TypePointer _type) const -{ - m_context.callLowLevelFunction( - "$clearStorageLoop_" + _type->identifier(), - 2, - 1, - [_type](CompilerContext& _context) - { - unsigned stackHeightStart = _context.stackHeight(); - if (_type->category() == Type::Category::Mapping) - { - _context << Instruction::POP; - return; - } - // stack: end_pos pos - - // jump to and return from the loop to allow for duplicate code removal - evmasm::AssemblyItem returnTag = _context.pushNewTag(); - _context << Instruction::SWAP2 << Instruction::SWAP1; - - // stack: end_pos pos - evmasm::AssemblyItem loopStart = _context.appendJumpToNew(); - _context << loopStart; - // check for loop condition - _context << - Instruction::DUP1 << - Instruction::DUP3 << - Instruction::GT << - Instruction::ISZERO; - evmasm::AssemblyItem zeroLoopEnd = _context.newTag(); - _context.appendConditionalJumpTo(zeroLoopEnd); - // delete - _context << u256(0); - StorageItem(_context, *_type).setToZero(SourceLocation(), false); - _context << Instruction::POP; - // increment - _context << _type->storageSize() << Instruction::ADD; - _context.appendJumpTo(loopStart); - // cleanup - _context << zeroLoopEnd; - _context << Instruction::POP << Instruction::SWAP1; - // "return" - _context << Instruction::JUMP; - - _context << returnTag; - solAssert(_context.stackHeight() == stackHeightStart - 1, ""); - } - ); -} - -void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const -{ - if (_arrayType.location() == DataLocation::Storage) - { - if (_arrayType.baseType()->storageSize() <= 1) - { - unsigned baseBytes = _arrayType.baseType()->storageBytes(); - if (baseBytes == 0) - m_context << Instruction::POP << u256(1); - else if (baseBytes <= 16) - { - unsigned itemsPerSlot = 32 / baseBytes; - m_context - << u256(itemsPerSlot - 1) << Instruction::ADD - << u256(itemsPerSlot) << Instruction::SWAP1 << Instruction::DIV; - } - } - else - m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; - } - else - { - if (!_arrayType.isByteArray()) - { - if (_arrayType.location() == DataLocation::Memory) - m_context << _arrayType.memoryStride(); - else - m_context << _arrayType.calldataStride(); - m_context << Instruction::MUL; - } - else if (_pad) - m_context << u256(31) << Instruction::ADD - << u256(32) << Instruction::DUP1 - << Instruction::SWAP2 << Instruction::DIV << Instruction::MUL; - } -} - -void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth) const -{ - if (!_arrayType.isDynamicallySized()) - m_context << _arrayType.length(); - else - { - m_context << dupInstruction(1 + _stackDepth); - switch (_arrayType.location()) - { - case DataLocation::CallData: - // length is stored on the stack - break; - case DataLocation::Memory: - m_context << Instruction::MLOAD; - break; - case DataLocation::Storage: - m_context << Instruction::SLOAD; - if (_arrayType.isByteArray()) - { - // Retrieve length both for in-place strings and off-place strings: - // Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2 - // i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it - // computes (x & (-1)) / 2, which is equivalent to just x / 2. - m_context << u256(1) << Instruction::DUP2 << u256(1) << Instruction::AND; - m_context << Instruction::ISZERO << u256(0x100) << Instruction::MUL; - m_context << Instruction::SUB << Instruction::AND; - m_context << u256(2) << Instruction::SWAP1 << Instruction::DIV; - } - break; - } - } -} - -void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const -{ - /// Stack: reference [length] index - DataLocation location = _arrayType.location(); - - if (_doBoundsCheck) - { - // retrieve length - ArrayUtils::retrieveLength(_arrayType, 1); - // Stack: ref [length] index length - // check out-of-bounds access - m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; - // out-of-bounds access throws exception - m_context.appendConditionalInvalid(); - } - if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) - // remove length if present - m_context << Instruction::SWAP1 << Instruction::POP; - - // stack: - switch (location) - { - case DataLocation::Memory: - // stack: - if (!_arrayType.isByteArray()) - m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL; - if (_arrayType.isDynamicallySized()) - m_context << u256(32) << Instruction::ADD; - if (_keepReference) - m_context << Instruction::DUP2; - m_context << Instruction::ADD; - break; - case DataLocation::CallData: - if (!_arrayType.isByteArray()) - { - m_context << _arrayType.calldataStride(); - m_context << Instruction::MUL; - } - // stack: - if (_keepReference) - m_context << Instruction::DUP2; - m_context << Instruction::ADD; - break; - case DataLocation::Storage: - { - if (_keepReference) - m_context << Instruction::DUP2; - else - m_context << Instruction::SWAP1; - // stack: [] - - evmasm::AssemblyItem endTag = m_context.newTag(); - if (_arrayType.isByteArray()) - { - // Special case of short byte arrays. - m_context << Instruction::SWAP1; - m_context << Instruction::DUP2 << Instruction::SLOAD; - m_context << u256(1) << Instruction::AND << Instruction::ISZERO; - // No action needed for short byte arrays. - m_context.appendConditionalJumpTo(endTag); - m_context << Instruction::SWAP1; - } - if (_arrayType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); - m_context << Instruction::SWAP1; - if (_arrayType.baseType()->storageBytes() <= 16) - { - // stack: - // goal: - // = <(index % itemsPerSlot) * byteSize> - unsigned byteSize = _arrayType.baseType()->storageBytes(); - solAssert(byteSize != 0, ""); - unsigned itemsPerSlot = 32 / byteSize; - m_context << u256(itemsPerSlot) << Instruction::SWAP2; - // stack: itemsPerSlot index data_ref - m_context - << Instruction::DUP3 << Instruction::DUP3 - << Instruction::DIV << Instruction::ADD - // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) - << Instruction::SWAP2 << Instruction::SWAP1 - << Instruction::MOD; - if (byteSize != 1) - m_context << u256(byteSize) << Instruction::MUL; - } - else - { - if (_arrayType.baseType()->storageSize() != 1) - m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; - m_context << Instruction::ADD << u256(0); - } - m_context << endTag; - break; - } - } -} - -void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const -{ - solAssert(_arrayType.location() == DataLocation::CallData, ""); - if (_arrayType.baseType()->isDynamicallyEncoded()) - { - // stack layout: - ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true); - // stack layout: - - CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType()); - // stack layout: [length] - } - else - { - ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck); - if (_arrayType.baseType()->isValueType()) - { - solAssert(_arrayType.baseType()->storageBytes() <= 32, ""); - if ( - !_arrayType.isByteArray() && - _arrayType.baseType()->storageBytes() < 32 && - m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2) - ) - { - m_context << u256(32); - CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false); - } - else - CompilerUtils(m_context).loadFromMemoryDynamic( - *_arrayType.baseType(), - true, - !_arrayType.isByteArray(), - false - ); - } - else - solAssert( - _arrayType.baseType()->category() == Type::Category::Struct || - _arrayType.baseType()->category() == Type::Category::Array, - "Invalid statically sized non-value base type on array access." - ); - } -} - -void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const -{ - solAssert(_byteSize < 32, ""); - solAssert(_byteSize != 0, ""); - // We do the following, but avoiding jumps: - // byteOffset += byteSize - // if (byteOffset + byteSize > 32) - // { - // storageOffset++; - // byteOffset = 0; - // } - if (_byteOffsetPosition > 1) - m_context << swapInstruction(_byteOffsetPosition - 1); - m_context << u256(_byteSize) << Instruction::ADD; - if (_byteOffsetPosition > 1) - m_context << swapInstruction(_byteOffsetPosition - 1); - // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 - m_context - << u256(32) << dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) - << Instruction::ADD << Instruction::DIV; - // increment storage offset if X == 1 (just add X to it) - // stack: X - m_context - << swapInstruction(_storageOffsetPosition) << dupInstruction(_storageOffsetPosition + 1) - << Instruction::ADD << swapInstruction(_storageOffsetPosition); - // stack: X - // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) - m_context << u256(1) << Instruction::SUB; - // stack: 1 - X - if (_byteOffsetPosition == 1) - m_context << Instruction::MUL; - else - m_context - << dupInstruction(_byteOffsetPosition + 1) << Instruction::MUL - << swapInstruction(_byteOffsetPosition) << Instruction::POP; -} diff --git a/compiler/libsolidity/codegen/ArrayUtils.h b/compiler/libsolidity/codegen/ArrayUtils.h deleted file mode 100644 index 2ee08797..00000000 --- a/compiler/libsolidity/codegen/ArrayUtils.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2015 - * Code generation utils that handle arrays. - */ - -#pragma once - -#include - -namespace solidity::frontend -{ - -class CompilerContext; -class Type; -class ArrayType; -using TypePointer = Type const*; - -/** - * Class that provides code generation for handling arrays. - */ -class ArrayUtils -{ -public: - explicit ArrayUtils(CompilerContext& _context): m_context(_context) {} - - /// Copies an array to an array in storage. The arrays can be of different types only if - /// their storage representation is the same. - /// Stack pre: source_reference [source_length] target_reference - /// Stack post: target_reference - void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; - /// Copies the data part of an array (which cannot be dynamically nested) from anywhere - /// to a given position in memory. - /// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in - /// place as required by the ABI encoding). Use CompilerUtils::convertType if you want real - /// memory copies of nested arrays. - /// Stack pre: memory_offset source_item - /// Stack post: memory_offest + length(padded) - void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; - /// Clears the given dynamic or static array. - /// Stack pre: storage_ref storage_byte_offset - /// Stack post: - void clearArray(ArrayType const& _type) const; - /// Clears the length and data elements of the array referenced on the stack. - /// Stack pre: reference (excludes byte offset) - /// Stack post: - void clearDynamicArray(ArrayType const& _type) const; - /// Changes the size of a dynamic array and clears the tail if it is shortened. - /// Stack pre: reference (excludes byte offset) new_length - /// Stack post: - void resizeDynamicArray(ArrayType const& _type) const; - /// Increments the size of a dynamic array by one. - /// Does not touch the new data element. In case of a byte array, this might move the - /// data. - /// Stack pre: reference (excludes byte offset) - /// Stack post: new_length - void incrementDynamicArraySize(ArrayType const& _type) const; - /// Decrements the size of a dynamic array by one if length is nonzero. Causes an invalid instruction otherwise. - /// Clears the removed data element. In case of a byte array, this might move the data. - /// Stack pre: reference - /// Stack post: - void popStorageArrayElement(ArrayType const& _type) const; - /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). - /// Stack pre: end_ref start_ref - /// Stack post: end_ref - void clearStorageLoop(TypePointer _type) const; - /// Converts length to size (number of storage slots or calldata/memory bytes). - /// if @a _pad then add padding to multiples of 32 bytes for calldata/memory. - /// Stack pre: length - /// Stack post: size - void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; - /// Retrieves the length (number of elements) of the array ref on the stack. This also - /// works for statically-sized arrays. - /// @param _stackDepth number of stack elements between top of stack and top (!) of reference - /// Stack pre: reference (excludes byte offset for dynamic storage arrays) - /// Stack post: reference length - void retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth = 0) const; - /// Stores the length of an array of type @a _arrayType in storage. The length itself is stored - /// on the stack at position @a _stackDepthLength and the storage reference at @a _stackDepthRef. - /// If @a _arrayType is a byte array, takes tight coding into account. - void storeLength(ArrayType const& _arrayType, unsigned _stackDepthLength = 0, unsigned _stackDepthRef = 1) const; - /// Checks whether the index is out of range and returns the absolute offset of the element reference[index] - /// (i.e. reference + index * size_of_base_type). - /// If @a _keepReference is true, the base reference to the beginning of the array is kept on the stack. - /// Stack pre: reference [length] index - /// Stack post (storage): [reference] storage_slot byte_offset - /// Stack post: [reference] memory/calldata_offset - void accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck = true, bool _keepReference = false) const; - /// Access calldata array's element and put it on stack. - /// Stack pre: reference [length] index - /// Stack post: value - void accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck = true) const; - -private: - /// Adds the given number of bytes to a storage byte offset counter and also increments - /// the storage offset if adding this number again would increase the counter over 32. - /// @param byteOffsetPosition the stack offset of the storage byte offset - /// @param storageOffsetPosition the stack offset of the storage slot offset - void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; - - CompilerContext& m_context; -}; - -} diff --git a/compiler/libsolidity/codegen/Compiler.cpp b/compiler/libsolidity/codegen/Compiler.cpp deleted file mode 100644 index 78198fff..00000000 --- a/compiler/libsolidity/codegen/Compiler.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Solidity compiler. - */ - -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::frontend; - -void Compiler::compileContract( - ContractDefinition const& _contract, - std::map> const& _otherCompilers, - bytes const& _metadata -) -{ - ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings); - runtimeCompiler.compileContract(_contract, _otherCompilers); - m_runtimeContext.appendAuxiliaryData(_metadata); - - // This might modify m_runtimeContext because it can access runtime functions at - // creation time. - OptimiserSettings creationSettings{m_optimiserSettings}; - // The creation code will be executed at most once, so we modify the optimizer - // settings accordingly. - creationSettings.expectedExecutionsPerDeployment = 1; - ContractCompiler creationCompiler(&runtimeCompiler, m_context, creationSettings); - m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); - - m_context.optimise(m_optimiserSettings); -} - -std::shared_ptr Compiler::runtimeAssemblyPtr() const -{ - solAssert(m_context.runtimeContext(), ""); - return m_context.runtimeContext()->assemblyPtr(); -} - -evmasm::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const -{ - return m_runtimeContext.functionEntryLabelIfExists(_function); -} diff --git a/compiler/libsolidity/codegen/Compiler.h b/compiler/libsolidity/codegen/Compiler.h deleted file mode 100644 index 8bd21e58..00000000 --- a/compiler/libsolidity/codegen/Compiler.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Solidity AST to EVM bytecode compiler. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace solidity::frontend { - -class Compiler -{ -public: - Compiler(langutil::EVMVersion _evmVersion, RevertStrings _revertStrings, OptimiserSettings _optimiserSettings): - m_optimiserSettings(std::move(_optimiserSettings)), - m_runtimeContext(_evmVersion, _revertStrings), - m_context(_evmVersion, _revertStrings, &m_runtimeContext) - { } - - /// Compiles a contract. - /// @arg _metadata contains the to be injected metadata CBOR - void compileContract( - ContractDefinition const& _contract, - std::map> const& _otherCompilers, - bytes const& _metadata - ); - /// @returns Entire assembly. - evmasm::Assembly const& assembly() const { return m_context.assembly(); } - /// @returns Entire assembly as a shared pointer to non-const. - std::shared_ptr assemblyPtr() const { return m_context.assemblyPtr(); } - /// @returns Runtime assembly. - std::shared_ptr runtimeAssemblyPtr() const; - /// @returns The entire assembled object (with constructor). - evmasm::LinkerObject assembledObject() const { return m_context.assembledObject(); } - /// @returns Only the runtime object (without constructor). - evmasm::LinkerObject runtimeObject() const { return m_context.assembledRuntimeObject(m_runtimeSub); } - /// @arg _sourceCodes is the map of input files to source code strings - std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const - { - return m_context.assemblyString(_sourceCodes); - } - /// @arg _sourceCodes is the map of input files to source code strings - Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const - { - return m_context.assemblyJSON(_sourceCodes); - } - /// @returns Assembly items of the normal compiler context - evmasm::AssemblyItems const& assemblyItems() const { return m_context.assembly().items(); } - /// @returns Assembly items of the runtime compiler context - evmasm::AssemblyItems const& runtimeAssemblyItems() const { return m_context.assembly().sub(m_runtimeSub).items(); } - - /// @returns the entry label of the given function. Might return an AssemblyItem of type - /// UndefinedItem if it does not exist yet. - evmasm::AssemblyItem functionEntryLabel(FunctionDefinition const& _function) const; - -private: - OptimiserSettings const m_optimiserSettings; - CompilerContext m_runtimeContext; - size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. - CompilerContext m_context; -}; - -} diff --git a/compiler/libsolidity/codegen/CompilerContext.cpp b/compiler/libsolidity/codegen/CompilerContext.cpp deleted file mode 100644 index f1f38630..00000000 --- a/compiler/libsolidity/codegen/CompilerContext.cpp +++ /dev/null @@ -1,559 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Utilities for the solidity compiler. - */ - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include - -// Change to "define" to output all intermediate code -#undef SOL_OUTPUT_ASM -#ifdef SOL_OUTPUT_ASM -#include -#endif - - -using namespace std; -using namespace solidity; -using namespace solidity::util; -using namespace solidity::evmasm; -using namespace solidity::frontend; -using namespace solidity::langutil; - -void CompilerContext::addStateVariable( - VariableDeclaration const& _declaration, - u256 const& _storageOffset, - unsigned _byteOffset -) -{ - m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); -} - -void CompilerContext::startFunction(Declaration const& _function) -{ - m_functionCompilationQueue.startFunction(_function); - *this << functionEntryLabel(_function); -} - -void CompilerContext::callLowLevelFunction( - string const& _name, - unsigned _inArgs, - unsigned _outArgs, - function const& _generator -) -{ - evmasm::AssemblyItem retTag = pushNewTag(); - CompilerUtils(*this).moveIntoStack(_inArgs); - - *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator); - - appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); - adjustStackOffset(int(_outArgs) - 1 - _inArgs); - *this << retTag.tag(); -} - -evmasm::AssemblyItem CompilerContext::lowLevelFunctionTag( - string const& _name, - unsigned _inArgs, - unsigned _outArgs, - function const& _generator -) -{ - auto it = m_lowLevelFunctions.find(_name); - if (it == m_lowLevelFunctions.end()) - { - evmasm::AssemblyItem tag = newTag().pushTag(); - m_lowLevelFunctions.insert(make_pair(_name, tag)); - m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator)); - return tag; - } - else - return it->second; -} - -void CompilerContext::appendMissingLowLevelFunctions() -{ - while (!m_lowLevelFunctionGenerationQueue.empty()) - { - string name; - unsigned inArgs; - unsigned outArgs; - function generator; - tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front(); - m_lowLevelFunctionGenerationQueue.pop(); - - setStackOffset(inArgs + 1); - *this << m_lowLevelFunctions.at(name).tag(); - generator(*this); - CompilerUtils(*this).moveToStackTop(outArgs); - appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); - solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + "."); - } -} - -void CompilerContext::addVariable( - VariableDeclaration const& _declaration, - unsigned _offsetToCurrent -) -{ - solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); - unsigned sizeOnStack = _declaration.annotation().type->sizeOnStack(); - // Variables should not have stack size other than [1, 2], - // but that might change when new types are introduced. - solAssert(sizeOnStack == 1 || sizeOnStack == 2, ""); - m_localVariables[&_declaration].push_back(unsigned(m_asm->deposit()) - _offsetToCurrent); -} - -void CompilerContext::removeVariable(Declaration const& _declaration) -{ - solAssert(m_localVariables.count(&_declaration) && !m_localVariables[&_declaration].empty(), ""); - m_localVariables[&_declaration].pop_back(); - if (m_localVariables[&_declaration].empty()) - m_localVariables.erase(&_declaration); -} - -void CompilerContext::removeVariablesAboveStackHeight(unsigned _stackHeight) -{ - vector toRemove; - for (auto _var: m_localVariables) - { - solAssert(!_var.second.empty(), ""); - solAssert(_var.second.back() <= stackHeight(), ""); - if (_var.second.back() >= _stackHeight) - toRemove.push_back(_var.first); - } - for (auto _var: toRemove) - removeVariable(*_var); -} - -unsigned CompilerContext::numberOfLocalVariables() const -{ - return m_localVariables.size(); -} - -shared_ptr CompilerContext::compiledContract(ContractDefinition const& _contract) const -{ - auto ret = m_otherCompilers.find(&_contract); - solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); - return ret->second->assemblyPtr(); -} - -shared_ptr CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const -{ - auto ret = m_otherCompilers.find(&_contract); - solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); - return ret->second->runtimeAssemblyPtr(); -} - -bool CompilerContext::isLocalVariable(Declaration const* _declaration) const -{ - return !!m_localVariables.count(_declaration); -} - -evmasm::AssemblyItem CompilerContext::functionEntryLabel(Declaration const& _declaration) -{ - return m_functionCompilationQueue.entryLabel(_declaration, *this); -} - -evmasm::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& _declaration) const -{ - return m_functionCompilationQueue.entryLabelIfExists(_declaration); -} - -FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function) -{ - // Libraries do not allow inheritance and their functions can be inlined, so we should not - // search the inheritance hierarchy (which will be the wrong one in case the function - // is inlined). - if (auto scope = dynamic_cast(_function.scope())) - if (scope->isLibrary()) - return _function; - solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin()); -} - -FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base) -{ - solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return resolveVirtualFunction(_function, superContract(_base)); -} - -FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const -{ - vector::const_iterator it = superContract(_contract); - for (; it != m_inheritanceHierarchy.end(); ++it) - if ((*it)->constructor()) - return (*it)->constructor(); - - return nullptr; -} - -Declaration const* CompilerContext::nextFunctionToCompile() const -{ - return m_functionCompilationQueue.nextFunctionToCompile(); -} - -ModifierDefinition const& CompilerContext::resolveVirtualFunctionModifier( - ModifierDefinition const& _modifier -) const -{ - // Libraries do not allow inheritance and their functions can be inlined, so we should not - // search the inheritance hierarchy (which will be the wrong one in case the function - // is inlined). - if (auto scope = dynamic_cast(_modifier.scope())) - if (scope->isLibrary()) - return _modifier; - solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - for (ContractDefinition const* contract: m_inheritanceHierarchy) - for (ModifierDefinition const* modifier: contract->functionModifiers()) - if (modifier->name() == _modifier.name()) - return *modifier; - solAssert(false, "Function modifier " + _modifier.name() + " not found in inheritance hierarchy."); -} - -unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declaration) const -{ - auto res = m_localVariables.find(&_declaration); - solAssert(res != m_localVariables.end(), "Variable not found on stack."); - solAssert(!res->second.empty(), ""); - return res->second.back(); -} - -unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const -{ - return m_asm->deposit() - _baseOffset - 1; -} - -unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const -{ - return m_asm->deposit() - _offset - 1; -} - -pair CompilerContext::storageLocationOfVariable(Declaration const& _declaration) const -{ - auto it = m_stateVariables.find(&_declaration); - solAssert(it != m_stateVariables.end(), "Variable not found in storage."); - return it->second; -} - -CompilerContext& CompilerContext::appendJump(evmasm::AssemblyItem::JumpType _jumpType) -{ - evmasm::AssemblyItem item(Instruction::JUMP); - item.setJumpType(_jumpType); - return *this << item; -} - -CompilerContext& CompilerContext::appendInvalid() -{ - return *this << Instruction::INVALID; -} - -CompilerContext& CompilerContext::appendConditionalInvalid() -{ - *this << Instruction::ISZERO; - evmasm::AssemblyItem afterTag = appendConditionalJump(); - *this << Instruction::INVALID; - *this << afterTag; - return *this; -} - -CompilerContext& CompilerContext::appendRevert(string const& _message) -{ - appendInlineAssembly("{ " + revertReasonIfDebug(_message) + " }"); - return *this; -} - -CompilerContext& CompilerContext::appendConditionalRevert(bool _forwardReturnData, string const& _message) -{ - if (_forwardReturnData && m_evmVersion.supportsReturndata()) - appendInlineAssembly(R"({ - if condition { - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - } - })", {"condition"}); - else - appendInlineAssembly("{ if condition { " + revertReasonIfDebug(_message) + " } }", {"condition"}); - *this << Instruction::POP; - return *this; -} - -void CompilerContext::resetVisitedNodes(ASTNode const* _node) -{ - stack newStack; - newStack.push(_node); - std::swap(m_visitedNodes, newStack); - updateSourceLocation(); -} - -void CompilerContext::appendInlineAssembly( - string const& _assembly, - vector const& _localVariables, - set const& _externallyUsedFunctions, - bool _system, - OptimiserSettings const& _optimiserSettings -) -{ - int startStackHeight = stackHeight(); - - set externallyUsedIdentifiers; - for (auto const& fun: _externallyUsedFunctions) - externallyUsedIdentifiers.insert(yul::YulString(fun)); - for (auto const& var: _localVariables) - externallyUsedIdentifiers.insert(yul::YulString(var)); - - yul::ExternalIdentifierAccess identifierAccess; - identifierAccess.resolve = [&]( - yul::Identifier const& _identifier, - yul::IdentifierContext, - bool _insideFunction - ) -> size_t - { - if (_insideFunction) - return size_t(-1); - auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name.str()); - return it == _localVariables.end() ? size_t(-1) : 1; - }; - identifierAccess.generateCode = [&]( - yul::Identifier const& _identifier, - yul::IdentifierContext _context, - yul::AbstractAssembly& _assembly - ) - { - auto it = std::find(_localVariables.begin(), _localVariables.end(), _identifier.name.str()); - solAssert(it != _localVariables.end(), ""); - int stackDepth = _localVariables.end() - it; - int stackDiff = _assembly.stackHeight() - startStackHeight + stackDepth; - if (_context == yul::IdentifierContext::LValue) - stackDiff -= 1; - if (stackDiff < 1 || stackDiff > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_identifier.location) << - util::errinfo_comment("Stack too deep (" + to_string(stackDiff) + "), try removing local variables.") - ); - if (_context == yul::IdentifierContext::RValue) - _assembly.appendInstruction(dupInstruction(stackDiff)); - else - { - _assembly.appendInstruction(swapInstruction(stackDiff)); - _assembly.appendInstruction(Instruction::POP); - } - }; - - ErrorList errors; - ErrorReporter errorReporter(errors); - auto scanner = make_shared(langutil::CharStream(_assembly, "--CODEGEN--")); - yul::EVMDialect const& dialect = yul::EVMDialect::strictAssemblyForEVM(m_evmVersion); - shared_ptr parserResult = yul::Parser(errorReporter, dialect).parse(scanner, false); -#ifdef SOL_OUTPUT_ASM - cout << yul::AsmPrinter(&dialect)(*parserResult) << endl; -#endif - - auto reportError = [&](string const& _context) - { - string message = - "Error parsing/analyzing inline assembly block:\n" + - _context + "\n" - "------------------ Input: -----------------\n" + - _assembly + "\n" - "------------------ Errors: ----------------\n"; - for (auto const& error: errorReporter.errors()) - message += SourceReferenceFormatter::formatErrorInformation(*error); - message += "-------------------------------------------\n"; - - solAssert(false, message); - }; - - yul::AsmAnalysisInfo analysisInfo; - bool analyzerResult = false; - if (parserResult) - analyzerResult = yul::AsmAnalyzer( - analysisInfo, - errorReporter, - dialect, - identifierAccess.resolve - ).analyze(*parserResult); - if (!parserResult || !errorReporter.errors().empty() || !analyzerResult) - reportError("Invalid assembly generated by code generator."); - - // Several optimizer steps cannot handle externally supplied stack variables, - // so we essentially only optimize the ABI functions. - if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) - { - bool const isCreation = m_runtimeContext != nullptr; - yul::GasMeter meter(dialect, isCreation, _optimiserSettings.expectedExecutionsPerDeployment); - yul::Object obj; - obj.code = parserResult; - obj.analysisInfo = make_shared(analysisInfo); - yul::OptimiserSuite::run( - dialect, - &meter, - obj, - _optimiserSettings.optimizeStackAllocation, - externallyUsedIdentifiers - ); - analysisInfo = std::move(*obj.analysisInfo); - parserResult = std::move(obj.code); - -#ifdef SOL_OUTPUT_ASM - cout << "After optimizer:" << endl; - cout << yul::AsmPrinter(&dialect)(*parserResult) << endl; -#endif - } - - if (!errorReporter.errors().empty()) - reportError("Failed to analyze inline assembly block."); - - solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - yul::CodeGenerator::assemble( - *parserResult, - analysisInfo, - *m_asm, - m_evmVersion, - identifierAccess, - _system, - _optimiserSettings.optimizeStackAllocation - ); - - // Reset the source location to the one of the node (instead of the CODEGEN source location) - updateSourceLocation(); -} - -FunctionDefinition const& CompilerContext::resolveVirtualFunction( - FunctionDefinition const& _function, - vector::const_iterator _searchStart -) -{ - string name = _function.name(); - FunctionType functionType(_function); - auto it = _searchStart; - for (; it != m_inheritanceHierarchy.end(); ++it) - for (FunctionDefinition const* function: (*it)->definedFunctions()) - if ( - function->name() == name && - !function->isConstructor() && - FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType) - ) - return *function; - solAssert(false, "Super function " + name + " not found."); - return _function; // not reached -} - -vector::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const -{ - solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_contract); - solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); - return ++it; -} - -string CompilerContext::revertReasonIfDebug(string const& _message) -{ - return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message); -} - -void CompilerContext::updateSourceLocation() -{ - m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); -} - -evmasm::Assembly::OptimiserSettings CompilerContext::translateOptimiserSettings(OptimiserSettings const& _settings) -{ - // Constructing it this way so that we notice changes in the fields. - evmasm::Assembly::OptimiserSettings asmSettings{false, false, false, false, false, false, m_evmVersion, 0}; - asmSettings.isCreation = true; - asmSettings.runJumpdestRemover = _settings.runJumpdestRemover; - asmSettings.runPeephole = _settings.runPeephole; - asmSettings.runDeduplicate = _settings.runDeduplicate; - asmSettings.runCSE = _settings.runCSE; - asmSettings.runConstantOptimiser = _settings.runConstantOptimiser; - asmSettings.expectedExecutionsPerDeployment = _settings.expectedExecutionsPerDeployment; - asmSettings.evmVersion = m_evmVersion; - return asmSettings; -} - -evmasm::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( - Declaration const& _declaration, - CompilerContext& _context -) -{ - auto res = m_entryLabels.find(&_declaration); - if (res == m_entryLabels.end()) - { - evmasm::AssemblyItem tag(_context.newTag()); - m_entryLabels.insert(make_pair(&_declaration, tag)); - m_functionsToCompile.push(&_declaration); - return tag.tag(); - } - else - return res->second.tag(); - -} - -evmasm::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabelIfExists(Declaration const& _declaration) const -{ - auto res = m_entryLabels.find(&_declaration); - return res == m_entryLabels.end() ? evmasm::AssemblyItem(evmasm::UndefinedItem) : res->second.tag(); -} - -Declaration const* CompilerContext::FunctionCompilationQueue::nextFunctionToCompile() const -{ - while (!m_functionsToCompile.empty()) - { - if (m_alreadyCompiledFunctions.count(m_functionsToCompile.front())) - m_functionsToCompile.pop(); - else - return m_functionsToCompile.front(); - } - return nullptr; -} - -void CompilerContext::FunctionCompilationQueue::startFunction(Declaration const& _function) -{ - if (!m_functionsToCompile.empty() && m_functionsToCompile.front() == &_function) - m_functionsToCompile.pop(); - m_alreadyCompiledFunctions.insert(&_function); -} diff --git a/compiler/libsolidity/codegen/CompilerContext.h b/compiler/libsolidity/codegen/CompilerContext.h deleted file mode 100644 index 28aefa03..00000000 --- a/compiler/libsolidity/codegen/CompilerContext.h +++ /dev/null @@ -1,358 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Utilities for the solidity compiler. - */ - -#pragma once - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace solidity::frontend { - -class Compiler; - -/** - * Context to be shared by all units that compile the same contract. - * It stores the generated bytecode and the position of identifiers in memory and on the stack. - */ -class CompilerContext -{ -public: - explicit CompilerContext( - langutil::EVMVersion _evmVersion, - RevertStrings _revertStrings, - CompilerContext* _runtimeContext = nullptr - ): - m_asm(std::make_shared()), - m_evmVersion(_evmVersion), - m_revertStrings(_revertStrings), - m_runtimeContext(_runtimeContext), - m_abiFunctions(m_evmVersion, m_revertStrings) - { - if (m_runtimeContext) - m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); - } - - langutil::EVMVersion const& evmVersion() const { return m_evmVersion; } - - /// Update currently enabled set of experimental features. - void setExperimentalFeatures(std::set const& _features) { m_experimentalFeatures = _features; } - /// @returns true if the given feature is enabled. - bool experimentalFeatureActive(ExperimentalFeature _feature) const { return m_experimentalFeatures.count(_feature); } - - void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); - void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); - void removeVariable(Declaration const& _declaration); - /// Removes all local variables currently allocated above _stackHeight. - void removeVariablesAboveStackHeight(unsigned _stackHeight); - /// Returns the number of currently allocated local variables. - unsigned numberOfLocalVariables() const; - - void setOtherCompilers(std::map> const& _otherCompilers) { m_otherCompilers = _otherCompilers; } - std::shared_ptr compiledContract(ContractDefinition const& _contract) const; - std::shared_ptr compiledContractRuntime(ContractDefinition const& _contract) const; - - void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } - void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } - unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } - - bool isLocalVariable(Declaration const* _declaration) const; - bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } - - /// @returns the entry label of the given function and creates it if it does not exist yet. - evmasm::AssemblyItem functionEntryLabel(Declaration const& _declaration); - /// @returns the entry label of the given function. Might return an AssemblyItem of type - /// UndefinedItem if it does not exist yet. - evmasm::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; - /// @returns the entry label of the given function and takes overrides into account. - FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); - /// @returns the function that overrides the given declaration from the most derived class just - /// above _base in the current inheritance hierarchy. - FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); - /// @returns the next constructor in the inheritance hierarchy. - FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; - /// Sets the current inheritance hierarchy from derived to base. - void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } - - /// @returns the next function in the queue of functions that are still to be compiled - /// (i.e. that were referenced during compilation but where we did not yet generate code for). - /// Returns nullptr if the queue is empty. Does not remove the function from the queue, - /// that will only be done by startFunction below. - Declaration const* nextFunctionToCompile() const; - /// Resets function specific members, inserts the function entry label and marks the function - /// as "having code". - void startFunction(Declaration const& _function); - - /// Appends a call to the named low-level function and inserts the generator into the - /// list of low-level-functions to be generated, unless it already exists. - /// Note that the generator should not assume that objects are still alive when it is called, - /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). - void callLowLevelFunction( - std::string const& _name, - unsigned _inArgs, - unsigned _outArgs, - std::function const& _generator - ); - /// Returns the tag of the named low-level function and inserts the generator into the - /// list of low-level-functions to be generated, unless it already exists. - /// Note that the generator should not assume that objects are still alive when it is called, - /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example). - evmasm::AssemblyItem lowLevelFunctionTag( - std::string const& _name, - unsigned _inArgs, - unsigned _outArgs, - std::function const& _generator - ); - /// Generates the code for missing low-level functions, i.e. calls the generators passed above. - void appendMissingLowLevelFunctions(); - ABIFunctions& abiFunctions() { return m_abiFunctions; } - - ModifierDefinition const& resolveVirtualFunctionModifier(ModifierDefinition const& _modifier) const; - /// Returns the distance of the given local variable from the bottom of the stack (of the current function). - unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const; - /// If supplied by a value returned by @ref baseStackOffsetOfVariable(variable), returns - /// the distance of that variable from the current top of the stack. - unsigned baseToCurrentStackOffset(unsigned _baseOffset) const; - /// Converts an offset relative to the current stack height to a value that can be used later - /// with baseToCurrentStackOffset to point to the same stack element. - unsigned currentToBaseStackOffset(unsigned _offset) const; - /// @returns pair of slot and byte offset of the value inside this slot. - std::pair storageLocationOfVariable(Declaration const& _declaration) const; - - /// Appends a JUMPI instruction to a new tag and @returns the tag - evmasm::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } - /// Appends a JUMPI instruction to @a _tag - CompilerContext& appendConditionalJumpTo(evmasm::AssemblyItem const& _tag) { m_asm->appendJumpI(_tag); return *this; } - /// Appends a JUMP to a new tag and @returns the tag - evmasm::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } - /// Appends a JUMP to a tag already on the stack - CompilerContext& appendJump(evmasm::AssemblyItem::JumpType _jumpType = evmasm::AssemblyItem::JumpType::Ordinary); - /// Appends an INVALID instruction - CompilerContext& appendInvalid(); - /// Appends a conditional INVALID instruction - CompilerContext& appendConditionalInvalid(); - /// Appends a REVERT(0, 0) call - /// @param _message is an optional revert message used in debug mode - CompilerContext& appendRevert(std::string const& _message = ""); - /// Appends a conditional REVERT-call, either forwarding the RETURNDATA or providing the - /// empty string. Consumes the condition. - /// If the current EVM version does not support RETURNDATA, uses REVERT but does not forward - /// the data. - /// @param _message is an optional revert message used in debug mode - CompilerContext& appendConditionalRevert(bool _forwardReturnData = false, std::string const& _message = ""); - /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo( - evmasm::AssemblyItem const& _tag, - evmasm::AssemblyItem::JumpType _jumpType = evmasm::AssemblyItem::JumpType::Ordinary - ) { *m_asm << _tag.pushTag(); return appendJump(_jumpType); } - /// Appends pushing of a new tag and @returns the new tag. - evmasm::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } - /// @returns a new tag without pushing any opcodes or data - evmasm::AssemblyItem newTag() { return m_asm->newTag(); } - /// @returns a new tag identified by name. - evmasm::AssemblyItem namedTag(std::string const& _name) { return m_asm->namedTag(_name); } - /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) - /// on the stack. @returns the pushsub assembly item. - evmasm::AssemblyItem addSubroutine(evmasm::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); } - /// Pushes the size of the subroutine. - void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); } - /// Pushes the offset of the subroutine. - void pushSubroutineOffset(size_t _subRoutine) { m_asm->pushSubroutineOffset(_subRoutine); } - /// Pushes the size of the final program - void appendProgramSize() { m_asm->appendProgramSize(); } - /// Adds data to the data section, pushes a reference to the stack - evmasm::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } - /// Appends the address (virtual, will be filled in by linker) of a library. - void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } - /// Appends a zero-address that can be replaced by something else at deploy time (if the - /// position in bytecode is known). - void appendDeployTimeAddress() { m_asm->append(evmasm::PushDeployTimeAddress); } - /// Resets the stack of visited nodes with a new stack having only @c _node - void resetVisitedNodes(ASTNode const* _node); - /// Pops the stack of visited nodes - void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); } - /// Pushes an ASTNode to the stack of visited nodes - void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } - - /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(evmasm::AssemblyItem const& _item) { m_asm->append(_item); return *this; } - CompilerContext& operator<<(evmasm::Instruction _instruction) { m_asm->append(_instruction); return *this; } - CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } - CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } - - /// Appends inline assembly (strict mode). - /// @a _replacements are string-matching replacements that are performed prior to parsing the inline assembly. - /// @param _localVariables assigns stack positions to variables with the last one being the stack top - /// @param _externallyUsedFunctions a set of function names that are not to be renamed or removed. - /// @param _system if true, this is a "system-level" assembly where all functions use named labels. - void appendInlineAssembly( - std::string const& _assembly, - std::vector const& _localVariables = std::vector(), - std::set const& _externallyUsedFunctions = std::set(), - bool _system = false, - OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() - ); - - /// If m_revertStrings is debug, @returns inline assembly code that - /// stores @param _message in memory position 0 and reverts. - /// Otherwise returns "revert(0, 0)". - std::string revertReasonIfDebug(std::string const& _message = ""); - - /// Appends arbitrary data to the end of the bytecode. - void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); } - - /// Run optimisation step. - void optimise(OptimiserSettings const& _settings) { m_asm->optimise(translateOptimiserSettings(_settings)); } - - /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. - CompilerContext* runtimeContext() const { return m_runtimeContext; } - /// @returns the identifier of the runtime subroutine. - size_t runtimeSub() const { return m_runtimeSub; } - - /// @returns a const reference to the underlying assembly. - evmasm::Assembly const& assembly() const { return *m_asm; } - /// @returns a shared pointer to the assembly. - /// Should be avoided except when adding sub-assemblies. - std::shared_ptr assemblyPtr() const { return m_asm; } - - /// @arg _sourceCodes is the map of input files to source code strings - std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const - { - return m_asm->assemblyString(_sourceCodes); - } - - /// @arg _sourceCodes is the map of input files to source code strings - Json::Value assemblyJSON(StringMap const& _sourceCodes = StringMap()) const - { - return m_asm->assemblyJSON(_sourceCodes); - } - - evmasm::LinkerObject const& assembledObject() const { return m_asm->assemble(); } - evmasm::LinkerObject const& assembledRuntimeObject(size_t _subIndex) const { return m_asm->sub(_subIndex).assemble(); } - - /** - * Helper class to pop the visited nodes stack when a scope closes - */ - class LocationSetter: public ScopeGuard - { - public: - LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): - ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } - }; - - void setModifierDepth(size_t _modifierDepth) { m_asm->m_currentModifierDepth = _modifierDepth; } - - RevertStrings revertStrings() const { return m_revertStrings; } - -private: - /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns - /// the first function definition that is overwritten by _function. - FunctionDefinition const& resolveVirtualFunction( - FunctionDefinition const& _function, - std::vector::const_iterator _searchStart - ); - /// @returns an iterator to the contract directly above the given contract. - std::vector::const_iterator superContract(ContractDefinition const& _contract) const; - /// Updates source location set in the assembly. - void updateSourceLocation(); - - evmasm::Assembly::OptimiserSettings translateOptimiserSettings(OptimiserSettings const& _settings); - - /** - * Helper class that manages function labels and ensures that referenced functions are - * compiled in a specific order. - */ - struct FunctionCompilationQueue - { - /// @returns the entry label of the given function and creates it if it does not exist yet. - /// @param _context compiler context used to create a new tag if needed - evmasm::AssemblyItem entryLabel(Declaration const& _declaration, CompilerContext& _context); - /// @returns the entry label of the given function. Might return an AssemblyItem of type - /// UndefinedItem if it does not exist yet. - evmasm::AssemblyItem entryLabelIfExists(Declaration const& _declaration) const; - - /// @returns the next function in the queue of functions that are still to be compiled - /// (i.e. that were referenced during compilation but where we did not yet generate code for). - /// Returns nullptr if the queue is empty. Does not remove the function from the queue, - /// that will only be done by startFunction below. - Declaration const* nextFunctionToCompile() const; - /// Informs the queue that we are about to compile the given function, i.e. removes - /// the function from the queue of functions to compile. - void startFunction(Declaration const& _function); - - /// Labels pointing to the entry points of functions. - std::map m_entryLabels; - /// Set of functions for which we did not yet generate code. - std::set m_alreadyCompiledFunctions; - /// Queue of functions that still need to be compiled (important to be a queue to maintain - /// determinism even in the presence of a non-deterministic allocator). - /// Mutable because we will throw out some functions earlier than needed. - mutable std::queue m_functionsToCompile; - } m_functionCompilationQueue; - - evmasm::AssemblyPointer m_asm; - /// Version of the EVM to compile against. - langutil::EVMVersion m_evmVersion; - RevertStrings const m_revertStrings; - /// Activated experimental features. - std::set m_experimentalFeatures; - /// Other already compiled contracts to be used in contract creation calls. - std::map> m_otherCompilers; - /// Storage offsets of state variables - std::map> m_stateVariables; - /// Offsets of local variables on the stack (relative to stack base). - /// This needs to be a stack because if a modifier contains a local variable and this - /// modifier is applied twice, the position of the variable needs to be restored - /// after the nested modifier is left. - std::map> m_localVariables; - /// List of current inheritance hierarchy from derived to base. - std::vector m_inheritanceHierarchy; - /// Stack of current visited AST nodes, used for location attachment - std::stack m_visitedNodes; - /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. - CompilerContext *m_runtimeContext; - /// The index of the runtime subroutine. - size_t m_runtimeSub = -1; - /// An index of low-level function labels by name. - std::map m_lowLevelFunctions; - /// Container for ABI functions to be generated. - ABIFunctions m_abiFunctions; - /// The queue of low-level functions to generate. - std::queue>> m_lowLevelFunctionGenerationQueue; -}; - -} diff --git a/compiler/libsolidity/codegen/CompilerUtils.cpp b/compiler/libsolidity/codegen/CompilerUtils.cpp deleted file mode 100644 index 4c75081f..00000000 --- a/compiler/libsolidity/codegen/CompilerUtils.cpp +++ /dev/null @@ -1,1513 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Routines used by both the compiler and the expression compiler. - */ - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::frontend; -using namespace solidity::langutil; - -using solidity::util::Whiskers; -using solidity::util::h256; -using solidity::util::toCompactHexWithPrefix; - -unsigned const CompilerUtils::dataStartOffset = 4; -size_t const CompilerUtils::freeMemoryPointer = 64; -size_t const CompilerUtils::zeroPointer = CompilerUtils::freeMemoryPointer + 32; -size_t const CompilerUtils::generalPurposeMemoryStart = CompilerUtils::zeroPointer + 32; - -static_assert(CompilerUtils::freeMemoryPointer >= 64, "Free memory pointer must not overlap with scratch area."); -static_assert(CompilerUtils::zeroPointer >= CompilerUtils::freeMemoryPointer + 32, "Zero pointer must not overlap with free memory pointer."); -static_assert(CompilerUtils::generalPurposeMemoryStart >= CompilerUtils::zeroPointer + 32, "General purpose memory must not overlap with zero area."); - -void CompilerUtils::initialiseFreeMemoryPointer() -{ - m_context << u256(generalPurposeMemoryStart); - storeFreeMemoryPointer(); -} - -void CompilerUtils::fetchFreeMemoryPointer() -{ - m_context << u256(freeMemoryPointer) << Instruction::MLOAD; -} - -void CompilerUtils::storeFreeMemoryPointer() -{ - m_context << u256(freeMemoryPointer) << Instruction::MSTORE; -} - -void CompilerUtils::allocateMemory() -{ - fetchFreeMemoryPointer(); - m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD; - storeFreeMemoryPointer(); -} - -void CompilerUtils::allocateMemory(u256 const& size) -{ - fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << size << Instruction::ADD; - storeFreeMemoryPointer(); -} - -void CompilerUtils::toSizeAfterFreeMemoryPointer() -{ - fetchFreeMemoryPointer(); - m_context << Instruction::DUP1 << Instruction::SWAP2 << Instruction::SUB; - m_context << Instruction::SWAP1; -} - -void CompilerUtils::revertWithStringData(Type const& _argumentType) -{ - solAssert(_argumentType.isImplicitlyConvertibleTo(*TypeProvider::fromElementaryTypeName("string memory")), ""); - fetchFreeMemoryPointer(); - m_context << (u256(util::FixedHash<4>::Arith(util::FixedHash<4>(util::keccak256("Error(string)")))) << (256 - 32)); - m_context << Instruction::DUP2 << Instruction::MSTORE; - m_context << u256(4) << Instruction::ADD; - // Stack: - abiEncode({&_argumentType}, {TypeProvider::array(DataLocation::Memory, true)}); - toSizeAfterFreeMemoryPointer(); - m_context << Instruction::REVERT; -} - -void CompilerUtils::returnDataToArray() -{ - if (m_context.evmVersion().supportsReturndata()) - { - m_context << Instruction::RETURNDATASIZE; - m_context.appendInlineAssembly(R"({ - switch v case 0 { - v := 0x60 - } default { - v := mload(0x40) - mstore(0x40, add(v, and(add(returndatasize(), 0x3f), not(0x1f)))) - mstore(v, returndatasize()) - returndatacopy(add(v, 0x20), 0, returndatasize()) - } - })", {"v"}); - } - else - pushZeroPointer(); -} - -void CompilerUtils::accessCalldataTail(Type const& _type) -{ - solAssert(_type.dataStoredIn(DataLocation::CallData), ""); - solAssert(_type.isDynamicallyEncoded(), ""); - - unsigned int tailSize = _type.calldataEncodedTailSize(); - solAssert(tailSize > 1, ""); - - // returns the absolute offset of the tail in "base_ref" - m_context.appendInlineAssembly(Whiskers(R"({ - let rel_offset_of_tail := calldataload(ptr_to_tail) - if iszero(slt(rel_offset_of_tail, sub(sub(calldatasize(), base_ref), sub(, 1)))) { } - base_ref := add(base_ref, rel_offset_of_tail) - })") - ("neededLength", toCompactHexWithPrefix(tailSize)) - ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail offset")) - .render(), {"base_ref", "ptr_to_tail"}); - // stack layout: - - if (!_type.isDynamicallySized()) - { - m_context << Instruction::POP; - // stack layout: - solAssert( - _type.category() == Type::Category::Struct || - _type.category() == Type::Category::Array, - "Invalid dynamically encoded base type on tail access." - ); - } - else - { - auto const* arrayType = dynamic_cast(&_type); - solAssert(!!arrayType, "Invalid dynamically sized type."); - unsigned int calldataStride = arrayType->calldataStride(); - solAssert(calldataStride > 0, ""); - - // returns the absolute offset of the tail in "base_ref" - // and the length of the tail in "length" - m_context.appendInlineAssembly( - Whiskers(R"({ - length := calldataload(base_ref) - base_ref := add(base_ref, 0x20) - if gt(length, 0xffffffffffffffff) { } - if sgt(base_ref, sub(calldatasize(), mul(length, ))) { revert(0, 0) } - })") - ("calldataStride", toCompactHexWithPrefix(calldataStride)) - ("revertString", m_context.revertReasonIfDebug("Invalid calldata tail length")) - .render(), - {"base_ref", "length"} - ); - // stack layout: - } -} - -unsigned CompilerUtils::loadFromMemory( - unsigned _offset, - Type const& _type, - bool _fromCalldata, - bool _padToWordBoundaries -) -{ - solAssert(_type.category() != Type::Category::Array, "Unable to statically load dynamic type."); - m_context << u256(_offset); - return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); -} - -void CompilerUtils::loadFromMemoryDynamic( - Type const& _type, - bool _fromCalldata, - bool _padToWordBoundaries, - bool _keepUpdatedMemoryOffset -) -{ - if (_keepUpdatedMemoryOffset) - m_context << Instruction::DUP1; - - if (auto arrayType = dynamic_cast(&_type)) - { - solAssert(!arrayType->isDynamicallySized(), ""); - solAssert(!_fromCalldata, ""); - solAssert(_padToWordBoundaries, ""); - if (_keepUpdatedMemoryOffset) - m_context << arrayType->memoryDataSize() << Instruction::ADD; - } - else - { - unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries); - if (_keepUpdatedMemoryOffset) - { - // update memory counter - moveToStackTop(_type.sizeOnStack()); - m_context << u256(numBytes) << Instruction::ADD; - } - } -} - -void CompilerUtils::storeInMemory(unsigned _offset) -{ - unsigned numBytes = prepareMemoryStore(*TypeProvider::uint256(), true); - if (numBytes > 0) - m_context << u256(_offset) << Instruction::MSTORE; -} - -void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries) -{ - // process special types (Reference, StringLiteral, Function) - if (auto ref = dynamic_cast(&_type)) - { - solUnimplementedAssert( - ref->location() == DataLocation::Memory, - "Only in-memory reference type can be stored." - ); - storeInMemoryDynamic(*TypeProvider::uint256(), _padToWordBoundaries); - } - else if (auto str = dynamic_cast(&_type)) - { - m_context << Instruction::DUP1; - storeStringData(bytesConstRef(str->value())); - if (_padToWordBoundaries) - m_context << u256(max(32, ((str->value().size() + 31) / 32) * 32)); - else - m_context << u256(str->value().size()); - m_context << Instruction::ADD; - } - else if ( - _type.category() == Type::Category::Function && - dynamic_cast(_type).kind() == FunctionType::Kind::External - ) - { - combineExternalFunctionType(true); - m_context << Instruction::DUP2 << Instruction::MSTORE; - m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; - } - else if (_type.isValueType()) - { - unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); - m_context << Instruction::DUP2 << Instruction::MSTORE; - m_context << u256(numBytes) << Instruction::ADD; - } - else // Should never happen - { - solAssert( - false, - "Memory store of type " + _type.toString(true) + " not allowed." - ); - } -} - -void CompilerUtils::abiDecode(TypePointers const& _typeParameters, bool _fromMemory) -{ - /// Stack: - if (m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2)) - { - // Use the new Yul-based decoding function - auto stackHeightBefore = m_context.stackHeight(); - abiDecodeV2(_typeParameters, _fromMemory); - solAssert(m_context.stackHeight() - stackHeightBefore == sizeOnStack(_typeParameters) - 2, ""); - return; - } - - //@todo this does not yet support nested dynamic arrays - size_t encodedSize = 0; - for (auto const& t: _typeParameters) - encodedSize += t->decodingType()->calldataHeadSize(); - - Whiskers templ(R"({ - if lt(len, ) { } - })"); - templ("encodedSize", to_string(encodedSize)); - templ("revertString", m_context.revertReasonIfDebug("Calldata too short")); - m_context.appendInlineAssembly(templ.render(), {"len"}); - - m_context << Instruction::DUP2 << Instruction::ADD; - m_context << Instruction::SWAP1; - /// Stack: - - // Retain the offset pointer as base_offset, the point from which the data offsets are computed. - m_context << Instruction::DUP1; - for (TypePointer const& parameterType: _typeParameters) - { - // stack: v1 v2 ... v(k-1) input_end base_offset current_offset - TypePointer type = parameterType->decodingType(); - solUnimplementedAssert(type, "No decoding type found."); - if (type->category() == Type::Category::Array) - { - auto const& arrayType = dynamic_cast(*type); - solUnimplementedAssert(!arrayType.baseType()->isDynamicallyEncoded(), "Nested arrays not yet implemented."); - if (_fromMemory) - { - solUnimplementedAssert( - arrayType.baseType()->isValueType(), - "Nested memory arrays not yet implemented here." - ); - // @todo If base type is an array or struct, it is still calldata-style encoded, so - // we would have to convert it like below. - solAssert(arrayType.location() == DataLocation::Memory, ""); - if (arrayType.isDynamicallySized()) - { - // compute data pointer - m_context << Instruction::DUP1 << Instruction::MLOAD; - // stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset - - fetchFreeMemoryPointer(); - // stack: v1 v2 ... v(k-1) input_end base_offset current_offset data_offset dstmem - moveIntoStack(4); - // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset - m_context << Instruction::DUP5; - // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_offset dstmem - - // Check that the data pointer is valid and that length times - // item size is still inside the range. - Whiskers templ(R"({ - if gt(ptr, 0x100000000) { } - ptr := add(ptr, base_offset) - let array_data_start := add(ptr, 0x20) - if gt(array_data_start, input_end) { } - let array_length := mload(ptr) - if or( - gt(array_length, 0x100000000), - gt(add(array_data_start, mul(array_length, )), input_end) - ) { } - mstore(dst, array_length) - dst := add(dst, 0x20) - })"); - templ("item_size", to_string(arrayType.calldataStride())); - // TODO add test - templ("revertStringPointer", m_context.revertReasonIfDebug("ABI memory decoding: invalid data pointer")); - templ("revertStringStart", m_context.revertReasonIfDebug("ABI memory decoding: invalid data start")); - templ("revertStringLength", m_context.revertReasonIfDebug("ABI memory decoding: invalid data length")); - m_context.appendInlineAssembly(templ.render(), {"input_end", "base_offset", "offset", "ptr", "dst"}); - // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset data_ptr dstdata - m_context << Instruction::SWAP1; - // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset dstdata data_ptr - ArrayUtils(m_context).copyArrayToMemory(arrayType, true); - // stack: v1 v2 ... v(k-1) dstmem input_end base_offset current_offset mem_end - storeFreeMemoryPointer(); - m_context << u256(0x20) << Instruction::ADD; - } - else - { - // Size has already been checked for this one. - moveIntoStack(2); - m_context << Instruction::DUP3; - m_context << u256(arrayType.calldataHeadSize()) << Instruction::ADD; - } - } - else - { - // first load from calldata and potentially convert to memory if arrayType is memory - TypePointer calldataType = TypeProvider::withLocation(&arrayType, DataLocation::CallData, false); - if (calldataType->isDynamicallySized()) - { - // put on stack: data_pointer length - loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory); - m_context << Instruction::SWAP1; - // stack: input_end base_offset next_pointer data_offset - m_context.appendInlineAssembly(Whiskers(R"({ - if gt(data_offset, 0x100000000) { } - })") - // TODO add test - ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data offset")) - .render(), {"data_offset"}); - m_context << Instruction::DUP3 << Instruction::ADD; - // stack: input_end base_offset next_pointer array_head_ptr - m_context.appendInlineAssembly(Whiskers(R"({ - if gt(add(array_head_ptr, 0x20), input_end) { } - })") - ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid head pointer")) - .render(), {"input_end", "base_offset", "next_ptr", "array_head_ptr"}); - - // retrieve length - loadFromMemoryDynamic(*TypeProvider::uint256(), !_fromMemory, true); - // stack: input_end base_offset next_pointer array_length data_pointer - m_context << Instruction::SWAP2; - // stack: input_end base_offset data_pointer array_length next_pointer - m_context.appendInlineAssembly(Whiskers(R"({ - if or( - gt(array_length, 0x100000000), - gt(add(data_ptr, mul(array_length, )" + to_string(arrayType.calldataStride()) + R"()), input_end) - ) { } - })") - ("revertString", m_context.revertReasonIfDebug("ABI calldata decoding: invalid data pointer")) - .render(), {"input_end", "base_offset", "data_ptr", "array_length", "next_ptr"}); - } - else - { - // size has already been checked - // stack: input_end base_offset data_offset - m_context << Instruction::DUP1; - m_context << u256(calldataType->calldataHeadSize()) << Instruction::ADD; - } - if (arrayType.location() == DataLocation::Memory) - { - // stack: input_end base_offset calldata_ref [length] next_calldata - // copy to memory - // move calldata type up again - moveIntoStack(calldataType->sizeOnStack()); - convertType(*calldataType, arrayType, false, false, true); - // fetch next pointer again - moveToStackTop(arrayType.sizeOnStack()); - } - // move input_end up - // stack: input_end base_offset calldata_ref [length] next_calldata - moveToStackTop(2 + arrayType.sizeOnStack()); - m_context << Instruction::SWAP1; - // stack: base_offset calldata_ref [length] input_end next_calldata - moveToStackTop(2 + arrayType.sizeOnStack()); - m_context << Instruction::SWAP1; - // stack: calldata_ref [length] input_end base_offset next_calldata - } - } - else - { - solAssert(!type->isDynamicallyEncoded(), "Unknown dynamically sized type: " + type->toString()); - loadFromMemoryDynamic(*type, !_fromMemory, true); - // stack: v1 v2 ... v(k-1) input_end base_offset v(k) mem_offset - moveToStackTop(1, type->sizeOnStack()); - moveIntoStack(3, type->sizeOnStack()); - } - // stack: v1 v2 ... v(k-1) v(k) input_end base_offset next_offset - } - popStackSlots(3); -} - -void CompilerUtils::encodeToMemory( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _padToWordBoundaries, - bool _copyDynamicDataInPlace, - bool _encodeAsLibraryTypes -) -{ - // stack: ... - bool const encoderV2 = m_context.experimentalFeatureActive(ExperimentalFeature::ABIEncoderV2); - TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes; - solAssert(targetTypes.size() == _givenTypes.size(), ""); - for (TypePointer& t: targetTypes) - { - TypePointer tEncoding = t->fullEncodingType(_encodeAsLibraryTypes, encoderV2, !_padToWordBoundaries); - solUnimplementedAssert(tEncoding, "Encoding type \"" + t->toString() + "\" not yet implemented."); - t = std::move(tEncoding); - } - - if (_givenTypes.empty()) - return; - if (encoderV2) - { - // Use the new Yul-based encoding function - solAssert( - _padToWordBoundaries != _copyDynamicDataInPlace, - "Non-padded and in-place encoding can only be combined." - ); - auto stackHeightBefore = m_context.stackHeight(); - abiEncodeV2(_givenTypes, targetTypes, _encodeAsLibraryTypes, _padToWordBoundaries); - solAssert(stackHeightBefore - m_context.stackHeight() == sizeOnStack(_givenTypes), ""); - return; - } - - // Stack during operation: - // ... ... - // The values dyn_head_n are added during the first loop and they point to the head part - // of the nth dynamic parameter, which is filled once the dynamic parts are processed. - - // store memory start pointer - m_context << Instruction::DUP1; - - unsigned argSize = CompilerUtils::sizeOnStack(_givenTypes); - unsigned stackPos = 0; // advances through the argument values - unsigned dynPointers = 0; // number of dynamic head pointers on the stack - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - // leave end_of_mem as dyn head pointer - m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; - dynPointers++; - solAssert((argSize + dynPointers) < 16, "Stack too deep, try using fewer variables."); - } - else - { - copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->sizeOnStack()); - solAssert(!!targetType, "Externalable type expected."); - TypePointer type = targetType; - if (_givenTypes[i]->dataStoredIn(DataLocation::Storage) && targetType->isValueType()) - { - // special case: convert storage reference type to value type - this is only - // possible for library calls where we just forward the storage reference - solAssert(_encodeAsLibraryTypes, ""); - solAssert(_givenTypes[i]->sizeOnStack() == 1, ""); - } - else if ( - _givenTypes[i]->dataStoredIn(DataLocation::Storage) || - _givenTypes[i]->dataStoredIn(DataLocation::CallData) || - _givenTypes[i]->category() == Type::Category::StringLiteral || - _givenTypes[i]->category() == Type::Category::Function - ) - type = _givenTypes[i]; // delay conversion - else - convertType(*_givenTypes[i], *targetType, true); - if (auto arrayType = dynamic_cast(type)) - ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries); - else - storeInMemoryDynamic(*type, _padToWordBoundaries); - } - stackPos += _givenTypes[i]->sizeOnStack(); - } - - // now copy the dynamic part - // Stack: ... ... - stackPos = 0; - unsigned thisDynPointer = 0; - for (size_t i = 0; i < _givenTypes.size(); ++i) - { - TypePointer targetType = targetTypes[i]; - solAssert(!!targetType, "Externalable type expected."); - if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace) - { - // copy tail pointer (=mem_end - mem_start) to memory - m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2; - m_context << Instruction::SUB; - m_context << dupInstruction(2 + dynPointers - thisDynPointer); - m_context << Instruction::MSTORE; - // stack: ... - if (_givenTypes[i]->category() == Type::Category::StringLiteral) - { - auto const& strType = dynamic_cast(*_givenTypes[i]); - m_context << u256(strType.value().size()); - storeInMemoryDynamic(*TypeProvider::uint256(), true); - // stack: ... - storeInMemoryDynamic(strType, _padToWordBoundaries); - } - else - { - solAssert(_givenTypes[i]->category() == Type::Category::Array, "Unknown dynamic type."); - auto const& arrayType = dynamic_cast(*_givenTypes[i]); - // now copy the array - copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.sizeOnStack()); - // stack: ... - // copy length to memory - m_context << dupInstruction(1 + arrayType.sizeOnStack()); - ArrayUtils(m_context).retrieveLength(arrayType, 1); - // stack: ... - storeInMemoryDynamic(*TypeProvider::uint256(), true); - // stack: ... - // copy the new memory pointer - m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP; - // stack: ... - // copy data part - ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries); - // stack: ... - } - - thisDynPointer++; - } - stackPos += _givenTypes[i]->sizeOnStack(); - } - - // remove unneeded stack elements (and retain memory pointer) - m_context << swapInstruction(argSize + dynPointers + 1); - popStackSlots(argSize + dynPointers + 1); -} - -void CompilerUtils::abiEncodeV2( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes, - bool _padToWordBoundaries -) -{ - if (!_padToWordBoundaries) - solAssert(!_encodeAsLibraryTypes, "Library calls cannot be packed."); - - // stack: <$value0> <$value1> ... <$value(n-1)> <$headStart> - - auto ret = m_context.pushNewTag(); - moveIntoStack(sizeOnStack(_givenTypes) + 1); - - string encoderName = - _padToWordBoundaries ? - m_context.abiFunctions().tupleEncoder(_givenTypes, _targetTypes, _encodeAsLibraryTypes) : - m_context.abiFunctions().tupleEncoderPacked(_givenTypes, _targetTypes); - m_context.appendJumpTo(m_context.namedTag(encoderName)); - m_context.adjustStackOffset(-int(sizeOnStack(_givenTypes)) - 1); - m_context << ret.tag(); -} - -void CompilerUtils::abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory) -{ - // stack: [stack top] - auto ret = m_context.pushNewTag(); - moveIntoStack(2); - // stack: [stack top] - m_context << Instruction::DUP2 << Instruction::ADD; - m_context << Instruction::SWAP1; - // stack: - string decoderName = m_context.abiFunctions().tupleDecoder(_parameterTypes, _fromMemory); - m_context.appendJumpTo(m_context.namedTag(decoderName)); - m_context.adjustStackOffset(int(sizeOnStack(_parameterTypes)) - 3); - m_context << ret.tag(); -} - -void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) -{ - if (_type.baseType()->hasSimpleZeroValueInMemory()) - { - solAssert(_type.baseType()->isValueType(), ""); - Whiskers templ(R"({ - let size := mul(length, ) - // cheap way of zero-initializing a memory range - calldatacopy(memptr, calldatasize(), size) - memptr := add(memptr, size) - })"); - templ("element_size", to_string(_type.memoryStride())); - m_context.appendInlineAssembly(templ.render(), {"length", "memptr"}); - } - else - { - auto repeat = m_context.newTag(); - m_context << repeat; - pushZeroValue(*_type.baseType()); - storeInMemoryDynamic(*_type.baseType()); - m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1; - m_context << Instruction::SUB << Instruction::SWAP1; - m_context << Instruction::DUP2; - m_context.appendConditionalJumpTo(repeat); - } - m_context << Instruction::SWAP1 << Instruction::POP; -} - -void CompilerUtils::memoryCopy32() -{ - // Stack here: size target source - - m_context.appendInlineAssembly(R"( - { - for { let i := 0 } lt(i, len) { i := add(i, 32) } { - mstore(add(dst, i), mload(add(src, i))) - } - } - )", - { "len", "dst", "src" } - ); - m_context << Instruction::POP << Instruction::POP << Instruction::POP; -} - -void CompilerUtils::memoryCopy() -{ - // Stack here: size target source - - m_context.appendInlineAssembly(R"( - { - // copy 32 bytes at once - for - {} - iszero(lt(len, 32)) - { - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - } - { mstore(dst, mload(src)) } - - // copy the remainder (0 < len < 32) - let mask := sub(exp(256, sub(32, len)), 1) - let srcpart := and(mload(src), not(mask)) - let dstpart := and(mload(dst), mask) - mstore(dst, or(srcpart, dstpart)) - } - )", - { "len", "dst", "src" } - ); - m_context << Instruction::POP << Instruction::POP << Instruction::POP; -} - -void CompilerUtils::splitExternalFunctionType(bool _leftAligned) -{ - // We have to split the left-aligned
into two stack slots: - // address (right aligned), function identifier (right aligned) - if (_leftAligned) - { - m_context << Instruction::DUP1; - rightShiftNumberOnStack(64 + 32); - //
- m_context << Instruction::SWAP1; - rightShiftNumberOnStack(64); - } - else - { - m_context << Instruction::DUP1; - rightShiftNumberOnStack(32); - m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; - } - m_context << u256(0xffffffffUL) << Instruction::AND; -} - -void CompilerUtils::combineExternalFunctionType(bool _leftAligned) -{ - //
- m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; - if (!_leftAligned) - m_context << ((u256(1) << 160) - 1) << Instruction::AND; - leftShiftNumberOnStack(32); - m_context << Instruction::OR; - if (_leftAligned) - leftShiftNumberOnStack(64); -} - -void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly) -{ - m_context << m_context.functionEntryLabel(_function).pushTag(); - // If there is a runtime context, we have to merge both labels into the same - // stack slot in case we store it in storage. - if (CompilerContext* rtc = m_context.runtimeContext()) - { - leftShiftNumberOnStack(32); - if (_runtimeOnly) - m_context << - rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << - Instruction::OR; - } -} - -void CompilerUtils::convertType( - Type const& _typeOnStack, - Type const& _targetType, - bool _cleanupNeeded, - bool _chopSignBits, - bool _asPartOfArgumentDecoding -) -{ - // For a type extension, we need to remove all higher-order bits that we might have ignored in - // previous operations. - // @todo: store in the AST whether the operand might have "dirty" higher order bits - - if (_typeOnStack == _targetType && !_cleanupNeeded) - return; - Type::Category stackTypeCategory = _typeOnStack.category(); - Type::Category targetTypeCategory = _targetType.category(); - - if (auto contrType = dynamic_cast(&_typeOnStack)) - solAssert(!contrType->isSuper(), "Cannot convert magic variable \"super\""); - - bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); - bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer; - if (chopSignBitsPending) - { - IntegerType const& targetIntegerType = dynamic_cast(_targetType); - chopSignBitsPending = targetIntegerType.isSigned(); - } - - if (targetTypeCategory == Type::Category::FixedPoint) - solUnimplemented("Not yet implemented - FixedPointType."); - - switch (stackTypeCategory) - { - case Type::Category::FixedBytes: - { - FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); - if (targetTypeCategory == Type::Category::Integer) - { - // conversion from bytes to integer. no need to clean the high bit - // only to shift right because of opposite alignment - IntegerType const& targetIntegerType = dynamic_cast(_targetType); - rightShiftNumberOnStack(256 - typeOnStack.numBytes() * 8); - if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8) - convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded); - } - else if (targetTypeCategory == Type::Category::Address) - { - solAssert(typeOnStack.numBytes() * 8 == 160, ""); - rightShiftNumberOnStack(256 - 160); - } - else - { - // clear for conversion to longer bytes - solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); - FixedBytesType const& targetType = dynamic_cast(_targetType); - if (typeOnStack.numBytes() == 0 || targetType.numBytes() == 0) - m_context << Instruction::POP << u256(0); - else if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded) - { - unsigned bytes = min(typeOnStack.numBytes(), targetType.numBytes()); - m_context << ((u256(1) << (256 - bytes * 8)) - 1); - m_context << Instruction::NOT << Instruction::AND; - } - } - break; - } - case Type::Category::Enum: - solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); - if (enumOverflowCheckPending) - { - EnumType const& enumType = dynamic_cast(_typeOnStack); - solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); - m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - if (_asPartOfArgumentDecoding) - m_context.appendConditionalRevert(false, "Enum out of range"); - else - m_context.appendConditionalInvalid(); - enumOverflowCheckPending = false; - } - break; - case Type::Category::FixedPoint: - solUnimplemented("Not yet implemented - FixedPointType."); - case Type::Category::Address: - case Type::Category::Integer: - case Type::Category::Contract: - case Type::Category::RationalNumber: - if (targetTypeCategory == Type::Category::FixedBytes) - { - solAssert( - stackTypeCategory == Type::Category::Address || - stackTypeCategory == Type::Category::Integer || - stackTypeCategory == Type::Category::RationalNumber, - "Invalid conversion to FixedBytesType requested." - ); - // conversion from bytes to string. no need to clean the high bit - // only to shift left because of opposite alignment - FixedBytesType const& targetBytesType = dynamic_cast(_targetType); - if (auto typeOnStack = dynamic_cast(&_typeOnStack)) - { - if (targetBytesType.numBytes() * 8 > typeOnStack->numBits()) - cleanHigherOrderBits(*typeOnStack); - } - else if (stackTypeCategory == Type::Category::Address) - solAssert(targetBytesType.numBytes() * 8 == 160, ""); - leftShiftNumberOnStack(256 - targetBytesType.numBytes() * 8); - } - else if (targetTypeCategory == Type::Category::Enum) - { - solAssert(stackTypeCategory != Type::Category::Address, "Invalid conversion to EnumType requested."); - solAssert(_typeOnStack.mobileType(), ""); - // just clean - convertType(_typeOnStack, *_typeOnStack.mobileType(), true); - EnumType const& enumType = dynamic_cast(_targetType); - solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); - m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalInvalid(); - enumOverflowCheckPending = false; - } - else if (targetTypeCategory == Type::Category::FixedPoint) - { - solAssert( - stackTypeCategory == Type::Category::Integer || - stackTypeCategory == Type::Category::RationalNumber || - stackTypeCategory == Type::Category::FixedPoint, - "Invalid conversion to FixedMxNType requested." - ); - //shift all integer bits onto the left side of the fixed type - FixedPointType const& targetFixedPointType = dynamic_cast(_targetType); - if (auto typeOnStack = dynamic_cast(&_typeOnStack)) - if (targetFixedPointType.numBits() > typeOnStack->numBits()) - cleanHigherOrderBits(*typeOnStack); - solUnimplemented("Not yet implemented - FixedPointType."); - } - else - { - solAssert( - targetTypeCategory == Type::Category::Integer || - targetTypeCategory == Type::Category::Contract || - targetTypeCategory == Type::Category::Address, - "" - ); - IntegerType addressType(160); - IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast(_targetType) : addressType; - if (stackTypeCategory == Type::Category::RationalNumber) - { - RationalNumberType const& constType = dynamic_cast(_typeOnStack); - // We know that the stack is clean, we only have to clean for a narrowing conversion - // where cleanup is forced. - solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); - if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded) - cleanHigherOrderBits(targetType); - } - else - { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast(_typeOnStack) : addressType; - // Widening: clean up according to source type width - // Non-widening and force: clean up according to target type bits - if (targetType.numBits() > typeOnStack.numBits()) - cleanHigherOrderBits(typeOnStack); - else if (_cleanupNeeded) - cleanHigherOrderBits(targetType); - if (chopSignBitsPending) - { - if (targetType.numBits() < 256) - m_context - << ((u256(1) << targetType.numBits()) - 1) - << Instruction::AND; - chopSignBitsPending = false; - } - } - } - break; - case Type::Category::StringLiteral: - { - auto const& literalType = dynamic_cast(_typeOnStack); - string const& value = literalType.value(); - bytesConstRef data(value); - if (targetTypeCategory == Type::Category::FixedBytes) - { - unsigned const numBytes = dynamic_cast(_targetType).numBytes(); - solAssert(data.size() <= 32, ""); - m_context << (h256::Arith(h256(data, h256::AlignLeft)) & (~(u256(-1) >> (8 * numBytes)))); - } - else if (targetTypeCategory == Type::Category::Array) - { - auto const& arrayType = dynamic_cast(_targetType); - solAssert(arrayType.isByteArray(), ""); - unsigned storageSize = 32 + ((data.size() + 31) / 32) * 32; - allocateMemory(storageSize); - // stack: mempos - m_context << Instruction::DUP1 << u256(data.size()); - storeInMemoryDynamic(*TypeProvider::uint256()); - // stack: mempos datapos - storeStringData(data); - } - else - solAssert( - false, - "Invalid conversion from string literal to " + _targetType.toString(false) + " requested." - ); - break; - } - case Type::Category::Array: - { - solAssert(targetTypeCategory == stackTypeCategory, ""); - ArrayType const& typeOnStack = dynamic_cast(_typeOnStack); - ArrayType const& targetType = dynamic_cast(_targetType); - switch (targetType.location()) - { - case DataLocation::Storage: - // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. - solAssert( - (targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) && - typeOnStack.location() == DataLocation::Storage, - "Invalid conversion to storage type." - ); - break; - case DataLocation::Memory: - { - // Copy the array to a free position in memory, unless it is already in memory. - if (typeOnStack.location() != DataLocation::Memory) - { - // stack: (variably sized) - unsigned stackSize = typeOnStack.sizeOnStack(); - ArrayUtils(m_context).retrieveLength(typeOnStack); - - // allocate memory - // stack: (variably sized) - m_context << Instruction::DUP1; - ArrayUtils(m_context).convertLengthToSize(targetType, true); - // stack: (variably sized) - if (targetType.isDynamicallySized()) - m_context << u256(0x20) << Instruction::ADD; - allocateMemory(); - // stack: (variably sized) - m_context << Instruction::DUP1; - moveIntoStack(2 + stackSize); - if (targetType.isDynamicallySized()) - { - m_context << Instruction::DUP2; - storeInMemoryDynamic(*TypeProvider::uint256()); - } - // stack: (variably sized) - if (targetType.baseType()->isValueType()) - { - copyToStackTop(2 + stackSize, stackSize); - ArrayUtils(m_context).copyArrayToMemory(typeOnStack); - } - else - { - m_context << u256(0) << Instruction::SWAP1; - // stack: (variably sized) - auto repeat = m_context.newTag(); - m_context << repeat; - m_context << Instruction::DUP3 << Instruction::DUP3; - m_context << Instruction::LT << Instruction::ISZERO; - auto loopEnd = m_context.appendConditionalJump(); - copyToStackTop(3 + stackSize, stackSize); - copyToStackTop(2 + stackSize, 1); - ArrayUtils(m_context).accessIndex(typeOnStack, false); - if (typeOnStack.location() == DataLocation::Storage) - StorageItem(m_context, *typeOnStack.baseType()).retrieveValue(SourceLocation(), true); - convertType(*typeOnStack.baseType(), *targetType.baseType(), _cleanupNeeded); - storeInMemoryDynamic(*targetType.baseType(), true); - m_context << Instruction::SWAP1 << u256(1) << Instruction::ADD; - m_context << Instruction::SWAP1; - m_context.appendJumpTo(repeat); - m_context << loopEnd; - m_context << Instruction::POP; - } - // stack: (variably sized) - popStackSlots(2 + stackSize); - // Stack: - } - break; - } - case DataLocation::CallData: - solAssert( - targetType.isByteArray() && - typeOnStack.isByteArray() && - typeOnStack.location() == DataLocation::CallData, - "Invalid conversion to calldata type." - ); - break; - } - break; - } - case Type::Category::ArraySlice: - { - auto& typeOnStack = dynamic_cast(_typeOnStack); - solAssert(_targetType == typeOnStack.arrayType(), ""); - solUnimplementedAssert( - typeOnStack.arrayType().location() == DataLocation::CallData && - typeOnStack.arrayType().isDynamicallySized(), - "" - ); - break; - } - case Type::Category::Struct: - { - solAssert(targetTypeCategory == stackTypeCategory, ""); - auto& targetType = dynamic_cast(_targetType); - auto& typeOnStack = dynamic_cast(_typeOnStack); - solAssert( - targetType.location() != DataLocation::CallData - , ""); - switch (targetType.location()) - { - case DataLocation::Storage: - // Other cases are done explicitly in LValue::storeValue, and only possible by assignment. - solAssert( - targetType.isPointer() && - typeOnStack.location() == DataLocation::Storage, - "Invalid conversion to storage type." - ); - break; - case DataLocation::Memory: - // Copy the array to a free position in memory, unless it is already in memory. - switch (typeOnStack.location()) - { - case DataLocation::Storage: - { - auto conversionImpl = - [typeOnStack = &typeOnStack, targetType = &targetType](CompilerContext& _context) - { - CompilerUtils utils(_context); - // stack: - utils.allocateMemory(typeOnStack->memoryDataSize()); - _context << Instruction::SWAP1 << Instruction::DUP2; - // stack: - for (auto const& member: typeOnStack->members(nullptr)) - { - if (!member.type->canLiveOutsideStorage()) - continue; - pair const& offsets = typeOnStack->storageOffsetsOfMember(member.name); - _context << offsets.first << Instruction::DUP3 << Instruction::ADD; - _context << u256(offsets.second); - StorageItem(_context, *member.type).retrieveValue(SourceLocation(), true); - TypePointer targetMemberType = targetType->memberType(member.name); - solAssert(!!targetMemberType, "Member not found in target type."); - utils.convertType(*member.type, *targetMemberType, true); - utils.storeInMemoryDynamic(*targetMemberType, true); - } - _context << Instruction::POP << Instruction::POP; - }; - if (typeOnStack.recursive()) - m_context.callLowLevelFunction( - "$convertRecursiveArrayStorageToMemory_" + typeOnStack.identifier() + "_to_" + targetType.identifier(), - 1, - 1, - conversionImpl - ); - else - conversionImpl(m_context); - break; - } - case DataLocation::CallData: - { - solUnimplementedAssert(!typeOnStack.isDynamicallyEncoded(), ""); - m_context << Instruction::DUP1; - m_context << Instruction::CALLDATASIZE; - m_context << Instruction::SUB; - abiDecode({&targetType}, false); - break; - } - case DataLocation::Memory: - // nothing to do - break; - } - break; - case DataLocation::CallData: - solAssert(false, "Invalid type conversion target location CallData."); - break; - } - break; - } - case Type::Category::Tuple: - { - TupleType const& sourceTuple = dynamic_cast(_typeOnStack); - TupleType const& targetTuple = dynamic_cast(_targetType); - solAssert(targetTuple.components().size() == sourceTuple.components().size(), ""); - unsigned depth = sourceTuple.sizeOnStack(); - for (size_t i = 0; i < sourceTuple.components().size(); ++i) - { - TypePointer sourceType = sourceTuple.components()[i]; - TypePointer targetType = targetTuple.components()[i]; - if (!sourceType) - { - solAssert(!targetType, ""); - continue; - } - unsigned sourceSize = sourceType->sizeOnStack(); - unsigned targetSize = targetType ? targetType->sizeOnStack() : 0; - if (!targetType || *sourceType != *targetType || _cleanupNeeded) - { - if (targetType) - { - if (sourceSize > 0) - copyToStackTop(depth, sourceSize); - convertType(*sourceType, *targetType, _cleanupNeeded); - } - if (sourceSize > 0 || targetSize > 0) - { - // Move it back into its place. - for (unsigned j = 0; j < min(sourceSize, targetSize); ++j) - m_context << - swapInstruction(depth + targetSize - sourceSize) << - Instruction::POP; - // Value shrank - for (unsigned j = targetSize; j < sourceSize; ++j) - { - moveToStackTop(depth - 1, 1); - m_context << Instruction::POP; - } - // Value grew - if (targetSize > sourceSize) - moveIntoStack(depth + targetSize - sourceSize - 1, targetSize - sourceSize); - } - } - depth -= sourceSize; - } - break; - } - case Type::Category::Bool: - solAssert(_targetType == _typeOnStack, "Invalid conversion for bool."); - if (_cleanupNeeded) - m_context << Instruction::ISZERO << Instruction::ISZERO; - break; - default: - // we used to allow conversions from function to address - solAssert(!(stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Address), ""); - if (stackTypeCategory == Type::Category::Function && targetTypeCategory == Type::Category::Function) - { - FunctionType const& typeOnStack = dynamic_cast(_typeOnStack); - FunctionType const& targetType = dynamic_cast(_targetType); - solAssert( - typeOnStack.isImplicitlyConvertibleTo(targetType) && - typeOnStack.sizeOnStack() == targetType.sizeOnStack() && - (typeOnStack.kind() == FunctionType::Kind::Internal || typeOnStack.kind() == FunctionType::Kind::External) && - typeOnStack.kind() == targetType.kind(), - "Invalid function type conversion requested." - ); - } - else - // All other types should not be convertible to non-equal types. - solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); - - if (_cleanupNeeded && _targetType.canBeStored() && _targetType.storageBytes() < 32) - m_context - << ((u256(1) << (8 * _targetType.storageBytes())) - 1) - << Instruction::AND; - break; - } - - solAssert(!enumOverflowCheckPending, "enum overflow checking missing."); - solAssert(!chopSignBitsPending, "forgot to chop the sign bits."); -} - -void CompilerUtils::pushZeroValue(Type const& _type) -{ - if (auto const* funType = dynamic_cast(&_type)) - { - if (funType->kind() == FunctionType::Kind::Internal) - { - m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { - _context.appendInvalid(); - }); - if (CompilerContext* runCon = m_context.runtimeContext()) - { - leftShiftNumberOnStack(32); - m_context << runCon->lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) { - _context.appendInvalid(); - }).toSubAssemblyTag(m_context.runtimeSub()); - m_context << Instruction::OR; - } - return; - } - } - auto const* referenceType = dynamic_cast(&_type); - if (!referenceType || referenceType->location() == DataLocation::Storage) - { - for (size_t i = 0; i < _type.sizeOnStack(); ++i) - m_context << u256(0); - return; - } - solAssert(referenceType->location() == DataLocation::Memory, ""); - if (auto arrayType = dynamic_cast(&_type)) - if (arrayType->isDynamicallySized()) - { - // Push a memory location that is (hopefully) always zero. - pushZeroPointer(); - return; - } - - TypePointer type = &_type; - m_context.callLowLevelFunction( - "$pushZeroValue_" + referenceType->identifier(), - 0, - 1, - [type](CompilerContext& _context) { - CompilerUtils utils(_context); - - utils.allocateMemory(max(32u, type->memoryDataSize())); - _context << Instruction::DUP1; - - if (auto structType = dynamic_cast(type)) - for (auto const& member: structType->members(nullptr)) - { - utils.pushZeroValue(*member.type); - utils.storeInMemoryDynamic(*member.type); - } - else if (auto arrayType = dynamic_cast(type)) - { - solAssert(!arrayType->isDynamicallySized(), ""); - if (arrayType->length() > 0) - { - _context << arrayType->length() << Instruction::SWAP1; - // stack: items_to_do memory_pos - utils.zeroInitialiseMemoryArray(*arrayType); - // stack: updated_memory_pos - } - } - else - solAssert(false, "Requested initialisation for unknown type: " + type->toString()); - - // remove the updated memory pointer - _context << Instruction::POP; - } - ); -} - -void CompilerUtils::pushZeroPointer() -{ - m_context << u256(zeroPointer); -} - -void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) -{ - unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.baseStackOffsetOfVariable(_variable)); - unsigned const size = _variable.annotation().type->sizeOnStack(); - solAssert(stackPosition >= size, "Variable size and position mismatch."); - // move variable starting from its top end in the stack - if (stackPosition - size + 1 > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_variable.location()) << - util::errinfo_comment("Stack too deep, try removing local variables.") - ); - for (unsigned i = 0; i < size; ++i) - m_context << swapInstruction(stackPosition - size + 1) << Instruction::POP; -} - -void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) -{ - solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables."); - for (unsigned i = 0; i < _itemSize; ++i) - m_context << dupInstruction(_stackDepth); -} - -void CompilerUtils::moveToStackTop(unsigned _stackDepth, unsigned _itemSize) -{ - moveIntoStack(_itemSize, _stackDepth); -} - -void CompilerUtils::moveIntoStack(unsigned _stackDepth, unsigned _itemSize) -{ - if (_stackDepth <= _itemSize) - for (unsigned i = 0; i < _stackDepth; ++i) - rotateStackDown(_stackDepth + _itemSize); - else - for (unsigned i = 0; i < _itemSize; ++i) - rotateStackUp(_stackDepth + _itemSize); -} - -void CompilerUtils::rotateStackUp(unsigned _items) -{ - solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); - for (unsigned i = 1; i < _items; ++i) - m_context << swapInstruction(_items - i); -} - -void CompilerUtils::rotateStackDown(unsigned _items) -{ - solAssert(_items - 1 <= 16, "Stack too deep, try removing local variables."); - for (unsigned i = 1; i < _items; ++i) - m_context << swapInstruction(i); -} - -void CompilerUtils::popStackElement(Type const& _type) -{ - popStackSlots(_type.sizeOnStack()); -} - -void CompilerUtils::popStackSlots(size_t _amount) -{ - for (size_t i = 0; i < _amount; ++i) - m_context << Instruction::POP; -} - -void CompilerUtils::popAndJump(unsigned _toHeight, evmasm::AssemblyItem const& _jumpTo) -{ - solAssert(m_context.stackHeight() >= _toHeight, ""); - unsigned amount = m_context.stackHeight() - _toHeight; - popStackSlots(amount); - m_context.appendJumpTo(_jumpTo); - m_context.adjustStackOffset(amount); -} - -unsigned CompilerUtils::sizeOnStack(vector const& _variableTypes) -{ - unsigned size = 0; - for (Type const* const& type: _variableTypes) - size += type->sizeOnStack(); - return size; -} - -void CompilerUtils::computeHashStatic() -{ - storeInMemory(0); - m_context << u256(32) << u256(0) << Instruction::KECCAK256; -} - -void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation) -{ - string which = _creation ? "Creation" : "Runtime"; - m_context.callLowLevelFunction( - "$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(), - 1, - 1, - [&contract, _creation](CompilerContext& _context) - { - // copy the contract's code into memory - shared_ptr assembly = - _creation ? - _context.compiledContract(contract) : - _context.compiledContractRuntime(contract); - // pushes size - auto subroutine = _context.addSubroutine(assembly); - _context << Instruction::DUP1 << subroutine; - _context << Instruction::DUP4 << Instruction::CODECOPY; - _context << Instruction::ADD; - } - ); -} - -void CompilerUtils::storeStringData(bytesConstRef _data) -{ - //@todo provide both alternatives to the optimiser - // stack: mempos - if (_data.size() <= 32) - { - for (unsigned i = 0; i < _data.size(); i += 32) - { - m_context << h256::Arith(h256(_data.cropped(i), h256::AlignLeft)); - storeInMemoryDynamic(*TypeProvider::uint256()); - } - m_context << Instruction::POP; - } - else - { - // stack: mempos mempos_data - m_context.appendData(_data.toBytes()); - m_context << u256(_data.size()) << Instruction::SWAP2; - m_context << Instruction::CODECOPY; - } -} - -unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords) -{ - solAssert(_type.isValueType(), ""); - - unsigned numBytes = _type.calldataEncodedSize(_padToWords); - bool isExternalFunctionType = false; - if (auto const* funType = dynamic_cast(&_type)) - if (funType->kind() == FunctionType::Kind::External) - isExternalFunctionType = true; - if (numBytes == 0) - { - m_context << Instruction::POP << u256(0); - return numBytes; - } - solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); - m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); - bool cleanupNeeded = true; - if (isExternalFunctionType) - splitExternalFunctionType(true); - else if (numBytes != 32) - { - bool leftAligned = _type.category() == Type::Category::FixedBytes; - // add leading or trailing zeros by dividing/multiplying depending on alignment - int shiftFactor = (32 - numBytes) * 8; - rightShiftNumberOnStack(shiftFactor); - if (leftAligned) - { - leftShiftNumberOnStack(shiftFactor); - cleanupNeeded = false; - } - else if (IntegerType const* intType = dynamic_cast(&_type)) - if (!intType->isSigned()) - cleanupNeeded = false; - } - if (_fromCalldata) - convertType(_type, _type, cleanupNeeded, false, true); - - return numBytes; -} - -void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack) -{ - if (_typeOnStack.numBits() == 256) - return; - else if (_typeOnStack.isSigned()) - m_context << u256(_typeOnStack.numBits() / 8 - 1) << Instruction::SIGNEXTEND; - else - m_context << ((u256(1) << _typeOnStack.numBits()) - 1) << Instruction::AND; -} - -void CompilerUtils::leftShiftNumberOnStack(unsigned _bits) -{ - solAssert(_bits < 256, ""); - if (m_context.evmVersion().hasBitwiseShifting()) - m_context << _bits << Instruction::SHL; - else - m_context << (u256(1) << _bits) << Instruction::MUL; -} - -void CompilerUtils::rightShiftNumberOnStack(unsigned _bits) -{ - solAssert(_bits < 256, ""); - // NOTE: If we add signed right shift, SAR rounds differently than SDIV - if (m_context.evmVersion().hasBitwiseShifting()) - m_context << _bits << Instruction::SHR; - else - m_context << (u256(1) << _bits) << Instruction::SWAP1 << Instruction::DIV; -} - -unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWords) -{ - solAssert( - _type.sizeOnStack() == 1, - "Memory store of types with stack size != 1 not allowed (Type: " + _type.toString(true) + ")." - ); - - solAssert(!_type.isDynamicallyEncoded(), ""); - - unsigned numBytes = _type.calldataEncodedSize(_padToWords); - - solAssert( - numBytes > 0, - "Memory store of 0 bytes requested (Type: " + _type.toString(true) + ")." - ); - - solAssert( - numBytes <= 32, - "Memory store of more than 32 bytes requested (Type: " + _type.toString(true) + ")." - ); - - bool leftAligned = _type.category() == Type::Category::FixedBytes; - - convertType(_type, _type, true); - if (numBytes != 32 && !leftAligned && !_padToWords) - // shift the value accordingly before storing - leftShiftNumberOnStack((32 - numBytes) * 8); - - return numBytes; -} diff --git a/compiler/libsolidity/codegen/CompilerUtils.h b/compiler/libsolidity/codegen/CompilerUtils.h deleted file mode 100644 index 3d5d8582..00000000 --- a/compiler/libsolidity/codegen/CompilerUtils.h +++ /dev/null @@ -1,329 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Routines used by both the compiler and the expression compiler. - */ - -#pragma once - -#include -#include -#include -#include -#include - -namespace solidity::frontend { - -class Type; // forward - -class CompilerUtils -{ -public: - explicit CompilerUtils(CompilerContext& _context): m_context(_context) - {} - - /// Stores the initial value of the free-memory-pointer at its position; - void initialiseFreeMemoryPointer(); - /// Copies the free memory pointer to the stack. - /// Stack pre: - /// Stack post: - void fetchFreeMemoryPointer(); - /// Stores the free memory pointer from the stack. - /// Stack pre: - /// Stack post: - void storeFreeMemoryPointer(); - /// Allocates a number of bytes in memory as given on the stack. - /// Stack pre: - /// Stack post: - void allocateMemory(); - /// Allocates a number of bytes in memory as given on the stack. - /// Stack pre: - /// Stack post: - void allocateMemory(u256 const& size); - /// Appends code that transforms memptr to (memptr - free_memptr) memptr - /// Stack pre: - /// Stack post: - void toSizeAfterFreeMemoryPointer(); - - /// Appends code that performs a revert, providing the given string data. - /// Will also append an error signature corresponding to Error(string). - /// @param _argumentType the type of the string argument, will be converted to memory string. - /// Stack pre: string data - /// Stack post: - void revertWithStringData(Type const& _argumentType); - - /// Allocates a new array and copies the return data to it. - /// If the EVM does not support return data, creates an empty array. - void returnDataToArray(); - - /// Computes the absolute calldata offset of a tail given a base reference and the (absolute) - /// offset of the tail pointer. Performs bounds checks. If @a _type is a dynamically sized array it also - /// returns the array length on the stack. - /// Stack pre: base_ref tail_ptr - /// Stack post: tail_ref [length] - void accessCalldataTail(Type const& _type); - - /// Loads data from memory to the stack. - /// @param _offset offset in memory (or calldata) - /// @param _type data type to load - /// @param _fromCalldata if true, load from calldata, not from memory - /// @param _padToWords if true, assume the data is padded to full words (32 bytes) - /// @returns the number of bytes consumed in memory. - unsigned loadFromMemory( - unsigned _offset, - Type const& _type = *TypeProvider::uint256(), - bool _fromCalldata = false, - bool _padToWords = false - ); - /// Dynamic version of @see loadFromMemory, expects the memory offset on the stack. - /// Stack pre: memory_offset - /// Stack post: value... (memory_offset+length) - void loadFromMemoryDynamic( - Type const& _type, - bool _fromCalldata = false, - bool _padToWords = true, - bool _keepUpdatedMemoryOffset = true - ); - /// Stores a 256 bit integer from stack in memory. - /// @param _offset offset in memory - void storeInMemory(unsigned _offset); - /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack - /// and also updates that. For reference types, only copies the data pointer. Fails for - /// non-memory-references. - /// @param _padToWords if true, adds zeros to pad to multiple of 32 bytes. Array elements - /// are always padded (except for byte arrays), regardless of this parameter. - /// Stack pre: memory_offset value... - /// Stack post: (memory_offset+length) - void storeInMemoryDynamic(Type const& _type, bool _padToWords = true); - - /// Creates code that unpacks the arguments according to their types specified by a vector of TypePointers. - /// From memory if @a _fromMemory is true, otherwise from call data. - /// Calls revert if the supplied size is shorter than the static data requirements - /// or if dynamic data pointers reach outside of the area. - /// Also has a hard cap of 0x100000000 for any given length/offset field. - /// Stack pre: - /// Stack post: ... - void abiDecode(TypePointers const& _typeParameters, bool _fromMemory = false); - - /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given - /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes. - /// Removes the values from the stack and leaves the updated memory pointer. - /// Stack pre: ... - /// Stack post: - /// Does not touch the memory-free pointer. - /// @param _padToWords if false, all values are concatenated without padding. - /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length) - /// together with fixed-length data. - /// @param _encodeAsLibraryTypes if true, encodes for a library function, e.g. does not - /// convert storage pointer types to memory types. - /// @note the locations of target reference types are ignored, because it will always be - /// memory. - void encodeToMemory( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _padToWords, - bool _copyDynamicDataInPlace, - bool _encodeAsLibraryTypes = false - ); - - /// Special case of @a encodeToMemory which assumes tight packing, e.g. no zero padding - /// and dynamic data is encoded in-place. - /// Stack pre: ... - /// Stack post: - void packedEncode( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes = false - ) - { - encodeToMemory(_givenTypes, _targetTypes, false, true, _encodeAsLibraryTypes); - } - - /// Special case of @a encodeToMemory which assumes that everything is padded to words - /// and dynamic data is not copied in place (i.e. a proper ABI encoding). - /// Stack pre: ... - /// Stack post: - void abiEncode( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes = false - ) - { - encodeToMemory(_givenTypes, _targetTypes, true, false, _encodeAsLibraryTypes); - } - - /// Special case of @a encodeToMemory which assumes that everything is padded to words - /// and dynamic data is not copied in place (i.e. a proper ABI encoding). - /// Uses a new, less tested encoder implementation. - /// Stack pre: ... - /// Stack post: - void abiEncodeV2( - TypePointers const& _givenTypes, - TypePointers const& _targetTypes, - bool _encodeAsLibraryTypes = false, - bool _padToWordBoundaries = true - ); - - /// Decodes data from ABI encoding into internal encoding. If @a _fromMemory is set to true, - /// the data is taken from memory instead of from calldata. - /// Can allocate memory. - /// Stack pre: - /// Stack post: ... - void abiDecodeV2(TypePointers const& _parameterTypes, bool _fromMemory = false); - - /// Zero-initialises (the data part of) an already allocated memory array. - /// Length has to be nonzero! - /// Stack pre: - /// Stack post: - void zeroInitialiseMemoryArray(ArrayType const& _type); - - /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length. - /// Length can be zero, in this case, it copies nothing. - /// Stack pre: - /// Stack post: - void memoryCopy32(); - /// Copies data in memory (regions cannot overlap). - /// Length can be zero, in this case, it copies nothing. - /// Stack pre: - /// Stack post: - void memoryCopy(); - - /// Stores the given string in memory. - /// Stack pre: mempos - /// Stack post: - void storeStringData(bytesConstRef _data); - - /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) - /// external function type
into two stack slots: - /// address (right aligned), function identifier (right aligned) - void splitExternalFunctionType(bool _rightAligned); - /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) - void combineExternalFunctionType(bool _rightAligned); - /// Appends code that combines the construction-time (if available) and runtime function - /// entry label of the given function into a single stack slot. - /// Note: This might cause the compilation queue of the runtime context to be extended. - /// If @a _runtimeOnly, the entry label will include the runtime assembly tag. - void pushCombinedFunctionEntryLabel(Declaration const& _function, bool _runtimeOnly = true); - - /// Appends code for an implicit or explicit type conversion. This includes erasing higher - /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory - /// if a reference type is converted from calldata or storage to memory. - /// If @a _cleanupNeeded, high order bits cleanup is also done if no type conversion would be - /// necessary. - /// If @a _chopSignBits, the function resets the signed bits out of the width of the signed integer. - /// If @a _asPartOfArgumentDecoding is true, failed conversions are flagged via REVERT, - /// otherwise they are flagged with INVALID. - void convertType( - Type const& _typeOnStack, - Type const& _targetType, - bool _cleanupNeeded = false, - bool _chopSignBits = false, - bool _asPartOfArgumentDecoding = false - ); - - /// Creates a zero-value for the given type and puts it onto the stack. This might allocate - /// memory for memory references. - void pushZeroValue(Type const& _type); - /// Pushes a pointer to the stack that points to a (potentially shared) location in memory - /// that always contains a zero. It is not allowed to write there. - void pushZeroPointer(); - - /// Moves the value that is at the top of the stack to a stack variable. - void moveToStackVariable(VariableDeclaration const& _variable); - /// Copies an item that occupies @a _itemSize stack slots from a stack depth of @a _stackDepth - /// to the top of the stack. - void copyToStackTop(unsigned _stackDepth, unsigned _itemSize); - /// Moves an item that occupies @a _itemSize stack slots and has items occupying @a _stackDepth - /// slots above it to the top of the stack. - void moveToStackTop(unsigned _stackDepth, unsigned _itemSize = 1); - /// Moves @a _itemSize elements past @a _stackDepth other stack elements - void moveIntoStack(unsigned _stackDepth, unsigned _itemSize = 1); - /// Rotates the topmost @a _items items on the stack, such that the previously topmost element - /// is bottom-most. - void rotateStackUp(unsigned _items); - /// Rotates the topmost @a _items items on the stack, such that the previously bottom-most element - /// is now topmost. - void rotateStackDown(unsigned _items); - /// Removes the current value from the top of the stack. - void popStackElement(Type const& _type); - /// Removes element from the top of the stack _amount times. - void popStackSlots(size_t _amount); - /// Pops slots from the stack such that its height is _toHeight. - /// Adds jump to _jumpTo. - /// Readjusts the stack offset to the original value. - void popAndJump(unsigned _toHeight, evmasm::AssemblyItem const& _jumpTo); - - template - static unsigned sizeOnStack(std::vector const& _variables); - static unsigned sizeOnStack(std::vector const& _variableTypes); - - /// Helper function to shift top value on the stack to the left. - /// Stack pre: - /// Stack post: - void leftShiftNumberOnStack(unsigned _bits); - - /// Helper function to shift top value on the stack to the right. - /// Stack pre: - /// Stack post: - void rightShiftNumberOnStack(unsigned _bits); - - /// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type. - void computeHashStatic(); - - /// Apppends code that copies the code of the given contract to memory. - /// Stack pre: Memory position - /// Stack post: Updated memory position - /// @param creation if true, copies creation code, if false copies runtime code. - /// @note the contract has to be compiled already, so beware of cyclic dependencies! - void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode); - - /// Bytes we need to the start of call data. - /// - The size in bytes of the function (hash) identifier. - static unsigned const dataStartOffset; - - /// Position of the free-memory-pointer in memory; - static size_t const freeMemoryPointer; - /// Position of the memory slot that is always zero. - static size_t const zeroPointer; - /// Starting offset for memory available to the user (aka the contract). - static size_t const generalPurposeMemoryStart; - -private: - /// Appends code that cleans higher-order bits for integer types. - void cleanHigherOrderBits(IntegerType const& _typeOnStack); - - /// Prepares the given type for storing in memory by shifting it if necessary. - unsigned prepareMemoryStore(Type const& _type, bool _padToWords); - /// Loads type from memory assuming memory offset is on stack top. - unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWords); - - CompilerContext& m_context; -}; - - -template -unsigned CompilerUtils::sizeOnStack(std::vector const& _variables) -{ - unsigned size = 0; - for (T const& variable: _variables) - size += variable->annotation().type->sizeOnStack(); - return size; -} - -} diff --git a/compiler/libsolidity/codegen/ContractCompiler.cpp b/compiler/libsolidity/codegen/ContractCompiler.cpp deleted file mode 100644 index 0d2d9aac..00000000 --- a/compiler/libsolidity/codegen/ContractCompiler.cpp +++ /dev/null @@ -1,1343 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Solidity compiler. - */ - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include - -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::frontend; -using namespace solidity::langutil; - -using solidity::util::FixedHash; -using solidity::util::h256; -using solidity::util::errinfo_comment; - - -namespace -{ - -/** - * Simple helper class to ensure that the stack height is the same at certain places in the code. - */ -class StackHeightChecker -{ -public: - explicit StackHeightChecker(CompilerContext const& _context): - m_context(_context), stackHeight(m_context.stackHeight()) {} - void check() - { - solAssert( - m_context.stackHeight() == stackHeight, - std::string("I sense a disturbance in the stack: ") + to_string(m_context.stackHeight()) + " vs " + to_string(stackHeight) - ); - } -private: - CompilerContext const& m_context; - unsigned stackHeight; -}; - -} - -void ContractCompiler::compileContract( - ContractDefinition const& /*_contract*/, - map> const& /*_otherCompilers*/ -) -{ - solAssert(false, ""); -// CompilerContext::LocationSetter locationSetter(m_context, _contract); - -// TVMCompilerProceedContract(_contract, nullptr); - -// if (_contract.isLibrary()) -// // Check whether this is a call (true) or a delegatecall (false). -// // This has to be the first code in the contract. -// appendDelegatecallCheck(); - -// initializeContext(_contract, _otherCompilers); -// // This generates the dispatch function for externally visible functions -// // and adds the function to the compilation queue. Additionally internal functions, -// // which are referenced directly or indirectly will be added. -// appendFunctionSelector(_contract); -// // This processes the above populated queue until it is empty. -// appendMissingFunctions(); -} - -size_t ContractCompiler::compileConstructor( - ContractDefinition const& _contract, - std::map> const& _otherCompilers -) -{ - CompilerContext::LocationSetter locationSetter(m_context, _contract); - if (_contract.isLibrary()) - return deployLibrary(_contract); - else - { - initializeContext(_contract, _otherCompilers); - return packIntoContractCreator(_contract); - } -} - -void ContractCompiler::initializeContext( - ContractDefinition const& _contract, - map> const& _otherCompilers -) -{ - m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); - m_context.setOtherCompilers(_otherCompilers); - m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); - CompilerUtils(m_context).initialiseFreeMemoryPointer(); - registerStateVariables(_contract); - m_context.resetVisitedNodes(&_contract); -} - -void ContractCompiler::appendCallValueCheck() -{ - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalRevert(false, "Ether sent to non-payable function"); -} - -void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) -{ - solAssert(!_contract.isLibrary(), "Tried to initialize library."); - CompilerContext::LocationSetter locationSetter(m_context, _contract); - - m_baseArguments = &_contract.annotation().baseConstructorArguments; - - // Initialization of state variables in base-to-derived order. - for (ContractDefinition const* contract: boost::adaptors::reverse( - _contract.annotation().linearizedBaseContracts - )) - initializeStateVariables(*contract); - - if (FunctionDefinition const* constructor = _contract.constructor()) - appendConstructor(*constructor); - else if (auto c = m_context.nextConstructor(_contract)) - appendBaseConstructor(*c); - else - appendCallValueCheck(); -} - -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) -{ - solAssert(!!m_runtimeCompiler, ""); - solAssert(!_contract.isLibrary(), "Tried to use contract creator or library."); - - appendInitAndConstructorCode(_contract); - - // We jump to the deploy routine because we first have to append all missing functions, - // which can cause further functions to be added to the runtime context. - evmasm::AssemblyItem deployRoutine = m_context.appendJumpToNew(); - - // We have to include copies of functions in the construction time and runtime context - // because of absolute jumps. - appendMissingFunctions(); - m_runtimeCompiler->appendMissingFunctions(); - - CompilerContext::LocationSetter locationSetter(m_context, _contract); - m_context << deployRoutine; - - solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); - m_context.pushSubroutineSize(m_context.runtimeSub()); - m_context << Instruction::DUP1; - m_context.pushSubroutineOffset(m_context.runtimeSub()); - m_context << u256(0) << Instruction::CODECOPY; - m_context << u256(0) << Instruction::RETURN; - - return m_context.runtimeSub(); -} - -size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract) -{ - solAssert(!!m_runtimeCompiler, ""); - solAssert(_contract.isLibrary(), "Tried to deploy contract as library."); - - CompilerContext::LocationSetter locationSetter(m_context, _contract); - - solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); - m_context.pushSubroutineSize(m_context.runtimeSub()); - m_context.pushSubroutineOffset(m_context.runtimeSub()); - // This code replaces the address added by appendDeployTimeAddress(). - m_context.appendInlineAssembly(R"( - { - // If code starts at 11, an mstore(0) writes to the full PUSH20 plus data - // without the need for a shift. - let codepos := 11 - codecopy(codepos, subOffset, subSize) - // Check that the first opcode is a PUSH20 - if iszero(eq(0x73, byte(0, mload(codepos)))) { invalid() } - mstore(0, address()) - mstore8(codepos, 0x73) - return(codepos, subSize) - } - )", {"subSize", "subOffset"}); - - return m_context.runtimeSub(); -} - -void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) -{ - CompilerContext::LocationSetter locationSetter(m_context, _constructor); - FunctionType constructorType(_constructor); - if (!constructorType.parameterTypes().empty()) - { - solAssert(m_baseArguments, ""); - solAssert(m_baseArguments->count(&_constructor), ""); - std::vector> const* arguments = nullptr; - ASTNode const* baseArgumentNode = m_baseArguments->at(&_constructor); - if (auto inheritanceSpecifier = dynamic_cast(baseArgumentNode)) - arguments = inheritanceSpecifier->arguments(); - else if (auto modifierInvocation = dynamic_cast(baseArgumentNode)) - arguments = modifierInvocation->arguments(); - solAssert(arguments, ""); - solAssert(arguments->size() == constructorType.parameterTypes().size(), ""); - for (unsigned i = 0; i < arguments->size(); ++i) - compileExpression(*(arguments->at(i)), constructorType.parameterTypes()[i]); - } - _constructor.accept(*this); -} - -void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) -{ - CompilerContext::LocationSetter locationSetter(m_context, _constructor); - if (!_constructor.isPayable()) - appendCallValueCheck(); - - // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program - if (!_constructor.parameters().empty()) - { - CompilerUtils(m_context).fetchFreeMemoryPointer(); - // CODESIZE returns the actual size of the code, - // which is the size of the generated code (``programSize``) - // plus the constructor arguments added to the transaction payload. - m_context.appendProgramSize(); - m_context << Instruction::CODESIZE << Instruction::SUB; - // stack: - m_context << Instruction::DUP1; - m_context.appendProgramSize(); - m_context << Instruction::DUP4 << Instruction::CODECOPY; - // stack: - m_context << Instruction::DUP2 << Instruction::DUP2 << Instruction::ADD; - // stack: - CompilerUtils(m_context).storeFreeMemoryPointer(); - // stack: - CompilerUtils(m_context).abiDecode(FunctionType(_constructor).parameterTypes(), true); - } - _constructor.accept(*this); -} - -void ContractCompiler::appendDelegatecallCheck() -{ - // Special constant that will be replaced by the address at deploy time. - // At compilation time, this is just "PUSH20 00...000". - m_context.appendDeployTimeAddress(); - m_context << Instruction::ADDRESS << Instruction::EQ; - // The result on the stack is - // "We have not been called via DELEGATECALL". -} - -void ContractCompiler::appendInternalSelector( - map, evmasm::AssemblyItem const> const& _entryPoints, - vector> const& _ids, - evmasm::AssemblyItem const& _notFoundTag, - size_t _runs -) -{ - // Code for selecting from n functions without split: - // n times: dup1, push4 , eq, push2/3 , jumpi - // push2/3 jump - // (called SELECT[n]) - // Code for selecting from n functions with split: - // dup1, push4 , gt, push2/3, jumpi - // SELECT[n/2] - // tag_less: - // SELECT[n/2] - // - // This means each split adds 16-18 bytes of additional code (note the additional jump out!) - // The average execution cost if we do not split at all are: - // (3 + 3 + 3 + 3 + 10) * n/2 = 24 * n/2 = 12 * n - // If we split once: - // (3 + 3 + 3 + 3 + 10) + 24 * n/4 = 24 * (n/4 + 1) = 6 * n + 24; - // - // We should split if - // _runs * 12 * n > _runs * (6 * n + 24) + 17 * createDataGas - // <=> _runs * 6 * (n - 4) > 17 * createDataGas - // - // Which also means that the execution itself is not profitable - // unless we have at least 5 functions. - - // Start with some comparisons to avoid overflow, then do the actual comparison. - bool split = false; - if (_ids.size() <= 4) - split = false; - else if (_runs > (17 * evmasm::GasCosts::createDataGas) / 6) - split = true; - else - split = (_runs * 6 * (_ids.size() - 4) > 17 * evmasm::GasCosts::createDataGas); - - if (split) - { - size_t pivotIndex = _ids.size() / 2; - FixedHash<4> pivot{_ids.at(pivotIndex)}; - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT; - evmasm::AssemblyItem lessTag{m_context.appendConditionalJump()}; - // Here, we have funid >= pivot - vector> larger{_ids.begin() + pivotIndex, _ids.end()}; - appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs); - m_context << lessTag; - // Here, we have funid < pivot - vector> smaller{_ids.begin(), _ids.begin() + pivotIndex}; - appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs); - } - else - { - for (auto const& id: _ids) - { - m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ; - m_context.appendConditionalJumpTo(_entryPoints.at(id)); - } - m_context.appendJumpTo(_notFoundTag); - } -} - -namespace -{ - -// Helper function to check if any function is payable -bool hasPayableFunctions(ContractDefinition const& _contract) -{ - if (_contract.receiveFunction()) - return true; - - FunctionDefinition const* fallback = _contract.fallbackFunction(); - if (fallback && fallback->isPayable()) - return true; - - for (auto const& it: _contract.interfaceFunctions()) - if (it.second->isPayable()) - return true; - - return false; -} - -} - -void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract) -{ - map, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions(); - map, evmasm::AssemblyItem const> callDataUnpackerEntryPoints; - - if (_contract.isLibrary()) - { - solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected."); - } - - FunctionDefinition const* fallback = _contract.fallbackFunction(); - solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions"); - - FunctionDefinition const* etherReceiver = _contract.receiveFunction(); - solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have ether receiver functions"); - - bool needToAddCallvalueCheck = true; - if (!hasPayableFunctions(_contract) && !interfaceFunctions.empty() && !_contract.isLibrary()) - { - appendCallValueCheck(); - needToAddCallvalueCheck = false; - } - - evmasm::AssemblyItem notFoundOrReceiveEther = m_context.newTag(); - // If there is neither a fallback nor a receive ether function, we only need one label to jump to, which - // always reverts. - evmasm::AssemblyItem notFound = (!fallback && !etherReceiver) ? notFoundOrReceiveEther : m_context.newTag(); - - // directly jump to fallback or ether receiver if the data is too short to contain a function selector - // also guards against short data - m_context << u256(4) << Instruction::CALLDATASIZE << Instruction::LT; - m_context.appendConditionalJumpTo(notFoundOrReceiveEther); - - // retrieve the function signature hash from the calldata - if (!interfaceFunctions.empty()) - { - CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true); - - // stack now is: ? - vector> sortedIDs; - for (auto const& it: interfaceFunctions) - { - callDataUnpackerEntryPoints.emplace(it.first, m_context.newTag()); - sortedIDs.emplace_back(it.first); - } - std::sort(sortedIDs.begin(), sortedIDs.end()); - appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimiserSettings.expectedExecutionsPerDeployment); - } - - m_context << notFoundOrReceiveEther; - - if (!fallback && !etherReceiver) - m_context.appendRevert("Contract does not have fallback nor receive functions"); - else - { - if (etherReceiver) - { - // directly jump to fallback, if there is calldata - m_context << Instruction::CALLDATASIZE; - m_context.appendConditionalJumpTo(notFound); - - solAssert(!_contract.isLibrary(), ""); - solAssert(etherReceiver->isReceive(), ""); - solAssert(FunctionType(*etherReceiver).parameterTypes().empty(), ""); - solAssert(FunctionType(*etherReceiver).returnParameterTypes().empty(), ""); - etherReceiver->accept(*this); - m_context << Instruction::STOP; - } - - m_context << notFound; - if (fallback) - { - solAssert(!_contract.isLibrary(), ""); - if (!fallback->isPayable() && needToAddCallvalueCheck) - appendCallValueCheck(); - - solAssert(fallback->isFallback(), ""); - solAssert(FunctionType(*fallback).parameterTypes().empty(), ""); - solAssert(FunctionType(*fallback).returnParameterTypes().empty(), ""); - fallback->accept(*this); - m_context << Instruction::STOP; - } - else - m_context.appendRevert("Unknown signature and no fallback defined"); - } - - - for (auto const& it: interfaceFunctions) - { - FunctionTypePointer const& functionType = it.second; - solAssert(functionType->hasDeclaration(), ""); - CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration()); - - m_context << callDataUnpackerEntryPoints.at(it.first); - if (_contract.isLibrary() && functionType->stateMutability() > StateMutability::View) - { - // If the function is not a view function and is called without DELEGATECALL, - // we revert. - m_context << dupInstruction(2); - m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL"); - } - m_context.setStackOffset(0); - // We have to allow this for libraries, because value of the previous - // call is still visible in the delegatecall. - if (!functionType->isPayable() && !_contract.isLibrary() && needToAddCallvalueCheck) - appendCallValueCheck(); - - // Return tag is used to jump out of the function. - evmasm::AssemblyItem returnTag = m_context.pushNewTag(); - if (!functionType->parameterTypes().empty()) - { - // Parameter for calldataUnpacker - m_context << CompilerUtils::dataStartOffset; - m_context << Instruction::DUP1 << Instruction::CALLDATASIZE << Instruction::SUB; - CompilerUtils(m_context).abiDecode(functionType->parameterTypes()); - } - m_context.appendJumpTo( - m_context.functionEntryLabel(functionType->declaration()), - evmasm::AssemblyItem::JumpType::IntoFunction - ); - m_context << returnTag; - // Return tag and input parameters get consumed. - m_context.adjustStackOffset( - CompilerUtils(m_context).sizeOnStack(functionType->returnParameterTypes()) - - CompilerUtils(m_context).sizeOnStack(functionType->parameterTypes()) - - 1 - ); - // Consumes the return parameters. - appendReturnValuePacker(functionType->returnParameterTypes(), _contract.isLibrary()); - } -} - -void ContractCompiler::appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary) -{ - CompilerUtils utils(m_context); - if (_typeParameters.empty()) - m_context << Instruction::STOP; - else - { - utils.fetchFreeMemoryPointer(); - //@todo optimization: if we return a single memory array, there should be enough space before - // its data to add the needed parts and we avoid a memory copy. - utils.abiEncode(_typeParameters, _typeParameters, _isLibrary); - utils.toSizeAfterFreeMemoryPointer(); - m_context << Instruction::RETURN; - } -} - -void ContractCompiler::registerStateVariables(ContractDefinition const& _contract) -{ - for (auto const& var: ContractType(_contract).stateVariables()) - m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); -} - -void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract) -{ - solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library."); - for (VariableDeclaration const* variable: _contract.stateVariables()) - if (variable->value() && !variable->isConstant()) - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals).appendStateVariableInitialization(*variable); -} - -bool ContractCompiler::visit(VariableDeclaration const& _variableDeclaration) -{ - solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); - CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration); - - m_context.startFunction(_variableDeclaration); - m_breakTags.clear(); - m_continueTags.clear(); - - if (_variableDeclaration.isConstant()) - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) - .appendConstStateVariableAccessor(_variableDeclaration); - else - ExpressionCompiler(m_context, m_optimiserSettings.runOrderLiterals) - .appendStateVariableAccessor(_variableDeclaration); - - return false; -} - -bool ContractCompiler::visit(FunctionDefinition const& _function) -{ - CompilerContext::LocationSetter locationSetter(m_context, _function); - - m_context.startFunction(_function); - - // stack upon entry: [return address] [arg0] [arg1] ... [argn] - // reserve additional slots: [retarg0] ... [retargm] - - unsigned parametersSize = CompilerUtils::sizeOnStack(_function.parameters()); - if (!_function.isConstructor()) - // adding 1 for return address. - m_context.adjustStackOffset(parametersSize + 1); - for (ASTPointer const& variable: _function.parameters()) - { - m_context.addVariable(*variable, parametersSize); - parametersSize -= variable->annotation().type->sizeOnStack(); - } - - for (ASTPointer const& variable: _function.returnParameters()) - appendStackVariableInitialisation(*variable); - - if (_function.isConstructor()) - if (auto c = m_context.nextConstructor(dynamic_cast(*_function.scope()))) - appendBaseConstructor(*c); - - solAssert(m_returnTags.empty(), ""); - m_breakTags.clear(); - m_continueTags.clear(); - m_currentFunction = &_function; - m_modifierDepth = -1; - m_scopeStackHeight.clear(); - m_context.setModifierDepth(0); - - appendModifierOrFunctionCode(); - m_context.setModifierDepth(0); - solAssert(m_returnTags.empty(), ""); - - // Now we need to re-shuffle the stack. For this we keep a record of the stack layout - // that shows the target positions of the elements, where "-1" denotes that this element needs - // to be removed from the stack. - // Note that the fact that the return arguments are of increasing index is vital for this - // algorithm to work. - - unsigned const c_argumentsSize = CompilerUtils::sizeOnStack(_function.parameters()); - unsigned const c_returnValuesSize = CompilerUtils::sizeOnStack(_function.returnParameters()); - - vector stackLayout; - stackLayout.push_back(c_returnValuesSize); // target of return address - stackLayout += vector(c_argumentsSize, -1); // discard all arguments - for (unsigned i = 0; i < c_returnValuesSize; ++i) - stackLayout.push_back(i); - - if (stackLayout.size() > 17) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_function.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - while (stackLayout.back() != int(stackLayout.size() - 1)) - if (stackLayout.back() < 0) - { - m_context << Instruction::POP; - stackLayout.pop_back(); - } - else - { - m_context << swapInstruction(stackLayout.size() - stackLayout.back() - 1); - swap(stackLayout[stackLayout.back()], stackLayout.back()); - } - for (int i = 0; i < int(stackLayout.size()); ++i) - if (stackLayout[i] != i) - solAssert(false, "Invalid stack layout on cleanup."); - - for (ASTPointer const& variable: _function.parameters() + _function.returnParameters()) - m_context.removeVariable(*variable); - - m_context.adjustStackOffset(-(int)c_returnValuesSize); - - /// The constructor and the fallback function doesn't to jump out. - if (!_function.isConstructor()) - { - solAssert(m_context.numberOfLocalVariables() == 0, ""); - if (!_function.isFallback() && !_function.isReceive()) - m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); - } - - return false; -} - -bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) -{ - unsigned startStackHeight = m_context.stackHeight(); - yul::ExternalIdentifierAccess identifierAccess; - identifierAccess.resolve = [&](yul::Identifier const& _identifier, yul::IdentifierContext, bool) - { - auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); - if (ref == _inlineAssembly.annotation().externalReferences.end()) - return size_t(-1); - return ref->second.valueSize; - }; - identifierAccess.generateCode = [&](yul::Identifier const& _identifier, yul::IdentifierContext _context, yul::AbstractAssembly& _assembly) - { - auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); - solAssert(ref != _inlineAssembly.annotation().externalReferences.end(), ""); - Declaration const* decl = ref->second.declaration; - solAssert(!!decl, ""); - if (_context == yul::IdentifierContext::RValue) - { - int const depositBefore = _assembly.stackHeight(); - solAssert(!!decl->type(), "Type of declaration required but not yet determined."); - if (FunctionDefinition const* functionDef = dynamic_cast(decl)) - { - solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); - functionDef = &m_context.resolveVirtualFunction(*functionDef); - auto functionEntryLabel = m_context.functionEntryLabel(*functionDef).pushTag(); - solAssert(functionEntryLabel.data() <= std::numeric_limits::max(), ""); - _assembly.appendLabelReference(size_t(functionEntryLabel.data())); - // If there is a runtime context, we have to merge both labels into the same - // stack slot in case we store it in storage. - if (CompilerContext* rtc = m_context.runtimeContext()) - { - _assembly.appendConstant(u256(1) << 32); - _assembly.appendInstruction(Instruction::MUL); - auto runtimeEntryLabel = rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub()); - solAssert(runtimeEntryLabel.data() <= std::numeric_limits::max(), ""); - _assembly.appendLabelReference(size_t(runtimeEntryLabel.data())); - _assembly.appendInstruction(Instruction::OR); - } - } - else if (auto variable = dynamic_cast(decl)) - { - if (variable->isConstant()) - { - variable = rootVariableDeclaration(*variable); - u256 value; - if (variable->value()->annotation().type->category() == Type::Category::RationalNumber) - { - value = dynamic_cast(*variable->value()->annotation().type).literalValue(nullptr); - if (FixedBytesType const* bytesType = dynamic_cast(variable->type())) - value = value << (256 - 8 * bytesType->numBytes()); - else - solAssert(variable->type()->category() == Type::Category::Integer, ""); - } - else if (Literal const* literal = dynamic_cast(variable->value().get())) - { - TypePointer type = literal->annotation().type; - - switch (type->category()) - { - case Type::Category::Bool: - case Type::Category::Address: - solAssert(*type == *variable->annotation().type, ""); - value = type->literalValue(literal); - break; - case Type::Category::StringLiteral: - { - StringLiteralType const& stringLiteral = dynamic_cast(*type); - solAssert(variable->type()->category() == Type::Category::FixedBytes, ""); - unsigned const numBytes = dynamic_cast(*variable->type()).numBytes(); - solAssert(stringLiteral.value().size() <= numBytes, ""); - value = u256(h256(stringLiteral.value(), h256::AlignLeft)); - break; - } - default: - solAssert(false, ""); - } - } - else - solAssert(false, "Invalid constant in inline assembly."); - m_context << value; - } - else if (m_context.isStateVariable(decl)) - { - auto const& location = m_context.storageLocationOfVariable(*decl); - if (ref->second.isSlot) - m_context << location.first; - else if (ref->second.isOffset) - m_context << u256(location.second); - else - solAssert(false, ""); - } - else if (m_context.isLocalVariable(decl)) - { - int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable); - if (ref->second.isSlot || ref->second.isOffset) - { - solAssert(variable->type()->dataStoredIn(DataLocation::Storage), ""); - unsigned size = variable->type()->sizeOnStack(); - if (size == 2) - { - // slot plus offset - if (ref->second.isOffset) - stackDiff--; - } - else - { - solAssert(size == 1, ""); - // only slot, offset is zero - if (ref->second.isOffset) - { - _assembly.appendConstant(u256(0)); - return; - } - } - } - else - solAssert(variable->type()->sizeOnStack() == 1, ""); - if (stackDiff < 1 || stackDiff > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - solAssert(variable->type()->sizeOnStack() == 1, ""); - _assembly.appendInstruction(dupInstruction(stackDiff)); - } - else - solAssert(false, ""); - } - else if (auto contract = dynamic_cast(decl)) - { - solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); - solAssert(contract->isLibrary(), ""); - _assembly.appendLinkerSymbol(contract->fullyQualifiedName()); - } - else - solAssert(false, "Invalid declaration type."); - solAssert(_assembly.stackHeight() - depositBefore == int(ref->second.valueSize), ""); - } - else - { - // lvalue context - solAssert(!ref->second.isOffset && !ref->second.isSlot, ""); - auto variable = dynamic_cast(decl); - solAssert( - !!variable && m_context.isLocalVariable(variable), - "Can only assign to stack variables in inline assembly." - ); - solAssert(variable->type()->sizeOnStack() == 1, ""); - int stackDiff = _assembly.stackHeight() - m_context.baseStackOffsetOfVariable(*variable) - 1; - if (stackDiff > 16 || stackDiff < 1) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_inlineAssembly.location()) << - errinfo_comment("Stack too deep(" + to_string(stackDiff) + "), try removing local variables.") - ); - _assembly.appendInstruction(swapInstruction(stackDiff)); - _assembly.appendInstruction(Instruction::POP); - } - }; - solAssert(_inlineAssembly.annotation().analysisInfo, ""); - yul::CodeGenerator::assemble( - _inlineAssembly.operations(), - *_inlineAssembly.annotation().analysisInfo, - *m_context.assemblyPtr(), - m_context.evmVersion(), - identifierAccess, - false, - m_optimiserSettings.optimizeStackAllocation - ); - m_context.setStackOffset(startStackHeight); - return false; -} - -bool ContractCompiler::visit(TryStatement const& _tryStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _tryStatement); - - compileExpression(_tryStatement.externalCall()); - int const returnSize = static_cast(_tryStatement.externalCall().annotation().type->sizeOnStack()); - - // Stack: [ return values] - evmasm::AssemblyItem successTag = m_context.appendConditionalJump(); - - // Catch case. - m_context.adjustStackOffset(-returnSize); - - handleCatch(_tryStatement.clauses()); - - evmasm::AssemblyItem endTag = m_context.appendJumpToNew(); - - m_context << successTag; - m_context.adjustStackOffset(returnSize); - { - // Success case. - // Stack: return values - TryCatchClause const& successClause = *_tryStatement.clauses().front(); - if (successClause.parameters()) - { - vector exprTypes{_tryStatement.externalCall().annotation().type}; - if (auto tupleType = dynamic_cast(exprTypes.front())) - exprTypes = tupleType->components(); - vector> const& params = successClause.parameters()->parameters(); - solAssert(exprTypes.size() == params.size(), ""); - for (size_t i = 0; i < exprTypes.size(); ++i) - solAssert(params[i] && exprTypes[i] && *params[i]->annotation().type == *exprTypes[i], ""); - } - else - CompilerUtils(m_context).popStackSlots(returnSize); - - _tryStatement.clauses().front()->accept(*this); - } - - m_context << endTag; - checker.check(); - return false; -} - -void ContractCompiler::handleCatch(vector> const& _catchClauses) -{ - // Stack is empty. - ASTPointer structured{}; - ASTPointer fallback{}; - for (size_t i = 1; i < _catchClauses.size(); ++i) - if (_catchClauses[i]->errorName() == "Error") - structured = _catchClauses[i]; - else if (_catchClauses[i]->errorName().empty()) - fallback = _catchClauses[i]; - else - solAssert(false, ""); - - solAssert(_catchClauses.size() == size_t(1 + (structured ? 1 : 0) + (fallback ? 1 : 0)), ""); - - evmasm::AssemblyItem endTag = m_context.newTag(); - evmasm::AssemblyItem fallbackTag = m_context.newTag(); - if (structured) - { - solAssert( - structured->parameters() && - structured->parameters()->parameters().size() == 1 && - structured->parameters()->parameters().front() && - *structured->parameters()->parameters().front()->annotation().type == *TypeProvider::stringMemory(), - "" - ); - solAssert(m_context.evmVersion().supportsReturndata(), ""); - - string errorHash = FixedHash<4>(util::keccak256("Error(string)")).hex(); - - // Try to decode the error message. - // If this fails, leaves 0 on the stack, otherwise the pointer to the data string. - m_context << u256(0); - m_context.appendInlineAssembly( - util::Whiskers(R"({ - data := mload(0x40) - mstore(data, 0) - for {} 1 {} { - if lt(returndatasize(), 0x44) { data := 0 break } - returndatacopy(0, 0, 4) - let sig := - if iszero(eq(sig, 0x)) { data := 0 break } - returndatacopy(data, 4, sub(returndatasize(), 4)) - let offset := mload(data) - if or( - gt(offset, 0xffffffffffffffff), - gt(add(offset, 0x24), returndatasize()) - ) { - data := 0 - break - } - let msg := add(data, offset) - let length := mload(msg) - if gt(length, 0xffffffffffffffff) { data := 0 break } - let end := add(add(msg, 0x20), length) - if gt(end, add(data, returndatasize())) { data := 0 break } - mstore(0x40, and(add(end, 0x1f), not(0x1f))) - data := msg - break - } - })") - ("ErrorSignature", errorHash) - ("getSig", - m_context.evmVersion().hasBitwiseShifting() ? - "shr(224, mload(0))" : - "div(mload(0), " + (u256(1) << 224).str() + ")" - ).render(), - {"data"} - ); - m_context << Instruction::DUP1; - AssemblyItem decodeSuccessTag = m_context.appendConditionalJump(); - m_context << Instruction::POP; - m_context.appendJumpTo(fallbackTag); - m_context.adjustStackOffset(1); - - m_context << decodeSuccessTag; - structured->accept(*this); - m_context.appendJumpTo(endTag); - } - m_context << fallbackTag; - if (fallback) - { - if (fallback->parameters()) - { - solAssert(m_context.evmVersion().supportsReturndata(), ""); - solAssert( - fallback->parameters()->parameters().size() == 1 && - fallback->parameters()->parameters().front() && - *fallback->parameters()->parameters().front()->annotation().type == *TypeProvider::bytesMemory(), - "" - ); - CompilerUtils(m_context).returnDataToArray(); - } - - fallback->accept(*this); - } - else - { - // re-throw - if (m_context.evmVersion().supportsReturndata()) - m_context.appendInlineAssembly(R"({ - returndatacopy(0, 0, returndatasize()) - revert(0, returndatasize()) - })"); - else - // Since both returndata and revert are >=byzantium, this should be unreachable. - solAssert(false, ""); - } - m_context << endTag; -} - -bool ContractCompiler::visit(TryCatchClause const& _clause) -{ - CompilerContext::LocationSetter locationSetter(m_context, _clause); - - unsigned varSize = 0; - - if (_clause.parameters()) - for (ASTPointer const& varDecl: _clause.parameters()->parameters() | boost::adaptors::reversed) - { - solAssert(varDecl, ""); - varSize += varDecl->annotation().type->sizeOnStack(); - m_context.addVariable(*varDecl, varSize); - } - - _clause.block().accept(*this); - - m_context.removeVariablesAboveStackHeight(m_context.stackHeight() - varSize); - CompilerUtils(m_context).popStackSlots(varSize); - - return false; -} - -bool ContractCompiler::visit(IfStatement const& _ifStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); - compileExpression(_ifStatement.condition()); - m_context << Instruction::ISZERO; - evmasm::AssemblyItem falseTag = m_context.appendConditionalJump(); - evmasm::AssemblyItem endTag = falseTag; - _ifStatement.trueStatement().accept(*this); - if (_ifStatement.falseStatement()) - { - endTag = m_context.appendJumpToNew(); - m_context << falseTag; - _ifStatement.falseStatement()->accept(*this); - } - m_context << endTag; - - checker.check(); - return false; -} - -bool ContractCompiler::visit(WhileStatement const& _whileStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); - - evmasm::AssemblyItem loopStart = m_context.newTag(); - evmasm::AssemblyItem loopEnd = m_context.newTag(); - m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); - - m_context << loopStart; - - if (_whileStatement.isDoWhile()) - { - evmasm::AssemblyItem condition = m_context.newTag(); - m_continueTags.emplace_back(condition, m_context.stackHeight()); - - _whileStatement.body().accept(*this); - - m_context << condition; - compileExpression(_whileStatement.condition()); - m_context << Instruction::ISZERO << Instruction::ISZERO; - m_context.appendConditionalJumpTo(loopStart); - } - else - { - m_continueTags.emplace_back(loopStart, m_context.stackHeight()); - compileExpression(_whileStatement.condition()); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(loopEnd); - - _whileStatement.body().accept(*this); - - m_context.appendJumpTo(loopStart); - } - m_context << loopEnd; - - m_continueTags.pop_back(); - m_breakTags.pop_back(); - - checker.check(); - return false; -} - -bool ContractCompiler::visit(ForStatement const& _forStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _forStatement); - evmasm::AssemblyItem loopStart = m_context.newTag(); - evmasm::AssemblyItem loopEnd = m_context.newTag(); - evmasm::AssemblyItem loopNext = m_context.newTag(); - - storeStackHeight(&_forStatement); - - if (_forStatement.initializationExpression()) - _forStatement.initializationExpression()->accept(*this); - - m_breakTags.emplace_back(loopEnd, m_context.stackHeight()); - m_continueTags.emplace_back(loopNext, m_context.stackHeight()); - m_context << loopStart; - - // if there is no terminating condition in for, default is to always be true - if (_forStatement.condition()) - { - compileExpression(*_forStatement.condition()); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(loopEnd); - } - - _forStatement.body().accept(*this); - - m_context << loopNext; - - // for's loop expression if existing - if (_forStatement.loopExpression()) - _forStatement.loopExpression()->accept(*this); - - m_context.appendJumpTo(loopStart); - - m_context << loopEnd; - - m_continueTags.pop_back(); - m_breakTags.pop_back(); - - // For the case where no break/return is executed: - // loop initialization variables have to be freed - popScopedVariables(&_forStatement); - - checker.check(); - return false; -} - -bool ContractCompiler::visit(Continue const& _continueStatement) -{ - CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); - solAssert(!m_continueTags.empty(), ""); - CompilerUtils(m_context).popAndJump(m_continueTags.back().second, m_continueTags.back().first); - return false; -} - -bool ContractCompiler::visit(Break const& _breakStatement) -{ - CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); - solAssert(!m_breakTags.empty(), ""); - CompilerUtils(m_context).popAndJump(m_breakTags.back().second, m_breakTags.back().first); - return false; -} - -bool ContractCompiler::visit(Return const& _return) -{ - CompilerContext::LocationSetter locationSetter(m_context, _return); - if (Expression const* expression = _return.expression()) - { - solAssert(_return.annotation().functionReturnParameters, "Invalid return parameters pointer."); - vector> const& returnParameters = - _return.annotation().functionReturnParameters->parameters(); - TypePointers types; - for (auto const& retVariable: returnParameters) - types.push_back(retVariable->annotation().type); - - TypePointer expectedType; - if (expression->annotation().type->category() == Type::Category::Tuple || types.size() != 1) - expectedType = TypeProvider::tuple(move(types)); - else - expectedType = types.front(); - compileExpression(*expression, expectedType); - - for (auto const& retVariable: boost::adaptors::reverse(returnParameters)) - CompilerUtils(m_context).moveToStackVariable(*retVariable); - } - - CompilerUtils(m_context).popAndJump(m_returnTags.back().second, m_returnTags.back().first); - return false; -} - -bool ContractCompiler::visit(Throw const&) -{ - solAssert(false, "Throw statement is disallowed."); - return false; -} - -bool ContractCompiler::visit(EmitStatement const& _emit) -{ - CompilerContext::LocationSetter locationSetter(m_context, _emit); - StackHeightChecker checker(m_context); - compileExpression(_emit.eventCall()); - checker.check(); - return false; -} - -bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) -{ - CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); - - // Local variable slots are reserved when their declaration is visited, - // and freed in the end of their scope. - for (auto decl: _variableDeclarationStatement.declarations()) - if (decl) - appendStackVariableInitialisation(*decl); - - StackHeightChecker checker(m_context); - if (Expression const* expression = _variableDeclarationStatement.initialValue()) - { - CompilerUtils utils(m_context); - compileExpression(*expression); - TypePointers valueTypes; - if (auto tupleType = dynamic_cast(expression->annotation().type)) - valueTypes = tupleType->components(); - else - valueTypes = TypePointers{expression->annotation().type}; - auto const& declarations = _variableDeclarationStatement.declarations(); - solAssert(declarations.size() == valueTypes.size(), ""); - for (size_t i = 0; i < declarations.size(); ++i) - { - size_t j = declarations.size() - i - 1; - solAssert(!!valueTypes[j], ""); - if (VariableDeclaration const* varDecl = declarations[j].get()) - { - utils.convertType(*valueTypes[j], *varDecl->annotation().type); - utils.moveToStackVariable(*varDecl); - } - else - utils.popStackElement(*valueTypes[j]); - } - } - checker.check(); - return false; -} - -bool ContractCompiler::visit(ExpressionStatement const& _expressionStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); - Expression const& expression = _expressionStatement.expression(); - compileExpression(expression); - CompilerUtils(m_context).popStackElement(*expression.annotation().type); - checker.check(); - return false; -} - -bool ContractCompiler::visit(PlaceholderStatement const& _placeholderStatement) -{ - StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); - appendModifierOrFunctionCode(); - checker.check(); - return true; -} - -bool ContractCompiler::visit(Block const& _block) -{ - storeStackHeight(&_block); - return true; -} - -void ContractCompiler::endVisit(Block const& _block) -{ - // Frees local variables declared in the scope of this block. - popScopedVariables(&_block); -} - -void ContractCompiler::appendMissingFunctions() -{ - while (Declaration const* function = m_context.nextFunctionToCompile()) - { - m_context.setStackOffset(0); - function->accept(*this); - solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?"); - } - m_context.appendMissingLowLevelFunctions(); - auto abiFunctions = m_context.abiFunctions().requestedFunctions(); - if (!abiFunctions.first.empty()) - m_context.appendInlineAssembly( - "{" + move(abiFunctions.first) + "}", - {}, - abiFunctions.second, - true, - m_optimiserSettings - ); -} - -void ContractCompiler::appendModifierOrFunctionCode() -{ - solAssert(m_currentFunction, ""); - unsigned stackSurplus = 0; - Block const* codeBlock = nullptr; - vector addedVariables; - - m_modifierDepth++; - m_context.setModifierDepth(m_modifierDepth); - - if (m_modifierDepth >= m_currentFunction->modifiers().size()) - { - solAssert(m_currentFunction->isImplemented(), ""); - codeBlock = &m_currentFunction->body(); - } - else - { - ASTPointer const& modifierInvocation = m_currentFunction->modifiers()[m_modifierDepth]; - - // constructor call should be excluded - if (dynamic_cast(modifierInvocation->name()->annotation().referencedDeclaration)) - appendModifierOrFunctionCode(); - else - { - ModifierDefinition const& nonVirtualModifier = dynamic_cast( - *modifierInvocation->name()->annotation().referencedDeclaration - ); - ModifierDefinition const& modifier = m_context.resolveVirtualFunctionModifier(nonVirtualModifier); - CompilerContext::LocationSetter locationSetter(m_context, modifier); - std::vector> const& modifierArguments = - modifierInvocation->arguments() ? *modifierInvocation->arguments() : std::vector>(); - - solAssert(modifier.parameters().size() == modifierArguments.size(), ""); - for (unsigned i = 0; i < modifier.parameters().size(); ++i) - { - m_context.addVariable(*modifier.parameters()[i]); - addedVariables.push_back(modifier.parameters()[i].get()); - compileExpression( - *modifierArguments[i], - modifier.parameters()[i]->annotation().type - ); - } - - stackSurplus = CompilerUtils::sizeOnStack(modifier.parameters()); - codeBlock = &modifier.body(); - } - } - - if (codeBlock) - { - m_returnTags.emplace_back(m_context.newTag(), m_context.stackHeight()); - codeBlock->accept(*this); - - solAssert(!m_returnTags.empty(), ""); - m_context << m_returnTags.back().first; - m_returnTags.pop_back(); - - CompilerUtils(m_context).popStackSlots(stackSurplus); - for (auto var: addedVariables) - m_context.removeVariable(*var); - } - m_modifierDepth--; - m_context.setModifierDepth(m_modifierDepth); -} - -void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) -{ - CompilerContext::LocationSetter location(m_context, _variable); - m_context.addVariable(_variable); - CompilerUtils(m_context).pushZeroValue(*_variable.annotation().type); -} - -void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) -{ - ExpressionCompiler expressionCompiler(m_context, m_optimiserSettings.runOrderLiterals); - expressionCompiler.compile(_expression); - if (_targetType) - CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); -} - -void ContractCompiler::popScopedVariables(ASTNode const* _node) -{ - unsigned blockHeight = m_scopeStackHeight.at(m_modifierDepth).at(_node); - m_context.removeVariablesAboveStackHeight(blockHeight); - solAssert(m_context.stackHeight() >= blockHeight, ""); - unsigned stackDiff = m_context.stackHeight() - blockHeight; - CompilerUtils(m_context).popStackSlots(stackDiff); - m_scopeStackHeight[m_modifierDepth].erase(_node); - if (m_scopeStackHeight[m_modifierDepth].empty()) - m_scopeStackHeight.erase(m_modifierDepth); -} - -void ContractCompiler::storeStackHeight(ASTNode const* _node) -{ - m_scopeStackHeight[m_modifierDepth][_node] = m_context.stackHeight(); -} diff --git a/compiler/libsolidity/codegen/ContractCompiler.h b/compiler/libsolidity/codegen/ContractCompiler.h deleted file mode 100644 index 0a2ecf7e..00000000 --- a/compiler/libsolidity/codegen/ContractCompiler.h +++ /dev/null @@ -1,161 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Code generator for contracts. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace solidity::frontend -{ - -/** - * Code generator at the contract level. Can be used to generate code for exactly one contract - * either either in "runtime mode" or "creation mode". - */ -class ContractCompiler: private ASTConstVisitor -{ -public: - explicit ContractCompiler( - ContractCompiler* _runtimeCompiler, - CompilerContext& _context, - OptimiserSettings _optimiserSettings - ): - m_optimiserSettings(std::move(_optimiserSettings)), - m_runtimeCompiler(_runtimeCompiler), - m_context(_context) - { - } - - void compileContract( - ContractDefinition const& _contract, - std::map> const& _otherCompilers - ); - /// Compiles the constructor part of the contract. - /// @returns the identifier of the runtime sub-assembly. - size_t compileConstructor( - ContractDefinition const& _contract, - std::map> const& _otherCompilers - ); - -private: - /// Registers the non-function objects inside the contract with the context and stores the basic - /// information about the contract like the AST annotations. - void initializeContext( - ContractDefinition const& _contract, - std::map> const& _otherCompilers - ); - /// Adds the code that is run at creation time. Should be run after exchanging the run-time context - /// with a new and initialized context. Adds the constructor code. - /// @returns the identifier of the runtime sub assembly - size_t packIntoContractCreator(ContractDefinition const& _contract); - /// Appends code that deploys the given contract as a library. - /// Will also add code that modifies the contract in memory by injecting the current address - /// for the call protector. - size_t deployLibrary(ContractDefinition const& _contract); - /// Appends state variable initialisation and constructor code. - void appendInitAndConstructorCode(ContractDefinition const& _contract); - void appendBaseConstructor(FunctionDefinition const& _constructor); - void appendConstructor(FunctionDefinition const& _constructor); - /// Appends code that returns a boolean flag on the stack that tells whether - /// the contract has been called via delegatecall (false) or regular call (true). - /// This is done by inserting a specific push constant as the first instruction - /// whose data will be modified in memory at deploy time. - void appendDelegatecallCheck(); - /// Appends the function selector. Is called recursively to create a binary search tree. - /// @a _runs the number of intended executions of the contract to tune the split point. - void appendInternalSelector( - std::map, evmasm::AssemblyItem const> const& _entryPoints, - std::vector> const& _ids, - evmasm::AssemblyItem const& _notFoundTag, - size_t _runs - ); - void appendFunctionSelector(ContractDefinition const& _contract); - void appendCallValueCheck(); - void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary); - - void registerStateVariables(ContractDefinition const& _contract); - void initializeStateVariables(ContractDefinition const& _contract); - - bool visit(VariableDeclaration const& _variableDeclaration) override; - bool visit(FunctionDefinition const& _function) override; - bool visit(InlineAssembly const& _inlineAssembly) override; - bool visit(TryStatement const& _tryStatement) override; - void handleCatch(std::vector> const& _catchClauses); - bool visit(TryCatchClause const& _clause) override; - bool visit(IfStatement const& _ifStatement) override; - bool visit(WhileStatement const& _whileStatement) override; - bool visit(ForStatement const& _forStatement) override; - bool visit(Continue const& _continueStatement) override; - bool visit(Break const& _breakStatement) override; - bool visit(Return const& _return) override; - bool visit(Throw const& _throw) override; - bool visit(EmitStatement const& _emit) override; - bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override; - bool visit(ExpressionStatement const& _expressionStatement) override; - bool visit(PlaceholderStatement const&) override; - bool visit(Block const& _block) override; - void endVisit(Block const& _block) override; - - /// Repeatedly visits all function which are referenced but which are not compiled yet. - void appendMissingFunctions(); - - /// Appends one layer of function modifier code of the current function, or the function - /// body itself if the last modifier was reached. - void appendModifierOrFunctionCode(); - - void appendStackVariableInitialisation(VariableDeclaration const& _variable); - void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); - - /// Frees the variables of a certain scope (to be used when leaving). - void popScopedVariables(ASTNode const* _node); - - /// Sets the stack height for the visited loop. - void storeStackHeight(ASTNode const* _node); - - OptimiserSettings const m_optimiserSettings; - /// Pointer to the runtime compiler in case this is a creation compiler. - ContractCompiler* m_runtimeCompiler = nullptr; - CompilerContext& m_context; - /// Tag to jump to for a "break" statement and the stack height after freeing the local loop variables. - std::vector> m_breakTags; - /// Tag to jump to for a "continue" statement and the stack height after freeing the local loop variables. - std::vector> m_continueTags; - /// Tag to jump to for a "return" statement and the stack height after freeing the local function or modifier variables. - /// Needs to be stacked because of modifiers. - std::vector> m_returnTags; - unsigned m_modifierDepth = 0; - FunctionDefinition const* m_currentFunction = nullptr; - - // arguments for base constructors, filled in derived-to-base order - std::map const* m_baseArguments; - - /// Stores the variables that were declared inside a specific scope, for each modifier depth. - std::map> m_scopeStackHeight; -}; - -} diff --git a/compiler/libsolidity/codegen/ExpressionCompiler.cpp b/compiler/libsolidity/codegen/ExpressionCompiler.cpp deleted file mode 100644 index 695bc710..00000000 --- a/compiler/libsolidity/codegen/ExpressionCompiler.cpp +++ /dev/null @@ -1,2536 +0,0 @@ -/* - This file is part of solidity. - - solidity 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. - - solidity 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 solidity. If not, see . -*/ -/** - * @author Christian - * @date 2014 - * Solidity AST to EVM bytecode compiler for expressions. - */ - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -using namespace std; -using namespace solidity; -using namespace solidity::evmasm; -using namespace solidity::frontend; -using namespace solidity::langutil; -using namespace solidity::util; - - -void ExpressionCompiler::compile(Expression const& _expression) -{ - _expression.accept(*this); -} - -void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration const& _varDecl) -{ - if (!_varDecl.value()) - return; - TypePointer type = _varDecl.value()->annotation().type; - solAssert(!!type, "Type information not available."); - CompilerContext::LocationSetter locationSetter(m_context, _varDecl); - _varDecl.value()->accept(*this); - - if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage)) - { - // reference type, only convert value to mobile type and do final conversion in storeValue. - auto mt = type->mobileType(); - solAssert(mt, ""); - utils().convertType(*type, *mt); - type = mt; - } - else - { - utils().convertType(*type, *_varDecl.annotation().type); - type = _varDecl.annotation().type; - } - StorageItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true); -} - -void ExpressionCompiler::appendConstStateVariableAccessor(VariableDeclaration const& _varDecl) -{ - solAssert(_varDecl.isConstant(), ""); - acceptAndConvert(*_varDecl.value(), *_varDecl.annotation().type); - - // append return - m_context << dupInstruction(_varDecl.annotation().type->sizeOnStack() + 1); - m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); -} - -void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) -{ - solAssert(!_varDecl.isConstant(), ""); - CompilerContext::LocationSetter locationSetter(m_context, _varDecl); - FunctionType accessorType(_varDecl); - - TypePointers paramTypes = accessorType.parameterTypes(); - m_context.adjustStackOffset(1 + CompilerUtils::sizeOnStack(paramTypes)); - - // retrieve the position of the variable - auto const& location = m_context.storageLocationOfVariable(_varDecl); - m_context << location.first << u256(location.second); - - TypePointer returnType = _varDecl.annotation().type; - - for (size_t i = 0; i < paramTypes.size(); ++i) - { - if (auto mappingType = dynamic_cast(returnType)) - { - solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - - // pop offset - m_context << Instruction::POP; - if (paramTypes[i]->isDynamicallySized()) - { - solAssert( - dynamic_cast(*paramTypes[i]).isByteArray(), - "Expected string or byte array for mapping key type" - ); - - // stack: - - // copy key[i] to top. - utils().copyToStackTop(paramTypes.size() - i + 1, 1); - - m_context.appendInlineAssembly(R"({ - let key_len := mload(key_ptr) - // Temp. use the memory after the array data for the slot - // position - let post_data_ptr := add(key_ptr, add(key_len, 0x20)) - let orig_data := mload(post_data_ptr) - mstore(post_data_ptr, slot_pos) - let hash := keccak256(add(key_ptr, 0x20), add(key_len, 0x20)) - mstore(post_data_ptr, orig_data) - slot_pos := hash - })", {"slot_pos", "key_ptr"}); - - m_context << Instruction::POP; - } - else - { - solAssert(paramTypes[i]->isValueType(), "Expected value type for mapping key"); - - // move storage offset to memory. - utils().storeInMemory(32); - - // move key to memory. - utils().copyToStackTop(paramTypes.size() - i, 1); - utils().storeInMemory(0); - m_context << u256(64) << u256(0); - m_context << Instruction::KECCAK256; - } - - // push offset - m_context << u256(0); - returnType = mappingType->valueType(); - } - else if (auto arrayType = dynamic_cast(returnType)) - { - // pop offset - m_context << Instruction::POP; - utils().copyToStackTop(paramTypes.size() - i + 1, 1); - ArrayUtils(m_context).accessIndex(*arrayType); - returnType = arrayType->baseType(); - } - else - solAssert(false, "Index access is allowed only for \"mapping\" and \"array\" types."); - } - // remove index arguments. - if (paramTypes.size() == 1) - m_context << Instruction::SWAP2 << Instruction::POP << Instruction::SWAP1; - else if (paramTypes.size() >= 2) - { - m_context << swapInstruction(paramTypes.size()); - m_context << Instruction::POP; - m_context << swapInstruction(paramTypes.size()); - utils().popStackSlots(paramTypes.size() - 1); - } - unsigned retSizeOnStack = 0; - auto returnTypes = accessorType.returnParameterTypes(); - solAssert(returnTypes.size() >= 1, ""); - if (StructType const* structType = dynamic_cast(returnType)) - { - // remove offset - m_context << Instruction::POP; - auto const& names = accessorType.returnParameterNames(); - // struct - for (size_t i = 0; i < names.size(); ++i) - { - if (returnTypes[i]->category() == Type::Category::Mapping) - continue; - if (auto arrayType = dynamic_cast(returnTypes[i])) - if (!arrayType->isByteArray()) - continue; - pair const& offsets = structType->storageOffsetsOfMember(names[i]); - m_context << Instruction::DUP1 << u256(offsets.first) << Instruction::ADD << u256(offsets.second); - TypePointer memberType = structType->memberType(names[i]); - StorageItem(m_context, *memberType).retrieveValue(SourceLocation(), true); - utils().convertType(*memberType, *returnTypes[i]); - utils().moveToStackTop(returnTypes[i]->sizeOnStack()); - retSizeOnStack += returnTypes[i]->sizeOnStack(); - } - // remove slot - m_context << Instruction::POP; - } - else - { - // simple value or array - solAssert(returnTypes.size() == 1, ""); - StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); - utils().convertType(*returnType, *returnTypes.front()); - retSizeOnStack = returnTypes.front()->sizeOnStack(); - } - solAssert(retSizeOnStack == utils().sizeOnStack(returnTypes), ""); - if (retSizeOnStack > 15) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_varDecl.location()) << - errinfo_comment("Stack too deep.") - ); - m_context << dupInstruction(retSizeOnStack + 1); - m_context.appendJump(evmasm::AssemblyItem::JumpType::OutOfFunction); -} - -bool ExpressionCompiler::visit(Conditional const& _condition) -{ - CompilerContext::LocationSetter locationSetter(m_context, _condition); - _condition.condition().accept(*this); - evmasm::AssemblyItem trueTag = m_context.appendConditionalJump(); - acceptAndConvert(_condition.falseExpression(), *_condition.annotation().type); - evmasm::AssemblyItem endTag = m_context.appendJumpToNew(); - m_context << trueTag; - int offset = _condition.annotation().type->sizeOnStack(); - m_context.adjustStackOffset(-offset); - acceptAndConvert(_condition.trueExpression(), *_condition.annotation().type); - m_context << endTag; - return false; -} - -bool ExpressionCompiler::visit(Assignment const& _assignment) -{ - CompilerContext::LocationSetter locationSetter(m_context, _assignment); - Token op = _assignment.assignmentOperator(); - Token binOp = op == Token::Assign ? op : TokenTraits::AssignmentToBinaryOp(op); - Type const& leftType = *_assignment.leftHandSide().annotation().type; - if (leftType.category() == Type::Category::Tuple) - { - solAssert(*_assignment.annotation().type == TupleType(), ""); - solAssert(op == Token::Assign, ""); - } - else - solAssert(*_assignment.annotation().type == leftType, ""); - bool cleanupNeeded = false; - if (op != Token::Assign) - cleanupNeeded = cleanupNeededForOp(leftType.category(), binOp); - _assignment.rightHandSide().accept(*this); - // Perform some conversion already. This will convert storage types to memory and literals - // to their actual type, but will not convert e.g. memory to storage. - TypePointer rightIntermediateType; - if (op != Token::Assign && TokenTraits::isShiftOp(binOp)) - rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType(); - else - rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( - _assignment.leftHandSide().annotation().type - ); - solAssert(rightIntermediateType, ""); - utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded); - - _assignment.leftHandSide().accept(*this); - solAssert(!!m_currentLValue, "LValue not retrieved."); - - if (op == Token::Assign) - m_currentLValue->storeValue(*rightIntermediateType, _assignment.location()); - else // compound assignment - { - solAssert(leftType.isValueType(), "Compound operators only available for value types."); - unsigned lvalueSize = m_currentLValue->sizeOnStack(); - unsigned itemSize = _assignment.annotation().type->sizeOnStack(); - if (lvalueSize > 0) - { - utils().copyToStackTop(lvalueSize + itemSize, itemSize); - utils().copyToStackTop(itemSize + lvalueSize, lvalueSize); - // value lvalue_ref value lvalue_ref - } - m_currentLValue->retrieveValue(_assignment.location(), true); - utils().convertType(leftType, leftType, cleanupNeeded); - - if (TokenTraits::isShiftOp(binOp)) - appendShiftOperatorCode(binOp, leftType, *rightIntermediateType); - else - { - solAssert(leftType == *rightIntermediateType, ""); - appendOrdinaryBinaryOperatorCode(binOp, leftType); - } - if (lvalueSize > 0) - { - if (itemSize + lvalueSize > 16) - BOOST_THROW_EXCEPTION( - CompilerError() << - errinfo_sourceLocation(_assignment.location()) << - errinfo_comment("Stack too deep, try removing local variables.") - ); - // value [lvalue_ref] updated_value - for (unsigned i = 0; i < itemSize; ++i) - m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP; - } - m_currentLValue->storeValue(*_assignment.annotation().type, _assignment.location()); - } - m_currentLValue.reset(); - return false; -} - -bool ExpressionCompiler::visit(TupleExpression const& _tuple) -{ - if (_tuple.isInlineArray()) - { - ArrayType const& arrayType = dynamic_cast(*_tuple.annotation().type); - - solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array."); - utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize())); - m_context << Instruction::DUP1; - - for (auto const& component: _tuple.components()) - { - acceptAndConvert(*component, *arrayType.baseType(), true); - utils().storeInMemoryDynamic(*arrayType.baseType(), true); - } - - m_context << Instruction::POP; - } - else - { - vector> lvalues; - for (auto const& component: _tuple.components()) - if (component) - { - component->accept(*this); - if (_tuple.annotation().lValueRequested) - { - solAssert(!!m_currentLValue, ""); - lvalues.push_back(move(m_currentLValue)); - } - } - else if (_tuple.annotation().lValueRequested) - lvalues.push_back(unique_ptr()); - if (_tuple.annotation().lValueRequested) - { - if (_tuple.components().size() == 1) - m_currentLValue = move(lvalues[0]); - else - m_currentLValue = make_unique(m_context, move(lvalues)); - } - } - return false; -} - -bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) -{ - CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); - if (_unaryOperation.annotation().type->category() == Type::Category::RationalNumber) - { - m_context << _unaryOperation.annotation().type->literalValue(nullptr); - return false; - } - - _unaryOperation.subExpression().accept(*this); - - switch (_unaryOperation.getOperator()) - { - case Token::Not: // ! - m_context << Instruction::ISZERO; - break; - case Token::BitNot: // ~ - m_context << Instruction::NOT; - break; - case Token::Delete: // delete - solAssert(!!m_currentLValue, "LValue not retrieved."); - m_currentLValue->setToZero(_unaryOperation.location()); - m_currentLValue.reset(); - break; - case Token::Inc: // ++ (pre- or postfix) - case Token::Dec: // -- (pre- or postfix) - solAssert(!!m_currentLValue, "LValue not retrieved."); - solUnimplementedAssert( - _unaryOperation.annotation().type->category() != Type::Category::FixedPoint, - "Not yet implemented - FixedPointType." - ); - m_currentLValue->retrieveValue(_unaryOperation.location()); - if (!_unaryOperation.isPrefixOperation()) - { - // store value for later - solUnimplementedAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); - m_context << Instruction::DUP1; - if (m_currentLValue->sizeOnStack() > 0) - for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); - } - m_context << u256(1); - if (_unaryOperation.getOperator() == Token::Inc) - m_context << Instruction::ADD; - else - m_context << Instruction::SWAP1 << Instruction::SUB; - // Stack for prefix: [ref...] (*ref)+-1 - // Stack for postfix: *ref [ref...] (*ref)+-1 - for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); - m_currentLValue->storeValue( - *_unaryOperation.annotation().type, _unaryOperation.location(), - !_unaryOperation.isPrefixOperation()); - m_currentLValue.reset(); - break; - case Token::Add: // + - // unary add, so basically no-op - break; - case Token::Sub: // - - m_context << u256(0) << Instruction::SUB; - break; - default: - solAssert(false, "Invalid unary operator: " + string(TokenTraits::toString(_unaryOperation.getOperator()))); - } - return false; -} - -bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) -{ - CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation); - Expression const& leftExpression = _binaryOperation.leftExpression(); - Expression const& rightExpression = _binaryOperation.rightExpression(); - solAssert(!!_binaryOperation.annotation().commonType, ""); - TypePointer const& commonType = _binaryOperation.annotation().commonType; - Token const c_op = _binaryOperation.getOperator(); - - if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting - appendAndOrOperatorCode(_binaryOperation); - else if (commonType->category() == Type::Category::RationalNumber) - m_context << commonType->literalValue(nullptr); - else - { - bool cleanupNeeded = cleanupNeededForOp(commonType->category(), c_op); - - TypePointer leftTargetType = commonType; - TypePointer rightTargetType = TokenTraits::isShiftOp(c_op) ? rightExpression.annotation().type->mobileType() : commonType; - solAssert(rightTargetType, ""); - - // for commutative operators, push the literal as late as possible to allow improved optimization - auto isLiteral = [](Expression const& _e) - { - return dynamic_cast(&_e) || _e.annotation().type->category() == Type::Category::RationalNumber; - }; - bool swap = m_optimiseOrderLiterals && TokenTraits::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); - if (swap) - { - acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); - acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); - } - else - { - acceptAndConvert(rightExpression, *rightTargetType, cleanupNeeded); - acceptAndConvert(leftExpression, *leftTargetType, cleanupNeeded); - } - if (TokenTraits::isShiftOp(c_op)) - // shift only cares about the signedness of both sides - appendShiftOperatorCode(c_op, *leftTargetType, *rightTargetType); - else if (TokenTraits::isCompareOp(c_op)) - appendCompareOperatorCode(c_op, *commonType); - else - appendOrdinaryBinaryOperatorCode(c_op, *commonType); - } - - // do not visit the child nodes, we already did that explicitly - return false; -} - -bool ExpressionCompiler::visit(FunctionCall const& _functionCall) -{ - CompilerContext::LocationSetter locationSetter(m_context, _functionCall); - if (_functionCall.annotation().kind == FunctionCallKind::TypeConversion) - { - solAssert(_functionCall.arguments().size() == 1, ""); - solAssert(_functionCall.names().empty(), ""); - auto const& expression = *_functionCall.arguments().front(); - auto const& targetType = *_functionCall.annotation().type; - if (auto const* typeType = dynamic_cast(expression.annotation().type)) - if (auto const* addressType = dynamic_cast(&targetType)) - { - auto const* contractType = dynamic_cast(typeType->actualType()); - solAssert( - contractType && - contractType->contractDefinition().isLibrary() && - addressType->stateMutability() == StateMutability::NonPayable, - "" - ); - m_context.appendLibraryAddress(contractType->contractDefinition().fullyQualifiedName()); - return false; - } - acceptAndConvert(expression, targetType); - return false; - } - - FunctionTypePointer functionType; - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) - { - auto const& type = dynamic_cast(*_functionCall.expression().annotation().type); - auto const& structType = dynamic_cast(*type.actualType()); - functionType = structType.constructorType(); - } - else - functionType = dynamic_cast(_functionCall.expression().annotation().type); - - TypePointers parameterTypes = functionType->parameterTypes(); - vector> const& callArguments = _functionCall.arguments(); - vector> const& callArgumentNames = _functionCall.names(); - if (!functionType->takesArbitraryParameters()) - solAssert(callArguments.size() == parameterTypes.size(), ""); - - vector> arguments; - if (callArgumentNames.empty()) - // normal arguments - arguments = callArguments; - else - // named arguments - for (auto const& parameterName: functionType->parameterNames()) - { - bool found = false; - for (size_t j = 0; j < callArgumentNames.size() && !found; j++) - if ((found = (parameterName == *callArgumentNames[j]))) - // we found the actual parameter position - arguments.push_back(callArguments[j]); - solAssert(found, ""); - } - - if (_functionCall.annotation().kind == FunctionCallKind::StructConstructorCall) - { - TypeType const& type = dynamic_cast(*_functionCall.expression().annotation().type); - auto const& structType = dynamic_cast(*type.actualType()); - - utils().allocateMemory(max(u256(32u), structType.memoryDataSize())); - m_context << Instruction::DUP1; - - for (unsigned i = 0; i < arguments.size(); ++i) - { - acceptAndConvert(*arguments[i], *functionType->parameterTypes()[i]); - utils().storeInMemoryDynamic(*functionType->parameterTypes()[i]); - } - m_context << Instruction::POP; - } - else - { - FunctionType const& function = *functionType; - if (function.bound()) - // Only delegatecall and internal functions can be bound, this might be lifted later. - solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, ""); - switch (function.kind()) - { - case FunctionType::Kind::AddressIsZero: - case FunctionType::Kind::AddressMakeAddrExtern: - case FunctionType::Kind::AddressMakeAddrNone: - case FunctionType::Kind::AddressMakeAddrStd: - case FunctionType::Kind::AddressType: - case FunctionType::Kind::AddressUnpack: - case FunctionType::Kind::DecodeFunctionParams: - case FunctionType::Kind::ExtraCurrencyCollectionMethods: - case FunctionType::Kind::LogTVM: - case FunctionType::Kind::MappingDelMin: - case FunctionType::Kind::MappingEmpty: - case FunctionType::Kind::MappingExists: - case FunctionType::Kind::MappingFetch: - case FunctionType::Kind::MappingGetMaxKey: - case FunctionType::Kind::MappingGetMinKey: - case FunctionType::Kind::MappingGetNextKey: - case FunctionType::Kind::MappingGetPrevKey: - case FunctionType::Kind::MessagePubkey: - case FunctionType::Kind::SetFlag: - case FunctionType::Kind::StringMethod: - case FunctionType::Kind::TVMAccept: - case FunctionType::Kind::TVMBuilderMethods: - case FunctionType::Kind::TVMcdatasize: - case FunctionType::Kind::TVMCellToSlice: - case FunctionType::Kind::TVMChecksign: - case FunctionType::Kind::TVMCommit: - case FunctionType::Kind::TVMConfigParam: - case FunctionType::Kind::TVMDeploy: - case FunctionType::Kind::TVMDestAddr: - case FunctionType::Kind::TVMFunctionId: - case FunctionType::Kind::TVMMaxMin: - case FunctionType::Kind::TVMHash: - case FunctionType::Kind::TVMLoadRef: - case FunctionType::Kind::TVMPubkey: - case FunctionType::Kind::TVMResetStorage: - case FunctionType::Kind::TVMSendMsg: - case FunctionType::Kind::TVMSetcode: - case FunctionType::Kind::TVMSliceDecode: - case FunctionType::Kind::TVMSliceSize: - case FunctionType::Kind::TVMTransfer: - case FunctionType::Kind::TVMTransLT: - { - solAssert(false, ""); - break; - } - case FunctionType::Kind::Declaration: - solAssert(false, "Attempted to generate code for calling a function definition."); - break; - case FunctionType::Kind::Internal: - { - // Calling convention: Caller pushes return address and arguments - // Callee removes them and pushes return values - - evmasm::AssemblyItem returnLabel = m_context.pushNewTag(); - for (unsigned i = 0; i < arguments.size(); ++i) - acceptAndConvert(*arguments[i], *function.parameterTypes()[i]); - - { - bool shortcutTaken = false; - if (auto identifier = dynamic_cast(&_functionCall.expression())) - { - solAssert(!function.bound(), ""); - if (auto functionDef = dynamic_cast(identifier->annotation().referencedDeclaration)) - { - // Do not directly visit the identifier, because this way, we can avoid - // the runtime entry label to be created at the creation time context. - CompilerContext::LocationSetter locationSetter2(m_context, *identifier); - utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef), false); - shortcutTaken = true; - } - } - - if (!shortcutTaken) - _functionCall.expression().accept(*this); - } - - unsigned parameterSize = CompilerUtils::sizeOnStack(function.parameterTypes()); - if (function.bound()) - { - // stack: arg2, ..., argn, label, arg1 - unsigned depth = parameterSize + 1; - utils().moveIntoStack(depth, function.selfType()->sizeOnStack()); - parameterSize += function.selfType()->sizeOnStack(); - } - - if (m_context.runtimeContext()) - // We have a runtime context, so we need the creation part. - utils().rightShiftNumberOnStack(32); - else - // Extract the runtime part. - m_context << ((u256(1) << 32) - 1) << Instruction::AND; - - m_context.appendJump(evmasm::AssemblyItem::JumpType::IntoFunction); - m_context << returnLabel; - - unsigned returnParametersSize = CompilerUtils::sizeOnStack(function.returnParameterTypes()); - // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(returnParametersSize - parameterSize - 1); - break; - } - case FunctionType::Kind::BareCall: - case FunctionType::Kind::BareDelegateCall: - case FunctionType::Kind::BareStaticCall: - solAssert(!_functionCall.annotation().tryCall, ""); - [[fallthrough]]; - case FunctionType::Kind::External: - case FunctionType::Kind::DelegateCall: - _functionCall.expression().accept(*this); - appendExternalFunctionCall(function, arguments, _functionCall.annotation().tryCall); - break; - case FunctionType::Kind::BareCallCode: - solAssert(false, "Callcode has been removed."); - case FunctionType::Kind::Creation: - { - _functionCall.expression().accept(*this); - // Stack: [salt], [value] - - solAssert(!function.gasSet(), "Gas limit set for contract creation."); - solAssert(function.returnParameterTypes().size() == 1, ""); - TypePointers argumentTypes; - for (auto const& arg: arguments) - { - arg->accept(*this); - argumentTypes.push_back(arg->annotation().type); - } - ContractDefinition const* contract = - &dynamic_cast(*function.returnParameterTypes().front()).contractDefinition(); - utils().fetchFreeMemoryPointer(); - utils().copyContractCodeToMemory(*contract, true); - utils().abiEncode(argumentTypes, function.parameterTypes()); - // now on stack: [salt], [value], memory_end_ptr - // need: [salt], size, offset, value - - if (function.saltSet()) - { - m_context << dupInstruction(2 + (function.valueSet() ? 1 : 0)); - m_context << Instruction::SWAP1; - } - - // now: [salt], [value], [salt], memory_end_ptr - utils().toSizeAfterFreeMemoryPointer(); - - // now: [salt], [value], [salt], size, offset - if (function.valueSet()) - m_context << dupInstruction(3 + (function.saltSet() ? 1 : 0)); - else - m_context << u256(0); - - // now: [salt], [value], [salt], size, offset, value - if (function.saltSet()) - m_context << Instruction::CREATE2; - else - m_context << Instruction::CREATE; - - // now: [salt], [value], address - - if (function.valueSet()) - m_context << swapInstruction(1) << Instruction::POP; - if (function.saltSet()) - m_context << swapInstruction(1) << Instruction::POP; - - // Check if zero (reverted) - m_context << Instruction::DUP1 << Instruction::ISZERO; - if (_functionCall.annotation().tryCall) - { - // If this is a try call, return "
1" in the success case and - // "0" in the error case. - AssemblyItem errorCase = m_context.appendConditionalJump(); - m_context << u256(1); - m_context << errorCase; - } - else - m_context.appendConditionalRevert(true); - break; - } - case FunctionType::Kind::SetGas: - { - // stack layout: contract_address function_id [gas] [value] - _functionCall.expression().accept(*this); - - acceptAndConvert(*arguments.front(), *TypeProvider::uint256(), true); - // Note that function is not the original function, but the ".gas" function. - // Its values of gasSet and valueSet is equal to the original function's though. - unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); - if (stackDepth > 0) - m_context << swapInstruction(stackDepth); - if (function.gasSet()) - m_context << Instruction::POP; - break; - } - case FunctionType::Kind::SetValue: - // stack layout: contract_address function_id [gas] [value] - _functionCall.expression().accept(*this); - // Note that function is not the original function, but the ".value" function. - // Its values of gasSet and valueSet is equal to the original function's though. - if (function.valueSet()) - m_context << Instruction::POP; - arguments.front()->accept(*this); - break; - case FunctionType::Kind::Send: - case FunctionType::Kind::Transfer: - _functionCall.expression().accept(*this); - // Provide the gas stipend manually at first because we may send zero ether. - // Will be zeroed if we send more than zero ether. - m_context << u256(evmasm::GasCosts::callStipend); - acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); - // gas <- gas * !value - m_context << Instruction::SWAP1 << Instruction::DUP2; - m_context << Instruction::ISZERO << Instruction::MUL << Instruction::SWAP1; - appendExternalFunctionCall( - FunctionType( - TypePointers{}, - TypePointers{}, - strings(), - strings(), - FunctionType::Kind::BareCall, - false, - StateMutability::NonPayable, - nullptr, - true, - true - ), - {}, - false - ); - if (function.kind() == FunctionType::Kind::Transfer) - { - // Check if zero (out of stack or not enough balance). - m_context << Instruction::ISZERO; - // Revert message bubbles up. - m_context.appendConditionalRevert(true); - } - break; - case FunctionType::Kind::Selfdestruct: - acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), true); - m_context << Instruction::SELFDESTRUCT; - break; - case FunctionType::Kind::Revert: - { - if (arguments.empty()) - m_context.appendRevert(); - else - { - // function-sel(Error(string)) + encoding - solAssert(arguments.size() == 1, ""); - solAssert(function.parameterTypes().size() == 1, ""); - if (m_context.revertStrings() == RevertStrings::Strip) - { - if (!arguments.front()->annotation().isPure) - { - arguments.front()->accept(*this); - utils().popStackElement(*arguments.front()->annotation().type); - } - m_context.appendRevert(); - } - else - { - arguments.front()->accept(*this); - utils().revertWithStringData(*arguments.front()->annotation().type); - } - } - break; - } - case FunctionType::Kind::KECCAK256: - { - solAssert(arguments.size() == 1, ""); - solAssert(!function.padArguments(), ""); - TypePointer const& argType = arguments.front()->annotation().type; - solAssert(argType, ""); - arguments.front()->accept(*this); - // Optimization: If type is bytes or string, then do not encode, - // but directly compute keccak256 on memory. - if (*argType == *TypeProvider::bytesMemory() || *argType == *TypeProvider::stringMemory()) - { - ArrayUtils(m_context).retrieveLength(*TypeProvider::bytesMemory()); - m_context << Instruction::SWAP1 << u256(0x20) << Instruction::ADD; - } - else - { - utils().fetchFreeMemoryPointer(); - utils().packedEncode({argType}, TypePointers()); - utils().toSizeAfterFreeMemoryPointer(); - } - m_context << Instruction::KECCAK256; - break; - } - case FunctionType::Kind::Log0: - case FunctionType::Kind::Log1: - case FunctionType::Kind::Log2: - case FunctionType::Kind::Log3: - case FunctionType::Kind::Log4: - { - unsigned logNumber = int(function.kind()) - int(FunctionType::Kind::Log0); - for (unsigned arg = logNumber; arg > 0; --arg) - acceptAndConvert(*arguments[arg], *function.parameterTypes()[arg], true); - arguments.front()->accept(*this); - utils().fetchFreeMemoryPointer(); - solAssert(function.parameterTypes().front()->isValueType(), ""); - utils().packedEncode( - {arguments.front()->annotation().type}, - {function.parameterTypes().front()} - ); - utils().toSizeAfterFreeMemoryPointer(); - m_context << logInstruction(logNumber); - break; - } - case FunctionType::Kind::Event: - { - _functionCall.expression().accept(*this); - auto const& event = dynamic_cast(function.declaration()); - unsigned numIndexed = 0; - TypePointers paramTypes = function.parameterTypes(); - // All indexed arguments go to the stack - for (unsigned arg = arguments.size(); arg > 0; --arg) - if (event.parameters()[arg - 1]->isIndexed()) - { - ++numIndexed; - arguments[arg - 1]->accept(*this); - if (auto const& referenceType = dynamic_cast(paramTypes[arg - 1])) - { - utils().fetchFreeMemoryPointer(); - utils().packedEncode( - {arguments[arg - 1]->annotation().type}, - {referenceType} - ); - utils().toSizeAfterFreeMemoryPointer(); - m_context << Instruction::KECCAK256; - } - else - { - solAssert(paramTypes[arg - 1]->isValueType(), ""); - utils().convertType( - *arguments[arg - 1]->annotation().type, - *paramTypes[arg - 1], - true - ); - } - } - if (!event.isAnonymous()) - { - m_context << u256(h256::Arith(keccak256(function.externalSignature()))); - ++numIndexed; - } - solAssert(numIndexed <= 4, "Too many indexed arguments."); - // Copy all non-indexed arguments to memory (data) - // Memory position is only a hack and should be removed once we have free memory pointer. - TypePointers nonIndexedArgTypes; - TypePointers nonIndexedParamTypes; - for (unsigned arg = 0; arg < arguments.size(); ++arg) - if (!event.parameters()[arg]->isIndexed()) - { - arguments[arg]->accept(*this); - nonIndexedArgTypes.push_back(arguments[arg]->annotation().type); - nonIndexedParamTypes.push_back(paramTypes[arg]); - } - utils().fetchFreeMemoryPointer(); - utils().abiEncode(nonIndexedArgTypes, nonIndexedParamTypes); - // need: topic1 ... topicn memsize memstart - utils().toSizeAfterFreeMemoryPointer(); - m_context << logInstruction(numIndexed); - break; - } - case FunctionType::Kind::BlockHash: - { - acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true); - m_context << Instruction::BLOCKHASH; - break; - } - case FunctionType::Kind::AddMod: - case FunctionType::Kind::MulMod: - { - acceptAndConvert(*arguments[2], *TypeProvider::uint256()); - m_context << Instruction::DUP1 << Instruction::ISZERO; - m_context.appendConditionalInvalid(); - for (unsigned i = 1; i < 3; i ++) - acceptAndConvert(*arguments[2 - i], *TypeProvider::uint256()); - if (function.kind() == FunctionType::Kind::AddMod) - m_context << Instruction::ADDMOD; - else - m_context << Instruction::MULMOD; - break; - } - case FunctionType::Kind::ECRecover: - case FunctionType::Kind::SHA256: - case FunctionType::Kind::RIPEMD160: - { - _functionCall.expression().accept(*this); - static map const contractAddresses{ - {FunctionType::Kind::ECRecover, 1}, - {FunctionType::Kind::SHA256, 2}, - {FunctionType::Kind::RIPEMD160, 3} - }; - m_context << contractAddresses.at(function.kind()); - for (unsigned i = function.sizeOnStack(); i > 0; --i) - m_context << swapInstruction(i); - solAssert(!_functionCall.annotation().tryCall, ""); - appendExternalFunctionCall(function, arguments, false); - break; - } - case FunctionType::Kind::ByteArrayPush: - case FunctionType::Kind::ArrayPush: - { - _functionCall.expression().accept(*this); - - if (function.parameterTypes().size() == 0) - { - auto paramType = function.returnParameterTypes().at(0); - solAssert(paramType, ""); - - ArrayType const* arrayType = - function.kind() == FunctionType::Kind::ArrayPush ? - TypeProvider::array(DataLocation::Storage, paramType) : - TypeProvider::bytesStorage(); - - // stack: ArrayReference - m_context << u256(1) << Instruction::DUP2; - ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); - // stack: ArrayReference 1 newLength - m_context << Instruction::SUB; - // stack: ArrayReference (newLength-1) - ArrayUtils(m_context).accessIndex(*arrayType, false); - - if (arrayType->isByteArray()) - setLValue(_functionCall); - else - setLValueToStorageItem(_functionCall); - } - else - { - solAssert(function.parameterTypes().size() == 1, ""); - solAssert(!!function.parameterTypes()[0], ""); - TypePointer paramType = function.parameterTypes()[0]; - ArrayType const* arrayType = - function.kind() == FunctionType::Kind::ArrayPush ? - TypeProvider::array(DataLocation::Storage, paramType) : - TypeProvider::bytesStorage(); - - // stack: ArrayReference - arguments[0]->accept(*this); - TypePointer const& argType = arguments[0]->annotation().type; - // stack: ArrayReference argValue - utils().moveToStackTop(argType->sizeOnStack(), 1); - // stack: argValue ArrayReference - m_context << Instruction::DUP1; - ArrayUtils(m_context).incrementDynamicArraySize(*arrayType); - // stack: argValue ArrayReference newLength - m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; - // stack: argValue ArrayReference (newLength-1) - ArrayUtils(m_context).accessIndex(*arrayType, false); - // stack: argValue storageSlot slotOffset - utils().moveToStackTop(2, argType->sizeOnStack()); - // stack: storageSlot slotOffset argValue - TypePointer type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType()); - solAssert(type, ""); - utils().convertType(*argType, *type); - utils().moveToStackTop(1 + type->sizeOnStack()); - utils().moveToStackTop(1 + type->sizeOnStack()); - // stack: argValue storageSlot slotOffset - if (function.kind() == FunctionType::Kind::ArrayPush) - StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); - else - StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); - } - break; - } - case FunctionType::Kind::ArrayPop: - { - _functionCall.expression().accept(*this); - solAssert(function.parameterTypes().empty(), ""); - - ArrayType const& arrayType = dynamic_cast( - *dynamic_cast(_functionCall.expression()).expression().annotation().type - ); -// solAssert(arrayType.dataStoredIn(DataLocation::Storage), ""); - - ArrayUtils(m_context).popStorageArrayElement(arrayType); - break; - } - case FunctionType::Kind::ObjectCreation: - { - ArrayType const& arrayType = dynamic_cast(*_functionCall.annotation().type); - _functionCall.expression().accept(*this); - solAssert(arguments.size() == 1, ""); - - // Fetch requested length. - acceptAndConvert(*arguments[0], *TypeProvider::uint256()); - - // Stack: requested_length - utils().fetchFreeMemoryPointer(); - - // Stack: requested_length memptr - m_context << Instruction::SWAP1; - // Stack: memptr requested_length - // store length - m_context << Instruction::DUP1 << Instruction::DUP3 << Instruction::MSTORE; - // Stack: memptr requested_length - // update free memory pointer - m_context << Instruction::DUP1; - // Stack: memptr requested_length requested_length - if (arrayType.isByteArray()) - // Round up to multiple of 32 - m_context << u256(31) << Instruction::ADD << u256(31) << Instruction::NOT << Instruction::AND; - else - m_context << arrayType.baseType()->memoryHeadSize() << Instruction::MUL; - // stacK: memptr requested_length data_size - m_context << u256(32) << Instruction::ADD; - m_context << Instruction::DUP3 << Instruction::ADD; - utils().storeFreeMemoryPointer(); - // Stack: memptr requested_length - - // Check if length is zero - m_context << Instruction::DUP1 << Instruction::ISZERO; - auto skipInit = m_context.appendConditionalJump(); - // Always initialize because the free memory pointer might point at - // a dirty memory area. - m_context << Instruction::DUP2 << u256(32) << Instruction::ADD; - utils().zeroInitialiseMemoryArray(arrayType); - m_context << skipInit; - m_context << Instruction::POP; - break; - } - case FunctionType::Kind::Assert: - case FunctionType::Kind::Require: - { - acceptAndConvert(*arguments.front(), *function.parameterTypes().front(), false); - - bool haveReasonString = arguments.size() > 1 && m_context.revertStrings() != RevertStrings::Strip; - - bool secArgIsNum = (arguments.size() > 1) ? - (arguments.at(1)->annotation().type->category() == Type::Category::RationalNumber) : - false; - if ((arguments.size() > 1) && !secArgIsNum) - { - // Users probably expect the second argument to be evaluated - // even if the condition is false, as would be the case for an actual - // function call. - solAssert(arguments.size() == 2, ""); - solAssert(function.kind() == FunctionType::Kind::Require, ""); - if (m_context.revertStrings() == RevertStrings::Strip) - { - if (!arguments.at(1)->annotation().isPure) - { - arguments.at(1)->accept(*this); - utils().popStackElement(*arguments.at(1)->annotation().type); - } - } - else - { - arguments.at(1)->accept(*this); - utils().moveIntoStack(1, arguments.at(1)->annotation().type->sizeOnStack()); - } - } - // Stack: - // jump if condition was met - m_context << Instruction::ISZERO << Instruction::ISZERO; - auto success = m_context.appendConditionalJump(); - if (function.kind() == FunctionType::Kind::Assert) - // condition was not met, flag an error - m_context.appendInvalid(); - else if (haveReasonString && !secArgIsNum) - { - utils().revertWithStringData(*arguments.at(1)->annotation().type); - // Here, the argument is consumed, but in the other branch, it is still there. - m_context.adjustStackOffset(arguments.at(1)->annotation().type->sizeOnStack()); - } - else - m_context.appendRevert(); - // the success branch - m_context << success; - if (haveReasonString && !secArgIsNum) - utils().popStackElement(*arguments.at(1)->annotation().type); - break; - } - case FunctionType::Kind::ABIEncode: - case FunctionType::Kind::ABIEncodePacked: - case FunctionType::Kind::ABIEncodeWithSelector: - case FunctionType::Kind::ABIEncodeWithSignature: - { - bool const isPacked = function.kind() == FunctionType::Kind::ABIEncodePacked; - bool const hasSelectorOrSignature = - function.kind() == FunctionType::Kind::ABIEncodeWithSelector || - function.kind() == FunctionType::Kind::ABIEncodeWithSignature; - - TypePointers argumentTypes; - TypePointers targetTypes; - for (unsigned i = 0; i < arguments.size(); ++i) - { - arguments[i]->accept(*this); - // Do not keep the selector as part of the ABI encoded args - if (!hasSelectorOrSignature || i > 0) - argumentTypes.push_back(arguments[i]->annotation().type); - } - utils().fetchFreeMemoryPointer(); - // stack now: [] .. - - // adjust by 32(+4) bytes to accommodate the length(+selector) - m_context << u256(32 + (hasSelectorOrSignature ? 4 : 0)) << Instruction::ADD; - // stack now: [] .. - - if (isPacked) - { - solAssert(!function.padArguments(), ""); - utils().packedEncode(argumentTypes, TypePointers()); - } - else - { - solAssert(function.padArguments(), ""); - utils().abiEncode(argumentTypes, TypePointers()); - } - utils().fetchFreeMemoryPointer(); - // stack: [] - - // size is end minus start minus length slot - m_context.appendInlineAssembly(R"({ - mstore(mem_ptr, sub(sub(mem_end, mem_ptr), 0x20)) - })", {"mem_end", "mem_ptr"}); - m_context << Instruction::SWAP1; - utils().storeFreeMemoryPointer(); - // stack: [] - - if (hasSelectorOrSignature) - { - // stack: - solAssert(arguments.size() >= 1, ""); - TypePointer const& selectorType = arguments[0]->annotation().type; - utils().moveIntoStack(selectorType->sizeOnStack()); - TypePointer dataOnStack = selectorType; - // stack: - if (function.kind() == FunctionType::Kind::ABIEncodeWithSignature) - { - // hash the signature - if (auto const* stringType = dynamic_cast(selectorType)) - { - FixedHash<4> hash(keccak256(stringType->value())); - m_context << (u256(FixedHash<4>::Arith(hash)) << (256 - 32)); - dataOnStack = TypeProvider::fixedBytes(4); - } - else - { - utils().fetchFreeMemoryPointer(); - // stack: - utils().packedEncode(TypePointers{selectorType}, TypePointers()); - utils().toSizeAfterFreeMemoryPointer(); - m_context << Instruction::KECCAK256; - // stack: - - dataOnStack = TypeProvider::fixedBytes(32); - } - } - else - { - solAssert(function.kind() == FunctionType::Kind::ABIEncodeWithSelector, ""); - } - - utils().convertType(*dataOnStack, FixedBytesType(4), true); - - // stack: - - // load current memory, mask and combine the selector - string mask = formatNumber((u256(-1) >> 32)); - m_context.appendInlineAssembly(R"({ - let data_start := add(mem_ptr, 0x20) - let data := mload(data_start) - let mask := )" + mask + R"( - mstore(data_start, or(and(data, mask), selector)) - })", {"mem_ptr", "selector"}); - m_context << Instruction::POP; - } - - // stack now: - break; - } - case FunctionType::Kind::ABIDecode: - { - arguments.front()->accept(*this); - TypePointer firstArgType = arguments.front()->annotation().type; - TypePointers targetTypes; - if (TupleType const* targetTupleType = dynamic_cast(_functionCall.annotation().type)) - targetTypes = targetTupleType->components(); - else - targetTypes = TypePointers{_functionCall.annotation().type}; - if ( - auto referenceType = dynamic_cast(firstArgType); - referenceType && referenceType->dataStoredIn(DataLocation::CallData) - ) - { - solAssert(referenceType->isImplicitlyConvertibleTo(*TypeProvider::bytesCalldata()), ""); - utils().convertType(*referenceType, *TypeProvider::bytesCalldata()); - utils().abiDecode(targetTypes, false); - } - else - { - utils().convertType(*firstArgType, *TypeProvider::bytesMemory()); - m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; - m_context << Instruction::SWAP1 << Instruction::MLOAD; - // stack now: - - utils().abiDecode(targetTypes, true); - } - break; - } - case FunctionType::Kind::GasLeft: - m_context << Instruction::GAS; - break; - case FunctionType::Kind::MetaType: - // No code to generate. - break; - } - } - return false; -} - -bool ExpressionCompiler::visit(FunctionCallOptions const& _functionCallOptions) -{ - _functionCallOptions.expression().accept(*this); - - // Desired Stack: [salt], [gas], [value] - enum Option { Salt, Gas, Value }; - - vector