mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #1468 from ethereum/memcpy-assembly
Implement memcpy without the identity precompile
This commit is contained in:
commit
1c3605362d
@ -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:
|
||||
|
@ -335,9 +335,14 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
if (baseSize > 1)
|
||||
m_context << u256(baseSize) << Instruction::MUL;
|
||||
// stack: <target> <source> <size>
|
||||
//@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: <target> <size>
|
||||
|
@ -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() <<
|
||||
@ -217,7 +219,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(
|
||||
|
@ -298,22 +298,73 @@ void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
}
|
||||
|
||||
void CompilerUtils::memoryCopyPrecompile()
|
||||
{
|
||||
// Stack here: size target source
|
||||
|
||||
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<string, string> {
|
||||
{ "$identityContractAddress", toString(identityContractAddress) }
|
||||
}
|
||||
);
|
||||
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
|
||||
}
|
||||
|
||||
void CompilerUtils::memoryCopy32()
|
||||
{
|
||||
// Stack here: size target source
|
||||
|
||||
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
|
||||
// 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());
|
||||
|
||||
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)
|
||||
|
@ -112,6 +112,14 @@ public:
|
||||
/// Uses a CALL to the identity contract to perform a memory-to-memory copy.
|
||||
/// Stack pre: <size> <target> <source>
|
||||
/// Stack post:
|
||||
void memoryCopyPrecompile();
|
||||
/// Copies full 32 byte words in memory (regions cannot overlap), i.e. may copy more than length.
|
||||
/// Stack pre: <size> <target> <source>
|
||||
/// Stack post:
|
||||
void memoryCopy32();
|
||||
/// Copies data in memory (regions cannot overlap).
|
||||
/// Stack pre: <size> <target> <source>
|
||||
/// Stack post:
|
||||
void memoryCopy();
|
||||
|
||||
/// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true)
|
||||
|
Loading…
Reference in New Issue
Block a user