From c92fe69a6090de2f3b97e19c4e214c1d797a7557 Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Mon, 10 Feb 2020 11:44:52 +0100 Subject: [PATCH] Finishes external call implementation. --- .../codegen/ir/IRGeneratorForStatements.cpp | 60 ++++++++++--------- .../codegen/ir/IRGeneratorForStatements.h | 4 +- .../semanticTests/array/memory.sol | 17 ++++++ .../functionCall/external_call.sol | 17 ++++++ .../external_call_dynamic_returndata.sol | 24 ++++++++ 5 files changed, 93 insertions(+), 29 deletions(-) create mode 100644 test/libsolidity/semanticTests/array/memory.sol create mode 100644 test/libsolidity/semanticTests/functionCall/external_call.sol create mode 100644 test/libsolidity/semanticTests/functionCall/external_call_dynamic_returndata.sol diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index f6b70332c..dfb95cdd3 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -622,13 +622,13 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall) } solAssert(indexedArgs.size() <= 4, "Too many indexed arguments."); Whiskers templ(R"({ - let := mload() + let := let := ( ) (, sub(, ) ) })"); 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(
)) { revert(0, 0) } - let := + let := + mstore(, ()) let := (add(, 4) ) - let := (,
, , , sub(, ), , ) - if iszero() { } + let := (,
, , , sub(, ), , ) + if iszero() { () } returndatacopy(, 0, returndatasize()) - - mstore(, add(, and(add(, 0x1f), not(0x1f)))) - let := (, ) + + mstore(, add(, and(add(, 0x1f), not(0x1f)))) + let := (, add(, )) )"); 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) + ")"; } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 60861e5be..785b02e09 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -75,7 +75,9 @@ private: std::vector> 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. diff --git a/test/libsolidity/semanticTests/array/memory.sol b/test/libsolidity/semanticTests/array/memory.sol new file mode 100644 index 000000000..dba62a336 --- /dev/null +++ b/test/libsolidity/semanticTests/array/memory.sol @@ -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 diff --git a/test/libsolidity/semanticTests/functionCall/external_call.sol b/test/libsolidity/semanticTests/functionCall/external_call.sol new file mode 100644 index 000000000..b732f5e95 --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/external_call.sol @@ -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 diff --git a/test/libsolidity/semanticTests/functionCall/external_call_dynamic_returndata.sol b/test/libsolidity/semanticTests/functionCall/external_call_dynamic_returndata.sol new file mode 100644 index 000000000..47ccc8dfa --- /dev/null +++ b/test/libsolidity/semanticTests/functionCall/external_call_dynamic_returndata.sol @@ -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