Skip to content

Commit 1357b8f

Browse files
feat: unify LiteralTuple representation to always use alloca and add function-context guard
1 parent 4acd6dc commit 1357b8f

File tree

2 files changed

+76
-49
lines changed

2 files changed

+76
-49
lines changed

src/irx/builders/llvmliteir.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,16 +1652,23 @@ def visit(self, node: astx.LiteralList) -> None:
16521652
"are supported"
16531653
)
16541654

1655+
def _ensure_function_context(self) -> None:
1656+
"""Ensure tuple lowering occurs inside a function."""
1657+
if self._llvm.ir_builder.function is None:
1658+
raise RuntimeError(
1659+
"LiteralTuple must be lowered inside a function"
1660+
)
1661+
16551662
@dispatch # type: ignore[no-redef]
16561663
def visit(self, node: astx.LiteralTuple) -> None:
16571664
"""Lower a LiteralTuple to LLVM IR.
16581665
16591666
Representation
16601667
--------------
1661-
- Empty tuple -> constant ``{}`` (empty literal struct).
1662-
- All-constant elements -> constant literal struct ``{T0, T1, …}``.
1663-
- Otherwise -> alloca of the struct in the function
1664-
entry block with each field stored; pushes the *pointer*.
1668+
Always materialises an ``alloca`` in the function entry block and
1669+
stores each element. The *pointer* is pushed onto the result
1670+
stack, giving downstream code a uniform aggregate representation
1671+
(GEP + load) regardless of whether elements are constant.
16651672
16661673
Tuples are heterogeneous and ordered, so they are modelled as a
16671674
literal struct rather than an array.
@@ -1675,38 +1682,29 @@ def visit(self, node: astx.LiteralTuple) -> None:
16751682
raise Exception("LiteralTuple: failed to lower an element.")
16761683
llvm_vals.append(v)
16771684

1678-
n = len(llvm_vals)
1679-
1680-
# 2) Empty tuple -> constant {} (empty struct)
1681-
if n == 0:
1682-
struct_ty = ir.LiteralStructType([])
1683-
self.result_stack.append(ir.Constant(struct_ty, []))
1684-
return
1685-
1686-
# 3) Build the struct type from element value types
1685+
# 2) Build the struct type from element value types
16871686
elem_tys = [v.type for v in llvm_vals]
16881687
struct_ty = ir.LiteralStructType(elem_tys)
16891688

1690-
# 4) All-constant fast path -> constant struct
1691-
if all(isinstance(v, ir.Constant) for v in llvm_vals):
1692-
self.result_stack.append(ir.Constant(struct_ty, llvm_vals))
1693-
return
1689+
# 3) Guard: must be inside a function to use alloca
1690+
self._ensure_function_context()
16941691

1695-
# 5) Non-constant path: alloca + store each field
1696-
entry_bb = self._llvm.ir_builder.function.entry_basic_block
1697-
cur_bb = self._llvm.ir_builder.block
1698-
self._llvm.ir_builder.position_at_start(entry_bb)
1699-
alloca = self._llvm.ir_builder.alloca(struct_ty, name="tuple.lit")
1700-
self._llvm.ir_builder.position_at_end(cur_bb)
1692+
# 4) Alloca in the entry block, store each element, push pointer
1693+
builder = self._llvm.ir_builder
1694+
entry_bb = builder.function.entry_basic_block
1695+
cur_bb = builder.block
1696+
builder.position_at_start(entry_bb)
1697+
alloca = builder.alloca(struct_ty, name="tuple.lit")
1698+
builder.position_at_end(cur_bb)
17011699

17021700
i32 = ir.IntType(32)
17031701
for idx, v in enumerate(llvm_vals):
1704-
field_ptr = self._llvm.ir_builder.gep(
1702+
field_ptr = builder.gep(
17051703
alloca,
17061704
[ir.Constant(i32, 0), ir.Constant(i32, idx)],
17071705
inbounds=True,
17081706
)
1709-
self._llvm.ir_builder.store(v, field_ptr)
1707+
builder.store(v, field_ptr)
17101708

17111709
self.result_stack.append(alloca)
17121710

tests/test_literal_tuple.py

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,44 @@
1414
HAS_LITERAL_TUPLE = hasattr(astx, "LiteralTuple")
1515

1616

17+
def _assert_unpacked_literal_struct(alloca: ir.AllocaInstr) -> None:
18+
"""Assert alloca points to an unpacked literal struct."""
19+
assert isinstance(alloca, ir.AllocaInstr)
20+
pointee = alloca.type.pointee
21+
assert isinstance(pointee, ir.LiteralStructType)
22+
assert not pointee.packed
23+
24+
25+
def _assert_empty_stack(visitor: LLVMLiteIRVisitor) -> None:
26+
"""Assert translator result stack is empty after evaluation."""
27+
assert len(visitor.result_stack) == 0
28+
29+
30+
def _setup_function_context(visitor: LLVMLiteIRVisitor) -> None:
31+
"""Create a dummy function so the visitor can emit alloca instructions."""
32+
fn_ty = ir.FunctionType(ir.VoidType(), [])
33+
fn = ir.Function(visitor._llvm.module, fn_ty, "test_tuple_fn")
34+
bb = fn.append_basic_block("entry")
35+
visitor._llvm.ir_builder.position_at_end(bb)
36+
37+
1738
@pytest.mark.skipif(
1839
not HAS_LITERAL_TUPLE, reason="astx.LiteralTuple not available"
1940
)
2041
@pytest.mark.parametrize("builder_class", [LLVMLiteIR])
2142
def test_literal_tuple_empty(builder_class: Type[Builder]) -> None:
22-
"""Empty tuple lowers to constant {} (empty literal struct)."""
43+
"""Empty tuple lowers to alloca of {} (empty literal struct)."""
2344
builder = builder_class()
2445
visitor = cast(LLVMLiteIRVisitor, builder.translator)
2546
visitor.result_stack.clear()
47+
_setup_function_context(visitor)
2648

2749
visitor.visit(astx.LiteralTuple(elements=()))
28-
const = visitor.result_stack.pop()
50+
result = visitor.result_stack.pop()
2951

30-
assert isinstance(const, ir.Constant)
31-
assert isinstance(const.type, ir.LiteralStructType)
32-
assert len(const.type.elements) == 0
52+
_assert_unpacked_literal_struct(result)
53+
assert len(result.type.pointee.elements) == 0
54+
_assert_empty_stack(visitor)
3355

3456

3557
@pytest.mark.skipif(
@@ -39,10 +61,11 @@ def test_literal_tuple_empty(builder_class: Type[Builder]) -> None:
3961
def test_literal_tuple_homogeneous_ints(
4062
builder_class: Type[Builder],
4163
) -> None:
42-
"""Homogeneous integer constants lower to constant struct."""
64+
"""Homogeneous integer constants lower to alloca of struct."""
4365
builder = builder_class()
4466
visitor = cast(LLVMLiteIRVisitor, builder.translator)
4567
visitor.result_stack.clear()
68+
_setup_function_context(visitor)
4669

4770
visitor.visit(
4871
astx.LiteralTuple(
@@ -53,16 +76,17 @@ def test_literal_tuple_homogeneous_ints(
5376
)
5477
)
5578
)
56-
const = visitor.result_stack.pop()
79+
result = visitor.result_stack.pop()
5780

58-
assert isinstance(const, ir.Constant)
59-
assert isinstance(const.type, ir.LiteralStructType)
81+
_assert_unpacked_literal_struct(result)
6082
elem_count = 3
61-
assert len(const.type.elements) == elem_count
83+
pointee = result.type.pointee
84+
assert len(pointee.elements) == elem_count
6285
assert all(
6386
isinstance(t, ir.IntType) and t.width == 32 # noqa: PLR2004
64-
for t in const.type.elements
87+
for t in pointee.elements
6588
)
89+
_assert_empty_stack(visitor)
6690

6791

6892
@pytest.mark.skipif(
@@ -72,10 +96,11 @@ def test_literal_tuple_homogeneous_ints(
7296
def test_literal_tuple_heterogeneous(
7397
builder_class: Type[Builder],
7498
) -> None:
75-
"""Heterogeneous tuple (int, float) lowers to constant struct."""
99+
"""Heterogeneous tuple (int, float) lowers to alloca of struct."""
76100
builder = builder_class()
77101
visitor = cast(LLVMLiteIRVisitor, builder.translator)
78102
visitor.result_stack.clear()
103+
_setup_function_context(visitor)
79104

80105
visitor.visit(
81106
astx.LiteralTuple(
@@ -85,14 +110,16 @@ def test_literal_tuple_heterogeneous(
85110
)
86111
)
87112
)
88-
const = visitor.result_stack.pop()
113+
result = visitor.result_stack.pop()
89114

90-
assert isinstance(const, ir.Constant)
91-
assert isinstance(const.type, ir.LiteralStructType)
115+
_assert_unpacked_literal_struct(result)
92116
elem_count = 2
93-
assert len(const.type.elements) == elem_count
94-
assert isinstance(const.type.elements[0], ir.IntType)
95-
assert isinstance(const.type.elements[1], ir.FloatType)
117+
pointee = result.type.pointee
118+
assert len(pointee.elements) == elem_count
119+
assert isinstance(pointee.elements[0], ir.IntType)
120+
assert isinstance(pointee.elements[1], ir.FloatType)
121+
assert pointee.elements[1] == ir.FloatType()
122+
_assert_empty_stack(visitor)
96123

97124

98125
@pytest.mark.skipif(
@@ -102,15 +129,17 @@ def test_literal_tuple_heterogeneous(
102129
def test_literal_tuple_single_element(
103130
builder_class: Type[Builder],
104131
) -> None:
105-
"""Single-element tuple lowers to constant struct {i32}."""
132+
"""Single-element tuple lowers to alloca of struct {i32}."""
106133
builder = builder_class()
107134
visitor = cast(LLVMLiteIRVisitor, builder.translator)
108135
visitor.result_stack.clear()
136+
_setup_function_context(visitor)
109137

110138
visitor.visit(astx.LiteralTuple(elements=(astx.LiteralInt32(42),)))
111-
const = visitor.result_stack.pop()
139+
result = visitor.result_stack.pop()
112140

113-
assert isinstance(const, ir.Constant)
114-
assert isinstance(const.type, ir.LiteralStructType)
115-
assert len(const.type.elements) == 1
116-
assert isinstance(const.type.elements[0], ir.IntType)
141+
_assert_unpacked_literal_struct(result)
142+
pointee = result.type.pointee
143+
assert len(pointee.elements) == 1
144+
assert isinstance(pointee.elements[0], ir.IntType)
145+
_assert_empty_stack(visitor)

0 commit comments

Comments
 (0)