Architecture
Thorn is structured around a single-binary embedding model: one tool binary serves every project without per-project compilation or AOT plugin extraction.
Component Overview
Thorn’s core is implemented in clean, modular C99 with separated compilation units and public headers in src/include/:
1. CLI Driver & Embedding (src/main.c, src/engine.c)
src/main.c: The command-line entry point. Handles option parsing (supporting subcommandsbuild,decompile,version,help, and positional spec paths), toolchain binary verification inPATH, Pith runtime context initialization, host function registration under theenginenamespace, and execution orchestration.src/engine.c: The host runtime API bridge. Implements the C host functions (c_engine_*) exposed to the embedded Pith evaluation environment, providing typed wrappers between Pith’s ARC/value system and Thorn’s internal build graph.
2. Build Graph & Invariants (src/graph.c, src/include/graph.h)
src/graph.c: In-memory Directed Acyclic Graph (DAG) state manager. Manages projects, targets (Target), custom commands (Command), flags, includes, and order dependencies.- Enforces strict deterministic invariants:
- Whitespace Rejection: Rejects spaces or newlines in target names, source paths, flags, and include directories.
- Source Classification: Classifies sources into C (
SRC_C), raw assembly (SRC_ASM_RAW), and preprocessed assembly (SRC_ASM_CPP). - Duplicate Elimination: Detects and eliminates duplicate sources, includes, and flags.
- Dynamic Package Queries: Resolves system
pkg-configflags at generation time without deferred shell subshells.
3. Backend Emitters (src/emit.c, src/ninja.c, src/make.c)
src/emit.c: Generation coordinator and filesystem driver. Validates the graph, ensures destination directories exist, and dispatches generation to the active backend(s).src/ninja.c: High-performance Ninja backend generator (build.ninja). Emits canonical rule definitions (cc,as,as_cpp,ar, custom commands), dependency tracking withdeps = gccanddepfile = $out.d, and topologically ordered build edges for byte-for-byte reproducibility.src/make.c: Portable POSIX / GNU Makefile generator (Makefile). Emits pattern rules (%.o: %.c,%.o: %.s,%.o: %.S), header dependency tracking (-MMD -MP), automatic defaultall:rules, token translation ($in->$<,$out->$@), and automaticcleantargets.
4. Reverse Decompiler Engine (src/decompile.c, src/parser.c, src/lex.c, src/spec.c)
src/decompile.c: Reverse engineering orchestrator. Drives parsing of existing Ninja or Make syntax and exposes a rich, hookable FFI API (decompile.*) usable by standalone scripts and external tools (such asthornkfor Linux Kbuild conversion).src/lex.c: Lexical scanner and tokenizer for Ninja and Make syntax.src/parser.c: AST parser extracting variables, rule definitions, and build edges from backend files.src/spec.c: Specification synthesizer; translates extracted build graphs into idiomatic, formattedbuild.thornPith specifications.
5. Modular Header Architecture (src/include/)
Internal subsystem boundaries are strictly enforced via modular C headers:
engine.h: Target model, source kinds, and host function prototypes.graph.h: Graph allocation, source classification, and validation.emit.h: Backend generation entry points.ninja.h: Ninja backend emitter declarations.make.h: Makefile backend emitter declarations.parser.h: Ninja and Makefile AST structures and parse APIs.lex.h: Token definitions and lexical scanner state.spec.h: Spec synthesis and formatting declarations.
Runtime Embedding of Pith
Thorn embeds the Pith compiler frontend directly:
- Host Function Registration: During startup,
main.cregisters the engine functions usingpith_register_ns_fn()under theenginenamespace. Each function specifies its return and parameter classes according to Pith’s C ABI (e.g.vfor void,wfor 32-bit int,pfor ARC string pointer). - Epilogue Evaluation: When evaluating
build.thorn, Thorn wraps or appends a call toengine.emit()after executing the project statements. - Execution Fallback & Link Objects:
- In environments permitting JIT (libtcc in-memory execution), Pith executes the byte instructions directly against the loaded host symbols in Thorn’s process memory.
- On SELinux-hardened or
W^Xsystems where runtime memory execution is restricted, Pith falls back to compiling a temporary child executable via QBE/TCC. - To support this fallback child, Thorn registers
out/artifacts/engine.o(orlibthorn_core.a) usingpith_register_link_object(). The child links against this object and builds the identical graph, emitting the backend files seamlessly.
Self-Hosting
Thorn builds itself using its own generated backend. During tests/verify.sh:
out/thornis built from C source using the bootstrappingMakefile.out/thornruns on its ownbuild.thorn, emittingout/artifacts/build.ninja.samubuildsout/artifacts/thornfrombuild.ninja.- The resulting Stage 2 binary runs, builds projects, and decompiles backends deterministically.