Finishes external call implementation.

This commit is contained in:
Christian Parpart 2020-02-10 11:44:52 +01:00 committed by chriseth
parent 96709b3285
commit c92fe69a60
5 changed files with 93 additions and 29 deletions

View File

@ -622,13 +622,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
}
solAssert(indexedArgs.size() <= 4, "Too many indexed arguments.");
Whiskers templ(R"({
let <pos> := mload(<freeMemoryPointer>)
let <pos> := <freeMemory>
let <end> := <encode>(<pos> <nonIndexedArgs>)
<log>(<pos>, sub(<end>, <pos>) <indexedArgs>)
})");
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer));
templ("freeMemory", freeMemory());
templ("encode", abi.tupleEncoder(nonIndexedArgTypes, nonIndexedParamTypes));
templ("nonIndexedArgs", nonIndexedArgs);
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 haveReturndatacopy = m_context.evmVersion().supportsReturndata();
unsigned retSize = 0;
unsigned estimatedReturnSize = 0;
bool dynamicReturnSize = false;
TypePointers returnTypes;
if (!returnSuccessConditionAndReturndata)
@ -1188,13 +1188,13 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
{
solAssert(haveReturndatacopy, "");
dynamicReturnSize = true;
retSize = 0;
estimatedReturnSize = 0;
break;
}
else if (retType->decodingType())
retSize += retType->decodingType()->calldataEncodedSize();
estimatedReturnSize += retType->decodingType()->calldataEncodedSize();
else
retSize += retType->calldataEncodedSize();
estimatedReturnSize += retType->calldataEncodedSize();
}
TypePointers argumentTypes;
@ -1204,7 +1204,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
argumentTypes.emplace_back(&type(*arg));
argumentStrings.emplace_back(IRVariable(*arg).commaSeparatedList());
}
string argumentString = joinHumanReadable(argumentStrings);
string argumentString = ", " + joinHumanReadable(argumentStrings);
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
@ -1214,8 +1214,8 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
// (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
// would remove that, so we use MSTORE here.
if (!funType.gasSet() && retSize > 0)
m_code << "mstore(add(" << fetchFreeMem() << ", " << to_string(retSize) << "), 0)\n";
if (!funType.gasSet() && estimatedReturnSize > 0)
m_code << "mstore(add(" << freeMemory() << ", " << to_string(estimatedReturnSize) << "), 0)\n";
}
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) }
</checkExistence>
let <pos> := <freeMem>
let <pos> := <freeMemory>
mstore(<pos>, <shl28>(<funId>))
let <end> := <encodeArgs>(add(<pos>, 4) <argumentString>)
let <result> := <call>(<gas>, <address>, <value>, <pos>, sub(<end>, <pos>), <pos>, <retSize>)
if iszero(<result>) { <forwardingRevert> }
let <result> := <call>(<gas>, <address>, <?hasValue> <value>, </hasValue> <pos>, sub(<end>, <pos>), <pos>, <reservedReturnSize>)
if iszero(<result>) { <forwardingRevert>() }
<?dynamicReturnSize>
returndatacopy(<pos>, 0, returndatasize())
</dynamicReturnSize>
<allocate>
mstore(<freeMem>, add(<pos>, and(add(<retSize>, 0x1f), not(0x1f))))
<?returns> let <retvars> := </returns> <abiDecode>(<pos>, <retSize>)
mstore(<freeMemoryPointer>, add(<pos>, and(add(<returnSize>, 0x1f), not(0x1f))))
<?returns> let <retVars> := </returns> <abiDecode>(<pos>, add(<pos>, <returnSize>))
)");
templ("pos", m_context.newYulVariable());
templ("end", 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("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.
// 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;
bool encodeForLibraryCall = funKind == FunctionType::Kind::DelegateCall;
solUnimplementedAssert(!encodeInPlace, "");
solUnimplementedAssert(!funType.padArguments(), "");
solUnimplementedAssert(funType.padArguments(), "");
templ("encodeArgs", abi.tupleEncoder(argumentTypes, funType.parameterTypes(), encodeForLibraryCall));
templ("argumentString", argumentString);
// Output data will replace input data, unless we have ECRecover (then, output
// area will be 32 bytes just before input area).
templ("retSize", to_string(retSize));
solUnimplementedAssert(funKind != FunctionType::Kind::ECRecover, "");
if (isDelegateCall)
solAssert(!funType.valueSet(), "Value set for delegatecall");
else if (useStaticCall)
solAssert(!funType.valueSet(), "Value set for staticcall");
else if (funType.valueSet())
templ("value", IRVariable(_functionCall.expression()).part("value").name());
else
templ("value", "0");
solAssert(!isDelegateCall || !funType.valueSet(), "Value set for delegatecall");
solAssert(!useStaticCall || !funType.valueSet(), "Value set for staticcall");
templ("hasValue", !isDelegateCall && !useStaticCall);
templ("value", funType.valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
// Check that the target contract exists (has code) for non-low-level calls.
bool checkExistence = (funKind == FunctionType::Kind::External || funKind == FunctionType::Kind::DelegateCall);
@ -1315,13 +1314,18 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
if (haveReturndatacopy)
templ("returnSize", "returndatasize()");
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("returns", !returnTypes.empty());
templ("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << templ.render();
}
string IRGeneratorForStatements::fetchFreeMem() const
string IRGeneratorForStatements::freeMemory()
{
return "mload(" + to_string(CompilerUtils::freeMemoryPointer) + ")";
}

View File

@ -75,7 +75,9 @@ private:
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
/// converted to type @a _to.

View 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

View File

@ -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

View File

@ -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