Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 18 additions & 3 deletions libyul/backends/evm/ssa/CodeTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,13 @@ void CodeTransform::operator()(SSACFG::BlockId const _blockId)
// Shuffle to the block's exit layout before dispatching the exit.
// This ensures the condition is on top for ConditionalJump, phi pre-images are
// in the right positions for jumps, and return values are accessible for FunctionReturn.
StackData const preShuffle = m_stack.data();
auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle(m_stack, blockLayout->exitIn);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA codegen block {} exit shuffle", _blockId.value),
shuffleResult,
fmt::format("source stack {}, target stack {}", stackToString(preShuffle), stackToString(blockLayout->exitIn))
);

// handle the block exit
std::visit(util::GenericVisitor{ [this, &_blockId](auto const& exit) { (*this)(_blockId, exit); } }, block.exit);
Expand All @@ -211,8 +216,13 @@ void CodeTransform::operator()(SSACFG::OperationId _opId, StackData const& _oper

// prepare stack for operation
{
StackData const preShuffle = m_stack.data();
auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle(m_stack, _operationInputLayout);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA codegen operation {} input shuffle", _opId.value),
shuffleResult,
fmt::format("source stack {}, target stack {}", stackToString(preShuffle), stackToString(_operationInputLayout))
);
}

// check that the assembly stack height corresponds to the stack size after shuffling
Expand Down Expand Up @@ -400,8 +410,13 @@ void CodeTransform::prepareBlockExitStack(StackData const& _target, PhiInverse c
auto const pulledBackTarget = stackPreImage(_target, _phiInverse);
// shuffle to target
{
StackData const preShuffle = m_stack.data();
auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle(m_stack, pulledBackTarget);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
"SSA codegen branch-exit shuffle",
shuffleResult,
fmt::format("source stack {}, target stack {}", stackToString(preShuffle), stackToString(pulledBackTarget))
);
}
// check that shuffling was successful
assertLayoutCompatibility(m_stack.data(), pulledBackTarget);
Expand Down
2 changes: 2 additions & 0 deletions libyul/backends/evm/ssa/CodeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include <libevmasm/Instruction.h>

#include <fmt/format.h>

namespace solidity::yul
{
struct BuiltinContext;
Expand Down
44 changes: 40 additions & 4 deletions libyul/backends/evm/ssa/StackLayoutGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
#include <range/v3/to_container.hpp>

#include <boost/container/flat_map.hpp>

#include <fmt/format.h>

#include <queue>

using namespace solidity::yul::ssa;
Expand Down Expand Up @@ -198,7 +201,15 @@ void StackLayoutGenerator::defineStackIn(SSACFG::BlockId const& _blockId)
{},
proposals[i].size()
);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA stack-in proposal evaluation for block {}", _blockId.value),
shuffleResult,
fmt::format(
"candidate {}, source proposal {}",
stackToString(proposals[i]),
stackToString(proposals[j])
)
);
cumulativeCost += stack.callbacks().opGas;
}
cumulativeCosts[i] = cumulativeCost;
Expand Down Expand Up @@ -261,13 +272,23 @@ void StackLayoutGenerator::visitBlock(SSACFG::BlockId const& _blockId)
m_hasFunctionReturnLabel
);
{
StackData const preShuffle = stack.data();
auto const shuffleResult = StackShuffler<StackType::Callbacks>::shuffle(
stack,
requiredStackTop,
opLiveOutWithoutOutputs,
targetSize
);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA stack layout operation {} shuffle in block {}", operationIndex, _blockId.value),
shuffleResult,
fmt::format(
"source stack {}, target args {}, target size {}",
stackToString(preShuffle),
stackToString(requiredStackTop),
targetSize
)
);
}

blockLayout.operationIn.push_back(currentStackData);
Expand Down Expand Up @@ -297,10 +318,20 @@ void StackLayoutGenerator::visitBlock(SSACFG::BlockId const& _blockId)
false,
m_hasFunctionReturnLabel
);
StackData const preShuffle = stack.data();
auto const shuffleResult = StackShuffler<StackType::Callbacks>::shuffle(
stack, {condition}, blockLiveOut, targetSize
);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA conditional-jump shuffle in block {}", _blockId.value),
shuffleResult,
fmt::format(
"source stack {}, target args {}, target size {}",
stackToString(preShuffle),
stackToString(StackData{condition}),
targetSize
)
);
}

