Merge pull request #8068 from ethereum/evmTypedDialect

[Yul] EVM typed dialect
This commit is contained in:
chriseth
2020-01-30 14:53:25 +01:00
committed by GitHub
42 changed files with 303 additions and 149 deletions
+1 -1
View File
@@ -608,7 +608,7 @@ Scope& AsmAnalyzer::scope(Block const* _block)
}
void AsmAnalyzer::expectValidType(YulString _type, SourceLocation const& _location)
{
if (!_type.empty() && !contains(m_dialect.types, _type))
if (!_type.empty() && !m_dialect.types.count(_type))
m_errorReporter.typeError(
_location,
"\"" + _type.str() + "\" is not a valid type (user defined types are not yet supported)."
+4 -1
View File
@@ -366,7 +366,7 @@ Parser::ElementaryOperation Parser::parseElementaryOperation()
location(),
kind,
YulString{currentLiteral()},
{}
kind == LiteralKind::Boolean ? m_dialect.boolType : m_dialect.defaultType
};
advance();
if (currentToken() == Token::Colon)
@@ -497,6 +497,9 @@ TypedName Parser::parseTypedName()
typedName.location.end = endPosition();
typedName.type = expectAsmIdentifier();
}
else
typedName.type = m_dialect.defaultType;
return typedName;
}
+14 -4
View File
@@ -23,6 +23,7 @@
#include <libyul/AsmPrinter.h>
#include <libyul/AsmData.h>
#include <libyul/Exceptions.h>
#include <libyul/Dialect.h>
#include <libsolutil/CommonData.h>
@@ -49,7 +50,7 @@ string AsmPrinter::operator()(Literal const& _literal) const
return _literal.value.str() + appendTypeName(_literal.type);
case LiteralKind::Boolean:
yulAssert(_literal.value == "true"_yulstring || _literal.value == "false"_yulstring, "Invalid bool literal.");
return ((_literal.value == "true"_yulstring) ? "true" : "false") + appendTypeName(_literal.type);
return ((_literal.value == "true"_yulstring) ? "true" : "false") + appendTypeName(_literal.type, true);
case LiteralKind::String:
break;
}
@@ -236,9 +237,18 @@ string AsmPrinter::formatTypedName(TypedName _variable) const
return _variable.name.str() + appendTypeName(_variable.type);
}
string AsmPrinter::appendTypeName(YulString _type) const
string AsmPrinter::appendTypeName(YulString _type, bool _isBoolLiteral) const
{
if (!_type.empty())
if (m_dialect && !_type.empty())
{
if (!_isBoolLiteral && _type == m_dialect->defaultType)
_type = {};
else if (_isBoolLiteral && _type == m_dialect->boolType && !m_dialect->defaultType.empty())
// Special case: If we have a bool type but empty default type, do not remove the type.
_type = {};
}
if (_type.empty())
return {};
else
return ":" + _type.str();
return "";
}
+10 -2
View File
@@ -30,10 +30,16 @@ namespace solidity::yul
{
struct Dialect;
/**
* Converts a parsed Yul AST into readable string representation.
* Ignores source locations.
* If a dialect is provided, the dialect's default type is omitted.
*/
class AsmPrinter
{
public:
explicit AsmPrinter() {}
AsmPrinter() {}
explicit AsmPrinter(Dialect const& _dialect): m_dialect(&_dialect) {}
std::string operator()(Literal const& _literal) const;
std::string operator()(Identifier const& _identifier) const;
@@ -52,7 +58,9 @@ public:
private:
std::string formatTypedName(TypedName _variable) const;
std::string appendTypeName(YulString _type) const;
std::string appendTypeName(YulString _type, bool _isBoolLiteral = false) const;
Dialect const* m_dialect = nullptr;
};
}
+4 -4
View File
@@ -59,12 +59,12 @@ Dialect const& languageToDialect(AssemblyStack::Language _language, EVMVersion _
case AssemblyStack::Language::StrictAssembly:
return EVMDialect::strictAssemblyForEVMObjects(_version);
case AssemblyStack::Language::Yul:
return Dialect::yul();
return EVMDialectTyped::instance(_version);
case AssemblyStack::Language::Ewasm:
return WasmDialect::instance();
}
yulAssert(false, "");
return Dialect::yul();
return Dialect::yulDeprecated();
}
}
@@ -157,7 +157,7 @@ void AssemblyStack::compileEVM(AbstractAssembly& _assembly, bool _evm15, bool _o
dialect = &EVMDialect::strictAssemblyForEVMObjects(m_evmVersion);
break;
case Language::Yul:
dialect = &EVMDialect::yulForEVM(m_evmVersion);
dialect = &EVMDialectTyped::instance(m_evmVersion);
break;
default:
solAssert(false, "Invalid language.");
@@ -236,7 +236,7 @@ string AssemblyStack::print() const
{
yulAssert(m_parserResult, "");
yulAssert(m_parserResult->code, "");
return m_parserResult->toString(m_language == Language::Yul) + "\n";
return m_parserResult->toString(&languageToDialect(m_language, m_evmVersion)) + "\n";
}
shared_ptr<Object> AssemblyStack::parserResult() const
+1 -1
View File
@@ -23,7 +23,7 @@
using namespace solidity::yul;
using namespace std;
Dialect const& Dialect::yul()
Dialect const& Dialect::yulDeprecated()
{
static unique_ptr<Dialect> dialect;
static YulStringRepository::ResetCallback callback{[&] { dialect.reset(); }};
+4 -2
View File
@@ -48,10 +48,11 @@ struct BuiltinFunction
struct Dialect: boost::noncopyable
{
/// Default type, can be omitted.
YulString defaultType;
/// Type used for the literals "true" and "false".
YulString boolType;
std::vector<YulString> types;
std::set<YulString> types;
/// @returns the builtin function of the given name or a nullptr if it is not a builtin function.
virtual BuiltinFunction const* builtin(YulString /*_name*/) const { return nullptr; }
@@ -65,7 +66,8 @@ struct Dialect: boost::noncopyable
Dialect() = default;
virtual ~Dialect() = default;
static Dialect const& yul();
/// Old "yul" dialect. This is only used for testing.
static Dialect const& yulDeprecated();
};
}
+4 -4
View File
@@ -45,18 +45,18 @@ string indent(std::string const& _input)
}
string Data::toString(bool) const
string Data::toString(Dialect const*) const
{
return "data \"" + name.str() + "\" hex\"" + util::toHex(data) + "\"";
}
string Object::toString(bool _yul) const
string Object::toString(Dialect const* _dialect) const
{
yulAssert(code, "No code");
string inner = "code " + AsmPrinter{}(*code);
string inner = "code " + (_dialect ? AsmPrinter{*_dialect} : AsmPrinter{})(*code);
for (auto const& obj: subObjects)
inner += "\n" + obj->toString(_yul);
inner += "\n" + obj->toString(_dialect);
return "object \"" + name.str() + "\" {\n" + indent(inner) + "\n}";
}
+5 -3
View File
@@ -30,6 +30,7 @@
namespace solidity::yul
{
struct Dialect;
struct AsmAnalysisInfo;
@@ -39,7 +40,8 @@ struct AsmAnalysisInfo;
struct ObjectNode
{
virtual ~ObjectNode() = default;
virtual std::string toString(bool _yul) const = 0;
virtual std::string toString(Dialect const* _dialect) const = 0;
std::string toString() { return toString(nullptr); }
YulString name;
};
@@ -50,7 +52,7 @@ struct ObjectNode
struct Data: ObjectNode
{
Data(YulString _name, bytes _data): data(std::move(_data)) { name = _name; }
std::string toString(bool _yul) const override;
std::string toString(Dialect const* _dialect) const override;
bytes data;
};
@@ -62,7 +64,7 @@ struct Object: ObjectNode
{
public:
/// @returns a (parseable) string representation. Includes types if @a _yul is set.
std::string toString(bool _yul) const override;
std::string toString(Dialect const* _dialect) const override;
/// @returns the set of names of data objects accessible from within the code of
/// this object.
+74 -9
View File
@@ -203,15 +203,6 @@ EVMDialect const& EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion _
return *dialects[_version];
}
EVMDialect const& EVMDialect::yulForEVM(langutil::EVMVersion _version)
{
static map<langutil::EVMVersion, unique_ptr<EVMDialect const>> dialects;
static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }};
if (!dialects[_version])
dialects[_version] = make_unique<EVMDialect>(_version, false);
return *dialects[_version];
}
SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instruction)
{
return SideEffects{
@@ -222,3 +213,77 @@ SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instructio
evmasm::SemanticInformation::invalidatesMemory(_instruction)
};
}
EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess):
EVMDialect(_evmVersion, _objectAccess)
{
defaultType = "u256"_yulstring;
boolType = "bool"_yulstring;
types = {defaultType, boolType};
m_functions["lt"_yulstring].returns = {"bool"_yulstring};
m_functions["gt"_yulstring].returns = {"bool"_yulstring};
m_functions["slt"_yulstring].returns = {"bool"_yulstring};
m_functions["sgt"_yulstring].returns = {"bool"_yulstring};
m_functions["eq"_yulstring].returns = {"bool"_yulstring};
// "not" and "bitnot" replace "iszero" and "not"
m_functions["bitnot"_yulstring] = m_functions["not"_yulstring];
m_functions["bitnot"_yulstring].name = "bitnot"_yulstring;
m_functions["not"_yulstring] = m_functions["iszero"_yulstring];
m_functions["not"_yulstring].name = "not"_yulstring;
m_functions["not"_yulstring].returns = {"bool"_yulstring};
m_functions["not"_yulstring].parameters = {"bool"_yulstring};
m_functions.erase("iszero"_yulstring);
m_functions["bitand"_yulstring] = m_functions["and"_yulstring];
m_functions["bitand"_yulstring].name = "bitand"_yulstring;
m_functions["bitor"_yulstring] = m_functions["or"_yulstring];
m_functions["bitor"_yulstring].name = "bitor"_yulstring;
m_functions["bitxor"_yulstring] = m_functions["xor"_yulstring];
m_functions["bitxor"_yulstring].name = "bitxor"_yulstring;
m_functions["and"_yulstring].parameters = {"bool"_yulstring, "bool"_yulstring};
m_functions["and"_yulstring].returns = {"bool"_yulstring};
m_functions["or"_yulstring].parameters = {"bool"_yulstring, "bool"_yulstring};
m_functions["or"_yulstring].returns = {"bool"_yulstring};
m_functions["xor"_yulstring].parameters = {"bool"_yulstring, "bool"_yulstring};
m_functions["xor"_yulstring].returns = {"bool"_yulstring};
m_functions["popbool"_yulstring] = m_functions["pop"_yulstring];
m_functions["popbool"_yulstring].name = "popbool"_yulstring;
m_functions["popbool"_yulstring].parameters = {"bool"_yulstring};
m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, false, [](
FunctionCall const&,
AbstractAssembly&,
BuiltinContext&,
std::function<void()> _visitArguments
) {
_visitArguments();
}));
m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring};
m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, false, [](
FunctionCall const&,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void()> _visitArguments
) {
// A value larger than 1 causes an invalid instruction.
_visitArguments();
_assembly.appendConstant(2);
_assembly.appendInstruction(evmasm::Instruction::DUP2);
_assembly.appendInstruction(evmasm::Instruction::LT);
AbstractAssembly::LabelID inRange = _assembly.newLabelId();
_assembly.appendJumpToIf(inRange);
_assembly.appendInstruction(evmasm::Instruction::INVALID);
_assembly.appendLabel(inRange);
}));
m_functions["u256_to_bool"_yulstring].returns = {"bool"_yulstring};
}
EVMDialectTyped const& EVMDialectTyped::instance(langutil::EVMVersion _version)
{
static map<langutil::EVMVersion, unique_ptr<EVMDialectTyped const>> dialects;
static YulStringRepository::ResetCallback callback{[&] { dialects.clear(); }};
if (!dialects[_version])
dialects[_version] = make_unique<EVMDialectTyped>(_version, true);
return *dialects[_version];
}
+18 -1
View File
@@ -74,7 +74,6 @@ struct EVMDialect: public Dialect
static EVMDialect const& strictAssemblyForEVM(langutil::EVMVersion _version);
static EVMDialect const& strictAssemblyForEVMObjects(langutil::EVMVersion _version);
static EVMDialect const& yulForEVM(langutil::EVMVersion _version);
langutil::EVMVersion evmVersion() const { return m_evmVersion; }
@@ -88,4 +87,22 @@ protected:
std::map<YulString, BuiltinFunctionForEVM> m_functions;
};
/**
* EVM dialect with types u256 (default) and bool.
* Difference to EVMDialect:
* - All comparison functions return type bool
* - bitwise operations are called bitor, bitand, bitxor and bitnot
* - and, or, xor take bool and return bool
* - iszero is replaced by not, which takes bool and returns bool
* - there are conversion functions bool_to_u256 and u256_to_bool.
* - there is popbool
*/
struct EVMDialectTyped: public EVMDialect
{
/// Constructor, should only be used internally. Use the factory function below.
EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess);
static EVMDialectTyped const& instance(langutil::EVMVersion _version);
};
}