mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #8270 from ethereum/sol2yul-ExternalCall
Finishes external call implementation.
This commit is contained in:
commit
3e4493dc35
@ -622,13 +622,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
|
|||||||
}
|
}
|
||||||
solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
|
solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
|
||||||
Whiskers templ(R"({
|
Whiskers templ(R"({
|
||||||
let <pos> := mload(<freeMemoryPointer>)
|
let <pos> := <freeMemory>
|
||||||
let <end> := <encode>(<pos> <nonIndexedArgs>)
|
let <end> := <encode>(<pos> <nonIndexedArgs>)
|
||||||
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
|
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
|
||||||
})");
|
})");
|
||||||
templ("pos", m_context.newYulVariable());
|
templ("pos", m_context.newYulVariable());
|
||||||
templ("end", m_context.newYulVariable());
|
templ("end", m_context.newYulVariable());
|
||||||
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
templ("freeMemory", freeMemory());
|
||||||
templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
|
templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
|
||||||
templ("nonIndexedArgs", nonIndexedArgs);
|
templ("nonIndexedArgs", nonIndexedArgs);
|
||||||
templ("log", "log" + to_string(indexedArgs.size()));
|
templ("log", "log" + to_string(indexedArgs.size()));
|
||||||
@ -1173,7 +1173,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (funType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall());
|
||||||
|
|
||||||
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
bool haveReturndatacopy = m_context.evmVersion().supportsReturndata();
|
||||||
unsigned retSize = 0;
|
unsigned estimatedReturnSize = 0;
|
||||||
bool dynamicReturnSize = false;
|
bool dynamicReturnSize = false;
|
||||||
TypePointers returnTypes;
|
TypePointers returnTypes;
|
||||||
if (!returnSuccessConditionAndReturndata)
|
if (!returnSuccessConditionAndReturndata)
|
||||||
@ -1188,13 +1188,13 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
{
|
{
|
||||||
solAssert(haveReturndatacopy, "");
|
solAssert(haveReturndatacopy, "");
|
||||||
dynamicReturnSize = true;
|
dynamicReturnSize = true;
|
||||||
retSize = 0;
|
estimatedReturnSize = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (retType->decodingType())
|
else if (retType->decodingType())
|
||||||
retSize += retType->decodingType()->calldataEncodedSize();
|
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
|
||||||
else
|
else
|
||||||
retSize += retType->calldataEncodedSize();
|
estimatedReturnSize += retType->calldataEncodedSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePointers argumentTypes;
|
TypePointers argumentTypes;
|
||||||
@ -1204,7 +1204,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
argumentTypes.emplace_back(&type(*arg));
|
argumentTypes.emplace_back(&type(*arg));
|
||||||
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
|
||||||
}
|
}
|
||||||
string argumentString = joinHumanReadable(argumentStrings);
|
string argumentString = ", " + joinHumanReadable(argumentStrings);
|
||||||
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||||
|
|
||||||
@ -1214,8 +1214,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
// (which we would have to subtract from the gas left)
|
// (which we would have to subtract from the gas left)
|
||||||
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
// We could also just use MLOAD; POP right before the gas calculation, but the optimizer
|
||||||
// would remove that, so we use MSTORE here.
|
// would remove that, so we use MSTORE here.
|
||||||
if (!funType.gasSet() && retSize > 0)
|
if (!funType.gasSet() && estimatedReturnSize > 0)
|
||||||
m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n";
|
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
ABIFunctions abi(m_context.evmVersion(), m_context.revertStrings(), m_context.functionCollector());
|
||||||
@ -1226,26 +1226,29 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
if iszero(extcodesize(<address>)) { revert(0, 0) }
|
||||||
</checkExistence>
|
</checkExistence>
|
||||||
|
|
||||||
let <pos> := <freeMem>
|
let <pos> := <freeMemory>
|
||||||
|
|
||||||
mstore(<pos>, <shl28>(<funId>))
|
mstore(<pos>, <shl28>(<funId>))
|
||||||
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
|
||||||
|
|
||||||
let <result> := <call>(<gas>, <address>, <value>, <pos>, sub(<end>, <pos>), <pos>, <retSize>)
|
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
|
||||||
if iszero(<result>) { <forwardingRevert> }
|
if iszero(<result>) { <forwardingRevert>() }
|
||||||
|
|
||||||
<?dynamicReturnSize>
|
<?dynamicReturnSize>
|
||||||
returndatacopy(<pos>, 0, returndatasize())
|
returndatacopy(<pos>, 0, returndatasize())
|
||||||
</dynamicReturnSize>
|
</dynamicReturnSize>
|
||||||
<allocate>
|
|
||||||
mstore(<freeMem>, add(<pos>, and(add(<retSize>, 0x1f), not(0x1f))))
|
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
|
||||||
<?returns> let <retvars> := </returns> <abiDecode>(<pos>, <retSize>)
|
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
|
||||||
)");
|
)");
|
||||||
templ("pos", m_context.newYulVariable());
|
templ("pos", m_context.newYulVariable());
|
||||||
templ("end", m_context.newYulVariable());
|
templ("end", m_context.newYulVariable());
|
||||||
templ("result", m_context.newYulVariable());
|
templ("result", m_context.newYulVariable());
|
||||||
templ("freeMem", fetchFreeMem());
|
templ("freeMemory", freeMemory());
|
||||||
|
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
|
||||||
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
templ("shl28", m_utils.shiftLeftFunction(8 * (32 - 4)));
|
||||||
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
templ("funId", IRVariable(_functionCall.expression()).part("functionIdentifier").name());
|
||||||
|
templ("address", IRVariable(_functionCall.expression()).part("address").name());
|
||||||
|
|
||||||
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
|
// If the function takes arbitrary parameters or is a bare call, copy dynamic length data in place.
|
||||||
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
// Move arguments to memory, will not update the free memory pointer (but will update the memory
|
||||||
@ -1257,23 +1260,19 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
encodeInPlace = false;
|
encodeInPlace = false;
|
||||||
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
|
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
|
||||||
solUnimplementedAssert(!encodeInPlace, "");
|
solUnimplementedAssert(!encodeInPlace, "");
|
||||||
solUnimplementedAssert(!funType.padArguments(), "");
|
solUnimplementedAssert(funType.padArguments(), "");
|
||||||
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
|
||||||
templ("argumentString", argumentString);
|
templ("argumentString", argumentString);
|
||||||
|
|
||||||
// Output data will replace input data, unless we have ECRecover (then, output
|
// Output data will replace input data, unless we have ECRecover (then, output
|
||||||
// area will be 32 bytes just before input area).
|
// area will be 32 bytes just before input area).
|
||||||
templ("retSize", to_string(retSize));
|
|
||||||
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
|
||||||
|
|
||||||
if (isDelegateCall)
|
solAssert(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
|
||||||
solAssert(!funType.valueSet(), "Value set for delegatecall");
|
solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
|
||||||
else if (useStaticCall)
|
|
||||||
solAssert(!funType.valueSet(), "Value set for staticcall");
|
templ("hasValue", !isDelegateCall && !useStaticCall);
|
||||||
else if (funType.valueSet())
|
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
|
||||||
templ("value", IRVariable(_functionCall.expression()).part("value").name());
|
|
||||||
else
|
|
||||||
templ("value", "0");
|
|
||||||
|
|
||||||
// Check that the target contract exists (has code) for non-low-level calls.
|
// Check that the target contract exists (has code) for non-low-level calls.
|
||||||
bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
|
bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
|
||||||
@ -1315,13 +1314,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
|||||||
if (haveReturndatacopy)
|
if (haveReturndatacopy)
|
||||||
templ("returnSize", "returndatasize()");
|
templ("returnSize", "returndatasize()");
|
||||||
else
|
else
|
||||||
templ("returnSize", to_string(retSize));
|
templ("returnSize", to_string(estimatedReturnSize));
|
||||||
|
|
||||||
|
templ("reservedReturnSize", dynamicReturnSize ? "0" : to_string(estimatedReturnSize));
|
||||||
|
|
||||||
templ("abiDecode", abi.tupleDecoder(returnTypes, true));
|
templ("abiDecode", abi.tupleDecoder(returnTypes, true));
|
||||||
templ("returns", !returnTypes.empty());
|
templ("returns", !returnTypes.empty());
|
||||||
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
|
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
|
||||||
|
|
||||||
|
m_code << templ.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
string IRGeneratorForStatements::fetchFreeMem() const
|
string IRGeneratorForStatements::freeMemory()
|
||||||
{
|
{
|
||||||
return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")";
|
return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")";
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,9 @@ private:
|
|||||||
std::vector<ASTPointer<Expression const>> const& _arguments
|
std::vector<ASTPointer<Expression const>> const& _arguments
|
||||||
);
|
);
|
||||||
|
|
||||||
std::string fetchFreeMem() const;
|
/// @returns code that evaluates to the first unused memory slot (which does not have to
|
||||||
|
/// be empty).
|
||||||
|
static std::string freeMemory();
|
||||||
|
|
||||||
/// Generates the required conversion code and @returns an IRVariable referring to the value of @a _variable
|
/// Generates the required conversion code and @returns an IRVariable referring to the value of @a _variable
|
||||||
/// converted to type @a _to.
|
/// converted to type @a _to.
|
||||||
|
17
test/libsolidity/semanticTests/array/memory.sol
Normal file
17
test/libsolidity/semanticTests/array/memory.sol
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
pragma solidity >= 0.6.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function h(uint[4] memory n) public pure returns (uint) {
|
||||||
|
return n[0] + n[1] + n[2] + n[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
function i(uint[4] memory n) public view returns (uint) {
|
||||||
|
return this.h(n) * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// h(uint256[4]): 1, 2, 3, 4 -> 10
|
||||||
|
// i(uint256[4]): 1, 2, 3, 4 -> 20
|
@ -0,0 +1,17 @@
|
|||||||
|
pragma solidity >= 0.6.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function g(uint n) external pure returns (uint) {
|
||||||
|
return n + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function f(uint n) public view returns (uint) {
|
||||||
|
return this.g(2 * n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// ----
|
||||||
|
// g(uint256): 4 -> 5
|
||||||
|
// f(uint256): 2 -> 5
|
@ -0,0 +1,24 @@
|
|||||||
|
pragma solidity >= 0.6.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
function d(uint n) external pure returns (uint[] memory) {
|
||||||
|
uint[] memory data = new uint[](n);
|
||||||
|
for (uint i = 0; i < data.length; ++i)
|
||||||
|
data[i] = i;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dt(uint n) public view returns (uint) {
|
||||||
|
uint[] memory data = this.d(n);
|
||||||
|
uint sum = 0;
|
||||||
|
for (uint i = 0; i < data.length; ++i)
|
||||||
|
sum += data[i];
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====
|
||||||
|
// compileViaYul: also
|
||||||
|
// EVMVersion: >=byzantium
|
||||||
|
// ----
|
||||||
|
// dt(uint256): 4 -> 6
|
Loading…
Reference in New Issue
Block a user