yulAssert(!stack.empty() && stack.top().isValueID() && stack.top().valueID() == _cJump.condition);
Expand All @@ -322,8 +353,13 @@ void StackLayoutGenerator::visitBlock(SSACFG::BlockId const& _blockId)
// in case there are return values, let's bring the function return label to the top
StackData returnStack = _functionReturn.returnValues | ranges::views::transform(StackSlot::makeValueID) | ranges::to<std::vector>;
returnStack.push_back(StackSlot::makeFunctionReturnLabel(m_graphID));
StackData const preShuffle = stack.data();
auto const shuffleResult = StackShuffler<StackType::Callbacks>::shuffle(stack, returnStack);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
fmt::format("SSA function-return shuffle in block {}", _blockId.value),
shuffleResult,
fmt::format("source stack {}, target stack {}", stackToString(preShuffle), stackToString(returnStack))
);
blockLayout.exitIn = currentStackData;
},
[&](SSACFG::BasicBlock::Jump const& _jump) {
Expand Down
73 changes: 72 additions & 1 deletion libyul/backends/evm/ssa/StackUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <libyul/backends/evm/ssa/StackShuffler.h>

#include <liblangutil/Exceptions.h>

#include <libevmasm/GasMeter.h>

#include <range/v3/numeric/accumulate.hpp>
Expand All @@ -32,6 +34,45 @@

using namespace solidity::yul::ssa;

namespace
{

char const* stackShufflerStatusToString(StackShufflerResult::Status _status)
{
switch (_status)
{
case StackShufflerResult::Status::Continue:
return "Continue";
case StackShufflerResult::Status::Admissible:
return "Admissible";
case StackShufflerResult::Status::StackTooDeep:
return "StackTooDeep";
case StackShufflerResult::Status::MaxIterationsReached:
return "MaxIterationsReached";
}
solAssert(false, "Unknown StackShufflerResult status.");
}

std::string formatShuffleFailure(
std::string_view _context,
StackShufflerResult const& _shuffleResult,
std::string_view _details
)
{
std::string message = fmt::format(
"{} failed with {}",
_context,
stackShufflerStatusToString(_shuffleResult.status)
);
if (_shuffleResult.status == StackShufflerResult::Status::StackTooDeep)
message += fmt::format(" (culprit: {})", slotToString(_shuffleResult.culprit));
if (!_details.empty())
message += fmt::format(": {}", _details);
return message;
}

}

void GasAccumulatingCallbacks::swap(StackDepth _depth)
{
opGas += evmasm::GasMeter::swapGas(_depth.value, cfg.evmDialect.evmVersion());
Expand Down Expand Up @@ -77,6 +118,21 @@ StackData solidity::yul::ssa::stackPreImage(StackData _stack, PhiInverse const&
return _stack;
}

void solidity::yul::ssa::requireAdmissibleShuffle(
std::string_view _context,
StackShufflerResult const& _shuffleResult,
std::string _details
)
{
if (_shuffleResult.status == StackShufflerResult::Status::Admissible)
return;

std::string const message = formatShuffleFailure(_context, _shuffleResult, _details);
if (_shuffleResult.status == StackShufflerResult::Status::StackTooDeep)
solThrow(langutil::StackTooDeepError, message);
solThrow(langutil::InternalCompilerError, message);
}

std::size_t solidity::yul::ssa::findOptimalTargetSize
(
StackData const& _stackData,
Expand Down Expand Up @@ -107,7 +163,22 @@ std::size_t solidity::yul::ssa::findOptimalTargetSize
data = _stackData;
Stack<OpsCountingCallbacks> countOpsStack(data, {});
auto const shuffleResult = StackShuffler<OpsCountingCallbacks>::shuffle(countOpsStack, _targetArgs, _targetLiveOut, _targetSize);
yulAssert(shuffleResult.status == StackShufflerResult::Status::Admissible);
requireAdmissibleShuffle(
"SSA stack target-size evaluation",
shuffleResult,
fmt::format(
"source stack {}, target args {}, live-out {{{}}}, target size {}",
stackToString(_stackData),
stackToString(_targetArgs),
fmt::join(
_targetLiveOut | ranges::views::keys | ranges::views::transform(
[](auto const& _value) { return slotToString(StackSlot::makeValueID(_value)); }
),
", "
),
_targetSize
)
);
yulAssert(countOpsStack.size() == _targetSize);
return countOpsStack.callbacks().numOps;
};
Expand Down
9 changes: 9 additions & 0 deletions libyul/backends/evm/ssa/StackUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
#include <libyul/backends/evm/ssa/Stack.h>

#include <string>
#include <string_view>
#include <vector>

namespace solidity::yul::ssa
{

struct StackShufflerResult;

class ValidationResult
{
public:
Expand Down Expand Up @@ -62,6 +65,12 @@ struct GasAccumulatingCallbacks
/// Transform stack data by replacing all its phi variables with their respective preimages.
StackData stackPreImage(StackData _stack, PhiInverse const& _phiInverse);

void requireAdmissibleShuffle(
std::string_view _context,
StackShufflerResult const& _shuffleResult,
std::string _details = {}
);

std::size_t findOptimalTargetSize(
StackData const& _stackData,
StackData const& _targetArgs,
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ set(libyul_sources
libyul/ssa/StackLayoutGeneratorTest.h
libyul/ssa/StackShufflerTest.cpp
libyul/ssa/StackShufflerTest.h
libyul/ssa/StackUtilsTest.cpp
)
detect_stray_source_files("${libyul_sources}" "libyul/")

Expand Down
115 changes: 115 additions & 0 deletions test/libyul/ssa/StackUtilsTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
This file is part of solidity.

solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/**
* Unit tests for SSA stack utility helpers.
*/

#include <liblangutil/Exceptions.h>

#include <libyul/backends/evm/ssa/SSACFG.h>
#include <libyul/backends/evm/ssa/StackShuffler.h>
#include <libyul/backends/evm/ssa/StackUtils.h>

#include <boost/test/unit_test.hpp>

using namespace solidity::langutil;
using namespace solidity::yul::ssa;

namespace solidity::yul::ssa::test
{

namespace
{

std::string errorMessage(util::Exception const& _exception)
{
return _exception.comment() ? *_exception.comment() : "";
}

StackSlot makeVariable(unsigned _id)
{
return StackSlot::makeValueID(SSACFG::ValueId::makeVariable(_id));
}

}

BOOST_AUTO_TEST_SUITE(StackUtilsTest)

BOOST_AUTO_TEST_CASE(requireAdmissibleShuffle_throws_stack_too_deep_error)
{
StackShufflerResult const stackTooDeepResult{
.status = StackShufflerResult::Status::StackTooDeep,
.culprit = makeVariable(7)
};
BOOST_CHECK_EXCEPTION(
requireAdmissibleShuffle(
"SSA test shuffle",
stackTooDeepResult,
"source stack [v1]"
),
langutil::StackTooDeepError,
[](langutil::StackTooDeepError const& _exception)
{
BOOST_TEST(
errorMessage(_exception) ==
"SSA test shuffle failed with StackTooDeep (culprit: v7): source stack [v1]"
);
return true;
}
);
}

BOOST_AUTO_TEST_CASE(requireAdmissibleShuffle_throws_internal_compiler_error_for_max_iterations)
{
StackShufflerResult const maxIterationsResult{.status = StackShufflerResult::Status::MaxIterationsReached};
BOOST_CHECK_EXCEPTION(
requireAdmissibleShuffle(
"SSA test shuffle",
maxIterationsResult,
"target size 17"
),
InternalCompilerError,
[](InternalCompilerError const& _exception)
{
BOOST_TEST(errorMessage(_exception) == "SSA test shuffle failed with MaxIterationsReached: target size 17");
return true;
}
);
}

BOOST_AUTO_TEST_CASE(requireAdmissibleShuffle_throws_internal_compiler_error_for_continue)
{
StackShufflerResult const continueResult{.status = StackShufflerResult::Status::Continue};
BOOST_CHECK_EXCEPTION(
requireAdmissibleShuffle(
"SSA test shuffle",
continueResult,
"target size 23"
),
InternalCompilerError,
[](InternalCompilerError const& _exception)
{
BOOST_TEST(errorMessage(_exception) == "SSA test shuffle failed with Continue: target size 23");
return true;
}
);
}

BOOST_AUTO_TEST_SUITE_END()

}