Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions libyul/optimiser/DataFlowAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ DataFlowAnalyzer::DataFlowAnalyzer(
):
m_dialect(_dialect),
m_functionSideEffects(std::move(_functionSideEffects)),
m_knowledgeBase(_dialect, [this](YulString _var) { return variableValue(_var); }),
m_knowledgeBase([this](YulString _var) { return variableValue(_var); }),
m_analyzeStores(_analyzeStores == MemoryAndStorage::Analyze)
{
if (m_analyzeStores)
Expand All @@ -76,7 +76,7 @@ void DataFlowAnalyzer::operator()(ExpressionStatement& _statement)
cxx20::erase_if(m_state.environment.storage, mapTuple([&](auto&& key, auto&& value) {
return
!m_knowledgeBase.knownToBeDifferent(vars->first, key) &&
!m_knowledgeBase.knownToBeEqual(vars->second, value);
vars->second != value;
}));
m_state.environment.storage[vars->first] = vars->second;
return;
Expand Down Expand Up @@ -271,7 +271,17 @@ void DataFlowAnalyzer::handleAssignment(set<YulString> const& _variables, Expres
auto const& referencedVariables = movableChecker.referencedVariables();
for (auto const& name: _variables)
{
// TODO these might be interdependent and it could matter which order we
// run this loop!
if (!_isDeclaration)
{
for (YulString v: m_state.references[name])
m_state.referencedBy[v].erase(name);
}
for (YulString v: referencedVariables)
m_state.referencedBy[v].insert(name);
m_state.references[name] = referencedVariables;

if (!_isDeclaration)
{
// assignment to slot denoted by "name"
Expand Down Expand Up @@ -316,6 +326,8 @@ void DataFlowAnalyzer::popScope()
for (auto const& name: m_variableScopes.back().variables)
{
m_state.value.erase(name);
for (YulString v: m_state.references[name])
m_state.referencedBy[v].erase(name);
m_state.references.erase(name);
}
m_variableScopes.pop_back();
Expand Down Expand Up @@ -351,16 +363,17 @@ void DataFlowAnalyzer::clearValues(set<YulString> _variables)
_variables.count(_item.second);
});

// Use referencedBy
// Also clear variables that reference variables to be cleared.
for (auto const& variableToClear: _variables)
for (auto const& [ref, names]: m_state.references)
if (names.count(variableToClear))
_variables.emplace(ref);
_variables += m_state.referencedBy[variableToClear];

// Clear the value and update the reference relation.
for (auto const& name: _variables)
{
m_state.value.erase(name);
for (YulString v: m_state.references[name])
m_state.referencedBy[v].erase(name);
m_state.references.erase(name);
}
}
Expand Down
4 changes: 3 additions & 1 deletion libyul/optimiser/DataFlowAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,10 @@ class DataFlowAnalyzer: public ASTModifier
{
/// Current values of variables, always movable.
std::map<YulString, AssignedValue> value;
/// m_references[a].contains(b) <=> the current expression assigned to a references b
/// references[a].contains(b) <=> the current expression assigned to a references b
std::unordered_map<YulString, std::set<YulString>> references;
/// The inverse of references.
std::unordered_map<YulString, std::set<YulString>> referencedBy;

Environment environment;
};
Expand Down
184 changes: 146 additions & 38 deletions libyul/optimiser/KnowledgeBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@

#include <libyul/AST.h>
#include <libyul/Utilities.h>
#include <libyul/optimiser/SimplificationRules.h>
#include <libyul/optimiser/DataFlowAnalyzer.h>

#include <libsolutil/CommonData.h>
Expand All @@ -34,39 +33,31 @@ using namespace std;
using namespace solidity;
using namespace solidity::yul;

KnowledgeBase::KnowledgeBase(map<YulString, AssignedValue> const& _ssaValues):
m_valuesAreSSA(true),
m_variableValues([_ssaValues](YulString _var) { return util::valueOrNullptr(_ssaValues, _var); })
{}

bool KnowledgeBase::knownToBeDifferent(YulString _a, YulString _b)
{
// Try to use the simplification rules together with the
// current values to turn `sub(_a, _b)` into a nonzero constant.
// If that fails, try `eq(_a, _b)`.

if (optional<u256> difference = differenceIfKnownConstant(_a, _b))
return difference != 0;

Expression expr2 = simplify(FunctionCall{{}, {{}, "eq"_yulstring}, util::make_vector<Expression>(Identifier{{}, _a}, Identifier{{}, _b})});
if (holds_alternative<Literal>(expr2))
return valueOfLiteral(std::get<Literal>(expr2)) == 0;

return false;
}

optional<u256> KnowledgeBase::differenceIfKnownConstant(YulString _a, YulString _b)
{
// Try to use the simplification rules together with the
// current values to turn `sub(_a, _b)` into a constant.

Expression expr1 = simplify(FunctionCall{{}, {{}, "sub"_yulstring}, util::make_vector<Expression>(Identifier{{}, _a}, Identifier{{}, _b})});
if (Literal const* value = get_if<Literal>(&expr1))
return valueOfLiteral(*value);

return {};
VariableOffset offA = explore(_a);
VariableOffset offB = explore(_b);
if (offA.reference == offB.reference)
return offA.offset - offB.offset;
else
return nullopt;
Comment on lines +54 to +55
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
else
return nullopt;
return nullopt;

}


bool KnowledgeBase::knownToBeDifferentByAtLeast32(YulString _a, YulString _b)
{
// Try to use the simplification rules together with the
// current values to turn `sub(_a, _b)` into a constant whose absolute value is at least 32.

if (optional<u256> difference = differenceIfKnownConstant(_a, _b))
return difference >= 32 && difference <= u256(0) - 32;

Expand All @@ -75,34 +66,151 @@ bool KnowledgeBase::knownToBeDifferentByAtLeast32(YulString _a, YulString _b)

bool KnowledgeBase::knownToBeZero(YulString _a)
{
return valueIfKnownConstant(_a) == u256{};
return valueIfKnownConstant(_a) == 0;
}

optional<u256> KnowledgeBase::valueIfKnownConstant(YulString _a)
{
if (AssignedValue const* value = m_variableValues(_a))
if (Literal const* literal = get_if<Literal>(value->value))
return valueOfLiteral(*literal);
return {};
VariableOffset offset = explore(_a);
if (offset.reference.empty())
return offset.offset;
else
return nullopt;
}

optional<u256> KnowledgeBase::valueIfKnownConstant(Expression const& _expression)
{
if (Identifier const* ident = get_if<Identifier>(&_expression))
return valueIfKnownConstant(ident->name);
else if (Literal const* lit = get_if<Literal>(&_expression))
return valueOfLiteral(*lit);
else
return nullopt;
}

Expression KnowledgeBase::simplify(Expression _expression)
KnowledgeBase::VariableOffset KnowledgeBase::explore(YulString _var)
{
m_counter = 0;
return simplifyRecursively(std::move(_expression));
Expression const* value = nullptr;
if (m_valuesAreSSA)
{
// In SSA, a once determined offset is always valid, so we first see
// if we already computed it.
if (VariableOffset const* varOff = util::valueOrNullptr(m_offsets, _var))
return *varOff;
value = valueOf(_var);
}
else
{
// For non-SSA, we query the value first so that the variable is reset if it has changed
// since the last call.
value = valueOf(_var);
if (VariableOffset const* varOff = util::valueOrNullptr(m_offsets, _var))
return *varOff;
}

if (value)
if (optional<VariableOffset> offset = explore(*value))
return setOffset(_var, *offset);
return setOffset(_var, VariableOffset{_var, 0});

}

Expression KnowledgeBase::simplifyRecursively(Expression _expression)
optional<KnowledgeBase::VariableOffset> KnowledgeBase::explore(Expression const& _value)
{
if (m_counter++ > 100)
return _expression;
if (Literal const* literal = get_if<Literal>(&_value))
return VariableOffset{YulString{}, valueOfLiteral(*literal)};
else if (Identifier const* identifier = get_if<Identifier>(&_value))
return explore(identifier->name);
else if (FunctionCall const* f = get_if<FunctionCall>(&_value))
{
if (f->functionName.name == "add"_yulstring)
{
if (optional<VariableOffset> a = explore(f->arguments[0]))
if (optional<VariableOffset> b = explore(f->arguments[1]))
{
u256 offset = a->offset + b->offset;
if (a->reference.empty())
// a is constant
return VariableOffset{b->reference, offset};
else if (b->reference.empty())
// b is constant
return VariableOffset{a->reference, offset};
}
}
else if (f->functionName.name == "sub"_yulstring)
if (optional<VariableOffset> a = explore(f->arguments[0]))
if (optional<VariableOffset> b = explore(f->arguments[1]))
{
u256 offset = a->offset - b->offset;
if (a->reference == b->reference)
return VariableOffset{YulString{}, offset};
else if (b->reference.empty())
// b is constant
return VariableOffset{a->reference, offset};
}
}

return nullopt;
}

if (holds_alternative<FunctionCall>(_expression))
for (Expression& arg: std::get<FunctionCall>(_expression).arguments)
arg = simplifyRecursively(arg);
Expression const* KnowledgeBase::valueOf(YulString _var)
{
AssignedValue const* assignedValue = m_variableValues(_var);
Expression const* currentValue = assignedValue ? assignedValue->value : nullptr;
if (m_valuesAreSSA)
return currentValue;

Expression const* lastValue = m_lastKnownValue[_var];
if (lastValue != currentValue)
reset(_var);
m_lastKnownValue[_var] = currentValue;
return currentValue;
}

if (auto match = SimplificationRules::findFirstMatch(_expression, m_dialect, m_variableValues))
return simplifyRecursively(match->action().toExpression(debugDataOf(_expression)));
void KnowledgeBase::reset(YulString _var)
{
yulAssert(!m_valuesAreSSA);

m_lastKnownValue.erase(_var);
if (VariableOffset const* offset = util::valueOrNullptr(m_offsets, _var))
{
// Remove var from its group
if (!offset->reference.empty())
m_groupMembers[offset->reference].erase(_var);
m_offsets.erase(_var);
}
if (set<YulString>* group = util::valueOrNullptr(m_groupMembers, _var))
{
// _var was a representative, we might have to find a new one.
if (!group->empty())
{
YulString newRepresentative = *group->begin();
yulAssert(newRepresentative != _var);
u256 newOffset = m_offsets[newRepresentative].offset;
// newOffset = newRepresentative - _var
for (YulString groupMember: *group)
{
yulAssert(m_offsets[groupMember].reference == _var);
m_offsets[groupMember].reference = newRepresentative;
// groupMember = _var + m_offsets[groupMember].offset (old)
// = newRepresentative - newOffset + m_offsets[groupMember].offset (old)
// so subtracting newOffset from .offset yields the original relation again,
// just with _var replaced by newRepresentative
m_offsets[groupMember].offset -= newOffset;
}
m_groupMembers[newRepresentative] = std::move(*group);

}
m_groupMembers.erase(_var);
}
}

return _expression;
KnowledgeBase::VariableOffset KnowledgeBase::setOffset(YulString _variable, VariableOffset _value)
{
m_offsets[_variable] = _value;
// Constants are not tracked in m_groupMembers because
// the "representative" can never be reset.
if (!_value.reference.empty())
m_groupMembers[_value.reference].insert(_variable);
return _value;
}
66 changes: 56 additions & 10 deletions libyul/optimiser/KnowledgeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,78 @@ struct AssignedValue;

/**
* Class that can answer questions about values of variables and their relations.
*
* Requires a callback that returns the current value of the variable.
* The value can change any time during the lifetime of the KnowledgeBase,
* it will update its internal data structure accordingly.
*
* This means that the code the KnowledgeBase is used on does not need to be in SSA
* form.
* The only requirement is that the assigned values are movable expressions.
*
* There is a constructor to provide all SSA values right at the beginning.
* If you use this, the KnowledgeBase will be slightly more efficient.
*
* Internally, tries to find groups of variables that have a mutual constant
* difference and stores these differences always relative to a specific
* representative variable of the group.
*
* There is a special group which is the constant values. Those use the
* empty YulString as representative "variable".
*/
class KnowledgeBase
{
public:
KnowledgeBase(
Dialect const& _dialect,
std::function<AssignedValue const*(YulString)> _variableValues
):
m_dialect(_dialect),
/// Constructor for arbitrary value callback that allows for variable values
/// to change in between calls to functions of this class.
KnowledgeBase(std::function<AssignedValue const*(YulString)> _variableValues):
m_variableValues(std::move(_variableValues))
{}
/// Constructor to use if source code is in SSA form and values are constant.
KnowledgeBase(std::map<YulString, AssignedValue> const& _ssaValues);

bool knownToBeDifferent(YulString _a, YulString _b);
std::optional<u256> differenceIfKnownConstant(YulString _a, YulString _b);
bool knownToBeDifferentByAtLeast32(YulString _a, YulString _b);
bool knownToBeEqual(YulString _a, YulString _b) const { return _a == _b; }
bool knownToBeZero(YulString _a);
std::optional<u256> valueIfKnownConstant(YulString _a);
std::optional<u256> valueIfKnownConstant(Expression const& _expression);

private:
Expression simplify(Expression _expression);
Expression simplifyRecursively(Expression _expression);
/**
* Constant offset relative to a reference variable, or absolute constant if the
* reference variable is the empty YulString.
*/
struct VariableOffset
{
YulString reference;
u256 offset;
};

Dialect const& m_dialect;
VariableOffset explore(YulString _var);
std::optional<VariableOffset> explore(Expression const& _value);

/// Retrieves the current value of a variable and potentially resets the variable if it is not up to date.
Expression const* valueOf(YulString _var);

/// Resets all information about the variable and removes it from its group,
/// potentially finding a new representative.
void reset(YulString _var);

VariableOffset setOffset(YulString _variable, VariableOffset _value);

/// If true, we can assume that variable values never change and skip some steps.
bool m_valuesAreSSA = false;
/// Callback to retrieve the current value of a variable.
std::function<AssignedValue const*(YulString)> m_variableValues;
size_t m_counter = 0;

/// Offsets for each variable to one representative per group.
/// The empty string is the representative of the constant value zero.
std::map<YulString, VariableOffset> m_offsets;
/// Last known value of each variable we queried.
std::map<YulString, Expression const*> m_lastKnownValue;
/// For each representative, variables that use it to offset from.
std::map<YulString, std::set<YulString>> m_groupMembers;
};

}
Loading