Skip to content

ASDAlexander77/TypeScriptCompiler

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

92 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TypeScript Native Compiler

Powered by LLVM|MLIR

Donate

A native ahead-of-time (AOT) and JIT compiler for TypeScript, built on LLVM/MLIR. It compiles .ts files directly to native executables, WebAssembly, or runs them on the fly via a built-in JIT — no Node.js or JavaScript runtime required.

CI Status

Test Build (Windows) Test Build (Linux)

Contents

What's new

  • Implemented try/catch exception handling in the JIT on Windows (x64 SEH unwinding via a custom LLJIT runtime)

  • Migrared to LLVM 22.1.8

  • Migrated to Visual Studio 2026 (Windows build chain)

  • JavaScript Built-in objects library [Default Library repo]

  • Visual Studio Code project

tslang --new Test1
  • CMake project (mixed C++/TypeScript, TSLANG registered as a first-class CMake language)
tslang --cmake Test1

Generates a ready-to-build project (CMakeLists.txt, CMakePresets.json, main.cpp, sample .ts sources and the cmake/ TSLANG language modules). Build it with:

cd Test1
cmake --preset default && cmake --build --preset default
  • Strict null checks
let sn: string | null = null; // Ok
let s: string = null; // error
  • Improved Template Literal Types
type Color = "red" | "green" | "blue";
type HexColor<T extends Color> = `#${string}`;
  • Public, private, and protected modifiers
class Point {
    private x: number;
    #y: number;
}

const p = new Point();
p.x // access error
p.#y // error
  • Class from Tuple
class Point {
    x: number;
    y: number;
}

class Line {
    constructor(public start: Point, public end: Point) { }
}

const l = new Line({ x: 0, y: 1 }, { x: 1.0, y: 2.0 });
  • Compile-time ifs
function isArray<T extends unknown[]>(value: T): value is T {
    return true;
}

function gen<T>(t: T)
{
    if (isArray(t))
    {
        return t.length.toString();
    }

    return "int";
}

const v1 = gen<i32>(23); // result: int
const v2 = gen<string[]>([]); // result: 0
  • Migrated to LLVM 19.1.3

  • Improved generating debug information — more info here: Wiki:How-To

tslang --di --opt_level=0 --emit=exe example.ts

Roadmap

  • Migrating to LLVM 22.1.8
  • Shared libraries
  • JavaScript Built-in classes library

Demo

See the release page for a recorded demo

Demo

Try it

Open the WASM example on Compiler Explorer

Open the native example on Compiler Explorer

Chat Room

Want to chat with other members of the TypeScriptCompiler community?

Example

abstract class Department {
    constructor(public name: string) {}

    printName(): void {
        print("Department name: " + this.name);
    }

    abstract printMeeting(): void; // must be implemented in derived classes
}

class AccountingDepartment extends Department {
    constructor() {
        super("Accounting and Auditing"); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        print("The Accounting Department meets each Monday at 10am.");
    }

    generateReports(): void {
        print("Generating accounting reports...");
    }
}

function main() {
    let department: Department; // ok to create a reference to an abstract type
    department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
    department.printName();
    department.printMeeting();
    //department.generateReports(); // error: department is not of type AccountingDepartment, cannot access generateReports
}

Run

tslang --emit=jit --opt --shared-libs=TypeScriptRuntime.dll example.ts

Result

Department name: Accounting and Auditing
The Accounting Department meets each Monday at 10am.

Run as JIT

File hello.ts

function main() {
    print("Hello World!");
}

Build

tslang hello.ts

Result

Hello World!

Compile as Binary Executable

Make sure you have download tslang from releases first.

The compile process may use a few environment variables so the linker can find the runtime libraries, then invoke tslang with --emit=exe. Edit the paths to match your checkout — the C:\dev\... / ~/dev/... values are only examples.

Variable Points to
GC_LIB_PATH Boehm GC library (the garbage collector)
LLVM_LIB_PATH LLVM/MLIR libraries
TSLANG_LIB_PATH TSLANG runtime library
DEFAULT_LIB_PATH Default library

Compile on Windows

Build

tslang --emit=exe hello.ts

Run

hello.exe

Result

Hello World!

Compile on Linux (Ubuntu 20.04 and 22.04)

Build

./tslang --emit=exe hello.ts --relocation-model=pic

Run

./hello

Result

Hello World!

Compiling as WASM

WASM build on Windows

Build

tslang.exe --emit=exe --nogc -mtriple=wasm32-unknown-unknown hello.ts

Run run.html

Click to expand the full run.html WebAssembly loader
<!DOCTYPE html>
<html>

<head></head>

