Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4f8844e
Add static, const and C99 record initialization
jserv Sep 9, 2026
d6beb31
Add unsigned, long long and enum types
jserv Sep 9, 2026
29712e5
Complete the preprocessor, qualifiers and sizeof
jserv Sep 10, 2026
3130445
Add bit-fields, include paths and deeper arrays
jserv Sep 10, 2026
b6c4203
Add C99 literals, standard headers and strict mode
jserv Sep 11, 2026
dd0be37
Support function designators and array pointers
jserv Sep 12, 2026
65d7cc7
Add block typedefs and pointer-row lowering
jserv Sep 13, 2026
c5debf9
Fix initializer, sizeof and branch edge cases
jserv Sep 13, 2026
6ab8267
Report failures and run the full check everywhere
jserv Sep 12, 2026
fe6f794
Speed up plain-source lexing and type lookup
jserv Sep 13, 2026
d7a4ca5
Split parser.c into ordered fragments
jserv Sep 13, 2026
5f5b7c0
Fix -E keywords and libc assertion headers
jserv Sep 14, 2026
7417de0
Move AArch64 instruction encoding to src/arm64.c
jserv Sep 14, 2026
3c1c3b7
Fix backend miscompiles found by self-hosting
jserv Sep 14, 2026
13e1d6d
Fix declarator, initializer and sizeof edge cases
jserv Sep 14, 2026
30bdaa5
Harden literal lexing and preprocessing
jserv Sep 14, 2026
fb75f4c
Scope record tags and check their kinds
jserv Sep 14, 2026
11e8063
Fix va_arg, address-taken and peephole miscompiles
jserv Sep 14, 2026
1686e61
Lower long long values on 32-bit targets
jserv Sep 14, 2026
1ea08c3
Pass long long arguments per the 32-bit ABIs
jserv Sep 14, 2026
a6a6f0e
Match C99 in the preprocessor, lexer and libc
jserv Sep 14, 2026
0f8b8cd
Fix Arm, AArch64 and peephole code generation
jserv Sep 14, 2026
679fe9a
Fix initializer, declaration and sizeof gaps
jserv Sep 14, 2026
cf4d600
Fix expression, record and typedef lowering
jserv Sep 14, 2026
ab1bf96
Consolidate parser lookups and bound their costs
jserv Sep 14, 2026
e0b5e9c
Fix integer lowering found by differential fuzzing
jserv Sep 14, 2026
bc2ab99
Keep every access to a volatile object
jserv Sep 14, 2026
b1effbe
Complete designators and static address constants
jserv Sep 14, 2026
9dc4f3f
Fix declarator, typedef and conversion gaps
jserv Sep 14, 2026
8c14996
Fix expression lowering and function pointer forms
jserv Sep 15, 2026
25b69d8
Complete printf and variadic macro comma elision
jserv Sep 14, 2026
ef39f5b
Reach the RISC-V PLT beyond the JAL range
jserv Sep 15, 2026
dfb071d
Diagnose pointer conversions in every initializer
jserv Sep 15, 2026
41810d0
Fix function pointer typedefs and const lvalues
jserv Sep 15, 2026
e0d4a4e
Keep narrow and folded values in range
jserv Sep 16, 2026
bc17346
Resolve declarations in their declaring scope
jserv Sep 16, 2026
9cac258
Subscript rows and members of a call result
jserv Sep 16, 2026
d541215
Evaluate every static initializer as a constant
jserv Sep 16, 2026
205bad9
Complete preprocessor argument and directive rules
jserv Sep 16, 2026
90eacfa
Reach host libc objects through the dynamic linker
jserv Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 33 additions & 34 deletions COMPLIANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ This document tracks compliance gaps and non-standard behaviors.
- `if`/`else` statements
- `goto` and label statements
- `while`, `do-while`, `for` loops
- `switch`/`case`/`default` statements
- `switch`/`case`/`default` statements, including labels after ordinary
statements and within nested compound blocks; strict C99 rejects duplicate
case values and declarations immediately following an ordinary, case, or
default label
- `break`, `continue`, `return` statements

### Operators
Expand Down Expand Up @@ -48,73 +51,69 @@ This document tracks compliance gaps and non-standard behaviors.

