Allow for per-parameter literalValues builtin functions

This commit is contained in:
Mathias Baumann 2020-04-06 14:47:44 +02:00
parent 582c754598
commit 5203503583
12 changed files with 75 additions and 52 deletions

View File

@ -255,14 +255,14 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
yulAssert(!_funCall.functionName.name.empty(), ""); yulAssert(!_funCall.functionName.name.empty(), "");
vector<YulString> const* parameterTypes = nullptr; vector<YulString> const* parameterTypes = nullptr;
vector<YulString> const* returnTypes = nullptr; vector<YulString> const* returnTypes = nullptr;
bool needsLiteralArguments = false; vector<bool> const* needsLiteralArguments = nullptr;
if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name)) if (BuiltinFunction const* f = m_dialect.builtin(_funCall.functionName.name))
{ {
parameterTypes = &f->parameters; parameterTypes = &f->parameters;
returnTypes = &f->returns; returnTypes = &f->returns;
if (f->literalArguments) if (f->literalArguments)
needsLiteralArguments = true; needsLiteralArguments = &f->literalArguments.value();
} }
else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{ else if (!m_currentScope->lookup(_funCall.functionName.name, GenericVisitor{
[&](Scope::Variable const&) [&](Scope::Variable const&)
@ -293,11 +293,13 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
); );
vector<YulString> argTypes; vector<YulString> argTypes;
for (auto const& arg: _funCall.arguments | boost::adaptors::reversed) for (size_t i = _funCall.arguments.size(); i > 0; i--)
{ {
Expression const& arg = _funCall.arguments[i - 1];
argTypes.emplace_back(expectExpression(arg)); argTypes.emplace_back(expectExpression(arg));
if (needsLiteralArguments) if (needsLiteralArguments && (*needsLiteralArguments)[i - 1])
{ {
if (!holds_alternative<Literal>(arg)) if (!holds_alternative<Literal>(arg))
typeError( typeError(

View File

@ -28,6 +28,7 @@
#include <vector> #include <vector>
#include <set> #include <set>
#include <optional>
namespace solidity::yul namespace solidity::yul
{ {
@ -46,8 +47,8 @@ struct BuiltinFunction
ControlFlowSideEffects controlFlowSideEffects; ControlFlowSideEffects controlFlowSideEffects;
/// If true, this is the msize instruction. /// If true, this is the msize instruction.
bool isMSize = false; bool isMSize = false;
/// If true, can only accept literals as arguments and they cannot be moved to variables. /// If set, same length as the arguments, if true at index i, the i'th argument has to be a literal which means it can't be moved to variables.
bool literalArguments = false; std::optional<std::vector<bool>> literalArguments;
}; };
struct Dialect: boost::noncopyable struct Dialect: boost::noncopyable

View File

@ -55,7 +55,7 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction); f.controlFlowSideEffects.terminates = evmasm::SemanticInformation::terminatesControlFlow(_instruction);
f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction); f.controlFlowSideEffects.reverts = evmasm::SemanticInformation::reverts(_instruction);
f.isMSize = _instruction == evmasm::Instruction::MSIZE; f.isMSize = _instruction == evmasm::Instruction::MSIZE;
f.literalArguments = false; f.literalArguments.reset();
f.instruction = _instruction; f.instruction = _instruction;
f.generateCode = [_instruction]( f.generateCode = [_instruction](
FunctionCall const&, FunctionCall const&,
@ -75,17 +75,22 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
size_t _params, size_t _params,
size_t _returns, size_t _returns,
SideEffects _sideEffects, SideEffects _sideEffects,
bool _literalArguments, vector<bool> _literalArguments,
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> _generateCode
) )
{ {
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
YulString name{std::move(_name)}; YulString name{std::move(_name)};
BuiltinFunctionForEVM f; BuiltinFunctionForEVM f;
f.name = name; f.name = name;
f.parameters.resize(_params); f.parameters.resize(_params);
f.returns.resize(_returns); f.returns.resize(_returns);
f.sideEffects = std::move(_sideEffects); f.sideEffects = std::move(_sideEffects);
f.literalArguments = _literalArguments; if (!_literalArguments.empty())
f.literalArguments = std::move(_literalArguments);
else
f.literalArguments.reset();
f.isMSize = false; f.isMSize = false;
f.instruction = {}; f.instruction = {};
f.generateCode = std::move(_generateCode); f.generateCode = std::move(_generateCode);
@ -107,7 +112,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
if (_objectAccess) if (_objectAccess)
{ {
builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, true, []( builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -128,7 +133,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
_assembly.appendDataSize(_context.subIDs.at(dataName)); _assembly.appendDataSize(_context.subIDs.at(dataName));
} }
})); }));
builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, true, []( builtins.emplace(createFunction("dataoffset", 1, 1, SideEffects{}, {true}, [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
@ -154,7 +159,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
3, 3,
0, 0,
SideEffects{false, false, false, false, true}, SideEffects{false, false, false, false, true},
false, {},
[]( [](
FunctionCall const&, FunctionCall const&,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
@ -262,7 +267,7 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
m_functions["popbool"_yulstring] = m_functions["pop"_yulstring]; m_functions["popbool"_yulstring] = m_functions["pop"_yulstring];
m_functions["popbool"_yulstring].name = "popbool"_yulstring; m_functions["popbool"_yulstring].name = "popbool"_yulstring;
m_functions["popbool"_yulstring].parameters = {"bool"_yulstring}; m_functions["popbool"_yulstring].parameters = {"bool"_yulstring};
m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, false, []( m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, {}, [](
FunctionCall const&, FunctionCall const&,
AbstractAssembly&, AbstractAssembly&,
BuiltinContext&, BuiltinContext&,
@ -272,7 +277,7 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
})); }));
m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring}; m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring};
m_functions["bool_to_u256"_yulstring].returns = {"u256"_yulstring}; m_functions["bool_to_u256"_yulstring].returns = {"u256"_yulstring};
m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, false, []( m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, {}, [](
FunctionCall const&, FunctionCall const&,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,

View File

@ -45,7 +45,7 @@ struct BuiltinContext
std::map<YulString, AbstractAssembly::SubID> subIDs; std::map<YulString, AbstractAssembly::SubID> subIDs;
}; };
struct BuiltinFunctionForEVM: BuiltinFunction struct BuiltinFunctionForEVM: public BuiltinFunction
{ {
std::optional<evmasm::Instruction> instruction; std::optional<evmasm::Instruction> instruction;
/// Function to generate code for the given function call and append it to the abstract /// Function to generate code for the given function call and append it to the abstract

View File

@ -136,11 +136,15 @@ wasm::Expression WasmCodeTransform::operator()(FunctionCall const& _call)
} }
typeConversionNeeded = true; typeConversionNeeded = true;
} }
else if (builtin->literalArguments) else if (builtin->literalArguments && contains(builtin->literalArguments.value(), true))
{ {
vector<wasm::Expression> literals; vector<wasm::Expression> literals;
for (auto const& arg: _call.arguments) for (size_t i = 0; i < _call.arguments.size(); i++)
literals.emplace_back(wasm::StringLiteral{std::get<Literal>(arg).value.str()}); if (builtin->literalArguments.value()[i])
literals.emplace_back(wasm::StringLiteral{std::get<Literal>(_call.arguments[i]).value.str()});
else
literals.emplace_back(visitReturnByValue(_call.arguments[i]));
return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)}; return wasm::BuiltinCall{_call.functionName.name.str(), std::move(literals)};
} }
else else

