AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Perry compiles TypeScript ahead of time to native executables. Native builds need no external Node.js installation or JavaScript engine; Perry's runtime and GC are statically linked.
Translation pending
Developer toolsTypeScript · CGitHub @PerryTSOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
TypeScript-to-native compiler with LLVM backend
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A TypeScript-to-native compiler with an LLVM backend, written in TypeScript.
Anvil parses a subset of TypeScript, lowers it through a high-level IR, emits LLVM IR, and links against the Perry runtime to produce native executables.
Anvil exists as the self-hosting test for the Perry compiler. Perry (written in Rust) compiles TypeScript to native code. Anvil is itself written in Perry-compatible TypeScript — so when Perry can compile Anvil, and Anvil can compile programs, the toolchain has passed the "King's Test" of compiler maturity.Read the full README ↗A TypeScript-to-native compiler with an LLVM backend, written in TypeScript.
Anvil parses a subset of TypeScript, lowers it through a high-level IR, emits LLVM IR, and links against the Perry runtime to produce native executables.
Anvil exists as the self-hosting test for the Perry compiler. Perry (written in Rust) compiles TypeScript to native code. Anvil is itself written in Perry-compatible TypeScript — so when Perry can compile Anvil, and Anvil can compile programs, the toolchain has passed the "King's Test" of compiler maturity.
Anvil and Perry share the same runtime (libperry_runtime.a) and NaN-boxing scheme. A test suite of 81 TypeScript files validates that both compilers produce identical output.
Current parity: 68/68 deterministic tests match (100%)
| Status | Count | Details |
|---|---|---|
| PASS | 68 | Perry output = Anvil output |
| Inherent DIFF | 2 | test_date (timestamps), test_math (Math.random) |
| Skipped | 11 | UI/timer/crypto/plugin (platform-specific) |
| Fix | Root cause | Files |
|---|---|---|
| i64 wrapper shim conversion | bitcast instead of fcvt_to_sint_sat/fcvt_from_sint in integer-specialized function wrappers corrupted return values | functions.rs |
| Default params in i64 functions | fcvt_to_sint_sat converts undefined (missing arg) to 0 instead of triggering default value logic; now excluded from i64 specialization | functions.rs |
| Closure default param handling | Closures didn't check for TAG_UNDEFINED on missing args or apply default values | closures.rs, expr.rs |
Class instance .map() interception | .map() unconditionally lowered as ArrayMap even on class instances like Box.map() | lower.rs |
| User-defined class type inference | const b = new Box(5) got Type::Any instead of Type::Named("Box") | destructuring.rs |
| Class method return type inference | Stmt::Let didn't check ClassMeta.method_return_types before falling through to array method heuristics | stmt.rs |
| Module-level variable staleness | Named functions cached module-level variables in Cranelift locals at entry and never reloaded after calls that modify them | functions.rs |
| TsParamProp handling | constructor(public name: string) parameter properties not registered as fields, no this.name = name synthesized | lower_decl.rs |
| CLASS_CAPTURE LocalId collision | Inlined function LocalIds colliding with class constructor parameter LocalIds caused wrong variable promotion to globals | codegen.rs |
Regex .test() on named vars | Type::Named("RegExp") not recognized in .test() type check, falling through to dynamic dispatch returning [object Object] | lower.rs |
| Math constants | Math.PI, Math.E, etc. not resolved at compile time, falling through to property lookup returning undefined | lower.rs |
delete/in operator booleans | js_object_has_property returned raw 0.0/1.0 instead of NaN-boxed TAG_TRUE/TAG_FALSE; Expr::Delete used fcvt_from_sint | object.rs, expr.rs |
String() BigInt coercion | js_string_coerce missing BigInt case, NaN-boxed BigInt fell through to float path producing NaN | builtins.rs |
Object literal this binding | Object methods referencing this compiled as plain FuncRef instead of Closure with captures_this: true | lower.rs, closures.rs |
Detached method this unbinding | const fn = obj.getX; fn() retained this binding; now uses CAPTURES_THIS_FLAG and js_closure_unbind_this to strip this on PropertyGet | closure.rs, expr.rs |
| Generic identity string args | I64 string values passed to Any-typed F64 parameters via raw bitcast instead of proper NaN-boxing with STRING_TAG | expr.rs |
var loop closure hoisting | var declarations in for-loop init not hoisted, closures didn't capture them as mutable | lower.rs, lower_decl.rs |
| ArrayPush mutable capture | collect_mutable_captures_from_expr didn't walk into ArrayPush/ArrayUnshift/ArrayPushSpread | closures.rs |
.map() on new ClassName() | ArrayMap guard only checked Ident receivers, not New expressions | lower.rs, expr.rs |
| Fix | Root cause | Files |
|---|---|---|
| Interface method dispatch | Interface-typed variables returned undefined for method calls; no runtime dispatch based on implementing class | lower.ts, expr.ts, ast.ts, parser.ts |
| Property/global dispatch | Interface dispatch only handled local variable receivers, not this._field.method() or module-level vars | lower.ts, expr.ts |
1/0 instead of true/false — both compilers treat boolean results as f64(1.0)/f64(0.0). A working fix exists (NaN-boxed booleans + arithmetic unboxing + while-loop truthiness fix) but requires changes across 8 files.String() coercion was recently fixed and some edge cases may remain.source.ts
→ Scanner → Parser → AST
→ Lowerer → HIR (typed intermediate representation)
→ Codegen → LLVM IR (.ll)
→ clang → object file (.o)
→ cc + libperry_runtime.a → native executable
| Stage | Key files |
|---|---|
| Lexer | src/parser/scanner.ts, src/parser/token.ts |
| Parser | src/parser/parser.ts |
| HIR & lowering | src/hir/ir.ts, src/hir/types.ts, src/hir/lower.ts |
| Codegen | src/codegen/compiler.ts, src/codegen/expr.ts, src/codegen/stmt.ts |
| LLVM IR builder | src/llvm/module.ts, src/llvm/block.ts, src/llvm/function.ts |
| Driver & linker | src/driver/compile.ts, src/driver/linker.ts |
| Runtime stubs | src/stubs.c |
Values are represented using NaN-boxing (src/codegen/nanbox.ts), matching the Perry runtime's encoding.
let, const, var)implementsif/else, while, for, for...of, for...in)push, pop, shift, splice, slice, map, filter, reduce, forEach, find, length)console.log, console.error, console.warnclang (for compiling LLVM IR to object files)libperry_runtime.a — build it from the Perry project:
cd ../perry && cargo build --release -p perry-runtime
npm install
npx tsc
# Compile a TypeScript file to a native executable
node dist/main.js myfile.ts -o myfile --runtime /path/to/libperry_runtime.a
# Keep the generated LLVM IR for inspection
node dist/main.js myfile.ts --emit-ll -o myfile --runtime /path/to/libperry_runtime.a
# Run the parity test suite (requires Perry compiler installed)
RUNTIME=/path/to/libperry_runtime.a
for f in ../perry/test-files/test_*.ts; do
name=$(basename "${f%.ts}")
perry compile "$f" -o "/tmp/perry_$name" 2>/dev/null || continue
expected=$("/tmp/perry_$name" 2>/dev/null)
node dist/main.js "$f" -o "/tmp/anvil_$name" --runtime "$RUNTIME" 2>/dev/null || continue
actual=$("/tmp/anvil_$name" 2>/dev/null)
[ "$actual" = "$expected" ] && echo "PASS $name" || echo "DIFF $name"
done
MIT