Skip to content

cowboycodr/tscc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

67 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tscc

A compiler that takes TypeScript syntax and compiles it to native machine code via LLVM.

Not a transpiler. Not a runtime. tscc produces standalone native binaries from .ts files.

tscc run examples/hello.ts

Performance

All benchmarks on Apple Silicon M3. Best of 3 runs.

Recursive Fibonacci — fib(40)

Runtime Time Relative Memory
tscc 0.28s 1.0x 1.2 MB
C (cc -O3) 0.28s 1.0x 1.2 MB
Rust (rustc -O, i64) 0.28s 1.0x 1.4 MB
Rust (rustc -O, f64) 0.41s 1.5x slower 1.4 MB
Bun 1.3 0.48s 1.7x slower 27 MB
Node 25 0.79s 2.8x slower 49 MB

Loop Sum — sum(0..1_000_000_000)

Runtime Time Relative
tscc < 0.01s --
Rust (rustc -O) < 0.01s --
Bun 1.3 0.88s --
Node 25 1.47s --

Both tscc and Rust produce effectively instant results — LLVM optimizes the entire loop into a closed-form computation at compile time.

Why is tscc fast?

  • LLVM O3 — Full optimization pipeline (loop vectorization, SLP vectorization, function inlining, dead code elimination)
  • Native CPU targeting — Generates code tuned for the exact CPU (-mcpu=native)
  • Integer narrowing — Analysis pass detects when number values can compile as i64 instead of f64, enabling LLVM's integer-specific optimizations (accumulator transformation, strength reduction)
  • No runtime overhead — No JIT warmup, no garbage collector, no event loop. Just a native binary
  • Tiny binariesfib(40) compiles to a 37 KB binary (vs 441 KB for Rust)

Compilation Speed

Mode Time
Optimized (O3) ~90ms
Debug (no optimization) ~80ms

Install

Requires LLVM 18 and Rust.

# Install LLVM 18
brew install llvm@18

# Set environment variables (add to your shell profile)
export LLVM_SYS_180_PREFIX=/opt/homebrew/opt/llvm@18
export LIBRARY_PATH="/opt/homebrew/lib:$LIBRARY_PATH"

# Build tscc
cargo install --path .

Usage

# Compile and run
tscc run file.ts

# Compile to binary
tscc build file.ts            # outputs ./file
tscc build file.ts -o output  # custom output path

# Flags
tscc run file.ts --benchmark  # time execution
tscc build file.ts --debug    # skip optimization (faster compile)
tscc build file.ts --emit-ir  # print LLVM IR

Examples

// hello.ts
function greet(name: string): string {
    return `Hello, ${name}!`
}

console.log(greet("World"))

let numbers = [10, 20, 30]
console.log("Second:", numbers[1])

let double = (x: number): number => x * 2
console.log(double(21))  // 42
$ tscc run examples/hello.ts
Hello, World!
Second: 20
42

Objects and classes compile to zero-overhead LLVM structs:

class Point {
    x: number
    y: number
    constructor(x: number, y: number) {
        this.x = x
        this.y = y
    }
    toString(): string {
        return this.x + "," + this.y
    }
}

let p = new Point(3, 4)
console.log(p.toString()) // 3,4

See examples/ for more.

Architecture

TypeScript source
    |
    +- Lexer -------- Hand-written scanner
    +- Parser ------- Recursive descent + Pratt precedence
    +- Type Checker -- Structural typing, inference
    +- Codegen ------ LLVM IR via inkwell
    +- Optimizer ---- LLVM O3 + native CPU targeting
    +- Linker ------- Links with pre-compiled runtime
    |
    Native binary

Written in Rust. Single crate. ~15,000 lines of Rust + ~546 lines of C runtime.

The runtime (runtime/runtime.c) provides print functions, string operations, math functions, and array support. It is compiled once at cargo build time and embedded directly into the tscc binary — no C toolchain is required on the user's machine to compile TypeScript files.

