Skip to content

Latest commit

 

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

unGodly

A self-hosting JavaScript compiler and bare-metal OS, written entirely in JavaScript. Compiles JS to native x86-64 and AArch64 machine code — no interpreter, no VM, no libc. The compiler compiles itself. The OS boots on real hardware.

import { compileToLinuxExe } from './src/compiler/pipeline.js';
import { writeFileSync } from 'fs';

writeFileSync('hello', compileToLinuxExe(`
  function fib(n) {
    if (n < 2) return n;
    return fib(n - 1) + fib(n - 2);
  }
  console.log(fib(30));
`));
// chmod +x hello && ./hello  → 832040

55,700 lines of JS. 1,214 tests. The natively-compiled compiler produces byte-for-byte identical ELF output to the Bun-hosted compiler. The kernel boots on QEMU and VMware via GRUB, with a GUI window manager, ext2 filesystem, full TCP/IP networking, TLS 1.3 encryption, a Bourne shell, fork/exec, and a built-in Claude AI assistant with 10 agentic tools.

Quick start

bun install
bun test                    # 1,214 tests, both architectures

Compile and run JS

# x86-64 (runs natively on Linux x86-64)
bun -e '
  import { compileToLinuxExe } from "./src/compiler/pipeline.js";
  import { writeFileSync } from "fs";
  writeFileSync("out", compileToLinuxExe("console.log(6 * 7);"));
'
chmod +x out && ./out   # 42

# AArch64 (requires qemu-aarch64-static on x86 hosts)
bun -e '
  import { compileToLinuxExeAArch64 } from "./src/compiler/pipeline.js";
  import { writeFileSync } from "fs";
  writeFileSync("out", compileToLinuxExeAArch64("console.log(6 * 7);"));
'
chmod +x out && qemu-aarch64-static ./out   # 42

Boot the OS

bun run tools/build-kernel.js                    # build kernel image
bash tools/make-iso.sh                           # create GRUB bootable ISO

# QEMU (serial I/O on stdio)
qemu-system-x86_64 -kernel build/ungodly.bin \
  -vga std -serial stdio -m 256 -no-reboot

# With disk + networking
qemu-system-x86_64 -cdrom build/ungodly.iso \
  -vga std -serial stdio -m 256 -no-reboot \
  -drive file=disk.img,format=raw,if=none,id=disk0 \
  -device ahci,id=ahci -device ide-hd,drive=disk0,bus=ahci.0 \
  -netdev user,id=net0 -device e1000,netdev=net0

What works

Feature Example Status
Integer + float arithmetic (10.5 + 5) * 2 - 7 done
Variables, scoping let x = 42; const y = x + 1; done
All operators a > b ? a : b, x ?? y, a?.b done
Bitwise ops 0xFF & 0x0F, x << 3, n >>> 0 done
All loop types while, for, for-in, for-of, do-while done
Functions + recursion function fib(n) { ... } done
Arrow functions const f = (x) => x * 2; done
Closures + mutable captures let x = 0; function inc() { x++; } done
Objects + prototypes let o = { x: 1 }; o.x = 2; done
Arrays + methods push, map, filter, reduce, sort, find, ... done
Strings + methods concat, slice, indexOf, charCodeAt, template literals done
Classes + inheritance class Foo extends Bar { ... } done
new + constructors new Point(1, 2) done
this binding methods, constructors, arrow this capture done
Destructuring let { a, b } = obj; let [x, y] = arr; done
Switch + break switch (x) { case 1: ... } done
Throw / try-catch throw new Error("msg"), try { ... } catch (e) { ... } done
typeof, instanceof typeof x === "string" done
console.log multi-arg, all value types done
Garbage collection Cheney semi-space, conservative stack scanning done
String interning "hello" === "hello" → pointer compare done
NaN-boxing int32 fast path, f64 fallback, pointer-biased done
AArch64 cross-compile same JS → ARM64 binary done
Optimization passes DCE, constant folding, GVN, type specialization done
Self-hosting compiler compiles itself, byte-for-byte identical done
Promises + microtasks new Promise(...), .then(), microtask queue done

What doesn't work yet

  • No async/await, generators
  • No regular expressions
  • No modules (import/export) — worked around by file concatenation
  • No Proxy, Reflect, Symbol, WeakMap/WeakSet
  • No getters/setters
  • No private class fields

Architecture

JS source
  │
  ▼
┌──────────────────┐
│  Parser           │  Recursive-descent, ES2023 subset, ESTree AST
│  parser.js        │  1,510 lines
└────────┬─────────┘
         │ AST
         ▼
