Skip to content

Commit 13fb55f

Browse files
committed
[compiler] isolate mutable state across dynamic if branches
Rebuild Python container state from carried IR values for each branch so in-place updates during tracing cannot leak into sibling branches.
1 parent ac227c3 commit 13fb55f

3 files changed

Lines changed: 70 additions & 2 deletions

File tree

python/flydsl/compiler/ast_rewriter.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -738,9 +738,11 @@ def scf_if_dispatch(
738738

739739
def _emit_branch(fn, block, label):
740740
with ir.InsertionPoint(block):
741-
branch_result = ReplaceIfWithDispatch._call_branch(fn, result_names, result_values)
741+
branch_inputs = _pack_states(state_raw, exemplar)
742+
branch_result = ReplaceIfWithDispatch._call_branch(fn, result_names, branch_inputs)
743+
branch_map = dict(zip(result_names, branch_inputs))
742744
branch_values = ReplaceIfWithDispatch._normalize_branch_result(
743-
branch_result, result_names, result_map, label
745+
branch_result, result_names, branch_map, label
744746
)
745747
scf.YieldOp(
746748
_unpack_branch_outputs(result_names, branch_values, exemplar, label),

tests/system/test_dynamic_controlflow_list_carry_e2e.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ def _run_if_list(Out: fx.Tensor, flag: fx.Int32, stream: fx.Stream = fx.Stream(N
4949
_kernel_if_list(Out, flag).launch(grid=(1, 1, 1), block=(1, 1, 1), stream=stream.value)
5050

5151

52+
@flyc.kernel
53+
def _kernel_if_inplace_list(Out: fx.Tensor, flag: fx.Int32):
54+
lst = [fx.Int32(1), fx.Int32(2)]
55+
if flag > fx.Int32(0):
56+
lst[0] = fx.Int32(10)
57+
Out[0] = lst[0]
58+
Out[1] = lst[1]
59+
60+
61+
@flyc.jit
62+
def _run_if_inplace_list(Out: fx.Tensor, flag: fx.Int32, stream: fx.Stream = fx.Stream(None)):
63+
_kernel_if_inplace_list(Out, flag).launch(grid=(1, 1, 1), block=(1, 1, 1), stream=stream.value)
64+
65+
5266
# ── dynamic for carrying a list ─────────────────────────────────────────────
5367

5468

@@ -286,6 +300,18 @@ def test_if_list_not_taken(self):
286300
torch.cuda.synchronize()
287301
assert out[0].item() == 1 and out[1].item() == 2, out.tolist()
288302

303+
def test_if_inplace_list_taken(self):
304+
out, t_out = _out(2)
305+
_run_if_inplace_list(t_out, fx.Int32(1))
306+
torch.cuda.synchronize()
307+
assert out.tolist() == [10, 2], out.tolist()
308+
309+
def test_if_inplace_list_not_taken(self):
310+
out, t_out = _out(2)
311+
_run_if_inplace_list(t_out, fx.Int32(0))
312+
torch.cuda.synchronize()
313+
assert out.tolist() == [1, 2], out.tolist()
314+
289315
def test_for_list(self):
290316
out, t_out = _out(2)
291317
_run_for_list(t_out, fx.Int32(5)) # +1/-1 x5 -> [5, 95]

tests/unit/test_dynamic_controlflow_list_carry.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,46 @@ def test_if_carries_list():
7474
assert "-> (i32, i32)" in text
7575

7676

77+
def test_if_inplace_list_updates_do_not_contaminate_branches():
78+
with Context(), Location.unknown():
79+
module = Module.create()
80+
i1 = IntegerType.get_signless(1)
81+
with InsertionPoint(module.body):
82+
f = func.FuncOp("if_inplace_list", FunctionType.get([i1], []))
83+
entry = f.add_entry_block()
84+
with InsertionPoint(entry):
85+
cond = entry.arguments[0]
86+
original_first = _i32(1)
87+
lst = [original_first, _i32(2)]
88+
branch_inputs = []
89+
90+
def then_branch(names, branch_lst):
91+
branch_inputs.append(branch_lst)
92+
branch_lst[0] = _i32(10)
93+
return {"lst": branch_lst}
94+
95+
def else_branch(names, branch_lst):
96+
branch_inputs.append(branch_lst)
97+
assert branch_lst[0].ir_value().owner == original_first.ir_value().owner
98+
return {"lst": branch_lst}
99+
100+
out = ReplaceIfWithDispatch.scf_if_dispatch(
101+
cond,
102+
then_branch,
103+
else_branch,
104+
result_names=("lst",),
105+
result_values=(lst,),
106+
)
107+
assert lst[0] is original_first
108+
assert branch_inputs[0] is not lst
109+
assert branch_inputs[1] is not lst
110+
assert branch_inputs[0] is not branch_inputs[1]
111+
assert isinstance(out, list) and len(out) == 2
112+
func.ReturnOp([])
113+
114+
assert module.operation.verify()
115+
116+
77117
def test_if_carries_nested_list():
78118
with Context(), Location.unknown():
79119
module = Module.create()

0 commit comments

Comments
 (0)