From 4bcb7fcc0887a1e1e3bfe81f0d825fc8a5562c5b Mon Sep 17 00:00:00 2001 From: Hung-Chun Tseng Date: Wed, 9 Sep 2026 22:57:48 +0800 Subject: [PATCH] Give an escaped parameter a slot before reloading it prepare_operand() reloads an address-taken variable from its stack slot instead of reading the register holding it, since a store through the pointer would not have reached the register. The reload assumes the slot holds the variable, and for a parameter it does not: parameters arrive in registers, and a slot is reserved the first time something spills one. Reloading before that read whatever the frame happened to hold. int f(int a, int b) { int x = a - b; int *p = &b; return x; } returned a rather than a - b, because the subtraction read an uninitialised slot that happened to contain zero. Nothing is stored through the pointer here; taking the address is enough, and the value is wrong before the pointer is ever used. Write the register back before the reload when the variable has no slot yet, which is what makes the slot stand for the variable, and keep using the register it is already in. All three targets were affected. --- src/reg-alloc.c | 13 +++++++++++++ tests/driver.sh | 1 + tests/escaped-param.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 tests/escaped-param.c diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 3e38c15c..77a23652 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -986,6 +986,19 @@ int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) return i; } + /* The reload below reads the variable's slot, so there has to be one + * holding its value. A parameter arrives in a register and is given a + * slot only when something spills it: reloading before that read whatever + * the frame happened to hold, and "int x = a - b; int *p = &b;" computed + * a - garbage. Writing the register back first is what makes the slot + * stand for the variable. + */ + if (i > -1 && var->address_taken && !var->space_is_allocated) { + store_var(bb, var, i); + vreg_map_to_phys(var, i); + return i; + } + for (i = 0; i < REG_CNT; i++) { if (reg_is_free(i)) { load_var(bb, var, i); diff --git a/tests/driver.sh b/tests/driver.sh index 0f3afd66..ed994e5a 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -477,6 +477,7 @@ begin_category "Standalone Programs" "Testing checked-in end-to-end programs" try_file 0 'F(10) = 55' "$TESTS_DIR/fib.c" try_file 0 $'1\nHello World' "$TESTS_DIR/hello.c" try_file 0 '' "$TESTS_DIR/strength-reduce.c" +try_file 0 '' "$TESTS_DIR/escaped-param.c" # Category: Basic Literals and Constants begin_category "Literals and Constants" "Testing integer, character, and string literals" diff --git a/tests/escaped-param.c b/tests/escaped-param.c new file mode 100644 index 00000000..e367b0bd --- /dev/null +++ b/tests/escaped-param.c @@ -0,0 +1,42 @@ +/* prepare_operand() reloads an address-taken variable from its stack slot + * rather than reading the register it is already in, because a store through + * the pointer would not have gone to the register. That reload needs the slot + * to hold the value, and a parameter arrives in a register with no slot at + * all: one is reserved the first time something spills it. + * + * Taking the address after the expression is what orders the two so the + * reload comes first. Nothing has to be stored through the pointer -- the + * value is already wrong by the time the address is taken. + */ + +int sub_then_escape(int a, int b) +{ + int x = a - b; + int *p = &b; + return x; +} + +int add_then_escape(int a, int b) +{ + int x = a + b; + int *p = &b; + return x; +} + +int escape_first_operand(int a, int b) +{ + int x = a - b; + int *p = &a; + return x; +} + +int main() +{ + if (sub_then_escape(5, 2) != 3) + return 1; + if (add_then_escape(1, 2) != 3) + return 2; + if (escape_first_operand(5, 2) != 3) + return 3; + return 0; +}