Repository navigation
cc: a compound literal did not zero the members its list leaves out - #628
Merged
Merged
Conversation
MEASURED ON THE VM: `initzero-test' reports 6 failures under pcc and 0
under gcc, so muon's crash is kencc's.
AND IT IS ONE MISSING CALL. `compoundlit()' lowers a literal to a
hidden auto -- adecl(CAUTO,...) -- and calls doinit(), which assigns
only the members the list names. `cc.y's `adlist', an ordinary
`T x = {...}', is doinit() FOLLOWED BY contig(), and contig() is
exactly "zero every byte the initialiser did not cover". compoundlit()
was the one caller of doinit() in the compiler that never had the
second half, which is why no ordinary C in this tree ever tripped over
it. C99 6.7.8p21: an omitted member is initialised as an object with
static storage duration would be.
THE PATTERN WAS THE DIAGNOSIS, NOT THE COUNT. `.len' was garbage and
`.s' zero through a POINTER, and the other way round into a LOCAL --
the same two gaps of the same struct, in one program -- and section
6b's array literal passed outright. A compiler that simply never
zeroed would have failed uniformly; that is an uninitialised slot.
And section 5, a DECLARATION, passed for a real reason rather than by
luck: it goes through adlist, so it has always had contig(). The
asymmetry between the two productions is the whole bug.
contig() COULD NOT SIMPLY BE CALLED. Its bulk arm emits an ODWHILE --
a STATEMENT -- and everything compoundlit() builds has to survive
cgen() in EXPRESSION context, since the literal's value is an operand.
clitzero() reuses contig's front and back trims verbatim and spells the
loop out as straight-line stores. The cost is one store per uncovered
pointer-sized word: linear, recorded rather than capped, because a cap
would have to reject legal C or fall back to a statement this
expression cannot hold.
TWO THINGS THAT WOULD HAVE BEEN EASY TO GET WRONG, both written down
beside the code. It must run BEFORE the existing OLIST->OCOMMA
flattening: covered1() descends OLIST and OASI and nothing else, so
against an OCOMMA tree every member reads as uncovered and would be
zeroed on top of its own initialiser. And zeroing a PARTIALLY covered
word is correct and deliberate -- initz() prepends, so every zero store
runs before the member stores, which is the ordering contig() already
relies on. CAUTO only: a file-scope literal is CSTATIC, doinit() emits
DATA records and BSS is already zero.
sys/lib/tests/clitzero-model.py is the instrument, beside
sualign-model.py and for the same reason -- the first build of any cc/
change is the VM's. It EXTRACTS the real function from dcl.c rather
than restating it, and asks which bytes it stores zero into over six
layouts by seven negative FRAME OFFSETS, since v is negative and every
`%' in that function is a negative modulo -- one sign error writes
zeros over a neighbouring local. It has no external oracle the way
sualign-model has the acid dump, so its CONTROL is a deliberate break:
a copy with the tail loop removed must report a gap, and it does. All
cases ok, nothing stored outside the object, and a fully-covered
literal costs no stores at all, so `(int){i}' and `&(T){...}' keep
their addable path.
Not compiled anywhere: kencc needs Plan 9's <u.h>/<libc.h>, so the
first build is the VM's -- the weaker position -J, the `auto' grammar
fix and -C all shipped from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
MEASURED ON THE VM: `initzero-test' reports 6 failures under pcc and 0 under gcc, so muon's crash is kencc's.
AND IT IS ONE MISSING CALL.
compoundlit()' lowers a literal to a hidden auto -- adecl(CAUTO,...) -- and calls doinit(), which assigns only the members the list names.cc.y'sadlist', an ordinaryT x = {...}', is doinit() FOLLOWED BY contig(), and contig() is exactly "zero every byte the initialiser did not cover". compoundlit() was the one caller of doinit() in the compiler that never had the second half, which is why no ordinary C in this tree ever tripped over it. C99 6.7.8p21: an omitted member is initialised as an object with static storage duration would be.THE PATTERN WAS THE DIAGNOSIS, NOT THE COUNT.
.len' was garbage and.s' zero through a POINTER, and the other way round into a LOCAL -- the same two gaps of the same struct, in one program -- and section 6b's array literal passed outright. A compiler that simply never zeroed would have failed uniformly; that is an uninitialised slot. And section 5, a DECLARATION, passed for a real reason rather than by luck: it goes through adlist, so it has always had contig(). The asymmetry between the two productions is the whole bug.contig() COULD NOT SIMPLY BE CALLED. Its bulk arm emits an ODWHILE -- a STATEMENT -- and everything compoundlit() builds has to survive cgen() in EXPRESSION context, since the literal's value is an operand. clitzero() reuses contig's front and back trims verbatim and spells the loop out as straight-line stores. The cost is one store per uncovered pointer-sized word: linear, recorded rather than capped, because a cap would have to reject legal C or fall back to a statement this expression cannot hold.
TWO THINGS THAT WOULD HAVE BEEN EASY TO GET WRONG, both written down beside the code. It must run BEFORE the existing OLIST->OCOMMA flattening: covered1() descends OLIST and OASI and nothing else, so against an OCOMMA tree every member reads as uncovered and would be zeroed on top of its own initialiser. And zeroing a PARTIALLY covered word is correct and deliberate -- initz() prepends, so every zero store runs before the member stores, which is the ordering contig() already relies on. CAUTO only: a file-scope literal is CSTATIC, doinit() emits DATA records and BSS is already zero.
sys/lib/tests/clitzero-model.py is the instrument, beside sualign-model.py and for the same reason -- the first build of any cc/ change is the VM's. It EXTRACTS the real function from dcl.c rather than restating it, and asks which bytes it stores zero into over six layouts by seven negative FRAME OFFSETS, since v is negative and every
%' in that function is a negative modulo -- one sign error writes zeros over a neighbouring local. It has no external oracle the way sualign-model has the acid dump, so its CONTROL is a deliberate break: a copy with the tail loop removed must report a gap, and it does. All cases ok, nothing stored outside the object, and a fully-covered literal costs no stores at all, so(int){i}' and `&(T){...}' keep their addable path.Not compiled anywhere: kencc needs Plan 9's <u.h>/<libc.h>, so the first build is the VM's -- the weaker position -J, the `auto' grammar fix and -C all shipped from.
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs