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
All benchmarks on Apple Silicon M3. Best of 3 runs.
| 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 |
| 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.
- 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
numbervalues can compile asi64instead off64, 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 binaries —
fib(40)compiles to a 37 KB binary (vs 441 KB for Rust)
| Mode | Time |
|---|---|
| Optimized (O3) | ~90ms |
| Debug (no optimization) | ~80ms |
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 .# 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// 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
42Objects 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,4See examples/ for more.
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.
294 tests passing, 63 pending. The goal is drop-in compatibility with existing TypeScript projects.
294 passing / 63 pending — run cargo test to see current counts.
✅ = implemented and correct
| 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() |
Tracks output correctness against Node.js. Each test asserts the exact output Node.js produces.
✅ = matches Node.js exactly
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 |
0 |
|
console.log(undefined) |
undefined |
0 |
|
console.log(NaN) |
NaN |
nan |
|
console.log(Infinity) |
Infinity |
inf |
|
console.log(-Infinity) |
-Infinity |
-inf |
|
console.log(-0) |
-0 |
0 |
|
console.log([]) |
[] |
[ ] |
|
console.log({}) |
{} |
{ } |
|
console.log(["a","b","c"]) |
[ 'a', 'b', 'c' ] |
||
console.log([[1,2],[3,4]]) |
[ [ 1, 2 ], [ 3, 4 ] ] |
||
console.log({a:{b:1}}) |
{ a: { b: 1 } } |
{ a: [complex] } |
|
console.log({name:"alice"}) |
{ name: 'alice' } |
||
| Class printing | console.log(new Point(3,4)) |
Point { x: 3, y: 4 } |
|
| class with string field | Person { name: 'Alice' } |
||
| inherited class | Dog { name: 'Rex' } |
||
| class with mixed fields | Item { id: 1, label: 'foo', active: true } |
||
| typeof | typeof 42 |
"number" |
✅ |
typeof "hello" |
"string" |
✅ | |
typeof true |
"boolean" |
✅ | |
typeof {} |
"object" |
✅ | |
typeof [1,2,3] |
"object" |
✅ | |
typeof null |
"object" |
"number" |
|
typeof undefined |
"undefined" |
"number" |
|
typeof (() => {}) |
"function" |
"object" |
|
typeof function |
"function" |
"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 |
0.3 |
|
console.log(1 / 0) |
Infinity |
inf |
|
console.log(-1 / 0) |
-Infinity |
-inf |
|
console.log(NaN === NaN) |
false |
true |
|
console.log(9007199254740992) |
9007199254740992 |
||
| String coercions | "" + true |
true |
✅ |
"" + false |
false |
✅ | |
"val=" + 42 |
val=42 |
✅ | |
"val=" + 3.14 |
val=3.14 |
✅ | |
"" + null |
null |
0 |
|
"" + undefined |
undefined |
0 |
|
"" + NaN |
NaN |
nan |
|
"" + Infinity |
Infinity |
inf |
|
| Equality | 1 === 1 |
true |
✅ |
"a" === "a" |
true |
✅ | |
true === true |
true |
✅ | |
NaN === NaN |
false |
true |
|
null === undefined |
false |
true |
|
null == undefined |
true |
||
0 == "" |
true |
||
| 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 |
-1 |
|
Math.trunc(1.9) |
1 |
||
Math.trunc(-1.9) |
-1 |
||
Math.sign(5) |
1 |
||
Math.sign(-5) |
-1 |
||
Math.sign(0) |
0 |
||
Math.log2(8) |
3 |
||
Math.log10(1000) |
3 |
||
Math.hypot(3, 4) |
5 |
||
Math.clz32(1) |
31 |
||
| Arrays | console.log([1, 2, 3]) |
[ 1, 2, 3 ] |
✅ |
console.log([42]) |
[ 42 ] |
✅ | |
| array length after push | 4 |
✅ | |
console.log([]) |
[] |
[ ] |
|
console.log(["a","b","c"]) |
[ 'a', 'b', 'c' ] |
||
console.log([[1,2],[3,4]]) |
[ [ 1, 2 ], [ 3, 4 ] ] |
||
console.log([true,false,true]) |
[ true, false, true ] |
||
| Objects | console.log({ x: 1, y: 2 }) |
{ x: 1, y: 2 } |
✅ |
console.log({ flag: true }) |
{ flag: true } |
✅ | |
console.log({}) |
{} |
{ } |
|
console.log({ name: "hello" }) |
{ name: 'hello' } |
||
console.log({ a: { b: 1 } }) |
{ a: { b: 1 } } |
{ a: [complex] } |
MIT