┌──────────────────┐
│  Lowering         │  AST → SSA IR with basic blocks, phi nodes
│  lower.js         │  NaN-boxing, closure capture analysis,
│                   │  mutable capture box cells, 30+ builtin interceptions
│                   │  3,831 lines
└────────┬─────────┘
         │ IRFunction (SSA, 196 opcodes)
         ▼
┌──────────────────┐
│  Optimization     │  constFold → DCE → typeSpec → constFold → GVN → DCE
│  optimize/        │  914 lines
└────────┬─────────┘
         │ optimized IR
         ▼
┌──────────────────┐
│  Register Alloc   │  Linear scan on SSA live intervals
│  regalloc.js      │  Dataflow liveness, call-aware spilling
│                   │  397 lines
└────────┬─────────┘
         │ allocation map
         ▼
┌──────────────────┐
│  Instruction Sel  │  IR → native machine code + runtime helpers
│  x86_64/isel.js   │  6,296 lines — System V ABI, 30+ __rt_* helpers
│  aarch64/isel.js  │  4,746 lines — AAPCS64, full feature parity
└────────┬─────────┘
         │ machine code bytes
         ▼
┌──────────────────┐
│  ELF Linker       │  ELF64 builder, single LOAD segment
│  elf.js           │  x86_64 + aarch64 + flat binary output
└────────┬─────────┘
         │
         ▼
    Linux ELF binary    — or —    Bare-metal kernel image

NaN-boxing scheme (pointer-biased, JSC-style)

Pointers:    0x0000_PPPP_PPPP_PPPP   (raw, zero-cost dereference)
Int32:       0xFFFF_0000_IIII_IIII   (tagged, extract with mask)
Double:      raw IEEE754 + 0x0002_0000_0000_0000 (bias)
Boolean:     false = 0x06, true = 0x07
Null:        0x02
Undefined:   0x0A

Object layout

Flat inline storage, no hash table. Up to 128 properties per object.

[GC header: u64] [num_props: u64] [key0: u64] [val0: u64] [key1: u64] [val1: u64] ...

Property names are hashed at compile time (djb2 with high bit set). Lookup is linear scan.

GC

Cheney semi-space collector. Two 256MB semispaces allocated via mmap (14MB on bare metal). Bump allocation into the from-space; when full, copy reachable objects to the to-space and swap. Conservative stack scanning for roots. GC control block pinned in R13 (x86-64) / X25 (AArch64). Bump pointer in R15 / X28.

Header format: (totalSize << 17) | (numScannable << 5) | (tag << 1) | 1

Tags: CLOSURE=0, OBJECT=1, STRING=2, ARRAY=3, BOX=4.

Closure layout

[GC header: 8] [code_ptr: 8 bytes] [capture0: 8] [capture1: 8] ...

Closure pointer passed in pinned register (R14 on x86-64, X26 on AArch64). Captures accessed as [closure_reg + 8 * (1 + slot)].

Mutable captures use heap-allocated box cells (GC_TAG_BOX). A box is a 24-byte GC object: [GC header: 8] [value: 8] [pad: 8]. Both the outer scope and all capturing closures share the same box, reading and writing through it. The lowerer's pre-analysis pass (_preAnalyzeMutableCaptures) identifies which variables need boxing.

String layout

Two representations:

  • Data-segment strings: [length: u64] [bytes...] — embedded in code section, interned
  • Heap strings: [GC header: u64] [length: u64] [bytes...] — allocated by concat/slice

Project structure

