Repository navigation
Conversation
teamchong
force-pushed
the
perf/checker-memory-and-wasm-speedups
branch
2 times, most recently
from
October 26, 2026 12:02
1badbc1 to
1a1a9a2
Compare
teamchong
force-pushed
the
perf/checker-memory-and-wasm-speedups
branch
6 times, most recently
from
October 27, 2026 15:47
b7b9f4b to
fbd936c
Compare
Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com> Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
Co-authored-by: typescript-automation[bot] <290192711+typescript-automation[bot]@users.noreply.github.com>
Signed-off-by: MaheshDesu <mahesh.desu3@gmail.com>
…pper records Enforces checked construction, immutable kind tags, and checked header casts across ast.Node, checker.Type, and checker.TypeMapper records.
…e PagedLinkStore - Fix Arena.New and Arena.NewSlice passing len(a.data) instead of cap(a.data) to nextArenaSize, and raise steady-state chunk ceiling to 32 KiB (1,024 elements). - Add IndexedArena (1,024-element blocks) and PagedValueLinkStore so PagedLinkStore pages store 32-bit uint32 indices instead of 64-bit pointers, halving page size and eliminating GC pointer scanning of link pages. - Split page-allocation slow paths out of PagedLinkStore.Get, PagedLinkStore.TryGet, and PagedValueLinkStore.Get so fast-path lookups inline. - Add core.SuspendGC and --maxMemory compiler option.
… lookup tables - Replace textToKeyword map[string]ast.Kind lookups in GetIdentifierToken and IdentifierToKeywordKind with a 0-collision 512-entry direct-mapped keywordTable keyed by (len, c0, c1, cLast). - Replace the per-byte scanASCIIWhile closure in scanIdentifier with a 256-byte isASCIIIdentPart lookup table and inline space/tab skipping in Scan.
…ields - Store diagnosticNode and diagnosticForNodeName directly on SymbolTrackerSharedState and evaluate accessibility diagnostics lazily via evalSymbolAccessibilityDiagnostic only when an error is reported, eliminating per-node closure allocations during declaration emit. - Construct a fresh ImportDeclaration via NewImportDeclaration instead of mutating Kind on a cloned node.
…r lists, and fast NameResolver
- Replace multi-case Kind switches in generated and handwritten Node accessors (Data, DeclarationData, ExportableData, LocalsContainerData, FunctionLikeData, ClassLikeData, BodyData, LiteralLikeData, TemplateLiteralLikeData, Name, Modifiers, SetModifiers, Text, Expression, TypeArgumentList, TypeParameterList, MemberList, StatementList, Type, SetType, Initializer, SetInitializer) with [kindFlowReduceLabelData + 1]uint8 byte-offset tables, keeping hot accessors under Go's 80-node inline budget.
- Make CompositeBase a zero-sized struct{} with a 64-shard subtreeFactsShards table (shrinking 46 AST node types by 8B), mark 28 hot AST node kinds arena: true, and merge FlowNode.Antecedent and FlowNode.Antecedents into a single pointer field (shrinking FlowNode from 32B to 24B).
- Pack declLen (15 bits), declCap (15 bits), and LazyState (2 bits) into Symbol.declHeader, merge Symbol.data and SymbolData.Parent into Symbol.parentOrData (shrinking SymbolWithData from 80B to 64B), add Symbol.FirstDeclaration() and checkFlagsMerged, and inline (*Symbol).Members() and (*Symbol).Exports().
- Add LazyNodeList for on-demand .d.ts interface, class, and type-literal member reparsing/binding.
- Add a tight inner ancestor loop for non-active scopes in NameResolver.Resolve, skip getIsDeferredContext on non-value lookups, and pre-size SymbolTable maps in getMembersSlow, getExportsSlow, and GetLocals.
…hot paths - Replace interface-dispatched Type, Signature, IndexInfo, and TypeMapper variants with concrete struct hierarchies and lazy extra allocations in Checker arenas. - Replace pointer-keyed NodeLinks/SymbolLinks stores with 32-bit node/symbol ID indexed PagedLinkStore and PagedValueLinkStore tables. - Precompute ObjectFlagsHasGenericArgumentsComputed, ObjectFlagsCouldContainTypeVariablesComputed, and ObjectFlagsIdenticalBaseTypeExists, and add unified ValueSymbolLinks.resolvedType and merged-symbol lookup fast paths.
…caches - Add bounded flowMemo and inferMemo caches in getTypeAtFlowNode and inferFromTypes to skip repeated control-flow walks and structural inference pairs. - Pool temporary conditional-inference contexts via acquireConditionalInferenceContext and releaseConditionalInferenceContext when contexts do not escape. - Pack relation comparison results into pointer-free simpleRelationMap and genericRelationMap slots, cache union discriminant constituent maps, and add a recursionId bloom filter in isDeeplyNestedOnStack.
…ip1 target - Recycle checker instances across files when heap usage exceeds --maxMemory and deduplicate parseTask fileIncludeReasons across root and sub-tasks. - Enable lazy .d.ts member reparsing and one-shot CLI GC suspension in performCompilation and performIncrementalCompilation. - Add build-wasm-tinygo.mjs, tinygo-overlay runtime/poll/hashmap/slice patches, and single-threaded wasm orchestrator/syncmap paths.
…lazy RHS/tuples - Cache parsed and bound static .d.ts SourceFiles across tsrs_run invocations in wasm_wasip1 and rewind the bump-allocator heap to a post-init watermark (MarkHeap/ResetHeap) after each run. - Scope TinyGo sync.Pool, sync.Once, and sync.Map state to the heap watermark epoch and restore forced lazy member/type-alias state so cached .d.ts ASTs hold no dangling post-watermark pointers across runs. - Defer TypeAliasDeclaration RHS parsing/binding in .d.ts files under skipLibCheck/noCheck (raising lazyListMaxText to 256 MiB so large generated .d.ts files like Doom's 53.5 MB module.d.ts skip unused type alias bodies). - Add a direct literal-index fast path for tuple indexed-access types and small Set/BSet mapped-tuple fast paths in the checker. - Raise TS2589 tailCount and instantiationDepth caps on wasm to match recursive type-level workloads. - Run wasm-opt -O3 with bulk-memory and SIMD enabled in build-wasm-tinygo.mjs when available.
teamchong
force-pushed
the
perf/checker-memory-and-wasm-speedups
branch
from
October 29, 2026 01:10
23c7823 to
4eb3a60
Compare
…hints, and lazy table lookup - Skip permissive/restrictive instantiation and duplicate assignability checks in getConditionalType when checkType and extendsType contain no type variables. - Fast-return non-generic types in instantiateTypeWithAlias and primitive results in CompositeTypeMapper.Map, and use SimpleTypeMapper for single-parameter tail-recursive conditionals. - Cache the last-matched parameter index in ArrayTypeMapper.Map and InferenceTypeMapper.Map. - Reuse a singleton emptyTypeLiteralType for empty anonymous type literals. - Support binary-searched single-member lazy parsing/binding (LookupLazyMember) and deferred keyof for large binary-keyed .d.ts type literal tables.
teamchong
force-pushed
the
perf/checker-memory-and-wasm-speedups
branch
from
October 29, 2026 02:11
4eb3a60 to
cb6d5dc
Compare
This was referenced Oct 29, 2026
Draft
Draft
Draft
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Explore how far Go (
tsgo) can be pushed in binary size, peak memory, and typechecking speed across both WebAssembly (wasm32-wasip1) and native CLI (darwin-arm64), and measure what gap remains against a native Rust port (tsrs, commite906c9e4) after matching checker algorithms and data layouts.Benchmark Results
1. Native CLI (
darwin-arm64, Apple Silicon, 3 Interleaved Runs)tsgoBaseline (main)tsgo-opt(This PR)tsrs(Default /--fast)tsgo-optvsmaintsgo-optvstsrs67 MB .d.ts)--singleThreaded67 MB .d.ts)--api, fuel 12288)--singleThreaded--singleThreaded--fast, 50.16B, 1,276 MiB)--fast, 56.35B, 1,511 MiB)--fast--singleThreaded--fast, 18.09B, 438 MiB)--fast, 21.34B, 579 MiB)--singleThreaded--fast, 12.84B, 231 MiB)--fast, 15.32B, 287 MiB)--singleThreaded--fast, 8.44B, 167 MiB)--fast, 10.15B, 244 MiB)--singleThreaded--fast, 8.77B, 205 MiB)--fast, 12.10B, 336 MiB)--fast, 1.40x RSS2. WebAssembly (
wasm32-wasip1, Node 24 WASI, 3 Interleaved Runs viatools/wasm/bench.mjs)tsgo-go127.wasm(Go 1.27)tsgo-tinygo-fast.wasm(This PR, TinyGo + LLVM)tsrs.wasm(Ruste906c9e4)tsgo-optvs Go 1.27tsgo-optvstsrs.wasm3. Doom Type-Level Wasm Runtime (12 Chunks, Node.js V8, Persistent
WebAssembly.Instance)tsgo-opt(This PR)tsgo-opt(This PR, TinyGowasm32-wasip1,9.89 MiB/3.51 MiBgz)tsrs(wasm32-wasip1,13.8 MiB)tsgoBaseline (GOOS=js GOARCH=wasm, Go 1.27)Toolchain & Runtime Analysis (
tsgovstsrs)Where
tsgobeatstsrswasm32-wasip1):tsgo-tinygo-fast.wasm(9.89 MiB(3.51 MiBgzipped)) beatstsrs.wasm(13.8 MiB) across all 4 benchmarks (0.91x4-project warm geomean, 9% faster, 24% smaller), and completesopenclaw-full(2.98M LoC) in8.22s(1.81 GiB) wheretsrs.wasmhits the 4 GiB Wasm32 address-space limit. Because TinyGo uses 32-bit pointers onwasm32and compiles through LLVM, Go's pointer-size and codegen disadvantages disappear on Wasm.tsgo-optbeatstsrsdefault onopenclaw-full(2.14svs2.50s, 14% faster),openclaw-core(0.66svs0.83s, 20% faster), anddoom-cold(1.11svs1.13s, 2% faster).Why stock TinyGo failed on
tsgobefore, and what we changed to make it workEarlier attempts to compile
tsgowith TinyGo (e.g. upstreamtypescript-goTinyGo experiments) ran into three layers of blockers: Go 1.26wasip1stdlib incompatibilities, 32-bit pointer/intassumptions intsc, and pathological runtime/allocator behavior in stock TinyGo 0.43.0 on compiler workloads. Commit 8 (aefc84a48a) fixes all of them viatools/scripts/testdata/tinygo-overlayandtools/scripts/build-wasm-tinygo.mjs:tsgoaefc84a48a)wasip1stdlib &os/exec(*poll.FD).WaitWritemissing in TinyGo'sfd_wasip1.gofor Go 1.26+net/fd_unix.go;fd_wasip1.gocallstime.AfterFunc/runtime_netpoll_*under-scheduler=none;os/execunsupported onwasm;testing.Testing()panics duringbundled.goinit)WaitWriteand guardedwait/parkUntilwithtask.OnSystemStack()intinygo-overlay/src/internal/poll/fd_wasip1.go, splitcontentmapper/hostimpl_{wasm,notwasm}.go, and guardedbundled.goinit-scheduler=asyncify& 64 KiB stack defaultwasip1.jsondefaults to"scheduler": "asyncify"and 64 KiBwasm-ldstack; Asyncify instruments every call frame (2-3x slowdown & binary bloat), and a 64 KiB stack overflows on deep AST/type recursion-scheduler=none -ldflags="-extldflags '-z stack-size=16777216'"(16 MiB linear stack), and gated goroutine spawns inorchestrator.go,program.go, andfswatch/debounce.goonGOOS == "wasip1" || GOARCH == "wasm"uintptr/intoverflows intsclazySentinelCap = 1 << 30overflows 32-bit byte slice length inunsafe.Slice;(uintptr(unsafe.Pointer(flow)) * 0x9e3779b97f4a7c15) >> 53overflows 32-bituintptrinflow.golazySentinelCapto1 << 28intsc/internal/ast/lazylist.goand widened pointer hashes touint64(uintptr(...))before shifting intsc/internal/checker/flow.gogrowHeap()int32overflow & doubling OOM at >2 GiBgrowHeap()inarch_tinygowasm.gomultiplieswasm_memory_size(0) * wasmPageSizein signedint32(overflowing at 2 GiB) and doublesmemorySizein onewasm_memory_growcall (failing once the heap passes 2 GiB onopenclaw-full)growHeap()intinygo-overlay/src/runtime/arch_tinygowasm.goto castwasm_memory_sizetouintptrbefore multiplying and cap growth steps at 64 MiB (1,024 pages) with halving backoff up to 65,535 pages-gc=leakinggc_leaking.gonever reuses freed slices/maps and leavesHeapAlloc = 0inReadMemStats, so every resized slice and map bucket leaks permanently andcheckerpool.go's--maxMemoryrecycling never triggerstinygo-overlay/src/runtime/gc_leaking.gowith a size-classed free-list allocator (freeLists[257]up to 2 KiB + 256-entry large-block ring buffer + 4 KiB tiny slab) and wiredgcLiveAlloc/gcTotalAllocintoruntime.ReadMemStatssliceGrowinslice.gorounds every slice growth up to the next power of 2 (1 << bits.Len(newCap)), wasting up to 50% of slice memorytinygo-overlay/src/runtime/slice.goto use 1.5x growth foroldCap >= 256hashmaprehash leaks & indirect-value churnhashmap.goheap-allocates ahashmapIterator+key/valuebuffers on everyhashmapGrow, never freesoldBucketsor overflow buckets, and allocates a new indirect value buffer on every map overwrite whenvalueSize > 128tinygo-overlay/src/runtime/hashmap.goto walk and freeoldBuckets+ overflow chains in-place on rehash, reuse existing indirect value slots on map overwrite, and skiphashmapClearwhenm.count == 0tsc/cmd/tsc/wasm_wasip1.go(matchingtsrs.wasm's zero-preopentsrs_host.fs/tsrs_host.fs_takebatch VFS ABI) and post-linked withwasm-opt -O3 --enable-simd --enable-bulk-memoryWhy a single-threaded native CLI (
1.20x-1.37xwall,1.08x-1.37xinst) and native RSS (1.26x-1.91x) gap remainsBoth upstream
tsgoandtsrsuseunsafeextensively (tsgousesunsafe.Pointerheader downcasts policed bytools/customlint/unsafelayout.go;tsrshas 498unsafesites across 83 files for'staticlifetime erasure, manualunsafearena slab deallocation,repr(C)tagged-header casts, 31unsafe impl Send/Syncdeclarations, and a 32 GiBmmappointer-compression region). After matching checker algorithms and struct layouts, the remaining gap on 64-bit native CLI comes down to three structural differences between Go (cmd/compile+ 64-bit Go runtime) and Rust + LLVM:P<T>compressed pointers + 12BThinSlice/PackedStrvs 64-bit Go pointers (8B) and 24B slice headers:In
tsrs,P<T>is a 4-byteNonZeroU32handle storing(addr - BASE) >> 3into a process-wide 32 GiBmmapreservation, transparently dereferenced viaunsafepointer arithmetic (*(BASE + (handle << 3))) insideimpl Deref for P<T>(cutting peak RSS by 14-15% at the cost of +6.5% instructions), alongside 12-byteThinSlice/PackedStrheaders. In 64-bit Go, every*Type,*Symbol, and*Nodeis 8 bytes,stringis 16 bytes, and[]*Tis 24 bytes (ptr, len, cap).mapruntime calls vs inlined SIMDhashbrown+FxHash:While this PR replaced the checker's hottest internal maps (
Relation.results,typeCacheTable,StructuredType.MemberIndex,FlowMemo,inferMemoEntries,unionFrontSlots) with custom flat open-addressed tables,SymbolTable(type SymbolTable map[string]*Symbol) andcachedTypesstill call out-of-line intoruntime.mapaccess1_faststr/runtime.mapaccess2_fast64(~3-4% of total CPU), whereas Rust'shashbrowninlines 16-byte NEON group probes with a single-multiply hash.cmd/compilecodegen, PGO/BOLT, & GC write barriers vs LLVM:Go's compiler (
cmd/compile) emits GC write barriers (runtime.gcWriteBarrier2) on every pointer store into arena structs, inserts stack-split checks in non-leaf function prologues, and enforces an 80-node AST inline budget (where any non-inlined slow-path call consumes 57 of the 80 points), whereastsrscompiles through LLVM with fat LTO, 1 codegen unit, PGO, andmimalloc.Can this remaining native gap be closed in Go?
darwin-arm64,linux-amd64) has no compressed-pointer mode (amd64p32was removed in Go 1.15), and even an LLVM backend on a 64-bit target cannot automatically rewrite 64-bit*Type/*Symbol/*Nodepointers and 24-byte[]*Tslice headers into 32-bitmmap-relative offsets or inline Go runtimemapcalls.impl Deref for P<T>lets 4-byteP<T>handles keept.flags/s.decl_headerfield syntax, Go lacksDeref,->, and[]operator overloading (as well as macros). Closing the remaining gap in 64-bit Go therefore requires a source-wide refactor across ~150K LoC:*Type,*Symbol, and*Nodepointers with 4-byteuint32handles (P[T]or arena IDs) and rewrite every field access (t.flags,s.declHeader,n.Kind) into explicit.Get()calls. Making arena structs pointer-free (noscan) also eliminates GC pointer scanning andruntime.gcWriteBarrier2write barriers on all arena mutations.type SymbolTable map[string]*Symbol(table[name],table[name] = sym,for k, v := range tableacross ~400 call sites inast,binder,checker,emit, andls) with a custom flat open-addressed struct (table.Get(name),table.Set(name, sym)).What Changed
1. Conditional Flow, Relation, & Inference Memoization
NodeFlagsCondFlowClean+TypeNodeLinks.resolvedFlowType+filterPossiblyReferencedTypeParameters):TrueType, unnamedMappedType, orParameterDeclarationancestor up to the enclosing statement setast.NodeFlagsCondFlowClean(1 << 29onnode.Flags) and returngetTypeFromTypeNodeWorker(node)directly without touchingc.typeNodeLinks.resolvedFlowTypeinTypeNodeLinksoncelen(c.typeResolutions) == c.resolutionStart.getTypeFromConditionalTypeNodeandgetObjectTypeInstantiationbatchisTypeParameterPossiblyReferencedviafilterPossiblyReferencedTypeParameters, walking the subtree once instead of once per outer type parameter.getSimplifiedConditionalTypeWorker,getConditionalTypeInstantiationEx,instantiateMappedType):getSimplifiedConditionalTypeWorkerchecks whetherrawTrue(d.root.node.TrueType) is a non-reverse-mappedObjectType(ObjectFlagsAnonymous | ObjectFlagsClassOrInterface | ObjectFlagsMapped) whenactualCheckis not anObjectType, returningtimmediately without allocatingconditionalTypeExtraor instantiatingTrueType/FalseType. WhenfalseTypeis notnever, it checksgetActualTypeVariable(falseType) == actualCheckbefore instantiatingTrueType(cutting 504K instantiations, 126K symbols, and 84K types oncapnwebvstsrs).getConditionalTypeInstantiationEx, reused already-instantiatedcheckType/extendsTypeinnewConditionalType(withpackedSingleTypeKeycaching onroot.instantiationsfor 1-parameter conditional roots), and fast-pathedgetPermissiveInstantiation/getRestrictiveInstantiationwhen!c.couldContainTypeVariables(t).packedSingleTypeKeycaching ininstantiateMappedTypefor 1-parameter homomorphic mapped types (t.AsObjectType().instantiations), and cachedfirstParamTypeinline onSignaturealongsideextra.paramTypesinsignatureExtrato avoid transientast.Symbolallocations ingetTypeOfSignatureParameterSymbol.flowMemo(tsc/internal/checker/flow.go,tsc/internal/checker/flowmemo.go) caches puregetTypeAtFlowNodesub-walks across references, using a 64-bit pointer hash ((uint64(uintptr(unsafe.Pointer(flow)))*0x9e37_79b9_7f4a_7c15)>>62 == 0) to sample 1/4 of flow nodes without alignment-bit collisions.flowReachableSlotstable (probed only onFlowFlagsSharednodes) replaces per-filemap[uintptr]boolallocations andclear()loops inisReachableFlowNodeWorker/isPostSuperFlowNodeWorker.someGenericTypeWithUnionConstraintskips walking union constituents ingetNarrowableTypeForReferencewhenObjectFlagsCouldContainTypeVariablesComputedis set withObjectFlagsCouldContainTypeVariables == 0.inferFromTypes(tsc/internal/checker/inference.go) packsInferenceKeyinto auint64and memoizes pure multi-step inference walks (inferMemoSteps >= 16).InferenceState.visiteduses an epoch-cleared flat open-addressed probe table with inline 16-slot storage (visitedInline).acquireConditionalInferenceContext/releaseConditionalInferenceContext) andInferenceInfostructs are pooled and recycled whenever their mapper does not escape (!context.escaped).inferFromProperties,inferFromSignatures,inferFromIndexTypes, andapplyToParameterTypescheckcouldContainTypeVariableson target property/parameter/this/rest types and non-empty target signatures/indexInfos before resolving or instantiating source members/signatures.sourceBuf [1]*TypeandmatchedSourcesBuf/matchedTargetsBuf [8]*TypeininferFromTypes/inferFromMatchingTypes, andtypeCacheIsReducibleIntersectionComputedcaching onType.cacheFlagsinisReducibleIntersection, eliminatinguniqueLiteralFilledInstantiationonIntersectionType(96B -> 88B).tsc/internal/checker/relater.go,tsc/internal/checker/checker.go):simpleRelationMapslots from 12 bytes to 8 bytes (uint64: 54-bit(source.id, target.id)key + five 2-bit relation slots, upgrading togenericResultsonly when variance/overflow reporting bits are set), cuttingsimpleRelationMapmemory by 33% and using a single 64-bit multiply hash.simpleRelationMap.getso 1st-probe hits/misses inline directly intoRelation.get.isTypeSubsetOfUnion,getSortOrderFlagsbounds checks incontainsType, andcontainsRegularLiteralType, eliminatingCompareTypesbinary searches when relating unions.isSimpleTypeRelatedTointo an inline fast path for enum-literal equality andTypeFlagsStructuredOrInstantiablepairs, and inlinedgetRecursionIdentityTargetby breaking mutual recursion withgetRecursionIdentityTargetSlow.relaterStackEntry.bloom) andrecursionIdIntersectionsentinel for intersection types onr.sourceStack/r.targetStackinisDeeplyNestedOnStack, avoidinge.t/e.targetpointer dereferences on stack scan misses.2. Lazy Anonymous & Mapped Member Queries & Compact AST / Symbol / Type Layouts
MemberLayout/LazyState, & anonymous/mapped queries (tsc/internal/checker/checker.go,tsc/internal/checker/types.go,tsc/internal/checker/relater.go):SignatureFlagsUninstantiatedParametersandSignatureFlagsUninstantiatedThisParameterdefer symbol slice andthisParameterinstantiation ininstantiateSignatureExuntilgetSignatureParameters/getSignatureThisParameteris queried, eliminating 97.6% of signature parameter symbol slice allocations acrosscapnweb,strada, andopenclaw-core.MemberLayout/LazyState(MemberLayout/LazyState) and mapped-type member resolution withsymbolListArena,memberInfoListArena, pointer-freememberInfoMap map[string]int32(skipped forInterfaceType.declaredMemberLayoutcaching for 0-baseType generics, and 128-bit packedCacheHashKeyfor 1-baseType generics.getPropertiesOfTypeLazily/getLayoutPropertiesOfType,getMemberOfStructuredTypeEx,everyPropertyOfStructuredType,isEmptyObjectType,getSignaturesOfStructuredType, andgetIndexInfosOfStructuredTypeto answer queries on unresolved instantiated anonymous types (ObjectFlagsAnonymous,d.target != nil) and homomorphic mapped types (getLazyMappedTable(t)) alongsideMemberLayout/LazyStatewithout eagerly resolving all members or instantiating signatures.signatureListArena,indexInfoListArena,typeListArena,valueSymbolExtraArena,symbolListArena,memberInfoListArena) onChecker, stack-bufferedTupleNormalizer(typesBuf/infosBuf [8]), and fastfindMixinsinresolveIntersectionTypeMembers.Kind/typeKindswitches (tools/scripts/generate-go-ast.ts,tsc/internal/ast/ast.go,tsc/internal/checker/types.go):switch n.Kindaccessors (Node.Data,DeclarationData,ExportableData,LocalsContainerData,FunctionLikeData,ClassLikeData,BodyData,LiteralLikeData,TemplateLiteralLikeData,Name,Modifiers,SetModifiers,Text,Expression,TypeArguments,TypeParameterList,MemberList,StatementList,Type,SetType,Initializer,SetInitializer,Type.Target,Type.Mapper) with[kindFlowReduceLabelData + 1]uint8byte-offset tables indexing directly into the concrete node struct, and kept(*Node).Text,(*Node).Expression,(*Node).MemberList,(*Node).StatementList, and(*Node).Statementswithin Go's 80-node inline budget.SymbolWithData,checkFlagsMerged, &NameResolverfast paths (tsc/internal/ast/symbol.go,tsc/internal/binder/nameresolver.go,tsc/internal/checker/checker.go):declLen(15 bits),declCap(15 bits), andLazyState(2 bits) into the 4-byte alignment padding ofSymbol(declHeader uint32), addedSymbol.FirstDeclaration(), and mergedSymbol.dataandSymbolData.ParentintoSymbol.parentOrData(withCheckFlagsSharedDataanddeclHeader = lazyStateMaskfor shared/instantiated symbols) so(*Symbol).Members()and(*Symbol).Exports()inline within Go's 80-node budget, shrinkingSymbolWithDatafrom 80B to 64B (1 cache line) and removing an interior pointer per symbol.checkFlagsMerged(1 << 27) onast.SymbolsogetMergedSymbolinlines and skipsc.mergedSymbols[symbol]map lookups for the 99.9% of symbols that are never keys inc.mergedSymbols.valueSymbolLinks.Get(symbol).resolvedTypefast path at the entry ofgetTypeOfSymbol, and inlinedgetReducedType,couldContainTypeVariables(pre-settingObjectFlagsCouldContainTypeVariablesComputedon non-TypeFlagsStructuredOrInstantiabletypes innewType),resolveStructuredTypeMembers,getSingleBaseForNonAugmentingSubtype(settingObjectFlagsIdenticalBaseTypeExiststo skipc.cachedTypesmap probes when no equivalent base exists), andisTypeReferenceWithGenericArguments.MappedSymbolLinks(keyTypeandsyntheticOrigin) intoValueSymbolLinks(extra.writeTypeandtarget), eliminatingChecker.mappedSymbolLinks(symbolLinkStore[MappedSymbolLinks]).KindBlockinNameResolver.Resolve, skippedgetIsDeferredContexton non-value lookups, skipped redundantClassDeclaration/ClassExpression/InterfaceDeclarationMembers()lookups whenmeaning&ast.SymbolFlagsTypeParameter == 0 || len(location.TypeParameters()) == 0, and deduplicatedmoduleExports[name]lookups onKindSourceFile/KindModuleDeclaration.SymbolTablemaps ingetMembersSlow,getExportsSlow, andGetLocalsfrom AST member/property/parameter/statement counts so large interfaces, classes, object literals, enums, and functions do not repeatedly rehash during binding.CompositeBase, compactFlowNode, & 0-collision keyword scanner (tsc/internal/ast/ast.go,tsc/internal/ast/flow.go,tsc/internal/scanner/scanner.go):CompositeBaseis a zero-sizedstruct{}(0B on all nodes), movingSubtreeFactsto a 64-shard side map (subtreeFactsShards) and shrinking 46 AST node types by 8B on 64-bit Go; 28 hot AST node kinds are markedarena: true.FlowNode.Antecedent(*FlowNodeon non-labels) andFlowNode.Antecedents(*FlowListonFlowFlagsLabelnodes) into a single pointer field (Antecedent *FlowNode), shrinkingFlowNodefrom 32B to 24B.textToKeywordmap[string]ast.Kindlookups inGetIdentifierTokenandIdentifierToKeywordKindwith a 0-collision 512-entry direct-mapped table (keywordTable) and replaced the per-bytescanASCIIWhileclosure inscanIdentifierwith a 256-byteisASCIIIdentParttable.ObjectType,ConditionalType,SignatureLinks,Type, &MemberIndex(tsc/internal/checker/types.go,tsc/internal/checker/checker.go,tsc/internal/checker/links.go,tsc/internal/core/arena.go,tsc/internal/core/linkstore.go):StructuredTypefields (members,properties,callSignatures,constructSignatures,indexInfos,objectTypeWithoutAbstractConstructSignatures) into an on-demandstructuredTypeExtraside struct, keepingStructuredTypeat 40B and shrinking everyObjectType,TypeReference,InterfaceType,TupleType, andMappedTypeby 32B.ConditionalTypefields intoconditionalTypeExtra, shrinking everyConditionalTypefrom 72B to 32B.effectsSignatureanddecoratorSignatureintoeffectsOrDecoratorSignature, shrinkingSignatureLinksfrom 24B to 16B.Type.objectFlagsandType.cacheFlagsinline into the 4-byte padding slot onTypeBase(24BTypeBaseheader) and packed 16-bit hash tags into the upper 16 bits ofMemberIndex.slots(24B to 16B).Checker.nodeIdGenacrosssignatureLinks,symbolNodeLinksandtypeNodeLinksandChecker.symbolIdGenacrossvalueSymbolLinkswith 1,024-ID block allocation (BlockIdSize = 1024withBlockIdOffsetfolded into block allocation soNodeIdGenerator.GetNodeIdandSymbolIdGenerator.GetSymbolIdinline),indexedArenaPageShift = 10(32 KiB blocks<= _MaxSmallSize), direct-pagePagedValueLinkStoreforsymbolNodeLinks,core.LinkStorefor sparse symbol link stores, and split page-allocation slow paths out ofPagedLinkStore,PagedValueLinkStore,nodeLinkStore,directNodeLinkStore, andsymbolLinkStore.tsc/internal/checker/checker.go,tsc/internal/checker/jsx.go):checkObjectLiteral,createJsxAttributesTypeFromAttributesProperty, andcreateSymbolWithTypenow allocate 24-byteSymbolheaders viac.newSharedDataSymbol(member)inc.symbolArenainstead of 64-byteSymbolWithDatacopies inc.symbolWithDataArena.instantiateSymbolskips allocating transientSymbolandValueSymbolLinksrecords for uninstantiated parameter symbols (SymbolFlagsFunctionScopedVariable) whose type node isNodeFlagsCondFlowCleanand structurally free of type variables (isTypeVariableFreeParameter), checkslinks.target's resolved type beforecombineTypeMappers.globalObjectAugmentFilter) over the members ofObject,Function,CallableFunction, andNewableFunctionso property misses ingetPropertyOfTypeWorkerandgetUnionOrIntersectionPropertySourcesskip fallback probes and signature checks on misses; addedgetResolvedPropertyOfPlainObjectTypeandgetMemberWithHashfast paths for resolved plain objects.len(indexInfos) == 0before materializing string-literal key types ingetApplicableIndexInfoForName,getTypeFromIndexInfosOfContextualType, andgetContextualTypeForObjectLiteralElement; cached constituentTypeFlagsfor unions/intersections withmaybeTypeOfKind..d.tsmember lists, one-shot CLISuspendGC, & union/instantiation table pooling (tsc/internal/ast/lazylist.go,tsc/internal/parser/parser.go,tsc/internal/binder/binder.go,tsc/internal/checker/checker.go,tsc/internal/execute/tsc.go):.d.tsinterface/class/type-literal member list parsing (parser.EnableLazyDts) andcore.SuspendGC()(debug.SetGCPercent(-1)whentesting == nil && MaxMemory == nil) in one-shot CLIperformCompilationandperformIncrementalCompilation(never in LSP, watch mode, or API), matchingtsrsleaking per-thread arenas in one-shot CLI mode so Go does not repeatedly mark-scan live arenas that live until process exit.unionFrontSlotscache (getUnionTypeEx), 128-bitpackedTypeListKeyWithAliasfast paths forkeyBuilderstack frames out ofinstantiateTypeWithAlias,getRelationKey,getIndexedAccessKey,getTypeListKey,getUnionKey,getIntersectionKey,getTypeInstantiationKey,getConditionalTypeKey,getAliasKey, andgetUnionFrontKey.firstTypeCacheSlot) ontypeCacheTable, allocated grown small tables fromc.typeCacheSlotsArena, pooled grown slot slices acrossc.unionTypes/c.intersectionTypes/c.indexedAccessTypesviac.freeTypeCacheSlots, and skipped self-instantiation insertions onInterfaceType,TupleType, andObjectType.ast.SymbolTablemaps up to 4,096 entries acrossresolveAnonymousTypeMembers,resolveMappedTypeMembers,createInstantiatedSymbolTable,instantiateSymbolTable,getSpreadType,tryMergeUnionOfObjectTypeAndEmptyObject,createEmptyObjectTypeFromStringLiteral, andcreateJsxAttributesTypeFromAttributesProperty.Arena[T]chunks,GOMAXPROCScap, & Wasmtinygobuild (tsc/internal/core/arena.go,tsc/internal/compiler/checkerpool.go,tsc/cmd/tsc/wasm_wasip1.go,tools/scripts/build-wasm-tinygo.mjs):Arena.NewandArena.NewSlicepassinglen(a.data)instead ofcap(a.data)tonextArenaSize, raisedArena[T]steady-state chunk ceiling to 32 KiB (1024elements), and cappedruntime.GOMAXPROCStocheckerCountduringforEachCheckerGroupDo.tsc/cmd/tsc/wasm_wasip1.go,tools/scripts/testdata/tinygo-overlay, andtools/scripts/build-wasm-tinygo.mjsto compiletscwith TinyGo (-opt=2 -gc=leaking -scheduler=none) +wasm-opt -O3 --enable-simd --enable-bulk-memory, producing a 9.90 MiB.wasmbinary.d.tsAST cache, watermark heap reset, lazyTypeAliasDeclarationRHS, & tuple/conditional fast paths (tsc/cmd/tsc/wasm_wasip1.go,tsc/internal/compiler/host.go,tsc/internal/ast/lazylist.go,tsc/internal/parser/parser.go,tsc/internal/checker/checker.go,tsc/internal/checker/mapper.go,tools/scripts/testdata/tinygo-overlay):.d.tsSourceFiles acrosstsrs_runcalls and rewound the bump-allocator heap to a post-init watermark (runtime.MarkHeap/runtime.ResetHeap), scopingsync.Pool,sync.Once, andsync.Maptoruntime.PoolEpoch()and restoring forced lazy member/type-alias state so one Wasm instance serves repeated runs with zero static.d.tsre-parsing and flat memory..d.tsTypeAliasDeclarationRHS parsing/binding under--skipLibCheck/--noCheck(raisinglazyListMaxTextto 256 MiB) and added binary-searched single-member lazy lookup (LookupLazyMember) + deferredkeyoffor large binary-keyed.d.tstype literal tables.tryFastTupleIndexedAccess), smallSet/BSetmapped-tuple fast paths (tryFastMappedTupleSet), non-generic fast paths ingetConditionalType/getPermissiveInstantiation/getRestrictiveInstantiation/instantiateTypeWithAlias/CompositeTypeMapper.Map, 1-parameterSimpleTypeMapperingetTailRecursionRoot, and last-hit index hints inArrayTypeMapperandInferenceTypeMapper. that beatstsrs.wasmon all 4 of 4 cold and warm benchmarks and on both 3-project and 4-project warm geomeans.