Skip to content

Test an escaped parameter read before its slot exists - #339

Merged
jserv merged 1 commit into
sysprog21:masterfrom
alanhc:fix-escaped-param-reload
Sep 17, 2026
Merged

jserv merged 1 commit into
sysprog21:masterfrom
alanhc:fix-escaped-param-reload

Conversation

@alanhc

@alanhc alanhc commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Rebased on 2d38107. The rebase conflicted in src/reg-alloc.c, and the
conflict turned out to be the point: master already fixes #337.

pin_registers()'s caller marks an incoming argument register polluted = 1
on function entry, so the || REGS[i].polluted arm of prepare_operand()
returns the register instead of reloading it, and the reload that read a slot
the parameter never had is no longer reached. The comment above that condition
already names this case.

Comparing what stage 0 emits for sub_then_escape():

4d3b4c0 (this branch's old base)     master
load %x2, 0(sp)                      %x2 = sub %x0, %x1
%x3 = sub %x0, %x2                   store %x1, 16(sp)
store %x2, 16(sp)                    %x0 = %sp + 16
%x0 = %sp + 16                       ret %x2
ret %x3

The three functions in the test all come out right on master, so the
address_taken && !space_is_allocated hunk this PR carried is dead code now.
Dropped it.

What is left is the regression test: tests/escaped-param.c covers the
subtraction, the same shape around an addition, and the address taken of the
first operand rather than the second. The shape is narrow -- it needs the
escaped variable to be a parameter, and the address taken after the expression
rather than before -- which is why it went uncovered until it broke.

Close this instead if the test is not worth a file of its own.

Regression test for #337, which master already fixes.

cubic-dev-ai[bot]

This comment was marked as resolved.

jserv

This comment was marked as resolved.

@alanhc
alanhc force-pushed the fix-escaped-param-reload branch from 4bcb7fc to b69a985 Compare September 17, 2026 14:08
@alanhc alanhc changed the title Give an escaped parameter a slot before reloading it Test an escaped parameter read before its slot exists Sep 17, 2026
@alanhc

alanhc commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on 2d38107. The conflict was in src/reg-alloc.c, and it is the
answer: master already fixes #337. An incoming argument register is marked
polluted = 1 on function entry, so prepare_operand() takes the
|| REGS[i].polluted arm and returns the register instead of reloading it
from a slot the parameter does not have yet.

Stage 0 for sub_then_escape(), at this branch's old base 4d3b4c0 versus
master:

load %x2, 0(sp)          %x2 = sub %x0, %x1
%x3 = sub %x0, %x2       store %x1, 16(sp)
store %x2, 16(sp)        %x0 = %sp + 16
%x0 = %sp + 16           ret %x2
ret %x3

All three functions in the test come out right on master, so the
address_taken && !space_is_allocated hunk is dead code and is dropped. What
remains is tests/escaped-param.c. Close this if the test is not worth its own
file.

@alanhc
alanhc force-pushed the fix-escaped-param-reload branch from b69a985 to 9ac11c1 Compare September 17, 2026 14:12
Comment thread tests/escaped-param.c
@@ -0,0 +1,41 @@
/*

@jserv jserv Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file opens with an empty line where its sibling tests/strength-reduce.c opens with a comment naming the shape it guards. This test depends on a narrow shape (a parameter still sitting in its arrival register, its address taken only after the expression reads it), and nothing in the file says so. A later cleanup could drop the unused p or move &b above the arithmetic, and the test would still pass while covering nothing. Move the explanation from the pull request description into the file:

Suggested change
/*
/* A parameter whose address is taken only after an expression has
* read it is still in the register it arrived in, and its stack slot may not
* exist yet. Reloading it from that slot read whatever sat at offset
* zero of the frame. Keep the `&` after the arithmetic and the
* pointers in place: both are what make this path reachable.
*/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied. The file has carried a header comment since the first push, but it explained why no store through the pointer is needed rather than warning against moving the & or dropping p, which is the part a later cleanup would break — your wording covers that, so I took it as is.

One deviation: .ci/check-commentflow.sh reflows the opening /* onto its own line, matching tests/strength-reduce.c. The text is yours; the layout is commentflow's output.

A parameter arrives in a register and is given a stack slot only when
something spills it. prepare_operand() reloads an address-taken variable
from its slot rather than reading the register it is already in, and for
a parameter whose address is taken after the expression that reads it,
that reload came before any slot existed:

    int f(int a, int b)
    {
        int x = a - b;
        int *p = &b;
        return x;
    }

emitted "load %x2, 0(sp)" and subtracted the frame's first word, so f
returned a. Marking an incoming argument register polluted on entry made
prepare_operand() keep the register, which fixed this before the shape
was covered; the test pins it down.
@alanhc
alanhc force-pushed the fix-escaped-param-reload branch from 9ac11c1 to c7c5392 Compare September 17, 2026 15:36
@jserv
jserv merged commit 362b94b into sysprog21:master Sep 17, 2026
40 checks passed
@jserv

jserv commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Thank @alanhc for contributing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Taking the address of a parameter corrupts an expression computed before it

2 participants