Source-linked AI summary
CPL: A Compact C-like Systems Language with Explicit Low-Level Control
Nikolay Fot, Alexander Vinarsky
TL;DR
The paper asks whether a compact C-like language can remain adequate for systems-level programming and compiler experimentation without reproducing the complexity of mature languages. It presents CPL and its compiler pipeline, low-level controls, target backends, and analysis architecture, then evaluates reproducible x86_64 and i386 microbenchmarks against C compiler baselines. The paper establishes feasibility for this design, while leaving compiler correctness, memory safety, broad performance competitiveness, backend maturity, and diagnostic effectiveness unestablished.
Problem
The paper studies the trade-off between a compact compiler suitable for experimentation and the systems-level controls required for realistic low-level programs.
Method
The paper develops CPL with a compact C-like grammar, explicit low-level controls, separated compiler phases, multiple x86 backends, SSA/SMT diagnostics, and reproducible microbenchmarks against C baselines.
Results
The evaluation suggests CPL can produce code comparable to GCC and Clang while providing an integrated compact C-like systems-language and compiler design.
Takeaways & Limitations
CPL establishes the feasibility of combining OS-facing controls with a conventional optimizing pipeline and SSA/SMT analysis in a compact C-like language.
Takeaways & Limitations
The formalization is partial and non-mechanized, CPL has no memory-safety discipline, and the symbolic diagnostic layer lacks established speed and accuracy measurements.
Abstract
from arXiv · showhide
This paper presents Cordell Programming Language (CPL), a compact C-like systems language that retains C's direct access to memory, layout, and machine interfaces while experimenting with a smaller grammar and selected conveniences from newer languages. Also this paper studies whether C-like are more convenient to use for compiler experiments than modern approaches and paradigms. While the language and compiler provide primitive values, pointers, arrays, containers with methods, unions, generic functions, overloads, entry-point and section control, system calls, and inline assembly, they do not provide high-level constructs such as classes, built-in methods, a standard library, or memory protection. The article describes the language design, compiler pipeline, target backends, static-analysis architecture, and OS-facing use cases, then evaluates the prototype backend with reproducible x86_64 and i386 microbenchmarks against C compiler baselines. The obtained results suggest that the compiler can produce code comparable to that produced by production compilers such as GCC and Clang, as well as by small compilers such as TinyC and SmallerC.
1 Introduction
CPL targets the trade-off between compiler compactness and systems-level adequacy by combining a small, parser-friendly grammar with explicit low-level control. Its C-like design exposes pointers, layouts, entry points, and machine interfaces while avoiding classes, implicit object lifecycles, and broader C complexity.
- Language design: The grammar favors compact parsing, explicit function and pointer-operation syntax, and compiler-supported low-level constructs.This design avoids reproducing the full C grammar and semantics while preserving direct systems programming controls.
- Motivation: CPL addresses the tension between making compiler experiments easier to inspect and retaining mechanisms needed for realistic low-level programs.The paper frames this as a trade-off between implementation compactness and systems-level adequacy.
- Language design: CPL exposes pointers, explicit dereferencing, C-like structures, arrays, unions, layout annotations, direct system calls, and inline assembly.Pointers use explicit ptr, ref, and dref forms; containers support layouts and attached functions without becoming classes.
- Program entry: A start block or @[entry] annotation can define a program entry point, while naked entry behavior supports code that manages its own prologue and epilogue.The naked form also changes i386 stack-argument addressing by suppressing the generated function frame.
- Containers: Container functions provide syntactic attachment or method-like calls, but initialization, cleanup, and ownership-like behavior remain explicit programmer responsibilities.The self annotation passes a receiver pointer automatically at the call syntax level, without introducing constructors, destructors, or an object-oriented runtime.
- Low-level control: Inline assembly is copied with minimal changes and is not optimized by the compiler, so programmers must preserve registers and match target syntax.This preserves machine-level control but makes the facility intentionally powerful and fragile.
3 Compiler Architecture
CPL’s compiler separates frontend analysis, SSA-based middle-end processing, target-sensitive backend lowering, and assembly generation. The architecture supports both compiler experimentation and low-level targets, including i386 ABI and naked-function handling.
- Pipeline: The frontend preprocesses, tokenizes, builds the AST, and performs early semantic checks before HIR and SSA construction.The middle-end then applies SSA-level checking and HIR optimizations.
- Pipeline: The middle-end converts HIR to SSA, performs symbolic checking, and applies high-level optimizations before lowering to LIR.This organization separates source analysis, SSA construction, symbolic checking, and optimization.
- Backend: The backend performs data-flow and copy-propagation passes, instruction selection, register allocation, peephole cleanup, and assembly emission.The evaluated targets include x86_64 Mach-O, x86_64 GNU/Linux, and i386 GNU/Linux NASM backends.
- Worked Example: CPL examples are lowered from source through HIR and LIR basic blocks into target-dependent register selections before generated assembly.The worked example represents stack variables with an s suffix and temporaries with a t suffix.
- i386 ABI: The i386 backend follows a C-style stack ABI, while naked functions suppress the frame prologue and address arguments from esp-relative locations.Ordinary functions may use ebp-based locations such as [ebp + 8], whereas naked functions use [esp + 4] for the first argument.
4 Static Diagnostics
CPL combines AST diagnostics with an SSA-level symbolic layer backed by Z3. The checker can report path-sensitive issues such as ignored return values and possible null dereferences, but it is diagnostic rather than protective and remains unevaluated for scalability and accuracy.
- Diagnostic Layers: The AST diagnostic layer checks source-structure issues including invalid references, argument mismatches, illegal accesses, dead code, and lossy conversions.It targets errors and suspicious constructs before solver-backed analysis.
- SSA-Level Analysis: SSA-level checking uses use-definition chains, phi nodes, and path conditions to structure symbolic reasoning over compiler variables.SSA simplifies symbolic value tracking and makes generated constraints easier to relate to compiler variables.
- Scope: The analysis does not enforce memory safety, ownership, or aliasing restrictions and cannot prevent undefined behavior from unsafe low-level operations.The subsystem is an experimental diagnostic component rather than a formal verification framework.
- Diagnostics: A representative example reports both an ignored return value and a possible null dereference for foo(a) when a equals null.More complex inputs can produce path-sensitive reports for definitely or conditionally null pointers.
- Query Interface: The symbolic layer supports CFG construction and solver-backed label-reachability and variable-equality queries through a wrapper interface.The interface accepts parsed JSON or textual HIR dumps and supports function selection and pointer-width configuration.
- Evaluation Limits: The evaluation does not isolate solver time, measure growth with function size or path count, or compute false-positive and false-negative rates.Consequently, the speed and diagnostic accuracy of the symbolic design remain unestablished.
5 Optimization Passes
CPL’s evaluated optimization pipeline combines loop-invariant code motion, constant and peephole optimizations, and LIR transformations across optimization levels. PTRN makes late local rewrites declarative and extensible, but its independent performance effect is not measured, and inlining is excluded from evidence.
- Optimization Profiles: At -O0 CPL uses default passes; -O2 adds LICM, constant optimization, and peephole cleanup; -O3 additionally enables LIR copy propagation and tail-recursion elimination.Experimental function inlining is excluded because known correctness defects prevent an interpretable performance claim.
- Loop-Invariant Code Motion: LICM moves loop-invariant computations such as 10 + 10 before the loop while phi nodes preserve loop-carried values.The pass operates on SSA High IR.
- Peephole Optimization: Late peephole optimization removes redundant self-moves, simplifies decrement-and-branch sequences, and can replace zero materialization with xor-style idioms.It runs after instruction selection and register allocation.
- PTRN: PTRN describes Low LIR rewrite patterns with match objects, conditions, and replacement actions, then generates C code for the peephole pass.Its rules can abstract over registers, constants, memory locations, and arbitrary objects.
- PTRN: PTRN improves extensibility by allowing new local simplifications to be added as declarative rules rather than direct peephole-engine modifications.It does not replace global optimization or data-flow analysis.
- Evaluation Limits: The benchmark data cannot attribute an independent performance improvement to PTRN because it measures the complete optimization pipeline.PTRN is therefore evaluated as an implementation mechanism rather than an isolated source of speedup.
6 Examples and Use Cases
CPL examples demonstrate low-level programming across user-space, kernel, driver, and boot contexts while retaining typed source structure alongside explicit assembly where required. These cases exercise pointers, syscalls, ABI boundaries, layout control, and register-specific operations.
- Representative Programs: The representative examples cover Hello World, CRC8, a Brainfuck interpreter, memory and file helpers, OS kernel helpers, and Multiboot boot code.The examples were shortened to isolate language mechanisms relevant to each case.
- Representative Programs: The Hello World case combines strings, pointers to string literals, strlen, inline assembly, direct syscalls, and entry-point termination.Its shortened listing uses a direct syscall wrapper.
- Operating-System Code on i386: CPL supports i386 kernel routines through NASM-compatible assembly, globally visible functions, and extern declarations that integrate with C kernel code.The keyboard-driver example uses C externs and an exported boundary.
- Multiboot Kernel Bootstrap: The Multiboot example represents packed layout, section placement, alignment, entry symbols, register bindings, stack construction, and kernel transfer in typed source.It uses explicit annotations for layout and control while preserving inline assembly for target-specific operations.
- Multiboot Kernel Bootstrap: Inline assembly remains necessary for loading esp, disabling interrupts, and halting the processor, so the bootstrap narrows rather than eliminates handwritten assembly.The remaining assembly covers privileged or register-specific effects not represented by ordinary CPL expressions.
7 Testing Infrastructure
CPL testing emphasizes phase observability and executable behavior, allowing regressions to be localized across compiler stages while covering both ordinary programs and OS-oriented i386 examples.
- Phase Observability: The phase-oriented framework inspects compiler stages from preprocessing and AST construction through SSA, optimization, register allocation, and assembly generation.This observability is intended to make regressions easier to localize than end-to-end testing alone.
- Test Oracles: OUTPUT oracles match runtime output, exit codes, and selected compiler dumps while tolerating unstable generated identifiers.Runtime tests can execute generated assembly and include multiple argument cases in one file.
- Regression Tracking: BUG preserves expected failing tests, and LEAK_TRACE enables memory-operation logging for leak localization in compiler-internal tests.Together with phase observability, these mechanisms track regressions across frontend, middle-end, backend, and runtime behavior.
- Regression Corpus: The regression corpus spans output programs, loops, CRC-style traversal, arithmetic kernels, function calls, Fibonacci, switch dispatch, Brainfuck, and OS-oriented i386 examples.It checks internal forms and executable behavior without constituting a proof of correctness.
8 Experimental Evaluation
The evaluation combines a qualitative systems case study with reproducible x86_64 and i386 microbenchmarks against GCC and Clang. CPL matches the baselines on selected workloads but shows architecture- and workload-dependent gaps, while several explanatory and size claims remain unmeasured.
- Evaluation Design: The evaluation combines a qualitative systems case study with a microbenchmark study, separating measured behavior from properties supported only by implementation design.The benchmark kernels expose loop, arithmetic, branching, calls, table traversal, string traversal, and Fibonacci behavior.
- Benchmark Method: GCC, Clang, and CPL are compared at -O0 and -O3 on x86_64 Linux and i386 Linux using freestanding entry points and ten-execution arithmetic means.CPL is assembled with nasm and linked with ld -e _main; the harness and raw repetitions are stored with the benchmark materials.
- Optimized Runtimes: CPL is effectively tied with GCC and Clang on the preserved empty counted loop on both x86_64 and i386.On x86_64, CPL is also within the same millisecond-scale range on Fibonacci, while the C compilers are faster on other optimized kernels.
- Optimized Runtimes: On i386, CPL slows more strongly on 64-bit arithmetic, function calls, pointer-string traversal, and Fibonacci, with the largest gaps in table traversal and pointer string scanning.The text presents register allocation and operation lowering as consistent explanations, but identifies them as hypotheses requiring instruction-count and hardware-counter validation.
- Effect of Optimization within CPL: CPL -O3 improves most measured kernels, but i386 Fibonacci is a counterexample in the optimization comparison.Because no pass-by-pass ablation was performed, the improvement cannot be assigned to an individual optimization.
- Evaluation Boundaries: Generated-code size was not measured in object bytes or decoded instruction counts, because source or assembly-line counts were treated as unreliable proxies.The reported evaluation therefore does not establish code-size comparisons.
9 Limitations and Threats to Validity
The evaluation has broad validity boundaries: it covers selected targets, small microbenchmarks, and known tests rather than complete compiler behavior. Important claims remain constrained by partial formalization, unsafe inline assembly, excluded inlining prototypes, and absent differential or hardware-level validation.
- The formalization covers only a small pointer-and-container core and provides no semantic-preservation proof or translation validation.
- CPL has no memory-safety or ownership discipline, although its static analyzer can find selected problems.
- Target validation is strongest for x86_64 Mach-O/NASM; Linux and other architecture or system options have narrower or partial coverage.
- Heuristic and model-guided inlining prototypes are excluded because their correctness and effectiveness have not been established.
- The microbenchmark suite omits large applications, broad target comparisons, and enough kernels to characterize general performance.
- Measurements use one Linux x86_64 host and omit cache, branch, bandwidth, and other memory-hierarchy analyses.
- The phase-oriented regression suite lacks randomized differential validation, so reliability beyond covered tests is not quantified.
- The results are prototype evidence for the implementation direction, not a complete evaluation of every backend and optimization combination.
10 Related Work
CPL occupies a narrow design niche between compact compiler infrastructures, practical C-compatible tools, richer systems languages, and formally assured systems. Its contribution is feasibility evidence for combining inspectable source syntax, OS-facing controls, optimization, and experimental SSA/SMT diagnostics, not superiority over those alternatives.
- The comparison separates language scope, objective, low-level access, formal assurance, and backend maturity across representative systems languages and compiler infrastructures.
- QBE offers a compact backend, whereas TinyCC offers practical C compatibility but retains C's complexity and lacks a small SSA/SMT experimentation organization.
- Zig provides stronger language and ecosystem support, while CompCert, Cogent, and Low* provide formal assurance outside CPL's claims.
- CPL combines an inspectable source language, boot- and kernel-facing controls, a multi-stage optimizing pipeline, and an experimental SSA/SMT diagnostic layer.
- The contribution is design and feasibility evidence for this engineering combination, not evidence of superiority, formal assurance, validation methodology, benchmark breadth, or backend maturity.
- Classes, inheritance, ownership-oriented aggregate models, and richer user-defined type systems remain outside CPL's deliberate language model.
11 Conclusion
The conclusion establishes feasibility for CPL's compact C-like design, OS-facing controls, optimizing pipeline, and SSA/SMT analysis. It explicitly limits that conclusion by withholding claims about correctness, safety, general performance, backend maturity, and diagnostic effectiveness.
- The article establishes feasibility of a compact C-like source language combining OS-facing controls with conventional optimization and SSA/SMT analysis.
- The article does not establish compiler correctness, memory safety, general performance competitiveness, backend maturity, or diagnostic effectiveness.
- The withheld claims require formalization, differential validation, expanded benchmarks, hardware-counter measurements, and labeled diagnostic evaluation.
A Abridged BNF Grammar of CPL
The abridged BNF presents CPL's top-level program forms, declarations, types, statements, and preprocessing-related constructs. It documents principal parser forms rather than complete operational semantics, while the compiler exposes multiple phases and optimization profiles around this grammar.
- The grammar is an abridged reconstruction of principal parser forms, not a complete formal operational semantics.
- A program consists of top-level items including start, function, container, external, import, section, alignment, variable, preprocessing, and block forms.
- Function declarations support optional generic parameters, parameter lists, return types, and either declarations or blocks.
- Parameter syntax includes annotations, variadic forms, self receivers, typed identifiers, and optional initializers.
- The keyword set exposes explicit low-level operations such as ptr, dref, ref, syscall, asm, section, and align, while containers support fields and method-like declarations.