Library / SDK
sysprog21/shecc avatar
sysprog21/shecc

shecc: self-hosting C compiler across four Linux targets

A self-hosting and educational C optimizing compiler

1,404 stars168 forksCBSD-2-Clause

At a glance

What is it?
shecc is an educational, self-hosting C compiler that generates Linux ELF executables for Armv7-A, AArch64, RV32IM and x86-64. Its self-contained pipeline suits compiler study, while its limited C subset and target-specific execution requirements narrow adoption.
Who is it for?
Adopt shecc when the goal is to study compiler construction, bootstrap a small C implementation, or generate Linux executables for one of its documented architectures. Do not select it for general production C compilation, unsupported language features, or targets outside ARMv7-A, AArch64, RV32IM and x86-64.
Can I use it commercially?
Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why shecc targets compiler education rather than general C work

shecc addresses a specific problem: providing a readable compiler that can compile its own implementation and expose its compilation pipeline for study. It is aimed at developers studying compiler construction, self-hosting and cross-target code generation, especially where an external assembler or linker would obscure how source text becomes an executable. It is not positioned as a replacement for a general-purpose C toolchain.

The supported source language is a subset of C rather than complete ISO C. The README lists scalar types, pointers, arrays, structures, unions, enumerations, `typedef`, control flow, function definitions and calls, common operators, initializers and a useful set of preprocessor directives. It also documents multi-level pointer dereference, multidimensional arrays, labels and `goto`, variadic functions through direct pointer arithmetic, function-like macros, `__VA_ARGS__`, stringification and token pasting. That is enough language surface for compiler experiments and substantial programs, but it does not establish compatibility with an arbitrary codebase.

shecc generates executable Linux ELF binaries for ARMv7-A, AArch64, RV32IM and x86-64. The architecture range is its clearest differentiator. A study project that can run natively on x86-64 GNU/Linux, cross-build for Arm or RISC-V and still participate in a three-stage self-compilation experiment gives learners several paths through the same compiler. The tradeoff is equally clear. Four backends do not turn the implementation into a universal C platform, and the documented execution environment for bootstrapping is narrower than the architecture list might first suggest.

From C source through VIR to ELF code generation

The compiler combines its own C front end with machine-code generation, so the README states that the path does not require an external assembler or linker. Its intermediate design is VIR, described by `docs/vir.md` as a value-centric SSA representation. Target-independent optimization operates on this layer. The implementation then lowers VIR into allocated machine IR before invoking the target-specific instruction emitter.

This division is significant. SSA gives the optimization stage a representation in which values are explicit and data dependencies can be handled before machine registers exist. After optimization, shared components handle ABI lowering, register allocation and spills for every supported target. Only after those target-independent and shared machine-level tasks is instruction selection or emission specialized. The pipeline therefore keeps optimization, ABI rules, register pressure and target encoding visible as distinct concerns.

The backend output differs by target. Arm and RV32IM use ELF32, while AArch64 and x86-64 use ELF64. The README says the ELF class follows the target pointer width. Armv7-A uses the Linux ABI with hard-float characteristics. AArch64 follows AAPCS64 and supports static or eager-bound dynamic linking. RV32IM is documented as verified with QEMU. The x86-64 backend follows the System V AMD64 ABI and can run natively on an x86-64 GNU/Linux host.

Dynamic linking adds infrastructure to the target machine. x86-64 output resolves its ELF interpreter and glibc from the host. Arm, AArch64 and RISC-V output need matching target components, and cross-compilation alone does not guarantee that the resulting program can start. The compiler architecture list and the runnable-platform list should therefore not be treated as interchangeable.

Configuring the x86-64 backend and building the compiler

On x86-64 GNU/Linux, configure x86-64 first because the README identifies it as the simplest native path. The documented target values are `arm`, `riscv`, `arm64` and `x64`. The selected architecture is recorded in the source tree, which prevents a later plain `make` from combining one target's generated state with another target's configuration.

Configure the x86-64 backend with:

shell
$ make config ARCH=x64
# Target machine code switch to x64

The command changes the recorded target machine-code setting to `x64`. Then run the documented build command:

shell
$ make

The repository includes a `Makefile`, compiler sources under `src/`, a minimal library under `lib/`, tests, scripts and documentation. The `Makefile` compiles with C99 and `-pedantic`; it also lists `-O2 -g` and `-fwrapv`. The last flag is not cosmetic. Its comment says the hashmap hash uses an FNV-1a accumulator in a signed `int`, because shecc cannot compile its own `unsigned` type, and the multiply is intended to overflow.

A useful first verification is the documented bootstrap model rather than an unrelated application build. For x86-64, compile the compiler source with an ordinary compiler to form stage0, use that native compiler to compile shecc's source and produce stage1, then run the stage1 output against the same source to produce stage2. On x86-64 GNU/Linux, the generated target program can execute natively. The README does not show a complete one-off compile command for stage0, so there is no justified command line to add here.

The three-stage bootstrap and its byte comparison

Self-hosting is defined through reproducible compiler stages. Stage0 is built from shecc's source using an ordinary C compiler such as `gcc` or `clang`, producing a native executable that can also act as a cross-compiler. Stage1 is generated when that compiler reads shecc's source. Stage2 is produced by running the stage1 compiler on the same source again.

The bootstrap check compares the stage1 and stage2 compiler binaries byte for byte. Equality is the acceptance condition because a self-hosting compiler should be able to consume its own source and reproduce the compiler that consumed it. This is more specific than merely confirming that both binaries execute.

Native execution is architecture-dependent. x86-64 output runs on an x86-64 GNU/Linux host. The build system can run Arm binaries on recognized Arm boards. Other cases, including the RISC-V target, use QEMU. macOS and Microsoft Windows can build the project, according to the README, but stage2 bootstrapping is expected to fail because the required `qemu-arm` executable is absent. The x86-64 target also expects an x86-64 GNU/Linux environment for running its output.

The README says ordinary C compilers are the only general build prerequisite because code generation does not depend on external utilities. That does not remove execution requirements. The host still needs either native support for the generated architecture or suitable QEMU user emulation. The repository's automatic native-execution check may also use a prebuilt `fastfetch` installation to identify machine capabilities, so installing that utility changes the detection path but not the compiler language.

Cross-target constraints for Arm, AArch64 and RV32IM

Cross-compiling a non-native target is possible, but producing a runnable binary has several layers. On an x86-64 GNU/Linux host, the README recommends installing QEMU user emulation for Arm and RISC-V work:

shell
$ sudo apt-get install qemu-user

For the Arm architecture, dynamic-linked output also needs a matching GNU toolchain and glibc. The documented package is:

shell
$ sudo apt-get install gcc-arm-linux-gnueabihf

The README associates the Arm backend with armv7hf and says it was verified on Raspberry Pi 3. AArch64 output requires its own matching cross toolchain and emulator package:

shell
$ sudo apt-get install gcc-aarch64-linux-gnu qemu-user

Its ABI is AAPCS64. The README states that the image loader separates load segments by 64 KiB so that output can load under Linux configurations using 4 KiB, 16 KiB or 64 KiB translation granules. It also limits AArch64 dynamic linking to static and eager-bound forms.

The RISC-V path has a less convenient package story. The README says the target is RV32IM and was verified with QEMU, but Debian-style `apt-get` does not provide the necessary GNU toolchain. It points to a manually obtained `riscv32-glibc-ubuntu-22.04-gcc.tar.xz` package for running dynamically linked output. This is a concrete setup cost.

When switching architectures, run the documented configuration command rather than changing an `ARCH` variable on an arbitrary later build. The repository deliberately records the chosen target so that stale generated settings cannot silently pair with a new selection. The available commands are:

shell
$ make config ARCH=arm
# Target machine code switch to arm
shell
$ make config ARCH=riscv
# Target machine code switch to riscv
shell
$ make config ARCH=arm64
# Target machine code switch to arm64

Reconfiguration is therefore part of target selection, not an optional cleanup step.

Maintenance, licensing and the limits of general C compatibility

The repository is not archived, and its last push was on 2026-10-15. That supports describing the project as receiving recent repository activity, based on its archived status and last-push date. There are no GitHub releases, so published release artifacts are not a documented upgrade path. Repository history and source changes are the relevant maintenance references.

shecc uses the BSD-2-Clause license. That identifier permits redistribution and modification under the license's stated conditions, including retention of the copyright notice, conditions and disclaimer. This is a project fact rather than legal advice. Code that embeds or redistributes shecc should inspect the actual `LICENSE` file rather than treating the short SPDX name as a substitute for its text.

The largest functional limitation is its restricted language model. Basic variadic support does not provide `<stdarg.h>` and depends on direct pointer arithmetic. Function arguments are described as fixed except for that limited variadic mechanism. Although the feature list is broad for a compiler of this kind, the README does not claim full C99 compliance, complete standard-library support, or source compatibility with GCC and Clang extensions. The minimal C library supplies basic I/O on GNU/Linux, not the breadth of glibc or other full C environments.

A real alternative is to use an ordinary compiler such as `gcc` or `clang` as stage0 and as the production toolchain. The difference is architectural: ordinary compilers accept much more real-world C and integrate with mature assemblers, linkers, debuggers and standard libraries, while shecc keeps front-end optimization, VIR, machine IR and emission inside an educational project that can compile itself.

That makes shecc suitable when a single visible compiler can cross four documented targets and demonstrate byte-identical bootstrapping. It is the wrong choice when portability across unknown C code, complete standard-library behavior, or production target coverage matters more than compiler transparency.

Editorial conclusion

Adopt shecc when the goal is to study compiler construction, bootstrap a small C implementation, or generate Linux executables for one of its documented architectures. Do not select it for general production C compilation, unsupported language features, or targets outside ARMv7-A, AArch64, RV32IM and x86-64. Verify first that your programs stay within the documented C subset, that the host can execute the target output, and that dynamic linking has the required target glibc and ELF interpreter.

Frequently asked questions

Which targets can shecc compile for?

shecc targets ARMv7-A, AArch64, RV32IM and x86-64, producing Linux ELF32 output for Arm and RISC-V and ELF64 output for AArch64 and x86-64.

What does it mean for shecc to bootstrap itself?

A stage0 compiler built by an ordinary C compiler produces stage1, and stage1 compiles shecc's own source to produce stage2. Bootstrap verification requires the stage1 and stage2 compiler binaries to be byte-wise identical.

Can shecc run its cross-compiled output on x86-64 Linux?

The README says x86-64 output can run natively on an x86-64 GNU/Linux host. Arm and RISC-V execution on that host requires suitable emulation, with QEMU user emulation given in the prerequisites.

Official sources

  1. Issues
  2. License: BSD-2-Clause
  3. README
  4. sysprog21/shecc on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sysprog21-shecc.svg)](https://hysenlabs.com/projects/sysprog21-shecc)

Was this useful?