This page provides a high-level architectural overview of Paserati, a TypeScript/JavaScript runtime that compiles TypeScript directly to bytecode and executes it on a register-based virtual machine. It covers the major subsystems, their interactions, and the data flow from source code to execution.
For detailed information on specific subsystems, see:
Paserati is organized into distinct subsystems that transform TypeScript source code into executable bytecode and manage its execution:
Sources: pkg/driver/driver.go1-100 cmd/paserati/main.go1-50 README.md1-150
The Driver struct maintains a persistent session with reusable VM, Checker, and Compiler instances, enabling REPL-style interactive execution while preserving state across multiple compilations.
Source code passes through a series of transformations, with each stage adding semantic information:
Sources: pkg/lexer/lexer.go326-450 pkg/parser/parser.go420-467 pkg/checker/checker.go425-580 pkg/compiler/compiler.go484-530 pkg/vm/vm.go595-726
The Lexer struct tokenizes source code into Token values, each containing:
Type (e.g., IDENT, NUMBER, STRING, FUNCTION)Literal (the actual text)Line, Column, StartPos, EndPos)Sources: pkg/lexer/lexer.go49-63 pkg/lexer/lexer.go326-450
The Parser struct uses a Pratt parser to build an AST with nodes implementing either Statement or Expression interfaces. Key node types:
Program - root node containing statements and hoisted declarationsFunctionLiteral, ArrowFunctionLiteral - function definitionsClassDeclaration - class definitionsInfixExpression, CallExpression - operations and callsIdentifier, MemberExpression, IndexExpression - property accessSources: pkg/parser/parser.go24-44 pkg/parser/ast.go14-50
The Checker struct performs multi-pass semantic analysis:
Each Expression node receives a ComputedType field populated during checking, enabling downstream type-aware compilation.
Sources: pkg/checker/checker.go425-698 pkg/parser/ast.go29-49
The Compiler struct translates the typed AST into a Chunk of bytecode:
Code []byte - sequence of OpCode instructionsConstants []vm.Value - constant pool for literals and functionsExceptionTable []*ExceptionHandler - try/catch/finally metadataMaxRegs int - register allocation requirementNumSpillSlots int - spill slot allocation for register overflowSources: pkg/compiler/compiler.go217-300 pkg/vm/bytecode.go1-200
The VM struct executes bytecode using a register-based architecture:
Sources: pkg/vm/vm.go146-309 pkg/vm/vm.go102-136 pkg/vm/value.go1-150 pkg/vm/object.go1-200
The VM uses a fixed-size register file (RegFileSize = 256 registers per frame, MaxFrames = 1024 call depth). Each CallFrame holds a slice window into the flat registerStack, avoiding per-call allocations:
Sources: pkg/vm/vm.go19-22 pkg/vm/vm.go146-155
The Value struct uses a tagged union design:
payload (8 bytes)obj pointertyp field discriminates between value typesSources: pkg/vm/value.go14-80
Objects use a shape-based system (hidden classes) for memory efficiency:
Shape defines property layout and orderShapeSources: pkg/vm/object.go178-280 pkg/vm/shapes.go1-200
The type system provides static analysis through the Type interface hierarchy:
Sources: pkg/types/types.go1-150 pkg/types/types.go300-500
The Environment struct manages nested scopes with separate namespaces:
variables map[string]Type - value bindingstypes map[string]Type - type alias bindingstypeParameters map[string]*TypeParameter - generic type parameter bindingsnarrowings map[string]Type - control-flow refined typesOuter *Environment - lexical parent scopeSources: pkg/checker/environment.go1-100
Key type operations implemented by the checker:
IsAssignable(source, target Type) bool - structural compatibility checkinginferGenericFunctionCall() - two-phase generic inferencedetectTypeGuard() - control-flow narrowing via type guardsinstantiateGenericType() - generic type instantiation with substitutionSources: pkg/types/assignable.go1-500 pkg/checker/checker.go2000-2500
The module system coordinates separate compilation units while preventing global index collisions:
Sources: pkg/compiler/heapalloc.go1-200 pkg/modules/loader.go1-150 pkg/vm/vm.go73-82
The HeapAlloc struct ensures global variable indices don't collide across modules:
sync.RWMutexSources: pkg/compiler/heapalloc.go15-100
The OpEvalModule instruction triggers module evaluation:
ModuleContexts cache for prior executionSources: pkg/vm/vm.go73-82 pkg/vm/op_eval_module.go1-150
The standard library initializes through a two-phase process:
Sources: pkg/builtins/registry.go1-100 pkg/builtins/object.go1-150
Built-ins are initialized in dependency order:
Object: priority 0 (base of inheritance)Function: priority 1 (functions inherit from Object)Array: priority 2 (arrays inherit from Object)TypedArray: priority 419 (base for typed array types)Uint8Array: priority 420 (inherits from TypedArray)Sources: pkg/builtins/registry.go50-150
Key built-in groups:
Object, Array, String, Number, Boolean, BigIntFunction.prototype.call/apply/bindError, TypeError, ReferenceError, RangeErrorMap, Set, typed arraysPromise, async generator protocolProxy, Reflect, SymbolSources: pkg/builtins/registry.go1-200 pkg/builtins/array.go1-100 pkg/builtins/function.go1-100
Paserati uses registers instead of a stack-based architecture:
Sources: pkg/vm/vm.go19-22 docs/bucketlist.md202-225
Property access uses per-site inline caches transitioning through states:
Uninitialized → Monomorphic → Polymorphic → MegamorphicChunk for cache-local accessSources: pkg/vm/ic_site_cache.go1-31 pkg/vm/inline_cache.go1-200
Objects use hidden classes for memory efficiency:
ShapeSources: pkg/vm/shapes.go1-200 pkg/vm/object.go178-280
The checker uses four passes to handle forward references:
This allows mutually recursive types and functions.
Sources: pkg/checker/checker.go425-698
Enabled by default (enableTCO = true), TCO reuses the current frame for tail-positioned calls:
OpTailCall instead of OpCall+OpReturnSources: pkg/compiler/compiler.go204 pkg/compiler/compile_expression.go800-950
Direct eval (calling eval by name) receives special treatment:
OpDirectEval instruction with caller scope accessevalCallerRegs provides read/write access to caller's registersScopeDescriptor maps caller's variable names to register indicesSources: pkg/vm/vm.go264-278 pkg/vm/scope_descriptor.go1-100
Refresh this wiki
This wiki was recently refreshed. Please wait 7 days to refresh again.