native-pascal-1981
A native reimplementation of a Pascal compiler from 1981
- Active · last change 10-10-2026
- ★ 1 on GitHub
README
Native Pascal 1981 Compiler
This repository contains a native compiler for the 1981 IBM Pascal dialect. It targets LLVM IR, the System V AMD64 ABI, and NVIDIA NVPTX.
Contributors and coding agents: read AGENTS.md first. It states the compatibility rule, what the checking switches cover, and the rules for writing Pascal and GPU kernels in this repository.

System Prerequisites
Install these packages before you build the toolchain (for example, on Debian or Ubuntu Linux x86_64):
clang(C compiler and linker)make(build tool)libllvm-20-dev/llvm-20(LLVM 20 library and headers)libcjson-dev(cJSON library and headers)indent(C code formatting tool)
The build needs no Python and no other Pascal compiler. Some optional test groups need more; see Running Tests.
Repository Layout
src/: Native compiler stages and driver in Pascal (lexer.pas,parser.pas,typechecker.pas,codegen.pas,driver.pas,jsonutil.pas,jsonutil.inc). The codegen stage is a composition root over fourteen separately compiled units. Each unit has a.incinterface and a.pasimplementation. The units layer lowest first. Each unit depends only on units at a lower layer:argparse: command-line argument parsing (shared by parser, typechecker, and codegen).features: shared language-feature resolution for the two dialects.cg_base: shared compiler state, LLVM-C and libc prototypes, constants and record types.cg_util: diagnostics, depth guards, string and pointer-array helpers.cg_types: the type registry, sizing and layout, constant folding, SysV classification.cg_symbols: symbol, scope and routine tables.cg_expr_shape: type-only expression-shape queries. It emits no IR.cg_expr_sets: set operations on already-evaluated operands.cg_expr_support: leaf expression-lowering helpers. It never evaluates an AST node.cg_expr_literals: leaf lowering for assignments from compile-time string literals.cg_expr_vector: lowering forVECTOR [n] OF scalaroperations.cg_expr: expression lowering.cg_io: WRITE/READ lowering.cg_stmt: statement lowering.cg_decl: declaration lowering.
runtime/: C runtime static library and headers (libpascalrt.a,pascalrt.h).bootstrap/:pasboot, a C99 translator from the bootstrap subset of the dialect to C. It builds Generation 1 of the compiler stages.docs/bootstrap_subset.mddefines the subset.bin/: Compiler driver (pascal1981-native, aliaspascal1981) and stage binaries (lexer,parser,typechecker,codegen).scripts/: Build scripts (build-stage.sh), formatting scripts (beautify.sh), and git hooks (scripts/hooks). To enable the pre-commit formatting hook, rungit config core.hooksPath scripts/hooksonce per clone. This setting is local config. A fresh checkout does not enable the hook. The rootMakefiledrives the multi-generation bootstrap with thebootstraptarget. It is not a standalone script.tests/: Test suites (golden files, unit tests, integration tests, dialect fixtures).docs/: The EBNF grammar of the dialect. The dialect notes cover widths, literals, and silent failure modes.
Temporary files
The temporary-file ownership contract is the canonical guide to scratch isolation and cleanup for the compiler, runtime, test harnesses, and Emacs mode. Read it before changing temporary-file handling or manually removing abandoned workspaces.
Building
To build the runtime, the driver, and all compiler stages (bootstrap):
makeBy default, the build uses llvm-config to determine the LLVM linker flags and libraries. If llvm-config is not in PATH, the build uses llvm-config-20. You can override the default by setting LLVM_CONFIG:
LLVM_CONFIG=llvm-config-20 makeYou can also override the C compiler/linker (default clang) with CC:
CC=clang-20 makeTo rebuild only the four compiler stages, run:
make bootstrapThen build the installed Pascal driver from the fixed-point stages:
make driverThe bootstrap process has four steps:
- Generation 1 (Bootstrap): Builds
bootstrap/pasboot. Thenpasboottranslates each compiler-stage compiland to C, andclangcompiles it. The Generation 1 sources must stay inside the bootstrap subset. - Generation 2 (Self-hosted): Recompiles all native compiler stages with the Generation 1 binaries.
- Generation 3 (Self-hosted): Recompiles all native compiler stages with the Generation 2 binaries.
- Generation 4 (Fixed Point): Recompiles all native compiler stages with the Generation 3 binaries and verifies binary identity (
cmp).make driverthen compilessrc/driver.paswith the fixed-point stages and installs it asbin/pascal1981-native(withbin/pascal1981as its alias).
Compiling a Program
To compile a Pascal program with the native compiler, run:
bin/pascal1981-native hello.pas -o hello
./helloSupported options:
-o <file>: Set output path-c: Compile to object file (.o)-S: Emit LLVM IR (.ll)-O0,-O1,-O2,-O3: Set optimization level--dialect <vintage|extended>: Select the language dialect (defaultvintage)--target-cpu <cpu>: Host target CPU, e.g.x86-64-v3(LLVMtarget-cpuattribute; default baseline x86-64)--target-features <list>: Host target features, comma-separated, e.g.+avx2,+fma--emit-ptx: Emit NVPTX assembly for device kernels (kernels doing integer arithmetic need{$MATHCK-}; see above)-v: Print pipeline commands
Running Tests
Run the routine test suites:
make testEvery suite is a script in a tier directory under tests/, grouped by what
it does mechanically:
| Tier | What its suites do |
|---|---|
tests/check/ |
Static checks: generation 1 stays inside the bootstrap subset; the pre-commit hook and formatter; the suite index in tests/README.md is current. |
tests/unit/ |
Runtime C unit tests, pasboot fixtures, the test launcher. |
tests/corpus/ |
Fixture corpora compiled and compared with expected output: golden programs, IR directives, depth limits, AST comparison. |
tests/contract/ |
Scripted checks of compiler, driver and runtime behavior (MATHCK, INITCK, INDEXCK, descriptors, stage CLIs, ...). |
tests/service/ |
The completion proxy against a deterministic stub backend (needs python3). |
tests/optional/ |
Opt-in: CUDA hardware, overhead measurements. Never run by make test. |
make test runs the check and unit tiers first (they need no
bootstrap), then builds every tool and runs the corpus, contract and
service tiers. Suites run in parallel, longest first. It runs every suite
even after one fails, and ends with a summary of failed suites with their
FAIL lines, and of suites that skipped checks; each suite's full output is
kept in build/test-results/<suite>.log. make test-quick runs only the
first two tiers, in seconds.
To run some suites, name tiers or suites, or run a suite script directly (from any directory; it sets up the same environment itself):
make test SUITES="contract depth"
./tests/contract/mathck_vector.shThe test runners do not require pytest. The proxy tests need python3.
Install bubblewrap (bwrap; the bubblewrap package on Debian or Ubuntu) as
well. The temporary-directory safety test runs its cases in a bwrap
sandbox with a private /tmp; without bwrap that test is skipped rather
than failed, so make test passes with less coverage. The summary lists it
among the suites that skipped checks.
These targets are separate from make test:
| Target | Test group |
|---|---|
make test-gpu |
CUDA compilation and execution on an NVIDIA GPU |
make test-elisp |
Emacs major-mode ERT tests |
make test-bootstrap |
Clean bootstrap and fixed-point comparison, with Python made unavailable |
If a CUDA prerequisite is not available, the test-gpu target skips the test.
The native compiler is the authoritative implementation; no test compares it with the earlier Python implementation. The test-elisp target requires Emacs and builds the compiler stages first.
See tests/README.md. It describes the compiler test suites. Before adding or changing a test, read docs/test_portability.md: the suites must not depend on a quiet compiler, particular bash/make/LLVM versions, or optional tools.
To run all available test groups, run (test-bootstrap deletes build/, so
it must run on its own):
make test-bootstrap
make test test-gpu test-elisp