src/
  compiler/
    parser/
      parser.js          Recursive-descent ES2023 subset parser (1,510 lines)
      token.js           Token types
    ir/
      opcodes.js         196 IR opcodes with metadata flags
      types.js           IR type system (i32, i64, f64, ptr, jsval, bool)
      builder.js         IRFunction, BasicBlock, instruction pool
    lowering/
      lower.js           AST → IR, scope analysis, closure capture (3,831 lines)
    codegen/
      regalloc.js        Linear scan register allocator (397 lines)
      i64.js             64-bit integer arithmetic (no BigInt)
      x86_64/
        regs.js          x86-64 register definitions, System V ABI
        assembler.js     x86-64 instruction encoder (904 lines)
        isel.js          IR → x86-64 instruction selection (6,296 lines)
      aarch64/
        regs.js          AArch64 register definitions, AAPCS64
        assembler.js     AArch64 instruction encoder (716 lines)
        isel.js          IR → AArch64 instruction selection (4,746 lines)
    optimize/
      dce.js             Dead code elimination
      constfold.js       Constant folding + branch simplification
      gvn.js             Global value numbering (CSE)
      typespec.js        Type specialization (int32 inference)
      utils.js           Shared optimization utilities
    linker/
      elf.js             ELF64 builder + flat binary output
    pipeline.js          Main entry: source → ELF (601 lines)
  kernel/                67 files, 33,200 lines
    main.js              Kernel entry, event loop, window manager (1,472 lines)
    drivers/
      serial.js          COM1 serial I/O
      vga.js             VGA text mode + framebuffer (800x600x32, PSF font)
      keyboard.js        PS/2 keyboard ISR
      mouse.js           PS/2 mouse driver
      pci.js             PCI bus enumeration
      pty.js             Pseudo-terminal (PTY) for terminal emulator
      e1000.js           Intel e1000 NIC driver
      ahci.js            AHCI SATA disk driver
      vmware.js          VMware Tools guest agent
      usb_scan.js        USB device enumeration
      xhci_core.js       xHCI host controller
      xhci_keyboard.js   USB keyboard via xHCI
    fs/
      ext2.js            ext2 filesystem (read/write, inode/block alloc, symlinks)
      ramdisk.js         Embedded ramdisk archive
      vfs.js             Virtual filesystem layer (10 FD types)
      pipe.js            Pipe IPC (fork/exec stdin piping)
    net/
      net.js             Ethernet/IPv4/ARP/ICMP stack
      tcp.js             TCP (3-way handshake, sliding window, retransmit)
      udp.js             UDP
      dhcp.js            DHCP client (auto-configure IP/gateway/DNS)
      dns.js             DNS resolver
      http.js            HTTP/1.1 client + HTTPS (TLS 1.3)
      claude_api.js      Claude API client (SSE streaming, agentic tool loop)
      oauth.js           OAuth 2.0 device code flow
    crypto/
      chacha.js          ChaCha20-Poly1305 AEAD + X25519 key exchange
      sha256.js          SHA-256 + HMAC-SHA256 + HKDF
      tls.js             TLS 1.3 handshake + record layer (ChaCha20-Poly1305-SHA256)
    lib/
      json.js            JSON parser (arena-based)
      sse.js             Server-Sent Events parser
      microtask.js       Microtask queue (Promise resolution)
      promise.js         Promise implementation
    tools/
      tools.js           10 Claude tools (read/write/list/edit/search/exec/glob/fetch/search_web)
    syscall/
      dispatch.js        Linux syscall trampoline (60+ syscalls)
      dir_ops.js         Directory syscalls (mkdir, rmdir, rename, getdents64)
      file_ops.js        File syscalls (open, read, write, stat, symlinks)
      socket_ops.js      Socket syscalls
    shell/
      eval.js            JIT eval (compile JS → native → execute at runtime)
      commands.js        Shell command dispatch (sub-dispatcher pattern)
      dispatch.js        Shell dispatch router
      claude_cmd.js      Claude AI shell integration (agentic loop)
      oauth_cmd.js       OAuth authentication commands
      eval_expr.js       Expression evaluator (arithmetic, precedence)
      exec.js            Process execution (fork, exec, wait)
      fs_cmds.js         Filesystem shell commands
      net_cmds.js        Network shell commands
      proc_cmds.js       Process shell commands
      misc_cmds.js       Miscellaneous commands
      vfs_cmds.js        VFS shell commands (fd, open, close, pipe)
      vga_helpers.js     VGA output helpers
      sh_lexer.js        Bourne shell lexer
      sh_parser.js       Bourne shell parser
      sh_exec.js         Bourne shell execution
      sh_runner.js       Bourne shell runner
      sh_shell.js        Bourne shell main loop
      sh_control.js      Bourne shell control flow (if/while/for)
    proc/
      pmm.js             Physical memory manager (bitmap allocator)
      vmm.js             Virtual memory (page tables, 4-level paging)
      process.js         Process table + context switching
      elf.js             ELF loader (static-PIE binaries, ring 3 exec)
  runtime/
    value.js             NaN-boxing encode/decode/type checks

tools/
  build-kernel.js        Boot image builder (Multiboot, ISRs, page tables, 4,439 lines)
  make-iso.sh            GRUB bootable ISO creator

tests/                   1,214 tests across 23 test files

Kernel features