| Feature | Status | Impact |
|---------|--------|--------|
| `static` | Not implemented | No internal linkage or persistent local variables |
| `extern` | Not implemented | No external linkage declarations |
| `register` | Not implemented | No register hint optimization |
| `auto` | Not implemented | Default storage class (implicit) |
| `const` | Parsed but ignored | No read-only enforcement |
| `volatile` | Not implemented | No volatile semantics |
| `restrict` | Not implemented | No pointer aliasing optimization |
| `inline` | Not implemented | No function inlining |
| `static` | Partial | File-scope internal linkage and persistent block-scope objects work; cross-translation-unit linkage remains incomplete. A `static` object in a `for` initializer is an extension: C99 6.8.5p3 allows only `auto` and `register` there, so `--std=c99` rejects it. |
| `extern` | Partial | File- and block-scope object declarations plus function prototypes bind to global declarations; remaining C99 forms need coverage. |
| `register` | Partial | Block-scope declarations and parameters lower as automatic objects and reject address-taking; no allocation hint is implemented. |
| `auto` | Supported | Block-scope declarations and C99 `for` initializers use ordinary automatic storage. |
| `const` | Supported | Enforced for direct and indirect lvalues; pointer-level conversions are checked. |
| `volatile` | Partial | Preserved through declarations and prevents key optimizations; exhaustive optimizer audit remains. |
| `restrict` | Partial | Accepted wherever C99 allows it and then ignored: the qualifier is not retained, so it drives no aliasing optimization. |
| `inline` | Partial | File-scope declarations/definitions are accepted; C99 linkage constraints remain incomplete. |

### Type System

| Feature | Status | Notes |
|---------|--------|-------|
| `long` | Missing | Only 4-byte integers |
| `long long` | Missing | No 64-bit integers |
| `unsigned` | Missing | All integers are signed |
| `signed` | Missing | Implicit for integers |
| `long` | Partial | Distinct rank with the current 32-bit representation. |
| `long long` | Partial | Eight-byte values work on every target: 32-bit Arm and RISC-V keep them in register pairs and pass them as AAPCS32 and the RV32 calling convention require. Randomized testing still finds wide expressions the x64 and AArch64 backends miscompile. |
| `unsigned` | Supported | Unsigned char/short/int/long families, arithmetic, conversions, and ABI paths are implemented. |
| `signed` | Supported | Signed scalar spellings, including signed char, are distinct and parsed. |
| `float` | Missing | No floating-point support |
| `double` | Missing | No floating-point support |
| `long double` | Missing | No floating-point support |
| Bit-fields | Missing | Cannot pack struct members |
| Bit-fields | Supported | `_Bool`, `int`, and `unsigned int` fields pack least-significant-bit first in their conventional allocation units; narrow unsigned fields receive C99 integer promotion. |

### Literals & Constants

| Feature | Status | Current Behavior |
|---------|--------|-----------------|
| Integer suffixes (`u`, `l`, `ll`) | Not parsed | All literals are `int` |
| Wide characters (`L'c'`) | Not supported | Single-byte only |
| Wide strings (`L"..."`) | Not supported | Single-byte only |
| Multi-character constants | Not supported | Single character only |
| Universal characters (`\u`, `\U`) | Not supported | ASCII only |
| Hex escapes (`\x...`) | Limited | Max 2 hex digits |
| Integer suffixes (`u`, `l`, `ll`) | Partial | Common suffix spellings and wide literals are parsed on every target; full candidate-type selection remains incomplete. |
| Wide characters (`L'c'`) | Supported | Lowered as the implementation's `int`-sized execution-wide-character representation. |
| Wide strings (`L"..."`) | Supported | Lowered as NUL-terminated `wchar_t` rodata; supported in expressions, `sizeof`, pointers, and compatible array initialization. |
| Multi-character constants | Supported | Implementation-defined left-to-right packing of up to four bytes. |
| Universal characters (`\u`, `\U`) | Partial | Narrow literals, identifiers, and wide character constants use the implementation's UTF-8 decoding; wide string literals decode to execution-wide-character units. |
| Hex escapes (`\x...`) | Supported | The full following hexadecimal run is consumed; a value that does not fit an `unsigned char` is rejected in narrow literals. |

### Preprocessor Gaps

| Feature | Status | Description |
|---------|--------|-------------|
| `#include` | Partial | Local file inclusion is supported, but lack of capability to include system files |
| Token pasting (`##`) | Missing | Cannot concatenate tokens |
| Stringizing (`#`) | Missing | Cannot convert to string |
| `__DATE__` | Missing | No compile date |
| `__TIME__` | Missing | No compile time |
| `__STDC__` | Missing | No standard compliance indicator |
| `#include` | Partial | Quoted includes and explicit `-I` angle-header search work; hosted C99 headers remain incomplete. |
| Token pasting (`##`) | Supported | Object- and function-like pastes are rescanned and diagnosed when invalid. |
| Stringizing (`#`) | Supported | Function-like macro arguments are stringized with C99 whitespace and escaping behavior. |
| `__DATE__` | Supported | Expands to the C99 date-character array shape. |
| `__TIME__` | Supported | Expands to the C99 time-character array shape. |
| `__STDC__` | Supported | Expands to integer constant `1`; `__STDC_HOSTED__` is also provided. |

