Merge pull request #10341 from ethereum/optimizeAllocation

Do not allocate memory objects if they will be assigned directly.
This commit is contained in:
chriseth 2020-11-24 16:05:46 +01:00 committed by GitHub
commit 2d235bf7b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 5 deletions

View File

@ -4,9 +4,11 @@ Language Features:
* The fallback function can now also have a single ``calldata`` argument (equaling ``msg.data``) and return ``bytes memory`` (which will not be ABI-encoded but returned as-is). * The fallback function can now also have a single ``calldata`` argument (equaling ``msg.data``) and return ``bytes memory`` (which will not be ABI-encoded but returned as-is).
Compiler Features: Compiler Features:
* Code Generator: Avoid memory allocation for default value if it is not used.
* SMTChecker: Support named arguments in function calls. * SMTChecker: Support named arguments in function calls.
* SMTChecker: Support struct constructor. * SMTChecker: Support struct constructor.
### 0.7.5 (2020-11-18) ### 0.7.5 (2020-11-18)
Language Features: Language Features:

View File

@ -612,7 +612,7 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
} }
for (ASTPointer<VariableDeclaration> const& variable: _function.returnParameters()) for (ASTPointer<VariableDeclaration> const& variable: _function.returnParameters())
appendStackVariableInitialisation(*variable); appendStackVariableInitialisation(*variable, /* _provideDefaultValue = */ true);
if (_function.isConstructor()) if (_function.isConstructor())
if (auto c = dynamic_cast<ContractDefinition const&>(*_function.scope()).nextConstructor( if (auto c = dynamic_cast<ContractDefinition const&>(*_function.scope()).nextConstructor(
@ -1230,7 +1230,7 @@ bool ContractCompiler::visit(VariableDeclarationStatement const& _variableDeclar
// and freed in the end of their scope. // and freed in the end of their scope.
for (auto decl: _variableDeclarationStatement.declarations()) for (auto decl: _variableDeclarationStatement.declarations())
if (decl) if (decl)
appendStackVariableInitialisation(*decl); appendStackVariableInitialisation(*decl, !_variableDeclarationStatement.initialValue());
StackHeightChecker checker(m_context); StackHeightChecker checker(m_context);
if (Expression const* expression = _variableDeclarationStatement.initialValue()) if (Expression const* expression = _variableDeclarationStatement.initialValue())
@ -1376,11 +1376,20 @@ void ContractCompiler::appendModifierOrFunctionCode()
m_context.setModifierDepth(m_modifierDepth); m_context.setModifierDepth(m_modifierDepth);
} }
void ContractCompiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) void ContractCompiler::appendStackVariableInitialisation(
VariableDeclaration const& _variable,
bool _provideDefaultValue
)
{ {
CompilerContext::LocationSetter location(m_context, _variable); CompilerContext::LocationSetter location(m_context, _variable);
m_context.addVariable(_variable); m_context.addVariable(_variable);
CompilerUtils(m_context).pushZeroValue(*_variable.annotation().type); if (!_provideDefaultValue && _variable.type()->dataStoredIn(DataLocation::Memory))
{
solAssert(_variable.type()->sizeOnStack() == 1, "");
m_context << u256(0);
}
else
CompilerUtils(m_context).pushZeroValue(*_variable.annotation().type);
} }
void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) void ContractCompiler::compileExpression(Expression const& _expression, TypePointer const& _targetType)

View File

@ -130,7 +130,10 @@ private:
/// body itself if the last modifier was reached. /// body itself if the last modifier was reached.
void appendModifierOrFunctionCode(); void appendModifierOrFunctionCode();
void appendStackVariableInitialisation(VariableDeclaration const& _variable); /// Creates a stack slot for the given variable and assigns a default value.
/// If the default value is complex (needs memory allocation) and @a _provideDefaultValue
/// is false, this might be skipped.
void appendStackVariableInitialisation(VariableDeclaration const& _variable, bool _provideDefaultValue);
void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer());
/// Frees the variables of a certain scope (to be used when leaving). /// Frees the variables of a certain scope (to be used when leaving).

View File

@ -0,0 +1,16 @@
contract C {
function f() public pure {
uint[] memory x;
uint y;
assembly {
y := x
}
// The value of an uninitialized dynamic array is not zero but rather
// an address of a location in memory that has the value of zero.
assert(y != 0);
}
}
// ====
// compileViaYul: also
// ----
// f() ->

View File

@ -0,0 +1,30 @@
contract C {
function memorySize() internal pure returns (uint s) {
assembly { s := mload(0x40) }
}
function f() public returns (uint, uint, uint) {
uint a = memorySize();
g();
uint b = memorySize();
h();
uint c = memorySize();
i();
uint d = memorySize();
return (b - a, c - b, d - c);
}
// In these functions, we do allocate memory in both cases.
// In `i()`, this could be avoided but we would have to check
// that all code paths return explicitly and provide a value.
function g() internal returns (uint[40] memory) {
}
function h() internal returns (uint[40] memory t) {
}
function i() internal returns (uint[40] memory) {
uint[40] memory x;
return x;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x0500, 0x0500, 0x0a00

View File

@ -0,0 +1,24 @@
contract C {
function memorySize() internal pure returns (uint s) {
assembly { s := mload(0x40) }
}
function withValue() public pure returns (uint) {
uint[20] memory x;
uint memorySizeBefore = memorySize();
uint[20] memory t = x;
uint memorySizeAfter = memorySize();
return memorySizeAfter - memorySizeBefore;
}
function withoutValue() public pure returns (uint) {
uint[20] memory x;
uint memorySizeBefore = memorySize();
uint[20] memory t;
uint memorySizeAfter = memorySize();
return memorySizeAfter - memorySizeBefore;
}
}
// ====
// compileViaYul: also
// ----
// withValue() -> 0x00
// withoutValue() -> 0x0280

View File

@ -0,0 +1,24 @@
contract C {
struct S { uint x; uint y; uint z; }
function memorySize() internal pure returns (uint s) {
assembly { s := mload(0x40) }
}
function withValue() public pure returns (uint) {
S memory x = S(1, 2, 3);
uint memorySizeBefore = memorySize();
S memory t = x;
uint memorySizeAfter = memorySize();
return memorySizeAfter - memorySizeBefore;
}
function withoutValue() public pure returns (uint) {
uint memorySizeBefore = memorySize();
S memory t;
uint memorySizeAfter = memorySize();
return memorySizeAfter - memorySizeBefore;
}
}
// ====
// compileViaYul: also
// ----
// withValue() -> 0x00
// withoutValue() -> 0x60

View File

@ -0,0 +1,7 @@
contract C {
function f() public pure {
uint[] memory x = x[0];
}
}
// ----
// DeclarationError 7576: (70-71): Undeclared identifier. "x" is not (or not yet) visible at this point.

View File

@ -0,0 +1,8 @@
contract C {
struct S { uint y; }
function f() public pure {
S memory x = x.y;
}
}
// ----
// DeclarationError 7576: (90-91): Undeclared identifier. "x" is not (or not yet) visible at this point.