The kernel boots via Multiboot1 (GRUB or QEMU -kernel), sets up 4-level paging, SSE, PIT timer at 100 Hz, PS/2 keyboard + mouse ISRs, and enters a GUI window manager with terminal emulator. The embedded compiler (19 files, ~18K lines) enables JIT evaluation of arbitrary JS at the shell prompt.

Window manager

Compositing window manager with mouse-driven drag/resize, terminal emulator with PTY backend, VGA framebuffer at 800x600x32.

Shell commands

55+ commands including:

Command Description
help List available commands
eval <expr> JIT-compile and execute JavaScript
run <path.js> JIT compile + execute JS file from ext2
exec <path> Execute ELF binary (fork + exec)
sh Enter Bourne shell mode
cls Clear screen
mem Show memory usage (GC heap, PMM)
ps List processes
kill <pid> Kill process
ls [path] List directory contents (ext2)
cat <path> Print file contents
echo text > file Write/append with redirection
cp <src> <dst> Copy file
mv <src> <dst> Rename/move file
mkdir <path> Create directory
rm <path> Remove file/directory
stat <path> Show file metadata
head <path> [n] Print first N lines
wc <path> Word/line/byte count
hexdump <path> Hex dump file contents
fd List open file descriptors
open/close/readfd/writefd/pipe Low-level FD operations
pci List PCI devices
disk Show disk info (AHCI)
net Show network configuration
ping <ip> Send ICMP echo request
dhcp Auto-configure network via DHCP
dns <hostname> Resolve hostname via DNS
http <url> HTTP GET request
tcp <host> <port> Open TCP connection
apikey <key> Set Claude API key
claude <msg> Send message to Claude (streaming)
claude Enter interactive Claude session
oauth github Authenticate via OAuth 2.0 device flow

Bourne shell

Built-in sh command launches a Bourne-compatible shell with:

  • Pipelines (cmd1 | cmd2 | cmd3)
  • Redirections (>, >>, <)
  • Control flow (if/then/elif/else/fi, while/do/done, for/in/do/done)
  • Variables and substitution ($VAR, $(command))
  • Quoting ("double", 'single', \escape)

Linux syscall compatibility

60+ Linux syscalls via SYSCALL instruction, enabling execution of unmodified static-PIE musl binaries in ring 3:

  • Process: fork(57), wait4(61), exit(60), exit_group(231), clone(56)
  • Files: open(2), openat(257), read(0), write(1), close(3), stat(4), fstat(5), lstat(6), lseek(8), readv(19), writev(20), pread64(17), getdents64(217), readlink(89), dup(32), dup2(33), dup3(292), fcntl(72), access(21), unlink(87), pipe2(293)
  • Directories: mkdir(83), rmdir(84), rename(82), mkdirat(258), unlinkat(263), renameat(264), chdir(80), getcwd(79)
  • Memory: mmap(9), mprotect(10), munmap(11), brk(12)
  • Sockets: socket(41), connect(42), accept(43), sendto(44), recvfrom(45), bind(49), listen(50), setsockopt(54), getsockopt(55), accept4(288)
  • Epoll: epoll_create1(291), epoll_ctl(233), epoll_wait(232)
  • Signals: rt_sigaction(13), rt_sigprocmask(14), sigaltstack(131)
  • Time: gettimeofday(96), clock_gettime(228), nanosleep(35)
  • Misc: ioctl(16), poll(7), futex(202), statx(332), getrandom(318)

QuickJS runs on unGodly (822KB static-pie musl binary — fibonacci, closures, classes, Array ops all pass).

Networking

Full TCP/IP stack implemented from scratch in JS, running on bare metal:

  • Link layer: Intel e1000 NIC driver (MMIO, descriptor rings, interrupt-driven)
  • Network layer: IPv4, ARP (cache + request/reply), ICMP (ping)
  • Transport layer: UDP, TCP (16 connections, 3-way handshake, sliding window, retransmit, FIN teardown)
  • Application layer: DHCP client, DNS resolver, HTTP/1.1 client, HTTPS (TLS 1.3), fetch_url (redirect following), web_search (DuckDuckGo)

Cryptography

All crypto implemented from scratch in JS with integer-only arithmetic (no floats, no BigInt):

  • ChaCha20: 256-bit stream cipher (quarter-round, 20 rounds)
  • Poly1305: MAC authenticator (modular arithmetic mod 2^130 - 5)
  • ChaCha20-Poly1305: AEAD construction (RFC 8439)
  • X25519: Elliptic-curve Diffie-Hellman key exchange
  • SHA-256: Hash function + HMAC-SHA256 + HKDF-SHA256
  • TLS 1.3: Full handshake (ClientHello → ServerHello → encrypted application data)

