Avoid sorting FunctionDefinitions by AST ID during codegen

Co-authored-by: Kamil Śliwak <kamil.sliwak@codepoets.it>
This commit is contained in:
r0qs 2023-09-12 15:17:27 +02:00
parent cc7a14a61d
commit e3b36f736d
No known key found for this signature in database
GPG Key ID: 61503DBA6667276C
10 changed files with 35 additions and 36 deletions

View File

@ -12,6 +12,7 @@ Compiler Features:
Bugfixes: Bugfixes:
* AST: Fix wrong initial ID for Yul nodes in the AST. * AST: Fix wrong initial ID for Yul nodes in the AST.
* Code Generator: Fix output from via-IR code generator being dependent on which files were discovered by import callback. In some cases, a different AST ID assignment would alter the order of functions in internal dispatch, resulting in superficially different but semantically equivalent bytecode.
* NatSpec: Fix internal error when requesting userdoc or devdoc for a contract that emits an event defined in a foreign contract or interface. * NatSpec: Fix internal error when requesting userdoc or devdoc for a contract that emits an event defined in a foreign contract or interface.
* SMTChecker: Fix encoding error that causes loops to unroll after completion. * SMTChecker: Fix encoding error that causes loops to unroll after completion.
* SMTChecker: Fix inconsistency on constant condition checks when ``while`` or ``for`` loops are unrolled before the condition check. * SMTChecker: Fix inconsistency on constant condition checks when ``while`` or ``for`` loops are unrolled before the condition check.

View File

