Merge remote-tracking branch 'origin/develop' into breaking

This commit is contained in:
chriseth 2020-05-04 18:46:45 +02:00
commit ecbf216354
51 changed files with 720 additions and 140 deletions

View File

@ -13,7 +13,22 @@ Compiler Features:
Bugfixes: Bugfixes:
### 0.6.7 (unreleased) ### 0.6.8 (unreleased)
Language Features:
Compiler Features:
Bugfixes:
### 0.6.7 (2020-05-04)
Language Features: Language Features:
* Add support for EIP 165 interface identifiers with `type(I).interfaceId`. * Add support for EIP 165 interface identifiers with `type(I).interfaceId`.
@ -22,6 +37,8 @@ Language Features:
Compiler Features: Compiler Features:
* Optimizer: Simplify repeated AND and OR operations. * Optimizer: Simplify repeated AND and OR operations.
* Standard Json Input: Support the prefix ``file://`` in the field ``urls``.
* Add option to specify optimization steps to be performed by Yul optimizer with `--yul-optimizations` in the commandline interface or `optimizer.details.yulDetails.optimizerSteps` in standard-json.
Bugfixes: Bugfixes:
* SMTChecker: Fix internal error when fixed points are used. * SMTChecker: Fix internal error when fixed points are used.
@ -29,8 +46,8 @@ Bugfixes:
* Type Checker: Disallow ``virtual`` and ``override`` for constructors. * Type Checker: Disallow ``virtual`` and ``override`` for constructors.
* Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking. * Type Checker: Fix several internal errors by performing size and recursiveness checks of types before the full type checking.
* Type Checker: Fix internal error when assigning to empty tuples. * Type Checker: Fix internal error when assigning to empty tuples.
* Type Checker: Fix internal error when applying unary operators to tuples with empty components.
* Type Checker: Perform recursiveness check on structs declared at the file level. * Type Checker: Perform recursiveness check on structs declared at the file level.
* Standard Json Input: Fix error when using prefix ``file://`` in the field ``urls``.
Build System: Build System:
* soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree. * soltest.sh: ``SOLIDITY_BUILD_DIR`` is no longer relative to ``REPO_ROOT`` to allow for build directories outside of the source tree.

View File

@ -1085,5 +1085,9 @@
"0.6.6": { "0.6.6": {
"bugs": [], "bugs": [],
"released": "2020-04-09" "released": "2020-04-09"
},
"0.6.7": {
"bugs": [],
"released": "2020-05-04"
} }
} }

View File

