From ae485001477f14d2c2cffc2ad30175866bf7e035 Mon Sep 17 00:00:00 2001 From: Daniel Kirchner Date: Mon, 16 Aug 2021 13:15:09 +0200 Subject: [PATCH] Refactor createIdealLayout slightly and add more comments. --- libyul/backends/evm/StackLayoutGenerator.cpp | 68 +++++++++++++------- libyul/backends/evm/StackLayoutGenerator.h | 1 + 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/libyul/backends/evm/StackLayoutGenerator.cpp b/libyul/backends/evm/StackLayoutGenerator.cpp index a79da0af0..266d02b06 100644 --- a/libyul/backends/evm/StackLayoutGenerator.cpp +++ b/libyul/backends/evm/StackLayoutGenerator.cpp @@ -63,14 +63,36 @@ StackLayoutGenerator::StackLayoutGenerator(StackLayout& _layout, vector -Stack createIdealLayout(Stack const& _post, vector> _layout, Callable _generateSlotOnTheFly) +Stack createIdealLayout(Stack const& _operationOutput, Stack const& _post, Callable _generateSlotOnTheFly) { - if (_layout.empty()) + struct PreviousSlot { size_t slot; }; + + // Determine the number of slots that have to be on stack before executing the operation. + // That is slots that should not be generated on the fly and are not outputs of the operation. + size_t previousLayoutSize = _post.size(); + for (auto const& slot: _post) + if (util::findOffset(_operationOutput, slot) || _generateSlotOnTheFly(slot)) + --previousLayoutSize; + + // The symbolic layout directly after the operation has the form + // PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output] + auto layout = ranges::views::iota(0u, previousLayoutSize) | + ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) | + ranges::to>>; + layout += _operationOutput; + + // Shortcut for trivial case. + if (layout.empty()) return Stack{}; + // Next we will shuffle the layout to the post stack using ShuffleOperations + // that are aware of PreviousSlot's. struct ShuffleOperations { vector>& layout; @@ -145,20 +167,24 @@ Stack createIdealLayout(Stack const& _post, vector::shuffle(_layout, _post, _generateSlotOnTheFly); + Shuffler::shuffle(layout, _post, _generateSlotOnTheFly); // Now we can construct the ideal layout before the operation. - // "layout" has the declared variables in the desired position and - // for any PreviousSlot{x}, x yields the ideal place of the slot before the declaration. + // "layout" has shuffled the PreviousSlot{x} to new places using minimal operations to move the operation + // output in place. The resulting permutation of the PreviousSlot yields the ideal positions of slots + // before the operation, i.e. if PreviousSlot{2} is at a position at which _post contains VariableSlot{"tmp"}, + // then we want the variable tmp in the slot at offset 2 in the layout before the operation. vector> idealLayout(_post.size(), nullopt); - for (auto const& [slot, idealPosition]: ranges::zip_view(_post, _layout)) + for (auto const& [slot, idealPosition]: ranges::zip_view(_post, layout)) if (PreviousSlot* previousSlot = std::get_if(&idealPosition)) idealLayout.at(previousSlot->slot) = slot; + // The tail of layout must have contained the operation outputs and will not have been assigned slots in the last loop. while (!idealLayout.empty() && !idealLayout.back()) idealLayout.pop_back(); + yulAssert(idealLayout.size() == previousLayoutSize, ""); + return idealLayout | ranges::views::transform([](optional s) { yulAssert(s, ""); return *s; @@ -168,8 +194,6 @@ Stack createIdealLayout(Stack const& _post, vector 12 && canBeFreelyGenerated(_slot); @@ -177,27 +201,23 @@ Stack StackLayoutGenerator::propagateStackThroughOperation(Stack _exitStack, CFG return false; }; - size_t previousLayoutSize = stack.size(); - for (auto const& slot: stack) - if (util::findOffset(_operation.output, slot) || generateSlotOnTheFly(slot)) - --previousLayoutSize; - - auto layout = ranges::views::iota(0u, previousLayoutSize) | - ranges::views::transform([](size_t _index) { return PreviousSlot{_index}; }) | - ranges::to>>; - // The call produces a known sequence of values. - layout += _operation.output; - - stack = createIdealLayout(stack, layout, generateSlotOnTheFly); + // Determine the ideal permutation of the slots in _exitLayout that are not operation outputs (and not to be + // generated on the fly), s.t. shuffling the stack+_operation.output to _exitLayout is cheap. + Stack stack = createIdealLayout(_operation.output, _exitStack, generateSlotOnTheFly); + // Make sure the resulting previous slots do not overlap with any assignmed variables. if (auto const* assignment = get_if(&_operation.operation)) for (auto& stackSlot: stack) if (auto const* varSlot = get_if(&stackSlot)) yulAssert(!util::findOffset(assignment->variables, *varSlot), ""); - for (StackSlot const& input: _operation.input) - stack.emplace_back(input); + // Since stack+_operation.output can be easily shuffled to _exitLayout, the desired layout before the operation + // is stack+_operation.input; + stack += _operation.input; + // Store the exact desired operation entry layout. The stored layout will be recreated by the code transform + // before executing the operation. However, this recreation can produce slots that can be freely generated or + // are duplicated, i.e. we can compress the stack afterwards without causing problems for code generation later. m_layout.operationEntryLayout[&_operation] = stack; // Remove anything from the stack top that can be freely generated or dupped from deeper on the stack. diff --git a/libyul/backends/evm/StackLayoutGenerator.h b/libyul/backends/evm/StackLayoutGenerator.h index 6e374a767..908ef60b5 100644 --- a/libyul/backends/evm/StackLayoutGenerator.h +++ b/libyul/backends/evm/StackLayoutGenerator.h @@ -54,6 +54,7 @@ private: /// @returns the optimal entry stack layout, s.t. @a _operation can be applied to it and /// the result can be transformed to @a _exitStack with minimal stack shuffling. + /// Simultaneously stores the entry layout required for executing the operation in m_layout. Stack propagateStackThroughOperation(Stack _exitStack, CFG::Operation const& _operation); /// @returns the desired stack layout at the entry of @a _block, assuming the layout after