Status

294 tests passing, 63 pending. The goal is drop-in compatibility with existing TypeScript projects.

TypeScript Feature Coverage

294 passing / 63 pending — run cargo test to see current counts.

✅ = implemented and correct ⚠️ = compiles but known-incorrect behavior ❌ = not yet implemented

Category Feature Status Notes
Literals Integer console.log(42)
Float console.log(3.14)
Negative number console.log(-7)
String (double/single quotes) "hello", 'world'
Boolean true, false
null / undefined ⚠️ compile as 0 — print as 0 not null/undefined
BigInt 9007199254740993n
Variables let / const let x = 10, const y = 99
Type annotations let x: number = 5
Reassignment x = 2
Uninitialized let let x: number (defaults to 0)
Optional semicolons let x = 42
var ⚠️ treated as let — no hoisting
Operators Arithmetic + - * / % ** 2 + 3 * 4
Comparison < > <= >= == != === !== 5 === 5
Logical && || ! true && !false
Nullish coalescing ?? null ?? 42
Optional chaining ?. obj?.a
Compound assignment += -= *= /= x += 3
Postfix ++ / -- x++, arr[i]++, obj[key]++
Prefix ++ / -- ++x, ++arr[i]
Unary negate -x
Ternary ? : x > 0 ? "pos" : "neg"
typeof ⚠️ typeof null"number" (should be "object"); typeof function"object"
Loose equality == ⚠️ treated as === — no type coercion
Strings Concatenation "hello" + " " + 42
Template literals `value is ${x}`
.length "hello".length
.toUpperCase() / .toLowerCase() "hi".toUpperCase()
.trim() / .trimStart() / .trimEnd() " hi ".trim()
.includes() / .startsWith() / .endsWith() "hello".includes("ell")
.indexOf() "hello world".indexOf("world")
.slice() / .substring() "hello".slice(1, 3)
.split() "a,b,c".split(",")
.replace() "hello".replace("l", "r")
.repeat() "ab".repeat(3)
.padStart() / .padEnd() "5".padStart(3, "0")
.charAt() "hello".charAt(1)
Chained methods " Hello ".trim().toLowerCase()
Control Flow if / else if / else if (x > 0) { ... }
while while (i < 5) { ... }
for for (let i = 0; i < n; i++)
for...of for (const x of arr)
for...in for (const key in obj)
do...while do { i++ } while (i < 5)
switch / case switch (x) { case 1: ... }
break / continue if (i == 5) break
Labeled statements outer: for (...)
Block scoping { let y = 2 }
Functions Declarations function add(a, b) { return a + b }
Arrow functions (a, b) => a + b
Arrow (block body) (x) => { return x * 2 }
Function expressions let f = function() {}
Default parameters function f(x = 10)
Rest parameters function f(...args: number[])
Closures capturing outer variables
Recursion function fib(n) { ... fib(n-1) }
Function hoisting call before declaration
Arrays Literals [1, 2, 3]
Index access arr[1]
Spread [...arr, 4, 5]
.length arr.length
.push() / .pop() arr.push(4)
.map() / .filter() / .reduce() arr.map(x => x * 2)
.forEach() arr.forEach(x => console.log(x))
String array printing ⚠️ ["a","b"] prints as garbage IEEE754 numbers
Nested array printing ⚠️ [[1,2],[3,4]] prints as garbage pointer values
Empty array printing ⚠️ [] prints as [ ] with extra spaces
Objects & Classes Object literals { x: 1, y: 2 }
Property / bracket access obj.x, obj["x"]
Shorthand properties { x, y }
Object spread { ...a, ...b }
Computed property keys { [Status.Todo]: 0 }
Object methods { greet() { ... } }
Object destructuring let { x, y } = point
Array destructuring let [a, b] = [1, 2]
Class declarations class Point { x: number; ... }
new + constructor new Point(3, 4)
Class methods p.toString()
Class inheritance class Dog extends Animal
Class field initializers class Foo { x = 5 }
Interfaces interface Point { x: number }
Interface inheritance interface B extends A
Class instance printing ⚠️ console.log(new Point(3,4)) omits class name prefix
Nested object printing ⚠️ console.log({a:{b:1}}) prints { a: [complex] }
Object/class string field printing ⚠️ string values printed unquoted in objects/classes
Type System Type annotations & inference let x: number, let y = 42
Union types string | number
Intersection types Named & Aged
Type aliases type ID = string | number
Enums (numeric & string) enum Dir { Up = "UP" }
Generics function identity<T>(x: T): T
Generic constraints <T extends { length: number }>
Tuple types let t: [number, string]
Type assertions x as string
Type narrowing if (typeof x === "string")
String literal types type Dir = "up" | "down"
Boolean literal types type T = | { success: true; data: T }
readonly readonly id: string
keyof keyof Point
Conditional types T extends number ? "yes" : "no"
Mapped types { [P in keyof T]: T[P] }
typeof in type position let y: typeof x
Type predicates x is SomeType
satisfies "red" satisfies Colors
as const [1, 2, 3] as const
Modules Named exports / imports export function f(), import { f }
Import aliasing import { add as sum }
Default export/import export default 42
import * as import * as math from "./math"
Re-exports export { foo } from "./bar"
Error Handling try / catch / finally try { ... } catch (e) { ... }
throw throw "message"
Async async / await async function f() { await g() }
Promise (basic) async functions return Promise<T>
Promise.resolve() / .reject() static methods not yet wired up
.then() / .catch() promise chaining not yet implemented
setTimeout runtime exists, codegen not wired up
Date new Date() / new Date(ms) new Date(0).getTime()
Date.now() returns ms since epoch
getFullYear/Month/Date/Hours/... local and UTC variants
toISOString() "2000-01-01T11:30:45.678Z"
Built-ins console.log() / .error() / .warn() console.log("hello")
parseInt() / parseFloat() parseInt("42")
Math.* (floor/ceil/round/abs/max/min/pow/sqrt/log/random/PI) Math.floor(1.9)
Math.trunc / Math.sign / Math.log2 / Math.log10 / Math.hypot not yet implemented
Number.isInteger() / .isFinite() / .isNaN() Number.isInteger(42)
.toFixed() (3.14).toFixed(2)
Map new Map(), .set(), .get(), .has(), .delete(), .values()
JSON.stringify() JSON.stringify({ a: 1 })
Set new Set([1, 2, 3])
RegExp /hello/.test("hello world")
Advanced Namespaces namespace Util { ... }
Decorators @log
Symbols Symbol("foo")
Generators function* range()