View File

@ -102,8 +102,8 @@ WasmDialect::WasmDialect()
m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true; m_functions["unreachable"_yulstring].controlFlowSideEffects.terminates = true;
m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true; m_functions["unreachable"_yulstring].controlFlowSideEffects.reverts = true;
addFunction("datasize", {i64}, {i64}, true, true); addFunction("datasize", {i64}, {i64}, true, {true});
addFunction("dataoffset", {i64}, {i64}, true, true); addFunction("dataoffset", {i64}, {i64}, true, {true});
addEthereumExternals(); addEthereumExternals();
} }
@ -204,7 +204,7 @@ void WasmDialect::addEthereumExternals()
f.controlFlowSideEffects = ext.controlFlowSideEffects; f.controlFlowSideEffects = ext.controlFlowSideEffects;
f.isMSize = false; f.isMSize = false;
f.sideEffects.invalidatesStorage = (ext.name == "storageStore"); f.sideEffects.invalidatesStorage = (ext.name == "storageStore");
f.literalArguments = false; f.literalArguments.reset();
} }
} }
@ -213,7 +213,7 @@ void WasmDialect::addFunction(
vector<YulString> _params, vector<YulString> _params,
vector<YulString> _returns, vector<YulString> _returns,
bool _movable, bool _movable,
bool _literalArguments std::vector<bool> _literalArguments
) )
{ {
YulString name{move(_name)}; YulString name{move(_name)};
@ -224,5 +224,8 @@ void WasmDialect::addFunction(
f.returns = std::move(_returns); f.returns = std::move(_returns);
f.sideEffects = _movable ? SideEffects{} : SideEffects::worst(); f.sideEffects = _movable ? SideEffects{} : SideEffects::worst();
f.isMSize = false; f.isMSize = false;
f.literalArguments = _literalArguments; if (!_literalArguments.empty())
f.literalArguments = std::move(_literalArguments);
else
f.literalArguments.reset();
} }

View File

@ -61,7 +61,7 @@ private:
std::vector<YulString> _params, std::vector<YulString> _params,
std::vector<YulString> _returns, std::vector<YulString> _returns,
bool _movable = true, bool _movable = true,
bool _literalArguments = false std::vector<bool> _literalArguments = std::vector<bool>{}
); );
std::map<YulString, BuiltinFunction> m_functions; std::map<YulString, BuiltinFunction> m_functions;