<body>
    <script type="module">
        let buffer;
        let buffer32;
        let buffer64;
        let bufferF64;
        let heap;

        let heap_base, heap_end, stack_low, stack_high;

        const allocated = [];

        const allocatedSize = (addr) => {
            return allocated["" + addr];
        };

        const setAllocatedSize = (addr, newSize) => {
            allocated["" + addr] = newSize;
        };

        const expand = (addr, newSize) => {

            const aligned_newSize = newSize + (4 - (newSize % 4))

            const end = addr + allocatedSize(addr);
            const newEnd = addr + aligned_newSize;

            for (const allocatedAddr in allocated) {
                const beginAllocatedAddr = parseInt(allocatedAddr);
                const endAllocatedAddr = beginAllocatedAddr + allocated[allocatedAddr];
                if (beginAllocatedAddr != addr && addr < endAllocatedAddr && newEnd > beginAllocatedAddr) {
                    return false;
                }
            }

            setAllocatedSize(addr, aligned_newSize);
            if (addr + aligned_newSize > heap) heap = addr + aligned_newSize;
            return true;
        };

        const endOf = (addr) => { while (buffer[addr] != 0) { addr++; if (addr > heap_end) throw "out of memory boundary"; }; return addr; };
        const strOf = (addr) => String.fromCharCode(...buffer.slice(addr, endOf(addr)));
        const copyStr = (dst, src) => { while (buffer[src] != 0) buffer[dst++] = buffer[src++]; buffer[dst] = 0; return dst; };
        const ncopy = (dst, src, count) => { while (count-- > 0) buffer[dst++] = buffer[src++]; return dst; };
        const append = (dst, src) => copyStr(endOf(dst), src);
        const cmp = (addrL, addrR) => { while (buffer[addrL] != 0) { if (buffer[addrL] != buffer[addrR]) break; addrL++; addrR++; } return buffer[addrL] - buffer[addrR]; };
        const prn = (str, addr) => { for (let i = 0; i < str.length; i++) buffer[addr++] = str.charCodeAt(i); buffer[addr] = 0; return addr; };
        const clear = (addr, size, val) => { for (let i = 0; i < size; i++) buffer[addr++] = val; };
        const aligned_alloc = (size) => { 
            const aligned_size = size + (4 - (size % 4)); 
            if ((heap + aligned_size) > heap_end) throw "out of memory"; 
            setAllocatedSize(heap, aligned_size); 
            const heapCurrent = heap; 
            heap += aligned_size; 
            return heapCurrent; 
        };
        const free = (addr) => delete allocated["" + addr];
        const realloc = (addr, size) => {
            if (!expand(addr, size)) {
                const newAddr = aligned_alloc(size);
                ncopy(newAddr, addr, allocatedSize(addr));
                free(addr);
                return newAddr;
            }

            return addr;
        }

        const envObj = {
            memory: new WebAssembly.Memory({ initial: 256 }),
            table: new WebAssembly.Table({
                initial: 0,
                element: 'anyfunc',
            }),
            fmod: (arg1, arg2) => arg1 % arg2,
            sqrt: (arg1) => Math.sqrt(arg1),
            floor: (arg1) => Math.floor(arg1),
            pow: (arg1, arg2) => Math.pow(arg1, arg2),
            fabs: (arg1) => Math.abs(arg1),
            _assert: (msg, file, line) => console.assert(false, strOf(msg), "| file:", strOf(file), "| line:", line, " DBG:", path),
            puts: (arg) => output += strOf(arg) + '\n',
            strcpy: copyStr,
            strcat: append,
            strcmp: cmp,
            strlen: (addr) => endOf(addr) - addr,
            malloc: aligned_alloc,
            realloc: realloc,
            free: free,
            memset: (addr, size, val) => clear(addr, size, val),
            atoi: (addr, rdx) => parseInt(strOf(addr), rdx),
            atof: (addr) => parseFloat(strOf(addr)),
            sprintf_s: (addr, sizeOfBuffer, format, ...args) => {
                const formatStr = strOf(format);
                switch (formatStr) {
                    case "%d": prn(buffer32[args[0] >> 2].toString(), addr); break;
                    case "%g": prn(bufferF64[args[0] >> 3].toString(), addr); break;
                    case "%llu": prn(buffer64[args[0] >> 3].toString(), addr); break;
                    default: throw "not implemented"; 
                }

                return 0;
            },
        }

        const config = {
            env: envObj,
        };

        WebAssembly.instantiateStreaming(fetch("./hello.wasm"), config)
            .then(results => {
                const { main, __wasm_call_ctors, __heap_base, __heap_end, __stack_low, __stack_high } = results.instance.exports;
                buffer = new Uint8Array(results.instance.exports.memory.buffer);
                buffer32 = new Uint32Array(results.instance.exports.memory.buffer);
                buffer64 = new BigUint64Array(results.instance.exports.memory.buffer);
                bufferF64 = new Float64Array(results.instance.exports.memory.buffer);
                heap = heap_base = __heap_base, heap_end = __heap_end, stack_low = __stack_low, stack_high = __stack_high;
                try
                {
                    if (__wasm_call_ctors) __wasm_call_ctors();
                    main();
                }
                catch (e)
                {
                    console.error(e);
                }
            });
    </script>
</body>

</html>

Build

Build on Windows

Windows Requirements

  • Visual Studio 2026
  • ~50 GB of free disk space (LLVM/MLIR build dominates this)

The hardcoded C:\dev\... paths in the scripts below are examples — adjust them to your checkout location.

First, precompile dependencies

cd TypeScriptCompiler
prepare_3rdParty.bat

To build TSLANG binaries:

cd TypeScriptCompiler\tslang
config_tslang_release.bat
build_tslang_release.bat

Build on Linux (Ubuntu 20.04 and 22.04)

Linux Requirements

  • gcc or clang
  • cmake
  • ninja-build
  • ~50 GB of free disk space (LLVM/MLIR build dominates this)
  • sudo apt-get install libtinfo-dev

First, precompile dependencies

chmod +x *.sh
cd ~/TypeScriptCompiler
./prepare_3rdParty.sh

To build TSLANG binaries:

cd ~/TypeScriptCompiler/tslang
chmod +x *.sh
./config_tslang_release.sh
./build_tslang_release.sh

License

This project is licensed under the MIT License — see the LICENSE file for details.

Contributing

Issues and pull requests are welcome. See the Wiki for documentation, build notes, and how-to guides.

About

TypeScript Compiler (by LLVM)

Resources

License

Code of conduct

Stars

751 stars

Watchers

18 watching

Forks

Packages

 
 
 

Contributors