[Sol->Yul] Enabling creation function call

This commit is contained in:
Djordje Mijovic 2020-04-09 21:59:17 +02:00
parent 99aa821410
commit f3f729549d
36 changed files with 420 additions and 28 deletions

View File

@ -1337,6 +1337,34 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocationTemporaryMemoryFunction()
{
string functionName = "allocateTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> memPtr {
memPtr := mload(<freeMemoryPointer>)
}
)")
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::releaseTemporaryMemoryFunction()
{
string functionName = "releaseTemporaryMemory";
return m_functionCollector.createFunction(functionName, [&](){
return Whiskers(R"(
function <functionName>() {
}
)")
("functionName", functionName)
.render();
});
}
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
if (_type.baseType()->hasSimpleZeroValueInMemory())

View File

@ -253,6 +253,13 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of the function that allocates temporary memory with predefined size
/// Return value: pointer
std::string allocationTemporaryMemoryFunction();
/// @returns the name of the function that releases previously allocated temporary memory
std::string releaseTemporaryMemoryFunction();
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);

View File

@ -21,6 +21,7 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
@ -96,6 +97,15 @@ string IRGenerationContext::functionName(VariableDeclaration const& _varDecl)
return "getter_fun_" + _varDecl.name() + "_" + to_string(_varDecl.id());
}
string IRGenerationContext::creationObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id());
}
string IRGenerationContext::runtimeObjectName(ContractDefinition const& _contract) const
{
return _contract.name() + "_" + toString(_contract.id()) + "_deployed";
}
string IRGenerationContext::newYulVariable()
{
return "_" + to_string(++m_varCounter);
@ -170,6 +180,11 @@ YulUtilFunctions IRGenerationContext::utils()
return YulUtilFunctions(m_evmVersion, m_revertStrings, m_functions);
}
ABIFunctions IRGenerationContext::abiFunctions()
{
return ABIFunctions(m_evmVersion, m_revertStrings, m_functions);
}
std::string IRGenerationContext::revertReasonIfDebug(std::string const& _message)
{
return YulUtilFunctions::revertReasonIfDebug(m_revertStrings, _message);

View File

@ -20,6 +20,7 @@
#pragma once
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/ir/IRVariable.h>
#include <libsolidity/interface/OptimiserSettings.h>
#include <libsolidity/interface/DebugSettings.h>
@ -38,11 +39,8 @@
namespace solidity::frontend
{
class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
class YulUtilFunctions;
class ABIFunctions;
/**
* Class that contains contextual information during IR generation.
@ -93,6 +91,9 @@ public:
std::string functionName(FunctionDefinition const& _function);
std::string functionName(VariableDeclaration const& _varDecl);
std::string creationObjectName(ContractDefinition const& _contract) const;
std::string runtimeObjectName(ContractDefinition const& _contract) const;
std::string newYulVariable();
std::string internalDispatch(size_t _in, size_t _out);
@ -102,6 +103,8 @@ public:
langutil::EVMVersion evmVersion() const { return m_evmVersion; };
ABIFunctions abiFunctions();
/// @returns code that stores @param _message for revert reason
/// if m_revertStrings is debug.
std::string revertReasonIfDebug(std::string const& _message = "");
@ -112,6 +115,8 @@ public:
/// function call that was invoked as part of the try statement.
std::string trySuccessConditionVariable(Expression const& _expression) const;
std::set<ContractDefinition const*, ASTNode::CompareByID>& subObjectsCreated() { return m_subObjects; }
private:
langutil::EVMVersion m_evmVersion;
RevertStrings m_revertStrings;
@ -131,6 +136,8 @@ private:
/// long as the order of Yul functions in the generated code is deterministic and the same on
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector.
std::set<FunctionDefinition const*> m_functionGenerationQueue;
std::set<ContractDefinition const*, ASTNode::CompareByID> m_subObjects;
};
}

View File

@ -48,9 +48,12 @@ using namespace solidity;
using namespace solidity::util;
using namespace solidity::frontend;
pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
pair<string, string> IRGenerator::run(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
string const ir = yul::reindent(generate(_contract));
string const ir = yul::reindent(generate(_contract, _otherYulSources));
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -73,8 +76,19 @@ pair<string, string> IRGenerator::run(ContractDefinition const& _contract)
return {warning + ir, warning + asmStack.print()};
}
string IRGenerator::generate(ContractDefinition const& _contract)
string IRGenerator::generate(
ContractDefinition const& _contract,
map<ContractDefinition const*, string const> const& _otherYulSources
)
{
auto subObjectSources = [&_otherYulSources](std::set<ContractDefinition const*, ASTNode::CompareByID> const& subObjects) -> string
{
std::string subObjectsSources;
for (ContractDefinition const* subObject: subObjects)
subObjectsSources += _otherYulSources.at(subObject);
return subObjectsSources;
};
Whiskers t(R"(
object "<CreationObject>" {
code {
@ -93,13 +107,15 @@ string IRGenerator::generate(ContractDefinition const& _contract)
<dispatch>
<runtimeFunctions>
}
<runtimeSubObjects>
}
<subObjects>
}
)");
resetContext(_contract);
t("CreationObject", creationObjectName(_contract));
t("CreationObject", m_context.creationObjectName(_contract));
t("memoryInit", memoryInit());
t("notLibrary", !_contract.isLibrary());
@ -112,7 +128,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
constructorParams.emplace_back(m_context.newYulVariable());
t(
"copyConstructorArguments",
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, creationObjectName(_contract))
m_utils.copyConstructorArgumentsToMemoryFunction(_contract, m_context.creationObjectName(_contract))
);
}
t("constructorParams", joinHumanReadable(constructorParams));
@ -123,12 +139,14 @@ string IRGenerator::generate(ContractDefinition const& _contract)
generateImplicitConstructors(_contract);
generateQueuedFunctions();
t("functions", m_context.functionCollector().requestedFunctions());
t("subObjects", subObjectSources(m_context.subObjectsCreated()));
resetContext(_contract);
t("RuntimeObject", runtimeObjectName(_contract));
t("RuntimeObject", m_context.runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
generateQueuedFunctions();
t("runtimeFunctions", m_context.functionCollector().requestedFunctions());
t("runtimeSubObjects", subObjectSources(m_context.subObjectsCreated()));
return t.render();
}
@ -313,6 +331,7 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract)
return generator.code();
}
void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contract)
{
auto listAllParams = [&](
@ -375,7 +394,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
codecopy(0, dataoffset("<object>"), datasize("<object>"))
return(0, datasize("<object>"))
)X");
t("object", runtimeObjectName(_contract));
t("object", m_context.runtimeObjectName(_contract));
return t.render();
}
@ -384,16 +403,6 @@ string IRGenerator::callValueCheck()
return "if callvalue() { revert(0, 0) }";
}
string IRGenerator::creationObjectName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id());
}
string IRGenerator::runtimeObjectName(ContractDefinition const& _contract)
{
return _contract.name() + "_" + to_string(_contract.id()) + "_deployed";
}
string IRGenerator::implicitConstructorName(ContractDefinition const& _contract)
{
return "constructor_" + _contract.name() + "_" + to_string(_contract.id());

View File

@ -50,10 +50,16 @@ public:
/// Generates and returns the IR code, in unoptimized and optimized form
/// (or just pretty-printed, depending on the optimizer settings).
std::pair<std::string, std::string> run(ContractDefinition const& _contract);
std::pair<std::string, std::string> run(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
private:
std::string generate(ContractDefinition const& _contract);
std::string generate(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, std::string const> const& _otherYulSources
);
std::string generate(Block const& _block);
/// Generates code for all the functions from the function generation queue.
@ -87,8 +93,6 @@ private:
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
std::string creationObjectName(ContractDefinition const& _contract);
std::string runtimeObjectName(ContractDefinition const& _contract);
std::string implicitConstructorName(ContractDefinition const& _contract);
std::string dispatchRoutine(ContractDefinition const& _contract);

View File

@ -849,11 +849,63 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
templ("pos", m_context.newYulVariable());
templ("end", m_context.newYulVariable());
templ("freeMemory", freeMemory());
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type},{parameterTypes.front()}));
templ("encode", abi.tupleEncoder({arguments.front()->annotation().type}, {parameterTypes.front()}));
templ("nonIndexedArgs", IRVariable(*arguments.front()).commaSeparatedList());
templ("log", "log" + to_string(logNumber));
templ("indexedArgs", indexedArgs);
m_code << templ.render();
break;
}
case FunctionType::Kind::Creation:
{
solAssert(!functionType->gasSet(), "Gas limit set for contract creation.");
solAssert(
functionType->returnParameterTypes().size() == 1,
"Constructor should return only one type"
);
TypePointers argumentTypes;
string constructorParams;
for (ASTPointer<Expression const> const& arg: arguments)
{
argumentTypes.push_back(arg->annotation().type);
constructorParams += ", " + IRVariable{*arg}.commaSeparatedList();
}
ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*functionType->returnParameterTypes().front()).contractDefinition();
m_context.subObjectsCreated().insert(contract);
Whiskers t(R"(
let <memPos> := <allocateTemporaryMemory>()
let <memEnd> := add(<memPos>, datasize("<object>"))
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { revert(0, 0) }
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
<?saltSet>
let <retVars> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
<!saltSet>
let <retVars> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
</saltSet>
<releaseTemporaryMemory>()
)");
t("memPos", m_context.newYulVariable());
t("memEnd", m_context.newYulVariable());
t("allocateTemporaryMemory", m_utils.allocationTemporaryMemoryFunction());
t("releaseTemporaryMemory", m_utils.releaseTemporaryMemoryFunction());
t("object", m_context.creationObjectName(*contract));
t("abiEncode",
m_context.abiFunctions().tupleEncoder(argumentTypes, functionType->parameterTypes(),false)
);
t("constructorParams", constructorParams);
t("value", functionType->valueSet() ? IRVariable(_functionCall.expression()).part("value").name() : "0");
t("saltSet", functionType->saltSet());
if (functionType->saltSet())
t("salt", IRVariable(_functionCall.expression()).part("salt").name());
t("retVars", IRVariable(_functionCall).commaSeparatedList());
m_code << t.render();
break;
}
default:

View File

@ -1129,15 +1129,21 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
if (!_contract.canBeDeployed())
return;
map<ContractDefinition const*, string const> otherYulSources;
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
if (!compiledContract.yulIR.empty())
return;
string dependenciesSource;
for (auto const* dependency: _contract.annotation().contractDependencies)
{
generateIR(*dependency);
otherYulSources.emplace(dependency, m_contracts.at(dependency->fullyQualifiedName()).yulIR);
}
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
}
void CompilerStack::generateEwasm(ContractDefinition const& _contract)

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,7 @@
pragma solidity >=0.6.0;
contract C {
constructor() public {}
}
contract D is C {
}

View File

@ -0,0 +1,55 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "C_6" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_6_deployed")
codecopy(0, dataoffset("C_6_deployed"), _1)
return(0, _1)
}
}
object "C_6_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_9" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("D_9_deployed")
codecopy(0, dataoffset("D_9_deployed"), _1)
return(0, _1)
}
}
object "D_9_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}

View File

@ -0,0 +1 @@
--ir-optimized --optimize

View File

@ -0,0 +1,5 @@
Warning: Unused local variable.
--> ir_compiler_subobjects/input.sol:6:9:
|
6 | C c = new C();
| ^^^

View File

@ -0,0 +1,8 @@
pragma solidity >=0.6.0;
contract C {}
contract D {
function f() public {
C c = new C();
}
}

View File

@ -0,0 +1,96 @@
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "C_2" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
return(0, _1)
}
}
object "C_2_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
Optimized IR:
/*******************************************************
* WARNING *
* Solidity to Yul compilation is still EXPERIMENTAL *
* It can result in LOSS OF FUNDS or worse *
* !USE AT YOUR OWN RISK! *
*******************************************************/
object "D_13" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("D_13_deployed")
codecopy(0, dataoffset("D_13_deployed"), _1)
return(0, _1)
}
}
object "D_13_deployed" {
code {
{
mstore(64, 128)
if iszero(lt(calldatasize(), 4))
{
let _1 := 0
if eq(0x26121ff0, shr(224, calldataload(_1)))
{
if callvalue() { revert(_1, _1) }
if slt(add(calldatasize(), not(3)), _1) { revert(_1, _1) }
let _2 := datasize("C_2")
let _3 := add(128, _2)
if or(gt(_3, 0xffffffffffffffff), lt(_3, 128)) { revert(_1, _1) }
datacopy(128, dataoffset("C_2"), _2)
pop(create(_1, 128, _2))
return(allocateMemory(_1), _1)
}
}
revert(0, 0)
}
function allocateMemory(size) -> memPtr
{
memPtr := mload(64)
let newFreePtr := add(memPtr, size)
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
mstore(64, newFreePtr)
}
}
object "C_2" {
code {
{
mstore(64, 128)
if callvalue() { revert(0, 0) }
let _1 := datasize("C_2_deployed")
codecopy(0, dataoffset("C_2_deployed"), _1)
return(0, _1)
}
}
object "C_2_deployed" {
code {
{
mstore(64, 128)
revert(0, 0)
}
}
}
}
}
}

View File

@ -76,7 +76,9 @@ object \"C_6\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -131,7 +131,9 @@ object \"C_10\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -99,7 +99,9 @@ object \"C_10\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -111,7 +111,9 @@ object \"C_10\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -135,7 +135,9 @@ object \"C_10\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -111,7 +111,9 @@ object \"C_10\" {
}
}
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -17,5 +17,7 @@ contract B {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 2, 3, 4, 5, 6, 1000, 1001, 1002, 1003, 1004

View File

@ -41,5 +41,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// test() -> 5, 6, 7

View File

@ -0,0 +1,15 @@
contract C {
uint public i;
constructor() public {
i = 2;
}
}
contract D {
function f() public returns (uint r) {
return new C().i();
}
}
// ====
// compileViaYul: also
// ----
// f() -> 2

View File

@ -0,0 +1,20 @@
contract C {
uint public i;
constructor(uint newI) public {
i = newI;
}
}
contract D {
C c;
constructor(uint v) public {
c = new C(v);
}
function f() public returns (uint r) {
return c.i();
}
}
// ====
// compileViaYul: also
// ----
// constructor(): 2 ->
// f() -> 2

View File

@ -0,0 +1,21 @@
contract C {
uint public i;
constructor(uint newI) public {
i = newI;
}
}
contract D {
C c;
constructor(uint v) public {
c = new C{salt: "abc"}(v);
}
function f() public returns (uint r) {
return c.i();
}
}
// ====
// EVMVersion: >=constantinople
// compileViaYul: also
// ----
// constructor(): 2 ->
// f() -> 2

View File

@ -36,8 +36,10 @@ contract test {
}
}
// ====
// compileViaYul: also
// ----
// constructor(), 20 wei ->
// sendAmount(uint256): 5 -> 5
// outOfGas() -> FAILURE # call to helper should not succeed but amount should be transferred anyway #
// checkState() -> false, 15
// checkState() -> false, 15

View File

@ -33,6 +33,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// convertParent() -> 1
// convertSubA() -> 1, 2

View File

@ -19,6 +19,8 @@ contract D {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 1
// g() -> 5

View File

@ -21,5 +21,7 @@ contract B {
}
}
// ====
// compileViaYul: also
// ----
// g() -> 42

View File

@ -21,5 +21,7 @@ contract B {
}
}
// ====
// compileViaYul: also
// ----
// g() -> 42

View File

@ -16,5 +16,7 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// constructor() ->

View File

@ -23,6 +23,8 @@ contract C {
}
}
// ====
// compileViaYul: also
// ----
// test() -> 9, 7
// t2() -> 9

View File

@ -15,6 +15,8 @@ contract D {
}
}
// ====
// compileViaYul: also
// ----
// constructor(), 27 wei ->
// f() -> 27

View File

@ -32,6 +32,7 @@ contract D {
}
}
// ====
// compileViaYul: also
// EVMVersion: >=byzantium
// ----
// f() -> 0x1 # This should work, next should throw #

View File

@ -34,6 +34,8 @@ contract D {
}
}
// ====
// compileViaYul: also
// ----
// f() -> 3
// g() -> 8