@ -31,6 +31,7 @@
#include <libsolutil/StringUtils.h> #include <libsolutil/StringUtils.h>
#include <range/v3/view/map.hpp> #include <range/v3/view/map.hpp>
#include <range/v3/algorithm/find.hpp>
using namespace solidity; using namespace solidity;
using namespace solidity::util; using namespace solidity::util;
@ -41,7 +42,7 @@ std::string IRGenerationContext::enqueueFunctionForCodeGeneration(FunctionDefini
std::string name = IRNames::function(_function); std::string name = IRNames::function(_function);
if (!m_functions.contains(name)) if (!m_functions.contains(name))
m_functionGenerationQueue.insert(&_function); m_functionGenerationQueue.push_back(&_function);
return name; return name;
} }
@ -50,8 +51,8 @@ FunctionDefinition const* IRGenerationContext::dequeueFunctionForCodeGeneration(
{ {
solAssert(!m_functionGenerationQueue.empty(), ""); solAssert(!m_functionGenerationQueue.empty(), "");
FunctionDefinition const* result = *m_functionGenerationQueue.begin(); FunctionDefinition const* result = m_functionGenerationQueue.front();
m_functionGenerationQueue.erase(m_functionGenerationQueue.begin()); m_functionGenerationQueue.pop_front();
return result; return result;
} }
@ -132,7 +133,7 @@ void IRGenerationContext::initializeInternalDispatch(InternalDispatchMap _intern
{ {
solAssert(internalDispatchClean(), ""); solAssert(internalDispatchClean(), "");
for (DispatchSet const& functions: _internalDispatch | ranges::views::values) for (DispatchQueue const& functions: _internalDispatch | ranges::views::values)
for (auto function: functions) for (auto function: functions)
enqueueFunctionForCodeGeneration(*function); enqueueFunctionForCodeGeneration(*function);
@ -149,17 +150,16 @@ InternalDispatchMap IRGenerationContext::consumeInternalDispatchMap()
void IRGenerationContext::addToInternalDispatch(FunctionDefinition const& _function) void IRGenerationContext::addToInternalDispatch(FunctionDefinition const& _function)
{ {
FunctionType const* functionType = TypeProvider::function(_function, FunctionType::Kind::Internal); FunctionType const* functionType = TypeProvider::function(_function, FunctionType::Kind::Internal);
solAssert(functionType, ""); solAssert(functionType);
YulArity arity = YulArity::fromType(*functionType); YulArity arity = YulArity::fromType(*functionType);
DispatchQueue& dispatchQueue = m_internalDispatchMap[arity];
if (m_internalDispatchMap.count(arity) != 0 && m_internalDispatchMap[arity].count(&_function) != 0) if (ranges::find(dispatchQueue, &_function) == ranges::end(dispatchQueue))
// Note that m_internalDispatchMap[arity] is a set with a custom comparator, which looks at function IDs not definitions {
solAssert(*m_internalDispatchMap[arity].find(&_function) == &_function, "Different definitions with the same function ID"); dispatchQueue.push_back(&_function);
m_internalDispatchMap[arity].insert(&_function);
enqueueFunctionForCodeGeneration(_function); enqueueFunctionForCodeGeneration(_function);
} }
}
void IRGenerationContext::internalFunctionCalledThroughDispatch(YulArity const& _arity) void IRGenerationContext::internalFunctionCalledThroughDispatch(YulArity const& _arity)

View File

@ -37,7 +37,6 @@
#include <set> #include <set>
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
namespace solidity::frontend namespace solidity::frontend
{ {
@ -45,20 +44,8 @@ namespace solidity::frontend
class YulUtilFunctions; class YulUtilFunctions;
class ABIFunctions; class ABIFunctions;
struct AscendingFunctionIDCompare using DispatchQueue = std::deque<FunctionDefinition const*>;
{ using InternalDispatchMap = std::map<YulArity, DispatchQueue>;
bool operator()(FunctionDefinition const* _f1, FunctionDefinition const* _f2) const
{
// NULLs always first.
if (_f1 != nullptr && _f2 != nullptr)
return _f1->id() < _f2->id();
else
return _f1 == nullptr;
}
};
using DispatchSet = std::set<FunctionDefinition const*, AscendingFunctionIDCompare>;
using InternalDispatchMap = std::map<YulArity, DispatchSet>;
/** /**
* Class that contains contextual information during IR generation. * Class that contains contextual information during IR generation.
@ -197,10 +184,9 @@ private:
/// were discovered by the IR generator during AST traversal. /// were discovered by the IR generator during AST traversal.
/// Note that the queue gets filled in a lazy way - new definitions can be added while the /// Note that the queue gets filled in a lazy way - new definitions can be added while the
/// collected ones get removed and traversed. /// collected ones get removed and traversed.
/// The order and duplicates are irrelevant here (hence std::set rather than std::queue) as /// The order and duplicates are relevant here
/// long as the order of Yul functions in the generated code is deterministic and the same on /// (see: IRGenerationContext::[enqueue|dequeue]FunctionForCodeGeneration)
/// all platforms - which is a property guaranteed by MultiUseYulFunctionCollector. DispatchQueue m_functionGenerationQueue;
DispatchSet m_functionGenerationQueue;
/// Collection of functions that need to be callable via internal dispatch. /// Collection of functions that need to be callable via internal dispatch.
/// Note that having a key with an empty set of functions is a valid situation. It means that /// Note that having a key with an empty set of functions is a valid situation. It means that

View File

@ -279,6 +279,7 @@ InternalDispatchMap IRGenerator::generateInternalDispatchFunctions(ContractDefin
templ("out", suffixedVariableNameList("out_", 0, arity.out)); templ("out", suffixedVariableNameList("out_", 0, arity.out));
std::vector<std::map<std::string, std::string>> cases; std::vector<std::map<std::string, std::string>> cases;
std::set<int64_t> caseValues;
for (FunctionDefinition const* function: internalDispatchMap.at(arity)) for (FunctionDefinition const* function: internalDispatchMap.at(arity))
{ {
solAssert(function, ""); solAssert(function, "");
@ -289,12 +290,14 @@ InternalDispatchMap IRGenerator::generateInternalDispatchFunctions(ContractDefin
solAssert(!function->isConstructor(), ""); solAssert(!function->isConstructor(), "");
// 0 is reserved for uninitialized function pointers // 0 is reserved for uninitialized function pointers
solAssert(function->id() != 0, "Unexpected function ID: 0"); solAssert(function->id() != 0, "Unexpected function ID: 0");
solAssert(caseValues.count(function->id()) == 0, "Duplicate function ID");
solAssert(m_context.functionCollector().contains(IRNames::function(*function)), ""); solAssert(m_context.functionCollector().contains(IRNames::function(*function)), "");
cases.emplace_back(std::map<std::string, std::string>{ cases.emplace_back(std::map<std::string, std::string>{
{"funID", std::to_string(m_context.mostDerivedContract().annotation().internalFunctionIDs.at(function))}, {"funID", std::to_string(m_context.mostDerivedContract().annotation().internalFunctionIDs.at(function))},
{"name", IRNames::function(*function)} {"name", IRNames::function(*function)}
}); });
caseValues.insert(function->id());
} }
templ("cases", std::move(cases)); templ("cases", std::move(cases));

View File

@ -63,6 +63,6 @@ contract C {
// gas legacy: 411269 // gas legacy: 411269
// gas legacyOptimized: 317754 // gas legacyOptimized: 317754
// test_uint256() -> // test_uint256() ->
// gas irOptimized: 509677 // gas irOptimized: 509479
// gas legacy: 577469 // gas legacy: 577469
// gas legacyOptimized: 441003 // gas legacyOptimized: 441003

View File

@ -64,6 +64,6 @@ contract C {
// gas legacy: 411269 // gas legacy: 411269
// gas legacyOptimized: 317754 // gas legacyOptimized: 317754
// test_uint256() -> // test_uint256() ->
// gas irOptimized: 509677 // gas irOptimized: 509479
// gas legacy: 577469 // gas legacy: 577469
// gas legacyOptimized: 441003 // gas legacyOptimized: 441003

View File

@ -33,7 +33,7 @@ contract test {
// EVMVersion: >=constantinople // EVMVersion: >=constantinople
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 406679 // gas irOptimized: 406691
// gas legacy: 737652 // gas legacy: 737652
// gas legacyOptimized: 527036 // gas legacyOptimized: 527036
// encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0 // encode_inline_asm(bytes): 0x20, 0 -> 0x20, 0

View File

@ -48,7 +48,7 @@ contract test {
} }
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 1722598 // gas irOptimized: 1722610
// gas legacy: 2210160 // gas legacy: 2210160
// gas legacyOptimized: 1734152 // gas legacyOptimized: 1734152
// div(uint256,uint256): 3141592653589793238, 88714123 -> 35412542528203691288251815328 // div(uint256,uint256): 3141592653589793238, 88714123 -> 35412542528203691288251815328

View File

@ -49,7 +49,7 @@ contract test {
} }
// ---- // ----
// constructor() // constructor()
// gas irOptimized: 634316 // gas irOptimized: 634328
// gas legacy: 1065857 // gas legacy: 1065857
// gas legacyOptimized: 725423 // gas legacyOptimized: 725423
// toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0 // toSlice(string): 0x20, 11, "hello world" -> 11, 0xa0

View File

@ -0,0 +1,9 @@
contract C {
function a() internal {}
function f() public {
function() ptr1 = a;
function() ptr2 = a;
}
}
// ----
// f()