Class AbstractControlFlowAsmGenerator

java.lang.Object
org.ek9lang.compiler.backend.jvm.AbstractAsmGenerator
org.ek9lang.compiler.backend.jvm.AbstractControlFlowAsmGenerator
Direct Known Subclasses:
DoWhileLoopAsmGenerator, ForRangePolymorphicAsmGenerator, GuardedAssignmentAsmGenerator, IfElseAsmGenerator, LogicalOperationAsmGenerator, QuestionOperatorAsmGenerator, SwitchAsmGenerator, TryCatchAsmGenerator, WhileLoopAsmGenerator

abstract class AbstractControlFlowAsmGenerator extends AbstractAsmGenerator
Abstract base class for control flow bytecode generators. Provides common patterns for: - Label creation with consistent naming - Condition evaluation processing - Result variable copying - Stack frame validation helpers

All control flow generators (question operator, if/else, switch, loops, try/catch) extend this class to reuse common logic and ensure consistent stack frame handling.

Stack Frame Invariant: All helper methods maintain the invariant that the JVM operand stack is empty before and after their execution. Results are always stored in local variables, never left on the stack. This ensures correct stack frame balancing at control flow merge points.

Label Naming Convention (MANDATORY)

ALL control flow generators MUST use scopeId from IR instructions for label uniqueness. NEVER use result variables, loop variable names, or any other identifiers for labels.

// CORRECT - uses scopeId from IR instruction
final var label = createControlFlowLabel("while_start", instr.getScopeId());
final var label = createControlFlowLabel("for_asc", scopeMetadata.loopScopeId());
final var label = createControlFlowLabel("if_next", conditionCase.caseScopeId());

// WRONG - uses result variable (fragile, inconsistent)
final var label = createControlFlowLabel("prefix", instr.getResult());

// WRONG - uses variable name (collides in nested loops)
final var label = createControlFlowLabel("for_asc", loopVariableName);

Rationale:

  • Guaranteed Uniqueness: Scope IDs are globally unique per method by IR generator contract
  • Semantic Clarity: Scope IDs represent lexical structure, not operational details
  • Consistency: All loop and conditional generators use this pattern
  • Robustness: Immune to IR optimizations that might reuse temp variables
  • Nested Safety: Each nested scope gets unique ID, preventing label collisions

For Nested Constructs:

  • Nested loops with same variable name (e.g., "for i" inside "for i"): Each has unique loopScopeId
  • Nested conditions: Each branch has unique caseScopeId
  • No collisions possible, even with identical variable names

See LABEL_NAMING_CONVENTION.md for complete documentation and examples.

  • Field Details

    • context

      protected final BytecodeGenerationContext context
      Context stack for nested control flow. Allows nested generators to query enclosing control flow context.
  • Constructor Details

  • Method Details

    • createControlFlowLabel

      protected org.objectweb.asm.Label createControlFlowLabel(String prefix, String uniqueId)
      Generate unique label for control flow construct. Pattern: prefix_[dispatchCase_]scopeId

      CRITICAL: Always use scopeId as the uniqueId parameter.

      FOR_RANGE Dispatch: When inside FOR_RANGE dispatch case, labels include dispatch case suffix to ensure uniqueness when same body IR is processed three times:

      // Outside FOR_RANGE: if_next_scope_5
      // Equal case:       if_next_equal_scope_5
      // Ascending case:   if_next_ascending_scope_5
      // Descending case:  if_next_descending_scope_5
      
      // CORRECT - main control flow structure
      createControlFlowLabel("while_start", instr.getScopeId());
      createControlFlowLabel("for_asc", scopeMetadata.loopScopeId());
      
      // CORRECT - condition cases
      createControlFlowLabel("if_next", conditionCase.caseScopeId());
      
      // WRONG - uses result variable (fragile)
      createControlFlowLabel("prefix", instr.getResult());
      
      // WRONG - uses variable name (collides in nested loops)
      createControlFlowLabel("for_asc", loopVariableName);
      

      Why scopeId?

      • Globally unique per method (guaranteed by IR generator)
      • Semantically appropriate (represents lexical scope)
      • Consistent across all control flow generators
      • Robust to IR refactoring (e.g., temp variable reuse)
      • Safe for nested constructs with same variable names
      Parameters:
      prefix - Label prefix identifying the construct type (e.g., "while_start", "if_end", "for_asc")
      uniqueId - MUST be scopeId from IR instruction (never use result variable or variable name)
      Returns:
      JVM Label for bytecode generation
    • processInstructions

      protected void processInstructions(List<IRInstr> instructions)
      Process list of IR instructions via recursive delegation to OutputVisitor.

      Each instruction knows how to generate its own bytecode via accept(visitor). This enables polymorphic dispatch to the correct generator:

      • CALL instructions → CallInstrAsmGenerator
      • LOAD/STORE/RETAIN/RELEASE → MemoryInstrAsmGenerator
      • LOAD_LITERAL → LiteralInstrAsmGenerator
      • ASSERT → BranchInstrAsmGenerator
      • SCOPE_ENTER/EXIT → ScopeInstrAsmGenerator
      • CONTROL_FLOW_CHAIN → ControlFlowChainAsmGenerator

      All instructions maintain stack-empty invariant.

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty (all results in local variables)

      Parameters:
      instructions - List of IR instructions to process
    • processConditionEvaluation

      protected void processConditionEvaluation(List<IRInstr> conditionEvaluation)
      Process condition evaluation instructions via recursive visiting. Semantic wrapper around processInstructions(java.util.List) for clarity.

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty (all results in local variables)

      Parameters:
      conditionEvaluation - List of IR instructions to evaluate condition
    • processBodyEvaluation

      protected void processBodyEvaluation(List<IRInstr> bodyEvaluation)
      Process body evaluation instructions via recursive visiting. Semantic wrapper around processInstructions(java.util.List) for clarity.

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty (all results in local variables)

      Parameters:
      bodyEvaluation - List of IR instructions for case/branch body
    • branchIfFalse

      protected void branchIfFalse(String primitiveCondition, org.objectweb.asm.Label falseLabel)
      Load primitive condition (int) and branch if false (0).

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty (IFEQ consumes the int)

      Control Flow: If condition is 0 (false), jumps to falseLabel. Otherwise, execution continues to next instruction.

      Parameters:
      primitiveCondition - Variable name holding primitive int (0 or 1)
      falseLabel - Label to jump to if condition is false (0)
    • branchIfTrue

      protected void branchIfTrue(String primitiveCondition, org.objectweb.asm.Label trueLabel)
      Load primitive condition (int) and branch if true (non-zero).

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty (IFNE consumes the int)

      Control Flow: If condition is non-zero (true), jumps to trueLabel. Otherwise, execution continues to next instruction.

      Used by logical OR operator for short-circuit optimization: If left operand is true, skip right evaluation and return left.

      Parameters:
      primitiveCondition - Variable name holding primitive int (0 or 1)
      trueLabel - Label to jump to if condition is true (non-zero)
    • copyResultVariable

      protected void copyResultVariable(String sourceVar, String destVar)
      Copy result from source variable to destination variable (object reference).

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty

      Bytecode: ALOAD source_index, ASTORE dest_index

      Parameters:
      sourceVar - Variable holding the result to copy
      destVar - Variable to store the result into
    • jumpTo

      protected void jumpTo(org.objectweb.asm.Label label)
      Generate unconditional jump to label.

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: control transferred (stack irrelevant after GOTO)

      Control Flow: Execution continues at the target label. Stack at target must match stack at GOTO (empty in our convention).

      Parameters:
      label - Target label for unconditional jump
    • placeLabel

      protected void placeLabel(org.objectweb.asm.Label label)
      Place label at current bytecode position.

      Stack Frame Invariant: Pre-condition: stack state depends on incoming control flow paths Post-condition: stack state unchanged (label is just a position marker)

      CRITICAL: All control flow paths reaching this label MUST have the same stack depth. In our convention, all paths arrive with empty stack.

      Parameters:
      label - Label to place at current position
    • processConditionCase

      protected void processConditionCase(ConditionCaseDetails conditionCase, org.objectweb.asm.Label nextCaseLabel, org.objectweb.asm.Label endLabel, String resultVar)
      Process a condition case: evaluate condition, branch, execute body. Common pattern used by question operator, if/else, switch.

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty at both nextCaseLabel and after GOTO

      Control Flow: 1. Evaluate condition → result in variable, stack empty 2. Branch to nextCaseLabel if false → stack empty at label 3. Execute body → result in variable, stack empty 4. Copy result to overall result → stack empty 5. Jump to endLabel → stack empty at label 6. Place nextCaseLabel → stack empty (from branch in step 2)

      Parameters:
      conditionCase - IR details for this case
      nextCaseLabel - Label to jump if condition is false
      endLabel - Label to jump after body execution
      resultVar - Overall result variable (null if no result)
    • processConditionCaseWithoutLabelPlacement

      protected void processConditionCaseWithoutLabelPlacement(ConditionCaseDetails conditionCase, org.objectweb.asm.Label nextCaseLabel, org.objectweb.asm.Label endLabel, String resultVar)
      Process a condition case WITHOUT placing the nextCaseLabel. Use this when the caller needs to control label placement (e.g., if/else-if chains).

      Stack Frame Invariant: Pre-condition: stack is empty Post-condition: stack is empty after GOTO

      Parameters:
      conditionCase - IR details for this case
      nextCaseLabel - Label to jump if condition is false
      endLabel - Label to jump after body execution
      resultVar - Overall result variable (null if no result)