From 9a998fe8e0c9ebfe6b0ca59bbbd148a4c4001ed0 Mon Sep 17 00:00:00 2001 From: Zimri Leisher Date: Sat, 5 Sep 2026 14:15:23 -0400 Subject: [PATCH] wasm: give a folded access a temp slot when a runtime index reaches into it (#209) --- src/fpy/codegen_llvm.py | 12 ++++++++++-- test/fpy/test_types_and_constructors.py | 22 ++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/fpy/codegen_llvm.py b/src/fpy/codegen_llvm.py index d81f2d9..d95a292 100644 --- a/src/fpy/codegen_llvm.py +++ b/src/fpy/codegen_llvm.py @@ -156,6 +156,14 @@ def is_addressable(expr: AstExpr, state: CompileState) -> bool: return is_instance_compat(sym, (VariableSymbol, FieldAccess)) +def denotes_location(expr: AstExpr, state: CompileState) -> bool: + """True when a pointer to *expr* is formed from its own location: it is + addressable and has not folded to a constant. A folded access (a constant + element of a constant constructor call) is emitted as its constant, so an + access into it copies that constant to a temp slot instead.""" + return is_addressable(expr, state) and state.const_expr_values.get(expr) is None + + class EmitLlvmExpr(Emitter): """Lowers a single Fpy arithmetic/comparison expression into LLVM IR. @@ -276,7 +284,7 @@ def _emit_ptr(self, expr: AstExpr, state: CompileState) -> ir.Value: """Emit a pointer to *expr*'s value, without loading it.""" b = self.builder i32 = ir.IntType(32) - if not is_addressable(expr, state): + if not denotes_location(expr, state): return self._emit_to_temp_slot(expr, state) sym = state.resolved_symbols[expr] if is_instance_compat(sym, VariableSymbol): @@ -983,7 +991,7 @@ def _temp_slot_parent(self, access: AstExpr, state: CompileState) -> AstExpr | N # _emit_ptr copies its argument to a slot on exactly this condition. parent = state.resolved_symbols[access].parent_expr - if is_addressable(parent, state): + if denotes_location(parent, state): return None if parent in state.backend.temp_slots: # The AST can share one expression between two places (a default diff --git a/test/fpy/test_types_and_constructors.py b/test/fpy/test_types_and_constructors.py index e3e047e..d0008ea 100644 --- a/test/fpy/test_types_and_constructors.py +++ b/test/fpy/test_types_and_constructors.py @@ -401,6 +401,28 @@ def pick(idx: I8) -> U32: assert pick(0) == 10 assert pick(1) == 20 +""" + assert_run_success(fprime_test_api, seq) + + def test_get_variable_idx_of_folded_element_of_ctor_result(self, fprime_test_api): + """A runtime index into a constant-indexed element of a constant + expression: the constant prefix folds, and the runtime index must + still find storage to index into.""" + seq = """ +j: I64 = 1 +c: Ref.Choice = Ref.TooManyChoices( \\ + Ref.ManyChoices(Ref.Choice.ONE, Ref.Choice.TWO), \\ + Ref.ManyChoices(Ref.Choice.RED, Ref.Choice.BLUE))[0][j] +assert c == Ref.Choice.TWO +""" + assert_run_success(fprime_test_api, seq) + + def test_get_variable_idx_of_folded_member_of_ctor_result(self, fprime_test_api): + seq = """ +j: I64 = 1 +x: F32 = Ref.SignalInfo(Ref.SignalType.SINE, \\ + Ref.SignalSet(1.0, 2.0, 3.0, 4.0), Ref.SignalPairSet()).history[j] +assert x == 2.0 """ assert_run_success(fprime_test_api, seq)