### Advanced Features

| Feature | Status | Description |
|---------|--------|-------------|
| Designated initializers | Missing | No `.field = value` syntax |
| Designated initializers | Partial | Record and bounded-array designators work for local, static, and file-scope objects; higher-rank continuation cases remain incomplete. |
| Compound literals | Partial | Limited support |
| Flexible array members | Missing | No `[]` at struct end |
| `sizeof` type names | Partial | Fixed arrays, pointer-to-array, array-of-pointer, and recursive function-pointer declarators are supported, including callback arrays and global constant expressions; arbitrary mixed derived declarators and a shared general type-name parser remain incomplete. |
| Flexible array members | Supported | Final `[]` struct members have zero fixed extent, support pointer-based element access, and enforce C99 placement constraints. |
| Variable-length arrays | Missing | No runtime-sized arrays |
| `_Complex` | Missing | No complex numbers |
| `_Imaginary` | Missing | No imaginary numbers |
| `_Static_assert` | Missing | No compile-time assertions |
| `_Alignof` | Missing | No alignment queries |
| `_Alignas` | Missing | No alignment specification |
| `_Generic` | Missing | No generic selection |

## Non-Standard Behaviors

### GNU Extensions
- Binary literals: `0b101010`
- Escape sequence: `\e` for ESC character
- `void*` arithmetic (treated as `char*`)
- `sizeof(void)` returns 0 (should be error)
- Computed goto
- Comma elision in variadic macros: `, ## __VA_ARGS__` drops the comma when no variadic argument is given

### Implementation-Specific
- Array compound literals in scalar context use first element
Expand Down
57 changes: 55 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ LIBDIR := $(shell find lib -type d)

BUILTIN_LIBC_SOURCE ?= c.c
BUILTIN_LIBC_HEADER := c.h
# The translation timestamp belongs to generated configuration rather than the
# compiler's host clock. A single value is consequently embedded in stages 0,
# 1, and 2, which keeps bootstrap byte-for-byte reproducible. Rebuilders can
# supply SOURCE_DATE_EPOCH for a stable timestamp across separate invocations.
# The fallback is read once: a recursive "?=" would rerun date for the date
# and again for the time, which then disagree across a second boundary.
ifeq ($(origin SOURCE_DATE_EPOCH),undefined)
SOURCE_DATE_EPOCH := $(shell date -u +%s)
endif
# The epoch is spliced into the date command below, so anything but decimal
# digits would be run by the shell there. $(value) keeps make from expanding a
# supplied $(...) before it is checked.
EPOCH_NONDIGITS := $(value SOURCE_DATE_EPOCH)
$(foreach d,0 1 2 3 4 5 6 7 8 9,$(eval EPOCH_NONDIGITS := $$(subst $(d),,$$(EPOCH_NONDIGITS))))
ifneq ($(if $(strip $(value SOURCE_DATE_EPOCH)),$(EPOCH_NONDIGITS),empty),)
$(error SOURCE_DATE_EPOCH must be a Unix epoch in decimal seconds)
endif
# GNU date converts an epoch given as -d @SECONDS, which BSD and macOS date do
# not accept; they take the seconds as -r SECONDS instead. Ask for the Unix
# epoch itself to learn which spelling this date understands.
ifeq ($(shell date -u -d @0 +%s 2>/dev/null),0)
EPOCH_DATE = LC_ALL=C TZ=UTC date -u -d "@$(SOURCE_DATE_EPOCH)"
Comment thread
jserv marked this conversation as resolved.
else
EPOCH_DATE = LC_ALL=C TZ=UTC date -u -r "$(SOURCE_DATE_EPOCH)"
endif
TRANSLATION_DATE := $(shell $(EPOCH_DATE) '+%b %e %Y')
TRANSLATION_TIME := $(shell $(EPOCH_DATE) '+%H:%M:%S')
ifeq ($(strip $(TRANSLATION_DATE)$(TRANSLATION_TIME)),)
$(error SOURCE_DATE_EPOCH must be a Unix epoch accepted by date)
endif
TRANSLATION_DEFS = "\#define SHECC_TRANSLATION_DATE \"$(TRANSLATION_DATE)\"\n\#define SHECC_TRANSLATION_TIME \"$(TRANSLATION_TIME)\"\n"
# --dump-ir is what makes out/shecc-stage1.log the IR of the stage 1 build
# rather than an empty file. It is the only thing in the tree that exercises
# dump_insn()/dump_ph2_ir(), and it is what a failed CI run uploads.
Expand Down Expand Up @@ -98,15 +129,16 @@ endif
# previous architecture's generated config, so require an explicit reconfigure
# instead.
#
# Naming "config" or "distclean" anywhere in the goals is that reconfigure: the
# Naming "config", "distclean", or "check-all-targets" anywhere in the goals
# is that reconfigure: the
# record is about to be rewritten or removed, so the architecture it still holds
# does not apply and the check must not fire. Testing for their presence rather
# than filtering them out is what lets a goal list combine them with real work,
# as "make distclean config check ARCH=riscv" does. "clean" touches no
# generated config, so a mismatch cannot affect it either.
CONFIGURED_ARCH := $(shell sed -n 's/^ARCH=//p' $(BUILD_SESSION) 2>/dev/null)
ifneq (,$(CONFIGURED_ARCH))
ifeq (,$(filter config distclean,$(MAKECMDGOALS)))
ifeq (,$(filter config distclean check-all-targets,$(MAKECMDGOALS)))
ifneq (,$(filter-out clean,$(or $(MAKECMDGOALS),all)))
ifneq ($(CONFIGURED_ARCH),$(ARCH))
$(error Tree is configured for ARCH=$(CONFIGURED_ARCH). Run "make config ARCH=$(ARCH)" to switch)
Expand All @@ -132,6 +164,7 @@ include mk/common.mk
config:
$(Q)ln -sf $(PWD)/$(SRCDIR)/$(ARCH)-codegen.c $(SRCDIR)/codegen.c
$(Q)$(PRINTF) $(ARCH_DEFS) > $@.tmp
$(Q)$(PRINTF) $(TRANSLATION_DEFS) >> $@.tmp
$(Q)if cmp -s $@.tmp $@; then $(RM) $@.tmp; else mv $@.tmp $@; fi
$(Q)$(PRINTF) "ARCH=$(ARCH)" > $(BUILD_SESSION)
$(VECHO) "Target machine code switch to %s\n" $(ARCH)
Expand All @@ -141,6 +174,26 @@ config:

