FuelLabs/sway · error

`PushAll` and `PopAll` don't belong in control flow ops sinc

Error message

`PushAll` and `PopAll` don't belong in control flow ops since they're not about control flow

What it means

This unreachable! guards worst_case_instruction_size, which computes the worst-case concrete instruction count for each allocated abstract op. PushAll and PopAll are abstract stack-management ops, not control-flow ops, so they must never appear in the ControlFlowOp arm of this match. Reaching this panic means an abstract instruction set was built with PushAll/PopAll ops inside the control-flow op representation, i.e. an internal codegen misclassification bug.

Source

Thrown at sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs:423

            // This is a concrete op, size is fixed.
            Either::Left(_) => 1,

            // Worst case for jump is 2 opcodes, and 3 for calls.
            Either::Right(Jump { ref type_, .. }) => match type_ {
                JumpType::Unconditional => 2,
                JumpType::NotZero(_) => 2,
                JumpType::Call => 3,
            },
            Either::Right(JumpToAddr(..)) => 1,
            Either::Right(ReturnFromCall { .. }) => 1,
            Either::Right(Comment) => 0,
            Either::Right(DataSectionOffsetPlaceholder) => {
                // If the placeholder is 32 bits, this is 1. If 64, this should be 2. We use LW
                // to load the data, which loads a whole word, so for now this is 2.
                2
            }
            Either::Right(ConfigurablesOffsetPlaceholder) => 2,
            Either::Right(PushAll(_)) | Either::Right(PopAll(_)) => unreachable!(
                "`PushAll` and `PopAll` don't belong in control flow ops \
                        since they're not about control flow"
            ),
        }
    }

    // Actual size of an instruction.
    //
    // **Note that this return incorrect values for far jumps, they must be handled separately.**
    // The return value is in concrete instructions, i.e. units of 4 bytes.
    fn instruction_size_not_far_jump(op: &AllocatedAbstractOp, data_section: &DataSection) -> u64 {
        use ControlFlowOp::*;
        match op.opcode {
            Either::Right(Label(_)) => 0,

            // A special case for LoadDataId which may be 1 or 2 ops, depending on the source size.
            Either::Left(AllocatedInstruction::LoadDataId(_, ref data_id)) => {
                let has_copy_type = data_section.has_copy_type(data_id).expect(

View on GitHub (pinned to dad95cc42b)

Solutions

  1. Find where the abstract op was classified as a ControlFlowOp and move PushAll/PopAll to the plain (concrete) instruction category
  2. Ensure lowering passes never wrap PushAll/PopAll in Either::Right (control flow) — they should be Either::Left concrete ops
  3. Add a debug-time classification check when AllocatedAbstractOp values are constructed so misclassification is caught at creation rather than during size computation
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs:423 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of FuelLabs/sway@dad95cc42b (2026-09-04). Data as JSON: /api/errors/a0097561584b8e6f. Report an issue: GitHub.