Parity Test Suite

Tracks output correctness against Node.js. Each test asserts the exact output Node.js produces.

✅ = matches Node.js exactly   ⚠️ = compiles but wrong output (auto-passes when fixed)

Run the full backlog: cargo test parity -- --ignored

Area Test Expected output Status
console.log multiple args: console.log(1, "hi", true) 1 hi true
number array: console.log([1, 2, 3]) [ 1, 2, 3 ]
object with numbers: console.log({ x: 1, y: 2 }) { x: 1, y: 2 }
booleans: console.log(true) true / false
console.log(null) null ⚠️ prints 0
console.log(undefined) undefined ⚠️ prints 0
console.log(NaN) NaN ⚠️ prints nan
console.log(Infinity) Infinity ⚠️ prints inf
console.log(-Infinity) -Infinity ⚠️ prints -inf
console.log(-0) -0 ⚠️ prints 0
console.log([]) [] ⚠️ prints [ ]
console.log({}) {} ⚠️ prints { }
console.log(["a","b","c"]) [ 'a', 'b', 'c' ] ⚠️ prints garbage IEEE754
console.log([[1,2],[3,4]]) [ [ 1, 2 ], [ 3, 4 ] ] ⚠️ prints garbage pointers
console.log({a:{b:1}}) { a: { b: 1 } } ⚠️ prints { a: [complex] }
console.log({name:"alice"}) { name: 'alice' } ⚠️ value unquoted
Class printing console.log(new Point(3,4)) Point { x: 3, y: 4 } ⚠️ no class name prefix
class with string field Person { name: 'Alice' } ⚠️ no name, unquoted
inherited class Dog { name: 'Rex' } ⚠️ no class name
class with mixed fields Item { id: 1, label: 'foo', active: true } ⚠️ no name, unquoted
typeof typeof 42 "number"
typeof "hello" "string"
typeof true "boolean"
typeof {} "object"
typeof [1,2,3] "object"
typeof null "object" ⚠️ returns "number"
typeof undefined "undefined" ⚠️ returns "number"
typeof (() => {}) "function" ⚠️ returns "object"
typeof function "function" ⚠️ returns "object"
Number formatting console.log(-3.14) -3.14
console.log(10 / 2) 5
console.log(7 / 2) 3.5
console.log(0.1 + 0.2) 0.30000000000000004 ⚠️ prints 0.3
console.log(1 / 0) Infinity ⚠️ prints inf
console.log(-1 / 0) -Infinity ⚠️ prints -inf
console.log(NaN === NaN) false ⚠️ returns true
console.log(9007199254740992) 9007199254740992 ⚠️ scientific notation
String coercions "" + true true
"" + false false
"val=" + 42 val=42
"val=" + 3.14 val=3.14
"" + null null ⚠️ yields 0
"" + undefined undefined ⚠️ yields 0
"" + NaN NaN ⚠️ yields nan
"" + Infinity Infinity ⚠️ yields inf
Equality 1 === 1 true
"a" === "a" true
true === true true
NaN === NaN false ⚠️ returns true
null === undefined false ⚠️ returns true
null == undefined true ⚠️ loose equality not implemented
0 == "" true ⚠️ loose equality not implemented
Math Math.floor(1.9) 1
Math.floor(-1.5) -2
Math.ceil(1.1) 2
Math.ceil(-1.5) -1
Math.round(0.5) 1
Math.abs(-5) 5
Math.max(3, 7) 7
Math.min(3, 7) 3
Math.pow(2, 10) 1024
Math.sqrt(9) 3
Math.round(-0.5) 0 ⚠️ returns -1
Math.trunc(1.9) 1 ⚠️ not implemented
Math.trunc(-1.9) -1 ⚠️ not implemented
Math.sign(5) 1 ⚠️ not implemented
Math.sign(-5) -1 ⚠️ not implemented
Math.sign(0) 0 ⚠️ not implemented
Math.log2(8) 3 ⚠️ not implemented
Math.log10(1000) 3 ⚠️ not implemented
Math.hypot(3, 4) 5 ⚠️ not implemented
Math.clz32(1) 31 ⚠️ not implemented
Arrays console.log([1, 2, 3]) [ 1, 2, 3 ]
console.log([42]) [ 42 ]
array length after push 4
console.log([]) [] ⚠️ prints [ ]
console.log(["a","b","c"]) [ 'a', 'b', 'c' ] ⚠️ garbage output
console.log([[1,2],[3,4]]) [ [ 1, 2 ], [ 3, 4 ] ] ⚠️ garbage output
console.log([true,false,true]) [ true, false, true ] ⚠️ unverified
Objects console.log({ x: 1, y: 2 }) { x: 1, y: 2 }
console.log({ flag: true }) { flag: true }
console.log({}) {} ⚠️ prints { }
console.log({ name: "hello" }) { name: 'hello' } ⚠️ value unquoted
console.log({ a: { b: 1 } }) { a: { b: 1 } } ⚠️ prints { a: [complex] }

License

MIT

About

A compiler that compiles TypeScript to native machine code via LLVM

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors