diff --git a/libyul/optimiser/VarNameCleaner.cpp b/libyul/optimiser/VarNameCleaner.cpp index 8d34ce85e408..9150d8a26474 100644 --- a/libyul/optimiser/VarNameCleaner.cpp +++ b/libyul/optimiser/VarNameCleaner.cpp @@ -55,6 +55,8 @@ void VarNameCleaner::operator()(FunctionDefinition& _funDef) m_usedNames = m_namesToKeep; std::map globalTranslatedNames; swap(globalTranslatedNames, m_translatedNames); + std::map globalNextSuffix; + swap(globalNextSuffix, m_nextSuffix); renameVariables(_funDef.parameters); renameVariables(_funDef.returnVariables); @@ -62,6 +64,7 @@ void VarNameCleaner::operator()(FunctionDefinition& _funDef) swap(globalUsedNames, m_usedNames); swap(globalTranslatedNames, m_translatedNames); + swap(globalNextSuffix, m_nextSuffix); m_insideFunction = false; } @@ -93,18 +96,25 @@ void VarNameCleaner::operator()(Identifier& _identifier) _identifier.name = name->second; } -YulName VarNameCleaner::findCleanName(YulName const& _name) const +YulName VarNameCleaner::findCleanName(YulName const& _name) { auto newName = stripSuffix(_name); if (!isUsedName(newName)) return newName; - // create new name with suffix (by finding a free identifier) - for (size_t i = 1; i < std::numeric_limits::max(); ++i) + // Use a per-base-name counter to avoid O(n²) probing when many + // variables share the same stripped base name. + size_t& nextSuffix = m_nextSuffix[newName]; + if (nextSuffix == 0) + nextSuffix = 1; + for (; nextSuffix < std::numeric_limits::max(); ++nextSuffix) { - YulName newNameSuffixed = YulName{newName.str() + "_" + std::to_string(i)}; + YulName newNameSuffixed = YulName{newName.str() + "_" + std::to_string(nextSuffix)}; if (!isUsedName(newNameSuffixed)) + { + ++nextSuffix; return newNameSuffixed; + } } yulAssert(false, "Exhausted by attempting to find an available suffix."); } diff --git a/libyul/optimiser/VarNameCleaner.h b/libyul/optimiser/VarNameCleaner.h index 47f3aca48db9..35e419b19896 100644 --- a/libyul/optimiser/VarNameCleaner.h +++ b/libyul/optimiser/VarNameCleaner.h @@ -75,7 +75,7 @@ class VarNameCleaner: public ASTModifier /// Looks out for a "clean name" the given @p name could be trimmed down to. /// @returns a trimmed down and "clean name" in case it found one, none otherwise. - YulName findCleanName(YulName const& name) const; + YulName findCleanName(YulName const& name); /// Tests whether a given name was already used within this pass /// or was set to be kept. @@ -89,6 +89,9 @@ class VarNameCleaner: public ASTModifier /// Set of names that are in use. std::set m_usedNames; + /// Next suffix to try per stripped base name, avoids O(n²) probing. + mutable std::map m_nextSuffix; + /// Maps old to new names. std::map m_translatedNames; diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/double_underscore_suffix.yul b/test/libyul/yulOptimizerTests/varNameCleaner/double_underscore_suffix.yul new file mode 100644 index 000000000000..3f67b621e871 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/double_underscore_suffix.yul @@ -0,0 +1,15 @@ +{ + let x__1 := 1 + let x__2 := 2 + let x___3 := 3 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/interleaved_bases.yul b/test/libyul/yulOptimizerTests/varNameCleaner/interleaved_bases.yul new file mode 100644 index 000000000000..1f7136ee6491 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/interleaved_bases.yul @@ -0,0 +1,25 @@ +{ + let x_5 := 1 + let y_3 := 2 + let x_10 := 3 + let y_7 := 4 + let z_1 := 5 + let x_20 := 6 + let y_15 := 7 + let z_8 := 8 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let y := 2 +// let x_1 := 3 +// let y_1 := 4 +// let z := 5 +// let x_2 := 6 +// let y_2 := 7 +// let z_1 := 8 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base.yul b/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base.yul new file mode 100644 index 000000000000..b7fd6a09d998 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base.yul @@ -0,0 +1,25 @@ +{ + let x_10 := 1 + let x_20 := 2 + let x_30 := 3 + let x_40 := 4 + let x_50 := 5 + let x_60 := 6 + let x_70 := 7 + let x_80 := 8 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// let x_3 := 4 +// let x_4 := 5 +// let x_5 := 6 +// let x_6 := 7 +// let x_7 := 8 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base_in_function.yul b/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base_in_function.yul new file mode 100644 index 000000000000..b3fe26a9c68a --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/many_same_base_in_function.yul @@ -0,0 +1,29 @@ +{ + let x_1 := 1 + function f() + { + let x_10 := 1 + let x_20 := 2 + let x_30 := 3 + let x_40 := 4 + let x_50 := 5 + } + let x_2 := 2 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// } +// function f() +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// let x_3 := 4 +// let x_4 := 5 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/nested_suffixes.yul b/test/libyul/yulOptimizerTests/varNameCleaner/nested_suffixes.yul new file mode 100644 index 000000000000..c3fcd27401aa --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/nested_suffixes.yul @@ -0,0 +1,17 @@ +{ + let x_1_2 := 1 + let x_3_4_5 := 2 + let x_6 := 3 + let x_1_2_3_4 := 4 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// let x_3 := 4 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/references_after_rename.yul b/test/libyul/yulOptimizerTests/varNameCleaner/references_after_rename.yul new file mode 100644 index 000000000000..4bdec45697bd --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/references_after_rename.yul @@ -0,0 +1,15 @@ +{ + let x_5 := 42 + let y_3 := x_5 + let z_1 := add(x_5, y_3) +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 42 +// let y := x +// let z := add(x, y) +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/same_base_with_gap.yul b/test/libyul/yulOptimizerTests/varNameCleaner/same_base_with_gap.yul new file mode 100644 index 000000000000..b8bc8e6eaa46 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/same_base_with_gap.yul @@ -0,0 +1,19 @@ +{ + let x := 1 + let x_1 := 2 + let x_5 := 3 + let x_10 := 4 + let x_20 := 5 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// let x_3 := 4 +// let x_4 := 5 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_function_name.yul b/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_function_name.yul new file mode 100644 index 000000000000..5ca68d9b65a4 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_function_name.yul @@ -0,0 +1,20 @@ +{ + // x_1 is a function name, so the suffix counter must skip it + // when cleaning x_100 and x_200 which both strip to "x" + function x_1() {} + let x := 1 + let x_100 := 2 + let x_200 := 3 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_2 := 2 +// let x_3 := 3 +// } +// function x_1() +// { } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_reserved.yul b/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_reserved.yul new file mode 100644 index 000000000000..33922df8b87d --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/suffix_skips_reserved.yul @@ -0,0 +1,19 @@ +{ + let x := 1 + let x_2 := 2 + let x_100 := 3 + let x_200 := 4 + let x_300 := 5 +} +// ---- +// step: varNameCleaner +// +// { +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// let x_3 := 4 +// let x_4 := 5 +// } +// } diff --git a/test/libyul/yulOptimizerTests/varNameCleaner/two_functions_same_base.yul b/test/libyul/yulOptimizerTests/varNameCleaner/two_functions_same_base.yul new file mode 100644 index 000000000000..b468b7671504 --- /dev/null +++ b/test/libyul/yulOptimizerTests/varNameCleaner/two_functions_same_base.yul @@ -0,0 +1,32 @@ +{ + function f() + { + let x_10 := 1 + let x_20 := 2 + let x_30 := 3 + } + function g() + { + let x_40 := 4 + let x_50 := 5 + let x_60 := 6 + } +} +// ---- +// step: varNameCleaner +// +// { +// { } +// function f() +// { +// let x := 1 +// let x_1 := 2 +// let x_2 := 3 +// } +// function g() +// { +// let x := 4 +// let x_1 := 5 +// let x_2 := 6 +// } +// }