@ -52,3 +52,8 @@ facilitating patterns like the `Template method <https://en.wikipedia.org/wiki/T
Abstract contracts are useful in the same way that defining methods Abstract contracts are useful in the same way that defining methods
in an interface is useful. It is a way for the designer of the in an interface is useful. It is a way for the designer of the
abstract contract to say "any child of mine must implement this method". abstract contract to say "any child of mine must implement this method".
.. note::
Abstract contracts cannot override an implemented virtual function with an
unimplemented one.

View File

@ -82,8 +82,12 @@ explanatory purposes.
deduplicate: false, deduplicate: false,
cse: false, cse: false,
constantOptimizer: false, constantOptimizer: false,
yul: false, yul: true,
yulDetails: {} // Optional: Only present if "yul" is "true"
yulDetails: {
stackAllocation: false,
optimizerSteps: "dhfoDgvulfnTUtnIf..."
}
} }
}, },
metadata: { metadata: {

View File

@ -138,8 +138,8 @@ the ``sum`` function iterates over to sum all the values.
if (keyIndex > 0) if (keyIndex > 0)
return true; return true;
else { else {
self.keys.push();
keyIndex = self.keys.length; keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1; self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key; self.keys[keyIndex].key = key;
self.size++; self.size++;

View File

@ -231,7 +231,10 @@ Input Description
"yulDetails": { "yulDetails": {
// Improve allocation of stack slots for variables, can free up stack slots early. // Improve allocation of stack slots for variables, can free up stack slots early.
// Activated by default if the Yul optimizer is activated. // Activated by default if the Yul optimizer is activated.
"stackAllocation": true "stackAllocation": true,
// Select optimization steps to be applied.
// Optional, the optimizer will use the default sequence if omitted.
"optimizerSteps": "dhfoDgvulfnTUtnIf..."
} }
} }
}, },

View File

@ -885,13 +885,6 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a
| gaslimit() | | F | block gas limit of the current block | | gaslimit() | | F | block gas limit of the current block |
+-------------------------+-----+---+-----------------------------------------------------------------+ +-------------------------+-----+---+-----------------------------------------------------------------+
There are three additional functions, ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``,
which are used to access other parts of a Yul object.
``datasize`` and ``dataoffset`` can only take string literals (the names of other objects)
as arguments and return the size and offset in the data area, respectively.
For the EVM, the ``datacopy`` function is equivalent to ``codecopy``.
.. _yul-call-return-area: .. _yul-call-return-area:
.. note:: .. note::
@ -903,6 +896,32 @@ For the EVM, the ``datacopy`` function is equivalent to ``codecopy``.
The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``), The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``),
nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``. nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``.
In some internal dialects, there are additional functions:
datasize, dataoffset, datacopy
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The functions ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``,
are used to access other parts of a Yul object.
``datasize`` and ``dataoffset`` can only take string literals (the names of other objects)
as arguments and return the size and offset in the data area, respectively.
For the EVM, the ``datacopy`` function is equivalent to ``codecopy``.
setimmutable, loadimmutable
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are
used for the immutable mechanism in Solidity and do not nicely map to pur Yul.
The function ``setimmutable`` assumes that the runtime code of a contract
is currently copied to memory at offsot zero. The call to ``setimmutable("name", value)``
will store ``value`` at all points in memory that contain a call to
``loadimmutable("name")``.
.. _yul-object: .. _yul-object:
Specification of Yul Object Specification of Yul Object
@ -1015,3 +1034,56 @@ If you want to use Solidity in stand-alone Yul mode, you activate the optimizer
solc --strict-assembly --optimize solc --strict-assembly --optimize
In Solidity mode, the Yul optimizer is activated together with the regular optimizer. In Solidity mode, the Yul optimizer is activated together with the regular optimizer.
Optimization step sequence
--------------------------
By default the Yul optimizer applies its predefined sequence of optimization steps to the generated assembly.
You can override this sequence and supply your own using the `--yul-optimizations` option when compiling
in Solidity mode:
.. code-block:: sh
solc --optimize --ir-optimized --yul-optimizations 'dhfoD[xarrscLMcCTU]uljmul'
By enclosing part of the sequence in square brackets (`[]`) you tell the optimizer to repeatedly
apply that part until it no longer improves the size of the resulting assembly.
You can use brackets multiple times in a single sequence but they cannot be nested.
The following optimization steps are available:
============ ===============================
Abbreviation Full name
============ ===============================
f `BlockFlattener`
l `CircularReferencesPruner`
c `CommonSubexpressionEliminator`
C `ConditionalSimplifier`
U `ConditionalUnsimplifier`
n `ControlFlowSimplifier`
D `DeadCodeEliminator`
v `EquivalentFunctionCombiner`
e `ExpressionInliner`
j `ExpressionJoiner`
s `ExpressionSimplifier`
x `ExpressionSplitter`
I `ForLoopConditionIntoBody`
O `ForLoopConditionOutOfBody`
o `ForLoopInitRewriter`
i `FullInliner`
g `FunctionGrouper`
h `FunctionHoister`
T `LiteralRematerialiser`
L `LoadResolver`
M `LoopInvariantCodeMotion`
r `RedundantAssignEliminator`
m `Rematerialiser`
V `SSAReverser`
a `SSATransform`
t `StructuralSimplifier`
u `UnusedPruner`
d `VarDeclInitializer`
============ ===============================
Some steps depend on properties ensured by `BlockFlattener`, `FunctionGrouper`, `ForLoopInitRewriter`.
For this reason the Yul optimizer always applies them before applying any steps supplied by the user.

View File

@ -1480,13 +1480,13 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op); TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op);
if (!t) if (!t)
{ {
m_errorReporter.typeError( string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->toString();
_operation.location(), if (modifying)
"Unary operator " + // Cannot just report the error, ignore the unary operator, and continue,
string(TokenTraits::toString(op)) + // because the sub-expression was already processed with requireLValue()
" cannot be applied to type " + m_errorReporter.fatalTypeError(_operation.location(), description);
subExprType->toString() else
); m_errorReporter.typeError(_operation.location(), description);
t = subExprType; t = subExprType;
} }
_operation.annotation().type = t; _operation.annotation().type = t;

View File

@ -733,7 +733,10 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
{ {
case Type::Category::Bool: case Type::Category::Bool:
case Type::Category::Address: case Type::Category::Address:
solAssert(*type == *variable->annotation().type, ""); // Either both the literal and the variable are bools, or they are both addresses.
// If they are both bools, comparing category is the same as comparing the types.
// If they are both addresses, compare category so that payable/nonpayable is not compared.
solAssert(type->category() == variable->annotation().type->category(), "");
value = type->literalValue(literal); value = type->literalValue(literal);
break; break;
case Type::Category::StringLiteral: case Type::Category::StringLiteral:

View File

@ -22,6 +22,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h> #include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/codegen/ABIFunctions.h> #include <libsolidity/codegen/ABIFunctions.h>
#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h> #include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h> #include <libsolidity/ast/TypeProvider.h>
@ -76,6 +77,36 @@ IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const&
return m_localVariables.at(&_varDecl); return m_localVariables.at(&_varDecl);
} }
void IRGenerationContext::registerImmutableVariable(VariableDeclaration const& _variable)
{
solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable.");
solUnimplementedAssert(
_variable.annotation().type->isValueType(),
"Only immutable variables of value type are supported."
);
solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset.");
m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory;
solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap.");
*m_reservedMemory += _variable.annotation().type->memoryHeadSize();
}
size_t IRGenerationContext::immutableMemoryOffset(VariableDeclaration const& _variable) const
{
solAssert(
m_immutableVariables.count(&_variable),
"Unknown immutable variable: " + _variable.name()
);
return m_immutableVariables.at(&_variable);
}
size_t IRGenerationContext::reservedMemory()
{
solAssert(m_reservedMemory.has_value(), "Reserved memory was used before.");
size_t reservedMemory = *m_reservedMemory;
m_reservedMemory = std::nullopt;
return reservedMemory;
}
void IRGenerationContext::addStateVariable( void IRGenerationContext::addStateVariable(
VariableDeclaration const& _declaration, VariableDeclaration const& _declaration,
u256 _storageOffset, u256 _storageOffset,

View File

@ -81,6 +81,17 @@ public:
bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); }
IRVariable const& localVariable(VariableDeclaration const& _varDecl); IRVariable const& localVariable(VariableDeclaration const& _varDecl);
/// Registers an immutable variable of the contract.
/// Should only be called at construction time.
void registerImmutableVariable(VariableDeclaration const& _varDecl);
/// @returns the reserved memory for storing the value of the
/// immutable @a _variable during contract creation.
size_t immutableMemoryOffset(VariableDeclaration const& _variable) const;
/// @returns the reserved memory and resets it to mark it as used.
/// Intended to be used only once for initializing the free memory pointer
/// to after the area used for immutables.
size_t reservedMemory();
void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset);
bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); }
std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const
@ -123,6 +134,12 @@ private:
OptimiserSettings m_optimiserSettings; OptimiserSettings m_optimiserSettings;
ContractDefinition const* m_mostDerivedContract = nullptr; ContractDefinition const* m_mostDerivedContract = nullptr;
std::map<VariableDeclaration const*, IRVariable> m_localVariables; std::map<VariableDeclaration const*, IRVariable> m_localVariables;
/// Memory offsets reserved for the values of immutable variables during contract creation.
/// This map is empty in the runtime context.
std::map<VariableDeclaration const*, size_t> m_immutableVariables;
/// Total amount of reserved memory. Reserved memory is used to store
/// immutable variables during contract creation.
std::optional<size_t> m_reservedMemory = {0};
/// Storage offsets of state variables /// Storage offsets of state variables
std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables;
MultiUseYulFunctionCollector m_functions; MultiUseYulFunctionCollector m_functions;

View File

@ -114,6 +114,8 @@ string IRGenerator::generate(
)"); )");
resetContext(_contract); resetContext(_contract);
for (VariableDeclaration const* var: ContractType(_contract).immutableVariables())
m_context.registerImmutableVariable(*var);
t("CreationObject", m_context.creationObjectName(_contract)); t("CreationObject", m_context.creationObjectName(_contract));
t("memoryInit", memoryInit()); t("memoryInit", memoryInit());
@ -142,6 +144,7 @@ string IRGenerator::generate(
t("subObjects", subObjectSources(m_context.subObjectsCreated())); t("subObjects", subObjectSources(m_context.subObjectsCreated()));
resetContext(_contract); resetContext(_contract);
// Do not register immutables to avoid assignment.
t("RuntimeObject", m_context.runtimeObjectName(_contract)); t("RuntimeObject", m_context.runtimeObjectName(_contract));
t("dispatch", dispatchRoutine(_contract)); t("dispatch", dispatchRoutine(_contract));
generateQueuedFunctions(); generateQueuedFunctions();
@ -200,7 +203,6 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
Type const* type = _varDecl.annotation().type; Type const* type = _varDecl.annotation().type;
solAssert(!_varDecl.isConstant(), ""); solAssert(!_varDecl.isConstant(), "");
solAssert(!_varDecl.immutable(), "");
solAssert(_varDecl.isStateVariable(), ""); solAssert(_varDecl.isStateVariable(), "");
if (auto const* mappingType = dynamic_cast<MappingType const*>(type)) if (auto const* mappingType = dynamic_cast<MappingType const*>(type))
@ -254,6 +256,20 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
solUnimplementedAssert(type->isValueType(), ""); solUnimplementedAssert(type->isValueType(), "");
return m_context.functionCollector().createFunction(functionName, [&]() { return m_context.functionCollector().createFunction(functionName, [&]() {
if (_varDecl.immutable())
{
solUnimplementedAssert(type->sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>() -> rval {
rval := loadimmutable("<id>")
}
)")
("functionName", functionName)
("id", to_string(_varDecl.id()))
.render();
}
else
{
pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl); pair<u256, unsigned> slot_offset = m_context.storageLocationOfVariable(_varDecl);
return Whiskers(R"( return Whiskers(R"(
@ -265,6 +281,7 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) ("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false))
("slot", slot_offset.first.str()) ("slot", slot_offset.first.str())
.render(); .render();
}
}); });
} }
} }
@ -325,7 +342,7 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract)
{ {
IRGeneratorForStatements generator{m_context, m_utils}; IRGeneratorForStatements generator{m_context, m_utils};
for (VariableDeclaration const* variable: _contract.stateVariables()) for (VariableDeclaration const* variable: _contract.stateVariables())
if (!variable->isConstant() && !variable->immutable()) if (!variable->isConstant())
generator.initializeStateVar(*variable); generator.initializeStateVar(*variable);
return generator.code(); return generator.code();
@ -391,10 +408,41 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra
string IRGenerator::deployCode(ContractDefinition const& _contract) string IRGenerator::deployCode(ContractDefinition const& _contract)
{ {
Whiskers t(R"X( Whiskers t(R"X(
<#loadImmutables>
let <var> := mload(<memoryOffset>)
</loadImmutables>
codecopy(0, dataoffset("<object>"), datasize("<object>")) codecopy(0, dataoffset("<object>"), datasize("<object>"))
<#storeImmutables>
setimmutable("<immutableName>", <var>)
</storeImmutables>
return(0, datasize("<object>")) return(0, datasize("<object>"))
)X"); )X");
t("object", m_context.runtimeObjectName(_contract)); t("object", m_context.runtimeObjectName(_contract));
vector<map<string, string>> loadImmutables;
vector<map<string, string>> storeImmutables;
for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables())
{
solUnimplementedAssert(immutable->type()->isValueType(), "");
solUnimplementedAssert(immutable->type()->sizeOnStack() == 1, "");
string yulVar = m_context.newYulVariable();
loadImmutables.emplace_back(map<string, string>{
{"var"s, yulVar},
{"memoryOffset"s, to_string(m_context.immutableMemoryOffset(*immutable))}
});
storeImmutables.emplace_back(map<string, string>{
{"var"s, yulVar},
{"immutableName"s, to_string(immutable->id())}
});
}
t("loadImmutables", std::move(loadImmutables));
// reverse order to ease stack strain
reverse(storeImmutables.begin(), storeImmutables.end());
t("storeImmutables", std::move(storeImmutables));
return t.render(); return t.render();
} }
@ -489,9 +537,9 @@ string IRGenerator::memoryInit()
// and thus can assume all memory to be zero, including the contents of // and thus can assume all memory to be zero, including the contents of
// the "zero memory area" (the position CompilerUtils::zeroPointer points to). // the "zero memory area" (the position CompilerUtils::zeroPointer points to).
return return
Whiskers{"mstore(<memPtr>, <generalPurposeStart>)"} Whiskers{"mstore(<memPtr>, <freeMemoryStart>)"}
("memPtr", to_string(CompilerUtils::freeMemoryPointer)) ("memPtr", to_string(CompilerUtils::freeMemoryPointer))
("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) ("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory()))
.render(); .render();
} }

View File

@ -140,20 +140,21 @@ string IRGeneratorForStatements::code() const
void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl) void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl)
{ {
solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable."); solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable.");
solAssert(!_varDecl.isConstant(), ""); solAssert(!_varDecl.isConstant(), "");
solAssert(!_varDecl.immutable(), ""); if (!_varDecl.value())
if (_varDecl.value()) return;
{
_varDecl.value()->accept(*this); _varDecl.value()->accept(*this);
writeToLValue(IRLValue{ writeToLValue(
*_varDecl.annotation().type, _varDecl.immutable() ?
IRLValue::Storage{ IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first), util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first),
m_context.storageLocationOfVariable(_varDecl).second m_context.storageLocationOfVariable(_varDecl).second
} }},
}, *_varDecl.value()); *_varDecl.value()
} );
} }
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl) void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
@ -584,7 +585,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
case FunctionType::Kind::Internal: case FunctionType::Kind::Internal:
{ {
vector<string> args; vector<string> args;
for (unsigned i = 0; i < arguments.size(); ++i) for (size_t i = 0; i < arguments.size(); ++i)
if (functionType->takesArbitraryParameters()) if (functionType->takesArbitraryParameters())
args.emplace_back(IRVariable(*arguments[i]).commaSeparatedList()); args.emplace_back(IRVariable(*arguments[i]).commaSeparatedList());
else else
@ -730,6 +731,16 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break; break;
} }
case FunctionType::Kind::Revert:
{
solAssert(arguments.size() == parameterTypes.size(), "");
if (arguments.empty())
m_code << "revert(0, 0)\n";
else
solUnimplementedAssert(false, "");
break;
}
// Array creation using new // Array creation using new
case FunctionType::Kind::ObjectCreation: case FunctionType::Kind::ObjectCreation:
{ {
@ -818,15 +829,43 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
{ {
break; break;
} }
case FunctionType::Kind::GasLeft: case FunctionType::Kind::AddMod:
case FunctionType::Kind::MulMod:
{ {
define(_functionCall) << "gas()\n"; static map<FunctionType::Kind, string> functions = {
{FunctionType::Kind::AddMod, "addmod"},
{FunctionType::Kind::MulMod, "mulmod"},
};
solAssert(functions.find(functionType->kind()) != functions.end(), "");
solAssert(arguments.size() == 3 && parameterTypes.size() == 3, "");
IRVariable modulus(m_context.newYulVariable(), *(parameterTypes[2]));
define(modulus, *arguments[2]);
Whiskers templ("if iszero(<modulus>) { invalid() }\n");
m_code << templ("modulus", modulus.name()).render();
string args;
for (size_t i = 0; i < 2; ++i)
args += expressionAsType(*arguments[i], *(parameterTypes[i])) + ", ";
args += modulus.name();
define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
break; break;
} }
case FunctionType::Kind::GasLeft:
case FunctionType::Kind::Selfdestruct: case FunctionType::Kind::Selfdestruct:
case FunctionType::Kind::BlockHash:
{ {
solAssert(arguments.size() == 1, ""); static map<FunctionType::Kind, string> functions = {
define(_functionCall) << "selfdestruct(" << expressionAsType(*arguments.front(), *parameterTypes.front()) << ")\n"; {FunctionType::Kind::GasLeft, "gas"},
{FunctionType::Kind::Selfdestruct, "selfdestruct"},
{FunctionType::Kind::BlockHash, "blockhash"},
};
solAssert(functions.find(functionType->kind()) != functions.end(), "");
string args;
for (size_t i = 0; i < arguments.size(); ++i)
args += (args.empty() ? "" : ", ") + expressionAsType(*arguments[i], *(parameterTypes[i]));
define(_functionCall) << functions[functionType->kind()] << "(" << args << ")\n";
break; break;
} }
case FunctionType::Kind::Log0: case FunctionType::Kind::Log0:
@ -908,6 +947,34 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
break; break;
} }
case FunctionType::Kind::Send:
case FunctionType::Kind::Transfer:
{
solAssert(arguments.size() == 1 && parameterTypes.size() == 1, "");
string address{IRVariable(_functionCall.expression()).part("address").name()};
string value{expressionAsType(*arguments[0], *(parameterTypes[0]))};
Whiskers templ(R"(
let <gas> := 0
if iszero(<value>) { <gas> := <callStipend> }
let <success> := call(<gas>, <address>, <value>, 0, 0, 0, 0)
<?isTransfer>
if iszero(<success>) { <forwardingRevert>() }
</isTransfer>
)");
templ("gas", m_context.newYulVariable());
templ("callStipend", toString(evmasm::GasCosts::callStipend));
templ("address", address);
templ("value", value);
if (functionType->kind() == FunctionType::Kind::Transfer)
templ("success", m_context.newYulVariable());
else
templ("success", IRVariable(_functionCall).commaSeparatedList());
templ("isTransfer", functionType->kind() == FunctionType::Kind::Transfer);
templ("forwardingRevert", m_utils.forwardingRevertFunction());
m_code << templ.render();
break;
}
default: default:
solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented"); solUnimplemented("FunctionKind " + toString(static_cast<int>(functionType->kind())) + " not yet implemented");
} }
@ -1479,8 +1546,12 @@ void IRGeneratorForStatements::handleVariableReference(
// If the value is visited twice, `defineExpression` is called twice on // If the value is visited twice, `defineExpression` is called twice on
// the same expression. // the same expression.
solUnimplementedAssert(!_variable.isConstant(), ""); solUnimplementedAssert(!_variable.isConstant(), "");
solUnimplementedAssert(!_variable.immutable(), ""); if (_variable.isStateVariable() && _variable.immutable())
if (m_context.isLocalVariable(_variable)) setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type,
IRLValue::Immutable{&_variable}
});
else if (m_context.isLocalVariable(_variable))
setLValue(_referencingExpression, IRLValue{ setLValue(_referencingExpression, IRLValue{
*_variable.annotation().type, *_variable.annotation().type,
IRLValue::Stack{m_context.localVariable(_variable)} IRLValue::Stack{m_context.localVariable(_variable)}
@ -1901,6 +1972,18 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
} }
}, },
[&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); }, [&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); },
[&](IRLValue::Immutable const& _immutable)
{
solUnimplementedAssert(_lvalue.type.isValueType(), "");
solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, "");
solAssert(_lvalue.type == *_immutable.variable->type(), "");
size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable);
IRVariable prepared(m_context.newYulVariable(), _lvalue.type);
define(prepared, _value);
m_code << "mstore(" << to_string(memOffset) << ", " << prepared.commaSeparatedList() << ")\n";
},
[&](IRLValue::Tuple const& _tuple) { [&](IRLValue::Tuple const& _tuple) {
auto components = std::move(_tuple.components); auto components = std::move(_tuple.components);
for (size_t i = 0; i < components.size(); i++) for (size_t i = 0; i < components.size(); i++)
@ -1956,6 +2039,12 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue)
[&](IRLValue::Stack const& _stack) { [&](IRLValue::Stack const& _stack) {
define(result, _stack.variable); define(result, _stack.variable);
}, },
[&](IRLValue::Immutable const& _immutable) {
solUnimplementedAssert(_lvalue.type.isValueType(), "");
solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, "");
solAssert(_lvalue.type == *_immutable.variable->type(), "");
define(result) << "loadimmutable(\"" << to_string(_immutable.variable->id()) << "\")\n";
},
[&](IRLValue::Tuple const&) { [&](IRLValue::Tuple const&) {
solAssert(false, "Attempted to read from tuple lvalue."); solAssert(false, "Attempted to read from tuple lvalue.");
} }

View File

@ -35,6 +35,10 @@ struct IRLValue
{ {
IRVariable variable; IRVariable variable;
}; };
struct Immutable
{
VariableDeclaration const* variable = nullptr;
};
struct Storage struct Storage
{ {
std::string const slot; std::string const slot;
@ -59,7 +63,7 @@ struct IRLValue
{ {
std::vector<std::optional<IRLValue>> components; std::vector<std::optional<IRLValue>> components;
}; };
std::variant<Stack, Storage, Memory, Tuple> kind; std::variant<Stack, Immutable, Storage, Memory, Tuple> kind;
}; };
} }

View File

@ -306,7 +306,11 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
_funCall.functionName.location, _funCall.functionName.location,
"Function expects direct literals as arguments." "Function expects direct literals as arguments."
); );
else if (!m_dataNames.count(std::get<Literal>(arg).value)) else if (
_funCall.functionName.name.str() == "datasize" ||
_funCall.functionName.name.str() == "dataoffset"
)
if (!m_dataNames.count(std::get<Literal>(arg).value))
typeError( typeError(
_funCall.functionName.location, _funCall.functionName.location,
"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"." "Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"."

View File

@ -105,6 +105,11 @@ public:
virtual void appendDataSize(SubID _sub) = 0; virtual void appendDataSize(SubID _sub) = 0;
/// Appends the given data to the assembly and returns its ID. /// Appends the given data to the assembly and returns its ID.
virtual SubID appendData(bytes const& _data) = 0; virtual SubID appendData(bytes const& _data) = 0;
/// Appends loading an immutable variable.
virtual void appendImmutable(std::string const& _identifier) = 0;
/// Appends an assignment to an immutable variable.
virtual void appendImmutableAssignment(std::string const& _identifier) = 0;
}; };
enum class IdentifierContext { LValue, RValue, VariableDeclaration }; enum class IdentifierContext { LValue, RValue, VariableDeclaration };

View File

@ -172,6 +172,16 @@ AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data)
return subID; return subID;
} }
void EthAssemblyAdapter::appendImmutable(std::string const& _identifier)
{
m_assembly.appendImmutable(_identifier);
}
void EthAssemblyAdapter::appendImmutableAssignment(std::string const& _identifier)
{
m_assembly.appendImmutableAssignment(_identifier);
}
EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag) EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag)
{ {
u256 id = _tag.data(); u256 id = _tag.data();

View File

@ -61,6 +61,9 @@ public:
void appendDataSize(SubID _sub) override; void appendDataSize(SubID _sub) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;
private: private:
static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag); static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag);

View File

@ -215,6 +215,16 @@ AbstractAssembly::SubID EVMAssembly::appendData(bytes const&)
yulAssert(false, "Data not implemented."); yulAssert(false, "Data not implemented.");
} }
void EVMAssembly::appendImmutable(std::string const&)
{
yulAssert(false, "loadimmutable not implemented.");
}
void EVMAssembly::appendImmutableAssignment(std::string const&)
{
yulAssert(false, "setimmutable not implemented.");
}
void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) void EVMAssembly::updateReference(size_t pos, size_t size, u256 value)
{ {
yulAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); yulAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, "");

View File

@ -83,6 +83,9 @@ public:
void appendDataSize(SubID _sub) override; void appendDataSize(SubID _sub) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;
/// Resolves references inside the bytecode and returns the linker object. /// Resolves references inside the bytecode and returns the linker object.
evmasm::LinkerObject finalize(); evmasm::LinkerObject finalize();

View File

@ -257,13 +257,9 @@ void CodeTransform::operator()(FunctionCall const& _call)
yulAssert(m_scope, ""); yulAssert(m_scope, "");
if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name)) if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name))
{ builtin->generateCode(_call, m_assembly, m_builtinContext, [&](Expression const& _expression) {
builtin->generateCode(_call, m_assembly, m_builtinContext, [&]() { visitExpression(_expression);
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
visitExpression(arg);
m_assembly.setSourceLocation(_call.location);
}); });
}
else else
{ {
m_assembly.setSourceLocation(_call.location); m_assembly.setSourceLocation(_call.location);

View File

@ -41,6 +41,20 @@ using namespace solidity::util;
namespace namespace
{ {
void visitArguments(
AbstractAssembly& _assembly,
FunctionCall const& _call,
function<void(Expression const&)> _visitExpression
)
{
for (auto const& arg: _call.arguments | boost::adaptors::reversed)
_visitExpression(arg);
_assembly.setSourceLocation(_call.location);
}
pair<YulString, BuiltinFunctionForEVM> createEVMFunction( pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
string const& _name, string const& _name,
evmasm::Instruction _instruction evmasm::Instruction _instruction
@ -58,12 +72,12 @@ pair<YulString, BuiltinFunctionForEVM> createEVMFunction(
f.literalArguments.reset(); f.literalArguments.reset();
f.instruction = _instruction; f.instruction = _instruction;
f.generateCode = [_instruction]( f.generateCode = [_instruction](
FunctionCall const&, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,
std::function<void()> _visitArguments std::function<void(Expression const&)> _visitExpression
) { ) {
_visitArguments(); visitArguments(_assembly, _call, _visitExpression);
_assembly.appendInstruction(_instruction); _assembly.appendInstruction(_instruction);
}; };
@ -76,7 +90,7 @@ pair<YulString, BuiltinFunctionForEVM> createFunction(
size_t _returns, size_t _returns,
SideEffects _sideEffects, SideEffects _sideEffects,
vector<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(Expression const&)>)> _generateCode
) )
{ {
solAssert(_literalArguments.size() == _params || _literalArguments.empty(), ""); solAssert(_literalArguments.size() == _params || _literalArguments.empty(), "");
@ -116,7 +130,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
function<void()> std::function<void(Expression const&)>
) { ) {
yulAssert(_context.currentObject, "No object available."); yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, ""); yulAssert(_call.arguments.size() == 1, "");
@ -137,7 +151,7 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext& _context, BuiltinContext& _context,
std::function<void()> std::function<void(Expression const&)>
) { ) {
yulAssert(_context.currentObject, "No object available."); yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, ""); yulAssert(_call.arguments.size() == 1, "");
@ -161,21 +175,58 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
SideEffects{false, false, false, false, true}, SideEffects{false, false, false, false, true},
{}, {},
[]( [](
FunctionCall const&, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,
std::function<void()> _visitArguments std::function<void(Expression const&)> _visitExpression
) { ) {
_visitArguments(); visitArguments(_assembly, _call, _visitExpression);
_assembly.appendInstruction(evmasm::Instruction::CODECOPY); _assembly.appendInstruction(evmasm::Instruction::CODECOPY);
} }
)); ));
builtins.emplace(createFunction(
"setimmutable",
2,
0,
SideEffects{false, false, false, false, true},
{true, false},
[](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void(Expression const&)> _visitExpression
) {
solAssert(_call.arguments.size() == 2, "");
_visitExpression(_call.arguments[1]);
_assembly.setSourceLocation(_call.location);
YulString identifier = std::get<Literal>(_call.arguments.front()).value;
_assembly.appendImmutableAssignment(identifier.str());
}
));
builtins.emplace(createFunction(
"loadimmutable",
1,
1,
SideEffects{},
{true},
[](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void(Expression const&)>
) {
solAssert(_call.arguments.size() == 1, "");
_assembly.appendImmutable(std::get<Literal>(_call.arguments.front()).value.str());
}
));
} }
return builtins; return builtins;
} }
} }
EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess): EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess):
m_objectAccess(_objectAccess), m_objectAccess(_objectAccess),
m_evmVersion(_evmVersion), m_evmVersion(_evmVersion),
@ -268,23 +319,23 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA
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, {}, {}, []( m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, {}, [](
FunctionCall const&, FunctionCall const& _call,
AbstractAssembly&, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,
std::function<void()> _visitArguments std::function<void(Expression const&)> _visitExpression
) { ) {
_visitArguments(); visitArguments(_assembly, _call, _visitExpression);
})); }));
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, {}, {}, []( m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, {}, [](
FunctionCall const&, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,
std::function<void()> _visitArguments std::function<void(Expression const&)> _visitExpression
) { ) {
// A value larger than 1 causes an invalid instruction. // A value larger than 1 causes an invalid instruction.
_visitArguments(); visitArguments(_assembly, _call, _visitExpression);
_assembly.appendConstant(2); _assembly.appendConstant(2);
_assembly.appendInstruction(evmasm::Instruction::DUP2); _assembly.appendInstruction(evmasm::Instruction::DUP2);
_assembly.appendInstruction(evmasm::Instruction::LT); _assembly.appendInstruction(evmasm::Instruction::LT);

View File

@ -23,6 +23,7 @@
#include <libyul/Dialect.h> #include <libyul/Dialect.h>
#include <libyul/backends/evm/AbstractAssembly.h> #include <libyul/backends/evm/AbstractAssembly.h>
#include <libyul/AsmData.h>
#include <liblangutil/EVMVersion.h> #include <liblangutil/EVMVersion.h>
#include <map> #include <map>
@ -49,9 +50,9 @@ 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
/// assembly. The fourth parameter is called to visit (and generate code for) the arguments /// assembly. The fourth parameter is called to visit (and generate code for) the given
/// from right to left. /// argument.
std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void()>)> generateCode; std::function<void(FunctionCall const&, AbstractAssembly&, BuiltinContext&, std::function<void(Expression const&)>)> generateCode;
}; };

View File

@ -19,10 +19,14 @@
*/ */
#include <libyul/backends/evm/NoOutputAssembly.h> #include <libyul/backends/evm/NoOutputAssembly.h>
#include <libyul/Exceptions.h> #include <libyul/Exceptions.h>
#include <libevmasm/Instruction.h> #include <libevmasm/Instruction.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::yul; using namespace solidity::yul;
@ -142,6 +146,17 @@ AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&)
return 1; return 1;
} }
void NoOutputAssembly::appendImmutable(std::string const&)
{
yulAssert(false, "loadimmutable not implemented.");
}
void NoOutputAssembly::appendImmutableAssignment(std::string const&)
{
yulAssert(false, "setimmutable not implemented.");
}
NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom):
EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess()) EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess())
{ {
@ -149,9 +164,11 @@ NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom):
{ {
size_t parameters = fun.second.parameters.size(); size_t parameters = fun.second.parameters.size();
size_t returns = fun.second.returns.size(); size_t returns = fun.second.returns.size();
fun.second.generateCode = [=](FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, std::function<void()> _visitArguments) fun.second.generateCode = [=](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, std::function<void(Expression const&)> _visitExpression)
{ {
_visitArguments(); for (auto const& arg: _call.arguments | boost::adaptors::reversed)
_visitExpression(arg);
for (size_t i = 0; i < parameters; i++) for (size_t i = 0; i < parameters; i++)
_assembly.appendInstruction(evmasm::Instruction::POP); _assembly.appendInstruction(evmasm::Instruction::POP);

View File

@ -71,6 +71,9 @@ public:
void appendDataSize(SubID _sub) override; void appendDataSize(SubID _sub) override;
SubID appendData(bytes const& _data) override; SubID appendData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;
private: private:
bool m_evm15 = false; ///< if true, switch to evm1.5 mode bool m_evm15 = false; ///< if true, switch to evm1.5 mode
int m_stackHeight = 0; int m_stackHeight = 0;

View File

@ -14,6 +14,7 @@ object \"C_6\" {
constructor_C_6() constructor_C_6()
codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\"))
return(0, datasize(\"C_6_deployed\")) return(0, datasize(\"C_6_deployed\"))
function constructor_C_6() { function constructor_C_6() {

View File

@ -14,6 +14,7 @@ object \"C_10\" {
constructor_C_10() constructor_C_10()
codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\"))
return(0, datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\"))
function constructor_C_10() { function constructor_C_10() {

View File

@ -14,6 +14,7 @@ object \"C_10\" {
constructor_C_10() constructor_C_10()
codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\"))
return(0, datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\"))
function constructor_C_10() { function constructor_C_10() {

View File

@ -14,6 +14,7 @@ object \"C_10\" {
constructor_C_10() constructor_C_10()
codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\"))
return(0, datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\"))
function constructor_C_10() { function constructor_C_10() {

View File

@ -14,6 +14,7 @@ object \"C_10\" {
constructor_C_10() constructor_C_10()
codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\"))
return(0, datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\"))
function constructor_C_10() { function constructor_C_10() {

View File

@ -14,6 +14,7 @@ object \"C_10\" {
constructor_C_10() constructor_C_10()
codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\"))
return(0, datasize(\"C_10_deployed\")) return(0, datasize(\"C_10_deployed\"))
function constructor_C_10() { function constructor_C_10() {

View File

@ -45,6 +45,9 @@ def AND(x, y):
def OR(x, y): def OR(x, y):
return x | y return x | y
def NOT(x):
return ~(x)
def SHL(x, y): def SHL(x, y):
return y << x return y << x

View File

@ -50,6 +50,7 @@ using namespace solidity::langutil;
#define ALSO_VIA_YUL(CODE) \ #define ALSO_VIA_YUL(CODE) \
{ \ { \
{ CODE } \ { CODE } \
reset(); \
m_compileViaYul = true; \ m_compileViaYul = true; \
{ CODE } \ { CODE } \
} }
@ -1058,11 +1059,13 @@ BOOST_AUTO_TEST_CASE(send_ether)
} }
} }
)"; )";
u256 amount(130); ALSO_VIA_YUL(
u256 amount(250);
compileAndRun(sourceCode, amount + 1); compileAndRun(sourceCode, amount + 1);
u160 address(23); u160 address(23);
ABI_CHECK(callContractFunction("a(address,uint256)", address, amount), encodeArgs(1)); ABI_CHECK(callContractFunction("a(address,uint256)", address, amount), encodeArgs(1));
BOOST_CHECK_EQUAL(balanceAt(address), amount); BOOST_CHECK_EQUAL(balanceAt(address), amount);
)
} }
BOOST_AUTO_TEST_CASE(transfer_ether) BOOST_AUTO_TEST_CASE(transfer_ether)
@ -1088,6 +1091,7 @@ BOOST_AUTO_TEST_CASE(transfer_ether)
} }
} }
)"; )";
ALSO_VIA_YUL(
compileAndRun(sourceCode, 0, "B"); compileAndRun(sourceCode, 0, "B");
u160 const nonPayableRecipient = m_contractAddress; u160 const nonPayableRecipient = m_contractAddress;
compileAndRun(sourceCode, 0, "C"); compileAndRun(sourceCode, 0, "C");
@ -1099,6 +1103,7 @@ BOOST_AUTO_TEST_CASE(transfer_ether)
BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10); BOOST_CHECK_EQUAL(balanceAt(m_contractAddress), 10);
ABI_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10), encodeArgs()); ABI_CHECK(callContractFunction("b(address,uint256)", nonPayableRecipient, 10), encodeArgs());
ABI_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10), encodeArgs()); ABI_CHECK(callContractFunction("b(address,uint256)", oogRecipient, 10), encodeArgs());
)
} }
BOOST_AUTO_TEST_CASE(uncalled_blockhash) BOOST_AUTO_TEST_CASE(uncalled_blockhash)

View File

@ -18,6 +18,8 @@ contract C {
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(uint256): 0 -> FAILURE // f(uint256): 0 -> FAILURE
// g(uint256): 0 -> FAILURE // g(uint256): 0 -> FAILURE

View File

@ -0,0 +1,17 @@
contract C {
function f() public returns(bytes32) {
return blockhash(1);
}
function g() public returns(bytes32) {
return blockhash(2);
}
function h() public returns(bytes32) {
return blockhash(3);
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0x3737373737373737373737373737373737373737373737373737373737373738
// g() -> 0x3737373737373737373737373737373737373737373737373737373737373739
// h() -> 0x373737373737373737373737373737373737373737373737373737373737373a

View File

@ -0,0 +1,12 @@
// Test for regression of https://github.com/ethereum/solidity/issues/8406
contract C {
address constant e = 0x1212121212121212121212121000002134593163;
function f() public returns (byte z) {
assembly { z := e }
}
}
// ----
// f() -> 0x00

View File

@ -4,5 +4,7 @@ contract A {
return a; return a;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 2 // f() -> 2

View File

@ -1,5 +1,7 @@
contract C { contract C {
uint immutable public x = 1; uint immutable public x = 1;
} }
// ====
// compileViaYul: also
// ---- // ----
// x() -> 1 // x() -> 1

View File

@ -26,5 +26,7 @@ contract D is B, C {
return (a, b, c, d); return (a, b, c, d);
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 4, 3, 2, 1 // f() -> 4, 3, 2, 1

View File

@ -10,6 +10,8 @@ contract C {
return z(); return z();
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 7 // f() -> 7
// callZ() -> 7 // callZ() -> 7

View File

@ -9,5 +9,7 @@ contract C {
return (x+x,y); return (x+x,y);
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 84, 23 // f() -> 84, 23

View File

@ -13,6 +13,8 @@ contract C {
return (x+x,y); return (x+x,y);
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// constructor(): 3 -> // constructor(): 3 ->
// f() -> 84, 23 // f() -> 84, 23

View File

@ -0,0 +1,9 @@
contract C
{
function f() public
{
int x = ~(0,);
}
}
// ----
// TypeError: (60-64): Tuple component cannot be empty.

View File

@ -0,0 +1,9 @@
contract C
{
function f() public
{
int x = --(,);
}
}
// ----
// TypeError: (59-64): Unary operator -- cannot be applied to type tuple(,)

View File

@ -0,0 +1,10 @@
contract C
{
function f() public
{
int x = delete (,0);
}
}
// ----
// TypeError: (68-69): Expression has to be an lvalue.
// TypeError: (59-70): Unary operator delete cannot be applied to type tuple(,int_const 0)

View File

@ -0,0 +1,9 @@
contract C
{
function f() public
{
(int x) = ++(,);
}
}
// ----
// TypeError: (61-66): Unary operator ++ cannot be applied to type tuple(,)

View File

@ -0,0 +1,20 @@
contract C
{
function f() public
{
int x = +(0, 0);
int y = -(0, 0);
(int z) = ~(0, 0);
(int t) = !(0, 0);
}
}
// ----
// SyntaxError: (59-66): Use of unary + is disallowed.
// TypeError: (59-66): Unary operator + cannot be applied to type tuple(int_const 0,int_const 0)
// TypeError: (51-66): Different number of components on the left hand side (1) than on the right hand side (2).
// TypeError: (84-91): Unary operator - cannot be applied to type tuple(int_const 0,int_const 0)
// TypeError: (76-91): Different number of components on the left hand side (1) than on the right hand side (2).
// TypeError: (111-118): Unary operator ~ cannot be applied to type tuple(int_const 0,int_const 0)
// TypeError: (101-118): Different number of components on the left hand side (1) than on the right hand side (2).
// TypeError: (138-145): Unary operator ! cannot be applied to type tuple(int_const 0,int_const 0)
// TypeError: (128-145): Different number of components on the left hand side (1) than on the right hand side (2).

View File

@ -0,0 +1,11 @@
contract C {
function f() public pure {
assembly {
setimmutable("abc", 0)
loadimmutable("abc")
}
}
}
// ----
// DeclarationError: (63-75): Function not found.
// DeclarationError: (92-105): Function not found.

View File

@ -344,62 +344,102 @@ void ProtoConverter::visit(BinaryOp const& _x)
m_output << ")"; m_output << ")";
} }
void ProtoConverter::visit(VarDecl const& _x) void ProtoConverter::scopeVariables(vector<string> const& _varNames)
{ {
string varName = newVarName();
m_output << "let " << varName << " := ";
visit(_x.expr());
m_output << "\n";
// If we are inside a for-init block, there are two places // If we are inside a for-init block, there are two places
// where the visited vardecl may have been defined: // where the visited vardecl may have been defined:
// - directly inside the for-init block // - directly inside the for-init block
// - inside a block within the for-init block // - inside a block within the for-init block
// In the latter case, we don't scope extend. // In the latter case, we don't scope extend. The flag
// m_forInitScopeExtEnabled (= true) indicates whether we are directly
// inside a for-init block e.g., for { let x } or (= false) inside a
// nested for-init block e.g., for { { let x } }
bool forInitScopeExtendVariable = m_inForInitScope && m_forInitScopeExtEnabled;
// There are four cases that are tackled here
// Case 1. We are inside a function definition and the variable declaration's
// scope needs to be extended.
// Case 2. We are inside a function definition but scope extension is disabled
// Case 3. We are inside global scope and scope extension is required
// Case 4. We are inside global scope but scope extension is disabled
if (m_inFunctionDef) if (m_inFunctionDef)
{ {
// Variables declared directly in for-init block // Variables declared directly in for-init block
// are tracked separately because their scope // are tracked separately because their scope
// extends beyond the block they are defined in // extends beyond the block they are defined in
// to the rest of the for-loop statement. // to the rest of the for-loop statement.
if (m_inForInitScope && m_forInitScopeExtEnabled) // Case 1
if (forInitScopeExtendVariable)
{ {
yulAssert( yulAssert(
!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
"Proto fuzzer: Invalid operation" "Proto fuzzer: Invalid operation"
); );
for (auto const& varName: _varNames)
m_funcForLoopInitVars.back().back().push_back(varName); m_funcForLoopInitVars.back().back().push_back(varName);
} }
// Case 2
else else
{ {
yulAssert( yulAssert(
!m_funcVars.empty() && !m_funcVars.back().empty(), !m_funcVars.empty() && !m_funcVars.back().empty(),
"Proto fuzzer: Invalid operation" "Proto fuzzer: Invalid operation"
); );
for (auto const& varName: _varNames)
m_funcVars.back().back().push_back(varName); m_funcVars.back().back().push_back(varName);
} }
} }
// If m_inFunctionDef is false, we are in global scope
else else
{ {
if (m_inForInitScope && m_forInitScopeExtEnabled) // Case 3
if (forInitScopeExtendVariable)
{ {
yulAssert( yulAssert(!m_globalForLoopInitVars.empty(), "Proto fuzzer: Invalid operation");
!m_globalForLoopInitVars.empty(),
"Proto fuzzer: Invalid operation" for (auto const& varName: _varNames)
);
m_globalForLoopInitVars.back().push_back(varName); m_globalForLoopInitVars.back().push_back(varName);
} }
// Case 4
else else
{ {
yulAssert( yulAssert(!m_globalVars.empty(), "Proto fuzzer: Invalid operation");
!m_globalVars.empty(),
"Proto fuzzer: Invalid operation" for (auto const& varName: _varNames)
);
m_globalVars.back().push_back(varName); m_globalVars.back().push_back(varName);
} }
} }
} }
void ProtoConverter::visit(VarDecl const& _x)
{
string varName = newVarName();
m_output << "let " << varName << " := ";
visit(_x.expr());
m_output << "\n";
scopeVariables({varName});
}
void ProtoConverter::visit(MultiVarDecl const& _x)
{
m_output << "let ";
vector<string> varNames;
// We support up to 4 variables in a single
// declaration statement.
unsigned numVars = _x.num_vars() % 3 + 2;
string delimiter = "";
for (unsigned i = 0; i < numVars; i++)
{
string varName = newVarName();
varNames.push_back(varName);
m_output << delimiter << varName;
if (i == 0)
delimiter = ", ";
}
m_output << "\n";
scopeVariables(varNames);
}
void ProtoConverter::visit(TypedVarDecl const& _x) void ProtoConverter::visit(TypedVarDecl const& _x)
{ {
string varName = newVarName(); string varName = newVarName();
@ -659,7 +699,7 @@ void ProtoConverter::visit(CopyFunc const& _x)
CopyFunc_CopyType type = _x.ct(); CopyFunc_CopyType type = _x.ct();
// datacopy() is valid only if we are inside // datacopy() is valid only if we are inside
// a yul object. // a Yul object.
if (type == CopyFunc::DATA && !m_isObject) if (type == CopyFunc::DATA && !m_isObject)
return; return;
@ -1093,7 +1133,8 @@ void ProtoConverter::visit(ForStmt const& _x)
{ {
yulAssert( yulAssert(
!m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(), !m_funcForLoopInitVars.empty() && !m_funcForLoopInitVars.back().empty(),
"Proto fuzzer: Invalid data structure"); "Proto fuzzer: Invalid data structure"
);
// Remove variables in for-init // Remove variables in for-init
m_funcForLoopInitVars.back().pop_back(); m_funcForLoopInitVars.back().pop_back();
} }
@ -1361,6 +1402,9 @@ void ProtoConverter::visit(Statement const& _x)
if (m_inFunctionDef) if (m_inFunctionDef)
visit(_x.leave()); visit(_x.leave());
break; break;
case Statement::kMultidecl:
visit(_x.multidecl());
break;
case Statement::STMT_ONEOF_NOT_SET: case Statement::STMT_ONEOF_NOT_SET:
break; break;
} }
@ -1606,7 +1650,7 @@ void ProtoConverter::fillFunctionCallInput(unsigned _numInParams)
{ {
// Throw a 4-sided dice to choose whether to populate function input // Throw a 4-sided dice to choose whether to populate function input
// argument from a pseudo-randomly chosen slot in one of the following // argument from a pseudo-randomly chosen slot in one of the following
// locations: calldata, memory, storage, or yul optimizer dictionary. // locations: calldata, memory, storage, or Yul optimizer dictionary.
unsigned diceValue = counter() % 4; unsigned diceValue = counter() % 4;
// Pseudo-randomly choose one of the first ten 32-byte // Pseudo-randomly choose one of the first ten 32-byte
// aligned slots. // aligned slots.
@ -1747,7 +1791,7 @@ void ProtoConverter::createFunctionDefAndCall(
yulAssert( yulAssert(
!m_inForInitScope, !m_inForInitScope,
"Proto fuzzer: Trying to create function call inside for-init block" "Proto fuzzer: Trying to create function call inside a for-init block"
); );
if (_x.force_call()) if (_x.force_call())
createFunctionCall(funcName, _numInParams, _numOutParams); createFunctionCall(funcName, _numInParams, _numOutParams);
@ -1837,7 +1881,7 @@ void ProtoConverter::visit(Program const& _x)
// Record EVM Version // Record EVM Version
m_evmVersion = evmVersionMapping(_x.ver()); m_evmVersion = evmVersionMapping(_x.ver());
// Program is either a yul object or a block of // Program is either a Yul object or a block of
// statements. // statements.
switch (_x.program_oneof_case()) switch (_x.program_oneof_case())
{ {
@ -1854,7 +1898,7 @@ void ProtoConverter::visit(Program const& _x)
visit(_x.obj()); visit(_x.obj());
break; break;
case Program::PROGRAM_ONEOF_NOT_SET: case Program::PROGRAM_ONEOF_NOT_SET:
// {} is a trivial yul program // {} is a trivial Yul program
m_output << "{}"; m_output << "{}";
break; break;
} }

View File

@ -68,7 +68,7 @@ private:
void visit(BinaryOp const&); void visit(BinaryOp const&);
/// Visits a basic block optionally adding @a _funcParams to scope. /// Visits a basic block optionally adding @a _funcParams to scope.
/// @param _block Reference to a basic block of yul statements. /// @param _block Reference to a basic block of Yul statements.
/// @param _funcParams List of function parameter names, defaults to /// @param _funcParams List of function parameter names, defaults to
/// an empty vector. /// an empty vector.
void visit(Block const& _block); void visit(Block const& _block);
@ -77,6 +77,7 @@ private:
void visit(VarRef const&); void visit(VarRef const&);
void visit(Expression const&); void visit(Expression const&);
void visit(VarDecl const&); void visit(VarDecl const&);
void visit(MultiVarDecl const&);
void visit(TypedVarDecl const&); void visit(TypedVarDecl const&);
void visit(UnaryOp const&); void visit(UnaryOp const&);
void visit(AssignmentStatement const&); void visit(AssignmentStatement const&);
@ -196,7 +197,7 @@ private:
/// false otherwise /// false otherwise
bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams); bool functionValid(FunctionCall_Returns _type, unsigned _numOutParams);
/// Converts protobuf function call to a yul function call and appends /// Converts protobuf function call to a Yul function call and appends
/// it to output stream. /// it to output stream.
/// @param _x Protobuf function call /// @param _x Protobuf function call
/// @param _name Function name /// @param _name Function name
@ -210,7 +211,7 @@ private:
bool _newLine = true bool _newLine = true
); );
/// Prints a yul formatted variable declaration statement to the output /// Prints a Yul formatted variable declaration statement to the output
/// stream. /// stream.
/// Example 1: createVarDecls(0, 1, true) returns {"x_0"} and prints /// Example 1: createVarDecls(0, 1, true) returns {"x_0"} and prints
/// let x_0 := /// let x_0 :=
@ -235,21 +236,26 @@ private:
/// @return A vector of strings containing the printed variable names. /// @return A vector of strings containing the printed variable names.
std::vector<std::string> createVars(unsigned _startIdx, unsigned _endIdx); std::vector<std::string> createVars(unsigned _startIdx, unsigned _endIdx);
/// Print the yul syntax to make a call to a function named @a _funcName to /// Manages scope of Yul variables
/// @param _varNames is a list of Yul variable names whose scope needs
/// to be tracked according to Yul scoping rules.
void scopeVariables(std::vector<std::string> const& _varNames);
/// Print the Yul syntax to make a call to a function named @a _funcName to
/// the output stream. /// the output stream.
/// @param _funcName Name of the function to be called /// @param _funcName Name of the function to be called
/// @param _numInParams Number of input parameters in function signature /// @param _numInParams Number of input parameters in function signature
/// @param _numOutParams Number of output parameters in function signature /// @param _numOutParams Number of output parameters in function signature
void createFunctionCall(std::string _funcName, unsigned _numInParams, unsigned _numOutParams); void createFunctionCall(std::string _funcName, unsigned _numInParams, unsigned _numOutParams);
/// Print the yul syntax to pass input arguments to a function that has /// Print the Yul syntax to pass input arguments to a function that has
/// @a _numInParams number of input parameters to the output stream. /// @a _numInParams number of input parameters to the output stream.
/// The input arguments are pseudo-randomly chosen from calldata, memory, /// The input arguments are pseudo-randomly chosen from calldata, memory,
/// storage, or the yul optimizer hex dictionary. /// storage, or the Yul optimizer hex dictionary.
/// @param _numInParams Number of input arguments to fill /// @param _numInParams Number of input arguments to fill
void fillFunctionCallInput(unsigned _numInParams); void fillFunctionCallInput(unsigned _numInParams);
/// Print the yul syntax to save values returned by a function call /// Print the Yul syntax to save values returned by a function call
/// to the output stream. The values are either stored to memory or /// to the output stream. The values are either stored to memory or
/// storage based on a simulated coin flip. The saved location is /// storage based on a simulated coin flip. The saved location is
/// decided pseudo-randomly. /// decided pseudo-randomly.
@ -266,7 +272,7 @@ private:
/// Build a tree of objects that contains the object/data /// Build a tree of objects that contains the object/data
/// identifiers that are in scope in a given object. /// identifiers that are in scope in a given object.
/// @param _x root object of the yul protobuf specification. /// @param _x root object of the Yul protobuf specification.
void buildObjectScopeTree(Object const& _x); void buildObjectScopeTree(Object const& _x);
/// Returns a pseudo-random dictionary token. /// Returns a pseudo-random dictionary token.

View File

@ -21,6 +21,10 @@ message VarDecl {
required Expression expr = 1; required Expression expr = 1;
} }
message MultiVarDecl {
required uint32 num_vars = 1;
}
message LowLevelCall { message LowLevelCall {
enum Type { enum Type {
CALL = 0; CALL = 0;
@ -373,6 +377,7 @@ message Statement {
FunctionDef funcdef = 16; FunctionDef funcdef = 16;
PopStmt pop = 17; PopStmt pop = 17;
LeaveStmt leave = 18; LeaveStmt leave = 18;
MultiVarDecl multidecl = 19;
} }
} }