The offending commit is the introduction of C23 keywords. There is a function called
constexpr()
, which needs to be renamed since it conflicts with the C23 keyword. I need to pick a new name I like.
This is a long-standing issue that stems from
funcinst
returningNULL
when we haven't introduced a new QBE block, and we have already have a jump for the current block. The intent was to avoid generating unreachable code, but it causes problems when more control flow follows after the jump.One fix could be to make
stmt()
split up parsing and code generation, and skip the latter if the current block is already terminated. However, the much simpler solution is just to insert a dummy block when we encounter this situation. QBE will discard unreachable blocks quite early in the pipeline, so I think that's what I'll do.Fixed by 4fa48e716d6381e51ae8dc7a9992c4c7d50219a1.
REPORTED
RESOLVED FIXEDI noticed this issue also during bootstrap on OpenBSD (https://builds.sr.ht/~mcf/job/757621).
The issue is related to qualifiers on array types. The C standard says that when an array type is qualified, the qualifiers apply to the element type, not the array type itself. Although this is handled partially at the end of
decl.c:declspecs()
, there are still a few cases where we incorrectly applied qualifiers to an array type:
- If you qualify a nested array type, e.g.
typedef int T[1][1]; const T x;
.declspecs
only passes the qualifiers through one layer of array, sox
ends up as "array of const array of int" instead of "array of array of const int".- If you access an array member of a struct or union, the qualifiers of the struct/union get applied to the member type, but don't get passed through to the element type of the array member.
Commit b82a231 fixed a bug where an expression with array type would decay into a pointer with the qualifiers of the array type (which should always be empty as per above) and ignore the qualifiers of the array element type. However, due to 1 and 2 above, there are cases where we might end up with a qualified array type, which now trigger the assertion. I think you are hitting case 2 above (removing the
const
fromrepl_cmds
is a workaround).As for a solution, I have two ideas:
- Add a function that applies qualifiers to an array type by walking down until it finds a non-array type, rebuilding it as it goes and applying the qualifiers to that non-array type. Then change
declspecs
andpostfixexpr
to use that.- Previously, things mostly ended up working despite this bug since the qualifiers were applied down to the element type during the implicit conversion of an array to a pointer to first element. Perhaps it would be sufficient to combine the qualifiers in
decay()
(i.e.t->qual | tq
), and then somehow relaxtypecompatible()
to treat "qualified array of type" and "array of qualified type" the as equivalent.I still need to think more about it to determine the best way forward. I have some plans to change up how types are represented, which I think should make this easier to deal with. So even though it's still not quite correct, perhaps the best thing to do for now is the diff below.
diff --git a/expr.c b/expr.c index 67976b8..532ad60 100644 --- a/expr.c +++ b/expr.c @@ -98,9 +98,14 @@ decay(struct expr *e) tq = e->qual; switch (t->kind) { case TYPEARRAY: + /* + TODO: qualifiers should be applied to the element + type of array types, not the array type itself + assert(tq == QUALNONE); + */ e = mkunaryexpr(TBAND, e); - e->type = mkpointertype(t->base, t->qual); + e->type = mkpointertype(t->base, t->qual | tq); e->decayed = true; break; case TYPEFUNC:
See the earlier discussion. The problem is that alloca and VLAs behave differently in a loop. Memory allocated by alloca gets deallocated when the function returns, but VLAs get deallocated at the end of the block. Naively compiling VLAs as alloca could result in stack overflows in some of the examples above.
It only uses -fPIC when it detects that the compiler enables it by default. Since cproc is using gcc as a preprocessor, if gcc is built with
--enable-default-pie
it defines__PIC__
by default, which is not correct since qbe doesn't generate position-independent code.I pushed a couple of commits to fix this. Now, you should be able to do
CC=cproc ./configure --target=x86_64-linux-musl && make -k lib/libc.a
to see all the errors.There are only a handful of unique errors, but these are all major features that need support from QBE (apart from _Complex maybe):
$ make -k lib/libc.a 2>&1 | grep -e error: -e cproc-qbe: | sort -u ./arch/x86_64/atomic_arch.h:3:2: error: inline assembly is not yet supported ./arch/x86_64/syscall_arch.h:6:2: error: inline assembly is not yet supported ./include/complex.h:16:8: error: _Complex is not yet supported cproc-qbe: long double is not yet supported src/network/if_nameindex.c:95:52: error: VLAs are not yet supported src/process/execl.c:12:20: error: VLAs are not yet supported src/process/execle.c:12:20: error: VLAs are not yet supported src/process/execlp.c:12:20: error: VLAs are not yet supported src/process/execvp.c:29:15: error: VLAs are not yet supported src/search/lsearch.c:6:17: error: VLAs are not yet supported src/string/explicit_bzero.c:6:2: error: inline assembly is not yet supported $
Thanks for the clear report and test case.
This syntax is perfectly fine for initializing local variables inside a function, but not for static or global variables. The C standard describes this restriction in 6.7.9p4:
All the expressions in an initializer for an object that has static or thread storage duration shall be constant expressions or string literals.
What you have here on the right hand side is a compound literal expression, which is not a constant expression or string literal. A constant expression must be one of the following (6.6p7):
- an arithmetic constant expression,
- a null pointer constant,
- an address constant, or
- an address constant for a complete object type plus or minus an integer constant expression.
I believe the reason why this works in gcc and clang is because of 6.7p10:
An implementation may accept other forms of constant expressions.
But this is not something you can rely upon in your code. And indeed, if you enable -Wpedantic, gcc will warn about this:
test.c:2:20: warning: initializer element is not constant [-Wpedantic] 2 | struct dummy str = (struct dummy){.label = "Redraw"};
The reason that
struct dummy str = {.label = "Redraw"};
works is because there is only one expression in the initializer, the string literal"Redraw"
, which is explicitly allowed in initializers of global and static variables.I got in the habit of doing this back when initializer "type inference" was worse and noticed it while trying to compile a program of mine.
I'm not sure what you mean by this. C doesn't have any initializer type inference; you always have to specify the types of the objects you initialize.