Summary
CompCert expands the implicit tail of an automatic-storage array declaration initializer into one assignment AST node per omitted element. As a result, a constant-size initializer such as { 0 } can make compiler memory consumption proportional to the declared array bound and can exhaust memory on valid C programs.
For example:
int f(void)
{
int aggregated[100000000] = { 0 };
return aggregated[0];
}
The same issue occurs with a partially explicit initializer:
int f(void)
{
int aggregated[100000000] = { 1, 2 };
return aggregated[0] + aggregated[1];
}
This minimal reproducer can be verified in a container limited to 512 MiB, where compilation is killed while the front end processes the initializer. Reducing the bound to 100,000 and compiling with -dparse makes the expansion directly visible in the post-Unblock .parsed.c output.
During the Unblock front-end transformation, the local initializer is recursively expanded into assignments for every array position, including every implicit zero-initialized element. Therefore the intermediate C AST itself becomes linear in the number of omitted elements.
I initially reproduced the problem with CompCert 3.17 and reproduced it again with CompCert 3.18 at commit 66a9fd0. The issue also affects real programs such as certain post-quantum cryptographic algorithms, which require a workaround to compile on typical consumer-grade hardware.
Expected behavior
The size of the AST produced for an implicit tail of a local array declaration should be independent of the number of omitted elements, apart from the nesting depth and the structure needed to initialize one element.
Explicitly written initializer expressions must retain their current typing and lowering behavior, and each expression must still be evaluated exactly once. Implicit elements must receive the typed default initialization required by C, including for nested aggregates, pointers, floating-point objects, and volatile subobjects.
Proposed solution
For an automatic array declaration with bound N and K explicitly supplied elements:
- Keep the explicit prefix as the existing typed assignments.
- If
K < N, emit one counted loop over the half-open range [K, N).
- Use a fresh function-local induction variable with the target's
size_t
integer kind.
- Recursively emit the existing typed default initialization for one element
as the loop body.
Conceptually:
would be lowered to the equivalent of:
a[0] = e0;
a[1] = e1;
for (size_t __init_index = 2; __init_index < N; ++__init_index) {
/* existing typed default initialization of a[__init_index] */
}
This solution is implemented in #603.
Summary
CompCert expands the implicit tail of an automatic-storage array declaration initializer into one assignment AST node per omitted element. As a result, a constant-size initializer such as
{ 0 }can make compiler memory consumption proportional to the declared array bound and can exhaust memory on valid C programs.For example:
The same issue occurs with a partially explicit initializer:
This minimal reproducer can be verified in a container limited to 512 MiB, where compilation is killed while the front end processes the initializer. Reducing the bound to 100,000 and compiling with
-dparsemakes the expansion directly visible in the post-Unblock.parsed.coutput.During the
Unblockfront-end transformation, the local initializer is recursively expanded into assignments for every array position, including every implicit zero-initialized element. Therefore the intermediate C AST itself becomes linear in the number of omitted elements.I initially reproduced the problem with CompCert 3.17 and reproduced it again with CompCert 3.18 at commit
66a9fd0. The issue also affects real programs such as certain post-quantum cryptographic algorithms, which require a workaround to compile on typical consumer-grade hardware.Expected behavior
The size of the AST produced for an implicit tail of a local array declaration should be independent of the number of omitted elements, apart from the nesting depth and the structure needed to initialize one element.
Explicitly written initializer expressions must retain their current typing and lowering behavior, and each expression must still be evaluated exactly once. Implicit elements must receive the typed default initialization required by C, including for nested aggregates, pointers, floating-point objects, and volatile subobjects.
Proposed solution
For an automatic array declaration with bound
NandKexplicitly supplied elements:K < N, emit one counted loop over the half-open range[K, N).size_tinteger kind.
as the loop body.
Conceptually:
would be lowered to the equivalent of:
This solution is implemented in #603.