FlattenCallExpr Pass¶
Flattens nested call expressions into three-address code form.
Overview¶
This pass ensures that call expressions do not appear in nested contexts by extracting them into temporary variables. It enforces a three-address code constraint where:
- Call arguments cannot be calls
- If conditions cannot be calls
- For loop ranges (start/stop/step) cannot be calls
- Binary/unary expression operands cannot be calls
- Return values cannot be calls
Requires: TypeChecked, SSAForm properties. These properties are automatically verified at BASIC level once produced; use a VerificationInstrument via PassContext if you need required properties to be validated before running passes.
When to use: Typically schedule this pass after the type-checking pass and before code generation to simplify downstream analysis and code generation; this ordering is a convention rather than an automatically enforced requirement.
API¶
| C++ | Python | Level |
|---|---|---|
pass::FlattenCallExpr() |
passes.flatten_call_expr() |
Function-level |
Factory function:
Python usage:
from pypto.pypto_core import passes
flatten_pass = passes.flatten_call_expr()
program_flat = flatten_pass(program)
Algorithm¶
- Detect Nested Calls: Identify call expressions in nested contexts
- Extract to Temps: Create temporary variables (named like
t__tmp_v0,t__tmp_v1, etc.) - Insert AssignStmt: Add assignment statements before the original statement
- Replace with Var: Replace nested call with temporary variable reference
- Handle Control Flow: For if/for statements, insert extracted temporaries directly before the control-flow node in the enclosing
SeqStmts
Extraction locations:
- Before AssignStmt/EvalStmt: Insert directly before
- Before IfStmt/ForStmt: Insert as sibling statements in the enclosing
SeqStmts - Inside ScopeStmt (
pl.at()): Temporaries are always inserted inside the scope body, preserving execution-context boundaries
Example¶
Nested Call Arguments¶
Before:
After:
Nested Call in If Condition¶
Before:
After:
Multiple Nested Calls¶
Before:
After:
Nested in Binary Expression¶
Before:
After:
Direct Call in Return¶
Before:
After:
Without this normalization, the Call reaches codegen still wrapped inside a ReturnStmt. Some codegen paths (notably OrchestrationCodegen::VisitStmt_(ReturnStmtPtr)) treat ReturnStmt as a no-op and would silently drop the kernel dispatch.
Nested Call inside Scope Block¶
Before:
After:
with pl.at(level=pl.Level.CORE_GROUP):
t__tmp_v0 = cast(x, BF16)
result = assemble(target, t__tmp_v0, offsets)
The temporary stays inside the pl.at() block. Without this scope-awareness, t__tmp_v0 = cast(...) would be hoisted outside the scope, causing a "Misplaced tensor op" error during codegen.
Implementation¶
Header: include/pypto/ir/transforms/passes.h
Implementation: src/ir/transforms/flatten_call_expr.cpp
- Uses IRMutator to traverse expressions
- Maintains temporary variable counter
- Collects extracted assignments
- Rebuilds statements with flattened expressions
Python binding: python/bindings/modules/passes.cpp
Tests: tests/ut/ir/transforms/test_flatten_call_expr_pass.py
- Tests call arguments extraction
- Tests if condition extraction
- Tests for range extraction
- Tests binary/unary expression extraction
- Tests multiple nested calls
- Tests scope-aware extraction (
pl.at()blocks)
Error Types¶
The pass can detect and report nested call violations via NestedCallErrorType:
CALL_IN_CALL_ARGS: Call in call argumentsCALL_IN_IF_CONDITION: Call in if conditionCALL_IN_FOR_RANGE: Call in for rangeCALL_IN_BINARY_EXPR: Call in binary expressionCALL_IN_UNARY_EXPR: Call in unary expression