From 4abc8ab5a9c1d2505cf781796f65de457028514d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Oct 2016 00:02:44 +0100 Subject: [PATCH 1/9] Add usingIdentity option to CompilerUtils::memoryCopy --- libsolidity/codegen/CompilerUtils.cpp | 9 ++++++++- libsolidity/codegen/CompilerUtils.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 7c159ff7d..461803fa8 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -298,9 +298,16 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) m_context << Instruction::SWAP1 << Instruction::POP; } -void CompilerUtils::memoryCopy() +void CompilerUtils::memoryCopy(bool _useIdentityPrecompile) { // Stack here: size target source + + if (!_useIdentityPrecompile) + { + // FIXME + return; + } + // stack for call: outsize target size source value contract gas //@TODO do not use ::CALL if less than 32 bytes? m_context << Instruction::DUP3 << Instruction::SWAP1; diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 0a5d8e1c5..fdcd1dfc8 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -112,7 +112,7 @@ public: /// Uses a CALL to the identity contract to perform a memory-to-memory copy. /// Stack pre: /// Stack post: - void memoryCopy(); + void memoryCopy(bool _useIdentityPrecompile = true); /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) /// external function type
into two stack slots: From 1bf412d9fd9a4d55802ebe4eb006cf835d371d90 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 30 Nov 2016 22:44:33 +0000 Subject: [PATCH 2/9] Implement CompilerUtils::memoryCopy using inline assembly --- libsolidity/codegen/CompilerUtils.cpp | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 461803fa8..634474a12 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -304,7 +304,32 @@ void CompilerUtils::memoryCopy(bool _useIdentityPrecompile) if (!_useIdentityPrecompile) { - // FIXME + m_context.appendInlineAssembly(R"( + { + // expects three locals: src, dst, len + + // copy 32 bytes at once + start32: + jumpi(end32, lt(len, 32)) + mstore(dst, mload(src)) + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + jump(start32) + end32: + + // 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; + m_context << Instruction::POP; + m_context << Instruction::POP; return; } From bf5dac1fb255a54825952219c852869b8c2663e9 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 10:23:41 +0000 Subject: [PATCH 3/9] Turn off the identity precompile for testing --- libsolidity/codegen/CompilerUtils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index fdcd1dfc8..52b5b0d62 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -112,7 +112,7 @@ public: /// Uses a CALL to the identity contract to perform a memory-to-memory copy. /// Stack pre: /// Stack post: - void memoryCopy(bool _useIdentityPrecompile = true); + void memoryCopy(bool _useIdentityPrecompile = false); /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) /// external function type
into two stack slots: From b93589b3b62ca12fe5446d7caed0c0964158cd6e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Fri, 2 Dec 2016 13:14:18 +0000 Subject: [PATCH 4/9] Implement identity call in inline assembly --- libsolidity/codegen/CompilerUtils.cpp | 35 ++++++++++++++++----------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 634474a12..8b94bd0de 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -300,6 +300,8 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) void CompilerUtils::memoryCopy(bool _useIdentityPrecompile) { + //@TODO do not use ::CALL if less than 32 bytes? + // Stack here: size target source if (!_useIdentityPrecompile) @@ -332,20 +334,25 @@ void CompilerUtils::memoryCopy(bool _useIdentityPrecompile) m_context << Instruction::POP; return; } - - // stack for call: outsize target size source value contract gas - //@TODO do not use ::CALL if less than 32 bytes? - m_context << Instruction::DUP3 << Instruction::SWAP1; - m_context << u256(0) << u256(identityContractAddress); - // compute gas costs - m_context << u256(32) << Instruction::DUP5 << u256(31) << Instruction::ADD; - static unsigned c_identityGas = 15; - static unsigned c_identityWordGas = 3; - m_context << Instruction::DIV << u256(c_identityWordGas) << Instruction::MUL; - m_context << u256(c_identityGas) << Instruction::ADD; - m_context << Instruction::CALL; - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(m_context.errorTag()); + else + { + m_context.appendInlineAssembly(R"( + { + let words := div(add(len, 31), 32) + let cost := add(15, mul(3, words)) + jump(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len))) + } + )", + { "len", "dst", "src" }, + map { + { "$identityContractAddress", toString(identityContractAddress) } + } + ); + m_context << Instruction::POP; + m_context << Instruction::POP; + m_context << Instruction::POP; + return; + } } void CompilerUtils::splitExternalFunctionType(bool _leftAligned) From 4a11200a272278383a003262361d865689d1c87b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 3 Dec 2016 00:21:29 +0000 Subject: [PATCH 5/9] Better assert message for appendInlineAssembnly --- libsolidity/codegen/CompilerContext.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 2de5a3ec2..3a70e5235 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -217,7 +217,7 @@ void CompilerContext::appendInlineAssembly( return true; }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), ""); + solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( From efd7b4bfbb5fef99bcf4ee7c98c2ade4c1467f87 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sat, 3 Dec 2016 00:21:46 +0000 Subject: [PATCH 6/9] Reset stack height after inline assembly block --- libsolidity/codegen/CompilerContext.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3a70e5235..597505045 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -218,6 +218,7 @@ void CompilerContext::appendInlineAssembly( }; solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); + setStackOffset(startStackHeight); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( From 4184525d4ad7aff3acb2d521c3cdc21054e36eff Mon Sep 17 00:00:00 2001 From: chriseth Date: Sun, 11 Dec 2016 17:50:59 +0100 Subject: [PATCH 7/9] Fix inline assembly. --- libsolidity/codegen/CompilerContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 597505045..c14ab845d 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -202,6 +202,8 @@ void CompilerContext::appendInlineAssembly( return false; unsigned stackDepth = _localVariables.end() - it; int stackDiff = _assembly.deposit() - startStackHeight + stackDepth; + if (_context == assembly::CodeGenerator::IdentifierContext::LValue) + stackDiff -= 1; if (stackDiff < 1 || stackDiff > 16) BOOST_THROW_EXCEPTION( CompilerError() << @@ -218,7 +220,6 @@ void CompilerContext::appendInlineAssembly( }; solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), "Failed to assemble inline assembly block."); - setStackOffset(startStackHeight); } FunctionDefinition const& CompilerContext::resolveVirtualFunction( From bfa4f451160bff14d79bf6e25d969b1f586a8b00 Mon Sep 17 00:00:00 2001 From: chriseth Date: Sun, 11 Dec 2016 17:51:17 +0100 Subject: [PATCH 8/9] Split memcopy into three functions. --- libsolidity/codegen/ArrayUtils.cpp | 9 ++- libsolidity/codegen/CompilerUtils.cpp | 112 ++++++++++++++------------ libsolidity/codegen/CompilerUtils.h | 10 ++- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index 2c982982e..352c7177a 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -335,9 +335,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord if (baseSize > 1) m_context << u256(baseSize) << Instruction::MUL; // stack: - //@TODO do not use ::CALL if less than 32 bytes? m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; - utils.memoryCopy(); + // 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 (((baseSize % 32) == 0) || _padToWordBoundaries) + utils.memoryCopy32(); + else + utils.memoryCopy(); m_context << Instruction::SWAP1 << Instruction::POP; // stack: diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 8b94bd0de..da2e78e83 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -298,61 +298,73 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type) m_context << Instruction::SWAP1 << Instruction::POP; } -void CompilerUtils::memoryCopy(bool _useIdentityPrecompile) +void CompilerUtils::memoryCopyPrecompile() { - //@TODO do not use ::CALL if less than 32 bytes? - // Stack here: size target source - if (!_useIdentityPrecompile) - { - m_context.appendInlineAssembly(R"( - { - // expects three locals: src, dst, len + m_context.appendInlineAssembly(R"( + { + let words := div(add(len, 31), 32) + let cost := add(15, mul(3, words)) + jumpi(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len))) + } + )", + { "len", "dst", "src" }, + map { + { "$identityContractAddress", toString(identityContractAddress) } + } + ); + m_context << Instruction::POP << Instruction::POP << Instruction::POP; +} - // copy 32 bytes at once - start32: - jumpi(end32, lt(len, 32)) - mstore(dst, mload(src)) - dst := add(dst, 32) - src := add(src, 32) - len := sub(len, 32) - jump(start32) - end32: +void CompilerUtils::memoryCopy32() +{ + // Stack here: size target source - // 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; - m_context << Instruction::POP; - m_context << Instruction::POP; - return; - } - else - { - m_context.appendInlineAssembly(R"( - { - let words := div(add(len, 31), 32) - let cost := add(15, mul(3, words)) - jump(invalidJumpLabel, iszero(call(cost, $identityContractAddress, 0, src, len, dst, len))) - } - )", - { "len", "dst", "src" }, - map { - { "$identityContractAddress", toString(identityContractAddress) } - } - ); - m_context << Instruction::POP; - m_context << Instruction::POP; - m_context << Instruction::POP; - return; - } + m_context.appendInlineAssembly(R"( + { + jumpi(end, eq(len, 0)) + start: + mstore(dst, mload(src)) + jumpi(end, iszero(gt(len, 32))) + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + jump(start) + end: + } + )", + { "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 + start32: + jumpi(end32, lt(len, 32)) + mstore(dst, mload(src)) + dst := add(dst, 32) + src := add(src, 32) + len := sub(len, 32) + jump(start32) + end32: + + // 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) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 52b5b0d62..ad53efea4 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -112,7 +112,15 @@ public: /// Uses a CALL to the identity contract to perform a memory-to-memory copy. /// Stack pre: /// Stack post: - void memoryCopy(bool _useIdentityPrecompile = false); + void memoryCopyPrecompile(); + /// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length. + /// Stack pre: + /// Stack post: + void memoryCopy32(); + /// Copies data in memory (regions cannot overlap). + /// Stack pre: + /// Stack post: + void memoryCopy(); /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) /// external function type
into two stack slots: From 81d7e0233aa24a2d38103b2bf746cef5eb12a893 Mon Sep 17 00:00:00 2001 From: chriseth Date: Sun, 11 Dec 2016 17:58:22 +0100 Subject: [PATCH 9/9] Changelog entry. --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 1eb90c222..c3b3f5fad 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Features: * Type checker: Warn when ``msg.value`` is used in non-payable function. * Code generator: Inject the Swarm hash of a metadata file into the bytecode. + * Code generator: Replace expensive memcpy precompile by simple assembly loop. * Optimizer: Some dead code elimination. Bugfixes: