Virtual function calls.

This commit is contained in:
chriseth 2019-04-02 12:37:48 +02:00 committed by Mathias Baumann
parent 18ab8aeb85
commit 565d9837e4
8 changed files with 243 additions and 12 deletions

View File

@ -20,8 +20,11 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libdevcore/Whiskers.h>
using namespace dev;
using namespace dev::solidity;
using namespace std;
@ -45,6 +48,35 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl)
return m_localVariables[&_varDecl];
}
string IRGenerationContext::functionName(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
return "fun_" + _function.name() + "_" + to_string(_function.id());
}
FunctionDefinition const& IRGenerationContext::virtualFunction(FunctionDefinition const& _function)
{
// @TODO previously, we had to distinguish creation context and runtime context,
// but since we do not work with jump positions anymore, this should not be a problem, right?
string name = _function.name();
FunctionType functionType(_function);
for (auto const& contract: m_inheritanceHierarchy)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
function->name() == name &&
!function->isConstructor() &&
FunctionType(*function).asCallableFunction(false)->hasEqualParameterTypes(functionType)
)
return *function;
solAssert(false, "Super function " + name + " not found.");
}
string IRGenerationContext::virtualFunctionName(FunctionDefinition const& _functionDeclaration)
{
return functionName(virtualFunction(_functionDeclaration));
}
string IRGenerationContext::newYulVariable()
{
return "_" + to_string(++m_varCounter);
@ -56,3 +88,43 @@ string IRGenerationContext::variable(Expression const& _expression)
solUnimplementedAssert(size == 1, "");
return "expr_" + to_string(_expression.id());
}
string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
{
// TODO can we limit the generated functions to only those visited
// in the expression context? What about creation / runtime context?
string funName = "dispatch_internal_in_" + to_string(_in) + "_out_" + to_string(_out);
return m_functions->createFunction(funName, [&]() {
Whiskers templ(R"(
function <functionName>(fun <comma> <in>) -> <out> {
switch fun
<#cases>
case <funID>
{
<out> := <name>(<in>)
}
</cases>
default { invalid() }
}
)");
templ("functionName", funName);
templ("comma", _in > 0 ? "," : "");
YulUtilFunctions utils(m_evmVersion, m_functions);
templ("in", utils.suffixedVariableNameList("in_", 0, _in));
templ("out", utils.suffixedVariableNameList("out_", 0, _out));
vector<map<string, string>> functions;
for (auto const& contract: m_inheritanceHierarchy)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
!function->isConstructor() &&
function->parameters().size() == _in &&
function->returnParameters().size() == _out
)
functions.emplace_back(map<string, string> {
{ "funID", to_string(function->id()) },
{ "name", functionName(*function)}
});
templ("cases", move(functions));
return templ.render();
});
}

View File

@ -28,13 +28,16 @@
#include <string>
#include <memory>
#include <vector>
namespace dev
{
namespace solidity
{
class ContractDefinition;
class VariableDeclaration;
class FunctionDefinition;
class Expression;
/**
@ -51,16 +54,30 @@ public:
std::shared_ptr<MultiUseYulFunctionCollector> functionCollector() const { return m_functions; }
/// Sets the current inheritance hierarchy from derived to base.
void setInheritanceHierarchy(std::vector<ContractDefinition const*> _hierarchy)
{
m_inheritanceHierarchy = std::move(_hierarchy);
}
std::string addLocalVariable(VariableDeclaration const& _varDecl);
std::string variableName(VariableDeclaration const& _varDecl);
std::string functionName(FunctionDefinition const& _function);
FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration);
std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration);
std::string newYulVariable();
/// @returns the variable (or comma-separated list of variables) that contain
/// the value of the given expression.
std::string variable(Expression const& _expression);
std::string internalDispatch(size_t _in, size_t _out);
private:
langutil::EVMVersion m_evmVersion;
OptimiserSettings m_optimiserSettings;
std::vector<ContractDefinition const*> m_inheritanceHierarchy;
std::map<VariableDeclaration const*, std::string> m_localVariables;
std::shared_ptr<MultiUseYulFunctionCollector> m_functions;
size_t m_varCounter = 0;

View File

@ -93,6 +93,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
)");
resetContext();
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("CreationObject", creationObjectName(_contract));
t("memoryInit", memoryInit());
t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : "");
@ -100,6 +101,7 @@ string IRGenerator::generate(ContractDefinition const& _contract)
t("functions", m_context.functionCollector()->requestedFunctions());
resetContext();
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
t("RuntimeObject", runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract));
t("runtimeFunctions", m_context.functionCollector()->requestedFunctions());
@ -115,7 +117,7 @@ string IRGenerator::generate(Block const& _block)
string IRGenerator::generateFunction(FunctionDefinition const& _function)
{
string functionName = "fun_" + to_string(_function.id()) + "_" + _function.name();
string functionName = m_context.functionName(_function);
return m_context.functionCollector()->createFunction(functionName, [&]() {
Whiskers t("\nfunction <functionName>(<params>) <returns> {\n<body>\n}\n");
t("functionName", functionName);

View File

@ -23,6 +23,8 @@
#include <libsolidity/codegen/ir/IRGenerationContext.h>
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libdevcore/StringUtils.h>
using namespace std;
using namespace dev;
using namespace dev::solidity;
@ -64,10 +66,10 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
_assignment.rightHandSide().accept(*this);
solUnimplementedAssert(
*_assignment.rightHandSide().annotation().type == *_assignment.leftHandSide().annotation().type,
"Type conversion not yet implemented"
);
// solUnimplementedAssert(
// *_assignment.rightHandSide().annotation().type == *_assignment.leftHandSide().annotation().type,
// "Type conversion not yet implemented"
// );
// TODO proper lvalue handling
auto const& identifier = dynamic_cast<Identifier const&>(_assignment.leftHandSide());
string varName = m_context.variableName(dynamic_cast<VariableDeclaration const&>(*identifier.annotation().referencedDeclaration));
@ -98,11 +100,115 @@ void IRGeneratorForStatements::endVisit(BinaryOperation const& _binOp)
solUnimplementedAssert(false, "");
}
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
bool IRGeneratorForStatements::visit(FunctionCall const& _functionCall)
{
auto const& decl = dynamic_cast<VariableDeclaration const&>(
*_identifier.annotation().referencedDeclaration
);
m_code << "let " << m_context.variable(_identifier) << " := " << m_context.variableName(decl) << "\n";
solUnimplementedAssert(_functionCall.annotation().kind == FunctionCallKind::FunctionCall, "");
FunctionTypePointer functionType = dynamic_pointer_cast<FunctionType const>(_functionCall.expression().annotation().type);
TypePointers parameterTypes = functionType->parameterTypes();
vector<ASTPointer<Expression const>> const& callArguments = _functionCall.arguments();
vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.names();
if (!functionType->takesArbitraryParameters())
solAssert(callArguments.size() == parameterTypes.size(), "");
vector<ASTPointer<Expression const>> arguments;
if (callArgumentNames.empty())
// normal arguments
arguments = callArguments;
else
// named arguments
for (auto const& parameterName: functionType->parameterNames())
{
auto const it = std::find_if(callArgumentNames.cbegin(), callArgumentNames.cend(), [&](ASTPointer<ASTString> const& _argName) {
return *_argName == parameterName;
});
solAssert(it != callArgumentNames.cend(), "");
arguments.push_back(callArguments[std::distance(callArgumentNames.begin(), it)]);
}
solUnimplementedAssert(!functionType->bound(), "");
switch (functionType->kind())
{
case FunctionType::Kind::Internal:
{
vector<string> args;
for (unsigned i = 0; i < arguments.size(); ++i)
{
arguments[i]->accept(*this);
// TODO convert
//utils().convertType(*arguments[i]->annotation().type, *function.parameterTypes()[i]);
args.emplace_back(m_context.variable(*arguments[i]));
}
if (auto identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
{
solAssert(!functionType->bound(), "");
if (auto functionDef = dynamic_cast<FunctionDefinition const*>(identifier->annotation().referencedDeclaration))
{
// @TODO The function can very well return multiple vars.
m_code <<
"let " <<
m_context.variable(_functionCall) <<
" := " <<
m_context.virtualFunctionName(*functionDef) <<
"(" <<
joinHumanReadable(args) <<
")\n";
return false;
}
}
_functionCall.expression().accept(*this);
// @TODO The function can very well return multiple vars.
args = vector<string>{m_context.variable(_functionCall.expression())} + args;
m_code <<
"let " <<
m_context.variable(_functionCall) <<
" := " <<
m_context.internalDispatch(functionType->parameterTypes().size(), functionType->returnParameterTypes().size()) <<
"(" <<
joinHumanReadable(args) <<
")\n";
break;
}
default:
solUnimplemented("");
}
return false;
}
bool IRGeneratorForStatements::visit(Identifier const& _identifier)
{
Declaration const* declaration = _identifier.annotation().referencedDeclaration;
string value;
if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
value = to_string(m_context.virtualFunction(*functionDef).id());
else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
value = m_context.variableName(*varDecl);
else
solUnimplemented("");
m_code << "let " << m_context.variable(_identifier) << " := " << value << "\n";
return false;
}
bool IRGeneratorForStatements::visit(Literal const& _literal)
{
TypePointer type = _literal.annotation().type;
switch (type->category())
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
case Type::Category::Address:
m_code << "let " << m_context.variable(_literal) << " := " << toCompactHexWithPrefix(type->literalValue(&_literal)) << "\n";
break;
case Type::Category::StringLiteral:
solUnimplemented("");
break; // will be done during conversion
default:
solUnimplemented("Only integer, boolean and string literals implemented for now.");
}
return false;
}

View File

@ -47,7 +47,9 @@ public:
bool visit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Assignment const& _assignment) override;
void endVisit(BinaryOperation const& _binOp) override;
bool visit(FunctionCall const& _funCall) override;
bool visit(Identifier const& _identifier) override;
bool visit(Literal const& _literal) override;
private:
std::ostringstream m_code;

View File

@ -1 +1 @@
{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_5_f()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_5_f()\n {\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}}
{"contracts":{"A":{"C":{"irOptimized":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\nobject \"C_6\" {\n code {\n mstore(64, 128)\n codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n return(0, datasize(\"C_6_deployed\"))\n }\n object \"C_6_deployed\" {\n code {\n mstore(64, 128)\n if iszero(lt(calldatasize(), 4))\n {\n let selector := shift_right_224_unsigned(calldataload(0))\n switch selector\n case 0x26121ff0 {\n if callvalue()\n {\n revert(0, 0)\n }\n abi_decode_tuple_(4, calldatasize())\n fun_f_5()\n let memPos := allocateMemory(0)\n let memEnd := abi_encode_tuple__to__fromStack(memPos)\n return(memPos, sub(memEnd, memPos))\n }\n default {\n }\n }\n revert(0, 0)\n function abi_decode_tuple_(headStart, dataEnd)\n {\n if slt(sub(dataEnd, headStart), 0)\n {\n revert(0, 0)\n }\n }\n function abi_encode_tuple__to__fromStack(headStart) -> tail\n {\n tail := add(headStart, 0)\n }\n function allocateMemory(size) -> memPtr\n {\n memPtr := mload(64)\n let newFreePtr := add(memPtr, size)\n if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr))\n {\n revert(0, 0)\n }\n mstore(64, newFreePtr)\n }\n function fun_f_5()\n {\n }\n function shift_right_224_unsigned(value) -> newValue\n {\n newValue := shr(224, value)\n }\n }\n }\n}\n"}}},"sources":{"A":{"id":0}}}

View File

@ -1 +1 @@
{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_5_f()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\tfunction fun_5_f() {}\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}}
{"contracts":{"A":{"C":{"ir":"/*******************************************************\n * WARNING *\n * Solidity to Yul compilation is still EXPERIMENTAL *\n * It can result in LOSS OF FUNDS or worse *\n * !USE AT YOUR OWN RISK! *\n *******************************************************/\n\n\n\t\tobject \"C_6\" {\n\t\t\tcode {\n\t\t\t\tmstore(64, 128)\n\t\t\t\t\n\t\t\t\t\n\t\tcodecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))\n\t\treturn(0, datasize(\"C_6_deployed\"))\n\t\n\t\t\t\t\n\t\t\t}\n\t\t\tobject \"C_6_deployed\" {\n\t\t\t\tcode {\n\t\t\t\t\tmstore(64, 128)\n\t\t\t\t\t\n\t\tif iszero(lt(calldatasize(), 4))\n\t\t{\n\t\t\tlet selector := shift_right_224_unsigned(calldataload(0))\n\t\t\tswitch selector\n\t\t\t\n\t\t\tcase 0x26121ff0\n\t\t\t{\n\t\t\t\t// f()\n\t\t\t\tif callvalue() { revert(0, 0) }\n\t\t\t\t abi_decode_tuple_(4, calldatasize())\n\t\t\t\t fun_f_5()\n\t\t\t\tlet memPos := allocateMemory(0)\n\t\t\t\tlet memEnd := abi_encode_tuple__to__fromStack(memPos )\n\t\t\t\treturn(memPos, sub(memEnd, memPos))\n\t\t\t}\n\t\t\t\n\t\t\tdefault {}\n\t\t}\n\t\trevert(0, 0)\n\t\n\t\t\t\t\t\n\t\t\tfunction abi_decode_tuple_(headStart, dataEnd) {\n\t\t\t\tif slt(sub(dataEnd, headStart), 0) { revert(0, 0) }\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction abi_encode_tuple__to__fromStack(headStart ) -> tail {\n\t\t\t\ttail := add(headStart, 0)\n\t\t\t\t\n\t\t\t}\n\t\t\n\t\t\tfunction allocateMemory(size) -> memPtr {\n\t\t\t\tmemPtr := mload(64)\n\t\t\t\tlet newFreePtr := add(memPtr, size)\n\t\t\t\t// protect against overflow\n\t\t\t\tif or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }\n\t\t\t\tmstore(64, newFreePtr)\n\t\t\t}\n\t\t\nfunction fun_f_5() {\n\n}\n\n\t\t\t\tfunction shift_right_224_unsigned(value) -> newValue {\n\t\t\t\t\tnewValue := shr(224, value)\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t"}}},"sources":{"A":{"id":0}}}

View File

@ -0,0 +1,32 @@
contract X {
function f() public returns (uint x) {
x = g();
}
function g() public returns (uint x) {
x = 2;
}
}
contract C is X {
function f1() public returns (uint x) {
// direct call
x = g();
}
function f2() public returns (uint x) {
// call via base
x = f();
}
function f3() public returns (uint x) {
// explicit call via base
//x = super.g();
}
function g() public returns (uint x) {
x = 3;
}
}
// ===
// compileViaYul: true
// ----
// f() -> 3
// f1() -> 3
// f2() -> 3
// g() -> 3