View File

@ -41,15 +41,24 @@ void WordSizeTransform::operator()(FunctionDefinition& _fd)
void WordSizeTransform::operator()(FunctionCall& _fc) void WordSizeTransform::operator()(FunctionCall& _fc)
{ {
vector<bool> const* literalArguments = nullptr;
if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name)) if (BuiltinFunction const* fun = m_inputDialect.builtin(_fc.functionName.name))
if (fun->literalArguments) if (fun->literalArguments)
literalArguments = &fun->literalArguments.value();
vector<Expression> newArgs;
for (size_t i = 0; i < _fc.arguments.size(); i++)
if (!literalArguments || !(*literalArguments)[i])
newArgs += expandValueToVector(_fc.arguments[i]);
else
{ {
for (Expression& arg: _fc.arguments) get<Literal>(_fc.arguments[i]).type = m_targetDialect.defaultType;
get<Literal>(arg).type = m_targetDialect.defaultType; newArgs.emplace_back(std::move(_fc.arguments[i]));
return;
} }
rewriteFunctionCallArguments(_fc.arguments); _fc.arguments = std::move(newArgs);
} }
void WordSizeTransform::operator()(If& _if) void WordSizeTransform::operator()(If& _if)
@ -97,9 +106,9 @@ void WordSizeTransform::operator()(Block& _block)
// Special handling for datasize and dataoffset - they will only need one variable. // Special handling for datasize and dataoffset - they will only need one variable.
if (BuiltinFunction const* f = m_inputDialect.builtin(std::get<FunctionCall>(*varDecl.value).functionName.name)) if (BuiltinFunction const* f = m_inputDialect.builtin(std::get<FunctionCall>(*varDecl.value).functionName.name))
if (f->literalArguments) if (f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring)
{ {
yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); yulAssert(f->literalArguments && f->literalArguments.value()[0], "");
yulAssert(varDecl.variables.size() == 1, ""); yulAssert(varDecl.variables.size() == 1, "");
auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name); auto newLhs = generateU64IdentifierNames(varDecl.variables[0].name);
vector<Statement> ret; vector<Statement> ret;
@ -158,9 +167,9 @@ void WordSizeTransform::operator()(Block& _block)
// Special handling for datasize and dataoffset - they will only need one variable. // Special handling for datasize and dataoffset - they will only need one variable.
if (BuiltinFunction const* f = m_inputDialect.builtin(std::get<FunctionCall>(*assignment.value).functionName.name)) if (BuiltinFunction const* f = m_inputDialect.builtin(std::get<FunctionCall>(*assignment.value).functionName.name))
if (f->literalArguments) if (f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring)
{ {
yulAssert(f->name == "datasize"_yulstring || f->name == "dataoffset"_yulstring, ""); yulAssert(f->literalArguments && f->literalArguments.value()[0], "");
yulAssert(assignment.variableNames.size() == 1, ""); yulAssert(assignment.variableNames.size() == 1, "");
auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name); auto newLhs = generateU64IdentifierNames(assignment.variableNames[0].name);
vector<Statement> ret; vector<Statement> ret;
@ -268,17 +277,6 @@ void WordSizeTransform::rewriteIdentifierList(vector<Identifier>& _ids)
); );
} }
void WordSizeTransform::rewriteFunctionCallArguments(vector<Expression>& _args)
{
iterateReplacing(
_args,
[&](Expression& _e) -> std::optional<vector<Expression>>
{
return expandValueToVector(_e);
}
);
}
vector<Statement> WordSizeTransform::handleSwitchInternal( vector<Statement> WordSizeTransform::handleSwitchInternal(
langutil::SourceLocation const& _location, langutil::SourceLocation const& _location,
vector<YulString> const& _splitExpressions, vector<YulString> const& _splitExpressions,

View File

@ -83,7 +83,6 @@ private:
void rewriteVarDeclList(std::vector<TypedName>&); void rewriteVarDeclList(std::vector<TypedName>&);
void rewriteIdentifierList(std::vector<Identifier>&); void rewriteIdentifierList(std::vector<Identifier>&);
void rewriteFunctionCallArguments(std::vector<Expression>&);
std::vector<Statement> handleSwitch(Switch& _switch); std::vector<Statement> handleSwitch(Switch& _switch);
std::vector<Statement> handleSwitchInternal( std::vector<Statement> handleSwitchInternal(

View File

@ -58,12 +58,21 @@ void CommonSubexpressionEliminator::visit(Expression& _e)
// If this is a function call to a function that requires literal arguments, // If this is a function call to a function that requires literal arguments,
// do not try to simplify there. // do not try to simplify there.
if (holds_alternative<FunctionCall>(_e)) if (holds_alternative<FunctionCall>(_e))
if (BuiltinFunction const* builtin = m_dialect.builtin(std::get<FunctionCall>(_e).functionName.name)) {
if (builtin->literalArguments) FunctionCall& funCall = std::get<FunctionCall>(_e);
if (BuiltinFunction const* builtin = m_dialect.builtin(funCall.functionName.name))
{
for (size_t i = funCall.arguments.size(); i > 0; i--)
// We should not modify function arguments that have to be literals // We should not modify function arguments that have to be literals
// Note that replacing the function call entirely is fine, // Note that replacing the function call entirely is fine,
// if the function call is movable. // if the function call is movable.
descend = false; if (!builtin->literalArguments || !builtin->literalArguments.value()[i - 1])
visit(funCall.arguments[i - 1]);
descend = false;
}
}
// We visit the inner expression first to first simplify inner expressions, // We visit the inner expression first to first simplify inner expressions,
// which hopefully allows more matches. // which hopefully allows more matches.

View File

@ -47,13 +47,15 @@ void ExpressionSplitter::run(OptimiserStepContext& _context, Block& _ast)
void ExpressionSplitter::operator()(FunctionCall& _funCall) void ExpressionSplitter::operator()(FunctionCall& _funCall)
{ {
vector<bool> const* literalArgs = nullptr;
if (BuiltinFunction const* builtin = m_dialect.builtin(_funCall.functionName.name)) if (BuiltinFunction const* builtin = m_dialect.builtin(_funCall.functionName.name))
if (builtin->literalArguments) if (builtin->literalArguments)
// We cannot outline function arguments that have to be literals literalArgs = &builtin->literalArguments.value();
return;
for (auto& arg: _funCall.arguments | boost::adaptors::reversed) for (size_t i = _funCall.arguments.size(); i > 0; i--)
outlineExpression(arg); if (!literalArgs || !(*literalArgs)[i - 1])
outlineExpression(_funCall.arguments[i - 1]);
} }
void ExpressionSplitter::operator()(If& _if) void ExpressionSplitter::operator()(If& _if)

View File

@ -539,7 +539,7 @@ BOOST_AUTO_TEST_CASE(builtins_analysis)
{ {
return _name == "builtin"_yulstring ? &f : nullptr; return _name == "builtin"_yulstring ? &f : nullptr;
} }
BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}, {}}; BuiltinFunction f{"builtin"_yulstring, vector<Type>(2), vector<Type>(3), {}, {}, false, {}};
}; };
SimpleDialect dialect; SimpleDialect dialect;