diff options
author | Gav Wood <i@gavwood.com> | 2015-02-21 04:56:37 +0800 |
---|---|---|
committer | Gav Wood <i@gavwood.com> | 2015-02-21 04:56:37 +0800 |
commit | 89d84edb16812ff9e4b1049ee0257d65c75f5a3c (patch) | |
tree | baeda13e44a8fc0f6ac2f705fdf334da14405f39 /ExpressionCompiler.cpp | |
parent | d552ceb50f8e3888abf5b9a6095a5fb60ee91b5f (diff) | |
parent | 852405116654bda9f4dc1d4876a2368184b30a8c (diff) | |
download | dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.gz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.bz2 dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.lz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.xz dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.tar.zst dexon-solidity-89d84edb16812ff9e4b1049ee0257d65c75f5a3c.zip |
Merge branch 'develop'
Conflicts:
README.md
evmjit
libdevcrypto/CryptoPP.cpp
libethereum/State.cpp
neth/main.cpp
Diffstat (limited to 'ExpressionCompiler.cpp')
-rw-r--r-- | ExpressionCompiler.cpp | 1043 |
1 files changed, 762 insertions, 281 deletions
diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index cf641935..a8bc53e0 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -22,7 +22,9 @@ #include <utility> #include <numeric> +#include <boost/range/adaptor/reversed.hpp> #include <libdevcore/Common.h> +#include <libdevcrypto/SHA3.h> #include <libsolidity/AST.h> #include <libsolidity/ExpressionCompiler.h> #include <libsolidity/CompilerContext.h> @@ -41,64 +43,75 @@ void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression _expression.accept(compiler); } -void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, - Type const& _typeOnStack, Type const& _targetType) +void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, + Type const& _targetType, bool _cleanupNeeded) { ExpressionCompiler compiler(_context); - compiler.appendTypeConversion(_typeOnStack, _targetType); + compiler.appendTypeConversion(_typeOnStack, _targetType, _cleanupNeeded); +} + +void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize) +{ + ExpressionCompiler compiler(_context, _optimize); + compiler.appendStateVariableAccessor(_varDecl); } bool ExpressionCompiler::visit(Assignment const& _assignment) { _assignment.getRightHandSide().accept(*this); - appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); + if (_assignment.getType()->isValueType()) + appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); _assignment.getLeftHandSide().accept(*this); solAssert(m_currentLValue.isValid(), "LValue not retrieved."); Token::Value op = _assignment.getAssignmentOperator(); - if (op != Token::ASSIGN) // compound assignment + if (op != Token::Assign) // compound assignment { + solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types."); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - m_currentLValue.retrieveValue(_assignment, true); + m_currentLValue.retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; } - m_currentLValue.storeValue(_assignment); + m_currentLValue.storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); return false; } -void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) +bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { //@todo type checking and creating code for an operator should be in the same place: // the operator should know how to convert itself and to which types it applies, so // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that // represents the operator + if (_unaryOperation.getType()->getCategory() == Type::Category::IntegerConstant) + { + m_context << _unaryOperation.getType()->literalValue(nullptr); + return false; + } + + _unaryOperation.getSubExpression().accept(*this); + switch (_unaryOperation.getOperator()) { - case Token::NOT: // ! + case Token::Not: // ! m_context << eth::Instruction::ISZERO; break; - case Token::BIT_NOT: // ~ + case Token::BitNot: // ~ m_context << eth::Instruction::NOT; break; - case Token::DELETE: // delete - // @todo semantics change for complex types + case Token::Delete: // delete solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - - m_context << u256(0); - if (m_currentLValue.storesReferenceOnStack()) - m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation); + m_currentLValue.setToZero(_unaryOperation.getLocation()); m_currentLValue.reset(); break; - case Token::INC: // ++ (pre- or postfix) - case Token::DEC: // -- (pre- or postfix) + case Token::Inc: // ++ (pre- or postfix) + case Token::Dec: // -- (pre- or postfix) solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.retrieveValue(_unaryOperation); + m_currentLValue.retrieveValue(_unaryOperation.getLocation()); if (!_unaryOperation.isPrefixOperation()) { if (m_currentLValue.storesReferenceOnStack()) @@ -107,7 +120,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) m_context << eth::Instruction::DUP1; } m_context << u256(1); - if (_unaryOperation.getOperator() == Token::INC) + if (_unaryOperation.getOperator() == Token::Inc) m_context << eth::Instruction::ADD; else m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap @@ -115,19 +128,21 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation) // Stack for postfix: *ref [ref] (*ref)+-1 if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; - m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation()); + m_currentLValue.storeValue(*_unaryOperation.getType(), _unaryOperation.getLocation(), + !_unaryOperation.isPrefixOperation()); m_currentLValue.reset(); break; - case Token::ADD: // + + case Token::Add: // + // unary add, so basically no-op break; - case Token::SUB: // - + case Token::Sub: // - m_context << u256(0) << eth::Instruction::SUB; break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid unary operator: " + string(Token::toString(_unaryOperation.getOperator())))); } + return false; } bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) @@ -135,21 +150,23 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) Expression const& leftExpression = _binaryOperation.getLeftExpression(); Expression const& rightExpression = _binaryOperation.getRightExpression(); Type const& commonType = _binaryOperation.getCommonType(); - Token::Value const op = _binaryOperation.getOperator(); + Token::Value const c_op = _binaryOperation.getOperator(); - if (op == Token::AND || op == Token::OR) // special case: short-circuiting + if (c_op == Token::And || c_op == Token::Or) // special case: short-circuiting appendAndOrOperatorCode(_binaryOperation); + else if (commonType.getCategory() == Type::Category::IntegerConstant) + m_context << commonType.literalValue(nullptr); else { - bool cleanupNeeded = false; - if (commonType.getCategory() == Type::Category::INTEGER) - if (Token::isCompareOp(op) || op == Token::DIV || op == Token::MOD) - cleanupNeeded = true; + bool cleanupNeeded = commonType.getCategory() == Type::Category::Integer && + (Token::isCompareOp(c_op) || c_op == Token::Div || c_op == Token::Mod); // for commutative operators, push the literal as late as possible to allow improved optimization - //@todo this has to be extended for literal expressions - bool swap = (m_optimize && Token::isCommutativeOp(op) && dynamic_cast<Literal const*>(&rightExpression) - && !dynamic_cast<Literal const*>(&leftExpression)); + auto isLiteral = [](Expression const& _e) + { + return dynamic_cast<Literal const*>(&_e) || _e.getType()->getCategory() == Type::Category::IntegerConstant; + }; + bool swap = m_optimize && Token::isCommutativeOp(c_op) && isLiteral(rightExpression) && !isLiteral(leftExpression); if (swap) { leftExpression.accept(*this); @@ -164,10 +181,10 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) leftExpression.accept(*this); appendTypeConversion(*leftExpression.getType(), commonType, cleanupNeeded); } - if (Token::isCompareOp(op)) - appendCompareOperatorCode(op, commonType); + if (Token::isCompareOp(c_op)) + appendCompareOperatorCode(c_op, commonType); else - appendOrdinaryBinaryOperatorCode(op, commonType); + appendOrdinaryBinaryOperatorCode(c_op, commonType); } // do not visit the child nodes, we already did that explicitly @@ -181,25 +198,39 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { //@todo struct construction solAssert(_functionCall.getArguments().size() == 1, ""); + solAssert(_functionCall.getNames().empty(), ""); Expression const& firstArgument = *_functionCall.getArguments().front(); firstArgument.accept(*this); - if (firstArgument.getType()->getCategory() == Type::Category::CONTRACT && - _functionCall.getType()->getCategory() == Type::Category::INTEGER) - { - // explicit type conversion contract -> address, nothing to do. - } - else - appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); + appendTypeConversion(*firstArgument.getType(), *_functionCall.getType()); } else { FunctionType const& function = dynamic_cast<FunctionType const&>(*_functionCall.getExpression().getType()); - vector<ASTPointer<Expression const>> arguments = _functionCall.getArguments(); - solAssert(arguments.size() == function.getParameterTypes().size(), ""); + TypePointers const& parameterTypes = function.getParameterTypes(); + vector<ASTPointer<Expression const>> const& callArguments = _functionCall.getArguments(); + vector<ASTPointer<ASTString>> const& callArgumentNames = _functionCall.getNames(); + if (!function.takesArbitraryParameters()) + solAssert(callArguments.size() == parameterTypes.size(), ""); + + vector<ASTPointer<Expression const>> arguments; + if (callArgumentNames.empty()) + // normal arguments + arguments = callArguments; + else + // named arguments + for (auto const& parameterName: function.getParameterNames()) + { + bool found = false; + for (size_t j = 0; j < callArgumentNames.size() && !found; j++) + if ((found = (parameterName == *callArgumentNames[j]))) + // we found the actual parameter position + arguments.push_back(callArguments[j]); + solAssert(found, ""); + } switch (function.getLocation()) { - case Location::INTERNAL: + case Location::Internal: { // Calling convention: Caller pushes return address and arguments // Callee removes them and pushes return values @@ -217,7 +248,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes()); // callee adds return parameters, but removes arguments and return label - m_context.adjustStackOffset(returnParametersSize - CompilerUtils::getSizeOnStack(arguments) - 1); + m_context.adjustStackOffset(returnParametersSize - CompilerUtils::getSizeOnStack(function.getParameterTypes()) - 1); // @todo for now, the return value of a function is its first return value, so remove // all others @@ -225,50 +256,143 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) CompilerUtils(m_context).popStackElement(*function.getReturnParameterTypes()[i]); break; } - case Location::EXTERNAL: - case Location::BARE: + case Location::External: + case Location::Bare: + _functionCall.getExpression().accept(*this); + appendExternalFunctionCall(function, arguments, function.getLocation() == Location::Bare); + break; + case Location::Creation: { - FunctionCallOptions options; - options.bare = function.getLocation() == Location::BARE; - options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; - appendExternalFunctionCall(function, arguments, options); + _functionCall.getExpression().accept(*this); + solAssert(!function.gasSet(), "Gas limit set for contract creation."); + solAssert(function.getReturnParameterTypes().size() == 1, ""); + ContractDefinition const& contract = dynamic_cast<ContractType const&>( + *function.getReturnParameterTypes().front()).getContractDefinition(); + // copy the contract's code into memory + bytes const& bytecode = m_context.getCompiledContract(contract); + m_context << u256(bytecode.size()); + //@todo could be done by actually appending the Assembly, but then we probably need to compile + // multiple times. Will revisit once external fuctions are inlined. + m_context.appendData(bytecode); + //@todo copy to memory position 0, shift as soon as we use memory + m_context << u256(0) << eth::Instruction::CODECOPY; + + m_context << u256(bytecode.size()); + appendArgumentsCopyToMemory(arguments, function.getParameterTypes()); + // size, offset, endowment + m_context << u256(0); + if (function.valueSet()) + m_context << eth::dupInstruction(3); + else + m_context << u256(0); + m_context << eth::Instruction::CREATE; + if (function.valueSet()) + m_context << eth::swapInstruction(1) << eth::Instruction::POP; break; } - case Location::SEND: + case Location::SetGas: { - FunctionCallOptions options; - options.bare = true; - options.obtainAddress = [&]() { _functionCall.getExpression().accept(*this); }; - options.obtainValue = [&]() { arguments.front()->accept(*this); }; - appendExternalFunctionCall(FunctionType({}, {}, Location::EXTERNAL), {}, options); + // stack layout: contract_address function_id [gas] [value] + _functionCall.getExpression().accept(*this); + arguments.front()->accept(*this); + appendTypeConversion(*arguments.front()->getType(), IntegerType(256), true); + // Note that function is not the original function, but the ".gas" function. + // Its values of gasSet and valueSet is equal to the original function's though. + unsigned stackDepth = (function.gasSet() ? 1 : 0) + (function.valueSet() ? 1 : 0); + if (stackDepth > 0) + m_context << eth::swapInstruction(stackDepth); + if (function.gasSet()) + m_context << eth::Instruction::POP; break; } - case Location::SUICIDE: + case Location::SetValue: + // stack layout: contract_address function_id [gas] [value] + _functionCall.getExpression().accept(*this); + // Note that function is not the original function, but the ".value" function. + // Its values of gasSet and valueSet is equal to the original function's though. + if (function.valueSet()) + m_context << eth::Instruction::POP; + arguments.front()->accept(*this); + break; + case Location::Send: + _functionCall.getExpression().accept(*this); + m_context << u256(0); // 0 gas, we do not want to execute code + arguments.front()->accept(*this); + appendTypeConversion(*arguments.front()->getType(), + *function.getParameterTypes().front(), true); + appendExternalFunctionCall(FunctionType(TypePointers{}, TypePointers{}, + Location::External, false, true, true), {}, true); + break; + case Location::Suicide: arguments.front()->accept(*this); - //@todo might not be necessary appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); m_context << eth::Instruction::SUICIDE; break; case Location::SHA3: - arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - m_context << u256(32) << u256(0) << eth::Instruction::SHA3; + { + m_context << u256(0); + appendArgumentsCopyToMemory(arguments, TypePointers(), function.padArguments()); + m_context << u256(0) << eth::Instruction::SHA3; + break; + } + case Location::Log0: + case Location::Log1: + case Location::Log2: + case Location::Log3: + case Location::Log4: + { + unsigned logNumber = int(function.getLocation()) - int(Location::Log0); + for (unsigned arg = logNumber; arg > 0; --arg) + { + arguments[arg]->accept(*this); + appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); + } + m_context << u256(0); + appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front()); + m_context << u256(0) << eth::logInstruction(logNumber); + break; + } + case Location::Event: + { + _functionCall.getExpression().accept(*this); + auto const& event = dynamic_cast<EventDefinition const&>(function.getDeclaration()); + unsigned numIndexed = 0; + // All indexed arguments go to the stack + for (unsigned arg = arguments.size(); arg > 0; --arg) + if (event.getParameters()[arg - 1]->isIndexed()) + { + ++numIndexed; + arguments[arg - 1]->accept(*this); + appendTypeConversion(*arguments[arg - 1]->getType(), + *function.getParameterTypes()[arg - 1], true); + } + m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); + ++numIndexed; + solAssert(numIndexed <= 4, "Too many indexed arguments."); + // Copy all non-indexed arguments to memory (data) + m_context << u256(0); + for (unsigned arg = 0; arg < arguments.size(); ++arg) + if (!event.getParameters()[arg]->isIndexed()) + appendExpressionCopyToMemory(*function.getParameterTypes()[arg], *arguments[arg]); + m_context << u256(0) << eth::logInstruction(numIndexed); break; - case Location::ECRECOVER: + } + case Location::BlockHash: + { + arguments[0]->accept(*this); + appendTypeConversion(*arguments[0]->getType(), *function.getParameterTypes()[0], true); + m_context << eth::Instruction::BLOCKHASH; + break; + } + case Location::ECRecover: case Location::SHA256: case Location::RIPEMD160: { - static const map<Location, u256> contractAddresses{{Location::ECRECOVER, 1}, + static const map<Location, u256> contractAddresses{{Location::ECRecover, 1}, {Location::SHA256, 2}, {Location::RIPEMD160, 3}}; - u256 contractAddress = contractAddresses.find(function.getLocation())->second; - FunctionCallOptions options; - options.bare = true; - options.obtainAddress = [&]() { m_context << contractAddress; }; - options.packDensely = false; - appendExternalFunctionCall(function, arguments, options); + m_context << contractAddresses.find(function.getLocation())->second; + appendExternalFunctionCall(function, arguments, true); break; } default: @@ -278,39 +402,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) return false; } -bool ExpressionCompiler::visit(NewExpression const& _newExpression) -{ - ContractType const* type = dynamic_cast<ContractType const*>(_newExpression.getType().get()); - solAssert(type, ""); - TypePointers const& types = type->getConstructorType()->getParameterTypes(); - vector<ASTPointer<Expression const>> arguments = _newExpression.getArguments(); - solAssert(arguments.size() == types.size(), ""); - - // copy the contracts code into memory - bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract()); - m_context << u256(bytecode.size()); - //@todo could be done by actually appending the Assembly, but then we probably need to compile - // multiple times. Will revisit once external fuctions are inlined. - m_context.appendData(bytecode); - //@todo copy to memory position 0, shift as soon as we use memory - m_context << u256(0) << eth::Instruction::CODECOPY; - - unsigned dataOffset = bytecode.size(); - for (unsigned i = 0; i < arguments.size(); ++i) - { - arguments[i]->accept(*this); - appendTypeConversion(*arguments[i]->getType(), *types[i]); - unsigned const numBytes = types[i]->getCalldataEncodedSize(); - if (numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(arguments[i]->getLocation()) - << errinfo_comment("Type " + types[i]->toString() + " not yet supported.")); - bool const leftAligned = types[i]->getCategory() == Type::Category::STRING; - CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); - dataOffset += numBytes; - } - // size, offset, endowment - m_context << u256(dataOffset) << u256(0) << u256(0) << eth::Instruction::CREATE; +bool ExpressionCompiler::visit(NewExpression const&) +{ + // code is created for the function call (CREATION) only return false; } @@ -319,33 +413,51 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) ASTString const& member = _memberAccess.getMemberName(); switch (_memberAccess.getExpression().getType()->getCategory()) { - case Type::Category::INTEGER: + case Type::Category::Contract: + { + bool alsoSearchInteger = false; + ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType()); + if (type.isSuper()) + m_context << m_context.getSuperFunctionEntryLabel(member, type.getContractDefinition()).pushTag(); + else + { + // ordinary contract type + u256 identifier = type.getFunctionIdentifier(member); + if (identifier != Invalid256) + { + appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::Address), true); + m_context << identifier; + } + else + // not found in contract, search in members inherited from address + alsoSearchInteger = true; + } + if (!alsoSearchInteger) + break; + } + case Type::Category::Integer: if (member == "balance") { appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::ADDRESS), true); + IntegerType(0, IntegerType::Modifier::Address), true); m_context << eth::Instruction::BALANCE; } else if (member == "send" || member.substr(0, min<size_t>(member.size(), 4)) == "call") appendTypeConversion(*_memberAccess.getExpression().getType(), - IntegerType(0, IntegerType::Modifier::ADDRESS), true); + IntegerType(0, IntegerType::Modifier::Address), true); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to integer.")); break; - case Type::Category::CONTRACT: - { - ContractType const& type = dynamic_cast<ContractType const&>(*_memberAccess.getExpression().getType()); - m_context << type.getFunctionIndex(member); + case Type::Category::Function: + solAssert(!!_memberAccess.getExpression().getType()->getMemberType(member), + "Invalid member access to function."); break; - } - case Type::Category::MAGIC: + case Type::Category::Magic: // we can ignore the kind of magic and only look at the name of the member if (member == "coinbase") m_context << eth::Instruction::COINBASE; else if (member == "timestamp") m_context << eth::Instruction::TIMESTAMP; - else if (member == "prevhash") - m_context << eth::Instruction::PREVHASH; else if (member == "difficulty") m_context << eth::Instruction::DIFFICULTY; else if (member == "number") @@ -362,17 +474,64 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::GAS; else if (member == "gasprice") m_context << eth::Instruction::GASPRICE; + else if (member == "data") + m_context << u256(0) << eth::Instruction::CALLDATASIZE; else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); break; - case Type::Category::STRUCT: + case Type::Category::Struct: { StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType()); m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; - m_currentLValue = LValue(m_context, LValue::STORAGE, *_memberAccess.getType()); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); break; } + case Type::Category::Enum: + { + EnumType const& type = dynamic_cast<EnumType const&>(*_memberAccess.getExpression().getType()); + m_context << type.getMemberValue(_memberAccess.getMemberName()); + break; + } + case Type::Category::TypeType: + { + TypeType const& type = dynamic_cast<TypeType const&>(*_memberAccess.getExpression().getType()); + if (!type.getMembers().getMemberType(member)) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to " + type.toString())); + + if (auto contractType = dynamic_cast<ContractType const*>(type.getActualType().get())) + { + ContractDefinition const& contract = contractType->getContractDefinition(); + for (ASTPointer<FunctionDefinition> const& function: contract.getDefinedFunctions()) + if (function->getName() == member) + { + m_context << m_context.getFunctionEntryLabel(*function).pushTag(); + return; + } + solAssert(false, "Function not found in member access."); + } + else if (auto enumType = dynamic_cast<EnumType const*>(type.getActualType().get())) + m_context << enumType->getMemberValue(_memberAccess.getMemberName()); + break; + } + case Type::Category::ByteArray: + { + solAssert(member == "length", "Illegal bytearray member."); + auto const& type = dynamic_cast<ByteArrayType const&>(*_memberAccess.getExpression().getType()); + switch (type.getLocation()) + { + case ByteArrayType::Location::CallData: + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + break; + case ByteArrayType::Location::Storage: + m_context << eth::Instruction::SLOAD; + break; + default: + solAssert(false, "Unsupported byte array location."); + break; + } + break; + } default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type.")); } @@ -381,16 +540,19 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { _indexAccess.getBaseExpression().accept(*this); - _indexAccess.getIndexExpression().accept(*this); - appendTypeConversion(*_indexAccess.getIndexExpression().getType(), - *dynamic_cast<MappingType const&>(*_indexAccess.getBaseExpression().getType()).getKeyType(), - true); - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - CompilerUtils(m_context).storeInMemory(32); - m_context << u256(64) << u256(0) << eth::Instruction::SHA3; - - m_currentLValue = LValue(m_context, LValue::STORAGE, *_indexAccess.getType()); + + Type const& baseType = *_indexAccess.getBaseExpression().getType(); + solAssert(baseType.getCategory() == Type::Category::Mapping, ""); + Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType(); + m_context << u256(0); + appendExpressionCopyToMemory(keyType, _indexAccess.getIndexExpression()); + solAssert(baseType.getSizeOnStack() == 1, + "Unexpected: Not exactly one stack slot taken by subscriptable expression."); + m_context << eth::Instruction::SWAP1; + appendTypeMoveToMemory(IntegerType(256)); + m_context << u256(0) << eth::Instruction::SHA3; + + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); return false; @@ -401,32 +563,44 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) Declaration const* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) { - if (magicVar->getType()->getCategory() == Type::Category::CONTRACT) // must be "this" - m_context << eth::Instruction::ADDRESS; - return; + if (magicVar->getType()->getCategory() == Type::Category::Contract) + // "this" or "super" + if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper()) + m_context << eth::Instruction::ADDRESS; } - if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) - { - m_context << m_context.getFunctionEntryLabel(*functionDef).pushTag(); - return; - } - if (dynamic_cast<VariableDeclaration const*>(declaration)) + else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration)) + m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); + else if (dynamic_cast<VariableDeclaration const*>(declaration)) { m_currentLValue.fromIdentifier(_identifier, *declaration); m_currentLValue.retrieveValueIfLValueNotRequested(_identifier); - return; } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + else if (dynamic_cast<ContractDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<EventDefinition const*>(declaration)) + { + // no-op + } + else if (dynamic_cast<EnumDefinition const*>(declaration)) + { + // no-op + } + else + { + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); + } } void ExpressionCompiler::endVisit(Literal const& _literal) { switch (_literal.getType()->getCategory()) { - case Type::Category::INTEGER: - case Type::Category::BOOL: - case Type::Category::STRING: - m_context << _literal.getType()->literalValue(_literal); + case Type::Category::IntegerConstant: + case Type::Category::Bool: + case Type::Category::String: + m_context << _literal.getType()->literalValue(&_literal); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Only integer, boolean and string literals implemented for now.")); @@ -435,12 +609,12 @@ void ExpressionCompiler::endVisit(Literal const& _literal) void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation) { - Token::Value const op = _binaryOperation.getOperator(); - solAssert(op == Token::OR || op == Token::AND, ""); + Token::Value const c_op = _binaryOperation.getOperator(); + solAssert(c_op == Token::Or || c_op == Token::And, ""); _binaryOperation.getLeftExpression().accept(*this); m_context << eth::Instruction::DUP1; - if (op == Token::AND) + if (c_op == Token::And) m_context << eth::Instruction::ISZERO; eth::AssemblyItem endLabel = m_context.appendConditionalJump(); m_context << eth::Instruction::POP; @@ -450,32 +624,32 @@ void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryO void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type const& _type) { - if (_operator == Token::EQ || _operator == Token::NE) + if (_operator == Token::Equal || _operator == Token::NotEqual) { m_context << eth::Instruction::EQ; - if (_operator == Token::NE) + if (_operator == Token::NotEqual) m_context << eth::Instruction::ISZERO; } else { IntegerType const& type = dynamic_cast<IntegerType const&>(_type); - bool const isSigned = type.isSigned(); + bool const c_isSigned = type.isSigned(); switch (_operator) { - case Token::GTE: - m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT) + case Token::GreaterThanOrEqual: + m_context << (c_isSigned ? eth::Instruction::SLT : eth::Instruction::LT) << eth::Instruction::ISZERO; break; - case Token::LTE: - m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT) + case Token::LessThanOrEqual: + m_context << (c_isSigned ? eth::Instruction::SGT : eth::Instruction::GT) << eth::Instruction::ISZERO; break; - case Token::GT: - m_context << (isSigned ? eth::Instruction::SGT : eth::Instruction::GT); + case Token::GreaterThan: + m_context << (c_isSigned ? eth::Instruction::SGT : eth::Instruction::GT); break; - case Token::LT: - m_context << (isSigned ? eth::Instruction::SLT : eth::Instruction::LT); + case Token::LessThan: + m_context << (c_isSigned ? eth::Instruction::SLT : eth::Instruction::LT); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown comparison operator.")); @@ -498,24 +672,27 @@ void ExpressionCompiler::appendOrdinaryBinaryOperatorCode(Token::Value _operator void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Type const& _type) { IntegerType const& type = dynamic_cast<IntegerType const&>(_type); - bool const isSigned = type.isSigned(); + bool const c_isSigned = type.isSigned(); switch (_operator) { - case Token::ADD: + case Token::Add: m_context << eth::Instruction::ADD; break; - case Token::SUB: + case Token::Sub: m_context << eth::Instruction::SUB; break; - case Token::MUL: + case Token::Mul: m_context << eth::Instruction::MUL; break; - case Token::DIV: - m_context << (isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); + case Token::Div: + m_context << (c_isSigned ? eth::Instruction::SDIV : eth::Instruction::DIV); break; - case Token::MOD: - m_context << (isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + case Token::Mod: + m_context << (c_isSigned ? eth::Instruction::SMOD : eth::Instruction::MOD); + break; + case Token::Exp: + m_context << eth::Instruction::EXP; break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown arithmetic operator.")); @@ -526,13 +703,13 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) { switch (_operator) { - case Token::BIT_OR: + case Token::BitOr: m_context << eth::Instruction::OR; break; - case Token::BIT_AND: + case Token::BitAnd: m_context << eth::Instruction::AND; break; - case Token::BIT_XOR: + case Token::BitXor: m_context << eth::Instruction::XOR; break; default: @@ -562,12 +739,82 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con if (_typeOnStack == _targetType && !_cleanupNeeded) return; - if (_typeOnStack.getCategory() == Type::Category::INTEGER) - appendHighBitsCleanup(dynamic_cast<IntegerType const&>(_typeOnStack)); - else if (_typeOnStack.getCategory() == Type::Category::STRING) + Type::Category stackTypeCategory = _typeOnStack.getCategory(); + Type::Category targetTypeCategory = _targetType.getCategory(); + + if (stackTypeCategory == Type::Category::String) + { + StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack); + if (targetTypeCategory == Type::Category::Integer) + { + // conversion from string to hash. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType); + solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + else + { + // clear lower-order bytes for conversion to shorter strings - we always clean + solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); + StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType); + if (targetType.getNumBytes() < typeOnStack.getNumBytes()) + { + if (targetType.getNumBytes() == 0) + m_context << eth::Instruction::DUP1 << eth::Instruction::XOR; + else + m_context << (u256(1) << (256 - targetType.getNumBytes() * 8)) + << eth::Instruction::DUP1 << eth::Instruction::SWAP2 + << eth::Instruction::DIV << eth::Instruction::MUL; + } + } + } + else if (stackTypeCategory == Type::Category::Enum) + solAssert(targetTypeCategory == Type::Category::Integer || + targetTypeCategory == Type::Category::Enum, ""); + else if (stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::Contract || + stackTypeCategory == Type::Category::IntegerConstant) { - // nothing to do, strings are high-order-bit-aligned - //@todo clear lower-order bytes if we allow explicit conversion to shorter strings + if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) + { + // conversion from hash to string. no need to clean the high bit + // only to shift left because of opposite alignment + StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType); + IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack); + solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + } + else if (targetTypeCategory == Type::Category::Enum) + // just clean + appendTypeConversion(_typeOnStack, *_typeOnStack.getRealType(), true); + else + { + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); + IntegerType addressType(0, IntegerType::Modifier::Address); + IntegerType const& targetType = targetTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_targetType) : addressType; + if (stackTypeCategory == Type::Category::IntegerConstant) + { + IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + appendHighBitsCleanup(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer + ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + appendHighBitsCleanup(typeOnStack); + else if (_cleanupNeeded) + appendHighBitsCleanup(targetType); + } + } } else if (_typeOnStack != _targetType) // All other types should not be convertible to non-equal types. @@ -586,181 +833,415 @@ void ExpressionCompiler::appendHighBitsCleanup(IntegerType const& _typeOnStack) void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functionType, vector<ASTPointer<Expression const>> const& _arguments, - FunctionCallOptions const& _options) + bool bare) { - solAssert(_arguments.size() == _functionType.getParameterTypes().size(), ""); + solAssert(_functionType.takesArbitraryParameters() || + _arguments.size() == _functionType.getParameterTypes().size(), ""); + + // Assumed stack content here: + // <stack top> + // value [if _functionType.valueSet()] + // gas [if _functionType.gasSet()] + // function identifier [unless bare] + // contract address + + unsigned gasValueSize = (_functionType.gasSet() ? 1 : 0) + (_functionType.valueSet() ? 1 : 0); + + unsigned contractStackPos = m_context.currentToBaseStackOffset(1 + gasValueSize + (bare ? 0 : 1)); + unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize); + unsigned valueStackPos = m_context.currentToBaseStackOffset(1); - unsigned dataOffset = _options.bare ? 0 : 1; // reserve one byte for the function index - for (unsigned i = 0; i < _arguments.size(); ++i) - { - _arguments[i]->accept(*this); - Type const& type = *_functionType.getParameterTypes()[i]; - appendTypeConversion(*_arguments[i]->getType(), type); - unsigned const numBytes = _options.packDensely ? type.getCalldataEncodedSize() : 32; - if (numBytes == 0 || numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_arguments[i]->getLocation()) - << errinfo_comment("Type " + type.toString() + " not yet supported.")); - bool const leftAligned = type.getCategory() == Type::Category::STRING; - CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned); - dataOffset += numBytes; - } //@todo only return the first return value for now Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr : _functionType.getReturnParameterTypes().front().get(); - unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0; - if (!_options.packDensely && retSize > 0) - retSize = 32; - // CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top) - m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0); - if (_options.obtainValue) - _options.obtainValue(); + unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0; + m_context << u256(retSize) << u256(0); + + if (bare) + m_context << u256(0); + else + { + // copy function identifier + m_context << eth::dupInstruction(gasValueSize + 3); + CompilerUtils(m_context).storeInMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8)); + m_context << u256(CompilerUtils::dataStartOffset); + } + + // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes, + // do not pad it to 32 bytes. + appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(), + _functionType.padArguments(), bare); + + // CALL arguments: outSize, outOff, inSize, (already present up to here) + // inOff, value, addr, gas (stack top) + m_context << u256(0); + if (_functionType.valueSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos)); else m_context << u256(0); - _options.obtainAddress(); - if (!_options.bare) - m_context << u256(0) << eth::Instruction::MSTORE8; - m_context << u256(25) << eth::Instruction::GAS << eth::Instruction::SUB - << eth::Instruction::CALL + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(contractStackPos)); + + if (_functionType.gasSet()) + m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(gasStackPos)); + else + // send all gas except for the 21 needed to execute "SUB" and "CALL" + m_context << u256(21) << eth::Instruction::GAS << eth::Instruction::SUB; + m_context << eth::Instruction::CALL << eth::Instruction::POP; // @todo do not ignore failure indicator - if (retSize > 0) + if (_functionType.valueSet()) + m_context << eth::Instruction::POP; + if (_functionType.gasSet()) + m_context << eth::Instruction::POP; + if (!bare) + m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP; // pop contract address + + if (firstType) + CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true); +} + +void ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expression const>> const& _arguments, + TypePointers const& _types, + bool _padToWordBoundaries, + bool _padExceptionIfFourBytes) +{ + solAssert(_types.empty() || _types.size() == _arguments.size(), ""); + for (size_t i = 0; i < _arguments.size(); ++i) { - bool const leftAligned = firstType->getCategory() == Type::Category::STRING; - CompilerUtils(m_context).loadFromMemory(0, retSize, leftAligned); + _arguments[i]->accept(*this); + TypePointer const& expectedType = _types.empty() ? _arguments[i]->getType()->getRealType() : _types[i]; + appendTypeConversion(*_arguments[i]->getType(), *expectedType, true); + bool pad = _padToWordBoundaries; + // Do not pad if the first argument has exactly four bytes + if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize() == 4) + pad = false; + appendTypeMoveToMemory(*expectedType, pad); } } -ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, - unsigned _baseStackOffset): - m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset), - m_stackSize(_dataType.getSizeOnStack()) +void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries) +{ + CompilerUtils(m_context).storeInMemoryDynamic(_type, _padToWordBoundaries); +} + +void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { + _expression.accept(*this); + appendTypeConversion(*_expression.getType(), _expectedType, true); + appendTypeMoveToMemory(_expectedType); } -void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bool _remove) const +void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) +{ + FunctionType accessorType(_varDecl); + + unsigned length = 0; + TypePointers const& paramTypes = accessorType.getParameterTypes(); + // move arguments to memory + for (TypePointer const& paramType: boost::adaptors::reverse(paramTypes)) + length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); + + // retrieve the position of the variable + m_context << m_context.getStorageLocationOfVariable(_varDecl); + TypePointer returnType = _varDecl.getType(); + + for (TypePointer const& paramType: paramTypes) + { + // move offset to memory + CompilerUtils(m_context).storeInMemory(length); + unsigned argLen = CompilerUtils::getPaddedSize(paramType->getCalldataEncodedSize()); + length -= argLen; + m_context << u256(argLen + 32) << u256(length) << eth::Instruction::SHA3; + + returnType = dynamic_cast<MappingType const&>(*returnType).getValueType(); + } + + unsigned retSizeOnStack = 0; + solAssert(accessorType.getReturnParameterTypes().size() >= 1, ""); + if (StructType const* structType = dynamic_cast<StructType const*>(returnType.get())) + { + auto const& names = accessorType.getReturnParameterNames(); + auto const& types = accessorType.getReturnParameterTypes(); + // struct + for (size_t i = 0; i < names.size(); ++i) + { + m_context << eth::Instruction::DUP1 + << structType->getStorageOffsetOfMember(names[i]) + << eth::Instruction::ADD; + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, types[i]); + m_currentLValue.retrieveValue(Location(), true); + solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); + m_context << eth::Instruction::SWAP1; + retSizeOnStack += types[i]->getSizeOnStack(); + } + m_context << eth::Instruction::POP; + } + else + { + // simple value + solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, returnType); + m_currentLValue.retrieveValue(Location(), true); + retSizeOnStack = returnType->getSizeOnStack(); + } + solAssert(retSizeOnStack <= 15, "Stack too deep."); + m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; +} + +ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, + TypePointer const& _dataType, unsigned _baseStackOffset): + m_context(&_compilerContext), m_type(_type), m_dataType(_dataType), + m_baseStackOffset(_baseStackOffset) +{ + //@todo change the type cast for arrays + solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType->toString() + " should fit in unsigned"); + if (m_type == LValueType::Storage) + m_size = unsigned(m_dataType->getStorageSize()); + else + m_size = unsigned(m_dataType->getSizeOnStack()); +} + +void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) +{ + if (m_context->isLocalVariable(&_declaration)) + { + m_type = LValueType::Stack; + m_dataType = _identifier.getType(); + m_size = m_dataType->getSizeOnStack(); + m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); + } + else if (m_context->isStateVariable(&_declaration)) + { + *m_context << m_context->getStorageLocationOfVariable(_declaration); + m_type = LValueType::Storage; + m_dataType = _identifier.getType(); + solAssert(m_dataType->getStorageSize() <= numeric_limits<unsigned>::max(), + "The storage size of " + m_dataType->toString() + " should fit in an unsigned"); + m_size = unsigned(m_dataType->getStorageSize()); } + else + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) + << errinfo_comment("Identifier type not supported or identifier not found.")); +} + +void ExpressionCompiler::LValue::retrieveValue(Location const& _location, bool _remove) const { switch (m_type) { - case STACK: + case LValueType::Stack: { unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_stackSize; ++i) + for (unsigned i = 0; i < m_size; ++i) *m_context << eth::dupInstruction(stackPos + 1); break; } - case STORAGE: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types - if (!_remove) - *m_context << eth::Instruction::DUP1; - if (m_stackSize == 1) - *m_context << eth::Instruction::SLOAD; - else - for (unsigned i = 0; i < m_stackSize; ++i) - { - *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_stackSize) - *m_context << u256(1) << eth::Instruction::ADD; - else - *m_context << eth::Instruction::POP; - } + case LValueType::Storage: + retrieveValueFromStorage(_remove); break; - case MEMORY: - if (!_expression.getType()->isValueType()) + case LValueType::Memory: + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } -void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool _move) const +void ExpressionCompiler::LValue::retrieveValueFromStorage(bool _remove) const +{ + if (!m_dataType->isValueType()) + return; // no distinction between value and reference for non-value types + if (!_remove) + *m_context << eth::Instruction::DUP1; + if (m_size == 1) + *m_context << eth::Instruction::SLOAD; + else + for (unsigned i = 0; i < m_size; ++i) + { + *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; + if (i + 1 < m_size) + *m_context << u256(1) << eth::Instruction::ADD; + else + *m_context << eth::Instruction::POP; + } +} + +void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, Location const& _location, bool _move) const { switch (m_type) { - case STACK: + case LValueType::Stack: { - unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_stackSize + 1; + unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_size + 1; if (stackDiff > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); else if (stackDiff > 0) - for (unsigned i = 0; i < m_stackSize; ++i) + for (unsigned i = 0; i < m_size; ++i) *m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; if (!_move) - retrieveValue(_expression); + retrieveValue(_location); break; } - case LValue::STORAGE: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types - // stack layout: value value ... value ref - if (!_move) // copy values + case LValueType::Storage: + // stack layout: value value ... value target_ref + if (m_dataType->isValueType()) { - if (m_stackSize + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) - << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_stackSize; ++i) - *m_context << eth::dupInstruction(m_stackSize + 1) << eth::Instruction::SWAP1; + if (!_move) // copy values + { + if (m_size + 1 > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Stack too deep.")); + for (unsigned i = 0; i < m_size; ++i) + *m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + } + if (m_size > 0) // store high index value first + *m_context << u256(m_size - 1) << eth::Instruction::ADD; + for (unsigned i = 0; i < m_size; ++i) + { + if (i + 1 >= m_size) + *m_context << eth::Instruction::SSTORE; + else + // stack here: value value ... value value (target_ref+offset) + *m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 + << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + } } - if (m_stackSize > 0) // store high index value first - *m_context << u256(m_stackSize - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_stackSize; ++i) + else { - if (i + 1 >= m_stackSize) - *m_context << eth::Instruction::SSTORE; + solAssert(_sourceType.getCategory() == m_dataType->getCategory(), ""); + if (m_dataType->getCategory() == Type::Category::ByteArray) + { + CompilerUtils(*m_context).copyByteArrayToStorage( + dynamic_cast<ByteArrayType const&>(*m_dataType), + dynamic_cast<ByteArrayType const&>(_sourceType)); + if (_move) + *m_context << eth::Instruction::POP; + } + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: source_ref target_ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + solAssert(structType == _sourceType, "Struct assignment with conversion."); + for (auto const& member: structType.getMembers()) + { + // assign each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + *m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_member_ref + LValue rightHandSide(*m_context, LValueType::Storage, memberType); + rightHandSide.retrieveValue(_location, true); + // stack: source_ref target_ref member_offset source_value... + *m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::dupInstruction(2 + memberType->getSizeOnStack()) + << eth::Instruction::ADD; + // stack: source_ref target_ref member_offset source_value... target_member_ref + LValue memberLValue(*m_context, LValueType::Storage, memberType); + memberLValue.storeValue(*memberType, _location, true); + *m_context << eth::Instruction::POP; + } + if (_move) + *m_context << eth::Instruction::POP; + else + *m_context << eth::Instruction::SWAP1; + *m_context << eth::Instruction::POP; + } else - // v v ... v v r+x - *m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Invalid non-value type for assignment.")); } break; - case LValue::MEMORY: - if (!_expression.getType()->isValueType()) + case LValueType::Memory: + if (!m_dataType->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } -void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) +void ExpressionCompiler::LValue::setToZero(Location const& _location) const { - if (!_expression.lvalueRequested()) + switch (m_type) { - retrieveValue(_expression, true); - reset(); + case LValueType::Stack: + { + unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); + if (stackDiff > 16) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Stack too deep.")); + solAssert(stackDiff >= m_size - 1, ""); + for (unsigned i = 0; i < m_size; ++i) + *m_context << u256(0) << eth::swapInstruction(stackDiff + 1 - i) + << eth::Instruction::POP; + break; + } + case LValueType::Storage: + if (m_dataType->getCategory() == Type::Category::ByteArray) + CompilerUtils(*m_context).clearByteArray(dynamic_cast<ByteArrayType const&>(*m_dataType)); + else if (m_dataType->getCategory() == Type::Category::Struct) + { + // stack layout: ref + auto const& structType = dynamic_cast<StructType const&>(*m_dataType); + for (auto const& member: structType.getMembers()) + { + // zero each member that is not a mapping + TypePointer const& memberType = member.second; + if (memberType->getCategory() == Type::Category::Mapping) + continue; + *m_context << structType.getStorageOffsetOfMember(member.first) + << eth::Instruction::DUP2 << eth::Instruction::ADD; + LValue memberValue(*m_context, LValueType::Storage, memberType); + memberValue.setToZero(); + } + *m_context << eth::Instruction::POP; + } + else + { + if (m_size == 0) + *m_context << eth::Instruction::POP; + for (unsigned i = 0; i < m_size; ++i) + if (i + 1 >= m_size) + *m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + else + *m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + } + break; + case LValueType::Memory: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Location type not yet implemented.")); + break; + default: + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) + << errinfo_comment("Unsupported location type.")); + break; } } -void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) +void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression) { - m_stackSize = _identifier.getType()->getSizeOnStack(); - if (m_context->isLocalVariable(&_declaration)) - { - m_type = STACK; - m_baseStackOffset = m_context->getBaseStackOffsetOfVariable(_declaration); - } - else if (m_context->isStateVariable(&_declaration)) + if (!_expression.lvalueRequested()) { - m_type = STORAGE; - *m_context << m_context->getStorageLocationOfVariable(_declaration); + retrieveValue(_expression.getLocation(), true); + reset(); } - else - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) - << errinfo_comment("Identifier type not supported or identifier not found.")); } } |