diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 88c1a31b1..572038d22 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -73,6 +73,8 @@ set(sources codegen/ir/IRGeneratorForStatements.h codegen/ir/IRGenerationContext.cpp codegen/ir/IRGenerationContext.h + codegen/ir/IRLValue.cpp + codegen/ir/IRLValue.h formal/EncodingContext.cpp formal/EncodingContext.h formal/SMTChecker.cpp diff --git a/libsolidity/codegen/YulUtilFunctions.cpp b/libsolidity/codegen/YulUtilFunctions.cpp index e8ca19f2a..42b81d34b 100644 --- a/libsolidity/codegen/YulUtilFunctions.cpp +++ b/libsolidity/codegen/YulUtilFunctions.cpp @@ -237,6 +237,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits) } } +string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) +{ + solAssert(_numBytes <= 32, ""); + solAssert(_shiftBytes <= 32, ""); + size_t numBits = _numBytes * 8; + size_t shiftBits = _shiftBytes * 8; + string functionName = "update_byte_slice_" + to_string(_numBytes) + "_shift_" + to_string(_shiftBytes); + return m_functionCollector->createFunction(functionName, [&]() { + return + Whiskers(R"( + function <functionName>(value, toInsert) -> result { + let mask := <mask> + toInsert := <shl>(toInsert) + value := and(value, not(mask)) + result := or(value, and(toInsert, mask)) + } + )") + ("functionName", functionName) + ("mask", formatNumber(((bigint(1) << numBits) - 1) << shiftBits)) + ("shl", shiftLeftFunction(shiftBits)) + .render(); + }); +} + string YulUtilFunctions::roundUpFunction() { string functionName = "round_up_to_mul_of_32"; @@ -541,6 +565,26 @@ string YulUtilFunctions::cleanupFromStorageFunction(Type const& _type, bool _spl }); } +string YulUtilFunctions::prepareStoreFunction(Type const& _type) +{ + solUnimplementedAssert(_type.category() != Type::Category::Function, ""); + + string functionName = "prepare_store_" + _type.identifier(); + return m_functionCollector->createFunction(functionName, [&]() { + Whiskers templ(R"( + function <functionName>(value) -> ret { + ret := <actualPrepare> + } + )"); + templ("functionName", functionName); + if (_type.category() == Type::Category::FixedBytes) + templ("actualPrepare", shiftRightFunction(256 - 8 * _type.storageBytes()) + "(value)"); + else + templ("actualPrepare", "value"); + return templ.render(); + }); +} + string YulUtilFunctions::allocationFunction() { string functionName = "allocateMemory"; diff --git a/libsolidity/codegen/YulUtilFunctions.h b/libsolidity/codegen/YulUtilFunctions.h index e22494b87..cca8ec209 100644 --- a/libsolidity/codegen/YulUtilFunctions.h +++ b/libsolidity/codegen/YulUtilFunctions.h @@ -69,6 +69,11 @@ public: std::string shiftLeftFunction(size_t _numBits); std::string shiftRightFunction(size_t _numBits); + /// @returns the name of a function f(value, toInsert) -> newValue which replaces the + /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant + /// byte) by the _numBytes least significant bytes of `toInsert`. + std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); + /// @returns the name of a function that rounds its input to the next multiple /// of 32 or the input if it is a multiple of 32. std::string roundUpFunction(); @@ -110,6 +115,12 @@ public: /// single variable. std::string cleanupFromStorageFunction(Type const& _type, bool _splitFunctionTypes); + /// @returns the name of a function that prepares a value of the given type + /// for being stored in storage. This usually includes cleanup and right-alignment + /// to fit the number of bytes in storage. + /// The resulting value might still have dirty higher order bits. + std::string prepareStoreFunction(Type const& _type); + /// @returns the name of a function that allocates memory. /// Modifies the "free memory pointer" /// Arguments: size diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index f0f076db6..9b7d3f169 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -39,7 +39,7 @@ string IRGenerationContext::addLocalVariable(VariableDeclaration const& _varDecl return m_localVariables[&_varDecl] = "vloc_" + _varDecl.name() + "_" + to_string(_varDecl.id()); } -string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) +string IRGenerationContext::localVariableName(VariableDeclaration const& _varDecl) { solAssert( m_localVariables.count(&_varDecl), @@ -48,6 +48,15 @@ string IRGenerationContext::variableName(VariableDeclaration const& _varDecl) return m_localVariables[&_varDecl]; } +void IRGenerationContext::addStateVariable( + VariableDeclaration const& _declaration, + u256 _storageOffset, + unsigned _byteOffset +) +{ + m_stateVariables[&_declaration] = make_pair(move(_storageOffset), _byteOffset); +} + string IRGenerationContext::functionName(FunctionDefinition const& _function) { // @TODO previously, we had to distinguish creation context and runtime context, @@ -128,3 +137,8 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out) return templ.render(); }); } + +YulUtilFunctions IRGenerationContext::utils() +{ + return YulUtilFunctions(m_evmVersion, m_functions); +} diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index d40a0f260..ec5c9cd31 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -26,6 +26,8 @@ #include <liblangutil/EVMVersion.h> +#include <libdevcore/Common.h> + #include <string> #include <memory> #include <vector> @@ -39,6 +41,7 @@ class ContractDefinition; class VariableDeclaration; class FunctionDefinition; class Expression; +class YulUtilFunctions; /** * Class that contains contextual information during IR generation. @@ -62,7 +65,16 @@ public: std::string addLocalVariable(VariableDeclaration const& _varDecl); - std::string variableName(VariableDeclaration const& _varDecl); + bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } + std::string localVariableName(VariableDeclaration const& _varDecl); + + void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); + bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } + std::pair<u256, unsigned> storageLocationOfVariable(VariableDeclaration const& _varDecl) const + { + return m_stateVariables.at(&_varDecl); + } + std::string functionName(FunctionDefinition const& _function); FunctionDefinition const& virtualFunction(FunctionDefinition const& _functionDeclaration); std::string virtualFunctionName(FunctionDefinition const& _functionDeclaration); @@ -74,11 +86,16 @@ public: std::string internalDispatch(size_t _in, size_t _out); + /// @returns a new copy of the utility function generator (but using the same function set). + YulUtilFunctions utils(); + private: langutil::EVMVersion m_evmVersion; OptimiserSettings m_optimiserSettings; std::vector<ContractDefinition const*> m_inheritanceHierarchy; std::map<VariableDeclaration const*, std::string> m_localVariables; + /// Storage offsets of state variables + std::map<VariableDeclaration const*, std::pair<u256, unsigned>> m_stateVariables; std::shared_ptr<MultiUseYulFunctionCollector> m_functions; size_t m_varCounter = 0; }; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index a2bea3992..d4560ef25 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -89,15 +89,14 @@ string IRGenerator::generate(ContractDefinition const& _contract) } )"); - resetContext(); - m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + resetContext(_contract); t("CreationObject", creationObjectName(_contract)); t("memoryInit", memoryInit()); t("constructor", _contract.constructor() ? constructorCode(*_contract.constructor()) : ""); t("deploy", deployCode(_contract)); t("functions", m_context.functionCollector()->requestedFunctions()); - resetContext(); + resetContext(_contract); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); t("RuntimeObject", runtimeObjectName(_contract)); t("dispatch", dispatchRoutine(_contract)); @@ -249,7 +248,7 @@ string IRGenerator::memoryInit() .render(); } -void IRGenerator::resetContext() +void IRGenerator::resetContext(ContractDefinition const& _contract) { solAssert( m_context.functionCollector()->requestedFunctions().empty(), @@ -257,4 +256,8 @@ void IRGenerator::resetContext() ); m_context = IRGenerationContext(m_evmVersion, m_optimiserSettings); m_utils = YulUtilFunctions(m_evmVersion, m_context.functionCollector()); + + m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); + for (auto const& var: ContractType(_contract).stateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); } diff --git a/libsolidity/codegen/ir/IRGenerator.h b/libsolidity/codegen/ir/IRGenerator.h index d78acfabe..612682a3b 100644 --- a/libsolidity/codegen/ir/IRGenerator.h +++ b/libsolidity/codegen/ir/IRGenerator.h @@ -68,7 +68,7 @@ private: std::string memoryInit(); - void resetContext(); + void resetContext(ContractDefinition const& _contract); langutil::EVMVersion const m_evmVersion; OptimiserSettings const m_optimiserSettings; diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 57fecc061..3c6d02c87 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -21,6 +21,7 @@ #include <libsolidity/codegen/ir/IRGeneratorForStatements.h> #include <libsolidity/codegen/ir/IRGenerationContext.h> +#include <libsolidity/codegen/ir/IRLValue.h> #include <libsolidity/codegen/YulUtilFunctions.h> #include <libsolidity/ast/TypeProvider.h> @@ -66,7 +67,7 @@ struct CopyTranslate: public yul::ASTCopier return yul::Identifier{ _identifier.location, - yul::YulString{m_context.variableName(*varDecl)} + yul::YulString{m_context.localVariableName(*varDecl)} }; } @@ -79,6 +80,12 @@ private: +string IRGeneratorForStatements::code() const +{ + solAssert(!m_currentLValue, "LValue not reset!"); + return m_code.str(); +} + bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDeclStatement) { for (auto const& decl: _varDeclStatement.declarations()) @@ -94,7 +101,7 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDec VariableDeclaration const& varDecl = *_varDeclStatement.declarations().front(); m_code << "let " << - m_context.variableName(varDecl) << + m_context.localVariableName(varDecl) << " := " << expressionAsType(*expression, *varDecl.type()) << "\n"; @@ -102,7 +109,7 @@ bool IRGeneratorForStatements::visit(VariableDeclarationStatement const& _varDec else for (auto const& decl: _varDeclStatement.declarations()) if (decl) - m_code << "let " << m_context.variableName(*decl) << "\n"; + m_code << "let " << m_context.localVariableName(*decl) << "\n"; return false; } @@ -112,17 +119,18 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment) solUnimplementedAssert(_assignment.assignmentOperator() == Token::Assign, ""); _assignment.rightHandSide().accept(*this); + Type const* intermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType( + _assignment.leftHandSide().annotation().type + ); + string intermediateValue = m_context.newYulVariable(); + m_code << "let " << intermediateValue << " := " << expressionAsType(_assignment.rightHandSide(), *intermediateType) << "\n"; - // TODO proper lvalue handling - auto const& lvalue = dynamic_cast<Identifier const&>(_assignment.leftHandSide()); - string varName = m_context.variableName(dynamic_cast<VariableDeclaration const&>(*lvalue.annotation().referencedDeclaration)); + _assignment.leftHandSide().accept(*this); + solAssert(!!m_currentLValue, "LValue not retrieved."); + m_currentLValue->storeValue(intermediateValue, *intermediateType); + m_currentLValue.reset(); - m_code << - varName << - " := " << - expressionAsType(_assignment.rightHandSide(), *lvalue.annotation().type) << - "\n"; - defineExpression(_assignment) << varName << "\n"; + defineExpression(_assignment) << intermediateValue << "\n"; return false; } @@ -179,7 +187,7 @@ bool IRGeneratorForStatements::visit(Return const& _return) // TODO support tuples solUnimplementedAssert(types.size() == 1, "Multi-returns not implemented."); m_code << - m_context.variableName(*returnParameters.front()) << + m_context.localVariableName(*returnParameters.front()) << " := " << expressionAsType(*value, *types.front()) << "\n"; @@ -329,14 +337,26 @@ bool IRGeneratorForStatements::visit(InlineAssembly const& _inlineAsm) 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()); + defineExpression(_identifier) << to_string(m_context.virtualFunction(*functionDef).id()) << "\n"; else if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) - value = m_context.variableName(*varDecl); + { + // TODO for the constant case, we have to be careful: + // If the value is visited twice, `defineExpression` is called twice on + // the same expression. + solUnimplementedAssert(!varDecl->isConstant(), ""); + unique_ptr<IRLValue> lvalue; + if (m_context.isLocalVariable(*varDecl)) + lvalue = make_unique<IRLocalVariable>(m_code, m_context, *varDecl); + else if (m_context.isStateVariable(*varDecl)) + lvalue = make_unique<IRStorageItem>(m_code, m_context, *varDecl); + else + solAssert(false, "Invalid variable kind."); + + setLValue(_identifier, move(lvalue)); + } else solUnimplemented(""); - defineExpression(_identifier) << value << "\n"; return false; } @@ -375,3 +395,14 @@ ostream& IRGeneratorForStatements::defineExpression(Expression const& _expressio { return m_code << "let " << m_context.variable(_expression) << " := "; } + +void IRGeneratorForStatements::setLValue(Expression const& _expression, unique_ptr<IRLValue> _lvalue) +{ + solAssert(!m_currentLValue, ""); + + if (_expression.annotation().lValueRequested) + // Do not define the expression, so it cannot be used as value. + m_currentLValue = std::move(_lvalue); + else + defineExpression(_expression) << _lvalue->retrieveValue() << "\n"; +} diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.h b/libsolidity/codegen/ir/IRGeneratorForStatements.h index 5f5259463..5f196ad7d 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.h +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.h @@ -21,6 +21,7 @@ #pragma once #include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/codegen/ir/IRLValue.h> namespace dev { @@ -42,7 +43,7 @@ public: m_utils(_utils) {} - std::string code() const { return m_code.str(); } + std::string code() const; bool visit(VariableDeclarationStatement const& _variableDeclaration) override; bool visit(Assignment const& _assignment) override; @@ -62,9 +63,12 @@ private: std::string expressionAsType(Expression const& _expression, Type const& _to); std::ostream& defineExpression(Expression const& _expression); + void setLValue(Expression const& _expression, std::unique_ptr<IRLValue> _lvalue); + std::ostringstream m_code; IRGenerationContext& m_context; YulUtilFunctions& m_utils; + std::unique_ptr<IRLValue> m_currentLValue; }; } diff --git a/libsolidity/codegen/ir/IRLValue.cpp b/libsolidity/codegen/ir/IRLValue.cpp new file mode 100644 index 000000000..3c97992ff --- /dev/null +++ b/libsolidity/codegen/ir/IRLValue.cpp @@ -0,0 +1,103 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Generator for code that handles LValues. + */ + +#include <libsolidity/codegen/ir/IRLValue.h> + +#include <libsolidity/codegen/ir/IRGenerationContext.h> +#include <libsolidity/codegen/YulUtilFunctions.h> +#include <libsolidity/ast/AST.h> + +#include <libdevcore/Whiskers.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +IRLocalVariable::IRLocalVariable( + ostream& _code, + IRGenerationContext& _context, + VariableDeclaration const& _varDecl +): + IRLValue(_code, _context, _varDecl.annotation().type), + m_variableName(_context.localVariableName(_varDecl)) +{ +} + +void IRLocalVariable::storeValue(string const& _value, Type const& _type) const +{ + solAssert(_type == *m_type, "Storing different types - not necessarily a problem."); + m_code << m_variableName << " := " << _value << "\n"; +} + +IRStorageItem::IRStorageItem( + ostream& _code, + IRGenerationContext& _context, + VariableDeclaration const& _varDecl +): + IRLValue(_code, _context, _varDecl.annotation().type) +{ + u256 slot; + unsigned offset; + std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl); + m_slot = toCompactHexWithPrefix(slot); + m_offset = offset; +} + +string IRStorageItem::retrieveValue() const +{ + if (!m_type->isValueType()) + return m_slot; + solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); + return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; +} + +void IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const +{ + if (m_type->isValueType()) + { + solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size."); + solAssert(m_type->storageBytes() + m_offset <= 32, ""); + + solAssert(_sourceType == *m_type, "Different type, but might not be an error."); + + m_code << + Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n") + ("slot", m_slot) + ("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) + ("prepare", m_context.utils().prepareStoreFunction(*m_type)) + ("value", _value) + .render(); + } + else + { + solAssert( + _sourceType.category() == m_type->category(), + "Wrong type conversation for assignment." + ); + if (m_type->category() == Type::Category::Array) + solUnimplementedAssert(false, ""); + else if (m_type->category() == Type::Category::Struct) + solUnimplementedAssert(false, ""); + else + solAssert(false, "Invalid non-value type for assignment."); + } +} + diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h new file mode 100644 index 000000000..f5a980935 --- /dev/null +++ b/libsolidity/codegen/ir/IRLValue.h @@ -0,0 +1,95 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * Generator for code that handles LValues. + */ + +#pragma once + +#include <string> +#include <ostream> + +namespace dev +{ +namespace solidity +{ + +class VariableDeclaration; +class IRGenerationContext; +class Type; + +/** + * Abstract class used to retrieve, delete and store data in LValues. + */ +class IRLValue +{ +protected: + IRLValue(std::ostream& _code, IRGenerationContext& _context, Type const* _type = nullptr): + m_code(_code), + m_context(_context), + m_type(_type) + {} + +public: + virtual ~IRLValue() = default; + /// @returns an expression to retrieve the value of the lvalue. This might also emit + /// code to m_code. + virtual std::string retrieveValue() const = 0; + /// Appends code to m_code to store the value of @a _value (should be an identifier) + /// of type @a _type in the lvalue. Might perform type conversion. + virtual void storeValue(std::string const& _value, Type const& _type) const = 0; + +protected: + std::ostream& m_code; + IRGenerationContext& m_context; + Type const* m_type; +}; + +class IRLocalVariable: public IRLValue +{ +public: + IRLocalVariable( + std::ostream& _code, + IRGenerationContext& _context, + VariableDeclaration const& _varDecl + ); + std::string retrieveValue() const override { return m_variableName; } + void storeValue(std::string const& _value, Type const& _type) const override; + +private: + std::string m_variableName; +}; + +class IRStorageItem: public IRLValue +{ +public: + IRStorageItem( + std::ostream& _code, + IRGenerationContext& _context, + VariableDeclaration const& _varDecl + ); + std::string retrieveValue() const override; + void storeValue(std::string const& _value, Type const& _type) const override; + +private: + std::string m_slot; + unsigned m_offset; +}; + + +} +} diff --git a/test/libsolidity/SolidityExecutionFramework.h b/test/libsolidity/SolidityExecutionFramework.h index a04b5373d..49c016699 100644 --- a/test/libsolidity/SolidityExecutionFramework.h +++ b/test/libsolidity/SolidityExecutionFramework.h @@ -92,7 +92,9 @@ public: yul::AssemblyStack asmStack( m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, - m_optimiserSettings + // Ignore optimiser settings here because we need Yul optimisation to + // get code that does not exhaust the stack. + OptimiserSettings::full() ); if (!asmStack.parseAndAnalyze("", m_compiler.yulIROptimized( _contractName.empty() ? m_compiler.lastContractName() : _contractName diff --git a/test/libsolidity/semanticTests/viaYul/storage/packed_storage.sol b/test/libsolidity/semanticTests/viaYul/storage/packed_storage.sol new file mode 100644 index 000000000..5ee7b6f4d --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/storage/packed_storage.sol @@ -0,0 +1,16 @@ +contract C { + uint16 x; + byte y; + uint16 z; + function f(uint8 a) public returns (uint _x) { + x = a; + y = byte(uint8(x) + 1); + z = uint8(y) + 1; + x = z + 1; + _x = x; + } +} +// ==== +// compileViaYul: true +// ---- +// f(uint8): 6 -> 9 diff --git a/test/libsolidity/semanticTests/viaYul/storage/simple_storage.sol b/test/libsolidity/semanticTests/viaYul/storage/simple_storage.sol new file mode 100644 index 000000000..51bf2efcb --- /dev/null +++ b/test/libsolidity/semanticTests/viaYul/storage/simple_storage.sol @@ -0,0 +1,17 @@ +contract C { + uint x; + uint y; + function setX(uint a) public returns (uint _x) { + x = a; + _x = x; + } + function setY(uint a) public returns (uint _y) { + y = a; + _y = y; + } +} +// ==== +// compileViaYul: true +// ---- +// setX(uint256): 6 -> 6 +// setY(uint256): 2 -> 2