A fold after inlining appears to risk using a pre-branch initializer instead of the CFG join block argument. The reduced pattern is:
x = default
if flag:
x = dynamic_value
return use(x)
After inlining, the IR correctly represents x as a join block argument. Any fold of use(x) after the join must use that block argument, not the initializer from before the branch.
Here is a pure-Kirin regression test:
from kirin import passes
from kirin.passes.inline import InlinePass
from kirin.prelude import basic
@basic
def helper(flag: bool, dynamic_value: float) -> float:
x = 10.0
if flag:
x = dynamic_value + 1.0
return x - 2.0
@basic
def main(flag: bool, dynamic_value: float) -> float:
return helper(flag, dynamic_value)
def test_inline_fold_preserves_cfg_join_argument():
assert main(True, 3.0) == 2.0
assert main(False, 3.0) == 8.0
InlinePass(main.dialects)(main)
passes.Fold(main.dialects)(main)
assert main(True, 3.0) == 2.0
assert main(False, 3.0) == 8.0
The key invariant is that a fold after a CFG join must respect the join block argument. It must not replace the joined value with the pre-branch initializer unless the branch value is proven identical on all incoming edges.
#322 is possible related
A fold after inlining appears to risk using a pre-branch initializer instead of the CFG join block argument. The reduced pattern is:
After inlining, the IR correctly represents x as a join block argument. Any fold of use(x) after the join must use that block argument, not the initializer from before the branch.
Here is a pure-Kirin regression test:
The key invariant is that a fold after a CFG join must respect the join block argument. It must not replace the joined value with the pre-branch initializer unless the branch value is proven identical on all incoming edges.
#322 is possible related