Claude AI integration

The kernel includes a Claude API client that communicates over TLS 1.3. It supports:

  • Streaming responses via Server-Sent Events (non-blocking, GUI stays responsive)
  • Agentic tool loop with 10 tools: read_file, write_file, list_files, edit_file, search_files, execute_command, glob, fetch_url, web_search
  • fetch_url: HTTP/HTTPS GET with redirect following (301/302/303/307/308)
  • web_search: DuckDuckGo search with HTML-to-text stripping
  • Conversation persistence across messages
  • OAuth 2.0 device code flow for authentication
  • API key injection via .env file at build time

API

compileToLinuxExe(source, options?)

Compiles JS source to a runnable x86-64 Linux ELF binary. Returns Uint8Array.

compileToLinuxExeAArch64(source, options?)

Compiles JS source to a runnable AArch64 Linux ELF binary. Returns Uint8Array.

compileToMachineCode(source, options?)

Compiles JS source to raw machine code bytes. Options:

  • arch: 'x86_64' (default) or 'aarch64'
  • name: function name (default '<main>')
  • mode: 'program' (default) or 'function'

compileToIR(source, options?)

Compiles JS source to SSA IR. Returns an IRFunction object.

compileToFlatBinary(source, options?)

Compiles to a raw flat binary (no ELF headers). Used for bare-metal targets.

compileToBareMetal(source, options?)

Compiles JS source with bare-metal intrinsics enabled. Sets up static GC heap, disables syscalls, enables __outb/__inb/__peek/__poke/__hlt/__cli/__sti intrinsics.

Milestones

Compiler

  • M1 — Minimal compiler (parser, IR, x86-64 codegen, ELF linker)
  • M2 — JS semantics (closures, objects, strings, NaN-boxing)
  • M3 — Optimization (DCE, constant folding, GVN, type specialization)
  • M4 — AArch64 backend (full feature parity)
  • M5 — Runtime built-ins (string/array methods, error handling)
  • M6 — Classes, prototypes, for-of, Map/Set polyfills
  • M7 — Self-hosting (true bootstrap — byte-for-byte identical ELF)

OS

  • M1 — Boot on QEMU, VGA framebuffer, serial I/O, PS/2 keyboard
  • M2 — Interactive shell, JIT eval, expression evaluator
  • M3 — Memory manager (PMM/VMM), process model, context-switch scheduler, ELF loader
  • M4 — Storage (PCI enumeration, AHCI SATA driver, ext2 filesystem, ramdisk)
  • M5 — Networking (e1000 NIC, IPv4/ARP/ICMP, UDP, DHCP, DNS, TCP, HTTP)
  • M6 — Linux syscall compat (ring 3 exec, mmap/brk/read/write/exit, static-PIE binaries)
  • M7 — USB (xHCI host controller, USB keyboard)
  • M8 — HTTP client + JSON parser
  • M9 — Cryptography (ChaCha20-Poly1305, X25519, SHA-256, HMAC, HKDF)
  • M10 — TLS 1.3 (full handshake, encrypted record layer)
  • M11 — Claude API client (SSE streaming)
  • M12 — Tool system (file read/write/list/edit/search on ext2)
  • M13 — Claude shell integration (agentic tool loop, conversation persistence)
  • M14 — OAuth 2.0, VMware Tools, serial input, edit/search tools
  • M15 — Promises, microtask queue
  • M16 — Linux syscall trampoline (60+ syscalls), threading (clone/futex), sockets, epoll, fork/wait4
  • M17 — QuickJS on unGodly (musl static-pie)
  • M18 — Bourne shell (lexer, parser, pipelines, control flow, redirections)
  • M19 — Window manager + terminal emulator + REPL
  • M20 — execve + child_process.spawnSync, mkdir/rmdir/rename syscalls
  • M21 — Symlinks (ext2 inode type 0xA000, lstat, path resolution)
  • M22 — stdin piping via spawnSync, conservative fork stack copy
  • M23 — Non-blocking Claude streaming (PID 0 polling, GUI responsive during crypto)
  • M24 — HTTPS fetch for arbitrary URLs (TLS retry, X25519 precomputation, ALPN)
  • M25 — Tool dispatch fix (capture pressure reduction, 21 captures)
  • M26 — Advanced tools (execute_command, glob with recursive BFS)
  • M27 — Web tools (fetch_url with redirects, web_search via DuckDuckGo, HTML-to-text)

License

MIT

About

Meta-circular JavaScript VM targeting bare metal

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages