Objeck Language Architecture

This document provides detailed architectural views of the Objeck programming language implementation, including the compiler, virtual machine, JIT compilation, and development tools.

Comprehensive technical architecture with interactive diagrams

Objeck toolchain architecture

The whole toolchain on one page (SVG); the sections below go into each part. For a short written map with the invariants and the page that goes deeper on each part, see ARCHITECTURE_MAP.md.


1. Complete System Overview

The whole toolchain, from the editor to the five platforms a release ships to. Source is compiled by obc into an executable (.obe) or a library (.obl); obr runs it, interpreting until a method is hot and then compiling it to native code. The language server analyzes the same source with the compiler's own front end, linked into a native library.

flowchart LR
    subgraph AUTHOR["Authoring"]
        direction TB
        EDITORS["Editors<br/>VS Code · Neovim · Vim · Emacs<br/>Helix · Sublime · Kate"]
        LSP["Language server<br/>tools/lsp (Objeck)<br/>LSP 3.17: diagnostics, hover,<br/>completion, rename, symbols,<br/>semantic tokens, code lens"]
        DIAGS["libobjk_diags<br/>compiler front end<br/>as a native library"]
        EDITORS <-->|"JSON-RPC over stdio"| LSP
        LSP --> DIAGS
    end

    subgraph COMPILE["Compiler · obc"]
        direction TB
        SRC[".obs source"]
        FRONT["Scanner → Parser<br/>→ Context analyzer<br/>types, generics, named and<br/>ref arguments, try/on/finally"]
        BACK["Intermediate emitter<br/>→ Optimizer s0–s3<br/>(inlining, tail calls,<br/>constant folding)"]
        LINK["Linker<br/>reads .obl libraries"]
        OUT[".obe program<br/>.obl library"]
        SRC --> FRONT --> BACK --> OUT
        LINK --> FRONT
    end

    subgraph RUN["Virtual machine · obr"]
        direction TB
        LOAD["Loader"]
        INTERP["Stack interpreter<br/>?-> and try regions,<br/>error recovery"]
        JIT["JIT compilers<br/>AMD64 · ARM64<br/>hot methods → native"]
        GC["Memory manager<br/>generational GC,<br/>multithreaded"]
        TRAPS["Trap processor<br/>I/O · sockets · threads<br/>time · native calls"]
        LOAD --> INTERP
        INTERP <-->|"bridge, statuses"| JIT
        INTERP <--> GC
        JIT <--> GC
        INTERP --> TRAPS
    end

    subgraph LIBS["Libraries · 46 sources, .obl"]
        direction TB
        CORE["lang · collections · concurrency<br/>regex · math · misc"]
        DOMAIN["Data · Networking · Web<br/>AI and ML · Media"]
        NATIVE["Native modules<br/>.dll · .so · .dylib<br/>(see the library stack below)"]
        DOMAIN --> NATIVE
    end

    subgraph TOOLS["Command-line tools"]
        direction TB
        OBI["obi · REPL and editor"]
        OBD["obd · debugger<br/>CLI and Debug Adapter Protocol"]
        OBU["obu · updater<br/>signed release manifests"]
        OBB["obb · app builder<br/>native launchers"]
    end

    subgraph PLAT["Platforms"]
        direction TB
        WIN["Windows x64 · ARM64"]
        LNX["Linux x64 · ARM64"]
        MAC["macOS ARM64"]
    end

    subgraph SHIP["Build, test and release"]
        direction TB
        CI["GitHub Actions<br/>CI on 5 platform legs<br/>regression suite ×3 JIT modes<br/>LSP, DAP, debugger tests"]
        REL["Release build and publish<br/>MSI · ZIP · TGZ · signed .pkg<br/>.vsix · SHA256SUMS (minisign)"]
        SITE["objeck.org API docs<br/>playground (sandboxed)"]
        CI --> REL --> SITE
    end

    DIAGS -.->|"same front end"| FRONT
    EDITORS -->|"debug sessions"| OBD
    OUT --> LOAD
    CORE --> LINK
    DOMAIN --> LINK
    NATIVE -->|"loaded at run time"| TRAPS
    OBI --> FRONT
    OBI --> INTERP
    OBD <--> INTERP
    OBU --> REL
    TRAPS --> PLAT
    JIT --> PLAT
    COMPILE -.-> CI
    RUN -.-> CI

The library stack

Most libraries are Objeck alone. The rest pair an Objeck interface with a C++ module that the VM loads when the program first calls it, and that module wraps the third-party code a release bundles or the platform supplies.

flowchart TB
    subgraph OBJECK["Objeck libraries (.obl)"]
        direction LR
        L_CORE["lang · gen_collect<br/>concurrent · regex · misc"]
        L_DATA["json · json_stream · xml<br/>csv · query · odbc"]
        L_NET["net · net_h2 · net_quic<br/>net_server · web_server<br/>json_rpc · rss · cipher"]
        L_AI["ml_* · ai_* · nlp · nlp_onnx<br/>ai_hub (Hugging Face Hub)<br/>openai · gemini · ollama<br/>models · inference"]
        L_VISION["onnx · opencv"]
        L_MEDIA["sdl2 · sdl_game · sdl_gl<br/>lame"]
        L_TOOLS["diags"]
    end

    subgraph MODULES["Native modules (C++)"]
        direction LR
        M_CRYPTO["crypto"]
        M_NET["net / TLS"]
        M_ODBC["odbc"]
        M_MATRIX["matrix"]
        M_ONNX["onnx"]
        M_CV["opencv"]
        M_SDL["sdl"]
        M_LAME["lame"]
        M_DIAGS["diags"]
    end

    subgraph THIRD["Third-party and system code"]
        direction LR
        T_TLS["mbedTLS"]
        T_HTTP["nghttp2 (HTTP/2)<br/>ngtcp2 + nghttp3 (HTTP/3, POSIX)"]
        T_ODBC["ODBC driver manager"]
        T_ORT["ONNX Runtime<br/>CPU · DirectML · QNN<br/>CUDA · CoreML"]
        T_CV["OpenCV"]
        T_SDL["SDL2 · image · mixer · ttf<br/>OpenGL 3.3 core"]
        T_LAME["LAME (MP3)"]
        T_ZLIB["zlib"]
        T_CC["obc front end"]
    end

    L_NET --> M_CRYPTO & M_NET
    L_DATA --> M_ODBC
    L_AI --> M_MATRIX
    L_AI -->|"model inference"| M_ONNX
    L_VISION --> M_ONNX & M_CV
    L_MEDIA --> M_SDL & M_LAME
    L_TOOLS --> M_DIAGS
    L_CORE -->|"compression"| T_ZLIB

    M_CRYPTO & M_NET --> T_TLS
    M_NET --> T_HTTP
    M_ODBC --> T_ODBC
    M_ONNX --> T_ORT
    M_CV --> T_CV
    M_SDL --> T_SDL
    M_LAME --> T_LAME
    M_DIAGS --> T_CC

Key components:


2. Compiler Pipeline

Detailed compilation flow from source to bytecode with optimization stages.

flowchart LR
    subgraph "Frontend"
        A[Source .obs] --> B[Scanner]
        B --> C[Parser]
        C --> D[AST]
        D --> E[Contextual Analyzer]
    end

    subgraph "Optimization Pipeline"
        E --> F[Intermediate Code]
        F --> G{Optimization Level?}

        G -->|s0 None| H[Skip Optimization]
        G -->|s1 Basic| I[Constant Folding<br/>Dead Code Removal]
        G -->|s2 Moderate| J[+ CSE, Loop-Invariant Motion<br/>+ Strength Reduction]
        G -->|s3 Aggressive| K[+ Method Inlining<br/>+ Instruction Replacement<br/>+ Peephole Optimization]

        H --> L[Bytecode]
        I --> L
        J --> L
        K --> L
    end

    subgraph "Backend"
        L --> M[Linker]
        N[Libraries .obl] --> M
        M --> O[Executable .obe]
    end

    style G fill:#fff4e1
    style K fill:#e1ffe1

Optimization Levels

LevelNameOptimizationsUse Case
s0NoneNo optimizationFast compilation, debugging
s1BasicConstant folding, dead code eliminationDevelopment builds
s2Moderate+ Common-subexpression elimination, loop-invariant code motion, strength reductionTesting
s3Aggressive+ Method inlining (not in libraries), instruction replacement, peephole optimizationProduction (default)

Optimization Passes (s3)

In the order ItermediateOptimizer runs them on each method, once each; the method inliner runs last, after every method of the class has been through the rest.

graph TD
    A[Intermediate Code] --> B[Jump Cleanup]
    B --> C[Dead Block Elimination]
    C --> D[Tail Calls, Getter/Setter Inlining]
    D --> E[Constant Propagation]
    E --> F[Dead Store Removal]
    F --> G[CSE, Loop-Invariant Motion]
    G --> H[Constant Folding]
    H --> I[Strength Reduction]
    I --> J[Instruction Replacement, Peephole]
    J --> K[Dead Code Elimination]
    K --> L[Method Inlining]
    L --> M[Optimized Bytecode]

    style A fill:#e1f5ff
    style M fill:#e1ffe1

Performance Impact:

Source Files:


3. Virtual Machine Runtime

Execution model with interpreter and JIT compilation.

flowchart TB
    subgraph "VM Initialization"
        A[Load Bytecode .obe] --> B[Parse Program Header]
        B --> C[Load Class Metadata]
        C --> D[Initialize Memory Manager]
    end

    subgraph "Execution Engine"
        D --> E[Runtime Interpreter]
        E --> F{Hot Code?}

        F -->|"not yet"| E
        F -->|"10th call, or first call<br/>of a method with a loop"| G[Compile Method]

        G --> H{Platform?}
        H -->|ARM64| I[ARM64 JIT Compiler]
        H -->|x64| J[AMD64 JIT Compiler]

        I --> K[JIT Code Cache]
        J --> K

        K --> L[Execute Native Code]
        L --> M{More Code?}
        M -->|Yes| F
        M -->|No| N[Exit]
    end

    subgraph "Memory Management"
        O[Memory Manager]
        O <--> E
        O <--> I
        O <--> J
        O <--> L
    end

    subgraph "Host Interaction"
        P[Host APIs]
        E --> P
        L --> P
        P --> Q[File I/O, Network, Graphics]
    end

    style F fill:#fff4e1
    style K fill:#e1ffe1
    style O fill:#ffe1e1

Execution Modes

stateDiagram-v2
    [*] --> Interpreted
    Interpreted --> Counting: Called
    Counting --> JIT_Compiling: 10th call, or first call with a loop
    JIT_Compiling --> Interpreted: Pre-scan rejects the method
    JIT_Compiling --> Native_Execution: Compilation complete
    Native_Execution --> [*]: Method returns

    note right of Counting
        Call count tracked
        per method; no
        deoptimization
    end note

    note right of JIT_Compiling
        Platform-specific
        ARM64 or AMD64
    end note

Memory Layout

graph TB
    subgraph "Memory Regions"
        A[Operand and Call Stacks]
        B[Nursery, bump-allocated]
        C[Old Generation]
    end

    A -->|"Roots: locals, frames"| F[Memory Manager]
    B -->|New objects| F
    C -->|"Promoted objects, arrays,<br/>native allocations"| F

    F -->|"O(1) membership"| G[Old-gen address set]
    F -->|GC| H["Minor: promote survivors<br/>Major: mark and sweep"]

    style F fill:#ffe1e1
    style G fill:#e1f5ff

Key Features:

Call Depth

Each VM thread has its own call stack: an array of frame pointers, with the operand stack beside it (core/vm/call_stack.h). Calls may nest 16,384 frames deep by default; obr --call-depth=<frames> (or OBJECK_CALL_DEPTH) sets it, from 256 to 1,048,576. Until 2026-10 the limit was a fixed 256 frames, which the Rosetta "Man or boy" test (647 frames at k=10) and a recursive-descent parser on input nested a few hundred deep both exceeded.

Source Files:


4. JIT Compilation Architecture

Platform-specific native code generation for ARM64 and AMD64.

graph TB
    subgraph "JIT Compilation Flow"
        A[Hot Method Detected] --> B{Select Platform}

        B -->|ARM64| C1[ARM64 JIT Compiler]
        B -->|AMD64| C2[AMD64 JIT Compiler]

        C1 --> D1[AArch64 Register Allocation]
        C2 --> D2[x86-64 Register Allocation]

        D1 --> E1[ARM64 Instruction Selection]
        D2 --> E2[x64 Instruction Selection]

        E1 --> F1[Generate AArch64 Code]
        E2 --> F2[Generate x86-64 Code]

        F1 --> G[Link with Memory Manager]
        F2 --> G

        G --> H[Store in Code Cache]
        H --> I[Execute Native Code]
    end

    subgraph "ARM64 Specifics"
        J1[30 General Purpose Registers]
        J2[NEON SIMD Support]
        J3[Conditional Select, csel]
        J4[Fixed 32-bit Instructions]
    end

    subgraph "AMD64 Specifics"
        K1[16 General Purpose Registers]
        K2[SSE/AVX SIMD Support]
        K3[Variable Length Instructions]
        K4[Complex Addressing Modes]
    end

    C1 -.-> J1 & J2 & J3 & J4
    C2 -.-> K1 & K2 & K3 & K4

    style C1 fill:#e1f5ff
    style C2 fill:#e1f5ff
    style H fill:#e1ffe1

Calls between compiled methods. Since v2026.9.1 a compiled caller enters a compiled callee's native entry directly — arguments in the caller's outgoing area, the frame record built in the callee's own frame, the result in XMM0 (D0 on ARM64) — with an inline cache at virtual and func-ref sites. The C++ callback bridge remains the slow path (a callee not yet compiled, a full call stack, a Nil receiver, a megamorphic site) and the only path for allocation, traps and conversions. A bound call costs 5.5 ns rather than 26.5 ns as a result. The call path is drawn in core/vm/arch/jit/README.md; the design and its measurements are in docs/JIT_CALLING_CONVENTION_DESIGN.md.

JIT Frame Structure

graph TD
    subgraph "JIT Function Frame"
        A[Prolog: Save registers]
        B[Link with Memory Manager]
        C[Store Local Variables]
        D[Execute Generated Code]
        E[Error Handling Routines]
        F[Epilog: Restore registers]

        A --> B --> C --> D --> E --> F
    end

    subgraph "Memory Manager Integration"
        G[GC Safepoints]
        H[Object Allocation]
        I[Write Barriers]
    end

    B -.-> G & H & I
    D -.-> G & H & I

    style B fill:#ffe1e1
    style D fill:#e1ffe1

Register Allocation Strategy

The registers each backend's allocator hands out; neither spills, so an expression that needs more falls back to the interpreter whole.

PlatformGeneral PurposeFloat/VectorReserved
ARM64x0-x7, x12-x15 (12 regs)d0-d15 (16 regs)x9-x11 (scratch), x19 (safepoint flag's address), x29 (FP), x30 (LR)
AMD64rax, rbx, rcx, rdx, r8-r11 (8 regs; Windows adds rsi, rdi)xmm10-xmm15 (6 regs)r12 (safepoint flag's address), r13-r15 (pinned loop locals), rsp (SP), rbp (BP)

Source Files:


5. Library Ecosystem

56 library sources providing comprehensive functionality.

mindmap
    root((Objeck Libraries
    56))
        Core Runtime
            lang: runtime, errors, strings
            gen_collect: Vector, Map, Hash, List
            concurrent: locks, Condition, Semaphore, Barrier
            math: Rational, Complex
            regex · misc · diags
        Machine Learning
            ml_core · ml_data
            ml_linear · ml_tree · ml_bayes
            ml_neighbors · ml_cluster · ml_svm · ml_outlier
            nlp: tokenizers (WordPiece, BPE), TF-IDF, sentiment
        AI
            onnx: in-process inference, face detection and recognition
            nlp_onnx: BERT embeddings, Hugging Face text classifiers
            ai_hub: Hugging Face Hub downloads into a verified cache
            inference: model server over HTTP
            models: shared Tensor and Engine
            openai · gemini · ollama
            ai_search · ai_game · ai_optimize · ai_rl
            opencv: computer vision
        Web & Networking
            net · net_common · net_secure
            net_h2 · net_quic
            net_server · web_server
            json_rpc · rss
        Data Processing
            json · json_stream · xml
            csv · query · odbc
            cipher: hashing and encryption via mbedTLS
        Media & Gaming
            sdl2 · sdl_game · sdl_gl
            lame: MP3 encoding

Library Dependencies

graph TB
    subgraph "Core Dependencies"
        A[lang.obl] --> B[All Libraries]
    end

    subgraph "AI/ML Stack"
        C[onnx.obl] --> D[ONNX Runtime]
        E[opencv.obl] --> F[OpenCV 4.12]
        G[ml_*.obl] --> H[matrix module · Eigen3]
        I[ollama.obl] --> J[HTTP Client]
        K[openai.obl] --> J
        L[gemini.obl] --> J
        HUB[ai_hub.obl] --> J
        NO[nlp_onnx.obl] --> C
    end

    subgraph "Web Stack"
        M[web_server.obl] --> N[net_server.obl]
        O[json_rpc.obl] --> P[json.obl]
        N --> Q[net_secure.obl]
        Q --> R[mbedTLS 3.6]
    end

    subgraph "Data Stack"
        S[json.obl]
        T[xml.obl]
        U[csv.obl]
        V[query.obl] --> S & T & U
    end

    subgraph "Media Stack"
        W[sdl2.obl] --> X[SDL2]
        Y[sdl_game.obl] --> W
        Z[lame.obl] --> AA[LAME 3.100]
    end

    style D fill:#e1f5ff
    style F fill:#e1f5ff
    style R fill:#ffe1e1
    style X fill:#fff4e1

External Dependencies

Versions differ by platform (vcpkg, vendored DLLs, apt, Homebrew, static builds); the per-platform table, and how to check each one, is in third_party_dependencies.md.

CategoryLibraryPurpose
CryptombedTLS 3.6 LTSTLS, hashing, encryption
HTTPnghttp2; ngtcp2 + nghttp3 + AWS-LC (POSIX)HTTP/2, HTTP/3
ML/AIONNX Runtime (CPU, DirectML, QNN, CUDA, CoreML)Neural network inference
VisionOpenCV 4.12Computer vision, image processing
MathEigen 3.4 (vendored)Linear algebra, matrices
GraphicsSDL2, SDL2_image, SDL2_mixer, SDL2_ttfCross-platform graphics/input/audio
AudioLAME 3.100MP3 encoding
DatabaseODBCSystem driver manager

Face Recognition Example

use API.OpenCV, API.Onnx;

class FaceDemo {
  function : Main(args : String[]) ~ Nil {
    session := FaceSession->New("det_10g.onnx", "w600k_r50.onnx");

    img1 := Image->Load("person_a.jpg")->Convert(Image->Format->JPEG);
    img2 := Image->Load("person_b.jpg")->Convert(Image->Format->JPEG);

    r1 := session->Recognize(img1, 0.5);
    r2 := session->Recognize(img2, 0.5);

    faces1 := r1->GetResults();
    faces2 := r2->GetResults();
    sim := FaceSession->Compare(faces1[0]->GetEmbedding(), faces2[0]->GetEmbedding());
    "Similarity: {$sim}"->PrintLine();   # >0.35 = same person
    session->Close();
  }
}

Uses SCRFD 10G-KPS detector + ArcFace R50 recognizer from InsightFace buffalo_l. Runs on DirectML (Windows), CPU/CUDA (Linux), CoreML (macOS).

Source Files:


6. Development Tools

Interactive development environment with REPL, debugger, and LSP support.

graph TB
    subgraph "REPL (Interactive Shell)"
        A[User Input] --> B[Command Parser]
        B --> C{Command Type?}

        C -->|Code| D[Scanner]
        C -->|File| E[Load File]
        C -->|Command| F[Execute Command]

        D --> G[Parser]
        E --> G
        G --> H[Contextual Analyzer]
        H --> I[Interpreter]
        I --> J[Display Result]

        F --> K[REPL Commands]
        K --> J
    end

    subgraph "Integrated Editor"
        L[Syntax Highlighting]
        M[Undo / Redo]
        N[F5 Run Pane]
    end

    A -.-> L & M & N

    style I fill:#e1ffe1
    style J fill:#fff4e1

Debugger Architecture

sequenceDiagram
    participant User
    participant Debugger
    participant VM
    participant Memory

    User->>Debugger: Set breakpoint at line 42
    Debugger->>VM: Install breakpoint

    User->>Debugger: Run program
    Debugger->>VM: Start execution

    VM->>VM: Execute bytecode
    VM->>Debugger: Hit breakpoint at line 42

    User->>Debugger: Inspect variable 'x'
    Debugger->>VM: Get stack frame
    VM->>Memory: Lookup variable 'x'
    Memory->>VM: Return value
    VM->>Debugger: Variable value
    Debugger->>User: Display: x = 42

    User->>Debugger: Step over
    Debugger->>VM: Execute next instruction
    VM->>Debugger: Paused at line 43

    User->>Debugger: Continue
    Debugger->>VM: Resume execution
    VM->>VM: Run to completion

LSP Server Integration

graph LR
    subgraph "Editors (tools/lsp/clients)"
        A1[VS Code]
        A2[Neovim]
        A3[Vim]
        A4[Emacs]
        A5[Helix]
        A6[Sublime Text]
    end

    subgraph "LSP Server (tools/lsp, LSP 3.17)"
        B[Language Server Protocol]
        C[Analysis, reused until the text changes]
        D[Symbol Table]
        E[Pull Diagnostics]
    end

    subgraph "Compiler Services (libobjk_diags)"
        F[Scanner]
        G[Parser]
        H[Contextual Analyzer]
    end

    A1 & A2 & A3 & A4 & A5 & A6 --> B
    B --> C --> F & G & H
    C --> D
    C --> E

    style B fill:#e1f5ff
    style D fill:#fff4e1

An analyzed program is keyed by the exact texts of its files and its library list. Diagnostics analyze each edit; hover and completion work from the last program that analyzed cleanly. Since v2026.10.2 an edit retires programs rather than freeing them, and a request whose texts and libraries match a retired program reuses it, so each edit is analyzed once instead of twice (about 2× faster per edit). Unclaimed programs are freed at the next edit, and OBJECK_LSP_NO_CACHE=1 turns all reuse off.

REPL Features

Full-Screen Editor (/e)

A terminal editor over the REPL buffer, header-only (core/repl/{term,screen,keymap,tui_editor,highlight,child_run}.h) so it adds no build-system changes on any platform. Raw-mode input and damage-diffed rendering work identically on Windows (VT console) and POSIX (termios), with display-width handling for CJK/wide characters.

Software Updater (obu)

core/utils/updater/obu.cpp — in-place version upgrades without reinstalling. obu check compares the installed version against GitHub releases; obu update/rollback download the platform archive, verify a SHA-256 digest against the release's SHA256SUMS before touching disk, then perform an all-or-nothing staged swap with an obc -v post-install check and automatic rollback. No shell is used anywhere — curl/tar/obc run via argument vectors (fork/execvp on POSIX, CreateProcess on Windows), including the release-JSON fetch behind obu check. The flow is covered by an offline CI test (test_update.py) that runs on both Linux and Windows: check is exercised on every platform against fake releases, and the update/rollback swap plus a command-injection PoC wherever the swap is implemented. The Windows swap needs no re-exec dance: Windows forbids deleting a running image but allows renaming one, so the running obu.exe moves into .previous and keeps executing. Since v2026.10.0 a release's SHA256SUMS is itself signed with minisign, and obu refuses a manifest whose signature does not verify against the release key compiled into it, so a substituted archive cannot pass with a substituted manifest. Still outstanding (docs/UPDATER_DESIGN.md): reconciling an MSI-managed install's registry entries, and elevation against a real Program Files tree.

Debugger Features

Source Files:


7. CI/CD Pipeline

Automated multi-platform testing and build system.

graph TB
    subgraph "GitHub Actions Trigger"
        A[Push to master] --> B[GitHub Actions]
        C[Pull Request] --> B
    end

    subgraph "Multi-Platform Matrix Build"
        B --> D{Platform Matrix}

        D -->|Linux x64| E1[Ubuntu Latest]
        D -->|Linux ARM64| E4[ubuntu-24.04-arm]
        D -->|macOS ARM64| E2[macos-15]
        D -->|Windows x64| E3[windows-2025-vs2026]
        D -->|Windows ARM64| E5[windows-11-arm]
    end

    subgraph "Build Process"
        E1 & E2 & E3 & E4 & E5 --> F[Install Dependencies]

        F --> G{Cache Hit?}
        G -->|Yes| H[Restore Cache]
        G -->|No| I[Download & Install]

        H & I --> J[Build Toolchain]
        J --> K[Compile Tests]
    end

    subgraph "Testing"
        K --> L[Run Deployment Tests]
        L --> M[Run Regression Suite]
        M --> N{All Tests Pass?}

        N -->|Yes| O[Upload Artifacts]
        N -->|No| P[Report Failure]
    end

    subgraph "Artifacts & Reporting"
        O --> Q[Build Artifacts<br/>7-day retention]
        O --> R[Generate API Docs]
        P --> S[CI Status Badge]
    end

    style N fill:#fff4e1
    style O fill:#e1ffe1
    style P fill:#ffe1e1

Platform Matrix

PlatformRunnerArchitectureCompilerTests
Linuxubuntu-24.04x64GCC529 regression
Linuxubuntu-24.04-armARM64GCC529 regression
macOSmacos-15ARM64 (Apple silicon)Clang529 regression
Windowswindows-2025-vs2026x64MSVC (v145)529 regression
Windowswindows-11-armARM64MSVC (v143)529 regression

Every leg builds natively and runs what it builds: the regression suite twice, at the default JIT threshold and with every method compiled on its first call (OBJECK_JIT_THRESHOLD=1), then the debugger, DAP and VM-flag tests. The Windows ARM64 leg used to be cross-compiled on an x64 host, which cannot execute what it builds, so its tests were skipped. That blind spot is how an ARM64-only miscompile reached six releases before anyone noticed. The release build still cross-compiles Windows ARM64 on the x64 image; CI's native leg is what tests it.

Caching Strategy

graph LR
    subgraph "Linux Cache"
        A1[APT Packages] --> B1[500MB]
        A2[ccache Objects] --> B2[500MB]
    end

    subgraph "macOS Cache"
        C1[Homebrew Bottles] --> D1[1GB]
        C2[ccache Objects] --> D2[500MB]
    end

    subgraph "Cache Management"
        E[Cache Key: workflow + commit]
        F[Restore Key: workflow]
        G["GitHub evicts at the<br/>10 GB repo ceiling"]
        H["codeql.yml prunes<br/>superseded entries"]
    end

    B1 & B2 & D1 & D2 --> E
    E --> F --> G
    H --> G

    style E fill:#e1ffe1
    style H fill:#e1ffe1

The 10 GB ceiling is the constraint that matters. Actions caches are capped per repository, and past the cap GitHub evicts the oldest entries -- which is how a release build lost its dependency caches and gained about forty minutes. The families that grow without bound are the ones whose key embeds something that changes: CodeQL writes a ~460 MB TRAP cache per analysed commit, and the dependency caches re-key whenever their input hash moves. A superseded entry can never be hit again, because the key names it.

A prune-caches job in codeql.yml therefore keeps only the newest few of each family, taking <prefix>:<keep> pairs -- currently codeql-trap:3 macOS-brew-downloads:2 Linux-x64-apt:2 Linux-arm64-apt:2. It keeps several rather than one because these actions restore by key prefix, so a recent entry still warm-starts a new run; pruning to exactly one would trade the ceiling problem for a slower build.

Regression Test Suite

782 tests under programs/regression/, grouped by the prefix on each filename. The counts change with almost every pull request, so they are not repeated here: programs/regression/TESTS.md (generated) has the current number per category.

mindmap
    root((Regression Tests
    782 total))
        Language core
            core_*
                Arithmetic, arrays, classes, generics, strings
        Rejected programs
            bad_*
                Errors the compiler must refuse
        JIT
            jit_*
                Native compilation, float paths, GC interaction
        Machine learning
            ml_*
                System.ML models and inference
        Fixed defects
            fix_*
                Regressions pinned to the bug that caused them
        Collections
            collect_*
                Vector, Hash, Map, Set, Stack, Queue
        Math and strings
            math_* string_* func_*
                Numerics, formatting, higher-order functions
        ARM64 specific
            arm64_*
                Bitwise, char arrays, 64-bit immediates, multiply
        Networking and web
            http_* mcp_* xml_* json_* api_*
                Clients, servers, serialisation
        Rosetta Code tasks
            rosetta_*
                Programs from Rosetta Code, output checked
        Remainder
            closure_* try_* vm_* gc_* opt_* dap_* debugger_* onnx_* and others
                Closures, error handling, the VM and its collector, AI bindings, debugger

8. Memory Management

Multithreaded Collection (Cooperative Stop-the-World)

With multiple threads running, a collection cannot begin until every other thread has reached a known-safe point. Objeck uses a cooperative stop-the-world scheme rather than OS thread suspension:

The collector remains generational and non-OS-suspending; correctness comes from complete root coverage at the safepoint, not from freezing threads mid-instruction.

Old-Generation Membership

The collector must answer "is this address a live old-generation object?" for words taken from conservative scans — operand stacks and JIT temporaries — which may not be pointers at all. That rules out reading a flag from the candidate: the answer has to be decidable without dereferencing it. An object's header does carry GC_OLD_BIT, but reading it means trusting the address first.

So the old generation keeps a set of known-good addresses, PtrSet (core/vm/arch/ptr_set.h). It is open-addressed: keys live directly in one power-of-two array, probed linearly, with nullptr for an empty slot and an all-ones sentinel for a tombstone.

graph LR
    subgraph "PtrSet -- open addressing"
        A[Object address] --> B["hash = address >> 3<br/>no mixing"]
        B --> C[index = hash & mask]
        C --> D[Slot array]
        D --> E{Occupied?}

        E -->|"empty"| F[Not a member]
        E -->|"this key"| G[Member]
        E -->|"other key<br/>or tombstone"| H[probe i+1]
        H --> D
    end

    style B fill:#e1f5ff
    style G fill:#e1ffe1

Two properties are deliberate and easy to undo by accident:

This replaced a std::unordered_set in v2026.9.7. That container is node-based, so every insert was a separate heap allocation, and the old generation gains one entry per promoted object — a minor collection paid that allocation once per survivor, on top of the object's own. On Windows it cost 239.5 ns per insert against the allocation's 38.5 ns. Promotion cost tracks promoted bytes rather than collection count, so it dominated: binarytrees(17) went from 2.392 s to 1.789 s (−25.2%) with peak RSS 194.6 MB → 179.1 MB, closing a 3.5× gap against Linux on the same hardware. ptr_set_test.cpp pins the behaviour the collector relies on, including that count() never dereferences its argument.

GC Algorithm Flow

sequenceDiagram
    participant App as Application
    participant MM as Memory Manager
    participant YG as Young Gen
    participant OG as Old Gen

    App->>MM: Allocate object
    MM->>YG: Allocate in young gen
    YG->>MM: Return pointer
    MM->>App: Object ready

    Note over YG: Young gen fills up

    YG->>MM: Trigger GC (nursery full, or old generation past its threshold)
    MM->>App: Stop the World (every other thread parks)

    MM->>MM: Mark phase (from roots, and the dirty list on a minor GC)
    MM->>YG: Mark reachable objects
    MM->>OG: Mark reachable objects (major GC)

    MM->>MM: Promote survivors
    YG->>OG: Copy each marked young object, leave a forwarding address
    MM->>MM: Fix up roots and old objects to the new addresses
    MM->>YG: Reset the nursery (nothing in it is swept)

    MM->>OG: Major GC only: sweep unmarked old objects into the free lists

    MM->>App: Resume execution

Memory Manager Improvements

mindmap
    root((Memory Manager))
        Constant-time lookups
            Open-addressed old-gen set
            No per-entry allocation
            No linear scans
        Generational collection
            Young gen frequent and fast
            Old gen infrequent and thorough
            Survivors promoted with forwarding pointers
        Old-gen sweeping in place
            No old-object copying
            Better cache locality

Where Allocations Go

RegionWhat lands thereCollected by
Nursery (young)Objects, bump-allocated in one contiguous region: 128 MB reserved (YOUNG_REGION_SIZE), its usable limit set by --nursery / OBJECK_NURSERY; on Windows only the limit is committedMinor GC: survivors promoted to the old generation, references fixed up
Old generationPromoted objects, arrays, objects larger than the nursery limit, and every allocation a native library makes; each tracked in PtrSetMajor GC: mark and sweep in place
Operand and call stacksLocals, frames, temporariesScanned as roots, never collected

When Collections Run

Key Characteristics:

Source Files:


9. Platform Abstraction

Cross-platform system call abstraction for POSIX and Win32.

graph TB
    subgraph "Application Layer"
        A[Objeck Program]
    end

    subgraph "VM & Libraries"
        B[Runtime Interpreter]
        C[JIT Compiled Code]
        D[Standard Libraries]
    end

    subgraph "Host API Layer"
        E[Host APIs<br/>Platform Abstraction]
    end

    subgraph "Platform Implementations"
        F1[POSIX APIs]
        F2[Win32 APIs]

        G1[File I/O]
        G2[Networking]
        G3[Threading]
        G4[Dynamic Loading]
        G5[System Calls]

        F1 --> G1 & G2 & G3 & G4 & G5
        F2 --> G1 & G2 & G3 & G4 & G5
    end

    subgraph "Operating Systems"
        H1[Linux]
        H2[macOS]
        H3[Windows]
    end

    A --> B & C & D
    B & C & D --> E
    E --> F1 & F2
    G1 & G2 & G3 & G4 & G5 --> H1 & H2 & H3

    style E fill:#fff4e1
    style F1 fill:#e1f5ff
    style F2 fill:#e1f5ff

Platform-Specific Implementations

graph LR
    subgraph "File I/O"
        A1[open/read/write] -->|POSIX| B1[open/read/write syscalls]
        A1 -->|Win32| B2[CreateFile/ReadFile/WriteFile]
    end

    subgraph "Threading"
        A2[Thread Create] -->|POSIX| C1[pthread_create]
        A2 -->|Win32| C2[CreateThread]

        A3[Mutex] -->|POSIX| C3[pthread_mutex_*]
        A3 -->|Win32| C4[CreateMutex/WaitForSingleObject]
    end

    subgraph "Networking"
        A4[Socket] -->|POSIX| D1[socket/bind/listen]
        A4 -->|Win32| D2[WSASocket/bind/listen]
        A6[HTTP/2] --> D3[nghttp2 + mbedTLS/OpenSSL]
        A7[HTTP/3] -->|POSIX| D4[ngtcp2 + nghttp3 + AWS-LC, static]
        A7 -->|Win32| D5[WinHTTP over MsQuic]
    end

    subgraph "Dynamic Loading"
        A5[Load Library] -->|POSIX| E1[dlopen/dlsym]
        A5 -->|Win32| E2[LoadLibrary/GetProcAddress]
    end

    style A1 fill:#fff4e1
    style A2 fill:#fff4e1
    style A4 fill:#fff4e1
    style A6 fill:#fff4e1
    style A7 fill:#fff4e1
    style A5 fill:#fff4e1

HTTP Protocol Stack

Three protocols, and they do not share a serialization path — which is the single most important thing to know before touching this code.

ProtocolWhere the request is builtEngine
HTTP/1.1, HTTPSObjeck (net.obs, net_secure.obs) — assembled as textsockets + mbedTLS/OpenSSL
HTTP/2Native (common.cpp)nghttp2, all platforms
HTTP/3Native (common.cpp)ngtcp2 + nghttp3 + AWS-LC on POSIX, linked statically (tools/deps/build_quic_deps.sh); WinHTTP over MsQuic on Windows 11 / Server 2022+

Consequences that have each produced a real bug:

Threading Model

sequenceDiagram
    participant Main as Main Thread
    participant VM as VM Thread Manager
    participant T1 as Worker Thread 1
    participant T2 as Worker Thread 2
    participant GC as GC Thread

    Main->>VM: Create thread
    VM->>T1: Start execution

    Main->>VM: Create thread
    VM->>T2: Start execution

    T1->>T1: Execute bytecode
    T2->>T2: Execute bytecode

    Note over T1,T2: GC triggered

    VM->>GC: Start GC
    GC->>T1: Suspend (safepoint)
    GC->>T2: Suspend (safepoint)

    GC->>GC: Mark & Sweep

    GC->>T1: Resume
    GC->>T2: Resume

    T1->>VM: Thread complete
    T2->>VM: Thread complete

Platform Detection

PlatformMacroCompilerABI
Linux x64__linux__ && __x86_64__GCC/ClangSystem V AMD64
Linux ARM64__linux__ && __aarch64__GCCARM64 AAPCS
macOS ARM64__APPLE__ && __aarch64__ClangARM64 AAPCS
Windows x64_WIN32 && _WIN64MSVCMicrosoft x64
Windows ARM64_WIN32 && _M_ARM64MSVCMicrosoft ARM64

Source Files:


10. Runtime Errors

Errors are objects. System.Error and its subclasses are raised with Runtime->Raise and caught by type with try { } on(e : T) { } finally { }; the runtime's own faults are raised the same way, as NilError, IndexError, DivideByZeroError, CastError and RuntimeError. ?-> still recovers from any error in a call chain, yielding Nil. An error nothing handles ends the program.

try {
  port := ParsePort(text, line);   # may Runtime->Raise(ConfigError->New(...))
}
on(e : ConfigError) {               # clauses are tested in order
  "line {$(e->GetLine())}: {$(e->GetMessage())}"->PrintLine();
}
on(e : Error) {                     # a catch-all goes last
  e->GetMessage()->PrintLine();
}
finally {                           # runs on every way out, return/break/continue included
  "checked"->PrintLine();
};
flowchart TB
    A["Error raised<br/>Runtime->Raise, or a runtime fault:<br/>Nil, index, divide by zero, cast"] --> B{"Handler on this<br/>thread's handler stack?"}
    B -->|yes| C["TryErrorRecovery: pop frames back to<br/>the handler's, restore the operand stack"]
    C --> D{"Which construct?"}
    D -->|"?-> / Try()"| E["The guarded chain yields Nil<br/>(?? may supply a default)"]
    D -->|"try / on"| F["First clause whose type matches runs;<br/>no match: the error goes on after finally"]
    F --> H["finally runs"]
    B -->|no| G["Message on stderr, the call stack,<br/>exit with status 1 (obd halts instead)"]

    style E fill:#e1ffe1
    style H fill:#e1ffe1
    style G fill:#ffe1e1

Source Files:


Summary

This architecture demonstrates a modern, multi-platform language implementation with:

✅ Multi-tiered Execution: Interpreter → call counting → JIT compilation ✅ Platform-Specific JIT: Optimized ARM64 and x64 code generation ✅ Efficient Memory Management: O(1) lookups, generational GC ✅ Structured Errors: try/on/finally over typed Error objects, plus ?-> recovery ✅ Rich Library Ecosystem: 46 libraries including AI/ML, web servers ✅ Modern Development Tools: REPL, debugger, LSP support ✅ Automated CI/CD: Multi-platform testing with caching ✅ Cross-Platform: Linux x64/ARM64, macOS ARM64 (Apple silicon), Windows x64/ARM64 ✅ Full Unicode: Emoji and supplementary plane characters on all platforms including Windows console

For More Information:

Contributing: See CONTRIBUTING.md for guidelines on improving this architecture.


Last updated: October 2026 | Version: 2026.10.2