check: check-stage0 check-stage2 check-abi-stage0 check-abi-stage2

# Run the complete check -- driver and ABI suites at stages 0 and 2 -- on every
# backend, as CI does. Driver cases that need 64-bit values are gated on the
# target's pointer width, so each target runs everything it can represent.
# Configuration is global to this worktree, so keep recursive invocations
# sequential and restore the caller's architecture even when a target fails.
# Each target is rebuilt after its own `config`, preventing an old compiler from
# being paired with a newly selected backend. This make exported the emulator
# for the caller's architecture, so each recursive make has to derive its own:
# an inherited qemu-arm would run the x64 binaries.
.PHONY: check-all-targets
check-all-targets:
$(Q)set -e; \
unset TARGET_EXEC; \
active_arch='$(or $(CONFIGURED_ARCH),$(ARCH))'; \
trap '$(MAKE) config ARCH="$$active_arch"' EXIT; \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: After check-all-targets succeeds, the restored architecture can disagree with the compiler binaries left in out/. Restore a usable artifact state as well, either by rebuilding the saved architecture after the trap or by removing target-specific outputs when restoring only the configuration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Makefile, line 183:

<comment>After `check-all-targets` succeeds, the restored architecture can disagree with the compiler binaries left in `out/`. Restore a usable artifact state as well, either by rebuilding the saved architecture after the trap or by removing target-specific outputs when restoring only the configuration.</comment>

<file context>
@@ -141,6 +166,26 @@ config:
+	$(Q)set -e; \
+	unset TARGET_EXEC; \
+	active_arch='$(or $(CONFIGURED_ARCH),$(ARCH))'; \
+	trap '$(MAKE) config ARCH="$$active_arch"' EXIT; \
+	for target_arch in $(ARCHS); do \
+		$(MAKE) config ARCH="$$target_arch"; \
</file context>

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This does not hold. Every object file lists config as a prerequisite, and make config rewrites config whenever its content changes, so the architecture restored after check-all-targets always differs in content from the last target in the loop and the next make or make check rebuilds main.o and then stages 0, 1 and 2. I confirmed it with make -n out/shecc after a switch. The state left behind is the same as after any make config, so no artifacts need removing.

for target_arch in $(ARCHS); do \
$(MAKE) config ARCH="$$target_arch"; \
$(MAKE) check ARCH="$$target_arch"; \
done

# One checker per target: they share nothing, so "make -j check-style" runs them
# concurrently and finishes in the time the slowest one takes.
check-style: check-newline check-comments check-format check-shell
Expand Down
Loading