diff --git a/AGENTS.md b/AGENTS.md index 2e2ed55..9677f7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ mangled string → Demangler → Node tree → NodePrinter → human-readable st - **`Node`** (`Node.swift`) — Immutable tree node (reference type, `Sendable`). Uses a unified `Payload` enum that merges contents (`.text`/`.index`/`.none`) and children (`.oneChild`/`.twoChildren`/`.manyChildren`) into a single discriminated union — contents and children are mutually exclusive. Mutation methods are `fileprivate`; external code must use `NodeBuilder`. - **`Node.Children`** (`Node.Children.swift`) — Inline storage for 0–2 children without heap allocation; falls back to `ContiguousArray` for 3+. - **`NodeBuilder`** (`Node.swift`) — Thread-safe builder for constructing `Node` trees incrementally (uses `os_unfair_lock`). -- **`Node.create()`** (`Node+Init.swift`) — Public static factories that go through `NodeCache.shared` for leaf-node interning. Always use these instead of `Node.init()` when creating nodes that should be cached. +- **`Node.create()`** (`Node+Init.swift`) — Public static factories that go through `NodeCache.shared` for leaf-node interning. Always use these instead of `Node.init()` when creating nodes that should be cached. The `@_spi(Internals)` `Node.createTransient(...)` counterparts never touch the cache — use them (together with `demangleAsNodeTransient`, which also accepts a `symbolicReferenceResolver`) in pipelines that must not pin anything in global state, such as symbolic-reference resolvers and store-feeding bulk demangling. - **`NodeCache` / `NodeFactory`** (`NodeFactory.swift`) — `NodeCache` is the global interning cache with two levels: leaf nodes are interned eagerly at creation time, and whole trees are hash-consed bottom-up via `intern(_:)` / `internTreeUnsafe(_:)` (structurally equal subtrees collapse to one shared instance; interior-node keys compare children by `===`, which is safe because children are canonicalized before their parent). `demangleAsNode` runs the tree-interning pass by default (`internsSubtrees: true`), so identical symbols demangle to the identical (`===`) tree; opt out with `internsSubtrees: false`. `NodeFactory` provides pre-created singletons for common parameterless nodes (e.g., `NodeFactory.emptyList`, `.asyncAnnotation`). The `Node.init(...)` convenience initializers in `NodeFactory.swift` are **internal** and bypass the cache — they exist for `Demangler`/`Remangler` internals. - **`Node.Kind`** (`Node+Kind.swift`) — Exhaustive enum of ~300 node kinds matching the Swift compiler's `Demangle::Node::Kind`. - **`Demangler`** (`Demangler.swift`) — Generic over `Collection`. Parses mangled prefixes `_T0`, `_$S`, `_$s`, `$S`, `$s`, `$e`, `_$e`, `@__swiftmacro_`. @@ -48,7 +48,11 @@ mangled string → Demangler → Node tree → NodePrinter → human-readable st - **`DemangleOptions`** (`DemangleOptions.swift`) — `OptionSet` with presets: `.default`, `.simplified`, `.interface`, `.interfaceType`, etc. - **`TypeDecoder`** (`TypeDecoder.swift`) — Walks a `Node` tree and builds abstract types via the `TypeBuilder` protocol. - **`Node.Rewriter`** (`Node+Rewriter.swift`) — Open class for bottom-up tree rewriting. Override `visit(_:)` to transform nodes. -- **`Node` as `Sequence`** (`Node+Sequence.swift`) — `Node` conforms to `Sequence` with preorder traversal as default. Also provides `.inorder()`, `.postorder()`, `.levelorder()`. Sequence extensions add `first(of:)`, `all(of:)`, `contains(_:)` by `Node.Kind`. +- **Traversal** (`Store/DemanglingNode+Sequence.swift`, `Node+Sequence.swift`) — the traversal machinery (`preorder`/`inorder`/`postorder`/`levelorder`), the kind-lookup helpers (`first(of:)`, `all(of:)`, `contains(_:)`, `filter(of:)` on `Sequence where Element: DemanglingNode`), and `identifier` are single generic implementations shared by both representations; `Node` and `NodeReference` each conform to `Sequence` with preorder as the default. Do not re-add `Node`-specific copies. +- **`NodeStore` / `NodeStoreBuilder` / `NodeReference`** (`Store/`) — Arena-based compact storage for bulk demangling (evolution proposal 0001; design notes and measurements in `Documentations/NodeStoreArena.md`). Nodes are flat 12-byte `CompactNode` values referenced by `UInt32` indices in one contiguous buffer; the `~Copyable` builder hash-conses on insert and `consuming freeze()` produces an immutable `Sendable` store. Interning tables are open-addressing slot arrays holding 4-byte indices (keys recovered from the buffers — no separate key storage). The builder's `demangle(_:)` bridge is fully cache-free (Phase 3): the transient tree is built with `internsLeaves: false`, so nothing touches `NodeCache.shared`. `intern(kind:...)` overloads construct nodes directly in the arena (wrapper `.type` nodes for index keys, etc.). `NodeReference` is a 16-byte value handle mirroring `Node` accessors (kind/text/index/children), plus `textUTF8` (zero-copy string-table bytes), allocation-free `isIdentifier`/`isSwiftModule` witnesses, `structurallyEquals(_ node: Node)` (zero-materialization cross-representation structural equality matching `Node.==` — the bridge for finding an externally demangled `Node` among `NodeReference` dictionary keys, since the frozen store drops its intern tables), `structurallyEquals(_ other: NodeReference)` (same-store O(1) via index equality, cross-store structural walk) plus `structuralHash(into:)` (structure-consistent hashing for value types that key dictionaries by node structure while storing references — `NodeReference`'s intrinsic `Hashable` is store-identity based), `NodeReference(interning:)` (interns one `Node` tree into a fresh private mini store — self-contained handles for values that outlive their source tree), and a `CustomStringConvertible` debug dump (materialization bridge). `demangleAsNodeTransient` is exported via `@_spi(Internals)` for bulk indexers that classify on the transient tree before interning it (the returned tree is NOT canonical). `materialize()` rebuilds a standalone (non-`NodeCache`) `Node` tree with an index-keyed memo, so store-level subtree sharing survives as shared instances instead of expanding the DAG. +- **`DemanglingNode` / generic engines** (`Store/DemanglingNode.swift`, `Node/Printer/NodePrinter.swift`, `Main/TypeDecoder/TypeDecoder.swift`) — read-only tree protocol conformed by both `Node` and `NodeReference` (members named to match `Node`'s API so generic engine bodies are representation-agnostic). Engines: `DemanglingPrinter` behind the public `NodePrinter` facade (store printing is **zero materialization**, byte-identical to the `Node` path across the full dyld-cache corpus), and `TypeDecoderEngine` behind `TypeDecoder` (the public `TypeBuilder` protocol still receives concrete `Node` at the five handoff points via `materializedNode`). The `Remangler` deliberately stays a `Node` engine — its walk constructs transient helper nodes with shared substitution state (same design as the C++ remangler) — so `mangleAsString(some DemanglingNode)` bridges through `materializedNode`. The derived helpers (`isSimpleType`, `needSpaceBeforeType`, `hasChildren`, `subscript(throwChild:)`, `isIdentifier(desired:)`, `isSwiftModule`, `isKind(of:)`, and `second` on `DemanglingNodeChildren`) live **only** on the `DemanglingNode` protocol/extension — do not re-add copies on `Node` or `Node.Children`: the generic engines dispatch to the shared implementation, so a parallel concrete copy would silently drift. `NodePrintContext.node` stays a concrete `Node?` (store path passes `name as? Node` → nil; harmless — no rich target reads it on the store path). `NodePrinterTarget.pushTypeReferenceScope` takes its node as `@autoclosure () -> Node?`: scope-ignoring targets (`String`, the default implementation) never evaluate it, keeping store-backed plain-text printing allocation-free, while rich targets (e.g. `SemanticString`) evaluate it and receive `materializedNode`, materializing only the nominal-reference subtree. Note the delivered node is canonical **only on the `Node` path**: store-backed printing builds a fresh non-interned subtree per evaluation, so two pushes of the same store index are not `===`. Rich targets must key scopes by structure (e.g. the remangled string, as `SemanticString` does) — never by `ObjectIdentifier`/`===`. `DemanglingPrinter` and `StackSafeExecutor` are exported via `@_spi(Internals)` for deep consumers (MachOSwiftSection rich targets). +- **`StackSafeExecutor`** (`Utils/StackSafeExecutor.swift`) — the stack-safety wrapper every recursive entry point (demangle / remangle / print) goes through. `currentThreadHasSufficientStack` requires 2MB of *remaining* stack, while a Swift Concurrency cooperative worker and a libdispatch worker each get a 512KB stack **in total** — so off the main thread the large-stack branch is taken unconditionally, for every call. Two layers keep that affordable: (a) `LargeStackThreadPool` reuses long-lived 8MB-stack workers (created on demand, retired after a 30s idle timeout) instead of creating and joining a `Thread` per call — this covers demangle, remangle and print alike, with no engine changes; a worker never re-submits into the pool (it runs on 8MB, so nested calls take the inline branch), so a saturated pool cannot deadlock. (b) `executeWithinStackBudget(budgetedAttempt:unbudgetedFallback:)` runs the recursion inline on the current thread and only falls back to a worker when it actually approaches the stack end — the budgeted attempt gets a `stackFloorAddress` (thread stack base + 64KB margin) and returns `nil` to give up. The **print** and **remangle** paths are wired into (b); both probe the real stack pointer at their existing convergence points (`printName` / `mangle(_:depth:)`) rather than counting frames, because per-frame size varies by `Target` and optimization level. **Stack safety lives inside the engine, not at the call site**: use `DemanglingPrinter.print(_:options:)` (or the `NodePrinter` facade's), which wraps (b) itself — it is `static` because the fallback needs a pristine printer and a `mutating printRoot` that already gave up cannot re-run itself. `printRoot(_:)` / `printRootWithinStackBudget(_:stackFloorAddress:)` remain as low-level entries for callers that know they have headroom. Do NOT go back to wrapping `printRoot` in `StackSafeExecutor` at the call site: that was the old convention and MachOSwiftSection's `printSemantic` silently lost the protection by forgetting it (and then silently missed the budget gain the same way). A partial result is discarded wholesale, so residue in `target`/`printCache`/`buffer` never escapes. Remangling being `throws(ManglingError)` there is a typed-throws overload of (b): a thrown error means the *tree* is bad and propagates immediately, only a `nil` return (budget exhausted) retries on a worker. `Demangler` deliberately takes (a) only, matching upstream — its main parse loop is **not recursive descent** (`parseAndPushNames()` is a `while` loop over an explicit `nameStack`), which is why upstream `Demangler.cpp` has no depth limit either. A call-graph analysis of its 160 methods found only 21 in any cycle, none on the main loop: `demangleBoundGenericArgs`, `setParentForOpaqueReturnTypeNodesImpl` ↔ `getParentId`, and a 19-method `demangleSwift3*` component (the Swift 3 mangling *is* recursive descent). Do NOT wire a new engine into (b) without a convergence point covering *every* recursion path: incomplete coverage trades a slow-but-safe call for an overflow. Measurements and the full rationale: `Documentations/StackSafeExecution.md`. +- **`Demangler` construction seam** (`Main/Demangle/Demangler+NodeCreation.swift`) — every node the demangler builds goes through `createNode(...)` instance methods; `internsLeaves: false` (used by the internal `demangleAsNodeTransient`) bypasses `NodeCache.shared` entirely. New construction sites in `Demangler` must use `createNode(...)`, never `Node.create(...)` directly. ### Node Identity vs Equality @@ -82,6 +86,7 @@ Sources/Demangling/ Main/TypeDecoder/ — TypeDecoder, TypeBuilder protocol Node/ — Node, Node.Children, NodeBuilder, NodeCache, Kind, Conversions, Sequence, Rewriter Node/Printer/ — NodePrinter, NodePrinterTarget protocol, NodePrintContext/State + Store/ — CompactNode, NodeStore, NodeStoreBuilder, NodeReference, DemanglingNode (evolution proposal 0001) Enums/ — SugarType, ManglingFlavor, DemanglingError, ManglingError, etc. Utils/ — Extensions, Common constants, Punycode Tests/DemanglingTests/ diff --git a/Documentations/NodeStoreArena.md b/Documentations/NodeStoreArena.md new file mode 100644 index 0000000..3a27530 --- /dev/null +++ b/Documentations/NodeStoreArena.md @@ -0,0 +1,140 @@ +# NodeStore:Arena 式紧凑节点存储 + +日期:2026-07-27 + +对应提案:`evolution/0001-node-store-arena.md`(Phase 1–3 已落地) +前置优化:`Documentations/SubtreeInterning.md`(全子树 hash-consing) + +## 动机 + +`SubtreeInterning.md` 结尾把「Arena + 索引式存储」列为被推迟的方向,本次即是它的兑现。 + +全子树 hash-consing 把 49k 符号语料的解析驻留从 39.5 MB 压到 12.9 MB,但剩下的开销已经无法在 class 形态下继续消除: + +- `Node` 是 `final class`,每实例 48 字节 —— 其中 16 字节是对象头,41 字节的实际内容还要向上取整到 48 字节的 malloc 桶; +- 每个节点一次独立 malloc,每次传递一次引用计数增减; +- 孩子引用是 8 字节指针。 + +对照 C++ `Demangle::Node`:24 字节/节点 + bump allocator。**差距全部来自 class 这个形态本身**,不是实现细节。 + +把 `Node` 改成带 `[Node]` 的 struct 并不能解决:children 数组仍然是逐节点堆分配(32 字节缓冲头 + CoW 引用计数),再加上大 struct 的拷贝成本,内存和 CPU 双双倒退。struct 的价值在于**精确布局 + 可平铺进连续缓冲**,所以正确形态是 arena + 索引。 + +同时,`Node` 是公共 API,下游(MachOSwiftSection、RuntimeViewer)大量依赖,不能就地重写。因此本方案是**并存**而非替换:新增一套存储层,老的 `Node` 路径行为一字不变。 + +## 范围 + +新增 `Sources/Demangling/Store/`: + +- `CompactNode.swift` — 12 字节平铺节点表示; +- `NodeStore.swift` — 冻结后的不可变存储; +- `NodeStoreBuilder.swift` — `~Copyable` 构建器,插入即 hash-consing; +- `NodeReference.swift` — 16 字节值句柄 + `ChildrenView`; +- `DemanglingNode.swift` — `Node` 与 `NodeReference` 共同遵循的只读树协议。 + +改造既有代码: + +- `Node/Printer/NodePrinter.swift` — printer 引擎泛型化为 `DemanglingPrinter`; +- `Main/TypeDecoder/TypeDecoder.swift` — 泛型化为 `TypeDecoderEngine`; +- `Main/Demangle/Demangler+NodeCreation.swift` — demangler 的节点构造 seam,支持绕开全局缓存。 + +**公共 API 零破坏**:`Node`、`NodeBuilder`、`NodeCache`、`demangleAsNode`、`NodePrinter`、`TypeDecoder`、`TypeBuilder` 的签名与行为全部保持。 + +## 关键设计 + +### 12 字节的节点 + +```swift +struct CompactNode { // size 12, alignment 4 + var kindAndPayloadKind: UInt16 // bit 0-8: kind 序号;bit 9-11: payloadKind + var payloadWord0: UInt32 + var payloadWord1: UInt32 +} +``` + +两个 payload 字的含义由 `payloadKind` 决定,沿用 `Node.Payload` 既有的互斥不变量(contents 与 children 不共存)。49k 语料实测分布:`oneChild` ~67%、`manyChildren` ~16%、`twoChildren` ~12%、`text` ~5%——**近八成节点只有零到一个孩子**,所以把两个孩子内联进 payload、只让 3+ 孩子外溢到 edges 缓冲,是很划算的选择。 + +每个 store 三块连续缓冲:`nodes`(主 arena,append 即 bump 分配)、`edges`(3+ 孩子节点的孩子索引区段)、`textBytes`(去重后的 UTF-8 字符串表)。孩子引用是 4 字节索引而非 8 字节指针——这正是相对 C++ 24 字节实现的反超点。 + +容量上限:42.9 亿节点、4 GB 字符串表。越界走 `precondition` 失败,不静默截断。 + +### 插入即 hash-consing,intern 表不留在成品里 + +`NodeStoreBuilder` 对每个插入的节点做 hash-consing:结构相等的子树收敛到同一个索引。因为孩子总是先于父节点被 intern,父节点的键可以直接用**已规范化的孩子索引**比较,无需递归结构哈希——与 `NodeCache.internTreeUnsafe` 同一套经典技巧。 + +intern 表本身是三张 open-addressing 槽数组(普通节点 / 3+ 孩子节点 / 文本),**每槽只存 4 字节索引,键按需从平铺缓冲区取回比较**,不为键单独保存一份拷贝。早期用字典(键各自持有 12 字节 compact 值、子索引数组或 `String` 副本)时,intern 表本身在全语料上要占约 10 MB 量级;改为槽数组后降到约 2 MB。 + +`consuming func freeze()` 消费 builder、丢弃全部 intern 表,只留三块缓冲。这带来两个结果: + +- 「构建完成后不可变」由类型系统保证,不再依赖锁和文档契约; +- 冻结后的 store **无法再做哈希查找**——这解释了为什么 `NodeReference` 需要 `structurallyEquals` 这类线性比较 API(见下文)。 + +### 构建流程:cache-free 临时树,而不是直写 arena + +提案原稿设想让 demangler 直接写入 arena。实际落地保留了 `Node` 作为中间的「第一代空间」: + +1. `demangleAsNodeTransient` 以 `internsLeaves: false` 解析,产出的临时 `Node` 树完全不碰 `NodeCache.shared`; +2. `builder.intern(tree)` 自底向上把可达节点 intern 进 arena —— **去重与垃圾回收是同一个 pass**; +3. 临时树失去引用即被 ARC 回收。 + +之所以没做直写:demangler 的解析逻辑(回填、substitution 复用)建立在引用语义上,改为索引式直写等于重写全部约 594 个构造点。而实测表明这一层临时成本可以忽略——234k 符号语料上构建期常驻增量仅 9.9 MB(≈ 留存 + 约 1 MB 瞬态),且构建耗时反而**快于** interning 的 `Node` 路径。 + +为此,demangler 的全部节点构造点收敛到 `createNode(...)` 这一个 seam 上。**新增构造点必须走 `createNode(...)`,不能直接调 `Node.create(...)`**,否则 cache-free 契约会被悄悄破坏。同理,用户提供的 symbolic reference resolver 需要用 `@_spi(Internals) Node.createTransient(...)` 构造回填节点——否则一启用 symbolic reference,批量管线的 cache-free 成果当场失效。 + +### 一套引擎,两种表示 + +`DemanglingNode` 是 `Node` 和 `NodeReference` 共同遵循的只读树协议(`kind`/`text`/`index`/`children` 加一个抽象的 `printCacheIdentity`)。成员名刻意与 `Node` 现有 API 一致,这样泛型引擎的函数体不论特化到哪种表示都是同一份代码。 + +据此,2000 行以上的 printer 引擎泛型化为 `DemanglingPrinter`,`TypeDecoder` 泛型化为 `TypeDecoderEngine`,公共 `NodePrinter` / `TypeDecoder` 退化为薄包装。store 路径的打印是**零物化**的:全量 dyld cache 语料 × 3 套选项,store 打印与 `Node` 打印逐字节零差异。 + +派生辅助(`isSimpleType`、`needSpaceBeforeType`、`isIdentifier(desired:)`、`isSwiftModule`、`isKind(of:)`、`children.second` 等)**只保留在协议扩展上**。它们是扩展成员而非协议要求,泛型引擎内部静态派发恒走扩展版本——在具体类型上再留一份拷贝不会被引擎调用,只会静默漂移。遍历机制(preorder/inorder/postorder/levelorder、`first(of:)`/`all(of:)`/`contains`)同理,已收敛为单一泛型实现。 + +**`Remangler` 刻意保持 `Node` 引擎**。审计确认它的遍历过程中节点构造是承重的:`getUnspecialized` 剥掉泛型后要把结果回流给 `mangle`,SIL box 布局要构造 wrapper,两者都共享 substitution 状态——这与 C++ remangler 的设计同构。对这样一个逐字节对齐关键的组件做「无构造」重设计不划算,因此 `mangleAsString(some DemanglingNode)` 经 `materializedNode` 桥接。remangle 的输出本来就是新分配的 `String`,属于瞬态成本,与常驻内存目标无关。 + +### 读路径的零分配细节 + +`NodeReference.textUTF8` 暴露字符串表字节的零拷贝 `ArraySlice` 视图。printer 的 sugar 检测热路径(判断 Swift module、Optional/Array/Dictionary)此前每次检查都要构造一个 `String`;升级为协议要求 `isIdentifier(desired:)` / `isSwiftModule` 并由 `NodeReference` 以字节比较见证后,store 路径上这些检查不再分配。非 ASCII 的比较目标回退到 `String` 比较,以保持 Unicode 规范等价语义。 + +### 跨表示的相等与哈希 + +冻结后 intern 表已丢弃,无法哈希查找,于是提供三个显式 API: + +- `structurallyEquals(_ node: Node)` —— 零物化的跨表示结构相等,语义对齐 `Node.==`;服务「手里有一棵外部 demangle 出来的 `Node`,要在 `NodeReference` 字典键中找到它」这个场景; +- `structurallyEquals(_ other: NodeReference)` —— 同 store 直接比索引即得答案(hash-consing 使索引相等 ⇔ 结构相等,O(1)),跨 store 才走双树遍历; +- `structuralHash(into:)` —— 与上述结构相等自洽的结构哈希。 + +**为什么不能直接用固有的 `Hashable`**:`NodeReference.hash(into:)` 组合的是 `ObjectIdentifier(store)` + 索引,是 store 身份基底的。跨 store 的结构相等键会被劈成两个桶。那些「按节点结构做字典键、内部却存 reference」的下游值类型,需要的正是这个可显式调用的结构哈希构件。 + +## 取舍与影响面 + +- **物化出来的树不是 canonical**。`materialize()` 重建的是一棵全新的、不进 `NodeCache` 的 `Node` 树。因此 `===` 共享假设在这条路径上**不成立**:同一个 store 索引物化两次得到两个不同实例。任何按节点身份(`ObjectIdentifier` / `===`)做关联的消费者,在 store 路径上必须改为按结构关联(例如用 remangle 后的字符串作键)。`NodePrinterTarget.pushTypeReferenceScope` 收到的节点同样受此约束。 + - 物化本身保留 DAG 共享:按索引 memo,同一子树只物化一次并复用实例。否则重度替换共享的符号会被指数展开(SwiftUI `View.Body` 量级即几十万节点),且展开树上按身份键的打印缓存会全部脱靶。 +- **索引只对签发它的 store 有效**。`NodeStore.NodeIndex` 是裸值,不带 store 反向引用。跨 store 混用索引不会被可靠拦截,只会读到无关子树。同时持有多个 builder 时需自行保证不混用。 +- **kind 序号不是序列化格式**。它取自 `Node.Kind.allCases` 中的位置,仅在单次进程运行内稳定。9 位空间共 512 槽,当前 `Node.Kind` 有 373 个 case,余量 139。本项目持续跟进 Apple 工具链的 `Demangle::Node::Kind`,超出时由 `kindsByStoreOrdinal` 的 `precondition` 拦截——注意那是**运行时**失败且位于 lazy static 中,只在首次触及 store 路径时才暴露。Phase 4 的持久化格式必须自带稳定的 kind 映射表。 +- **builder 是单写者**。`~Copyable` 保证了这一点,好处是全程无锁,代价是不能跨线程共享。多线程批量场景应每线程一个 builder,最后合并——合并 API 尚未实现。 +- **`Node` 路径完全不受影响**。默认的 `demangleAsNode` 仍然走 `NodeCache` 的叶 + 全树 interning,行为、输出、身份语义一字未变。 + +## 实测收益 + +Phase 1(49k 符号语料):唯一节点 201,876,与 `NodeCache` 全树 hash-consing 的计数逐一吻合(交叉验证了两套 intern 实现的正确性);平铺存储 3.0 MB(nodes 2.4 + edges 0.43 + text 0.26)。 + +Phase 3 验收(本机 dyld cache SwiftUI 语料 234,232 符号,debug 构建): + +| 指标 | 实测 | 目标 | +|---|---|---| +| 唯一节点 | 619,688 | — | +| 平铺存储 | 8.75 MB(nodes 7.4 + edges 0.75 + text 0.57) | — | +| 每唯一节点 | 14.1 字节 | ≤ 16 | +| 每符号 | 37 字节 | ≤ 64 | +| 构建耗时 | 25.3s(`Node` 路径 28.5s) | ≤ 1.2× 基线 | +| 构建期常驻增量 | 9.9 MB | — | + +下游(MachOSwiftSection)迁移实测:构建管线换成 transient + intern 之后,`NodeCache` 增长归零(此前每镜像 +1.9 万叶节点 / +56 万子树),store 本体 7 MB / 57.9 万唯一节点,`Storage` 释放即整镜像回收。 + +正确性:全量 dyld cache 对齐测试 0 失败;49k 语料 × 3 套打印选项,store 与 `Node` 路径逐字节零差异。 + +## 后续方向(未实施) + +- **Phase 4 — 平铺序列化**:三块缓冲直接二进制序列化 / mmap 加载(接近 memcpy 量级),把整个 dyld cache 的解析结果持久化为符号数据库。需先定义稳定的 kind 映射与格式版本号。 +- **分片并行 store 与终态合并**:配合每线程一个 builder。 +- **`Span` / `UTF8Span`(Swift 6.2)借用视图**:进一步消除 `ArraySlice` 层的开销。 +- **`NodeReference` 层的 `Node.Rewriter` 等价物**:写时拷贝进新 store。 diff --git a/Documentations/README.md b/Documentations/README.md new file mode 100644 index 0000000..e675bd0 --- /dev/null +++ b/Documentations/README.md @@ -0,0 +1,18 @@ +# 文档索引 + +本目录收录内部专题文档:每篇对应一次实质性的架构演进或一块独立的子系统。 + +## 专题 + +- [SubtreeInterning.md](SubtreeInterning.md) — 全子树 interning(hash-consing)内存优化。把 interning 从叶节点扩展到全部子树,49k 符号语料解析驻留 39.5 MB → 12.9 MB。 +- [NodeStoreArena.md](NodeStoreArena.md) — `NodeStore` arena 式紧凑存储。节点平铺进连续缓冲,每节点 12 字节、无对象头、无引用计数;printer 与 TypeDecoder 泛型化后可零物化直读。 +- [StackSafeExecution.md](StackSafeExecution.md) — `StackSafeExecutor` 的线程复用与乐观内联。原本在任何非主线程上每次调用都新建 8 MB 栈线程;改为常驻线程池 + 打印路径栈预算回退后,512 KB 栈线程上的打印提速 7.3 倍。 + +两篇是承接关系:`SubtreeInterning` 把 class 形态下能做的去重做到头,`NodeStoreArena` 兑现了它结尾列为「待将来单独评估」的 arena 方向。 + +## 其他位置的文档 + +- `evolution/` — 演进提案(设计意图 + 决策日志)。`0001-node-store-arena.md` 是 `NodeStoreArena.md` 的提案原文。 +- `docs/AlignmentGaps.md` — 与上游 Swift 编译器 `Demangling` 源码的对齐缺口追踪。 +- `AGENTS.md` / `CLAUDE.md`(仓库根) — 面向编码 agent 的架构速查。 +- `README.md`(仓库根) — 面向使用者的英文说明与用法示例。 diff --git a/Documentations/StackSafeExecution.md b/Documentations/StackSafeExecution.md new file mode 100644 index 0000000..3d4c46e --- /dev/null +++ b/Documentations/StackSafeExecution.md @@ -0,0 +1,127 @@ +# 栈安全执行:从「每次新建线程」到「复用 + 乐观内联」 + +## 动机 + +`StackSafeExecutor` 是本库所有递归入口(demangle / remangle / print)的栈安全包装。它的判断逻辑是: + +```swift +private static let minimumRequiredStackSize = 2 * 1024 * 1024 // 2MB + +private static var currentThreadHasSufficientStack: Bool { + let remainingStackSpace = currentAddress - Int(bitPattern: stackBase) + return remainingStackSpace >= minimumRequiredStackSize +} +``` + +判断的是**当前栈剩余空间是否 ≥ 2 MB**。实测三类线程的栈事实(Apple silicon,macOS 26): + +| 线程 | 栈总大小 | 进入时剩余 | 判断结果 | +|---|---|---|---| +| 主线程 | 8,372,224 (8 MB) | 8,358,432 | 够 → 内联执行 | +| `DispatchQueue.global()` | 536,576 (524 KB) | 536,064 | **不够 → 另起线程** | +| Swift Concurrency 协作线程 | 536,576 (524 KB) | 535,568 | **不够 → 另起线程** | + +后两类线程的**栈总量**(524 KB)就低于阈值(2 MB),因此剩余空间无论如何都不可能达标 —— 判断在这两类线程上**恒为假**。也就是说:只要不在主线程上,每一次 demangle、每一次 remangle、每一次 print 都会新建一条 8 MB 栈的 `Thread`,用信号量等它跑完,然后销毁。 + +这不是边缘情况,而是常态:`SymbolIndexStore` 的索引构建跑在 `DispatchQueue.global()` 上,逐个符号调用 `demangleAsNodeTransient`;任何 `async` 的打印管线都跑在协作线程上。实测单次线程建立/销毁成本约 50 µs: + +| 场景(2000 次,同一符号) | 主线程 | 512 KB 栈线程 | 倍率 | +|---|---|---|---| +| `demangleAsNodeTransient` | 63.9 ms | 162.5 ms | 2.54× | +| `Node.print(using:)` | 20.8 ms | 127.5 ms | **6.12×** | + +打印那一行最能说明问题:打印本体只要约 10 µs,线程开销 53 µs —— **开销是本体的 5 倍**。按一个框架 20 万个符号估算,仅线程建立就是十秒量级的纯浪费。 + +## 改动 + +分两层,互相独立、叠加生效。 + +### 第一层:常驻线程复用(`LargeStackThreadPool`) + +把「每次 `Thread(...)` + `start()` + 销毁」换成一个按需增长、空闲回收的常驻大栈线程池: + +- 提交任务时,仅当**待处理任务数超过空闲 worker 数**才新建线程;竞态下多建一条也无妨,它会在空闲超时(30 秒)后自行退出。 +- worker 拿到任务就执行,执行完回到 `NSCondition` 上等下一个,不销毁。 +- worker 自身跑在 8 MB 栈上,因此它内部的嵌套调用(例如 demangler 调用 remangler 处理 opaque return type)走的是 `currentThreadHasSufficientStack` 的内联分支,**不会**再往池子里提交,从而不存在「池子被占满导致自我死锁」的风险。 + +这一层对 demangle / remangle / print **三条路径同时生效**,且完全不触碰任何递归引擎。 + +### 第二层:乐观内联 + 栈预算回退(`executeWithinStackBudget`) + +线程池把单次成本从 ~50 µs 降到 ~8 µs(信号量往返 + 上下文切换),但这 8 µs 仍然是白付的 —— 绝大多数符号的递归深度只有几十帧,压根不需要 8 MB 栈。 + +于是新增一条乐观路径: + +```swift +StackSafeExecutor.executeWithinStackBudget { stackFloorAddress in + // 在当前线程直接跑,递归逼近 stackFloorAddress 时返回 nil 放弃 +} unbudgetedFallback: { + // 只有放弃了才在大栈 worker 上无限制重跑 +} +``` + +`stackFloorAddress` 由当前线程的栈底加上 64 KB 安全余量算出。打印引擎在**已有的递归收敛点** `DemanglingPrinter.printName` 上做检查 —— 该函数是所有打印递归的必经之路(原本就在那里做 `maxPrintDepth` 判断),因此覆盖是完整的: + +```swift +if didExhaustStackBudget { return nil } // 已放弃:快速退栈 +if stackFloorAddress != 0 { + var stackProbe = 0 + let currentAddress = withUnsafeMutablePointer(to: &stackProbe) { UInt(bitPattern: $0) } + if currentAddress <= stackFloorAddress { + didExhaustStackBudget = true + return nil + } +} +``` + +用**实测栈指针**而不是固定深度阈值,是刻意的选择:每帧占用多少字节随 `Target` 类型(`String` 与 `SemanticString` 的帧大小不同)和优化级别变化,固定深度必然要保守到浪费;直接比地址则无需猜测,且天然适配任何栈大小的线程。 + +放弃时整个 printer 值被丢弃、在大栈上重跑,因此中途写入 `target` 和 `printCache` 的残缺片段不会外泄。 + +### 保护归位:从调用方约定改为引擎内建 + +第二层刚落地时,每个打印入口都要自己写一遍 `executeWithinStackBudget` 的两个闭包。这是**约定式**保护:调用方得记得包,漏一个就是静默失去保护。而这个坑已经踩过一次——MachOSwiftSection 的 `printSemantic` 直接驱动 `DemanglingPrinter`,历史上就没包 `StackSafeExecutor`,深嵌套泛型符号能把它打爆;这次第二层落地时它又一次没跟上,白付跨线程开销。同一个位置栽两回,说明责任放错了地方。 + +于是把保护挪进引擎,成为 `DemanglingPrinter.print(_:options:)`: + +```swift +public static func print(_ root: SomeNode, options: DemangleOptions = .default) -> Target { + StackSafeExecutor.executeWithinStackBudget { stackFloorAddress in + var printer = DemanglingPrinter(options: options) + return printer.printRootWithinStackBudget(root, stackFloorAddress: stackFloorAddress) + } unbudgetedFallback: { + var printer = DemanglingPrinter(options: options) + return printer.printRoot(root) + } +} +``` + +它**必须是 static**:回退要用一个全新的 printer 重跑,而 `printRoot` 是 `mutating`,放弃那次已经污染了实例状态,没法自己重来。 + +`printRoot` / `printRootWithinStackBudget` 保留为低层入口,供已知有栈余量、想自己管理 printer 生命周期的调用方使用;库内四个打印入口全部塌缩成一行转发。`Node.description` 是例外,它走的是私有的 `printNode` 树 dump(debug 用途)而非打印引擎,继续用第一层。 + +### Remangler + +`Remangler` 用完全相同的形状接入(`mangleWithinStackBudget(_:stackFloorAddress:)`),检查点放在它已有的收敛点 `mangle(_:depth:)` 上——那里原本就在做 `maxDepth`(1024,对齐上游 `Remangler.cpp`)判断。放弃时丢弃整个 remangler 值,残缺的 `buffer` 同样不会外泄。因为 remangle 是 `throws(ManglingError)`,`StackSafeExecutor` 相应多一个 typed-throws 重载;其中「抛错」与「预算耗尽」是两回事:抛错说明树本身有问题,直接向上传播而不去 worker 上重跑(重跑只会复现同一个错误),预算耗尽才走回退。 + +## 效果 + +同一台机器、同一符号、2000 次: + +| 场景 | 改动前 | 仅线程池 | 线程池 + 栈预算 | +|---|---|---|---| +| `print` @512 KB 栈线程 | 127.5 ms | 42.4 ms | **17.5 ms** | +| `remangle` @512 KB 栈线程 | — | 160.0 ms | **130.1 ms** | +| `demangle` @512 KB 栈线程 | 162.5 ms | 78.0 ms | 74.8 ms | +| 32 任务并发 6400 次 demangle | 162.7 ms | 101.1 ms | 99.6 ms | + +打印路径提速 **7.3 倍**,且在 512 KB 栈线程上的耗时已低于主线程;remangle 从 1.24 倍主线程耗时回落到 1.00 倍 —— 两条路径的跨线程成本都完全消失。 + +提升幅度的差异完全由「本体耗时」决定:print 本体约 10 µs,线程开销占 84%,所以收益最大;remangle 本体约 65 µs,开销占比小,收益相应就是约 20%。 + +## 影响面与限制 + +- **行为不变**:深递归仍然受保护,只是保护方式从「无条件换线程」变成「先试,不够再换」。`maxPrintDepth`(768 帧,对齐上游 C++ `NodePrinter`)、`Remangler.maxDepth`(1024,对齐上游 `Remangler.cpp`)与 `<>` / `.tooComplex` 语义都保持原样。 +- **`Demangler` 只享受第一层的线程复用**,这是刻意的,且与上游一致:它的主解析路径**根本不是递归下降**——`parseAndPushNames()` 是 `while !scanner.isAtEnd` 配合 `nameStack` 显式栈,嵌套结构靠弹栈拼装而非调用栈,所以上游 `Demangler.cpp` 同样没有任何递归深度限制(有 `depth` 的是 `Remangler` 和 `NodePrinter`,本库两者都已对齐)。对 160 个方法做过调用图分析,真正参与递归的只有 21 个,且都不在主循环上:`demangleBoundGenericArgs`(深度 = 嵌套泛型上下文层数)、`setParentForOpaqueReturnTypeNodesImpl` ↔ `getParentId`(树遍历),以及 19 个 `demangleSwift3*` 组成的互递归团(Swift 3 老 mangling 才是真正的递归下降)。前两类深度很浅;Swift 3 那条路径理论上可被构造的深嵌套老符号打爆栈,但今天 `_T` 前缀符号已基本绝迹。若将来要给它加保护,正确做法是只覆盖这 21 个方法,而不是给全部方法塞一个恒为常数的参数。 +- **非 Darwin 平台**行为不变(整套机制都在 `#if canImport(Darwin)` 内,其他平台一律直接执行)。 +- **回归验证**:MachOSwiftSection 的 `MachOSwiftSectionTests` + `SwiftLayoutTests` 全量对比,改动前后失败集合逐条一致(157 项,均为该分支既有失败);`SwiftDumpTests` / `SwiftPrintingTests` / `MachOSymbolsTests` 共 100 项中除一项既有的快照失败外全部通过。 diff --git a/Package.resolved b/Package.resolved index ffe70d5..8fa9a03 100644 --- a/Package.resolved +++ b/Package.resolved @@ -6,8 +6,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Mx-Iris/FrameworkToolbox", "state" : { - "revision" : "b82281eb8a6ffcb312941c3d06584182837f4ca9", - "version" : "0.7.1" + "revision" : "61178c630fccf746bc2c13835dade9a7dfb79613", + "version" : "0.7.4" + } + }, + { + "identity" : "machokit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MxIris-Reverse-Engineering/MachOKit", + "state" : { + "revision" : "9de6599aaba425632bb38a35cf9acd12a022d8f2", + "version" : "0.51.100" } }, { @@ -24,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-asn1.git", "state" : { - "revision" : "eb50cbd14606a9161cbc5d452f18797c90ef0bab", - "version" : "1.7.0" + "revision" : "a9a5efd40eaf558a2bcd48d64b1d1646be686008", + "version" : "1.7.1" } }, { @@ -51,8 +60,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/p-x9/swift-fileio.git", "state" : { - "revision" : "d589ff3966f9f064574780f527449a946736b989", - "version" : "0.13.0" + "revision" : "e581ae04d4079c675042fa62aeabc5ba7480d3df", + "version" : "0.14.0" } }, { @@ -64,13 +73,22 @@ "version" : "0.2.2" } }, + { + "identity" : "swift-macro-toolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stackotter/swift-macro-toolkit", + "state" : { + "revision" : "d6ed555bb83c9f21a292e9769952e8f52610a6e2", + "version" : "0.9.0" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "f99ae8aa18f0cf0d53481901f88a0991dc3bd4a2", - "version" : "601.0.1" + "revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1", + "version" : "603.0.2" } } ], diff --git a/README.md b/README.md index b41e78b..4f5cf94 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This project is derived from [CwlDemangle](https://github.com/mattgallagher/CwlD - **Decode types** from mangled nodes via a pluggable `TypeBuilder` protocol - **Traverse & rewrite** trees with built-in iterators and `Node.Rewriter` - **Node interning (hash-consing)** via `NodeCache` — structurally equal subtrees share one instance, reducing memory ~4x for whole-binary demangling +- **Compact bulk storage** via `NodeStore` — an arena packing each node into 12 flat bytes with no object header, reference counting, or per-node allocation; printing and type decoding read straight from it without materializing a `Node` tree - Supports all mangling prefixes: `_T0`, `_$S`, `_$s`, `$S`, `$s`, `$e`, `_$e`, `@__swiftmacro_` - Swift 6 strict concurrency — all public types are `Sendable` @@ -240,6 +241,42 @@ For one-off demangling where the cache should not grow, opt out per call: let node = try demangleAsNode(symbol, internsSubtrees: false) ``` +### Bulk Demangling with NodeStore + +When demangling a whole binary and keeping every result, `NodeStore` stores nodes in a flat arena instead of as individual class instances: 12 bytes per node, no object header, no reference counting, no per-node allocation. Build with `NodeStoreBuilder`, then `freeze()` into an immutable, `Sendable` store: + +```swift +var builder = NodeStoreBuilder() +var rootIndices: [NodeStore.NodeIndex] = [] +for symbol in symbols { + rootIndices.append(try builder.demangle(symbol)) +} +let store = builder.freeze() +``` + +Nodes are addressed by `NodeReference`, a 16-byte value handle that mirrors `Node`'s accessors. Printing and type decoding read directly from the arena — no `Node` tree is materialized: + +```swift +let reference = store.reference(at: rootIndices[0]) +let readable = reference.print(using: .default) + +for child in reference.children where child.kind == .identifier { + // `textUTF8` borrows the store's string table without allocating + print(child.text ?? "") +} +``` + +The builder hash-conses on insert, so structurally equal subtrees collapse to one index and `NodeReference` equality is O(1) within a store. This path never touches `NodeCache.shared`, so bulk indexing leaves global state untouched. + +Interop with the `Node` API stays available in both directions — `builder.intern(existingNode)` imports a tree, and `reference.materialize()` rebuilds a standalone one: + +```swift +let index = builder.intern(try demangleAsNode(symbol)) +let node = store.reference(at: index).materialize() +``` + +Measured on a SwiftUI dyld-cache corpus of 234,232 symbols: 619,688 unique nodes in 8.75 MB of flat storage (14.1 bytes per unique node), built no slower than the `Node` path. + ## Acknowledgments - [CwlDemangle](https://github.com/mattgallagher/CwlDemangle) by Matt Gallagher — the original Swift translation of the demangler diff --git a/Sources/Demangling/Main/Demangle/DemangleInterface.swift b/Sources/Demangling/Main/Demangle/DemangleInterface.swift index 1261168..7c15ca3 100644 --- a/Sources/Demangling/Main/Demangle/DemangleInterface.swift +++ b/Sources/Demangling/Main/Demangle/DemangleInterface.swift @@ -42,8 +42,8 @@ public func demangleAsNode(_ mangled: String, isType: Bool = false, symbolicRefe /// - isType: if true, no prefix is parsed and, on completion, the first item on the parse stack is returned. /// - Returns: the successfully parsed result /// - Throws: a SwiftSymbolParseError error that contains parse position when the error occurred. -private func demangleAsNode(_ mangled: C, isType: Bool = false, symbolicReferenceResolver: DemangleSymbolicReferenceResolver? = nil, internsSubtrees: Bool = true) throws(DemanglingError) -> Node where C.Iterator.Element == UnicodeScalar, C.Index: Sendable { - var demangler = Demangler(scalars: mangled) +private func demangleAsNode(_ mangled: C, isType: Bool = false, symbolicReferenceResolver: DemangleSymbolicReferenceResolver? = nil, internsSubtrees: Bool = true, internsLeaves: Bool = true) throws(DemanglingError) -> Node where C.Iterator.Element == UnicodeScalar, C.Index: Sendable { + var demangler = Demangler(scalars: mangled, internsLeaves: internsLeaves) demangler.symbolicReferenceResolver = symbolicReferenceResolver let demangledNode: Node if isType { @@ -58,3 +58,21 @@ private func demangleAsNode(_ mangled: C, isType: Bool } return NodeCache.shared.intern(demangledNode) } + +/// Fully cache-free demangle for transient trees (proposal 0001, Phase 3): +/// neither leaves nor subtrees touch `NodeCache.shared`, so bulk demangling +/// through `NodeStoreBuilder` leaves no trace in global state. +/// +/// Exported via `@_spi(Internals)` for bulk-indexing consumers +/// (MachOSwiftSection's `SymbolIndexStore`) that classify each symbol on the +/// transient tree before interning it into a `NodeStoreBuilder`, so the whole +/// pipeline stays off the global cache. The returned tree is NOT canonical: +/// structurally equal nodes are distinct instances, so `===`-based sharing +/// assumptions and `NodeCache` identity guarantees do not apply. +@_spi(Internals) +public func demangleAsNodeTransient(_ mangled: String, isType: Bool = false, symbolicReferenceResolver: DemangleSymbolicReferenceResolver? = nil) throws(DemanglingError) -> Node { + let demangleBlock: @Sendable () throws(DemanglingError) -> Node = { + try demangleAsNode(mangled.unicodeScalars, isType: isType, symbolicReferenceResolver: symbolicReferenceResolver, internsSubtrees: false, internsLeaves: false) + } + return try StackSafeExecutor.execute(demangleBlock) +} diff --git a/Sources/Demangling/Main/Demangle/Demangler+NodeCreation.swift b/Sources/Demangling/Main/Demangle/Demangler+NodeCreation.swift new file mode 100644 index 0000000..4620122 --- /dev/null +++ b/Sources/Demangling/Main/Demangle/Demangler+NodeCreation.swift @@ -0,0 +1,71 @@ +/// The demangler's node-construction seam (proposal 0001, Phase 3). +/// +/// Every node the demangler builds goes through these instance methods. With +/// `internsLeaves` (the default) they forward to the `Node.create` factories, +/// which intern leaves in `NodeCache.shared` — the historical behavior. With +/// `internsLeaves: false` they construct plain uncached nodes: no global lock +/// traffic and nothing retained once the transient tree is dropped, which is +/// what the `NodeStore` bridge wants. +extension Demangler { + @inline(__always) + func createNode(kind: Node.Kind, contents: Node.Contents = .none, children: [Node] = []) -> Node { + internsLeaves + ? Node.create(kind: kind, contents: contents, children: children) + : Node(kind: kind, contents: contents, children: children) + } + + @inline(__always) + func createNode(kind: Node.Kind, contents: Node.Contents = .none, inlineChildren: Node.Children) -> Node { + internsLeaves + ? Node.create(kind: kind, contents: contents, inlineChildren: inlineChildren) + : Node(kind: kind, contents: contents, inlineChildren: inlineChildren) + } + + @inline(__always) + func createNode(kind: Node.Kind, child: Node) -> Node { + createNode(kind: kind, contents: .none, children: [child]) + } + + @inline(__always) + func createNode(kind: Node.Kind, text: String, child: Node) -> Node { + createNode(kind: kind, contents: .text(text), children: [child]) + } + + @inline(__always) + func createNode(kind: Node.Kind, text: String, children: [Node] = []) -> Node { + createNode(kind: kind, contents: .text(text), children: children) + } + + @inline(__always) + func createNode(kind: Node.Kind, index: UInt64, child: Node) -> Node { + createNode(kind: kind, contents: .index(index), children: [child]) + } + + @inline(__always) + func createNode(kind: Node.Kind, index: UInt64, children: [Node] = []) -> Node { + createNode(kind: kind, contents: .index(index), children: children) + } + + /// Compound: `.type` wrapping a node of `typeWithChildKind` with one child. + func createNode(typeWithChildKind: Node.Kind, childChild: Node) -> Node { + createNode(kind: .type, children: [createNode(kind: typeWithChildKind, children: [childChild])]) + } + + /// Compound: `.type` wrapping a node of `typeWithChildKind` with children. + func createNode(typeWithChildKind: Node.Kind, childChildren: [Node]) -> Node { + createNode(kind: .type, children: [createNode(kind: typeWithChildKind, children: childChildren)]) + } + + /// Compound: Swift stdlib type (`.type` > `kind` > [`.module("Swift")`, `.identifier(name)`]). + func createNode(swiftStdlibTypeKind: Node.Kind, name: String) -> Node { + createNode(kind: .type, children: [createNode(kind: swiftStdlibTypeKind, children: [ + createNode(kind: .module, text: stdlibName), + createNode(kind: .identifier, text: name), + ])]) + } + + /// Compound: Swift builtin type (`.type` > `kind(name)`). + func createNode(swiftBuiltinType: Node.Kind, name: String) -> Node { + createNode(kind: .type, children: [createNode(kind: swiftBuiltinType, text: name)]) + } +} diff --git a/Sources/Demangling/Main/Demangle/Demangler.swift b/Sources/Demangling/Main/Demangle/Demangler.swift index f419083..5235c37 100644 --- a/Sources/Demangling/Main/Demangle/Demangler.swift +++ b/Sources/Demangling/Main/Demangle/Demangler.swift @@ -9,8 +9,15 @@ struct Demangler: Sendable where C: Collection, C.Iterator.Element == Unicode var symbolicReferenceResolver: DemangleSymbolicReferenceResolver? - init(scalars: C) { + /// When false, node construction bypasses `NodeCache.shared` entirely — + /// no leaf interning, no global lock traffic, nothing retained after the + /// tree is dropped. Used by the `NodeStore` bridge, whose trees are + /// transient by design (proposal 0001, Phase 3). + let internsLeaves: Bool + + init(scalars: C, internsLeaves: Bool = true) { self.scanner = ScalarScanner(scalars: scalars) + self.internsLeaves = internsLeaves } static func getManglingPrefixLength(_ scalars: C) -> Int { @@ -116,7 +123,7 @@ extension Demangler { topLevelChildren.append(suffix) } try require(topLevelChildren.count != 0) - return Node.create(kind: .global, children: topLevelChildren) + return createNode(kind: .global, children: topLevelChildren) } package mutating func demangleType() throws(DemanglingError) -> Node { @@ -127,7 +134,7 @@ extension Demangler { return result } - return Node.create(kind: .suffix, text: String(String.UnicodeScalarView(scanner.scalars))) + return createNode(kind: .suffix, text: String(String.UnicodeScalarView(scanner.scalars))) } private mutating func parseAndPushNames() throws(DemanglingError) { @@ -156,16 +163,16 @@ extension Demangler { case "a": return NodeFactory.asyncAnnotation case "A": return NodeFactory.isolatedAnyFunctionType case "b": return NodeFactory.concurrentFunctionType - case "c": return try Node.create(kind: .globalActorFunctionType, child: require(popTypeAndGetChild())) + case "c": return try createNode(kind: .globalActorFunctionType, child: require(popTypeAndGetChild())) case "C": return NodeFactory.nonIsolatedCallerFunctionType - case "i": return try Node.create(typeWithChildKind: .isolated, childChild: require(popTypeAndGetChild())) + case "i": return try createNode(typeWithChildKind: .isolated, childChild: require(popTypeAndGetChild())) case "j": return try demangleDifferentiableFunctionType() - case "k": return try Node.create(typeWithChildKind: .noDerivative, childChild: require(popTypeAndGetChild())) - case "K": return try Node.create(kind: .typedThrowsAnnotation, child: require(popTypeAndGetChild())) - case "t": return try Node.create(typeWithChildKind: .compileTimeLiteral, childChild: require(popTypeAndGetChild())) + case "k": return try createNode(typeWithChildKind: .noDerivative, childChild: require(popTypeAndGetChild())) + case "K": return try createNode(kind: .typedThrowsAnnotation, child: require(popTypeAndGetChild())) + case "t": return try createNode(typeWithChildKind: .compileTimeLiteral, childChild: require(popTypeAndGetChild())) case "T": return NodeFactory.sendingResultFunctionType - case "u": return try Node.create(typeWithChildKind: .sending, childChild: require(popTypeAndGetChild())) - case "g": return try Node.create(typeWithChildKind: .constValue, childChild: require(popTypeAndGetChild())) + case "u": return try createNode(typeWithChildKind: .sending, childChild: require(popTypeAndGetChild())) + case "g": return try createNode(typeWithChildKind: .constValue, childChild: require(popTypeAndGetChild())) default: throw failure } } @@ -193,7 +200,7 @@ extension Demangler { case "A": return try demangleMultiSubstitutions() case "B": return try demangleBuiltinType() case "C": return try demangleAnyGenericType(kind: .class) - case "D": return try Node.create(kind: .typeMangling, child: require(pop(kind: .type))) + case "D": return try createNode(kind: .typeMangling, child: require(pop(kind: .type))) case "E": return try demangleExtensionContext() case "F": return try demanglePlainFunction() case "G": return try demangleBoundGenericType() @@ -204,13 +211,13 @@ extension Demangler { case "D": return try demangleDependentProtocolConformanceRoot() case "I": return try demangleDependentProtocolConformanceInherited() case "O": return try demangleDependentProtocolConformanceOpaque() - case "P": return try Node.create(kind: .protocolConformanceRefInTypeModule, child: popProtocol()) - case "p": return try Node.create(kind: .protocolConformanceRefInProtocolModule, child: popProtocol()) - case "X": return try Node.create(kind: .packProtocolConformance, child: popAnyProtocolConformanceList()) - case "c": return try Node.create(kind: .protocolConformanceDescriptorRecord, child: popProtocolConformance()) - case "n": return try Node.create(kind: .nominalTypeDescriptorRecord, child: require(pop(kind: .type))) - case "o": return try Node.create(kind: .opaqueTypeDescriptorRecord, child: require(pop())) - case "r": return try Node.create(kind: .protocolDescriptorRecord, child: popProtocol()) + case "P": return try createNode(kind: .protocolConformanceRefInTypeModule, child: popProtocol()) + case "p": return try createNode(kind: .protocolConformanceRefInProtocolModule, child: popProtocol()) + case "X": return try createNode(kind: .packProtocolConformance, child: popAnyProtocolConformanceList()) + case "c": return try createNode(kind: .protocolConformanceDescriptorRecord, child: popProtocolConformance()) + case "n": return try createNode(kind: .nominalTypeDescriptorRecord, child: require(pop(kind: .type))) + case "o": return try createNode(kind: .opaqueTypeDescriptorRecord, child: require(pop())) + case "r": return try createNode(kind: .protocolDescriptorRecord, child: popProtocol()) case "F": return NodeFactory.accessibleFunctionRecord default: try scanner.backtrack(count: 2) @@ -220,7 +227,7 @@ extension Demangler { case "K": return NodeFactory.throwsAnnotation case "L": return try demangleLocalIdentifier() case "M": return try demangleMetatype() - case "N": return try Node.create(kind: .typeMetadata, child: require(pop(kind: .type))) + case "N": return try createNode(kind: .typeMetadata, child: require(pop(kind: .type))) case "O": return try demangleAnyGenericType(kind: .enum) case "P": return try demangleAnyGenericType(kind: .protocol) case "Q": return try demangleArchetype() @@ -231,33 +238,33 @@ extension Demangler { case "W": return try demangleWitness() case "X": return try demangleSpecialType() case "Y": return try demangleTypeAnnotation() - case "Z": return try Node.create(kind: .static, child: require(pop(where: { $0.isEntity }))) + case "Z": return try createNode(kind: .static, child: require(pop(where: { $0.isEntity }))) case "a": return try demangleAnyGenericType(kind: .typeAlias) case "c": return try require(popFunctionType(kind: .functionType)) case "d": return NodeFactory.variadicMarker case "f": return try demangleFunctionEntity() case "g": return try demangleRetroactiveConformance() - case "h": return try Node.create(typeWithChildKind: .shared, childChild: require(popTypeAndGetChild())) + case "h": return try createNode(typeWithChildKind: .shared, childChild: require(popTypeAndGetChild())) case "i": return try demangleSubscript() case "l": return try demangleGenericSignature(hasParamCounts: false) - case "m": return try Node.create(typeWithChildKind: .metatype, childChild: require(pop(kind: .type))) - case "n": return try Node.create(typeWithChildKind: .owned, childChild: popTypeAndGetChild()) + case "m": return try createNode(typeWithChildKind: .metatype, childChild: require(pop(kind: .type))) + case "n": return try createNode(typeWithChildKind: .owned, childChild: popTypeAndGetChild()) case "o": return try demangleOperatorIdentifier() case "p": return try demangleProtocolListType() - case "q": return try Node.create(kind: .type, child: demangleGenericParamIndex()) + case "q": return try createNode(kind: .type, child: demangleGenericParamIndex()) case "r": return try demangleGenericSignature(hasParamCounts: true) - case "s": return Node.create(kind: .module, contents: .text(stdlibName)) + case "s": return createNode(kind: .module, contents: .text(stdlibName)) case "t": return try popTuple() case "u": return try demangleGenericType() case "v": return try demangleVariable() case "w": return try demangleValueWitness() - case "x": return try Node.create(kind: .type, child: getDependentGenericParamType(depth: 0, index: 0)) + case "x": return try createNode(kind: .type, child: getDependentGenericParamType(depth: 0, index: 0)) case "y": return NodeFactory.emptyList - case "z": return try Node.create(typeWithChildKind: .inOut, childChild: require(popTypeAndGetChild())) + case "z": return try createNode(typeWithChildKind: .inOut, childChild: require(popTypeAndGetChild())) case "_": return NodeFactory.firstElementMarker case ".": try scanner.backtrack() - return Node.create(kind: .suffix, contents: .text(scanner.remainder())) + return createNode(kind: .suffix, contents: .text(scanner.remainder())) case "$": return try demangleIntegerType() default: try scanner.backtrack() @@ -279,7 +286,7 @@ extension Demangler { } private mutating func demangleIndexAsName() throws(DemanglingError) -> Node { - return try Node.create(kind: .number, contents: .index(demangleIndex())) + return try createNode(kind: .number, contents: .index(demangleIndex())) } private mutating func demangleMultiSubstitutions() throws(DemanglingError) -> Node { @@ -354,25 +361,25 @@ extension Demangler { } try children.append(popFunctionParams(kind: .argumentTuple)) try children.append(popFunctionParams(kind: .returnType)) - let name = Node.create(kind: kind, children: children) - return Node.create(kind: .type, child: name) + let name = createNode(kind: kind, children: children) + return createNode(kind: .type, child: name) } private mutating func popFunctionParams(kind: Node.Kind) throws(DemanglingError) -> Node { let paramsType: Node if pop(kind: .emptyList) != nil { - return Node.create(kind: kind, child: Node.create(kind: .type, child: NodeFactory.tuple)) + return createNode(kind: kind, child: createNode(kind: .type, child: NodeFactory.tuple)) } else { paramsType = try require(pop(kind: .type)) } - return Node.create(kind: kind, children: [paramsType]) + return createNode(kind: kind, children: [paramsType]) } private mutating func getLabel(params: Node, idx: Int) throws(DemanglingError) -> Node { if isOldFunctionTypeMangling { let param = try require(params.children.at(idx)) if let label = param.children.first(where: { $0.kind == .tupleElementName }) { - return Node.create(kind: .identifier, contents: .text(label.text ?? "")) + return createNode(kind: .identifier, contents: .text(label.text ?? "")) } return NodeFactory.firstElementMarker } @@ -451,7 +458,7 @@ extension Demangler { return NodeFactory.labelList } - return Node.create(kind: .labelList, children: isOldFunctionTypeMangling ? children : children.reversed()) + return createNode(kind: .labelList, children: isOldFunctionTypeMangling ? children : children.reversed()) } private mutating func popTuple() throws(DemanglingError) -> Node { @@ -465,20 +472,20 @@ extension Demangler { elementChildren.append(variadicMarker) } if let ident = pop(kind: .identifier), case .text(let text) = ident.contents { - elementChildren.append(Node.create(kind: .tupleElementName, contents: .text(text))) + elementChildren.append(createNode(kind: .tupleElementName, contents: .text(text))) } try elementChildren.append(require(pop(kind: .type))) - tupleChildren.append(Node.create(kind: .tupleElement, children: elementChildren)) + tupleChildren.append(createNode(kind: .tupleElement, children: elementChildren)) } while !firstElem tupleChildren.reverse() } - let root = Node.create(kind: .tuple, children: tupleChildren) - return Node.create(kind: .type, child: root) + let root = createNode(kind: .tuple, children: tupleChildren) + return createNode(kind: .type, child: root) } private mutating func popPack(kind: Node.Kind = .pack) throws(DemanglingError) -> Node { if pop(kind: .emptyList) != nil { - return Node.create(kind: .type, child: NodeFactory.pack) + return createNode(kind: .type, child: NodeFactory.pack) } var firstElem = false var children = [Node]() @@ -487,7 +494,7 @@ extension Demangler { try children.append(require(pop(kind: .type))) } while !firstElem children.reverse() - return Node.create(kind: .type, child: Node.create(kind: .pack, children: children)) + return createNode(kind: .type, child: createNode(kind: .pack, children: children)) } private mutating func popSilPack() throws(DemanglingError) -> Node { @@ -507,7 +514,7 @@ extension Demangler { try children.insert(require(pop(kind: .type)), at: 0) } while !firstElem } - return Node.create(kind: .typeList, children: children) + return createNode(kind: .typeList, children: children) } private mutating func popProtocol() throws(DemanglingError) -> Node { @@ -524,7 +531,7 @@ extension Demangler { let name = try require(pop { $0.isDeclName }) let context = try popContext() - return Node.create(typeWithChildKind: .protocol, childChildren: [context, name]) + return createNode(typeWithChildKind: .protocol, childChildren: [context, name]) } private mutating func popAnyProtocolConformanceList() throws(DemanglingError) -> Node { @@ -537,7 +544,7 @@ extension Demangler { } while !firstElem children.reverse() } - return Node.create(kind: .anyProtocolConformanceList, children: children) + return createNode(kind: .anyProtocolConformanceList, children: children) } private mutating func popAnyProtocolConformance() -> Node? { @@ -557,14 +564,14 @@ extension Demangler { private mutating func demangleRetroactiveProtocolConformanceRef() throws(DemanglingError) -> Node { let module = try require(popModule()) let proto = try require(popProtocol()) - return Node.create(kind: .protocolConformanceRefInOtherModule, children: [proto, module]) + return createNode(kind: .protocolConformanceRefInOtherModule, children: [proto, module]) } private mutating func demangleConcreteProtocolConformance() throws(DemanglingError) -> Node { do { let conditionalConformanceList = try require(popAnyProtocolConformanceList()) let conformanceRef = try pop(kind: .protocolConformanceRefInTypeModule) ?? pop(kind: .protocolConformanceRefInProtocolModule) ?? demangleRetroactiveProtocolConformanceRef() - return try Node.create(kind: .concreteProtocolConformance, children: [require(pop(kind: .type)), conformanceRef, conditionalConformanceList]) + return try createNode(kind: .concreteProtocolConformance, children: [require(pop(kind: .type)), conformanceRef, conditionalConformanceList]) } catch { throw error as! DemanglingError } @@ -584,27 +591,27 @@ extension Demangler { private mutating func demangleDependentProtocolConformanceRoot() throws(DemanglingError) -> Node { let index = try demangleDependentConformanceIndex() let prot = try popProtocol() - return try Node.create(kind: .dependentProtocolConformanceRoot, children: [require(pop(kind: .type)), prot, index]) + return try createNode(kind: .dependentProtocolConformanceRoot, children: [require(pop(kind: .type)), prot, index]) } private mutating func demangleDependentProtocolConformanceInherited() throws(DemanglingError) -> Node { let index = try demangleDependentConformanceIndex() let prot = try popProtocol() let nested = try require(popDependentProtocolConformance()) - return Node.create(kind: .dependentProtocolConformanceInherited, children: [nested, prot, index]) + return createNode(kind: .dependentProtocolConformanceInherited, children: [nested, prot, index]) } private mutating func popDependentAssociatedConformance() throws(DemanglingError) -> Node { let prot = try popProtocol() let dependentType = try require(pop(kind: .type)) - return Node.create(kind: .dependentAssociatedConformance, children: [dependentType, prot]) + return createNode(kind: .dependentAssociatedConformance, children: [dependentType, prot]) } private mutating func demangleDependentProtocolConformanceAssociated() throws(DemanglingError) -> Node { let index = try demangleDependentConformanceIndex() let assoc = try popDependentAssociatedConformance() let nested = try require(popDependentProtocolConformance()) - return Node.create(kind: .dependentProtocolConformanceAssociated, children: [nested, assoc, index]) + return createNode(kind: .dependentProtocolConformanceAssociated, children: [nested, assoc, index]) } private mutating func demangleDependentConformanceIndex() throws(DemanglingError) -> Node { @@ -612,13 +619,13 @@ extension Demangler { if index == 1 { return NodeFactory.unknownIndex } - return Node.create(kind: .index, contents: .index(index - 2)) + return createNode(kind: .index, contents: .index(index - 2)) } private mutating func demangleDependentProtocolConformanceOpaque() throws(DemanglingError) -> Node { let type = try require(pop(kind: .type)) let conformance = try require(popDependentProtocolConformance()) - return Node.create(kind: .dependentProtocolConformanceOpaque, children: [conformance, type]) + return createNode(kind: .dependentProtocolConformanceOpaque, children: [conformance, type]) } private mutating func popModule() -> Node? { @@ -662,9 +669,9 @@ extension Demangler { let id = try require(pop(kind: .identifier)) if let p = proto { - return Node.create(kind: .dependentAssociatedTypeRef, children: [id, p]) + return createNode(kind: .dependentAssociatedTypeRef, children: [id, p]) } else { - return Node.create(kind: .dependentAssociatedTypeRef, child: id) + return createNode(kind: .dependentAssociatedTypeRef, child: id) } } @@ -675,7 +682,7 @@ extension Demangler { firstElem = pop(kind: .firstElementMarker) != nil try assocTypePath.append(require(popAssociatedTypeName())) } while !firstElem - return Node.create(kind: .assocTypePath, children: assocTypePath.reversed()) + return createNode(kind: .assocTypePath, children: assocTypePath.reversed()) } private mutating func popProtocolConformance() throws(DemanglingError) -> Node { @@ -689,13 +696,13 @@ extension Demangler { type = pop(kind: .type) } if let gs = genSig { - type = try Node.create(typeWithChildKind: .dependentGenericType, childChildren: [gs, require(type)]) + type = try createNode(typeWithChildKind: .dependentGenericType, childChildren: [gs, require(type)]) } var children = try [require(type), proto, module] if let i = ident { children.append(i) } - return Node.create(kind: .protocolConformance, children: children) + return createNode(kind: .protocolConformance, children: children) } private mutating func getDependentGenericParamType(depth: Int, index: Int) throws(DemanglingError) -> Node { @@ -710,20 +717,20 @@ extension Demangler { name = "\(name)\(depth)" } - return Node.create(kind: .dependentGenericParamType, children: [ - Node.create(kind: .index, index: UInt64(depth)), - Node.create(kind: .index, index: UInt64(index)), + return createNode(kind: .dependentGenericParamType, children: [ + createNode(kind: .index, index: UInt64(depth)), + createNode(kind: .index, index: UInt64(index)), ]) } private mutating func demangleStandardSubstitution() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "o": return Node.create(kind: .module, contents: .text(objcModule)) - case "C": return Node.create(kind: .module, contents: .text(cModule)) + case "o": return createNode(kind: .module, contents: .text(objcModule)) + case "C": return createNode(kind: .module, contents: .text(cModule)) case "g": - let op = try Node.create(typeWithChildKind: .boundGenericEnum, childChildren: [ - Node.create(swiftStdlibTypeKind: .enum, name: "Optional"), - Node.create(kind: .typeList, child: require(pop(kind: .type))), + let op = try createNode(typeWithChildKind: .boundGenericEnum, childChildren: [ + createNode(swiftStdlibTypeKind: .enum, name: "Optional"), + createNode(kind: .typeList, child: require(pop(kind: .type))), ]) substitutions.append(op) return op @@ -735,78 +742,78 @@ extension Demangler { let nd: Node if secondLevel { switch try scanner.readScalar() { - case "A": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Actor") - case "C": nd = Node.create(swiftStdlibTypeKind: .structure, name: "CheckedContinuation") - case "c": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeContinuation") - case "E": nd = Node.create(swiftStdlibTypeKind: .structure, name: "CancellationError") - case "e": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnownedSerialExecutor") - case "F": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Executor") - case "f": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "SerialExecutor") - case "G": nd = Node.create(swiftStdlibTypeKind: .structure, name: "TaskGroup") - case "g": nd = Node.create(swiftStdlibTypeKind: .structure, name: "ThrowingTaskGroup") - case "h": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "TaskExecutor") - case "I": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "AsyncIteratorProtocol") - case "i": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "AsyncSequence") - case "J": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnownedJob") - case "M": nd = Node.create(swiftStdlibTypeKind: .class, name: "MainActor") - case "P": nd = Node.create(swiftStdlibTypeKind: .structure, name: "TaskPriority") - case "S": nd = Node.create(swiftStdlibTypeKind: .structure, name: "AsyncStream") - case "s": nd = Node.create(swiftStdlibTypeKind: .structure, name: "AsyncThrowingStream") - case "T": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Task") - case "t": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeCurrentTask") + case "A": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Actor") + case "C": nd = createNode(swiftStdlibTypeKind: .structure, name: "CheckedContinuation") + case "c": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeContinuation") + case "E": nd = createNode(swiftStdlibTypeKind: .structure, name: "CancellationError") + case "e": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnownedSerialExecutor") + case "F": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Executor") + case "f": nd = createNode(swiftStdlibTypeKind: .protocol, name: "SerialExecutor") + case "G": nd = createNode(swiftStdlibTypeKind: .structure, name: "TaskGroup") + case "g": nd = createNode(swiftStdlibTypeKind: .structure, name: "ThrowingTaskGroup") + case "h": nd = createNode(swiftStdlibTypeKind: .protocol, name: "TaskExecutor") + case "I": nd = createNode(swiftStdlibTypeKind: .protocol, name: "AsyncIteratorProtocol") + case "i": nd = createNode(swiftStdlibTypeKind: .protocol, name: "AsyncSequence") + case "J": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnownedJob") + case "M": nd = createNode(swiftStdlibTypeKind: .class, name: "MainActor") + case "P": nd = createNode(swiftStdlibTypeKind: .structure, name: "TaskPriority") + case "S": nd = createNode(swiftStdlibTypeKind: .structure, name: "AsyncStream") + case "s": nd = createNode(swiftStdlibTypeKind: .structure, name: "AsyncThrowingStream") + case "T": nd = createNode(swiftStdlibTypeKind: .structure, name: "Task") + case "t": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeCurrentTask") default: throw failure } } else { switch try scanner.readScalar() { - case "a": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Array") - case "A": nd = Node.create(swiftStdlibTypeKind: .structure, name: "AutoreleasingUnsafeMutablePointer") - case "b": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Bool") - case "c": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnicodeScalar") - case "D": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Dictionary") - case "d": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Double") - case "f": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Float") - case "h": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Set") - case "I": nd = Node.create(swiftStdlibTypeKind: .structure, name: "DefaultIndices") - case "i": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Int") - case "J": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Character") - case "N": nd = Node.create(swiftStdlibTypeKind: .structure, name: "ClosedRange") - case "n": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Range") - case "O": nd = Node.create(swiftStdlibTypeKind: .structure, name: "ObjectIdentifier") - case "p": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeMutablePointer") - case "P": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafePointer") - case "R": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeBufferPointer") - case "r": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeMutableBufferPointer") - case "S": nd = Node.create(swiftStdlibTypeKind: .structure, name: "String") - case "s": nd = Node.create(swiftStdlibTypeKind: .structure, name: "Substring") - case "u": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UInt") - case "v": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeMutableRawPointer") - case "V": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeRawPointer") - case "W": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeRawBufferPointer") - case "w": nd = Node.create(swiftStdlibTypeKind: .structure, name: "UnsafeMutableRawBufferPointer") - case "q": nd = Node.create(swiftStdlibTypeKind: .enum, name: "Optional") - case "B": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "BinaryFloatingPoint") - case "E": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Encodable") - case "e": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Decodable") - case "F": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "FloatingPoint") - case "G": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "RandomNumberGenerator") - case "H": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Hashable") - case "j": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Numeric") - case "K": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "BidirectionalCollection") - case "k": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "RandomAccessCollection") - case "L": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Comparable") - case "l": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Collection") - case "M": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "MutableCollection") - case "m": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "RangeReplaceableCollection") - case "Q": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Equatable") - case "T": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Sequence") - case "t": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "IteratorProtocol") - case "U": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "UnsignedInteger") - case "X": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "RangeExpression") - case "x": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "Strideable") - case "Y": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "RawRepresentable") - case "y": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "StringProtocol") - case "Z": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "SignedInteger") - case "z": nd = Node.create(swiftStdlibTypeKind: .protocol, name: "BinaryInteger") + case "a": nd = createNode(swiftStdlibTypeKind: .structure, name: "Array") + case "A": nd = createNode(swiftStdlibTypeKind: .structure, name: "AutoreleasingUnsafeMutablePointer") + case "b": nd = createNode(swiftStdlibTypeKind: .structure, name: "Bool") + case "c": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnicodeScalar") + case "D": nd = createNode(swiftStdlibTypeKind: .structure, name: "Dictionary") + case "d": nd = createNode(swiftStdlibTypeKind: .structure, name: "Double") + case "f": nd = createNode(swiftStdlibTypeKind: .structure, name: "Float") + case "h": nd = createNode(swiftStdlibTypeKind: .structure, name: "Set") + case "I": nd = createNode(swiftStdlibTypeKind: .structure, name: "DefaultIndices") + case "i": nd = createNode(swiftStdlibTypeKind: .structure, name: "Int") + case "J": nd = createNode(swiftStdlibTypeKind: .structure, name: "Character") + case "N": nd = createNode(swiftStdlibTypeKind: .structure, name: "ClosedRange") + case "n": nd = createNode(swiftStdlibTypeKind: .structure, name: "Range") + case "O": nd = createNode(swiftStdlibTypeKind: .structure, name: "ObjectIdentifier") + case "p": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeMutablePointer") + case "P": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafePointer") + case "R": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeBufferPointer") + case "r": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeMutableBufferPointer") + case "S": nd = createNode(swiftStdlibTypeKind: .structure, name: "String") + case "s": nd = createNode(swiftStdlibTypeKind: .structure, name: "Substring") + case "u": nd = createNode(swiftStdlibTypeKind: .structure, name: "UInt") + case "v": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeMutableRawPointer") + case "V": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeRawPointer") + case "W": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeRawBufferPointer") + case "w": nd = createNode(swiftStdlibTypeKind: .structure, name: "UnsafeMutableRawBufferPointer") + case "q": nd = createNode(swiftStdlibTypeKind: .enum, name: "Optional") + case "B": nd = createNode(swiftStdlibTypeKind: .protocol, name: "BinaryFloatingPoint") + case "E": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Encodable") + case "e": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Decodable") + case "F": nd = createNode(swiftStdlibTypeKind: .protocol, name: "FloatingPoint") + case "G": nd = createNode(swiftStdlibTypeKind: .protocol, name: "RandomNumberGenerator") + case "H": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Hashable") + case "j": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Numeric") + case "K": nd = createNode(swiftStdlibTypeKind: .protocol, name: "BidirectionalCollection") + case "k": nd = createNode(swiftStdlibTypeKind: .protocol, name: "RandomAccessCollection") + case "L": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Comparable") + case "l": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Collection") + case "M": nd = createNode(swiftStdlibTypeKind: .protocol, name: "MutableCollection") + case "m": nd = createNode(swiftStdlibTypeKind: .protocol, name: "RangeReplaceableCollection") + case "Q": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Equatable") + case "T": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Sequence") + case "t": nd = createNode(swiftStdlibTypeKind: .protocol, name: "IteratorProtocol") + case "U": nd = createNode(swiftStdlibTypeKind: .protocol, name: "UnsignedInteger") + case "X": nd = createNode(swiftStdlibTypeKind: .protocol, name: "RangeExpression") + case "x": nd = createNode(swiftStdlibTypeKind: .protocol, name: "Strideable") + case "Y": nd = createNode(swiftStdlibTypeKind: .protocol, name: "RawRepresentable") + case "y": nd = createNode(swiftStdlibTypeKind: .protocol, name: "StringProtocol") + case "Z": nd = createNode(swiftStdlibTypeKind: .protocol, name: "SignedInteger") + case "z": nd = createNode(swiftStdlibTypeKind: .protocol, name: "BinaryInteger") default: throw failure } } @@ -886,7 +893,7 @@ extension Demangler { } } while hasWordSubs try require(!identifier.isEmpty) - let result = Node.create(kind: .identifier, contents: .text(identifier)) + let result = createNode(kind: .identifier, contents: .text(identifier)) substitutions.append(result) return result } @@ -907,9 +914,9 @@ extension Demangler { } } switch try scanner.readScalar() { - case "i": return Node.create(kind: .infixOperator, contents: .text(str)) - case "p": return Node.create(kind: .prefixOperator, contents: .text(str)) - case "P": return Node.create(kind: .postfixOperator, contents: .text(str)) + case "i": return createNode(kind: .infixOperator, contents: .text(str)) + case "p": return createNode(kind: .prefixOperator, contents: .text(str)) + case "P": return createNode(kind: .postfixOperator, contents: .text(str)) default: throw failure } } @@ -920,40 +927,40 @@ extension Demangler { case "L": let discriminator = try require(pop(kind: .identifier)) let name = try require(pop(where: { $0.isDeclName })) - return Node.create(kind: .privateDeclName, children: [discriminator, name]) + return createNode(kind: .privateDeclName, children: [discriminator, name]) case "l": let discriminator = try require(pop(kind: .identifier)) - return Node.create(kind: .privateDeclName, children: [discriminator]) + return createNode(kind: .privateDeclName, children: [discriminator]) case "a" ... "j", "A" ... "J": - return try Node.create(kind: .relatedEntityDeclName, children: [ - Node.create(kind: .identifier, contents: .text(String(c))), + return try createNode(kind: .relatedEntityDeclName, children: [ + createNode(kind: .identifier, contents: .text(String(c))), require(pop()), ]) default: try scanner.backtrack() let discriminator = try demangleIndexAsName() let name = try require(pop(where: { $0.isDeclName })) - return Node.create(kind: .localDeclName, children: [discriminator, name]) + return createNode(kind: .localDeclName, children: [discriminator, name]) } } private mutating func demangleBuiltinType() throws(DemanglingError) -> Node { let maxTypeSize: UInt64 = 4096 switch try scanner.readScalar() { - case "A": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.ImplicitActor") - case "b": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.BridgeObject") - case "B": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.UnsafeValueBuffer") - case "e": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.Executor") + case "A": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.ImplicitActor") + case "b": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.BridgeObject") + case "B": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.UnsafeValueBuffer") + case "e": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.Executor") case "f": let size = try demangleIndex() - 1 try require(size > 0 && size <= maxTypeSize) - return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.FPIEEE\(size)") + return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.FPIEEE\(size)") case "i": let size = try demangleIndex() - 1 try require(size > 0 && size <= maxTypeSize) - return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.Int\(size)") - case "I": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.IntLiteral") + return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.Int\(size)") + case "I": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.IntLiteral") case "v": let elts = try demangleIndex() - 1 try require(elts > 0 && elts <= maxTypeSize) @@ -961,25 +968,25 @@ extension Demangler { let text = try require(eltType.text) try require(eltType.kind == .builtinTypeName && text.starts(with: "Builtin.") == true) let name = text["Builtin.".endIndex...] - return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.Vec\(elts)x\(name)") + return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.Vec\(elts)x\(name)") case "V": let element = try require(pop(kind: .type)) let size = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .builtinFixedArray, childChildren: [size, element]) - case "O": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.UnknownObject") - case "o": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.NativeObject") - case "p": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.RawPointer") - case "t": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.SILToken") - case "w": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.Word") - case "c": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.RawUnsafeContinuation") - case "D": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.DefaultActorStorage") - case "d": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.NonDefaultDistributedActorStorage") - case "j": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.Job") - case "P": return Node.create(swiftBuiltinType: .builtinTypeName, name: "Builtin.PackIndex") - case "T": return Node.create(kind: .type, children: [Node.create(kind: .builtinTupleType)]) + return createNode(typeWithChildKind: .builtinFixedArray, childChildren: [size, element]) + case "O": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.UnknownObject") + case "o": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.NativeObject") + case "p": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.RawPointer") + case "t": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.SILToken") + case "w": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.Word") + case "c": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.RawUnsafeContinuation") + case "D": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.DefaultActorStorage") + case "d": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.NonDefaultDistributedActorStorage") + case "j": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.Job") + case "P": return createNode(swiftBuiltinType: .builtinTypeName, name: "Builtin.PackIndex") + case "T": return createNode(kind: .type, children: [createNode(kind: .builtinTupleType)]) case "W": let referent = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .builtinBorrow, childChildren: [referent]) + return createNode(typeWithChildKind: .builtinBorrow, childChildren: [referent]) default: throw failure } } @@ -987,7 +994,7 @@ extension Demangler { private mutating func demangleAnyGenericType(kind: Node.Kind) throws(DemanglingError) -> Node { let name = try require(pop(where: { $0.isDeclName })) let ctx = try popContext() - let type = Node.create(typeWithChildKind: kind, childChildren: [ctx, name]) + let type = createNode(typeWithChildKind: kind, childChildren: [ctx, name]) substitutions.append(type) return type } @@ -997,9 +1004,9 @@ extension Demangler { let module = try require(popModule()) let type = try popTypeAndGetAnyGeneric() if let g = genSig { - return Node.create(kind: .extension, children: [module, type, g]) + return createNode(kind: .extension, children: [module, type, g]) } else { - return Node.create(kind: .extension, children: [module, type]) + return createNode(kind: .extension, children: [module, type]) } } @@ -1034,7 +1041,7 @@ extension Demangler { if visited.children.last?.kind == .opaqueReturnTypeParent { return visited } - return visited.addingChild(Node.create(kind: .opaqueReturnTypeParent, contents: .text(getParentId()))) + return visited.addingChild(createNode(kind: .opaqueReturnTypeParent, contents: .text(getParentId()))) } switch visited.kind { @@ -1064,14 +1071,14 @@ extension Demangler { let labelList = try popFunctionParamLabels(type: type) if let g = genSig { - type = Node.create(typeWithChildKind: .dependentGenericType, childChildren: [g, type]) + type = createNode(typeWithChildKind: .dependentGenericType, childChildren: [g, type]) } let name = try require(pop(where: { $0.isDeclName })) let ctx = try popContext() let result = if let ll = labelList { - Node.create(kind: .function, children: [ctx, name, ll, type]) + createNode(kind: .function, children: [ctx, name, ll, type]) } else { - Node.create(kind: .function, children: [ctx, name, type]) + createNode(kind: .function, children: [ctx, name, type]) } let updatedType = setParentForOpaqueReturnTypeNodes(visited: type, parent: result) if updatedType !== type { @@ -1083,7 +1090,7 @@ extension Demangler { private mutating func demangleRetroactiveConformance() throws(DemanglingError) -> Node { let index = try demangleIndexAsName() let conformance = try require(popAnyProtocolConformance()) - return Node.create(kind: .retroactiveConformance, children: [index, conformance]) + return createNode(kind: .retroactiveConformance, children: [index, conformance]) } private mutating func demangleBoundGenericType() throws(DemanglingError) -> Node { @@ -1095,7 +1102,7 @@ extension Demangler { } else { boundNode } - let type = Node.create(kind: .type, child: finalBoundNode) + let type = createNode(kind: .type, child: finalBoundNode) substitutions.append(type) return type } @@ -1106,7 +1113,7 @@ extension Demangler { retroactiveConformances.append(conformance) } retroactiveConformances = retroactiveConformances.reversed() - return retroactiveConformances.isEmpty ? nil : Node.create(kind: .typeList, children: retroactiveConformances) + return retroactiveConformances.isEmpty ? nil : createNode(kind: .typeList, children: retroactiveConformances) } private mutating func demangleBoundGenerics() throws(DemanglingError) -> (typeLists: [Node], conformances: Node?) { @@ -1119,7 +1126,7 @@ extension Demangler { typeListChildren.append(t) } typeListChildren.reverse() - array.append(Node.create(kind: .typeList, children: typeListChildren)) + array.append(createNode(kind: .typeList, children: typeListChildren)) if pop(kind: .emptyList) != nil { break @@ -1134,7 +1141,7 @@ extension Demangler { private mutating func demangleBoundGenericArgs(nominal: Node, array: [Node], index: Int) throws(DemanglingError) -> Node { if nominal.kind == .typeSymbolicReference || nominal.kind == .protocolSymbolicReference { let remaining = array.reversed().flatMap { $0.children } - return Node.create(kind: .boundGenericOtherNominalType, children: [Node.create(kind: .type, child: nominal), Node.create(kind: .typeList, children: remaining)]) + return createNode(kind: .boundGenericOtherNominalType, children: [createNode(kind: .type, child: nominal), createNode(kind: .typeList, children: remaining)]) } let context = try require(nominal.children.first) @@ -1168,11 +1175,11 @@ extension Demangler { if let thirdChild = context.children.at(2) { extensionChildren.append(thirdChild) } - boundParent = Node.create(kind: .extension, children: extensionChildren) + boundParent = createNode(kind: .extension, children: extensionChildren) } else { boundParent = try demangleBoundGenericArgs(nominal: context, array: array, index: offsetIndex) } - n = Node.create(kind: nominal.kind, children: [boundParent] + nominal.children.dropFirst()) + n = createNode(kind: nominal.kind, children: [boundParent] + nominal.children.dropFirst()) } else { n = nominal } @@ -1191,12 +1198,12 @@ extension Demangler { case .typeAlias: kind = .boundGenericTypeAlias case .function, .constructor: - return Node.create(kind: .boundGenericFunction, children: [n, args]) + return createNode(kind: .boundGenericFunction, children: [n, args]) default: throw failure } - return Node.create(kind: kind, children: [Node.create(kind: .type, child: n), args]) + return createNode(kind: kind, children: [createNode(kind: .type, child: n), args]) } private mutating func demangleImplParamConvention(kind: Node.Kind) throws(DemanglingError) -> Node? { @@ -1219,7 +1226,7 @@ extension Demangler { try scanner.backtrack() return nil } - return Node.create(kind: kind, child: Node.create(kind: .implConvention, contents: .text(attr))) + return createNode(kind: kind, child: createNode(kind: .implConvention, contents: .text(attr))) } private mutating func demangleImplResultConvention(kind: Node.Kind) throws(DemanglingError) -> Node? { @@ -1238,34 +1245,34 @@ extension Demangler { try scanner.backtrack() return nil } - return Node.create(kind: kind, child: Node.create(kind: .implConvention, contents: .text(attr))) + return createNode(kind: kind, child: createNode(kind: .implConvention, contents: .text(attr))) } private mutating func demangleImplParameterSending() -> Node? { guard scanner.conditional(scalar: "T") else { return nil } - return Node.create(kind: .implParameterSending, contents: .text("sending")) + return createNode(kind: .implParameterSending, contents: .text("sending")) } private mutating func demangleImplParameterIsolated() -> Node? { guard scanner.conditional(scalar: "I") else { return nil } - return Node.create(kind: .implParameterIsolated, contents: .text("isolated")) + return createNode(kind: .implParameterIsolated, contents: .text("isolated")) } private mutating func demangleImplParameterImplicitLeading() -> Node? { guard scanner.conditional(scalar: "L") else { return nil } - return Node.create(kind: .implParameterImplicitLeading, contents: .text("sil_implicit_leading_param")) + return createNode(kind: .implParameterImplicitLeading, contents: .text("sil_implicit_leading_param")) } private mutating func demangleImplResultDifferentiability() -> Node { - return Node.create(kind: .implParameterResultDifferentiability, contents: .text(scanner.conditional(scalar: "w") ? "@noDerivative" : "")) + return createNode(kind: .implParameterResultDifferentiability, contents: .text(scanner.conditional(scalar: "w") ? "@noDerivative" : "")) } private mutating func demangleClangType() throws(DemanglingError) -> Node { let numChars = try require(demangleNatural()) let text = try scanner.readScalars(count: Int(numChars)) - return Node.create(kind: .clangType, contents: .text(text)) + return createNode(kind: .clangType, contents: .text(text)) } private mutating func demangleImplFunctionType() throws(DemanglingError) -> Node { @@ -1277,7 +1284,7 @@ extension Demangler { if let conformances { patternChildren.append(conformances) } - typeChildren.append(Node.create(kind: .implPatternSubstitutions, children: patternChildren)) + typeChildren.append(createNode(kind: .implPatternSubstitutions, children: patternChildren)) } if scanner.conditional(scalar: "I") { @@ -1286,7 +1293,7 @@ extension Demangler { if let conformances { invocationChildren.append(conformances) } - typeChildren.append(Node.create(kind: .implInvocationSubstitutions, children: invocationChildren)) + typeChildren.append(createNode(kind: .implInvocationSubstitutions, children: invocationChildren)) } var genSig = pop(kind: .dependentGenericSignature) @@ -1304,7 +1311,7 @@ extension Demangler { if let peek = scanner.peek(), let differentiability = Differentiability(rawValue: peek) { try scanner.skip() - typeChildren.append(Node.create(kind: .implDifferentiabilityKind, contents: .index(UInt64(differentiability.rawValue)))) + typeChildren.append(createNode(kind: .implDifferentiabilityKind, contents: .index(UInt64(differentiability.rawValue)))) } let cAttr: String @@ -1315,7 +1322,7 @@ extension Demangler { case "t": cAttr = "@convention(thin)" default: throw failure } - typeChildren.append(Node.create(kind: .implConvention, contents: .text(cAttr))) + typeChildren.append(createNode(kind: .implConvention, contents: .text(cAttr))) let fConv: String? var hasClangType = false @@ -1344,27 +1351,27 @@ extension Demangler { fConv = nil } if let fConv { - var convChildren = [Node.create(kind: .implFunctionConventionName, contents: .text(fConv))] + var convChildren = [createNode(kind: .implFunctionConventionName, contents: .text(fConv))] if hasClangType { try convChildren.append(demangleClangType()) } - typeChildren.append(Node.create(kind: .implFunctionConvention, children: convChildren)) + typeChildren.append(createNode(kind: .implFunctionConvention, children: convChildren)) } if scanner.conditional(scalar: "A") { - typeChildren.append(Node.create(kind: .implCoroutineKind, contents: .text("yield_once"))) + typeChildren.append(createNode(kind: .implCoroutineKind, contents: .text("yield_once"))) } else if scanner.conditional(scalar: "I") { - typeChildren.append(Node.create(kind: .implCoroutineKind, contents: .text("yield_once_2"))) + typeChildren.append(createNode(kind: .implCoroutineKind, contents: .text("yield_once_2"))) } else if scanner.conditional(scalar: "G") { - typeChildren.append(Node.create(kind: .implCoroutineKind, contents: .text("yield_many"))) + typeChildren.append(createNode(kind: .implCoroutineKind, contents: .text("yield_many"))) } if scanner.conditional(scalar: "h") { - typeChildren.append(Node.create(kind: .implFunctionAttribute, contents: .text("@Sendable"))) + typeChildren.append(createNode(kind: .implFunctionAttribute, contents: .text("@Sendable"))) } if scanner.conditional(scalar: "H") { - typeChildren.append(Node.create(kind: .implFunctionAttribute, contents: .text("@async"))) + typeChildren.append(createNode(kind: .implFunctionAttribute, contents: .text("@async"))) } if scanner.conditional(scalar: "T") { @@ -1410,73 +1417,73 @@ extension Demangler { typeChildren[idx] = try typeChildren[idx].addingChild(require(pop(kind: .type))) } - return Node.create(typeWithChildKind: .implFunctionType, childChildren: typeChildren) + return createNode(typeWithChildKind: .implFunctionType, childChildren: typeChildren) } private mutating func demangleMetatype() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "a": return try Node.create(kind: .typeMetadataAccessFunction, child: require(pop(kind: .type))) - case "A": return try Node.create(kind: .reflectionMetadataAssocTypeDescriptor, child: popProtocolConformance()) - case "b": return try Node.create(kind: .canonicalSpecializedGenericTypeMetadataAccessFunction, child: require(pop(kind: .type))) - case "B": return try Node.create(kind: .reflectionMetadataBuiltinDescriptor, child: require(pop(kind: .type))) - case "c": return try Node.create(kind: .protocolConformanceDescriptor, child: require(popProtocolConformance())) + case "a": return try createNode(kind: .typeMetadataAccessFunction, child: require(pop(kind: .type))) + case "A": return try createNode(kind: .reflectionMetadataAssocTypeDescriptor, child: popProtocolConformance()) + case "b": return try createNode(kind: .canonicalSpecializedGenericTypeMetadataAccessFunction, child: require(pop(kind: .type))) + case "B": return try createNode(kind: .reflectionMetadataBuiltinDescriptor, child: require(pop(kind: .type))) + case "c": return try createNode(kind: .protocolConformanceDescriptor, child: require(popProtocolConformance())) case "C": let t = try require(pop(kind: .type)) try require(t.children.first?.kind.isAnyGeneric == true) - return try Node.create(kind: .reflectionMetadataSuperclassDescriptor, child: require(t.children.first)) - case "D": return try Node.create(kind: .typeMetadataDemanglingCache, child: require(pop(kind: .type))) + return try createNode(kind: .reflectionMetadataSuperclassDescriptor, child: require(t.children.first)) + case "D": return try createNode(kind: .typeMetadataDemanglingCache, child: require(pop(kind: .type))) // Apple's closed toolchain demangler accepts these metatype forms that the open-source // lib/Demangling source omits: lowercase 'd' (same node as 'D') and 'R' // (TypeMetadataMangledNameRef). Real Apple symbols depend on them — see docs/AlignmentGaps.md. - case "d": return try Node.create(kind: .typeMetadataDemanglingCache, child: require(pop(kind: .type))) - case "R": return try Node.create(kind: .typeMetadataMangledNameRef, child: require(pop(kind: .type))) - case "f": return try Node.create(kind: .fullTypeMetadata, child: require(pop(kind: .type))) - case "F": return try Node.create(kind: .reflectionMetadataFieldDescriptor, child: require(pop(kind: .type))) - case "g": return try Node.create(kind: .opaqueTypeDescriptorAccessor, child: require(pop())) - case "h": return try Node.create(kind: .opaqueTypeDescriptorAccessorImpl, child: require(pop())) - case "i": return try Node.create(kind: .typeMetadataInstantiationFunction, child: require(pop(kind: .type))) - case "I": return try Node.create(kind: .typeMetadataInstantiationCache, child: require(pop(kind: .type))) - case "j": return try Node.create(kind: .opaqueTypeDescriptorAccessorKey, child: require(pop())) - case "J": return try Node.create(kind: .noncanonicalSpecializedGenericTypeMetadataCache, child: require(pop())) - case "k": return try Node.create(kind: .opaqueTypeDescriptorAccessorVar, child: require(pop())) - case "K": return try Node.create(kind: .metadataInstantiationCache, child: require(pop())) - case "l": return try Node.create(kind: .typeMetadataSingletonInitializationCache, child: require(pop(kind: .type))) - case "L": return try Node.create(kind: .typeMetadataLazyCache, child: require(pop(kind: .type))) - case "m": return try Node.create(kind: .metaclass, child: require(pop(kind: .type))) - case "M": return try Node.create(kind: .canonicalSpecializedGenericMetaclass, child: require(pop(kind: .type))) - case "n": return try Node.create(kind: .nominalTypeDescriptor, child: require(pop(kind: .type))) - case "N": return try Node.create(kind: .noncanonicalSpecializedGenericTypeMetadata, child: require(pop(kind: .type))) - case "o": return try Node.create(kind: .classMetadataBaseOffset, child: require(pop(kind: .type))) - case "p": return try Node.create(kind: .protocolDescriptor, child: popProtocol()) - case "P": return try Node.create(kind: .genericTypeMetadataPattern, child: require(pop(kind: .type))) - case "q": return try Node.create(kind: .uniquable, child: require(pop())) - case "Q": return try Node.create(kind: .opaqueTypeDescriptor, child: require(pop())) - case "r": return try Node.create(kind: .typeMetadataCompletionFunction, child: require(pop(kind: .type))) - case "s": return try Node.create(kind: .objCResilientClassStub, child: require(pop(kind: .type))) - case "S": return try Node.create(kind: .protocolSelfConformanceDescriptor, child: popProtocol()) - case "t": return try Node.create(kind: .fullObjCResilientClassStub, child: require(pop(kind: .type))) - case "u": return try Node.create(kind: .methodLookupFunction, child: require(pop(kind: .type))) - case "U": return try Node.create(kind: .objCMetadataUpdateFunction, child: require(pop(kind: .type))) - case "V": return try Node.create(kind: .propertyDescriptor, child: require(pop { $0.isEntity })) + case "d": return try createNode(kind: .typeMetadataDemanglingCache, child: require(pop(kind: .type))) + case "R": return try createNode(kind: .typeMetadataMangledNameRef, child: require(pop(kind: .type))) + case "f": return try createNode(kind: .fullTypeMetadata, child: require(pop(kind: .type))) + case "F": return try createNode(kind: .reflectionMetadataFieldDescriptor, child: require(pop(kind: .type))) + case "g": return try createNode(kind: .opaqueTypeDescriptorAccessor, child: require(pop())) + case "h": return try createNode(kind: .opaqueTypeDescriptorAccessorImpl, child: require(pop())) + case "i": return try createNode(kind: .typeMetadataInstantiationFunction, child: require(pop(kind: .type))) + case "I": return try createNode(kind: .typeMetadataInstantiationCache, child: require(pop(kind: .type))) + case "j": return try createNode(kind: .opaqueTypeDescriptorAccessorKey, child: require(pop())) + case "J": return try createNode(kind: .noncanonicalSpecializedGenericTypeMetadataCache, child: require(pop())) + case "k": return try createNode(kind: .opaqueTypeDescriptorAccessorVar, child: require(pop())) + case "K": return try createNode(kind: .metadataInstantiationCache, child: require(pop())) + case "l": return try createNode(kind: .typeMetadataSingletonInitializationCache, child: require(pop(kind: .type))) + case "L": return try createNode(kind: .typeMetadataLazyCache, child: require(pop(kind: .type))) + case "m": return try createNode(kind: .metaclass, child: require(pop(kind: .type))) + case "M": return try createNode(kind: .canonicalSpecializedGenericMetaclass, child: require(pop(kind: .type))) + case "n": return try createNode(kind: .nominalTypeDescriptor, child: require(pop(kind: .type))) + case "N": return try createNode(kind: .noncanonicalSpecializedGenericTypeMetadata, child: require(pop(kind: .type))) + case "o": return try createNode(kind: .classMetadataBaseOffset, child: require(pop(kind: .type))) + case "p": return try createNode(kind: .protocolDescriptor, child: popProtocol()) + case "P": return try createNode(kind: .genericTypeMetadataPattern, child: require(pop(kind: .type))) + case "q": return try createNode(kind: .uniquable, child: require(pop())) + case "Q": return try createNode(kind: .opaqueTypeDescriptor, child: require(pop())) + case "r": return try createNode(kind: .typeMetadataCompletionFunction, child: require(pop(kind: .type))) + case "s": return try createNode(kind: .objCResilientClassStub, child: require(pop(kind: .type))) + case "S": return try createNode(kind: .protocolSelfConformanceDescriptor, child: popProtocol()) + case "t": return try createNode(kind: .fullObjCResilientClassStub, child: require(pop(kind: .type))) + case "u": return try createNode(kind: .methodLookupFunction, child: require(pop(kind: .type))) + case "U": return try createNode(kind: .objCMetadataUpdateFunction, child: require(pop(kind: .type))) + case "V": return try createNode(kind: .propertyDescriptor, child: require(pop { $0.isEntity })) case "X": return try demanglePrivateContextDescriptor() - case "z": return try Node.create(kind: .canonicalPrespecializedGenericTypeCachingOnceToken, child: require(pop(kind: .type))) + case "z": return try createNode(kind: .canonicalPrespecializedGenericTypeCachingOnceToken, child: require(pop(kind: .type))) default: throw failure } } private mutating func demanglePrivateContextDescriptor() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "E": return try Node.create(kind: .extensionDescriptor, child: popContext()) - case "M": return try Node.create(kind: .moduleDescriptor, child: require(popModule())) + case "E": return try createNode(kind: .extensionDescriptor, child: popContext()) + case "M": return try createNode(kind: .moduleDescriptor, child: require(popModule())) case "Y": let discriminator = try require(pop()) let context = try popContext() - return Node.create(kind: .anonymousDescriptor, children: [context, discriminator]) - case "X": return try Node.create(kind: .anonymousDescriptor, child: popContext()) + return createNode(kind: .anonymousDescriptor, children: [context, discriminator]) + case "X": return try createNode(kind: .anonymousDescriptor, child: popContext()) case "A": let path = try require(popAssociatedTypePath()) let base = try require(pop(kind: .type)) - return Node.create(kind: .associatedTypeGenericParamRef, children: [base, path]) + return createNode(kind: .associatedTypeGenericParamRef, children: [base, path]) default: throw failure } } @@ -1486,32 +1493,32 @@ extension Demangler { case "a": let ident = try require(pop(kind: .identifier)) let arch = try popTypeAndGetChild() - let assoc = Node.create(typeWithChildKind: .associatedTypeRef, childChildren: [arch, ident]) + let assoc = createNode(typeWithChildKind: .associatedTypeRef, childChildren: [arch, ident]) substitutions.append(assoc) return assoc case "O": - return try Node.create(kind: .opaqueReturnTypeOf, child: popContext()) + return try createNode(kind: .opaqueReturnTypeOf, child: popContext()) case "o": let index = try demangleIndex() let (boundGenericArgs, retroactiveConformances) = try demangleBoundGenerics() let name = try require(pop()) var opaqueChildren: [Node] = [ name, - Node.create(kind: .index, contents: .index(index)), - Node.create(kind: .typeList, children: boundGenericArgs.reversed()), + createNode(kind: .index, contents: .index(index)), + createNode(kind: .typeList, children: boundGenericArgs.reversed()), ] if let retroactiveConformances { opaqueChildren.append(retroactiveConformances) } - let opaque = Node.create(kind: .opaqueType, children: opaqueChildren) - let opaqueType = Node.create(kind: .type, child: opaque) + let opaque = createNode(kind: .opaqueType, children: opaqueChildren) + let opaqueType = createNode(kind: .type, child: opaque) substitutions.append(opaqueType) return opaqueType case "r": - return Node.create(typeWithChildKind: .opaqueReturnType, childChildren: []) + return createNode(typeWithChildKind: .opaqueReturnType, childChildren: []) case "R": let index = try demangleIndex() - return Node.create(typeWithChildKind: .opaqueReturnType, childChildren: [Node.create(kind: .opaqueReturnTypeIndex, index: index)]) + return createNode(typeWithChildKind: .opaqueReturnType, childChildren: [createNode(kind: .opaqueReturnTypeIndex, index: index)]) case "x": let t = try demangleAssociatedTypeSimple(index: nil) substitutions.append(t) @@ -1539,11 +1546,11 @@ extension Demangler { case "p": let count = try popTypeAndGetChild() let pattern = try popTypeAndGetChild() - return Node.create(kind: .type, child: Node.create(kind: .packExpansion, children: [pattern, count])) + return createNode(kind: .type, child: createNode(kind: .packExpansion, children: [pattern, count])) case "e": let pack = try popTypeAndGetChild() let level = try demangleIndex() - return Node.create(kind: .type, child: Node.create(kind: .packElement, children: [pack, Node.create(kind: .packElementLevel, contents: .index(level))])) + return createNode(kind: .type, child: createNode(kind: .packElement, children: [pack, createNode(kind: .packElementLevel, contents: .index(level))])) case "P": return try popPack() case "S": @@ -1555,8 +1562,8 @@ extension Demangler { private mutating func demangleAssociatedTypeSimple(index: Node?) throws(DemanglingError) -> Node { do { let atName = try popAssociatedTypeName() - let gpi = try index.map { Node.create(kind: .type, child: $0) } ?? require(pop(kind: .type)) - return Node.create(typeWithChildKind: .dependentMemberType, childChildren: [gpi, atName]) + let gpi = try index.map { createNode(kind: .type, child: $0) } ?? require(pop(kind: .type)) + return createNode(typeWithChildKind: .dependentMemberType, childChildren: [gpi, atName]) } catch { throw error as! DemanglingError } @@ -1571,10 +1578,10 @@ extension Demangler { try assocTypeNames.append(popAssociatedTypeName()) } while !firstElem - var base = try index.map { Node.create(kind: .type, child: $0) } ?? require(pop(kind: .type)) + var base = try index.map { createNode(kind: .type, child: $0) } ?? require(pop(kind: .type)) while let assocType = assocTypeNames.popLast() { - let depTy = Node.create(kind: .dependentMemberType, children: [base, assocType]) - base = Node.create(kind: .type, child: depTy) + let depTy = createNode(kind: .dependentMemberType, children: [base, assocType]) + base = createNode(kind: .type, child: depTy) } return base } catch { @@ -1603,13 +1610,13 @@ extension Demangler { switch c { case "T": switch try scanner.readScalar() { - case "I": return try Node.create(kind: .silThunkIdentity, child: require(pop(where: { $0.isEntity }))) + case "I": return try createNode(kind: .silThunkIdentity, child: require(pop(where: { $0.isEntity }))) case "H": throw failure default: throw failure } - case "c": return try Node.create(kind: .curryThunk, child: require(pop(where: { $0.isEntity }))) - case "j": return try Node.create(kind: .dispatchThunk, child: require(pop(where: { $0.isEntity }))) - case "q": return try Node.create(kind: .methodDescriptor, child: require(pop(where: { $0.isEntity }))) + case "c": return try createNode(kind: .curryThunk, child: require(pop(where: { $0.isEntity }))) + case "j": return try createNode(kind: .dispatchThunk, child: require(pop(where: { $0.isEntity }))) + case "q": return try createNode(kind: .methodDescriptor, child: require(pop(where: { $0.isEntity }))) case "o": return NodeFactory.objCAttribute case "O": return NodeFactory.nonObjCAttribute case "D": return NodeFactory.dynamicAttribute @@ -1622,9 +1629,9 @@ extension Demangler { case "X": return NodeFactory.dynamicallyReplaceableFunctionVar case "x": return NodeFactory.dynamicallyReplaceableFunctionKey case "I": return NodeFactory.dynamicallyReplaceableFunctionImpl - case "Y": return try Node.create(kind: .asyncSuspendResumePartialFunction, child: demangleIndexAsName()) - case "Q": return try Node.create(kind: .asyncAwaitResumePartialFunction, child: demangleIndexAsName()) - case "C": return try Node.create(kind: .coroutineContinuationPrototype, child: require(pop(kind: .type))) + case "Y": return try createNode(kind: .asyncSuspendResumePartialFunction, child: demangleIndexAsName()) + case "Q": return try createNode(kind: .asyncAwaitResumePartialFunction, child: demangleIndexAsName()) + case "C": return try createNode(kind: .coroutineContinuationPrototype, child: require(pop(kind: .type))) case "z": fallthrough case "Z": let flagMode = try demangleIndexAsName() @@ -1635,17 +1642,17 @@ extension Demangler { if let sig { children.append(sig) } - return Node.create(kind: c == "z" ? .objCAsyncCompletionHandlerImpl : .checkedObjCAsyncCompletionHandlerImpl, children: children) + return createNode(kind: c == "z" ? .objCAsyncCompletionHandlerImpl : .checkedObjCAsyncCompletionHandlerImpl, children: children) case "V": let base = try require(pop(where: { $0.isEntity })) let derived = try require(pop(where: { $0.isEntity })) - return Node.create(kind: .vTableThunk, children: [derived, base]) + return createNode(kind: .vTableThunk, children: [derived, base]) case "W": let entity = try require(pop(where: { $0.isEntity })) let conf = try popProtocolConformance() - return Node.create(kind: .protocolWitness, children: [conf, entity]) + return createNode(kind: .protocolWitness, children: [conf, entity]) case "S": - return try Node.create(kind: .protocolSelfConformanceWitness, child: require(pop(where: { $0.isEntity }))) + return try createNode(kind: .protocolSelfConformanceWitness, child: require(pop(where: { $0.isEntity }))) case "R", "r", "y": @@ -1663,7 +1670,7 @@ extension Demangler { } try children.append(require(pop(kind: .type))) try children.append(require(pop(kind: .type))) - return Node.create(kind: kind, children: children) + return createNode(kind: kind, children: children) case "g": return try demangleGenericSpecialization(kind: .genericSpecialization) case "G": return try demangleGenericSpecialization(kind: .genericSpecializationNotReAbstracted) case "B": return try demangleGenericSpecialization(kind: .genericSpecializationInResilienceDomain) @@ -1673,7 +1680,7 @@ extension Demangler { case "P", "p": let spec = try demangleSpecAttributes(kind: c == "P" ? .genericPartialSpecializationNotReAbstracted : .genericPartialSpecialization) - let param = try Node.create(kind: .genericSpecializationParam, child: require(pop(kind: .type))) + let param = try createNode(kind: .genericSpecializationParam, child: require(pop(kind: .type))) return spec.addingChild(param) case "f": return try demangleFunctionSpecialization() case "K", @@ -1712,24 +1719,24 @@ extension Demangler { if isSerialized { resultChildren.append(NodeFactory.isSerialized) } - return Node.create(kind: nodeKind, children: resultChildren) - case "l": return try Node.create(kind: .associatedTypeDescriptor, child: require(popAssociatedTypeName())) - case "L": return try Node.create(kind: .protocolRequirementsBaseDescriptor, child: require(popProtocol())) - case "M": return try Node.create(kind: .defaultAssociatedTypeMetadataAccessor, child: require(popAssociatedTypeName())) + return createNode(kind: nodeKind, children: resultChildren) + case "l": return try createNode(kind: .associatedTypeDescriptor, child: require(popAssociatedTypeName())) + case "L": return try createNode(kind: .protocolRequirementsBaseDescriptor, child: require(popProtocol())) + case "M": return try createNode(kind: .defaultAssociatedTypeMetadataAccessor, child: require(popAssociatedTypeName())) case "n": let requirement = try popProtocol() let associatedTypePath = try popAssociatedTypePath() let protocolType = try require(pop(kind: .type)) - return Node.create(kind: .associatedConformanceDescriptor, children: [protocolType, associatedTypePath, requirement]) + return createNode(kind: .associatedConformanceDescriptor, children: [protocolType, associatedTypePath, requirement]) case "N": let requirement = try popProtocol() let associatedTypePath = try popAssociatedTypePath() let protocolType = try require(pop(kind: .type)) - return Node.create(kind: .defaultAssociatedConformanceAccessor, children: [protocolType, associatedTypePath, requirement]) + return createNode(kind: .defaultAssociatedConformanceAccessor, children: [protocolType, associatedTypePath, requirement]) case "b": let requirement = try popProtocol() let protocolType = try require(pop(kind: .type)) - return Node.create(kind: .baseConformanceDescriptor, children: [protocolType, requirement]) + return createNode(kind: .baseConformanceDescriptor, children: [protocolType, requirement]) case "H", "h": let nodeKind: Node.Kind = c == "H" ? .keyPathEqualsThunkHelper : .keyPathHashThunkHelper @@ -1755,20 +1762,20 @@ extension Demangler { if isSerialized { resultChildren.append(NodeFactory.isSerialized) } - return Node.create(kind: nodeKind, children: resultChildren) + return createNode(kind: nodeKind, children: resultChildren) case "v": let index = try demangleIndex() if scanner.conditional(scalar: "r") { - return Node.create(kind: .outlinedReadOnlyObject, contents: .index(index)) + return createNode(kind: .outlinedReadOnlyObject, contents: .index(index)) } else { - return Node.create(kind: .outlinedVariable, contents: .index(index)) + return createNode(kind: .outlinedVariable, contents: .index(index)) } - case "e": return try Node.create(kind: .outlinedBridgedMethod, contents: .text(demangleBridgedMethodParams())) + case "e": return try createNode(kind: .outlinedBridgedMethod, contents: .text(demangleBridgedMethodParams())) case "u": return NodeFactory.asyncFunctionPointer case "U": let globalActor = try require(pop(kind: .type)) let reabstraction = try require(pop()) - return Node.create(kind: .reabstractionThunkHelperWithGlobalActor, children: [reabstraction, globalActor]) + return createNode(kind: .reabstractionThunkHelperWithGlobalActor, children: [reabstraction, globalActor]) case "J": switch try scanner.readScalar() { case "S": return try demangleAutoDiffSubsetParametersThunk() @@ -1803,7 +1810,7 @@ extension Demangler { try scanner.match(scalar: "p") try children.append(require(demangleIndexSubset())) try scanner.match(scalar: "r") - return Node.create(kind: kind, children: children) + return createNode(kind: kind, children: children) } private mutating func demangleAutoDiffFunctionKind() throws(DemanglingError) -> Node { @@ -1811,7 +1818,7 @@ extension Demangler { guard let autoDiffFunctionKind = AutoDiffFunctionKind(UInt64(kind.value)) else { throw failure } - return Node.create(kind: .autoDiffFunctionKind, contents: .index(UInt64(autoDiffFunctionKind.rawValue.value))) + return createNode(kind: .autoDiffFunctionKind, contents: .index(UInt64(autoDiffFunctionKind.rawValue.value))) } private mutating func demangleAutoDiffSubsetParametersThunk() throws(DemanglingError) -> Node { @@ -1828,7 +1835,7 @@ extension Demangler { try scanner.match(scalar: "r") try children.append(require(demangleIndexSubset())) try scanner.match(scalar: "P") - return Node.create(kind: .autoDiffSubsetParametersThunk, children: children) + return createNode(kind: .autoDiffSubsetParametersThunk, children: children) } private mutating func demangleAutoDiffSelfReorderingReabstractionThunk() throws(DemanglingError) -> Node { @@ -1840,7 +1847,7 @@ extension Demangler { try children.append(require(pop(kind: .type))) children.reverse() try children.append(demangleAutoDiffFunctionKind()) - return Node.create(kind: .autoDiffSelfReorderingReabstractionThunk, children: children) + return createNode(kind: .autoDiffSelfReorderingReabstractionThunk, children: children) } private mutating func demangleDifferentiabilityWitness() throws(DemanglingError) -> Node { @@ -1857,7 +1864,7 @@ extension Demangler { case "l": .linear default: throw failure } - children.append(Node.create(kind: .index, contents: .index(UInt64(kind.rawValue.value)))) + children.append(createNode(kind: .index, contents: .index(UInt64(kind.rawValue.value)))) try children.append(require(demangleIndexSubset())) try scanner.match(scalar: "p") try children.append(require(demangleIndexSubset())) @@ -1865,7 +1872,7 @@ extension Demangler { if let optionalGenSig { children.append(optionalGenSig) } - return Node.create(kind: .differentiabilityWitness, children: children) + return createNode(kind: .differentiabilityWitness, children: children) } private mutating func demangleIndexSubset() throws(DemanglingError) -> Node { @@ -1874,7 +1881,7 @@ extension Demangler { str.unicodeScalars.append(c) } try require(!str.isEmpty) - return Node.create(kind: .indexSubset, contents: .text(str)) + return createNode(kind: .indexSubset, contents: .text(str)) } private mutating func demangleDifferentiableFunctionType() throws(DemanglingError) -> Node { @@ -1885,7 +1892,7 @@ extension Demangler { case "l": .linear default: throw failure } - return Node.create(kind: .differentiableFunctionType, contents: .index(UInt64(kind.rawValue.value))) + return createNode(kind: .differentiableFunctionType, contents: .index(UInt64(kind.rawValue.value))) } private mutating func demangleBridgedMethodParams() throws(DemanglingError) -> String { @@ -1915,7 +1922,7 @@ extension Demangler { spec = spec.addingChildren(Array(droppedArguments.children)) } let list = try popTypeList() - let paramChildren = list.children.map { Node.create(kind: .genericSpecializationParam, child: $0) } + let paramChildren = list.children.map { createNode(kind: .genericSpecializationParam, child: $0) } return spec.addingChildren(paramChildren) } @@ -1924,9 +1931,9 @@ extension Demangler { var tmpChildren: [Node] = [] while scanner.conditional(scalar: "t") { let n = try demangleNatural().map { Node.Contents.index($0 + 1) } ?? Node.Contents.index(0) - tmpChildren.append(Node.create(kind: .droppedArgument, contents: n)) + tmpChildren.append(createNode(kind: .droppedArgument, contents: n)) } - let tmp = Node.create(kind: .genericSpecialization, children: tmpChildren) + let tmp = createNode(kind: .genericSpecialization, children: tmpChildren) let kind: Node.Kind = switch try scanner.readScalar() { case "g": .genericSpecialization case "G": .genericSpecializationNotReAbstracted @@ -1942,7 +1949,7 @@ extension Demangler { // RepresentationChanged specialization has no params if specBase.children.first?.kind == .representationChanged { - return Node.create(kind: .functionSignatureSpecialization, contents: specBase.contents, children: specChildren) + return createNode(kind: .functionSignatureSpecialization, contents: specBase.contents, children: specChildren) } while !scanner.conditional(scalar: "_") { @@ -1985,7 +1992,7 @@ extension Demangler { param = param.reversingChildren(from: fixedChildrenCount) specChildren[paramIndex] = param } - return Node.create(kind: .functionSignatureSpecialization, contents: specBase.contents, children: specChildren) + return createNode(kind: .functionSignatureSpecialization, contents: specBase.contents, children: specChildren) } private mutating func demangleFuncSpecParam(kind: Node.Kind) throws(DemanglingError) -> Node { @@ -1994,32 +2001,32 @@ extension Demangler { case "n": break case "c": // Consumes an identifier and multiple type parameters. Added later. - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closureProp.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closureProp.rawValue))) case "C": // ClosurePropPreviousArg: consumes an index - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closurePropPreviousArg.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closurePropPreviousArg.rawValue))) let prevArgIndex = try require(demangleNatural()) - children.append(Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .index(prevArgIndex))) + children.append(createNode(kind: .functionSignatureSpecializationParamPayload, contents: .index(prevArgIndex))) case "p": // Multiple constant prop kinds can appear in sequence constantPropLoop: while true { switch try scanner.readScalar() { case "S": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropStruct.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropStruct.rawValue))) case "f": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFunction.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFunction.rawValue))) case "g": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropGlobal.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropGlobal.rawValue))) case "i": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropInteger.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropInteger.rawValue))) let intStr = scanner.readWhile { $0.isDigit } try require(!intStr.isEmpty) - children.append(Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(intStr))) + children.append(createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(intStr))) case "d": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFloat.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFloat.rawValue))) let floatStr = scanner.readWhile { $0.isDigit } try require(!floatStr.isEmpty) - children.append(Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(floatStr))) + children.append(createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(floatStr))) case "s": let encoding: String switch try scanner.readScalar() { @@ -2028,10 +2035,10 @@ extension Demangler { case "c": encoding = "objc" default: throw failure } - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropString.rawValue))) - children.append(Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(encoding))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropString.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(encoding))) case "k": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropKeyPath.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropKeyPath.rawValue))) default: try scanner.backtrack() break constantPropLoop @@ -2051,7 +2058,7 @@ extension Demangler { if scanner.conditional(scalar: "X") { value |= FunctionSigSpecializationParamKind.sroa.rawValue } - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) case "d": var value = FunctionSigSpecializationParamKind.dead.rawValue if scanner.conditional(scalar: "G") { @@ -2063,37 +2070,37 @@ extension Demangler { if scanner.conditional(scalar: "X") { value |= FunctionSigSpecializationParamKind.sroa.rawValue } - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) case "g": var value = FunctionSigSpecializationParamKind.ownedToGuaranteed.rawValue if scanner.conditional(scalar: "X") { value |= FunctionSigSpecializationParamKind.sroa.rawValue } - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) case "o": var value = FunctionSigSpecializationParamKind.guaranteedToOwned.rawValue if scanner.conditional(scalar: "X") { value |= FunctionSigSpecializationParamKind.sroa.rawValue } - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) case "x": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.sroa.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.sroa.rawValue))) case "i": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.boxToValue.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.boxToValue.rawValue))) case "s": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.boxToStack.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.boxToStack.rawValue))) case "r": - children.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.inOutToOut.rawValue))) + children.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.inOutToOut.rawValue))) default: throw failure } - return Node.create(kind: kind, children: children) + return createNode(kind: kind, children: children) } private mutating func addFuncSpecParamNumber(param: Node, kind: FunctionSigSpecializationParamKind) throws(DemanglingError) -> Node { - let kindChild = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(kind.rawValue)) + let kindChild = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(kind.rawValue)) let str = scanner.readWhile { $0.isDigit } try require(!str.isEmpty) - let payloadChild = Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(str)) + let payloadChild = createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(str)) return param.addingChildren([kindChild, payloadChild]) } @@ -2112,17 +2119,17 @@ extension Demangler { children.append(NodeFactory.asyncRemoved) } if representationChanged { - children.append(Node.create(kind: .representationChanged)) + children.append(createNode(kind: .representationChanged)) } - children.append(Node.create(kind: .specializationPassID, contents: .index(UInt64(passId)))) - return Node.create(kind: kind, contents: contents, children: children) + children.append(createNode(kind: .specializationPassID, contents: .index(UInt64(passId)))) + return createNode(kind: kind, contents: contents, children: children) } private mutating func demangleWitness() throws(DemanglingError) -> Node { let c = try scanner.readScalar() switch c { - case "C": return try Node.create(kind: .enumCase, child: require(pop(where: { $0.isEntity }))) - case "V": return try Node.create(kind: .valueWitnessTable, child: require(pop(kind: .type))) + case "C": return try createNode(kind: .enumCase, child: require(pop(where: { $0.isEntity }))) + case "V": return try createNode(kind: .valueWitnessTable, child: require(pop(kind: .type))) case "v": let directness: UInt64 switch try scanner.readScalar() { @@ -2130,63 +2137,63 @@ extension Demangler { case "i": directness = Directness.indirect.rawValue default: throw failure } - return try Node.create(kind: .fieldOffset, children: [Node.create(kind: .directness, contents: .index(directness)), require(pop(where: { $0.isEntity }))]) - case "S": return try Node.create(kind: .protocolSelfConformanceWitnessTable, child: popProtocol()) - case "P": return try Node.create(kind: .protocolWitnessTable, child: popProtocolConformance()) - case "p": return try Node.create(kind: .protocolWitnessTablePattern, child: popProtocolConformance()) - case "G": return try Node.create(kind: .genericProtocolWitnessTable, child: popProtocolConformance()) - case "I": return try Node.create(kind: .genericProtocolWitnessTableInstantiationFunction, child: popProtocolConformance()) - case "r": return try Node.create(kind: .resilientProtocolWitnessTable, child: popProtocolConformance()) + return try createNode(kind: .fieldOffset, children: [createNode(kind: .directness, contents: .index(directness)), require(pop(where: { $0.isEntity }))]) + case "S": return try createNode(kind: .protocolSelfConformanceWitnessTable, child: popProtocol()) + case "P": return try createNode(kind: .protocolWitnessTable, child: popProtocolConformance()) + case "p": return try createNode(kind: .protocolWitnessTablePattern, child: popProtocolConformance()) + case "G": return try createNode(kind: .genericProtocolWitnessTable, child: popProtocolConformance()) + case "I": return try createNode(kind: .genericProtocolWitnessTableInstantiationFunction, child: popProtocolConformance()) + case "r": return try createNode(kind: .resilientProtocolWitnessTable, child: popProtocolConformance()) case "l": let conf = try popProtocolConformance() let type = try require(pop(kind: .type)) - return Node.create(kind: .lazyProtocolWitnessTableAccessor, children: [type, conf]) + return createNode(kind: .lazyProtocolWitnessTableAccessor, children: [type, conf]) case "L": let conf = try popProtocolConformance() let type = try require(pop(kind: .type)) - return Node.create(kind: .lazyProtocolWitnessTableCacheVariable, children: [type, conf]) - case "a": return try Node.create(kind: .protocolWitnessTableAccessor, child: popProtocolConformance()) + return createNode(kind: .lazyProtocolWitnessTableCacheVariable, children: [type, conf]) + case "a": return try createNode(kind: .protocolWitnessTableAccessor, child: popProtocolConformance()) case "t": let name = try require(pop(where: { $0.isDeclName })) let conf = try popProtocolConformance() - return Node.create(kind: .associatedTypeMetadataAccessor, children: [conf, name]) + return createNode(kind: .associatedTypeMetadataAccessor, children: [conf, name]) case "T": let protoType = try require(pop(kind: .type)) let assocTypePath = try popAssocTypePath() - return try Node.create(kind: .associatedTypeWitnessTableAccessor, children: [popProtocolConformance(), assocTypePath, protoType]) + return try createNode(kind: .associatedTypeWitnessTableAccessor, children: [popProtocolConformance(), assocTypePath, protoType]) case "b": let protoTy = try require(pop(kind: .type)) let conf = try popProtocolConformance() - return Node.create(kind: .baseWitnessTableAccessor, children: [conf, protoTy]) + return createNode(kind: .baseWitnessTableAccessor, children: [conf, protoTy]) case "O": let sig = pop(kind: .dependentGenericSignature) let type = try require(pop(kind: .type)) var children: [Node] = sig.map { [type, $0] } ?? [type] switch try scanner.readScalar() { case "B": - return Node.create(kind: .outlinedInitializeWithTakeNoValueWitness, children: children) - case "C": return Node.create(kind: .outlinedInitializeWithCopyNoValueWitness, children: children) - case "D": return Node.create(kind: .outlinedAssignWithTakeNoValueWitness, children: children) - case "F": return Node.create(kind: .outlinedAssignWithCopyNoValueWitness, children: children) - case "H": return Node.create(kind: .outlinedDestroyNoValueWitness, children: children) - case "y": return Node.create(kind: .outlinedCopy, children: children) - case "e": return Node.create(kind: .outlinedConsume, children: children) - case "r": return Node.create(kind: .outlinedRetain, children: children) - case "s": return Node.create(kind: .outlinedRelease, children: children) - case "b": return Node.create(kind: .outlinedInitializeWithTake, children: children) - case "c": return Node.create(kind: .outlinedInitializeWithCopy, children: children) - case "d": return Node.create(kind: .outlinedAssignWithTake, children: children) - case "f": return Node.create(kind: .outlinedAssignWithCopy, children: children) - case "h": return Node.create(kind: .outlinedDestroy, children: children) - case "g": return Node.create(kind: .outlinedEnumGetTag, children: children) + return createNode(kind: .outlinedInitializeWithTakeNoValueWitness, children: children) + case "C": return createNode(kind: .outlinedInitializeWithCopyNoValueWitness, children: children) + case "D": return createNode(kind: .outlinedAssignWithTakeNoValueWitness, children: children) + case "F": return createNode(kind: .outlinedAssignWithCopyNoValueWitness, children: children) + case "H": return createNode(kind: .outlinedDestroyNoValueWitness, children: children) + case "y": return createNode(kind: .outlinedCopy, children: children) + case "e": return createNode(kind: .outlinedConsume, children: children) + case "r": return createNode(kind: .outlinedRetain, children: children) + case "s": return createNode(kind: .outlinedRelease, children: children) + case "b": return createNode(kind: .outlinedInitializeWithTake, children: children) + case "c": return createNode(kind: .outlinedInitializeWithCopy, children: children) + case "d": return createNode(kind: .outlinedAssignWithTake, children: children) + case "f": return createNode(kind: .outlinedAssignWithCopy, children: children) + case "h": return createNode(kind: .outlinedDestroy, children: children) + case "g": return createNode(kind: .outlinedEnumGetTag, children: children) case "i": let enumCaseIndex = try demangleIndexAsName() children.append(enumCaseIndex) - return Node.create(kind: .outlinedEnumTagStore, children: children) + return createNode(kind: .outlinedEnumTagStore, children: children) case "j": let enumCaseIndex = try demangleIndexAsName() children.append(enumCaseIndex) - return Node.create(kind: .outlinedEnumProjectDataForLoad, children: children) + return createNode(kind: .outlinedEnumProjectDataForLoad, children: children) default: throw failure } case "Z", @@ -2197,8 +2204,8 @@ extension Demangler { declChildren.append(identifier) } declChildren.reverse() - let declList = Node.create(kind: .globalVariableOnceDeclList, children: declChildren) - return try Node.create(kind: c == "Z" ? .globalVariableOnceFunction : .globalVariableOnceToken, children: [popContext(), declList]) + let declList = createNode(kind: .globalVariableOnceDeclList, children: declChildren) + return try createNode(kind: c == "Z" ? .globalVariableOnceFunction : .globalVariableOnceToken, children: [popContext(), declList]) case "J": return try demangleDifferentiabilityWitness() default: throw failure @@ -2214,7 +2221,7 @@ extension Demangler { children.append(assocType) } while !firstElem children.reverse() - return Node.create(kind: .assocTypePath, children: children) + return createNode(kind: .assocTypePath, children: children) } private mutating func popAssocTypeName() -> Node? { @@ -2234,7 +2241,7 @@ extension Demangler { if let proto { children.append(proto) } - return Node.create(kind: .dependentAssociatedTypeRef, children: children) + return createNode(kind: .dependentAssociatedTypeRef, children: children) } private mutating func demangleSpecialType() throws(DemanglingError) -> Node { @@ -2257,29 +2264,29 @@ extension Demangler { case "C": return try popFunctionType(kind: .cFunctionPointer, hasClangType: true) default: throw failure } - case "o": return try Node.create(typeWithChildKind: .unowned, childChild: require(pop(kind: .type))) - case "u": return try Node.create(typeWithChildKind: .unmanaged, childChild: require(pop(kind: .type))) - case "w": return try Node.create(typeWithChildKind: .weak, childChild: require(pop(kind: .type))) - case "b": return try Node.create(typeWithChildKind: .silBoxType, childChild: require(pop(kind: .type))) - case "D": return try Node.create(typeWithChildKind: .dynamicSelf, childChild: require(pop(kind: .type))) + case "o": return try createNode(typeWithChildKind: .unowned, childChild: require(pop(kind: .type))) + case "u": return try createNode(typeWithChildKind: .unmanaged, childChild: require(pop(kind: .type))) + case "w": return try createNode(typeWithChildKind: .weak, childChild: require(pop(kind: .type))) + case "b": return try createNode(typeWithChildKind: .silBoxType, childChild: require(pop(kind: .type))) + case "D": return try createNode(typeWithChildKind: .dynamicSelf, childChild: require(pop(kind: .type))) case "M": let mtr = try demangleMetatypeRepresentation() let type = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .metatype, childChildren: [mtr, type]) + return createNode(typeWithChildKind: .metatype, childChildren: [mtr, type]) case "m": let mtr = try demangleMetatypeRepresentation() let type = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .existentialMetatype, childChildren: [mtr, type]) + return createNode(typeWithChildKind: .existentialMetatype, childChildren: [mtr, type]) case "P": let reqs = try demangleConstrainedExistentialRequirementList() let base = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .constrainedExistential, childChildren: [base, reqs]) - case "p": return try Node.create(typeWithChildKind: .existentialMetatype, childChild: require(pop(kind: .type))) + return createNode(typeWithChildKind: .constrainedExistential, childChildren: [base, reqs]) + case "p": return try createNode(typeWithChildKind: .existentialMetatype, childChild: require(pop(kind: .type))) case "c": let superclass = try require(pop(kind: .type)) let protocols = try demangleProtocolList() - return Node.create(typeWithChildKind: .protocolListWithClass, childChildren: [protocols, superclass]) - case "l": return try Node.create(typeWithChildKind: .protocolListWithAnyObject, childChild: demangleProtocolList()) + return createNode(typeWithChildKind: .protocolListWithClass, childChildren: [protocols, superclass]) + case "l": return try createNode(typeWithChildKind: .protocolListWithAnyObject, childChild: demangleProtocolList()) case "X", "x": var signatureGenericArgs: (Node, Node)? = nil @@ -2292,44 +2299,44 @@ extension Demangler { for fieldType in fieldTypes.children { try require(fieldType.kind == .type) if fieldType.children.first?.kind == .inOut { - try layoutChildren.append(Node.create(kind: .silBoxMutableField, child: Node.create(kind: .type, child: require(fieldType.children.first?.children.first)))) + try layoutChildren.append(createNode(kind: .silBoxMutableField, child: createNode(kind: .type, child: require(fieldType.children.first?.children.first)))) } else { - layoutChildren.append(Node.create(kind: .silBoxImmutableField, child: fieldType)) + layoutChildren.append(createNode(kind: .silBoxImmutableField, child: fieldType)) } } - let layout = Node.create(kind: .silBoxLayout, children: layoutChildren) + let layout = createNode(kind: .silBoxLayout, children: layoutChildren) var boxChildren: [Node] = [layout] if let (signature, genericArgs) = signatureGenericArgs { boxChildren.append(signature) boxChildren.append(genericArgs) } - return Node.create(kind: .type, child: Node.create(kind: .silBoxTypeWithLayout, children: boxChildren)) + return createNode(kind: .type, child: createNode(kind: .silBoxTypeWithLayout, children: boxChildren)) case "Y": return try demangleAnyGenericType(kind: .otherNominalType) case "Z": let types = try popTypeList() let name = try require(pop(kind: .identifier)) let parent = try popContext() - return Node.create(kind: .anonymousContext, children: [name, parent, types]) - case "e": return Node.create(kind: .type, child: NodeFactory.errorType) + return createNode(kind: .anonymousContext, children: [name, parent, types]) + case "e": return createNode(kind: .type, child: NodeFactory.errorType) case "S": switch try scanner.readScalar() { case "q": let type = try require(pop(kind: .type)) - return Node.create(kind: .type, child: Node.create(kind: .sugaredOptional, child: type)) + return createNode(kind: .type, child: createNode(kind: .sugaredOptional, child: type)) case "a": let type = try require(pop(kind: .type)) - return Node.create(kind: .type, child: Node.create(kind: .sugaredArray, child: type)) + return createNode(kind: .type, child: createNode(kind: .sugaredArray, child: type)) case "D": let value = try require(pop(kind: .type)) let key = try require(pop(kind: .type)) - return Node.create(kind: .type, child: Node.create(kind: .sugaredDictionary, children: [key, value])) + return createNode(kind: .type, child: createNode(kind: .sugaredDictionary, children: [key, value])) case "p": let type = try require(pop(kind: .type)) - return Node.create(kind: .type, child: Node.create(kind: .sugaredParen, child: type)) + return createNode(kind: .type, child: createNode(kind: .sugaredParen, child: type)) case "A": let element = try require(pop(kind: .type)) let count = try require(pop(kind: .type)) - return Node.create(kind: .type, child: Node.create(kind: .sugaredInlineArray, children: [count, element])) + return createNode(kind: .type, child: createNode(kind: .sugaredInlineArray, children: [count, element])) default: throw failure } default: throw failure @@ -2343,12 +2350,12 @@ extension Demangler { argsChildren.append(type) } argsChildren.reverse() - let args = Node.create(kind: .typeList, children: argsChildren) + let args = createNode(kind: .typeList, children: argsChildren) let shape = try require(pop(where: { $0 == .uniqueExtendedExistentialTypeShapeSymbolicReference || $0 == .nonUniqueExtendedExistentialTypeShapeSymbolicReference })) if let retroactiveConformances { - return Node.create(typeWithChildKind: .symbolicExtendedExistentialType, childChildren: [shape, args, retroactiveConformances]) + return createNode(typeWithChildKind: .symbolicExtendedExistentialType, childChildren: [shape, args, retroactiveConformances]) } else { - return Node.create(typeWithChildKind: .symbolicExtendedExistentialType, childChildren: [shape, args]) + return createNode(typeWithChildKind: .symbolicExtendedExistentialType, childChildren: [shape, args]) } } @@ -2359,9 +2366,9 @@ extension Demangler { genSig = pop(kind: .dependentGenericSignature) } if let genSig { - return Node.create(kind: .extendedExistentialTypeShape, children: [genSig, type]) + return createNode(kind: .extendedExistentialTypeShape, children: [genSig, type]) } else { - return Node.create(kind: .extendedExistentialTypeShape, child: type) + return createNode(kind: .extendedExistentialTypeShape, child: type) } } @@ -2373,7 +2380,7 @@ extension Demangler { case "o": value = "@objc_metatype" default: throw failure } - return Node.create(kind: .metatypeRepresentation, contents: .text(value)) + return createNode(kind: .metatypeRepresentation, contents: .text(value)) } private mutating func demangleAccessor(child: Node) throws(DemanglingError) -> Node { @@ -2411,7 +2418,7 @@ extension Demangler { case "p": return child default: throw failure } - return Node.create(kind: kind, child: child) + return createNode(kind: kind, child: child) } private mutating func demangleFunctionEntity() throws(DemanglingError) -> Node { @@ -2459,7 +2466,7 @@ extension Demangler { children.append(pn) } } - return try Node.create(kind: argsAndKind.kind, children: [popContext()] + children) + return try createNode(kind: argsAndKind.kind, children: [popContext()] + children) } private mutating func demangleEntity(kind: Node.Kind) throws(DemanglingError) -> Node { @@ -2468,9 +2475,9 @@ extension Demangler { let name = try require(pop(where: { $0.isDeclName })) let context = try popContext() let result = if let labelList = labelList { - Node.create(kind: kind, children: [context, name, labelList, type]) + createNode(kind: kind, children: [context, name, labelList, type]) } else { - Node.create(kind: kind, children: [context, name, type]) + createNode(kind: kind, children: [context, name, type]) } let updatedType = setParentForOpaqueReturnTypeNodes(visited: type, parent: result) if updatedType !== type { @@ -2497,7 +2504,7 @@ extension Demangler { if let pn = privateName { children.append(pn) } - var ss = Node.create(kind: .subscript, children: children) + var ss = createNode(kind: .subscript, children: children) let updatedType = setParentForOpaqueReturnTypeNodes(visited: type, parent: ss) if updatedType !== type { let typeIndex = labelList != nil ? 2 : 1 @@ -2516,11 +2523,11 @@ extension Demangler { } while !firstElem } typeChildren.reverse() - return Node.create(kind: .protocolList, child: Node.create(kind: .typeList, children: typeChildren)) + return createNode(kind: .protocolList, child: createNode(kind: .typeList, children: typeChildren)) } private mutating func demangleProtocolListType() throws(DemanglingError) -> Node { - return try Node.create(kind: .type, child: demangleProtocolList()) + return try createNode(kind: .type, child: demangleProtocolList()) } private mutating func demangleConstrainedExistentialRequirementList() throws(DemanglingError) -> Node { @@ -2532,7 +2539,7 @@ extension Demangler { reqChildren.append(req) } while !firstElement reqChildren.reverse() - return Node.create(kind: .constrainedExistentialRequirementList, children: reqChildren) + return createNode(kind: .constrainedExistentialRequirementList, children: reqChildren) } private mutating func demangleGenericSignature(hasParamCounts: Bool) throws(DemanglingError) -> Node { @@ -2543,17 +2550,17 @@ extension Demangler { if !scanner.conditional(scalar: "z") { count = try demangleIndex() + 1 } - paramCounts.append(Node.create(kind: .dependentGenericParamCount, contents: .index(count))) + paramCounts.append(createNode(kind: .dependentGenericParamCount, contents: .index(count))) } } else { - paramCounts.append(Node.create(kind: .dependentGenericParamCount, contents: .index(1))) + paramCounts.append(createNode(kind: .dependentGenericParamCount, contents: .index(1))) } var requirements: [Node] = [] while let req = pop(where: { $0.isRequirement }) { requirements.append(req) } requirements.reverse() - return Node.create(kind: .dependentGenericSignature, children: paramCounts + requirements) + return createNode(kind: .dependentGenericSignature, children: paramCounts + requirements) } private mutating func demangleGenericRequirement() throws(DemanglingError) -> Node { @@ -2591,7 +2598,7 @@ extension Demangler { let constrType: Node switch constraintAndTypeKinds.type { - case .generic: constrType = try Node.create(kind: .type, child: demangleGenericParamIndex()) + case .generic: constrType = try createNode(kind: .type, child: demangleGenericParamIndex()) case .assoc: constrType = try demangleAssociatedTypeSimple(index: demangleGenericParamIndex()) substitutions.append(constrType) @@ -2602,13 +2609,13 @@ extension Demangler { } switch constraintAndTypeKinds.constraint { - case .valueMarker: return try Node.create(kind: .dependentGenericParamValueMarker, children: [constrType, require(pop(kind: .type))]) - case .packMarker: return Node.create(kind: .dependentGenericParamPackMarker, children: [constrType]) - case .protocol: return try Node.create(kind: .dependentGenericConformanceRequirement, children: [constrType, popProtocol()]) - case .inverse: return try Node.create(kind: .dependentGenericInverseConformanceRequirement, children: [constrType, require(inverseKind)]) - case .baseClass: return try Node.create(kind: .dependentGenericConformanceRequirement, children: [constrType, require(pop(kind: .type))]) - case .sameType: return try Node.create(kind: .dependentGenericSameTypeRequirement, children: [constrType, require(pop(kind: .type))]) - case .sameShape: return try Node.create(kind: .dependentGenericSameShapeRequirement, children: [constrType, require(pop(kind: .type))]) + case .valueMarker: return try createNode(kind: .dependentGenericParamValueMarker, children: [constrType, require(pop(kind: .type))]) + case .packMarker: return createNode(kind: .dependentGenericParamPackMarker, children: [constrType]) + case .protocol: return try createNode(kind: .dependentGenericConformanceRequirement, children: [constrType, popProtocol()]) + case .inverse: return try createNode(kind: .dependentGenericInverseConformanceRequirement, children: [constrType, require(inverseKind)]) + case .baseClass: return try createNode(kind: .dependentGenericConformanceRequirement, children: [constrType, require(pop(kind: .type))]) + case .sameType: return try createNode(kind: .dependentGenericSameTypeRequirement, children: [constrType, require(pop(kind: .type))]) + case .sameShape: return try createNode(kind: .dependentGenericSameShapeRequirement, children: [constrType, require(pop(kind: .type))]) case .layout: let c = try scanner.readScalar() var size: Node? = nil @@ -2631,7 +2638,7 @@ extension Demangler { size = try demangleIndexAsName() default: throw failure } - let name = Node.create(kind: .identifier, contents: .text(String(String.UnicodeScalarView([c])))) + let name = createNode(kind: .identifier, contents: .text(String(String.UnicodeScalarView([c])))) var layoutChildren: [Node] = [constrType, name] if let s = size { layoutChildren.append(s) @@ -2639,7 +2646,7 @@ extension Demangler { if let a = alignment { layoutChildren.append(a) } - let layoutRequirement = Node.create(kind: .dependentGenericLayoutRequirement, children: layoutChildren) + let layoutRequirement = createNode(kind: .dependentGenericLayoutRequirement, children: layoutChildren) return layoutRequirement } } @@ -2647,16 +2654,16 @@ extension Demangler { private mutating func demangleGenericType() throws(DemanglingError) -> Node { let genSig = try require(pop(kind: .dependentGenericSignature)) let type = try require(pop(kind: .type)) - return Node.create(typeWithChildKind: .dependentGenericType, childChildren: [genSig, type]) + return createNode(typeWithChildKind: .dependentGenericType, childChildren: [genSig, type]) } private mutating func demangleValueWitness() throws(DemanglingError) -> Node { let code = try scanner.readScalars(count: 2) let kind = try require(ValueWitnessKind(code: code)) // ValueWitness node should have 2 children: Index node and Type node - let indexNode = Node.create(kind: .index, contents: .index(kind.rawValue)) + let indexNode = createNode(kind: .index, contents: .index(kind.rawValue)) let typeNode = try require(pop(kind: .type)) - return Node.create(kind: .valueWitness, children: [indexNode, typeNode]) + return createNode(kind: .valueWitness, children: [indexNode, typeNode]) } } @@ -2680,11 +2687,11 @@ extension Demangler { case "X": let line = try demangleIndex() let col = try demangleIndex() - let lineNode = Node.create(kind: .index, contents: .index(line)) - let colNode = Node.create(kind: .index, contents: .index(col)) + let lineNode = createNode(kind: .index, contents: .index(line)) + let colNode = createNode(kind: .index, contents: .index(col)) let buffer = try require(pop(kind: .identifier)) let module = try require(pop(kind: .identifier)) - return Node.create(kind: .macroExpansionLoc, children: [module, buffer, lineNode, colNode]) + return createNode(kind: .macroExpansionLoc, children: [module, buffer, lineNode, colNode]) default: throw failure } @@ -2703,7 +2710,7 @@ extension Demangler { if let privateDiscriminator { children.append(privateDiscriminator) } - return Node.create(kind: kind, children: children) + return createNode(kind: kind, children: children) } catch { throw error as! DemanglingError } @@ -2711,9 +2718,9 @@ extension Demangler { private mutating func demangleIntegerType() throws(DemanglingError) -> Node { if scanner.conditional(scalar: "n") { - return try Node.create(kind: .type, children: [Node.create(kind: .negativeInteger, contents: .index(demangleIndex()))]) + return try createNode(kind: .type, children: [createNode(kind: .negativeInteger, contents: .index(demangleIndex()))]) } else { - return try Node.create(kind: .type, children: [Node.create(kind: .integer, contents: .index(demangleIndex()))]) + return try createNode(kind: .type, children: [createNode(kind: .integer, contents: .index(demangleIndex()))]) } } @@ -2722,26 +2729,26 @@ extension Demangler { if scanner.conditional(scalar: "C") { let module: Node if scanner.conditional(scalar: "s") { - module = Node.create(kind: .module, contents: .text(stdlibName)) + module = createNode(kind: .module, contents: .text(stdlibName)) } else { module = try demangleIdentifier().changeKind(.module) } - typeChild = try Node.create(kind: .class, children: [module, demangleIdentifier()]) + typeChild = try createNode(kind: .class, children: [module, demangleIdentifier()]) } else if scanner.conditional(scalar: "P") { let module: Node if scanner.conditional(scalar: "s") { - module = Node.create(kind: .module, contents: .text(stdlibName)) + module = createNode(kind: .module, contents: .text(stdlibName)) } else { module = try demangleIdentifier().changeKind(.module) } - typeChild = try Node.create(kind: .protocolList, child: Node.create(kind: .typeList, child: Node.create(kind: .type, child: Node.create(kind: .protocol, children: [module, demangleIdentifier()])))) + typeChild = try createNode(kind: .protocolList, child: createNode(kind: .typeList, child: createNode(kind: .type, child: createNode(kind: .protocol, children: [module, demangleIdentifier()])))) try scanner.match(scalar: "_") } else { throw failure } try require(scanner.isAtEnd) - let type = Node.create(kind: .type, child: typeChild) - return Node.create(kind: .global, child: Node.create(kind: .typeMangling, child: type)) + let type = createNode(kind: .type, child: typeChild) + return createNode(kind: .global, child: createNode(kind: .typeMangling, child: type)) } } @@ -3172,32 +3179,32 @@ extension Demangler { let remainder = scanner.remainder() if !remainder.isEmpty { - children.append(Node.create(kind: .suffix, contents: .text(remainder))) + children.append(createNode(kind: .suffix, contents: .text(remainder))) } - return Node.create(kind: .global, children: children) + return createNode(kind: .global, children: children) } mutating func demangleSwift3Global() throws(DemanglingError) -> Node { let c1 = try scanner.readScalar() let c2 = try scanner.readScalar() switch (c1, c2) { - case ("M", "P"): return try Node.create(kind: .genericTypeMetadataPattern, children: [demangleSwift3Type()]) - case ("M", "a"): return try Node.create(kind: .typeMetadataAccessFunction, children: [demangleSwift3Type()]) - case ("M", "L"): return try Node.create(kind: .typeMetadataLazyCache, children: [demangleSwift3Type()]) - case ("M", "m"): return try Node.create(kind: .metaclass, children: [demangleSwift3Type()]) - case ("M", "n"): return try Node.create(kind: .nominalTypeDescriptor, children: [demangleSwift3Type()]) - case ("M", "f"): return try Node.create(kind: .fullTypeMetadata, children: [demangleSwift3Type()]) - case ("M", "p"): return try Node.create(kind: .protocolDescriptor, children: [demangleSwift3ProtocolName()]) + case ("M", "P"): return try createNode(kind: .genericTypeMetadataPattern, children: [demangleSwift3Type()]) + case ("M", "a"): return try createNode(kind: .typeMetadataAccessFunction, children: [demangleSwift3Type()]) + case ("M", "L"): return try createNode(kind: .typeMetadataLazyCache, children: [demangleSwift3Type()]) + case ("M", "m"): return try createNode(kind: .metaclass, children: [demangleSwift3Type()]) + case ("M", "n"): return try createNode(kind: .nominalTypeDescriptor, children: [demangleSwift3Type()]) + case ("M", "f"): return try createNode(kind: .fullTypeMetadata, children: [demangleSwift3Type()]) + case ("M", "p"): return try createNode(kind: .protocolDescriptor, children: [demangleSwift3ProtocolName()]) case ("M", _): try scanner.backtrack() - return try Node.create(kind: .typeMetadata, children: [demangleSwift3Type()]) + return try createNode(kind: .typeMetadata, children: [demangleSwift3Type()]) case ("P", "A"): - return try Node.create(kind: scanner.conditional(scalar: "o") ? .partialApplyObjCForwarder : .partialApplyForwarder, children: scanner.conditional(string: "__T") ? [demangleSwift3Global()] : []) + return try createNode(kind: scanner.conditional(scalar: "o") ? .partialApplyObjCForwarder : .partialApplyForwarder, children: scanner.conditional(string: "__T") ? [demangleSwift3Global()] : []) case ("P", _): throw scanner.unexpectedError() case ("t", _): try scanner.backtrack() - return try Node.create(kind: .typeMangling, children: [demangleSwift3Type()]) + return try createNode(kind: .typeMangling, children: [demangleSwift3Type()]) case ("w", _): let c3 = try scanner.readScalar() let value: UInt64 @@ -3225,21 +3232,21 @@ extension Demangler { case ("u", "p"): value = ValueWitnessKind.destructiveProjectEnumData.rawValue default: throw scanner.unexpectedError() } - return try Node.create(kind: .valueWitness, children: [Node.create(kind: .index, index: value), demangleSwift3Type()]) - case ("W", "V"): return try Node.create(kind: .valueWitnessTable, children: [demangleSwift3Type()]) - case ("W", "v"): return try Node.create(kind: .fieldOffset, children: [Node.create(kind: .directness, contents: .index(scanner.readScalar() == "d" ? 0 : 1)), demangleSwift3Entity()]) - case ("W", "P"): return try Node.create(kind: .protocolWitnessTable, children: [demangleSwift3ProtocolConformance()]) - case ("W", "G"): return try Node.create(kind: .genericProtocolWitnessTable, children: [demangleSwift3ProtocolConformance()]) - case ("W", "I"): return try Node.create(kind: .genericProtocolWitnessTableInstantiationFunction, children: [demangleSwift3ProtocolConformance()]) - case ("W", "l"): return try Node.create(kind: .lazyProtocolWitnessTableAccessor, children: [demangleSwift3Type(), demangleSwift3ProtocolConformance()]) - case ("W", "L"): return try Node.create(kind: .lazyProtocolWitnessTableCacheVariable, children: [demangleSwift3Type(), demangleSwift3ProtocolConformance()]) - case ("W", "a"): return try Node.create(kind: .protocolWitnessTableAccessor, children: [demangleSwift3ProtocolConformance()]) - case ("W", "t"): return try Node.create(kind: .associatedTypeMetadataAccessor, children: [demangleSwift3ProtocolConformance(), demangleSwift3DeclName()]) - case ("W", "T"): return try Node.create(kind: .associatedTypeWitnessTableAccessor, children: [demangleSwift3ProtocolConformance(), demangleSwift3DeclName(), demangleSwift3ProtocolName()]) + return try createNode(kind: .valueWitness, children: [createNode(kind: .index, index: value), demangleSwift3Type()]) + case ("W", "V"): return try createNode(kind: .valueWitnessTable, children: [demangleSwift3Type()]) + case ("W", "v"): return try createNode(kind: .fieldOffset, children: [createNode(kind: .directness, contents: .index(scanner.readScalar() == "d" ? 0 : 1)), demangleSwift3Entity()]) + case ("W", "P"): return try createNode(kind: .protocolWitnessTable, children: [demangleSwift3ProtocolConformance()]) + case ("W", "G"): return try createNode(kind: .genericProtocolWitnessTable, children: [demangleSwift3ProtocolConformance()]) + case ("W", "I"): return try createNode(kind: .genericProtocolWitnessTableInstantiationFunction, children: [demangleSwift3ProtocolConformance()]) + case ("W", "l"): return try createNode(kind: .lazyProtocolWitnessTableAccessor, children: [demangleSwift3Type(), demangleSwift3ProtocolConformance()]) + case ("W", "L"): return try createNode(kind: .lazyProtocolWitnessTableCacheVariable, children: [demangleSwift3Type(), demangleSwift3ProtocolConformance()]) + case ("W", "a"): return try createNode(kind: .protocolWitnessTableAccessor, children: [demangleSwift3ProtocolConformance()]) + case ("W", "t"): return try createNode(kind: .associatedTypeMetadataAccessor, children: [demangleSwift3ProtocolConformance(), demangleSwift3DeclName()]) + case ("W", "T"): return try createNode(kind: .associatedTypeWitnessTableAccessor, children: [demangleSwift3ProtocolConformance(), demangleSwift3DeclName(), demangleSwift3ProtocolName()]) case ("W", _): throw scanner.unexpectedError() - case ("T", "W"): return try Node.create(kind: .protocolWitness, children: [demangleSwift3ProtocolConformance(), demangleSwift3Entity()]) + case ("T", "W"): return try createNode(kind: .protocolWitness, children: [demangleSwift3ProtocolConformance(), demangleSwift3Entity()]) case ("T", "R"): fallthrough - case ("T", "r"): return try Node.create(kind: c2 == "R" ? Node.Kind.reabstractionThunkHelper : Node.Kind.reabstractionThunk, children: scanner.conditional(scalar: "G") ? [demangleSwift3GenericSignature(), demangleSwift3Type(), demangleSwift3Type()] : [demangleSwift3Type(), demangleSwift3Type()]) + case ("T", "r"): return try createNode(kind: c2 == "R" ? Node.Kind.reabstractionThunkHelper : Node.Kind.reabstractionThunk, children: scanner.conditional(scalar: "G") ? [demangleSwift3GenericSignature(), demangleSwift3Type(), demangleSwift3Type()] : [demangleSwift3Type(), demangleSwift3Type()]) default: try scanner.backtrack(count: 2) return try demangleSwift3Entity() @@ -3252,7 +3259,7 @@ extension Demangler { if scanner.conditional(scalar: "q") { children.append(NodeFactory.isSerialized) } - try children.append(Node.create(kind: .specializationPassID, contents: .index(UInt64(scanner.readScalar().value - 48)))) + try children.append(createNode(kind: .specializationPassID, contents: .index(UInt64(scanner.readScalar().value - 48)))) switch c { case "r": fallthrough case "g": @@ -3262,9 +3269,9 @@ extension Demangler { while !scanner.conditional(scalar: "_") { try parameterChildren.append(demangleSwift3ProtocolConformance()) } - children.append(Node.create(kind: .genericSpecializationParam, children: parameterChildren)) + children.append(createNode(kind: .genericSpecializationParam, children: parameterChildren)) } - return Node.create(kind: c == "r" ? .genericSpecializationNotReAbstracted : .genericSpecialization, children: children) + return createNode(kind: c == "r" ? .genericSpecializationNotReAbstracted : .genericSpecialization, children: children) case "f": var count: UInt64 = 0 while !scanner.conditional(scalar: "_") { @@ -3274,13 +3281,13 @@ extension Demangler { case ("n", "_"): break case ("c", "p"): try paramChildren.append(contentsOf: demangleSwift3FuncSigSpecializationConstantProp()) case ("c", "l"): - paramChildren.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closureProp.rawValue))) - try paramChildren.append(Node.create(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents)) + paramChildren.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.closureProp.rawValue))) + try paramChildren.append(createNode(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents)) while !scanner.conditional(scalar: "_") { try paramChildren.append(demangleSwift3Type()) } case ("i", "_"): fallthrough - case ("k", "_"): paramChildren.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(c == "i" ? FunctionSigSpecializationParamKind.boxToValue.rawValue : FunctionSigSpecializationParamKind.boxToStack.rawValue))) + case ("k", "_"): paramChildren.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(c == "i" ? FunctionSigSpecializationParamKind.boxToValue.rawValue : FunctionSigSpecializationParamKind.boxToStack.rawValue))) default: try scanner.backtrack(count: 2) var value: UInt64 = 0 @@ -3289,12 +3296,12 @@ extension Demangler { value |= scanner.conditional(scalar: "o") ? FunctionSigSpecializationParamKind.guaranteedToOwned.rawValue : 0 value |= scanner.conditional(scalar: "s") ? FunctionSigSpecializationParamKind.sroa.rawValue : 0 try scanner.match(scalar: "_") - paramChildren.append(Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) + paramChildren.append(createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(value))) } - children.append(Node.create(kind: .functionSignatureSpecializationParam, children: paramChildren)) + children.append(createNode(kind: .functionSignatureSpecializationParam, children: paramChildren)) count += 1 } - return Node.create(kind: .functionSignatureSpecialization, children: children) + return createNode(kind: .functionSignatureSpecialization, children: children) default: throw scanner.unexpectedError() } } @@ -3302,28 +3309,28 @@ extension Demangler { mutating func demangleSwift3FuncSigSpecializationConstantProp() throws(DemanglingError) -> [Node] { switch try (scanner.readScalar(), scanner.readScalar()) { case ("f", "r"): - let name = try Node.create(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) + let name = try createNode(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) try scanner.match(scalar: "_") - let kind = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFunction.rawValue)) + let kind = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFunction.rawValue)) return [kind, name] case ("g", _): try scanner.backtrack() - let name = try Node.create(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) + let name = try createNode(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) try scanner.match(scalar: "_") - let kind = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropGlobal.rawValue)) + let kind = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropGlobal.rawValue)) return [kind, name] case ("i", _): try scanner.backtrack() let string = try scanner.readUntil(scalar: "_") try scanner.match(scalar: "_") - let name = Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) - let kind = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropInteger.rawValue)) + let name = createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) + let kind = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropInteger.rawValue)) return [kind, name] case ("f", "l"): let string = try scanner.readUntil(scalar: "_") try scanner.match(scalar: "_") - let name = Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) - let kind = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFloat.rawValue)) + let name = createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) + let kind = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropFloat.rawValue)) return [kind, name] case ("s", "e"): var string: String @@ -3333,9 +3340,9 @@ extension Demangler { default: throw scanner.unexpectedError() } try scanner.match(scalar: "v") - let name = try Node.create(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) - let encoding = Node.create(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) - let kind = Node.create(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropString.rawValue)) + let name = try createNode(kind: .functionSignatureSpecializationParamPayload, contents: demangleSwift3Identifier().contents) + let encoding = createNode(kind: .functionSignatureSpecializationParamPayload, contents: .text(string)) + let kind = createNode(kind: .functionSignatureSpecializationParamKind, contents: .index(FunctionSigSpecializationParamKind.constantPropString.rawValue)) try scanner.match(scalar: "_") return [kind, encoding, name] default: throw scanner.unexpectedError() @@ -3346,7 +3353,7 @@ extension Demangler { let type = try demangleSwift3Type() let prot = try demangleSwift3ProtocolName() let context = try demangleSwift3Context() - return Node.create(kind: .protocolConformance, children: [type, prot, context]) + return createNode(kind: .protocolConformance, children: [type, prot, context]) } mutating func demangleSwift3ProtocolName() throws(DemanglingError) -> Node { @@ -3359,18 +3366,18 @@ extension Demangler { default: throw scanner.unexpectedError() } } else if scanner.conditional(scalar: "s") { - let stdlib = Node.create(kind: .module, contents: .text(stdlibName)) + let stdlib = createNode(kind: .module, contents: .text(stdlibName)) name = try demangleSwift3ProtocolNameGivenContext(context: stdlib) } else { name = try demangleSwift3DeclarationName(kind: .protocol) } - return Node.create(kind: .type, children: [name]) + return createNode(kind: .type, children: [name]) } mutating func demangleSwift3ProtocolNameGivenContext(context: Node) throws(DemanglingError) -> Node { let name = try demangleSwift3DeclName() - let result = Node.create(kind: .protocol, children: [context, name]) + let result = createNode(kind: .protocol, children: [context, name]) nameStack.append(result) return result } @@ -3398,7 +3405,7 @@ extension Demangler { parentOrModule = try demangleSwift3BoundGenericArgs(nominalType: parentOrModule) guard initialNominal.children.count > 1 else { throw scanner.unexpectedError() } - nominalType = Node.create(kind: initialNominal.kind, children: [parentOrModule, initialNominal.children[1]]) + nominalType = createNode(kind: initialNominal.kind, children: [parentOrModule, initialNominal.children[1]]) } var children = [Node]() @@ -3408,12 +3415,12 @@ extension Demangler { if children.isEmpty { return nominalType } - let args = Node.create(kind: .typeList, children: children) - let unboundType = Node.create(kind: .type, children: [nominalType]) + let args = createNode(kind: .typeList, children: children) + let unboundType = createNode(kind: .type, children: [nominalType]) switch nominalType.kind { - case .class: return Node.create(kind: .boundGenericClass, children: [unboundType, args]) - case .structure: return Node.create(kind: .boundGenericStructure, children: [unboundType, args]) - case .enum: return Node.create(kind: .boundGenericEnum, children: [unboundType, args]) + case .class: return createNode(kind: .boundGenericClass, children: [unboundType, args]) + case .structure: return createNode(kind: .boundGenericStructure, children: [unboundType, args]) + case .enum: return createNode(kind: .boundGenericEnum, children: [unboundType, args]) default: throw scanner.unexpectedError() } } @@ -3463,9 +3470,9 @@ extension Demangler { case "m": (kind, hasType, name, wrapEntity) = try (.materializeForSet, true, demangleSwift3DeclName(), true) case "w": (kind, hasType, name, wrapEntity) = try (.willSet, true, demangleSwift3DeclName(), true) case "W": (kind, hasType, name, wrapEntity) = try (.didSet, true, demangleSwift3DeclName(), true) - case "U": (kind, hasType, name) = try (.explicitClosure, true, Node.create(kind: .number, contents: .index(demangleSwift3Index()))) - case "u": (kind, hasType, name) = try (.implicitClosure, true, Node.create(kind: .number, contents: .index(demangleSwift3Index()))) - case "A" where basicKind == .initializer: (kind, hasType, name) = try (.defaultArgumentInitializer, false, Node.create(kind: .number, contents: .index(demangleSwift3Index()))) + case "U": (kind, hasType, name) = try (.explicitClosure, true, createNode(kind: .number, contents: .index(demangleSwift3Index()))) + case "u": (kind, hasType, name) = try (.implicitClosure, true, createNode(kind: .number, contents: .index(demangleSwift3Index()))) + case "A" where basicKind == .initializer: (kind, hasType, name) = try (.defaultArgumentInitializer, false, createNode(kind: .number, contents: .index(demangleSwift3Index()))) case "i" where basicKind == .initializer: (kind, hasType) = (.initializer, false) case _ where basicKind == .initializer: throw scanner.unexpectedError() default: @@ -3485,7 +3492,7 @@ extension Demangler { case .some(.privateDeclName): if let n = name, let first = n.children.at(0), let second = n.children.at(1), second.text == "subscript" { isSubscript = true - name = Node.create(kind: .privateDeclName, children: [first]) + name = createNode(kind: .privateDeclName, children: [first]) } default: break } @@ -3499,8 +3506,8 @@ extension Demangler { if isSubscript, let n = name { wrappedChildren.append(n) } - let wrappedEntity = Node.create(kind: isSubscript ? .subscript : .variable, children: wrappedChildren) - entity = Node.create(kind: kind, children: [wrappedEntity]) + let wrappedEntity = createNode(kind: isSubscript ? .subscript : .variable, children: wrappedChildren) + entity = createNode(kind: kind, children: [wrappedEntity]) } else { var entityChildren: [Node] = [context] if let n = name { @@ -3509,28 +3516,28 @@ extension Demangler { if hasType { try entityChildren.append(demangleSwift3Type()) } - entity = Node.create(kind: kind, children: entityChildren) + entity = createNode(kind: kind, children: entityChildren) } - return isStatic ? Node.create(kind: .static, children: [entity]) : entity + return isStatic ? createNode(kind: .static, children: [entity]) : entity } mutating func demangleSwift3DeclarationName(kind: Node.Kind) throws(DemanglingError) -> Node { - let result = try Node.create(kind: kind, children: [demangleSwift3Context(), demangleSwift3DeclName()]) + let result = try createNode(kind: kind, children: [demangleSwift3Context(), demangleSwift3DeclName()]) nameStack.append(result) return result } mutating func demangleSwift3Context() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "E": return try Node.create(kind: .extension, children: [demangleSwift3Module(), demangleSwift3Context()]) + case "E": return try createNode(kind: .extension, children: [demangleSwift3Module(), demangleSwift3Context()]) case "e": let module = try demangleSwift3Module() let signature = try demangleSwift3GenericSignature() let type = try demangleSwift3Context() - return Node.create(kind: .extension, children: [module, type, signature]) + return createNode(kind: .extension, children: [module, type, signature]) case "S": return try demangleSwift3SubstitutionIndex() - case "s": return Node.create(kind: .module, text: stdlibName) + case "s": return createNode(kind: .module, text: stdlibName) case "G": return try demangleSwift3BoundGenericArgs(nominalType: demangleSwift3NominalType()) case "F": fallthrough case "I": fallthrough @@ -3551,7 +3558,7 @@ extension Demangler { mutating func demangleSwift3Module() throws(DemanglingError) -> Node { switch try scanner.readScalar() { case "S": return try demangleSwift3SubstitutionIndex() - case "s": return Node.create(kind: .module, text: "Swift") + case "s": return createNode(kind: .module, text: "Swift") default: try scanner.backtrack() let module = try demangleSwift3Identifier(kind: .module) @@ -3561,13 +3568,13 @@ extension Demangler { } func swiftStdLibType(_ kind: Node.Kind, named: String) -> Node { - return Node.create(kind: kind, children: [Node.create(kind: .module, contents: .text(stdlibName)), Node.create(kind: .identifier, contents: .text(named))]) + return createNode(kind: kind, children: [createNode(kind: .module, contents: .text(stdlibName)), createNode(kind: .identifier, contents: .text(named))]) } mutating func demangleSwift3SubstitutionIndex() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "o": return Node.create(kind: .module, contents: .text(objcModule)) - case "C": return Node.create(kind: .module, contents: .text(cModule)) + case "o": return createNode(kind: .module, contents: .text(objcModule)) + case "C": return createNode(kind: .module, contents: .text(cModule)) case "a": return swiftStdLibType(.structure, named: "Array") case "b": return swiftStdLibType(.structure, named: "Bool") case "c": return swiftStdLibType(.structure, named: "UnicodeScalar") @@ -3598,11 +3605,11 @@ extension Demangler { var children = [Node]() var c = try scanner.requirePeek() while c != "R" && c != "r" { - try children.append(Node.create(kind: .dependentGenericParamCount, contents: .index(scanner.conditional(scalar: "z") ? 0 : (demangleSwift3Index() + 1)))) + try children.append(createNode(kind: .dependentGenericParamCount, contents: .index(scanner.conditional(scalar: "z") ? 0 : (demangleSwift3Index() + 1)))) c = try scanner.requirePeek() } if children.isEmpty { - children.append(Node.create(kind: .dependentGenericParamCount, contents: .index(1))) + children.append(createNode(kind: .dependentGenericParamCount, contents: .index(1))) } if !scanner.conditional(scalar: "r") { try scanner.match(scalar: "R") @@ -3610,13 +3617,13 @@ extension Demangler { try children.append(demangleSwift3GenericRequirement()) } } - return Node.create(kind: .dependentGenericSignature, children: children) + return createNode(kind: .dependentGenericSignature, children: children) } mutating func demangleSwift3GenericRequirement() throws(DemanglingError) -> Node { let constrainedType = try demangleSwift3ConstrainedType() if scanner.conditional(scalar: "z") { - return try Node.create(kind: .dependentGenericSameTypeRequirement, children: [constrainedType, demangleSwift3Type()]) + return try createNode(kind: .dependentGenericSameTypeRequirement, children: [constrainedType, demangleSwift3Type()]) } if scanner.conditional(scalar: "l") { @@ -3647,15 +3654,15 @@ extension Demangler { size = try require(demangleNatural()) default: throw failure } - let second = Node.create(kind: kind, contents: .text(name)) + let second = createNode(kind: kind, contents: .text(name)) var reqtChildren: [Node] = [constrainedType, second] if size != UInt64.max { - reqtChildren.append(Node.create(kind: .number, contents: .index(size))) + reqtChildren.append(createNode(kind: .number, contents: .index(size))) if alignment != UInt64.max { - reqtChildren.append(Node.create(kind: .number, contents: .index(alignment))) + reqtChildren.append(createNode(kind: .number, contents: .index(alignment))) } } - return Node.create(kind: .dependentGenericLayoutRequirement, children: reqtChildren) + return createNode(kind: .dependentGenericLayoutRequirement, children: reqtChildren) } let c = try scanner.requirePeek() @@ -3672,11 +3679,11 @@ extension Demangler { case .module: typename = try demangleSwift3ProtocolNameGivenContext(context: index) default: throw scanner.unexpectedError() } - constraint = Node.create(kind: .type, children: [typename]) + constraint = createNode(kind: .type, children: [typename]) } else { constraint = try demangleSwift3ProtocolName() } - return Node.create(kind: .dependentGenericConformanceRequirement, children: [constrainedType, constraint]) + return createNode(kind: .dependentGenericConformanceRequirement, children: [constrainedType, constraint]) } mutating func demangleSwift3ConstrainedType() throws(DemanglingError) -> Node { @@ -3690,13 +3697,13 @@ extension Demangler { mutating func demangleSwift3AssociatedTypeSimple() throws(DemanglingError) -> Node { let base = try demangleSwift3GenericParamIndex() - return try demangleSwift3DependentMemberTypeName(base: Node.create(kind: .type, children: [base])) + return try demangleSwift3DependentMemberTypeName(base: createNode(kind: .type, children: [base])) } mutating func demangleSwift3AssociatedTypeCompound() throws(DemanglingError) -> Node { var base = try demangleSwift3GenericParamIndex() while !scanner.conditional(scalar: "_") { - let type = Node.create(kind: .type, children: [base]) + let type = createNode(kind: .type, children: [base]) base = try demangleSwift3DependentMemberTypeName(base: type) } return base @@ -3712,7 +3719,7 @@ extension Demangler { try scanner.backtrack() (depth, index) = try (0, demangleSwift3Index() + 1) } - return Node.create(kind: .dependentGenericParamType, children: [Node.create(kind: .index, index: depth), Node.create(kind: .index, index: index)]) + return createNode(kind: .dependentGenericParamType, children: [createNode(kind: .index, index: depth), createNode(kind: .index, index: index)]) } mutating func demangleSwift3DependentMemberTypeName(base: Node) throws(DemanglingError) -> Node { @@ -3726,20 +3733,20 @@ extension Demangler { } let identifier = try demangleSwift3Identifier() if let p = prot { - associatedType = Node.create(kind: .dependentAssociatedTypeRef, children: [identifier, p]) + associatedType = createNode(kind: .dependentAssociatedTypeRef, children: [identifier, p]) } else { - associatedType = Node.create(kind: .dependentAssociatedTypeRef, children: [identifier]) + associatedType = createNode(kind: .dependentAssociatedTypeRef, children: [identifier]) } nameStack.append(associatedType) } - return Node.create(kind: .dependentMemberType, children: [base, associatedType]) + return createNode(kind: .dependentMemberType, children: [base, associatedType]) } mutating func demangleSwift3DeclName() throws(DemanglingError) -> Node { switch try scanner.readScalar() { - case "L": return try Node.create(kind: .localDeclName, children: [Node.create(kind: .number, contents: .index(demangleSwift3Index())), demangleSwift3Identifier()]) - case "P": return try Node.create(kind: .privateDeclName, children: [demangleSwift3Identifier(), demangleSwift3Identifier()]) + case "L": return try createNode(kind: .localDeclName, children: [createNode(kind: .number, contents: .index(demangleSwift3Index())), demangleSwift3Identifier()]) + case "P": return try createNode(kind: .privateDeclName, children: [demangleSwift3Identifier(), demangleSwift3Identifier()]) default: try scanner.backtrack() return try demangleSwift3Identifier() @@ -3760,16 +3767,16 @@ extension Demangler { switch try scanner.readScalar() { case "B": switch try scanner.readScalar() { - case "b": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.BridgeObject")) - case "B": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.UnsafeValueBuffer")) + case "b": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.BridgeObject")) + case "B": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.UnsafeValueBuffer")) case "f": let size = try scanner.readInt() try scanner.match(scalar: "_") - type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.FPIEEE\(size)")) + type = createNode(kind: .builtinTypeName, contents: .text("Builtin.FPIEEE\(size)")) case "i": let size = try scanner.readInt() try scanner.match(scalar: "_") - type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.Int\(size)")) + type = createNode(kind: .builtinTypeName, contents: .text("Builtin.Int\(size)")) case "v": let elements = try scanner.readInt() try scanner.match(scalar: "B") @@ -3784,28 +3791,28 @@ extension Demangler { try scanner.match(scalar: "_") default: throw scanner.unexpectedError() } - type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.Vec\(elements)\(name)\(size)")) - case "O": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.UnknownObject")) - case "o": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.NativeObject")) - case "t": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.SILToken")) - case "p": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.RawPointer")) - case "w": type = Node.create(kind: .builtinTypeName, contents: .text("Builtin.Word")) + type = createNode(kind: .builtinTypeName, contents: .text("Builtin.Vec\(elements)\(name)\(size)")) + case "O": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.UnknownObject")) + case "o": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.NativeObject")) + case "t": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.SILToken")) + case "p": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.RawPointer")) + case "w": type = createNode(kind: .builtinTypeName, contents: .text("Builtin.Word")) default: throw scanner.unexpectedError() } case "a": type = try demangleSwift3DeclarationName(kind: .typeAlias) case "b": type = try demangleSwift3FunctionType(kind: .objCBlock) case "c": type = try demangleSwift3FunctionType(kind: .cFunctionPointer) - case "D": type = try Node.create(kind: .dynamicSelf, children: [demangleSwift3Type()]) + case "D": type = try createNode(kind: .dynamicSelf, children: [demangleSwift3Type()]) case "E": guard try scanner.readScalars(count: 2) == "RR" else { throw scanner.unexpectedError() } - type = Node.create(kind: .errorType, text: "") + type = createNode(kind: .errorType, text: "") case "F": type = try demangleSwift3FunctionType(kind: .functionType) case "f": type = try demangleSwift3FunctionType(kind: .uncurriedFunctionType) case "G": type = try demangleSwift3BoundGenericArgs(nominalType: demangleSwift3NominalType()) case "X": let c = try scanner.readScalar() switch c { - case "b": type = try Node.create(kind: .silBoxType, children: [demangleSwift3Type()]) + case "b": type = try createNode(kind: .silBoxType, children: [demangleSwift3Type()]) case "B": var signature: Node? = nil if scanner.conditional(scalar: "G") { @@ -3820,23 +3827,23 @@ extension Demangler { default: throw failure } let type = try demangleType() - layoutChildren.append(Node.create(kind: fieldKind, child: type)) + layoutChildren.append(createNode(kind: fieldKind, child: type)) } - let layout = Node.create(kind: .silBoxLayout, children: layoutChildren) + let layout = createNode(kind: .silBoxLayout, children: layoutChildren) var genericArgs: Node? = nil if signature != nil { var gaChildren: [Node] = [] while !scanner.conditional(scalar: "_") { try gaChildren.append(demangleType()) } - genericArgs = Node.create(kind: .typeList, children: gaChildren) + genericArgs = createNode(kind: .typeList, children: gaChildren) } var boxTypeChildren: [Node] = [layout] if let s = signature, let ga = genericArgs { boxTypeChildren.append(s) boxTypeChildren.append(ga) } - return Node.create(kind: .silBoxTypeWithLayout, children: boxTypeChildren) + return createNode(kind: .silBoxTypeWithLayout, children: boxTypeChildren) case "P" where scanner.conditional(scalar: "M"): fallthrough case "M": let value: String @@ -3846,20 +3853,20 @@ extension Demangler { case "o": value = "@objc_metatype" default: throw scanner.unexpectedError() } - type = try Node.create(kind: c == "P" ? .existentialMetatype : .metatype, children: [Node.create(kind: .metatypeRepresentation, contents: .text(value)), demangleSwift3Type()]) + type = try createNode(kind: c == "P" ? .existentialMetatype : .metatype, children: [createNode(kind: .metatypeRepresentation, contents: .text(value)), demangleSwift3Type()]) case "P": var children = [Node]() while !scanner.conditional(scalar: "_") { try children.append(demangleSwift3ProtocolName()) } - type = Node.create(kind: .protocolList, children: [Node.create(kind: .typeList)]) + type = createNode(kind: .protocolList, children: [createNode(kind: .typeList)]) case "f": type = try demangleSwift3FunctionType(kind: .thinFunctionType) - case "o": type = try Node.create(kind: .unowned, children: [demangleSwift3Type()]) - case "u": type = try Node.create(kind: .unmanaged, children: [demangleSwift3Type()]) - case "w": type = try Node.create(kind: .weak, children: [demangleSwift3Type()]) + case "o": type = try createNode(kind: .unowned, children: [demangleSwift3Type()]) + case "u": type = try createNode(kind: .unmanaged, children: [demangleSwift3Type()]) + case "w": type = try createNode(kind: .weak, children: [demangleSwift3Type()]) case "F": var children = [Node]() - try children.append(Node.create(kind: .implConvention, contents: .text(demangleSwift3ImplConvention(kind: .implConvention)))) + try children.append(createNode(kind: .implConvention, contents: .text(demangleSwift3ImplConvention(kind: .implConvention)))) if scanner.conditional(scalar: "C") { let name: String switch try scanner.readScalar() { @@ -3870,7 +3877,7 @@ extension Demangler { case "w": name = "@convention(witness_method)" default: throw scanner.unexpectedError() } - children.append(Node.create(kind: .implFunctionAttribute, contents: .text(name))) + children.append(createNode(kind: .implFunctionAttribute, contents: .text(name))) } if scanner.conditional(scalar: "G") { try children.append(demangleSwift3GenericSignature(isPseudo: false)) @@ -3884,24 +3891,24 @@ extension Demangler { while !scanner.conditional(scalar: "_") { try children.append(demangleSwift3ImplParameterOrResult(kind: .implResult)) } - type = Node.create(kind: .implFunctionType, children: children) + type = createNode(kind: .implFunctionType, children: children) default: throw scanner.unexpectedError() } case "K": type = try demangleSwift3FunctionType(kind: .autoClosureType) - case "M": type = try Node.create(kind: .metatype, children: [demangleSwift3Type()]) - case "P" where scanner.conditional(scalar: "M"): type = try Node.create(kind: .existentialMetatype, children: [demangleSwift3Type()]) + case "M": type = try createNode(kind: .metatype, children: [demangleSwift3Type()]) + case "P" where scanner.conditional(scalar: "M"): type = try createNode(kind: .existentialMetatype, children: [demangleSwift3Type()]) case "P": var children = [Node]() while !scanner.conditional(scalar: "_") { try children.append(demangleSwift3ProtocolName()) } - type = Node.create(kind: .protocolList, children: [Node.create(kind: .typeList, children: children)]) + type = createNode(kind: .protocolList, children: [createNode(kind: .typeList, children: children)]) case "Q": if scanner.conditional(scalar: "u") { type = NodeFactory.opaqueReturnType } else if scanner.conditional(scalar: "U") { let index = try demangleIndex() - type = Node.create(kind: .opaqueReturnType, child: Node.create(kind: .opaqueReturnTypeIndex, contents: .index(index))) + type = createNode(kind: .opaqueReturnType, child: createNode(kind: .opaqueReturnTypeIndex, contents: .index(index))) } else { type = try demangleSwift3ArchetypeType() } @@ -3912,36 +3919,36 @@ extension Demangler { } else { type = try demangleSwift3GenericParamIndex() } - case "x": type = Node.create(kind: .dependentGenericParamType, children: [Node.create(kind: .index, index: 0), Node.create(kind: .index, index: 0)]) + case "x": type = createNode(kind: .dependentGenericParamType, children: [createNode(kind: .index, index: 0), createNode(kind: .index, index: 0)]) case "w": type = try demangleSwift3AssociatedTypeSimple() case "W": type = try demangleSwift3AssociatedTypeCompound() - case "R": type = try Node.create(kind: .inOut, inlineChildren: demangleSwift3Type().children) + case "R": type = try createNode(kind: .inOut, inlineChildren: demangleSwift3Type().children) case "S": type = try demangleSwift3SubstitutionIndex() case "T": type = try demangleSwift3Tuple(variadic: false) case "t": type = try demangleSwift3Tuple(variadic: true) - case "u": type = try Node.create(kind: .dependentGenericType, children: [demangleSwift3GenericSignature(), demangleSwift3Type()]) + case "u": type = try createNode(kind: .dependentGenericType, children: [demangleSwift3GenericSignature(), demangleSwift3Type()]) case "C": type = try demangleSwift3DeclarationName(kind: .class) case "V": type = try demangleSwift3DeclarationName(kind: .structure) case "O": type = try demangleSwift3DeclarationName(kind: .enum) default: throw scanner.unexpectedError() } - return Node.create(kind: .type, children: [type]) + return createNode(kind: .type, children: [type]) } mutating func demangleSwift3ArchetypeType() throws(DemanglingError) -> Node { switch try scanner.readScalar() { case "Q": - let result = try Node.create(kind: .associatedTypeRef, children: [demangleSwift3ArchetypeType(), demangleSwift3Identifier()]) + let result = try createNode(kind: .associatedTypeRef, children: [demangleSwift3ArchetypeType(), demangleSwift3Identifier()]) nameStack.append(result) return result case "S": let index = try demangleSwift3SubstitutionIndex() - let result = try Node.create(kind: .associatedTypeRef, children: [index, demangleSwift3Identifier()]) + let result = try createNode(kind: .associatedTypeRef, children: [index, demangleSwift3Identifier()]) nameStack.append(result) return result case "s": - let root = Node.create(kind: .module, contents: .text(stdlibName)) - let result = try Node.create(kind: .associatedTypeRef, children: [root, demangleSwift3Identifier()]) + let root = createNode(kind: .module, contents: .text(stdlibName)) + let result = try createNode(kind: .associatedTypeRef, children: [root, demangleSwift3Identifier()]) nameStack.append(result) return result default: throw scanner.unexpectedError() @@ -3982,8 +3989,8 @@ extension Demangler { let convention = try demangleSwift3ImplConvention(kind: k) let type = try demangleSwift3Type() - let conventionNode = Node.create(kind: .implConvention, contents: .text(convention)) - return Node.create(kind: k, children: [conventionNode, type]) + let conventionNode = createNode(kind: .implConvention, contents: .text(convention)) + return createNode(kind: k, children: [conventionNode, type]) } mutating func demangleSwift3Tuple(variadic: Bool) throws(DemanglingError) -> Node { @@ -3995,12 +4002,12 @@ extension Demangler { try elementChildren.append(demangleSwift3Identifier(kind: .tupleElementName)) } try elementChildren.append(demangleSwift3Type()) - children.append(Node.create(kind: .tupleElement, children: elementChildren)) + children.append(createNode(kind: .tupleElement, children: elementChildren)) } if variadic, let last = children.popLast() { children.append(last.insertingChild(NodeFactory.variadicMarker, at: 0)) } - return Node.create(kind: .tuple, children: children) + return createNode(kind: .tuple, children: children) } mutating func demangleSwift3FunctionType(kind: Node.Kind) throws(DemanglingError) -> Node { @@ -4008,9 +4015,9 @@ extension Demangler { if scanner.conditional(scalar: "z") { children.append(NodeFactory.throwsAnnotation) } - try children.append(Node.create(kind: .argumentTuple, children: [demangleSwift3Type()])) - try children.append(Node.create(kind: .returnType, children: [demangleSwift3Type()])) - return Node.create(kind: kind, children: children) + try children.append(createNode(kind: .argumentTuple, children: [demangleSwift3Type()])) + try children.append(createNode(kind: .returnType, children: [demangleSwift3Type()])) + return createNode(kind: kind, children: children) } mutating func demangleSwift3Identifier(kind: Node.Kind? = nil) throws(DemanglingError) -> Node { @@ -4064,7 +4071,7 @@ extension Demangler { } } - return Node.create(kind: k, text: identifier) + return createNode(kind: k, text: identifier) } } diff --git a/Sources/Demangling/Main/Remangle/RemangleInterface.swift b/Sources/Demangling/Main/Remangle/RemangleInterface.swift index d06eb86..ee8204f 100644 --- a/Sources/Demangling/Main/Remangle/RemangleInterface.swift +++ b/Sources/Demangling/Main/Remangle/RemangleInterface.swift @@ -11,11 +11,18 @@ /// - flavor: The mangling flavor (default Swift, or embedded Swift) /// - Returns: The mangled string, or nil if remangling failed public func mangleAsString(_ node: Node, usePunycode: Bool = true, flavor: ManglingFlavor = .default) throws(ManglingError) -> String { + let budgetedMangleBlock: (UInt) throws(ManglingError) -> String? = { stackFloorAddress throws(ManglingError) in + var remangler = Remangler(usePunycode: usePunycode, flavor: flavor) + return try remangler.mangleWithinStackBudget(node, stackFloorAddress: stackFloorAddress) + } let mangleBlock: @Sendable () throws(ManglingError) -> String = { var remangler = Remangler(usePunycode: usePunycode, flavor: flavor) return try remangler.mangle(node) } - return try StackSafeExecutor.execute(mangleBlock) + return try StackSafeExecutor.executeWithinStackBudget( + budgetedAttempt: budgetedMangleBlock, + unbudgetedFallback: mangleBlock + ) } /// Asynchronous variant of ``mangleAsString(_:usePunycode:flavor:)``. @@ -32,6 +39,28 @@ public func mangleAsString(_ node: Node, usePunycode: Bool = true, flavor: Mangl return try await StackSafeExecutor.executeAsync(mangleBlock) } +// MARK: - Store-Backed Remangling + +/// Remangle any `DemanglingNode` representation — in particular a +/// `NodeReference` pointing into a `NodeStore`. +/// +/// The remangling algorithm constructs transient helper nodes while walking +/// (unspecialized nominals, SIL box layout wrappers), exactly like the C++ +/// `Remangler` does with its `NodeFactory` — it is not a read-only consumer, +/// so it runs on the class representation. This entry bridges by +/// materializing the subtree once (subtree sharing preserved); the cost is +/// transient and proportional to the subtree, and remangling's output is a +/// fresh `String` either way, so the store's resident-memory goals are +/// unaffected. +public func mangleAsString(_ node: some DemanglingNode, usePunycode: Bool = true, flavor: ManglingFlavor = .default) throws(ManglingError) -> String { + try mangleAsString(node.materializedNode, usePunycode: usePunycode, flavor: flavor) +} + +/// Asynchronous variant of the `DemanglingNode` overload. +public func mangleAsString(_ node: some DemanglingNode, usePunycode: Bool = true, flavor: ManglingFlavor = .default) async throws(ManglingError) -> String { + try await mangleAsString(node.materializedNode, usePunycode: usePunycode, flavor: flavor) +} + // MARK: - Validation Helpers /// Check if a node tree can be successfully remangled @@ -41,3 +70,8 @@ public func mangleAsString(_ node: Node, usePunycode: Bool = true, flavor: Mangl public func canMangle(_ node: Node) -> Bool { return (try? mangleAsString(node)) != nil } + +/// Check if any `DemanglingNode` representation can be successfully remangled. +public func canMangle(_ node: some DemanglingNode) -> Bool { + return (try? mangleAsString(node)) != nil +} diff --git a/Sources/Demangling/Main/Remangle/Remangler.swift b/Sources/Demangling/Main/Remangle/Remangler.swift index 6ec721d..bae2316 100644 --- a/Sources/Demangling/Main/Remangle/Remangler.swift +++ b/Sources/Demangling/Main/Remangle/Remangler.swift @@ -46,6 +46,14 @@ struct Remangler { /// Overflow storage for substitutions beyond inline capacity private var overflowSubstitutions: [SubstitutionEntry: UInt64] = [:] + /// Address the stack must not grow past, or 0 when this remangler may + /// recurse freely. See ``mangleWithinStackBudget(_:stackFloorAddress:)``. + private var stackFloorAddress: UInt = 0 + + /// Set once the walk stopped early because it reached ``stackFloorAddress``; + /// the partial ``buffer`` is then unusable. + private var didExhaustStackBudget: Bool = false + // MARK: - Initialization init(usePunycode: Bool, flavor: ManglingFlavor = .default) { @@ -349,10 +357,37 @@ struct Remangler { return buffer } + /// Remangles `node` only as long as the walk stays clear of + /// `stackFloorAddress`, returning `nil` the moment it would not. + /// + /// Mirrors ``DemanglingPrinter/printRootWithinStackBudget(_:stackFloorAddress:)``: + /// it lets a caller on a small stack run the common shallow tree inline and + /// pay for a large-stack thread only for the rare tree that needs one. + mutating func mangleWithinStackBudget(_ node: Node, stackFloorAddress: UInt) throws(ManglingError) -> String? { + self.stackFloorAddress = stackFloorAddress + clearBuffer() + try mangle(node, depth: 0) + return didExhaustStackBudget ? nil : buffer + } + // MARK: - Core Mangling /// Main entry point for mangling a single node private mutating func mangle(_ node: Node, depth: Int) throws(ManglingError) { + // Unwind the whole walk once any frame ran out of budget: the result is + // discarded, so continuing would only risk the overflow the budget + // exists to avoid. + if didExhaustStackBudget { + return + } + if stackFloorAddress != 0 { + var stackProbe = 0 + let currentAddress = withUnsafeMutablePointer(to: &stackProbe) { UInt(bitPattern: $0) } + if currentAddress <= stackFloorAddress { + didExhaustStackBudget = true + return + } + } // Check recursion depth if depth > Self.maxDepth { throw .tooComplex(node) diff --git a/Sources/Demangling/Main/TypeDecoder/TypeDecoder.swift b/Sources/Demangling/Main/TypeDecoder/TypeDecoder.swift index 962a0fb..bb60a09 100644 --- a/Sources/Demangling/Main/TypeDecoder/TypeDecoder.swift +++ b/Sources/Demangling/Main/TypeDecoder/TypeDecoder.swift @@ -1,36 +1,40 @@ -/// Decode a mangled type to construct an abstract type using a custom builder. -/// This is a Swifty implementation that uses throws for error handling. -public final class TypeDecoder { - public typealias BuiltType = Builder.BuiltType - public typealias BuiltTypeDecl = Builder.BuiltTypeDecl - public typealias BuiltProtocolDecl = Builder.BuiltProtocolDecl - public typealias Field = Builder.BuiltSILBoxField - public typealias BuiltSubstitution = Builder.BuiltSubstitution - public typealias BuiltRequirement = Builder.BuiltRequirement - public typealias BuiltInverseRequirement = Builder.BuiltInverseRequirement - public typealias BuiltLayoutConstraint = Builder.BuiltLayoutConstraint - public typealias BuiltGenericSignature = Builder.BuiltGenericSignature - public typealias BuiltSubstitutionMap = Builder.BuiltSubstitutionMap +/// Generic type-decoding engine shared by the public `TypeDecoder` +/// (specialized on `Node`) and store-backed decoding (specialized on +/// `NodeReference`). The tree walk is representation-agnostic; the +/// `TypeBuilder` handoff points pass `materializedNode` so the public +/// `TypeBuilder` protocol keeps receiving concrete `Node` subtrees. +/// See evolution proposal 0001, Phase 2. +struct TypeDecoderEngine { + typealias BuiltType = Builder.BuiltType + typealias BuiltTypeDecl = Builder.BuiltTypeDecl + typealias BuiltProtocolDecl = Builder.BuiltProtocolDecl + typealias Field = Builder.BuiltSILBoxField + typealias BuiltSubstitution = Builder.BuiltSubstitution + typealias BuiltRequirement = Builder.BuiltRequirement + typealias BuiltInverseRequirement = Builder.BuiltInverseRequirement + typealias BuiltLayoutConstraint = Builder.BuiltLayoutConstraint + typealias BuiltGenericSignature = Builder.BuiltGenericSignature + typealias BuiltSubstitutionMap = Builder.BuiltSubstitutionMap private let builder: Builder private static var maxDepth: Int { 1024 } - public init(builder: Builder) { + init(builder: Builder) { self.builder = builder } /// Given a demangle tree, attempt to turn it into a type. - public func decodeMangledType(node: Node, forRequirement: Bool = true) throws(TypeLookupError) -> BuiltType { + func decodeMangledType(node: SomeNode, forRequirement: Bool = true) throws(TypeLookupError) -> BuiltType { try decodeMangledType(node: node, depth: 0, forRequirement: forRequirement) } } // MARK: - Main Type Decoding -extension TypeDecoder { +extension TypeDecoderEngine { private func decodeMangledType( - node: Node, + node: SomeNode, depth: Int, forRequirement: Bool = true ) throws(TypeLookupError) -> BuiltType { @@ -174,7 +178,7 @@ extension TypeDecoder { case .builtinTypeName: let mangling: String do { - mangling = try mangleAsString(node, flavor: builder.getManglingFlavor()) + mangling = try mangleAsString(node.materializedNode, flavor: builder.getManglingFlavor()) } catch { throw TypeLookupError(node: node, message: "failed to mangle node") } @@ -219,7 +223,7 @@ extension TypeDecoder { let shapeNode = node.children[0] let args = try decodeGenericArgs(node: node.children[1], depth: depth + 1) - return builder.createSymbolicExtendedExistentialType(shapeNode: shapeNode, args: args) + return builder.createSymbolicExtendedExistentialType(shapeNode: shapeNode.materializedNode, args: args) case .protocolList, .protocolListWithAnyObject, @@ -942,7 +946,7 @@ extension TypeDecoder { genericArgs.append(genericArgsBuf[start ..< end]) } - return builder.resolveOpaqueType(descriptor: descriptor, genericArgs: genericArgs, ordinal: ordinal) + return builder.resolveOpaqueType(descriptor: descriptor.materializedNode, genericArgs: genericArgs, ordinal: ordinal) case .integer: guard let index = node.index else { @@ -970,9 +974,9 @@ extension TypeDecoder { } } -extension TypeDecoder { +extension TypeDecoderEngine { private func decodeTypeSequenceElement( - node: Node, + node: SomeNode, depth: Int, resultCallback: (BuiltType) throws(TypeLookupError) -> Void ) throws(TypeLookupError) { @@ -1004,7 +1008,7 @@ extension TypeDecoder { } private func decodeImplFunctionParam( - node: Node, + node: SomeNode, depth: Int, results: inout [T] ) throws(TypeLookupError) where T.BuiltTypeParam == BuiltType { @@ -1055,7 +1059,7 @@ extension TypeDecoder { } private func decodeImplFunctionResult( - node: Node, + node: SomeNode, depth: Int, results: inout [T] ) throws(TypeLookupError) where T.BuiltTypeParam == BuiltType { @@ -1104,7 +1108,7 @@ extension TypeDecoder { results.append(result) } - private func decodeGenericArgs(node: Node, depth: Int) throws(TypeLookupError) -> [BuiltType] { + private func decodeGenericArgs(node: SomeNode, depth: Int) throws(TypeLookupError) -> [BuiltType] { guard node.kind == .typeList else { throw TypeLookupError(node: node, message: "is not TypeList") } @@ -1118,7 +1122,7 @@ extension TypeDecoder { } private func decodeMangledTypeDecl( - node: Node, + node: SomeNode, depth: Int, typeDecl: inout BuiltTypeDecl?, parent: inout BuiltType?, @@ -1138,10 +1142,12 @@ extension TypeDecoder { ) } + // The decl handoff to `TypeBuilder` requires the class representation, + // so `declNode` is materialized here (free on the `Node` path). var declNode: Node if node.kind == .typeSymbolicReference { // A symbolic reference can be directly resolved to a nominal type - declNode = node + declNode = node.materializedNode } else { guard node.children.count >= 2 else { throw TypeLookupError(node: node, message: "Number of node children (\(node.children.count)) less than required (2)") @@ -1154,7 +1160,7 @@ extension TypeDecoder { // in addition to a reference to the parent type. The // mangled name already includes the module and parent // types, if any. - declNode = node + declNode = node.materializedNode switch parentContext.kind { case .module: @@ -1176,7 +1182,7 @@ extension TypeDecoder { // Remove any generic arguments from the context node, producing a // node that references the nominal type declaration. - if let unspecNode = getUnspecialized(node) { + if let unspecNode = getUnspecialized(declNode) { declNode = unspecNode } else { throw TypeLookupError("Failed to unspecialize type") @@ -1190,7 +1196,7 @@ extension TypeDecoder { } } - private func decodeMangledProtocolType(node: Node, depth: Int) -> BuiltProtocolDecl? { + private func decodeMangledProtocolType(node: SomeNode, depth: Int) -> BuiltProtocolDecl? { guard depth <= Self.maxDepth else { return nil } @@ -1216,11 +1222,11 @@ extension TypeDecoder { } #endif - return builder.createProtocolDecl(node: node) + return builder.createProtocolDecl(node: node.materializedNode) } private func decodeMangledFunctionInputType( - node: Node, + node: SomeNode, depth: Int, params: inout [FunctionParam], hasParamFlags: inout Bool @@ -1241,7 +1247,7 @@ extension TypeDecoder { return } - func decodeParamTypeAndFlags(node: Node, param: inout FunctionParam) throws(TypeLookupError) { + func decodeParamTypeAndFlags(node: SomeNode, param: inout FunctionParam) throws(TypeLookupError) { var node = node var recurse = true while recurse { @@ -1290,7 +1296,7 @@ extension TypeDecoder { } } - func decodeParam(node: Node) throws(TypeLookupError) { + func decodeParam(node: SomeNode) throws(TypeLookupError) { guard node.kind == .tupleElement else { return } @@ -1330,7 +1336,7 @@ extension TypeDecoder { } } -extension TypeDecoder { +extension TypeDecoderEngine { private func functionConvention(for kind: Node.Kind) -> FunctionMetadataConvention { switch kind { case .objCBlock, @@ -1357,8 +1363,8 @@ extension TypeDecoder { } } -private func decodeRequirements( - node: Node, +private func decodeRequirements( + node: SomeNode, requirements: inout [BuilderType.BuiltRequirement], inverseRequirements: inout [BuilderType.BuiltInverseRequirement], builder: BuilderType @@ -1379,16 +1385,16 @@ private func decodeRequirements( } // Decode subject type - let subjectType = try builder.decodeMangledType(node: child.children[0], forRequirement: true) + let subjectType = try builder.decodeMangledType(node: child.children[0].materializedNode, forRequirement: true) switch child.kind { case .dependentGenericConformanceRequirement: - let constraintType = try builder.decodeMangledType(node: child.children[1], forRequirement: true) + let constraintType = try builder.decodeMangledType(node: child.children[1].materializedNode, forRequirement: true) let kind: RequirementKind = builder.isExistential(type: constraintType) ? .conformance : .superclass requirements.append(builder.createRequirement(kind: kind, subjectType: subjectType, constraintType: constraintType)) case .dependentGenericSameTypeRequirement: - let constraintType = try builder.decodeMangledType(node: child.children[1], forRequirement: false) + let constraintType = try builder.decodeMangledType(node: child.children[1].materializedNode, forRequirement: false) requirements.append(builder.createRequirement(kind: .sameType, subjectType: subjectType, constraintType: constraintType)) case .dependentGenericInverseConformanceRequirement: @@ -1443,7 +1449,7 @@ private func decodeRequirements( } #if canImport(ObjectiveC) -private func getObjCClassOrProtocolName(_ node: Node) -> String? { +private func getObjCClassOrProtocolName(_ node: some DemanglingNode) -> String? { guard node.kind == .class || node.kind == .protocol else { return nil } @@ -1467,3 +1473,45 @@ private func getObjCClassOrProtocolName(_ node: Node) -> String? { return nameNode.text } #endif + +// MARK: - Public facade + +/// Decode a mangled type to construct an abstract type using a custom builder. +/// This is a Swifty implementation that uses throws for error handling. +/// +/// A thin, source-compatible facade over `TypeDecoderEngine`. The decoding +/// logic lives in the generic engine so it can also decode `NodeReference` +/// trees straight from a `NodeStore` without materializing the class tree +/// (`TypeBuilder` implementations keep receiving concrete `Node` subtrees at +/// the handoff points). +public final class TypeDecoder { + public typealias BuiltType = Builder.BuiltType + public typealias BuiltTypeDecl = Builder.BuiltTypeDecl + public typealias BuiltProtocolDecl = Builder.BuiltProtocolDecl + public typealias Field = Builder.BuiltSILBoxField + public typealias BuiltSubstitution = Builder.BuiltSubstitution + public typealias BuiltRequirement = Builder.BuiltRequirement + public typealias BuiltInverseRequirement = Builder.BuiltInverseRequirement + public typealias BuiltLayoutConstraint = Builder.BuiltLayoutConstraint + public typealias BuiltGenericSignature = Builder.BuiltGenericSignature + public typealias BuiltSubstitutionMap = Builder.BuiltSubstitutionMap + + private let builder: Builder + + public init(builder: Builder) { + self.builder = builder + } + + /// Given a demangle tree, attempt to turn it into a type. + public func decodeMangledType(node: Node, forRequirement: Bool = true) throws(TypeLookupError) -> BuiltType { + try TypeDecoderEngine(builder: builder) + .decodeMangledType(node: node, forRequirement: forRequirement) + } + + /// Store-backed variant: decodes straight from a `NodeStore` without + /// materializing a `Node` tree (proposal 0001, Phase 2). + public func decodeMangledType(node: NodeReference, forRequirement: Bool = true) throws(TypeLookupError) -> BuiltType { + try TypeDecoderEngine(builder: builder) + .decodeMangledType(node: node, forRequirement: forRequirement) + } +} diff --git a/Sources/Demangling/Main/TypeDecoder/TypeLookupError.swift b/Sources/Demangling/Main/TypeDecoder/TypeLookupError.swift index 71b2582..ff93ee9 100644 --- a/Sources/Demangling/Main/TypeDecoder/TypeLookupError.swift +++ b/Sources/Demangling/Main/TypeDecoder/TypeLookupError.swift @@ -14,9 +14,9 @@ public struct TypeLookupError: Error, CustomStringConvertible, Sendable { } /// Initialize from a node error - public init(node: Node, message: String, file: String = #file, line: Int = #line) { + public init(node: some DemanglingNode, message: String, file: String = #file, line: Int = #line) { let nodeInfo = "Node kind \(node.kind)" - let textInfo = node.hasText ? " \"\(node.text ?? "")\"" : "" + let textInfo = node.text.map { " \"\($0)\"" } ?? "" self.message = "\(nodeInfo)\(textInfo) - \(message)" self.file = file self.line = line diff --git a/Sources/Demangling/Node/Node+Children.swift b/Sources/Demangling/Node/Node+Children.swift index da99216..9d9410d 100644 --- a/Sources/Demangling/Node/Node+Children.swift +++ b/Sources/Demangling/Node/Node+Children.swift @@ -322,11 +322,6 @@ extension Node.Children { at(index) } - @inlinable - public var second: Node? { - at(1) - } - @inlinable public func reversedFirst(_ count: Int) -> Self { var result = self diff --git a/Sources/Demangling/Node/Node+Conversions.swift b/Sources/Demangling/Node/Node+Conversions.swift index ec2075f..9a98520 100644 --- a/Sources/Demangling/Node/Node+Conversions.swift +++ b/Sources/Demangling/Node/Node+Conversions.swift @@ -73,13 +73,9 @@ extension Node { } } - @inlinable - public var hasChildren: Bool { - switch payload { - case .none, .index, .text: return false - default: return true - } - } + // `hasChildren` and `subscript(throwChild:)` live in the `DemanglingNode` + // extension (DemanglingNode.swift) as the single implementation for both + // `Node` and `NodeReference`. @inlinable public var firstChild: Node? { @@ -113,90 +109,11 @@ extension Node { } } - public var isSimpleType: Bool { - switch kind { - case .associatedType: fallthrough - case .associatedTypeRef: fallthrough - case .boundGenericClass: fallthrough - case .boundGenericEnum: fallthrough - case .boundGenericFunction: fallthrough - case .boundGenericOtherNominalType: fallthrough - case .boundGenericProtocol: fallthrough - case .boundGenericStructure: fallthrough - case .boundGenericTypeAlias: fallthrough - case .builtinBorrow: fallthrough - case .builtinTypeName: fallthrough - case .builtinTupleType: fallthrough - case .builtinFixedArray: fallthrough - case .class: fallthrough - case .dependentGenericType: fallthrough - case .dependentMemberType: fallthrough - case .dependentGenericParamType: fallthrough - case .dynamicSelf: fallthrough - case .enum: fallthrough - case .errorType: fallthrough - case .existentialMetatype: fallthrough - case .integer: fallthrough - case .labelList: fallthrough - case .metatype: fallthrough - case .metatypeRepresentation: fallthrough - case .module: fallthrough - case .negativeInteger: fallthrough - case .otherNominalType: fallthrough - case .pack: fallthrough - case .protocol: fallthrough - case .protocolSymbolicReference: fallthrough - case .returnType: fallthrough - case .silBoxType: fallthrough - case .silBoxTypeWithLayout: fallthrough - case .structure: fallthrough - case .sugaredArray: fallthrough - case .sugaredDictionary: fallthrough - case .sugaredOptional: fallthrough - case .sugaredInlineArray: fallthrough - case .sugaredParen: return true - case .tuple: fallthrough - case .tupleElementName: fallthrough - case .typeAlias: fallthrough - case .typeList: fallthrough - case .typeSymbolicReference: return true - case .type: - return children.first.map { $0.isSimpleType } ?? false - case .protocolList: - return children.first.map { $0.children.count <= 1 } ?? false - case .protocolListWithAnyObject: - return (children.first?.children.first).map { $0.children.count == 0 } ?? false - default: return false - } - } - - public var needSpaceBeforeType: Bool { - switch kind { - case .type: return children.first?.needSpaceBeforeType ?? false - case .functionType, - .noEscapeFunctionType, - .uncurriedFunctionType, - .dependentGenericType: return false - default: return true - } - } - - @inlinable - public func isIdentifier(desired: String) -> Bool { - return kind == .identifier && text == desired - } - - @inlinable - public var isSwiftModule: Bool { - return kind == .module && text == stdlibName - } -} - -extension Node { - @inlinable - public func isKind(of kinds: Node.Kind...) -> Bool { - return kinds.contains(kind) - } + // `isSimpleType`, `needSpaceBeforeType`, `isIdentifier(desired:)`, and + // `isSwiftModule` live in the `DemanglingNode` protocol extension + // (DemanglingNode.swift) as the single implementation for both `Node` + // and `NodeReference` — a parallel copy here would drift, since the + // generic printer engine statically dispatches to the extension. } extension Node { @@ -210,17 +127,6 @@ extension Node { children.at(childIndex) } - @inlinable - public subscript(throwChild childIndex: Int) -> Node { - get throws(IndexOutOfBoundError) { - if let child = children.at(childIndex) { - return child - } else { - throw .default - } - } - } - public struct IndexOutOfBoundError: Error { public static let `default` = IndexOutOfBoundError() } @@ -247,19 +153,6 @@ extension Node { return depths } - public var identifier: String? { - if let node = children.at(1), node.kind == .identifier { - return node.text - } else if let node = children.at(1), node.kind == .privateDeclName { - return node.children.at(1)?.text - } else if let node = first(of: .prefixOperator, .postfixOperator, .infixOperator) { - return node.text - } else if let node = first(of: .identifier) { - return node.text - } else if let node = first(of: .privateDeclName) { - return node.children.at(1)?.text - } else { - return nil - } - } + // `identifier` lives in DemanglingNode+Sequence.swift as the single + // implementation shared with `NodeReference`. } diff --git a/Sources/Demangling/Node/Node+CustomStringConvertible.swift b/Sources/Demangling/Node/Node+CustomStringConvertible.swift index e9f3d0a..c112ce6 100644 --- a/Sources/Demangling/Node/Node+CustomStringConvertible.swift +++ b/Sources/Demangling/Node/Node+CustomStringConvertible.swift @@ -14,10 +14,7 @@ extension Node: CustomStringConvertible { /// - Parameter options: an option set containing the different `DemangleOptions` from the Swift project. /// - Returns: `self` printed to a string according to the specified options. public func print(using options: DemangleOptions = .default) -> String { - StackSafeExecutor.execute { - var printer = NodePrinter(options: options) - return printer.printRoot(self) - } + NodePrinter.print(self, options: options) } /// Asynchronous variant of ``print(using:)``. diff --git a/Sources/Demangling/Node/Node+Init.swift b/Sources/Demangling/Node/Node+Init.swift index 3ab8bd8..ff283ee 100644 --- a/Sources/Demangling/Node/Node+Init.swift +++ b/Sources/Demangling/Node/Node+Init.swift @@ -65,6 +65,40 @@ extension Node { } } +// MARK: - Transient Factory Methods (no interning) + +/// Transient counterparts of `Node.create(...)` that never touch +/// `NodeCache.shared`: leaves are freshly allocated instead of interned, so +/// nothing gets pinned in global state for the process lifetime. +/// +/// Exported via `@_spi(Internals)` for consumers whose whole pipeline runs +/// off the global cache (transient demangling feeding a `NodeStoreBuilder`, +/// symbolic-reference resolvers building splice nodes, and similar). The +/// returned nodes are NOT canonical: structurally equal nodes are distinct +/// instances, so `===`-based sharing assumptions do not apply. +@_spi(Internals) +extension Node { + public static func createTransient(kind: Kind, contents: Contents = .none, children: [Node] = []) -> Node { + Node(kind: kind, contents: contents, children: children) + } + + public static func createTransient(kind: Kind, contents: Contents = .none, inlineChildren: Children) -> Node { + Node(kind: kind, contents: contents, inlineChildren: inlineChildren) + } + + public static func createTransient(kind: Kind, child: Node) -> Node { + Node(kind: kind, contents: .none, children: [child]) + } + + public static func createTransient(kind: Kind, text: String, children: [Node] = []) -> Node { + Node(kind: kind, contents: .text(text), children: children) + } + + public static func createTransient(kind: Kind, index: UInt64, children: [Node] = []) -> Node { + Node(kind: kind, contents: .index(index), children: children) + } +} + extension Node { @inlinable public static func create(kind: Kind, contents: Contents = .none, @ArrayBuilder childrenBuilder: () -> [Node]) -> Node { diff --git a/Sources/Demangling/Node/Node+Sequence.swift b/Sources/Demangling/Node/Node+Sequence.swift index d674f6e..fdaea90 100644 --- a/Sources/Demangling/Node/Node+Sequence.swift +++ b/Sources/Demangling/Node/Node+Sequence.swift @@ -1,163 +1,7 @@ -extension Node { - public func preorder() -> some Sequence { - PreorderSequence(root: self) - } - - public func inorder() -> some Sequence { - InorderSequence(root: self) - } - - public func postorder() -> some Sequence { - PostorderSequence(root: self) - } - - public func levelorder() -> some Sequence { - LevelorderSequence(root: self) - } - - private struct PreorderSequence: Sequence { - struct Iterator: IteratorProtocol { - private var stack: [Node] - - fileprivate init(root: Node) { - self.stack = [root] - } - - mutating func next() -> Node? { - guard !stack.isEmpty else { return nil } - - let current = stack.removeLast() - - // Add children in reverse order so we visit them left-to-right - for child in current.children.reversed() { - stack.append(child) - } - - return current - } - } - - private let root: Node - - fileprivate init(root: Node) { - self.root = root - } - - func makeIterator() -> Iterator { - Iterator(root: root) - } - } - - private struct InorderSequence: Sequence { - struct Iterator: IteratorProtocol { - private var stack: [Node] - private var current: Node? - - fileprivate init(root: Node) { - self.stack = [] - self.current = root - } - - mutating func next() -> Node? { - while current != nil || !stack.isEmpty { - // Go to the leftmost node - while let node = current { - stack.append(node) - current = node.children.first - } - - // Current must be nil at this point - if let node = stack.popLast() { - current = node.children.count > 1 ? node.children[1] : nil - return node - } - } - return nil - } - } - - private let root: Node - - fileprivate init(root: Node) { - self.root = root - } - - func makeIterator() -> Iterator { - Iterator(root: root) - } - } - - private struct PostorderSequence: Sequence { - struct Iterator: IteratorProtocol { - private var stack: [(node: Node, visited: Bool)] - - fileprivate init(root: Node) { - self.stack = [(root, false)] - } - - mutating func next() -> Node? { - while !stack.isEmpty { - let (node, visited) = stack.removeLast() - - if visited { - return node - } else { - // Mark as visited and push back - stack.append((node, true)) - - // Push children in reverse order - for child in node.children.reversed() { - stack.append((child, false)) - } - } - } - return nil - } - } - - private let root: Node - - fileprivate init(root: Node) { - self.root = root - } - - func makeIterator() -> Iterator { - Iterator(root: root) - } - } - - private struct LevelorderSequence: Sequence { - struct Iterator: IteratorProtocol { - private var queue: [Node] - - fileprivate init(root: Node) { - self.queue = [root] - } - - mutating func next() -> Node? { - guard !queue.isEmpty else { return nil } - - let current = queue.removeFirst() - - // Add all children to the queue - queue.append(contentsOf: current.children) - - return current - } - } - - private let root: Node - - fileprivate init(root: Node) { - self.root = root - } - - func makeIterator() -> Iterator { - Iterator(root: root) - } - } -} - +// The traversal machinery (preorder/inorder/postorder/levelorder) and the +// kind-lookup helpers (first(of:), all(of:), contains(_:), filter(of:)) live +// in DemanglingNode+Sequence.swift as single generic implementations shared +// with NodeReference — do not re-add Node-specific copies here. extension Node: Sequence { public typealias Element = Node @@ -165,50 +9,3 @@ extension Node: Sequence { preorder().makeIterator() } } - -extension Sequence where Element == Node { - @inlinable - public func first(of kind: Node.Kind) -> Node? { - first { $0.kind == kind } - } - - @inlinable - public func first(of kinds: Node.Kind...) -> Node? { - first { kinds.contains($0.kind) } - } - - @inlinable - public func contains(_ kind: Node.Kind) -> Bool { - contains { $0.kind == kind } - } - - @inlinable - public func contains(_ kinds: Node.Kind...) -> Bool { - contains { kinds.contains($0.kind) } - } - - @inlinable - public func all(of kind: Node.Kind) -> [Node] { - filter { $0.kind == kind } - } - - @inlinable - public func all(of kinds: Node.Kind...) -> [Node] { - filter { kinds.contains($0.kind) } - } - - @inlinable - public func all(of kinds: [Node.Kind]) -> [Node] { - filter { kinds.contains($0.kind) } - } - - @inlinable - public func filter(of kind: Node.Kind) -> some Sequence { - filter { $0.kind == kind } - } - - @inlinable - public func filter(of kinds: Node.Kind...) -> some Sequence { - filter { kinds.contains($0.kind) } - } -} diff --git a/Sources/Demangling/Node/Printer/NodePrinter.swift b/Sources/Demangling/Node/Printer/NodePrinter.swift index 26a5ea0..7020957 100644 --- a/Sources/Demangling/Node/Printer/NodePrinter.swift +++ b/Sources/Demangling/Node/Printer/NodePrinter.swift @@ -1,4 +1,19 @@ -public struct NodePrinter: Sendable { +/// Generic tree-printing engine shared by the public `NodePrinter` +/// (specialized on `Node`) and store-backed printing (specialized on +/// `NodeReference`). See evolution proposal 0001, Phase 2. +/// +/// SPI note: exposed as `@_spi(Internals)` so deep consumers (MachOSwiftSection) +/// can print `NodeReference` trees into custom rich targets. On the store path +/// `NodePrintContext.node` receives nil (`name as? Node`); a rich target that +/// keys on it must account for that until the context is abstracted over +/// `DemanglingNode`. The type-reference scope hooks do receive a node on both +/// paths, but only the `Node` path delivers a canonical instance: store-backed +/// printing materializes a fresh subtree per evaluation, so scopes must be +/// keyed by structure (e.g. the remangled string) rather than by +/// `===`/`ObjectIdentifier`. Stack safety is built into ``print(_:options:)`` — +/// use it rather than wrapping ``printRoot(_:)`` at the call site. +@_spi(Internals) +public struct DemanglingPrinter: Sendable { /// Mirrors ``swift::Demangle::NodePrinter::MaxDepth`` from /// ``swift/include/swift/Demangling/Demangle.h``. Bails the print /// recursion with ``<>`` once a single root-to-leaf path @@ -12,8 +27,14 @@ public struct NodePrinter: Sendable { private var hidingCurrentModule: String = "" private var dependentMemberTypeDepth: Int = 0 private var printDepth: Int = 0 + /// Address the stack must not grow past, or 0 when this printer may recurse + /// freely. See ``printRootWithinStackBudget(_:)``. + private var stackFloorAddress: UInt = 0 + /// Set once the recursion stopped early because it reached + /// ``stackFloorAddress``; the partial ``target`` is then unusable. + private var didExhaustStackBudget: Bool = false /// Memoizes the rendered fragment for each shared substitution node. - /// The demangler returns the same ``Node`` instance for every + /// The demangler returns the same ``SomeNode`` instance for every /// back-reference (``A23_`` etc.), so one mangling can produce a DAG /// that — naively walked child-by-child — expands into hundreds of /// thousands of node visits (a SwiftUI ``View.Body`` typealias was @@ -22,7 +43,7 @@ public struct NodePrinter: Sendable { /// brings cost back to the size of the unique node set instead of the /// exponential expansion. Apple's own C++ ``NodePrinter`` does not do /// this (so ``swift demangle`` itself hangs on the same input). - private var printCache: [ObjectIdentifier: Target] = [:] + private var printCache: [SomeNode.PrintCacheIdentity: Target] = [:] public init(options: DemangleOptions = .default) { self.target = .init() @@ -30,12 +51,66 @@ public struct NodePrinter: Sendable { self.options = options } - public mutating func printRoot(_ root: Node) -> Target { + /// Prints `root` with stack safety built in — the entry point callers + /// should reach for. + /// + /// Runs inline on the calling thread while the recursion stays clear of the + /// stack end, and re-runs on a large-stack worker only for a tree that + /// genuinely needs one. Stack safety belongs here rather than at each call + /// site: it used to be the caller's job to wrap `printRoot` in + /// `StackSafeExecutor`, and a call site that forgot (MachOSwiftSection's + /// `printSemantic` did) silently lost the protection entirely. + /// + /// This is `static` by necessity — the fallback needs a pristine printer, + /// and an instance that already gave up mid-walk cannot re-run itself. + public static func print(_ root: SomeNode, options: DemangleOptions = .default) -> Target { + StackSafeExecutor.executeWithinStackBudget { stackFloorAddress in + var printer = DemanglingPrinter(options: options) + return printer.printRootWithinStackBudget(root, stackFloorAddress: stackFloorAddress) + } unbudgetedFallback: { + var printer = DemanglingPrinter(options: options) + return printer.printRoot(root) + } + } + + /// Prints `root` on the current thread with no stack guard. + /// + /// Prefer ``print(_:options:)``; reach for this only when the caller + /// already knows it has stack headroom (for example inside a large-stack + /// worker) and wants to manage the printer instance itself. + public mutating func printRoot(_ root: SomeNode) -> Target { _ = printName(root) return target } - private mutating func printName(_ name: Node, asPrefixContext: Bool = false) -> Node? { + /// Prints `root` only as long as the recursion stays clear of + /// `stackFloorAddress`, returning `nil` the moment it would not. + /// + /// Lets a caller on a small stack — every Swift Concurrency cooperative + /// worker and every libdispatch worker gets 512KB — run the overwhelmingly + /// common shallow symbol inline, and pay for a large-stack thread only for + /// the rare input that genuinely needs one. + public mutating func printRootWithinStackBudget(_ root: SomeNode, stackFloorAddress: UInt) -> Target? { + self.stackFloorAddress = stackFloorAddress + _ = printName(root) + return didExhaustStackBudget ? nil : target + } + + private mutating func printName(_ name: SomeNode, asPrefixContext: Bool = false) -> SomeNode? { + // Unwind the whole recursion once any frame ran out of budget: the + // result is discarded, so continuing would only risk the overflow the + // budget exists to avoid. + if didExhaustStackBudget { + return nil + } + if stackFloorAddress != 0 { + var stackProbe = 0 + let currentAddress = withUnsafeMutablePointer(to: &stackProbe) { UInt(bitPattern: $0) } + if currentAddress <= stackFloorAddress { + didExhaustStackBudget = true + return nil + } + } if printDepth > Self.maxPrintDepth { target.write("<>") return nil @@ -45,7 +120,7 @@ public struct NodePrinter: Sendable { // the rendered output for the same node, so those calls bypass the // cache to stay correct. let canCache = !asPrefixContext && dependentMemberTypeDepth == 0 - if canCache, let cached = printCache[ObjectIdentifier(name)] { + if canCache, let cached = printCache[name.printCacheIdentity] { target.append(cached) return nil } @@ -59,14 +134,14 @@ public struct NodePrinter: Sendable { swap(&target, &subTarget) let result = dispatchPrintName(name, asPrefixContext: asPrefixContext) swap(&target, &subTarget) - printCache[ObjectIdentifier(name)] = subTarget + printCache[name.printCacheIdentity] = subTarget target.append(subTarget) return result } return dispatchPrintName(name, asPrefixContext: asPrefixContext) } - private mutating func dispatchPrintName(_ name: Node, asPrefixContext: Bool = false) -> Node? { + private mutating func dispatchPrintName(_ name: SomeNode, asPrefixContext: Bool = false) -> SomeNode? { switch name.kind { case .accessibleFunctionRecord: if !options.contains(.shortenThunk) { @@ -181,7 +256,11 @@ public struct NodePrinter: Sendable { // The full qualified-name print (module, dots, identifier) for // this nominal reference runs inside one scope so rich targets // can group the writes into a single span keyed by `name`. - target.pushTypeReferenceScope(name) + // The autoclosure defers materialization: targets that ignore + // scopes (String) never evaluate it, so store-backed plain-text + // printing stays allocation-free; rich targets materialize only + // this nominal reference's small subtree. + target.pushTypeReferenceScope(name.materializedNode) defer { target.popTypeReferenceScope() } return printEntity(name, asPrefixContext: asPrefixContext, typePrinting: .noType, hasName: true) case .classMetadataBaseOffset: printFirstChild(name, prefix: "class metadata base offset for ") @@ -549,7 +628,7 @@ public struct NodePrinter: Sendable { return nil } - private func shouldPrintContext(_ context: Node) -> Bool { + private func shouldPrintContext(_ context: SomeNode) -> Bool { guard options.contains(.qualifyEntities) else { return false } @@ -570,7 +649,7 @@ public struct NodePrinter: Sendable { return true } - private mutating func printOptional(_ optional: Node?, prefix: String? = nil, suffix: String? = nil, asPrefixContext: Bool = false) -> Node? { + private mutating func printOptional(_ optional: SomeNode?, prefix: String? = nil, suffix: String? = nil, asPrefixContext: Bool = false) -> SomeNode? { guard let o = optional else { return nil } prefix.map { target.write($0) } let r = printName(o) @@ -578,11 +657,11 @@ public struct NodePrinter: Sendable { return r } - private mutating func printFirstChild(_ ofName: Node, prefix: String? = nil, suffix: String? = nil, asPrefixContext: Bool = false) { + private mutating func printFirstChild(_ ofName: SomeNode, prefix: String? = nil, suffix: String? = nil, asPrefixContext: Bool = false) { _ = printOptional(ofName.children.at(0), prefix: prefix, suffix: suffix) } - private mutating func printSequence(_ names: S, prefix: String? = nil, suffix: String? = nil, separator: String? = nil) where S: Sequence, S.Element == Node { + private mutating func printSequence(_ names: S, prefix: String? = nil, suffix: String? = nil, separator: String? = nil) where S: Sequence, S.Element == SomeNode { var isFirst = true prefix.map { target.write($0) } for c in names { @@ -596,15 +675,15 @@ public struct NodePrinter: Sendable { suffix.map { target.write($0) } } - private mutating func printChildren(_ ofName: Node, prefix: String? = nil, suffix: String? = nil, separator: String? = nil) { + private mutating func printChildren(_ ofName: SomeNode, prefix: String? = nil, suffix: String? = nil, separator: String? = nil) { printSequence(ofName.children, prefix: prefix, suffix: suffix, separator: separator) } - private mutating func printMacro(name: Node, asPrefixContext: Bool, label: String) -> Node? { + private mutating func printMacro(name: SomeNode, asPrefixContext: Bool, label: String) -> SomeNode? { return printEntity(name, asPrefixContext: asPrefixContext, typePrinting: .noType, hasName: true, extraName: "\(label) macro @\(name.children.at(2)?.print(using: options) ?? "") expansion #", extraIndex: (name.children.at(3)?.index ?? 0) + 1) } - private mutating func printAnonymousContext(_ name: Node) { + private mutating func printAnonymousContext(_ name: SomeNode) { if options.contains(.qualifyEntities), options.contains(.displayExtensionContexts) { _ = printOptional(name.children.at(1)) target.write(".(unknown context at " + (name.children.first?.text ?? "") + ")") @@ -616,7 +695,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printExtension(_ name: Node) { + private mutating func printExtension(_ name: SomeNode) { if options.contains(.qualifyEntities), options.contains(.displayExtensionContexts) { printFirstChild(name, prefix: "(extension in ", suffix: "):", asPrefixContext: true) } @@ -624,25 +703,25 @@ public struct NodePrinter: Sendable { _ = printOptional(!options.contains(.printForTypeName) ? name.children.at(2) : nil) } - private mutating func printSuffix(_ name: Node) { + private mutating func printSuffix(_ name: SomeNode) { if options.contains(.displayUnmangledSuffix) { target.write(" with unmangled suffix ") quotedString(name.text ?? "") } } - private mutating func printPrivateDeclName(_ name: Node) { + private mutating func printPrivateDeclName(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: options.contains(.showPrivateDiscriminators) ? "(" : nil) target.write(options.contains(.showPrivateDiscriminators) ? "\(name.children.count > 1 ? " " : "(")in \(name.children.at(0)?.text ?? ""))" : "") } - private mutating func printModule(_ name: Node) { + private mutating func printModule(_ name: SomeNode) { if options.contains(.displayModuleNames) { - target.write(name.text ?? "", context: .context(for: name, state: .printModule)) + target.write(name.text ?? "", context: .context(for: name as? Node, state: .printModule)) } } - private mutating func printReturnType(_ name: Node) { + private mutating func printReturnType(_ name: SomeNode) { if name.children.isEmpty, let t = name.text { target.write(t) } else { @@ -650,13 +729,13 @@ public struct NodePrinter: Sendable { } } - private mutating func printRetroactiveConformance(_ name: Node) { + private mutating func printRetroactiveConformance(_ name: SomeNode) { if name.children.count == 2 { printChildren(name, prefix: "retroactive @ ") } } - private mutating func printGenericSpecializationParam(_ name: Node) { + private mutating func printGenericSpecializationParam(_ name: SomeNode) { printFirstChild(name) _ = printOptional(name.children.at(1), prefix: " with ") for slouse in name.children.slice(2, name.children.endIndex) { @@ -665,7 +744,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printFunctionSignatureSpecializationParam(_ name: Node) { + private mutating func printFunctionSignatureSpecializationParam(_ name: SomeNode) { var idx = 0 var argIdx = 0 let end = name.children.count @@ -757,7 +836,7 @@ public struct NodePrinter: Sendable { /// Prints the next non-kind/non-payload child node from a specialization param, /// applying demangling for function/global props and string leading underscore stripping. - private mutating func printNextParamChildNode(_ node: Node, argIdx: inout Int, kind: FunctionSigSpecializationParamKind?) { + private mutating func printNextParamChildNode(_ node: SomeNode, argIdx: inout Int, kind: FunctionSigSpecializationParamKind?) { while argIdx < node.children.count { let child = node.children[argIdx] argIdx += 1 @@ -795,7 +874,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printFunctionSignatureSpecializationParamKind(_ name: Node) { + private mutating func printFunctionSignatureSpecializationParamKind(_ name: SomeNode) { let raw = name.index ?? 0 var printedOptionSet = false if raw & FunctionSigSpecializationParamKind.existentialToGeneric.rawValue != 0 { @@ -832,41 +911,41 @@ public struct NodePrinter: Sendable { } } - private mutating func printLazyProtocolWitnesstableAccessor(_ name: Node) { + private mutating func printLazyProtocolWitnesstableAccessor(_ name: SomeNode) { _ = printOptional(name.children.at(0), prefix: "lazy protocol witness table accessor for type ") _ = printOptional(name.children.at(1), prefix: " and conformance ") } - private mutating func printLazyProtocolWitnesstableCacheVariable(_ name: Node) { + private mutating func printLazyProtocolWitnesstableCacheVariable(_ name: SomeNode) { _ = printOptional(name.children.at(0), prefix: "lazy protocol witness table cache variable for type ") _ = printOptional(name.children.at(1), prefix: " and conformance ") } - private mutating func printVTableThunk(_ name: Node) { + private mutating func printVTableThunk(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: "vtable thunk for ") _ = printOptional(name.children.at(0), prefix: " dispatching to ") } - private mutating func printProtocolWitness(_ name: Node) { + private mutating func printProtocolWitness(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: "protocol witness for ") _ = printOptional(name.children.at(0), prefix: " in conformance ") } - private mutating func printPartialApplyForwarder(_ name: Node) { + private mutating func printPartialApplyForwarder(_ name: SomeNode) { target.write("partial apply\(options.contains(.shortenPartialApply) ? "" : " forwarder")") if !name.children.isEmpty { printChildren(name, prefix: " for ") } } - private mutating func printPartialApplyObjCForwarder(_ name: Node) { + private mutating func printPartialApplyObjCForwarder(_ name: SomeNode) { target.write("partial apply\(options.contains(.shortenPartialApply) ? "" : " ObjC forwarder")") if !name.children.isEmpty { printChildren(name, prefix: " for ") } } - private mutating func printKeyPathAccessorThunkHelper(_ name: Node) { + private mutating func printKeyPathAccessorThunkHelper(_ name: SomeNode) { let prefix = switch name.kind { case .keyPathGetterThunkHelper: "getter for " case .keyPathSetterThunkHelper: "setter for " @@ -883,7 +962,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printKeyPathEqualityThunkHelper(_ name: Node) { + private mutating func printKeyPathEqualityThunkHelper(_ name: SomeNode) { target.write("key path index \(name.kind == .keyPathEqualsThunkHelper ? "equality" : "hash") operator for ") var dropLast = false if let lastChild = name.children.last, lastChild.kind == .dependentGenericSignature { @@ -897,12 +976,12 @@ public struct NodePrinter: Sendable { } } - private mutating func printFieldOffset(_ name: Node) { + private mutating func printFieldOffset(_ name: SomeNode) { printFirstChild(name) _ = printOptional(name.children.at(1), prefix: "field offset for ", asPrefixContext: true) } - private mutating func printReabstractionThunk(_ name: Node) { + private mutating func printReabstractionThunk(_ name: SomeNode) { if options.contains(.shortenThunk) { _ = printOptional(name.children.at(name.children.count - 2), prefix: "thunk for ") } else { @@ -914,30 +993,30 @@ public struct NodePrinter: Sendable { } } - private mutating func printAssociatedConformanceDescriptor(_ name: Node) { + private mutating func printAssociatedConformanceDescriptor(_ name: SomeNode) { _ = printOptional(name.children.at(0), prefix: "associated conformance descriptor for ") _ = printOptional(name.children.at(1), prefix: ".") _ = printOptional(name.children.at(2), prefix: ": ") } - private mutating func printDefaultAssociatedConformanceAccessor(_ name: Node) { + private mutating func printDefaultAssociatedConformanceAccessor(_ name: SomeNode) { _ = printOptional(name.children.at(0), prefix: "default associated conformance accessor for ") _ = printOptional(name.children.at(1), prefix: ".") _ = printOptional(name.children.at(2), prefix: ": ") } - private mutating func printAssociatedTypeMetadataAccessor(_ name: Node) { + private mutating func printAssociatedTypeMetadataAccessor(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: "associated type metadata accessor for ") _ = printOptional(name.children.at(0), prefix: " in ") } - private mutating func printAssociatedTypeWitnessTableAccessor(_ name: Node) { + private mutating func printAssociatedTypeWitnessTableAccessor(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: "associated type witness table accessor for ") _ = printOptional(name.children.at(2), prefix: " : ") _ = printOptional(name.children.at(0), prefix: " in ") } - private mutating func printValueWitness(_ name: Node) { + private mutating func printValueWitness(_ name: SomeNode) { // ValueWitness node structure: first child is Index node with the witness kind let witnessIndex = name.children.first?.index ?? 0 target.write(ValueWitnessKind(rawValue: witnessIndex)?.description ?? "") @@ -946,7 +1025,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(1)) } - private mutating func printConcreteProtocolConformance(_ name: Node) { + private mutating func printConcreteProtocolConformance(_ name: SomeNode) { target.write("concrete protocol conformance ") if let index = name.index { target.write("#\(index) ") @@ -960,7 +1039,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printMetatype(_ name: Node) { + private mutating func printMetatype(_ name: SomeNode) { if name.children.count == 2 { printFirstChild(name, suffix: " ") } @@ -972,19 +1051,19 @@ public struct NodePrinter: Sendable { target.write(type.kind.isExistentialType ? ".Protocol" : ".Type") } - private mutating func printExistentialMetatype(_ name: Node) { + private mutating func printExistentialMetatype(_ name: SomeNode) { if name.children.count == 2 { printFirstChild(name, suffix: " ") } _ = printOptional(name.children.at(name.children.count == 2 ? 1 : 0), suffix: ".Type") } - private mutating func printAssociatedTypeRef(_ name: Node) { + private mutating func printAssociatedTypeRef(_ name: SomeNode) { printFirstChild(name) target.write(".\(name.children.at(1)?.text ?? "")") } - private mutating func printProtocolList(_ name: Node) { + private mutating func printProtocolList(_ name: SomeNode) { guard let typeList = name.children.first else { return } if typeList.children.isEmpty { target.write("Any") @@ -993,7 +1072,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printProtocolListWithClass(_ name: Node) { + private mutating func printProtocolListWithClass(_ name: SomeNode) { guard name.children.count >= 2 else { return } _ = printOptional(name.children.at(1), suffix: " & ") if let protocolsTypeList = name.children.first?.children.first { @@ -1001,7 +1080,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printProtocolListWithAnyObject(_ name: Node) { + private mutating func printProtocolListWithAnyObject(_ name: SomeNode) { guard let prot = name.children.first, let protocolsTypeList = prot.children.first else { return } if protocolsTypeList.children.count > 0 { printChildren(protocolsTypeList, suffix: " & ", separator: " & ") @@ -1012,7 +1091,7 @@ public struct NodePrinter: Sendable { target.write("AnyObject") } - private mutating func printProtocolConformance(_ name: Node) { + private mutating func printProtocolConformance(_ name: SomeNode) { if name.children.count == 4 { _ = printOptional(name.children.at(2), prefix: "property behavior storage of ") _ = printOptional(name.children.at(0), prefix: " in ") @@ -1026,7 +1105,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplParameter(_ name: Node) { + private mutating func printImplParameter(_ name: SomeNode) { printFirstChild(name, suffix: " ") if name.children.count == 3 { _ = printOptional(name.children.at(1)) @@ -1037,7 +1116,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.last) } - private mutating func printDependentProtocolConformanceAssociated(_ name: Node) { + private mutating func printDependentProtocolConformanceAssociated(_ name: SomeNode) { target.write("dependent associated protocol conformance ") if let index = name.children.at(2)?.index { target.write("#\(index) ") @@ -1047,7 +1126,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(1)) } - private mutating func printDependentProtocolConformanceInherited(_ name: Node) { + private mutating func printDependentProtocolConformanceInherited(_ name: SomeNode) { target.write("dependent inherited protocol conformance ") if let index = name.children.at(2)?.index { target.write("#\(index) ") @@ -1057,7 +1136,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(1)) } - private mutating func printDependentProtocolConformanceRoot(_ name: Node) { + private mutating func printDependentProtocolConformanceRoot(_ name: SomeNode) { target.write("dependent root protocol conformance ") if let index = name.children.at(2)?.index { target.write("#\(index) ") @@ -1067,7 +1146,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(1)) } - private mutating func printDependentProtocolConformanceOpaque(_ name: Node) { + private mutating func printDependentProtocolConformanceOpaque(_ name: SomeNode) { target.write("opaque result conformance ") printFirstChild(name) target.write(" of ") @@ -1089,7 +1168,7 @@ public struct NodePrinter: Sendable { return name } - private mutating func printGenericSignature(_ name: Node) { + private mutating func printGenericSignature(_ name: SomeNode) { target.write("<") var numGenericParams = 0 for c in name.children { @@ -1125,7 +1204,7 @@ public struct NodePrinter: Sendable { return false } - let isGenericParamValue = { (depth: UInt64, index: UInt64) -> Node? in + let isGenericParamValue = { (depth: UInt64, index: UInt64) -> SomeNode? in for var child in name.children.dropFirst(numGenericParams).prefix(firstRequirement) { guard child.kind == .dependentGenericParamValueMarker else { continue } child = child.children.first ?? child @@ -1194,12 +1273,12 @@ public struct NodePrinter: Sendable { target.write(">") } - private mutating func printDependentGenericConformanceRequirement(_ name: Node) { + private mutating func printDependentGenericConformanceRequirement(_ name: SomeNode) { printFirstChild(name) _ = printOptional(name.children.at(1), prefix: ": ") } - private mutating func printDependentGenericLayoutRequirement(_ name: Node) { + private mutating func printDependentGenericLayoutRequirement(_ name: SomeNode) { guard let layout = name.children.at(1), let c = layout.text?.unicodeScalars.first else { return } printFirstChild(name, suffix: ": ") switch c { @@ -1222,18 +1301,18 @@ public struct NodePrinter: Sendable { } } - private mutating func printDependentGenericSameTypeRequirement(_ name: Node) { + private mutating func printDependentGenericSameTypeRequirement(_ name: SomeNode) { printFirstChild(name) _ = printOptional(name.children.at(1), prefix: " == ") } - private mutating func printDependentGenericType(_ name: Node) { + private mutating func printDependentGenericType(_ name: SomeNode) { guard let depType = name.children.at(1) else { return } printFirstChild(name) _ = printOptional(depType, prefix: depType.needSpaceBeforeType ? " " : "") } - private mutating func printDependentMemberType(_ name: Node) { + private mutating func printDependentMemberType(_ name: SomeNode) { dependentMemberTypeDepth += 1 defer { dependentMemberTypeDepth -= 1 } printFirstChild(name) @@ -1241,12 +1320,12 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(1)) } - private mutating func printDependentAssociatedTypeRef(_ name: Node) { + private mutating func printDependentAssociatedTypeRef(_ name: SomeNode) { _ = printOptional(name.children.at(1), suffix: ".") printFirstChild(name) } - private mutating func printSilBoxTypeWithLayout(_ name: Node) { + private mutating func printSilBoxTypeWithLayout(_ name: SomeNode) { guard let layout = name.children.first else { return } _ = printOptional(name.children.at(1), suffix: " ") _ = printName(layout) @@ -1255,7 +1334,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printSugaredOptional(_ name: Node) { + private mutating func printSugaredOptional(_ name: SomeNode) { if let type = name.children.first { let needParens = !type.isSimpleType target.write(needParens ? "(" : "") @@ -1265,12 +1344,12 @@ public struct NodePrinter: Sendable { } } - private mutating func printSugaredDictionary(_ name: Node) { + private mutating func printSugaredDictionary(_ name: SomeNode) { printFirstChild(name, prefix: "[", suffix: " : ") _ = printOptional(name.children.at(1), suffix: "]") } - private mutating func printOpaqueType(_ name: Node) { + private mutating func printOpaqueType(_ name: SomeNode) { printFirstChild(name) target.write(".") _ = printOptional(name.children.at(1)) @@ -1278,7 +1357,7 @@ public struct NodePrinter: Sendable { // printChildren(name, separator: ".") } - private mutating func printImplInvocationsSubstitutions(_ name: Node) { + private mutating func printImplInvocationsSubstitutions(_ name: SomeNode) { if let secondChild = name.children.at(0) { target.write(" for <") printChildren(secondChild, separator: ", ") @@ -1286,7 +1365,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplPatternSubstitutions(_ name: Node) { + private mutating func printImplPatternSubstitutions(_ name: SomeNode) { target.write("@substituted ") printFirstChild(name) if let secondChild = name.children.at(1) { @@ -1296,13 +1375,13 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplDifferentiability(_ name: Node) { + private mutating func printImplDifferentiability(_ name: SomeNode) { if let text = name.text, !text.isEmpty { target.write("\(text) ") } } - private mutating func printMacroExpansionLoc(_ name: Node) { + private mutating func printMacroExpansionLoc(_ name: SomeNode) { if let module = name.children.at(0) { target.write("module ") _ = printName(module) @@ -1321,7 +1400,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printGlobalActorFunctionType(_ name: Node) { + private mutating func printGlobalActorFunctionType(_ name: SomeNode) { if let firstChild = name.children.first { target.write("@") _ = printName(firstChild) @@ -1329,7 +1408,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printGlobalVariableOnceFunction(_ name: Node) { + private mutating func printGlobalVariableOnceFunction(_ name: SomeNode) { target.write(name.kind == .globalVariableOnceToken ? "one-time initialization token for " : "one-time initialization function for ") if let firstChild = name.children.first { _ = shouldPrintContext(firstChild) @@ -1339,7 +1418,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printGlobalVariableOnceDeclList(_ name: Node) { + private mutating func printGlobalVariableOnceDeclList(_ name: SomeNode) { if name.children.count == 1 { printFirstChild(name) } else { @@ -1347,7 +1426,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printTypeThrowsAnnotation(_ name: Node) { + private mutating func printTypeThrowsAnnotation(_ name: SomeNode) { target.write(" throws(") if let child = name.children.first { _ = printName(child) @@ -1355,7 +1434,7 @@ public struct NodePrinter: Sendable { target.write(")") } - private mutating func printDifferentiableFunctionType(_ name: Node) { + private mutating func printDifferentiableFunctionType(_ name: SomeNode) { target.write("@differentiable") switch UnicodeScalar(UInt8(name.index ?? 0)) { case "f": target.write("(_forward)") @@ -1365,7 +1444,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printDifferentiabilityWitness(_ name: Node) { + private mutating func printDifferentiabilityWitness(_ name: SomeNode) { let kindNodeIndex = name.children.count - (name.children.last?.kind == .dependentGenericSignature ? 4 : 3) let kind = (name.children.at(kindNodeIndex)?.index).flatMap { Differentiability($0) } switch kind { @@ -1386,7 +1465,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(idx + 3), prefix: " with ") } - private mutating func printAsyncAwaitResumePartialFunction(_ name: Node) { + private mutating func printAsyncAwaitResumePartialFunction(_ name: SomeNode) { if options.contains(.showAsyncResumePartial) { target.write("(") _ = printName(name.children.first!) @@ -1395,7 +1474,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printAsyncSuspendResumePartialFunction(_ name: Node) { + private mutating func printAsyncSuspendResumePartialFunction(_ name: SomeNode) { if options.contains(.showAsyncResumePartial) { target.write("(") _ = printName(name.children.first!) @@ -1404,11 +1483,11 @@ public struct NodePrinter: Sendable { } } - private mutating func printExtendedExistentialTypeShape(_ name: Node) { + private mutating func printExtendedExistentialTypeShape(_ name: SomeNode) { let savedDisplayWhereClauses = options.contains(.displayWhereClauses) options.insert(.displayWhereClauses) - var genSig: Node? - var type: Node? + var genSig: SomeNode? + var type: SomeNode? if name.children.count == 2 { genSig = name.children.at(1) type = name.children.at(2) @@ -1431,7 +1510,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printSymbolicExtendedExistentialType(_ name: Node) { + private mutating func printSymbolicExtendedExistentialType(_ name: SomeNode) { guard let shape = name.children.first else { return } let isUnique = shape.kind == .uniqueExtendedExistentialTypeShapeSymbolicReference target.write("symbolic existential type (\(isUnique ? "" : "non-")unique) 0x") @@ -1446,7 +1525,7 @@ public struct NodePrinter: Sendable { target.write(">") } - private mutating func printTupleElement(_ name: Node) { + private mutating func printTupleElement(_ name: SomeNode) { if let label = name.children.first(where: { $0.kind == .tupleElementName }) { target.write("\(label.text ?? ""): ") } @@ -1457,7 +1536,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printObjCAsyncCompletionHandlerImpl(_ name: Node) { + private mutating func printObjCAsyncCompletionHandlerImpl(_ name: SomeNode) { if name.kind == .checkedObjCAsyncCompletionHandlerImpl { target.write("predefined ") } @@ -1475,7 +1554,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplInvocationSubstitutions(_ name: Node) { + private mutating func printImplInvocationSubstitutions(_ name: SomeNode) { if let secondChild = name.children.at(0) { target.write(" for <") printChildren(secondChild, separator: ", ") @@ -1483,9 +1562,9 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplDifferentiabilityKind(_ name: Node) { + private mutating func printImplDifferentiabilityKind(_ name: SomeNode) { target.write("@differentiable") - if case .index(let value) = name.contents, let differentiability = Differentiability(value) { + if let value = name.index, let differentiability = Differentiability(value) { switch differentiability { case .normal: break case .linear: target.write("(_linear)") @@ -1495,12 +1574,12 @@ public struct NodePrinter: Sendable { } } - private mutating func printImplCoroutineKind(_ name: Node) { - guard case .text(let value) = name.contents, !value.isEmpty else { return } + private mutating func printImplCoroutineKind(_ name: SomeNode) { + guard let value = name.text, !value.isEmpty else { return } target.write("@\(value)") } - private mutating func printImplFunctionConvention(_ name: Node) { + private mutating func printImplFunctionConvention(_ name: SomeNode) { target.write("@convention(") if let second = name.children.at(1) { target.write("\(name.children.at(0)?.text ?? ""), mangledCType: \"") @@ -1512,17 +1591,17 @@ public struct NodePrinter: Sendable { target.write(")") } - private mutating func printImplParameterName(_ name: Node) { - guard case .text(let value) = name.contents, !value.isEmpty else { return } + private mutating func printImplParameterName(_ name: SomeNode) { + guard let value = name.text, !value.isEmpty else { return } target.write("\(value) ") } - private mutating func printBaseConformanceDescriptor(_ name: Node) { + private mutating func printBaseConformanceDescriptor(_ name: SomeNode) { printFirstChild(name, prefix: "base conformance descriptor for ") _ = printOptional(name.children.at(1), prefix: ": ") } - private mutating func printReabstractionThunkHelperWithSelf(_ name: Node) { + private mutating func printReabstractionThunkHelperWithSelf(_ name: SomeNode) { target.write("reabstraction thunk ") var idx = 0 if name.children.count == 4 { @@ -1534,17 +1613,17 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(idx), prefix: " self ") } - private mutating func printReabstracctionThunkHelperWithGlobalActor(_ name: Node) { + private mutating func printReabstracctionThunkHelperWithGlobalActor(_ name: SomeNode) { printFirstChild(name) _ = printOptional(name.children.at(1), prefix: " with global actor constraint ") } - private mutating func printBuildInFixedArray(_ name: Node) { + private mutating func printBuildInFixedArray(_ name: SomeNode) { _ = printOptional(name.children.first, prefix: "Builtin.FixedArray<") _ = printOptional(name.children.at(1), prefix: ", ", suffix: ">") } - private mutating func printAutoDiffFunctionOrSimpleThunk(_ name: Node) { + private mutating func printAutoDiffFunctionOrSimpleThunk(_ name: SomeNode) { var prefixEndIndex = 0 while prefixEndIndex < name.children.count, name.children[prefixEndIndex].kind != .autoDiffFunctionKind { prefixEndIndex += 1 @@ -1558,7 +1637,7 @@ public struct NodePrinter: Sendable { } _ = printOptional(funcKind) target.write(" of ") - var optionalGenSig: Node? + var optionalGenSig: SomeNode? for i in 0 ..< prefixEndIndex { if i == prefixEndIndex - 1, name.children.at(i)?.kind == .dependentGenericSignature { optionalGenSig = name.children.at(i) @@ -1576,7 +1655,7 @@ public struct NodePrinter: Sendable { _ = printOptional(options.contains(.displayWhereClauses) ? optionalGenSig : nil, prefix: " with ") } - private mutating func printAutoDiffFunctionKind(_ name: Node) { + private mutating func printAutoDiffFunctionKind(_ name: SomeNode) { guard let kind = name.index else { return } switch AutoDiffFunctionKind(kind) { case .forward: target.write("forward-mode derivative") @@ -1587,13 +1666,13 @@ public struct NodePrinter: Sendable { } } - private mutating func printAutoDiffSelfReorderingReabstractionThunk(_ name: Node) { + private mutating func printAutoDiffSelfReorderingReabstractionThunk(_ name: SomeNode) { target.write("autodiff self-reordering reabstraction thunk ") let fromType = name.children.first _ = printOptional(options.contains(.shortenThunk) ? fromType : nil, prefix: "for ") let toType = name.children.at(1) var kindIndex = 2 - var optionalGenSig: Node? + var optionalGenSig: SomeNode? if name.children.at(kindIndex)?.kind == .dependentGenericSignature { optionalGenSig = name.children.at(kindIndex) kindIndex += 1 @@ -1605,7 +1684,7 @@ public struct NodePrinter: Sendable { _ = printOptional(toType, prefix: " to ") } - private mutating func printAutoDiffSubsetParametersThunk(_ name: Node) { + private mutating func printAutoDiffSubsetParametersThunk(_ name: SomeNode) { target.write("autodiff subset parameters thunk for ") let lastIndex = name.children.count - 1 let toParamIndices = name.children.at(lastIndex) @@ -1631,7 +1710,7 @@ public struct NodePrinter: Sendable { _ = printOptional(currentIndex > 0 ? name.children.at(currentIndex) : nil, prefix: " of type ") } - private mutating func printIndexSubset(_ name: Node) { + private mutating func printIndexSubset(_ name: SomeNode) { target.write("{") var printedAnyIndex = false for (i, c) in (name.text ?? "").enumerated() { @@ -1647,12 +1726,12 @@ public struct NodePrinter: Sendable { target.write("}") } - private mutating func printBaseWitnessTableAccessor(_ name: Node) { + private mutating func printBaseWitnessTableAccessor(_ name: SomeNode) { _ = printOptional(name.children.at(1), prefix: "base witness table accessor for ") _ = printOptional(name.children.at(0), prefix: " in ") } - private mutating func printDependentGenericInverseConformanceRequirement(_ name: Node) { + private mutating func printDependentGenericInverseConformanceRequirement(_ name: SomeNode) { printFirstChild(name, suffix: ": ~") switch name.children.at(1)?.index { case 0: target.write("Swift.Copyable") @@ -1661,21 +1740,21 @@ public struct NodePrinter: Sendable { } } - private mutating func printDependentGenericSameShapeRequirement(_ name: Node) { + private mutating func printDependentGenericSameShapeRequirement(_ name: SomeNode) { _ = printOptional(name.children.at(0), suffix: ".shape == ") _ = printOptional(name.children.at(1), suffix: ".shape") } - private mutating func printConstrainedExistential(_ name: Node) { + private mutating func printConstrainedExistential(_ name: SomeNode) { printFirstChild(name, prefix: "any ") _ = printOptional(name.children.at(1), prefix: "<", suffix: ">") } - private mutating func printIdentifier(_ name: Node, asPrefixContext: Bool = false, parentKind: Node.Kind? = nil) { - target.write(name.text ?? "", context: .context(for: name, parentKind: parentKind, state: .printIdentifier)) + private mutating func printIdentifier(_ name: SomeNode, asPrefixContext: Bool = false, parentKind: Node.Kind? = nil) { + target.write(name.text ?? "", context: .context(for: name as? Node, parentKind: parentKind, state: .printIdentifier)) } - private mutating func printAbstractStorage(_ name: Node?, asPrefixContext: Bool, extraName: String) -> Node? { + private mutating func printAbstractStorage(_ name: SomeNode?, asPrefixContext: Bool, extraName: String) -> SomeNode? { guard let n = name else { return nil } switch n.kind { case .variable: return printEntity(n, asPrefixContext: asPrefixContext, typePrinting: .withColon, hasName: true, extraName: extraName) @@ -1684,7 +1763,7 @@ public struct NodePrinter: Sendable { } } - private mutating func printEntityType(name: Node, type: Node, genericFunctionTypeList: Node?) { + private mutating func printEntityType(name: SomeNode, type: SomeNode, genericFunctionTypeList: SomeNode?) { let labelList = name.children.first(where: { $0.kind == .labelList }) if labelList != nil || genericFunctionTypeList != nil { if let gftl = genericFunctionTypeList { @@ -1722,8 +1801,8 @@ public struct NodePrinter: Sendable { } } - private mutating func printEntity(_ name: Node, asPrefixContext: Bool, typePrinting: TypePrinting, hasName: Bool, extraName: String? = nil, extraIndex: UInt64? = nil, overwriteName: String? = nil) -> Node? { - var genericFunctionTypeList: Node? + private mutating func printEntity(_ name: SomeNode, asPrefixContext: Bool, typePrinting: TypePrinting, hasName: Bool, extraName: String? = nil, extraIndex: UInt64? = nil, overwriteName: String? = nil) -> SomeNode? { + var genericFunctionTypeList: SomeNode? var name = name if name.kind == .boundGenericFunction, let first = name.children.at(0), let second = name.children.at(1) { name = first @@ -1737,7 +1816,7 @@ public struct NodePrinter: Sendable { } guard let context = name.children.first else { return nil } - var postfixContext: Node? + var postfixContext: SomeNode? if shouldPrintContext(context) { if multiWordName { postfixContext = context @@ -1850,7 +1929,7 @@ public struct NodePrinter: Sendable { return postfixContext } - private mutating func printSpecializationPrefix(_ name: Node, description: String, paramPrefix: String = "") { + private mutating func printSpecializationPrefix(_ name: SomeNode, description: String, paramPrefix: String = "") { if !options.contains(.displayGenericSpecializations) { if !specializationPrefixPrinted { if name.children.first?.kind == .representationChanged { @@ -1896,7 +1975,7 @@ public struct NodePrinter: Sendable { target.write("> of ") } - private mutating func printFunctionParameters(labelList: Node?, parameterType: Node, showTypes: Bool) { + private mutating func printFunctionParameters(labelList: SomeNode?, parameterType: SomeNode, showTypes: Bool) { guard parameterType.kind == .argumentTuple else { return } guard let t = parameterType.children.first, t.kind == .type else { return } guard let parameters = t.children.first else { return } @@ -1915,17 +1994,17 @@ public struct NodePrinter: Sendable { target.write("(") for tuple in parameters.children.enumerated() { if let label = labelList?.children.at(tuple.offset) { - target.write(label.kind == .identifier ? (label.text ?? "") : "_", context: .context(for: parameterType, state: .printFunctionParameters)) + target.write(label.kind == .identifier ? (label.text ?? "") : "_", context: .context(for: parameterType as? Node, state: .printFunctionParameters)) target.write(":") if showTypes { target.write(" ") } } else if !showTypes { if let label = tuple.element.children.first(where: { $0.kind == .tupleElementName }) { - target.write(label.text ?? "", context: .context(for: parameterType, state: .printFunctionParameters)) + target.write(label.text ?? "", context: .context(for: parameterType as? Node, state: .printFunctionParameters)) target.write(":") } else { - target.write("_", context: .context(for: parameterType, state: .printFunctionParameters)) + target.write("_", context: .context(for: parameterType as? Node, state: .printFunctionParameters)) target.write(":") } } @@ -1940,7 +2019,7 @@ public struct NodePrinter: Sendable { target.write(")") } - private mutating func printConventionWithMangledCType(_ name: Node, label: String) { + private mutating func printConventionWithMangledCType(_ name: SomeNode, label: String) { target.write("@convention(\(label)") if let firstChild = name.children.first, firstChild.kind == .clangType { target.write(", mangledCType: \"") @@ -1950,7 +2029,7 @@ public struct NodePrinter: Sendable { target.write(") ") } - private mutating func printFunctionType(labelList: Node? = nil, _ name: Node) { + private mutating func printFunctionType(labelList: SomeNode? = nil, _ name: SomeNode) { switch name.kind { case .autoClosureType, .escapingAutoClosureType: target.write("@autoclosure ") @@ -1982,7 +2061,7 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(startIndex)) startIndex += 1 } - var nonIsolatedCallerNode: Node? + var nonIsolatedCallerNode: SomeNode? if name.children.at(startIndex)?.kind == .nonIsolatedCallerFunctionType { nonIsolatedCallerNode = name.children.at(startIndex) startIndex += 1 @@ -1995,7 +2074,7 @@ public struct NodePrinter: Sendable { diffKind = UnicodeScalar(UInt8(name.children.at(startIndex)?.index ?? 0)) startIndex += 1 } - var thrownErrorNode: Node? + var thrownErrorNode: SomeNode? if name.children.at(startIndex)?.kind == .throwsAnnotation || name.children.at(startIndex)?.kind == .typedThrowsAnnotation { thrownErrorNode = name.children.at(startIndex) startIndex += 1 @@ -2044,14 +2123,14 @@ public struct NodePrinter: Sendable { _ = printOptional(name.children.at(argIndex + 1)) } - private mutating func printBoundGenericNoSugar(_ name: Node) { + private mutating func printBoundGenericNoSugar(_ name: SomeNode) { guard let typeList = name.children.at(1) else { return } printFirstChild(name) guard !options.contains(.removeBoundGeneric) else { return } printChildren(typeList, prefix: "<", suffix: ">", separator: ", ") } - private func findSugar(_ name: Node) -> SugarType { + private func findSugar(_ name: SomeNode) -> SugarType { guard let firstChild = name.children.at(0) else { return .none } if name.children.count == 1, firstChild.kind == .type { return findSugar(firstChild) } @@ -2083,7 +2162,7 @@ public struct NodePrinter: Sendable { return .none } - private mutating func printBoundGeneric(_ name: Node) { + private mutating func printBoundGeneric(_ name: SomeNode) { guard name.children.count >= 2 else { return } guard name.children.count == 2, options.contains(.synthesizeSugarOnTypes), name.kind != .boundGenericClass else { printBoundGenericNoSugar(name) @@ -2117,12 +2196,12 @@ public struct NodePrinter: Sendable { } private enum PrintImplFunctionTypeState: Int { case attrs, inputs, results } - private mutating func printImplFunctionType(_ name: Node) { + private mutating func printImplFunctionType(_ name: SomeNode) { var curState: PrintImplFunctionTypeState = .attrs - var patternSubs: Node? - var invocationSubs: Node? - var sendingResult: Node? - let transitionTo = { (printer: inout NodePrinter, newState: PrintImplFunctionTypeState) in + var patternSubs: SomeNode? + var invocationSubs: SomeNode? + var sendingResult: SomeNode? + let transitionTo = { (printer: inout Self, newState: PrintImplFunctionTypeState) in while curState != newState { switch curState { case .attrs: @@ -2200,3 +2279,33 @@ public struct NodePrinter: Sendable { target.write("\"") } } + +/// Prints a `Node` tree to `Target`. +/// +/// A thin, source-compatible facade over `DemanglingPrinter`. +/// The printing logic lives in the generic engine so it can also print +/// `NodeReference` trees straight from a `NodeStore` without materializing +/// a class tree (see `NodeReference.print(using:)`). +public struct NodePrinter: Sendable { + public static var maxPrintDepth: Int { DemanglingPrinter.maxPrintDepth } + + private var engine: DemanglingPrinter + + public init(options: DemangleOptions = .default) { + self.engine = DemanglingPrinter(options: options) + } + + /// See ``DemanglingPrinter/print(_:options:)`` — stack-safe entry point. + public static func print(_ root: Node, options: DemangleOptions = .default) -> Target { + DemanglingPrinter.print(root, options: options) + } + + public mutating func printRoot(_ root: Node) -> Target { + engine.printRoot(root) + } + + /// See ``DemanglingPrinter/printRootWithinStackBudget(_:stackFloorAddress:)``. + public mutating func printRootWithinStackBudget(_ root: Node, stackFloorAddress: UInt) -> Target? { + engine.printRootWithinStackBudget(root, stackFloorAddress: stackFloorAddress) + } +} diff --git a/Sources/Demangling/Node/Printer/NodePrinterTarget.swift b/Sources/Demangling/Node/Printer/NodePrinterTarget.swift index b4b0ec8..ed8486e 100644 --- a/Sources/Demangling/Node/Printer/NodePrinterTarget.swift +++ b/Sources/Demangling/Node/Printer/NodePrinterTarget.swift @@ -14,7 +14,13 @@ public protocol NodePrinterTarget: Sendable { /// a nested push overrides it. Printers push the enclosing nominal /// node around a full qualified-name print (module, dots, identifier) /// so rich targets can group those writes into one logical span. - mutating func pushTypeReferenceScope(_ node: Node?) + /// + /// The node is delivered lazily: store-backed printing must materialize + /// a `Node` to service this hook, and only targets that actually use + /// scope identity (rich targets) should pay that cost. Targets that + /// ignore scopes (`String`, the default implementation) never evaluate + /// the autoclosure, keeping the plain-text store path allocation-free. + mutating func pushTypeReferenceScope(_ node: @autoclosure () -> Node?) mutating func popTypeReferenceScope() } @@ -23,7 +29,7 @@ extension NodePrinterTarget { write(content) } - public mutating func pushTypeReferenceScope(_ node: Node?) {} + public mutating func pushTypeReferenceScope(_ node: @autoclosure () -> Node?) {} public mutating func popTypeReferenceScope() {} diff --git a/Sources/Demangling/Store/CompactNode.swift b/Sources/Demangling/Store/CompactNode.swift new file mode 100644 index 0000000..b3675ad --- /dev/null +++ b/Sources/Demangling/Store/CompactNode.swift @@ -0,0 +1,103 @@ +/// Flat 12-byte node representation used by `NodeStore`. +/// +/// Nodes live in one contiguous buffer and refer to each other by `UInt32` +/// indices instead of pointers, eliminating per-node heap allocations, object +/// headers, and reference counting. The layout mirrors the mutual-exclusivity +/// invariant of `Node.Payload`: a node carries either contents (text/index) +/// or children, never both. +@usableFromInline +struct CompactNode: Hashable, Sendable { + /// Bits 0-8: kind ordinal (see `Node.Kind.storeOrdinal`). + /// Bits 9-11: payload kind. Bits 12-15: reserved. + @usableFromInline + var kindAndPayloadKind: UInt16 + + /// First 32-bit payload word; meaning depends on `payloadKind`. + @usableFromInline + var payloadWord0: UInt32 + + /// Second 32-bit payload word; meaning depends on `payloadKind`. + @usableFromInline + var payloadWord1: UInt32 +} + +extension CompactNode { + @usableFromInline + enum PayloadKind: UInt16, Sendable { + /// No contents and no children. + case none = 0 + /// `payloadWord0` = low 32 bits, `payloadWord1` = high 32 bits of a `UInt64` index. + case index = 1 + /// `payloadWord0` = offset into the store's text bytes, `payloadWord1` = byte length. + case text = 2 + /// `payloadWord0` = child node index. + case oneChild = 3 + /// `payloadWord0` / `payloadWord1` = child node indices. + case twoChildren = 4 + /// `payloadWord0` = offset into the store's edges buffer, `payloadWord1` = child count. + case manyChildren = 5 + } + + @usableFromInline + static let payloadKindShift: UInt16 = 9 + + @usableFromInline + static let kindOrdinalMask: UInt16 = (1 << payloadKindShift) - 1 + + @usableFromInline + init(kind: Node.Kind, payloadKind: PayloadKind, payloadWord0: UInt32, payloadWord1: UInt32) { + self.kindAndPayloadKind = kind.storeOrdinal | (payloadKind.rawValue << Self.payloadKindShift) + self.payloadWord0 = payloadWord0 + self.payloadWord1 = payloadWord1 + } + + @usableFromInline + var kind: Node.Kind { + Node.Kind.kindsByStoreOrdinal[Int(kindAndPayloadKind & Self.kindOrdinalMask)] + } + + @usableFromInline + var payloadKind: PayloadKind { + PayloadKind(rawValue: kindAndPayloadKind >> Self.payloadKindShift)! + } + + @usableFromInline + var childCount: Int { + switch payloadKind { + case .none, .index, .text: return 0 + case .oneChild: return 1 + case .twoChildren: return 2 + case .manyChildren: return Int(payloadWord1) + } + } +} + +extension Node.Kind { + /// Kinds in ordinal order. The ordinal is the position in `allCases` and is + /// only stable within a single process run — it is NOT a serialization + /// format. A persisted store format (proposal 0001 Phase 4) must define its + /// own stable kind mapping. + @usableFromInline + static let kindsByStoreOrdinal: [Node.Kind] = { + let orderedKinds = Array(allCases) + precondition( + orderedKinds.count <= Int(CompactNode.kindOrdinalMask) + 1, + "Node.Kind no longer fits the 9-bit ordinal space of CompactNode" + ) + return orderedKinds + }() + + @usableFromInline + static let storeOrdinalsByKind: [Node.Kind: UInt16] = { + var ordinals = [Node.Kind: UInt16](minimumCapacity: kindsByStoreOrdinal.count) + for (ordinal, kind) in kindsByStoreOrdinal.enumerated() { + ordinals[kind] = UInt16(ordinal) + } + return ordinals + }() + + @usableFromInline + var storeOrdinal: UInt16 { + Self.storeOrdinalsByKind[self]! + } +} diff --git a/Sources/Demangling/Store/DemanglingNode+Sequence.swift b/Sources/Demangling/Store/DemanglingNode+Sequence.swift new file mode 100644 index 0000000..c268f4a --- /dev/null +++ b/Sources/Demangling/Store/DemanglingNode+Sequence.swift @@ -0,0 +1,247 @@ +/// Tree traversal shared by every `DemanglingNode` representation. +/// +/// These are the single implementations behind `Node`'s and +/// `NodeReference`'s `Sequence` conformances and the kind-lookup helpers +/// (`first(of:)`, `all(of:)`, `contains(_:)`) — one copy, both +/// representations, mirroring the derived-helper rule in +/// `DemanglingNode.swift`. +extension DemanglingNode { + public func preorder() -> some Sequence { + PreorderSequence(root: self) + } + + public func inorder() -> some Sequence { + InorderSequence(root: self) + } + + public func postorder() -> some Sequence { + PostorderSequence(root: self) + } + + public func levelorder() -> some Sequence { + LevelorderSequence(root: self) + } +} + +private struct PreorderSequence: Sequence { + struct Iterator: IteratorProtocol { + private var stack: [SomeNode] + + fileprivate init(root: SomeNode) { + self.stack = [root] + } + + mutating func next() -> SomeNode? { + guard !stack.isEmpty else { return nil } + + let current = stack.removeLast() + + // Add children in reverse order so we visit them left-to-right + for child in current.children.reversed() { + stack.append(child) + } + + return current + } + } + + private let root: SomeNode + + fileprivate init(root: SomeNode) { + self.root = root + } + + func makeIterator() -> Iterator { + Iterator(root: root) + } +} + +private struct InorderSequence: Sequence { + struct Iterator: IteratorProtocol { + private var stack: [SomeNode] + private var current: SomeNode? + + fileprivate init(root: SomeNode) { + self.stack = [] + self.current = root + } + + mutating func next() -> SomeNode? { + while current != nil || !stack.isEmpty { + // Go to the leftmost node + while let node = current { + stack.append(node) + current = node.children.first + } + + // Current must be nil at this point + if let node = stack.popLast() { + current = node.children.count > 1 ? node.children[1] : nil + return node + } + } + return nil + } + } + + private let root: SomeNode + + fileprivate init(root: SomeNode) { + self.root = root + } + + func makeIterator() -> Iterator { + Iterator(root: root) + } +} + +private struct PostorderSequence: Sequence { + struct Iterator: IteratorProtocol { + private var stack: [(node: SomeNode, visited: Bool)] + + fileprivate init(root: SomeNode) { + self.stack = [(root, false)] + } + + mutating func next() -> SomeNode? { + while !stack.isEmpty { + let (node, visited) = stack.removeLast() + + if visited { + return node + } else { + // Mark as visited and push back + stack.append((node, true)) + + // Push children in reverse order + for child in node.children.reversed() { + stack.append((child, false)) + } + } + } + return nil + } + } + + private let root: SomeNode + + fileprivate init(root: SomeNode) { + self.root = root + } + + func makeIterator() -> Iterator { + Iterator(root: root) + } +} + +private struct LevelorderSequence: Sequence { + struct Iterator: IteratorProtocol { + private var queue: [SomeNode] + + fileprivate init(root: SomeNode) { + self.queue = [root] + } + + mutating func next() -> SomeNode? { + guard !queue.isEmpty else { return nil } + + let current = queue.removeFirst() + + // Add all children to the queue + queue.append(contentsOf: current.children) + + return current + } + } + + private let root: SomeNode + + fileprivate init(root: SomeNode) { + self.root = root + } + + func makeIterator() -> Iterator { + Iterator(root: root) + } +} + +// MARK: - Kind lookup over any node sequence + +extension Sequence where Element: DemanglingNode { + @inlinable + public func first(of kind: Node.Kind) -> Element? { + first { $0.kind == kind } + } + + @inlinable + public func first(of kinds: Node.Kind...) -> Element? { + first { kinds.contains($0.kind) } + } + + @inlinable + public func contains(_ kind: Node.Kind) -> Bool { + contains { $0.kind == kind } + } + + @inlinable + public func contains(_ kinds: Node.Kind...) -> Bool { + contains { kinds.contains($0.kind) } + } + + @inlinable + public func all(of kind: Node.Kind) -> [Element] { + filter { $0.kind == kind } + } + + @inlinable + public func all(of kinds: Node.Kind...) -> [Element] { + filter { kinds.contains($0.kind) } + } + + @inlinable + public func all(of kinds: [Node.Kind]) -> [Element] { + filter { kinds.contains($0.kind) } + } + + @inlinable + public func filter(of kind: Node.Kind) -> some Sequence { + filter { $0.kind == kind } + } + + @inlinable + public func filter(of kinds: Node.Kind...) -> some Sequence { + filter { kinds.contains($0.kind) } + } +} + +// MARK: - Identifier extraction + +extension DemanglingNode where Self: Sequence, Self.Element == Self { + /// The declaration identifier carried by this subtree, if any. + /// Mirrors the historical `Node.identifier` lookup order. + public var identifier: String? { + if let node = children.at(1), node.kind == .identifier { + return node.text + } else if let node = children.at(1), node.kind == .privateDeclName { + return node.children.at(1)?.text + } else if let node = first(of: .prefixOperator, .postfixOperator, .infixOperator) { + return node.text + } else if let node = first(of: .identifier) { + return node.text + } else if let node = first(of: .privateDeclName) { + return node.children.at(1)?.text + } else { + return nil + } + } +} + +// MARK: - NodeReference as Sequence (preorder, mirroring Node) + +extension NodeReference: Sequence { + public typealias Element = NodeReference + + public func makeIterator() -> some IteratorProtocol { + preorder().makeIterator() + } +} diff --git a/Sources/Demangling/Store/DemanglingNode.swift b/Sources/Demangling/Store/DemanglingNode.swift new file mode 100644 index 0000000..4c57f9c --- /dev/null +++ b/Sources/Demangling/Store/DemanglingNode.swift @@ -0,0 +1,215 @@ +/// The read-only tree shape shared by `Node` (the class tree) and +/// `NodeReference` (a handle into a `NodeStore`). +/// +/// It exists so that traversal-only consumers — first the `NodePrinter` +/// engine — can walk either representation without materializing a class +/// tree. The member names deliberately match `Node`'s existing API so that +/// the generic engine's body is identical whether specialized on `Node` or +/// `NodeReference`. See evolution proposal 0001, Phase 2. +public protocol DemanglingNode: Sendable { + associatedtype Children: DemanglingNodeChildren where Children.Element == Self + + /// A per-tree-node identity used to memoize shared subtrees while printing. + /// For `Node` this is `ObjectIdentifier`; for `NodeReference` it is the + /// store index. It must be equal exactly when two handles denote the same + /// shared node within a single traversal. + associatedtype PrintCacheIdentity: Hashable & Sendable + + var kind: Node.Kind { get } + var text: String? { get } + var index: UInt64? { get } + var hasIndex: Bool { get } + var children: Children { get } + var printCacheIdentity: PrintCacheIdentity { get } + + /// The concrete class-tree form of this subtree, for interop boundaries + /// that still require `Node` (`TypeBuilder` handoffs, remangling until the + /// `Remangler` is genericized). `Node` returns itself; `NodeReference` + /// materializes with subtree sharing preserved. + var materializedNode: Node { get } + + /// Requirements (with derived defaults) so representations can provide + /// allocation-free fast paths — `NodeReference` witnesses these with + /// byte comparisons against the store's string table instead of + /// constructing a `String` per check. + func isIdentifier(desired: String) -> Bool + var isSwiftModule: Bool { get } +} + +// MARK: - Derived helpers shared by the printer + +/// The convenience properties the printer engine derives from the protocol +/// primitives. These are the single implementation for both `Node` and +/// `NodeReference`: they are extension members (not requirements), so the +/// generic engine statically dispatches here for every conformer — keeping a +/// parallel copy on a concrete type would silently drift. +extension DemanglingNode { + /// Prints this subtree with the given options. Mirrors `Node.print(using:)`. + public func print(using options: DemangleOptions = .default) -> String { + DemanglingPrinter.print(self, options: options) + } + + @inlinable + public var hasChildren: Bool { + !children.isEmpty + } + + @inlinable + public subscript(throwChild childIndex: Int) -> Self { + get throws(Node.IndexOutOfBoundError) { + if let child = children.at(childIndex) { + return child + } else { + throw .default + } + } + } + + @inlinable + public func isKind(of kinds: Node.Kind...) -> Bool { + kinds.contains(kind) + } + + @inlinable + public func isIdentifier(desired: String) -> Bool { + kind == .identifier && text == desired + } + + @inlinable + public var isSwiftModule: Bool { + kind == .module && text == stdlibName + } + + public var isSimpleType: Bool { + switch kind { + case .associatedType, + .associatedTypeRef, + .boundGenericClass, + .boundGenericEnum, + .boundGenericFunction, + .boundGenericOtherNominalType, + .boundGenericProtocol, + .boundGenericStructure, + .boundGenericTypeAlias, + .builtinBorrow, + .builtinTypeName, + .builtinTupleType, + .builtinFixedArray, + .class, + .dependentGenericType, + .dependentMemberType, + .dependentGenericParamType, + .dynamicSelf, + .enum, + .errorType, + .existentialMetatype, + .integer, + .labelList, + .metatype, + .metatypeRepresentation, + .module, + .negativeInteger, + .otherNominalType, + .pack, + .protocol, + .protocolSymbolicReference, + .returnType, + .silBoxType, + .silBoxTypeWithLayout, + .structure, + .sugaredArray, + .sugaredDictionary, + .sugaredOptional, + .sugaredInlineArray, + .sugaredParen, + .tuple, + .tupleElementName, + .typeAlias, + .typeList, + .typeSymbolicReference: + return true + case .type: + return children.first.map(\.isSimpleType) ?? false + case .protocolList: + return children.first.map { $0.children.count <= 1 } ?? false + case .protocolListWithAnyObject: + return (children.first?.children.first).map { $0.children.count == 0 } ?? false + default: + return false + } + } + + public var needSpaceBeforeType: Bool { + switch kind { + case .type: + return children.first?.needSpaceBeforeType ?? false + case .functionType, + .noEscapeFunctionType, + .uncurriedFunctionType, + .dependentGenericType: + return false + default: + return true + } + } +} + +/// A node's children as a random-access collection, extended with the +/// safe-indexing helpers the printer relies on (`at`, `slice`). +public protocol DemanglingNodeChildren: RandomAccessCollection where Index == Int { + func at(_ index: Int) -> Element? + func slice(_ from: Int, _ to: Int) -> ArraySlice +} + +extension DemanglingNodeChildren { + @inlinable + public func at(_ index: Int) -> Element? { + (index >= startIndex && index < endIndex) ? self[index] : nil + } + + @inlinable + public var second: Element? { + at(1) + } + + @inlinable + public func slice(_ from: Int, _ to: Int) -> ArraySlice { + let elements = Array(self) + if from > to || from > elements.endIndex || to < elements.startIndex { + return ArraySlice() + } + let lowerBound = Swift.max(from, elements.startIndex) + let upperBound = Swift.min(to, elements.endIndex) + return elements[lowerBound ..< upperBound] + } +} + +// MARK: - Node conformance + +extension Node: DemanglingNode { + @inlinable + public var printCacheIdentity: ObjectIdentifier { ObjectIdentifier(self) } + + @inlinable + public var materializedNode: Node { self } +} + +extension Node.Children: DemanglingNodeChildren {} + +// MARK: - NodeReference conformance + +extension NodeReference: DemanglingNode { + @inlinable + public var hasIndex: Bool { + if case .index = compactNode.payloadKind { return true } + return false + } + + @inlinable + public var printCacheIdentity: NodeStore.NodeIndex { nodeIndex } + + @inlinable + public var materializedNode: Node { materialize() } +} + +extension NodeReference.ChildrenView: DemanglingNodeChildren {} diff --git a/Sources/Demangling/Store/NodeReference.swift b/Sources/Demangling/Store/NodeReference.swift new file mode 100644 index 0000000..b23283d --- /dev/null +++ b/Sources/Demangling/Store/NodeReference.swift @@ -0,0 +1,303 @@ +/// A lightweight handle to a node stored in a `NodeStore`. +/// +/// Sixteen bytes as a value: a store reference plus a node index. Because +/// stores are fully hash-consed, equality is O(1) — two references are equal +/// exactly when they address the same index of the same store, which within +/// one store coincides with structural equality. +public struct NodeReference: Sendable { + public let store: NodeStore + public let nodeIndex: NodeStore.NodeIndex + + @usableFromInline + init(store: NodeStore, nodeIndex: NodeStore.NodeIndex) { + self.store = store + self.nodeIndex = nodeIndex + } + + /// Interns a single `Node` tree into a fresh private store and + /// references its root. + /// + /// This is the convenience path for holding one externally built tree + /// (for example a transient demangle or a synthesized `Node`) in + /// compact form: the reference keeps its private store alive, so the + /// value is self-contained and `Sendable`. For bulk interning, drive a + /// `NodeStoreBuilder` directly so trees share one arena and its + /// deduplication. + public init(interning node: Node) { + var builder = NodeStoreBuilder() + let rootNodeIndex = builder.intern(node) + self = builder.freeze().reference(at: rootNodeIndex) + } + + @usableFromInline + var compactNode: CompactNode { + store.compactNode(at: nodeIndex.rawValue) + } + + // MARK: - Accessors (mirroring Node) + + public var kind: Node.Kind { + compactNode.kind + } + + /// The text contents, if this node carries text. + /// + /// Mirrors `Node.text`: for `.dependentGenericParamType` (which stores + /// `[depth, index]` as children rather than text) the generic parameter + /// name is synthesized, matching what the printer expects. + public var text: String? { + let compact = compactNode + if case .text = compact.payloadKind { + return store.text(offset: compact.payloadWord0, length: compact.payloadWord1) + } + if compact.kind == .dependentGenericParamType { + let childrenView = children + guard let depth = childrenView.at(0)?.index, let parameterIndex = childrenView.at(1)?.index else { return nil } + return genericParameterName(depth: depth, index: parameterIndex) + } + return nil + } + + /// Zero-copy view of this node's text as UTF-8 bytes in the store's + /// string table. Only covers text physically stored in the table; + /// `.dependentGenericParamType`'s synthesized name is not included + /// (use `text` for the composed form). + public var textUTF8: ArraySlice? { + let compact = compactNode + guard case .text = compact.payloadKind else { return nil } + let start = Int(compact.payloadWord0) + return store.textBytes[start ..< start + Int(compact.payloadWord1)] + } + + /// Allocation-free witness: compares string-table bytes directly for + /// ASCII needles (every kind/sugar check the printer performs), falling + /// back to `String` comparison for non-ASCII to preserve Unicode + /// canonical-equivalence semantics. + public func isIdentifier(desired: String) -> Bool { + guard kind == .identifier else { return false } + return textMatches(desired) + } + + /// Allocation-free witness, same strategy as `isIdentifier(desired:)`. + public var isSwiftModule: Bool { + guard kind == .module else { return false } + return textMatches(stdlibName) + } + + private func textMatches(_ expected: String) -> Bool { + guard let bytes = textUTF8 else { + return text == expected + } + let expectedUTF8 = expected.utf8 + guard expectedUTF8.allSatisfy({ $0 < 0x80 }) else { + return text == expected + } + return bytes.elementsEqual(expectedUTF8) + } + + /// The index contents, if this node carries an index. + public var index: UInt64? { + let compact = compactNode + guard case .index = compact.payloadKind else { return nil } + return UInt64(compact.payloadWord0) | (UInt64(compact.payloadWord1) << 32) + } + + public var children: ChildrenView { + ChildrenView(store: store, compactNode: compactNode) + } + + // MARK: - Interop with Node + + /// Rebuilds a standalone `Node` tree for this subtree. + /// + /// The returned tree is freshly constructed and does not interact with the + /// global `NodeCache`; intern it explicitly if canonical instances are needed. + public func materialize() -> Node { + store.materializeNode(at: nodeIndex.rawValue) + } + + /// Prints the demangled form of this subtree directly from the store, + /// without materializing a `Node` tree (proposal 0001, Phase 2). + public func print(using options: DemangleOptions = .default) -> String { + DemanglingPrinter.print(self, options: options) + } + + /// Whether this subtree is structurally equal to a `Node` tree, matching + /// the semantics of `Node.==` (kind + contents + children, recursively) + /// without materializing anything. + /// + /// This is the bridge for callers that hold an externally demangled + /// `Node` (for example from `demangleAsNode`) and need to find it among + /// `NodeReference` dictionary keys: reference-to-reference equality stays + /// O(1) via hash-consing, while reference-to-`Node` equality walks both + /// trees. Text payloads compare by string-table bytes first and fall back + /// to `String` equality so Unicode canonical equivalence matches `Node.==`. + public func structurallyEquals(_ node: Node) -> Bool { + let compact = compactNode + guard compact.kind == node.kind else { return false } + + switch node.contents { + case .none: + switch compact.payloadKind { + case .index, .text: + return false + case .none, .oneChild, .twoChildren, .manyChildren: + break + } + case .index(let indexValue): + guard index == indexValue else { return false } + case .text(let textValue): + guard case .text = compact.payloadKind else { return false } + guard let bytes = textUTF8 else { return false } + if !bytes.elementsEqual(textValue.utf8) { + guard text == textValue else { return false } + } + } + + let referenceChildren = children + let nodeChildren = node.children + guard referenceChildren.count == nodeChildren.count else { return false } + for (referenceChild, nodeChild) in zip(referenceChildren, nodeChildren) { + guard referenceChild.structurallyEquals(nodeChild) else { return false } + } + return true + } + + /// Whether this subtree is structurally equal to another reference's + /// subtree, matching `Node.==` semantics across stores. + /// + /// Within one store this is O(1): hash-consing makes index equality + /// coincide with structural equality. Across two stores it walks both + /// subtrees. Text payloads compare by string-table bytes first and + /// fall back to `String` equality so Unicode canonical equivalence + /// matches `Node.==`. + public func structurallyEquals(_ other: NodeReference) -> Bool { + if store === other.store { + return nodeIndex == other.nodeIndex + } + let compact = compactNode + let otherCompact = other.compactNode + guard compact.kind == otherCompact.kind else { return false } + + switch (compact.payloadKind, otherCompact.payloadKind) { + case (.text, .text): + guard let bytes = textUTF8, let otherBytes = other.textUTF8 else { return false } + if !bytes.elementsEqual(otherBytes) { + guard text == other.text else { return false } + } + case (.index, .index): + guard index == other.index else { return false } + case (.text, _), (_, .text), (.index, _), (_, .index): + return false + default: + break + } + + let selfChildren = children + let otherChildren = other.children + guard selfChildren.count == otherChildren.count else { return false } + for (selfChild, otherChild) in zip(selfChildren, otherChildren) { + guard selfChild.structurallyEquals(otherChild) else { return false } + } + return true + } +} + +// MARK: - CustomStringConvertible + +extension NodeReference: CustomStringConvertible { + /// Debug tree dump matching `Node.description`. Bridges through + /// materialization — a debugging convenience, not a hot path. + public var description: String { + materialize().description + } +} + +// MARK: - Hashable + +extension NodeReference: Hashable { + public static func == (lhs: NodeReference, rhs: NodeReference) -> Bool { + lhs.store === rhs.store && lhs.nodeIndex == rhs.nodeIndex + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(store)) + hasher.combine(nodeIndex) + } + + /// Hashes this subtree by structure (kind + contents + children, + /// recursively), consistent with `structurallyEquals(_:)` across + /// stores: structurally equal references — even from different stores — + /// produce the same hash. + /// + /// This is the building block for value types that key dictionaries by + /// a node's structure while storing a `NodeReference` (whose intrinsic + /// `Hashable` is store-identity based and would split structurally + /// equal keys from different stores). + public func structuralHash(into hasher: inout Hasher) { + let compact = compactNode + hasher.combine(compact.kind) + switch compact.payloadKind { + case .text: + hasher.combine(1) + hasher.combine(text) + case .index: + hasher.combine(2) + hasher.combine(index) + case .none, .oneChild, .twoChildren, .manyChildren: + hasher.combine(0) + } + let childrenView = children + hasher.combine(childrenView.count) + for child in childrenView { + child.structuralHash(into: &hasher) + } + } +} + +// MARK: - ChildrenView + +extension NodeReference { + /// Random-access view of a node's children, resolved lazily from the store. + public struct ChildrenView: RandomAccessCollection, Sendable { + public typealias Element = NodeReference + public typealias Index = Int + + @usableFromInline + let store: NodeStore + + @usableFromInline + let compactNode: CompactNode + + @usableFromInline + init(store: NodeStore, compactNode: CompactNode) { + self.store = store + self.compactNode = compactNode + } + + public var startIndex: Int { 0 } + + public var endIndex: Int { compactNode.childCount } + + public subscript(position: Int) -> NodeReference { + let rawChildIndex: UInt32 + switch compactNode.payloadKind { + case .oneChild: + precondition(position == 0, "Child index out of range") + rawChildIndex = compactNode.payloadWord0 + case .twoChildren: + switch position { + case 0: rawChildIndex = compactNode.payloadWord0 + case 1: rawChildIndex = compactNode.payloadWord1 + default: preconditionFailure("Child index out of range") + } + case .manyChildren: + precondition(position >= 0 && position < Int(compactNode.payloadWord1), "Child index out of range") + rawChildIndex = store.edges[Int(compactNode.payloadWord0) + position] + case .none, .index, .text: + preconditionFailure("Child index out of range for a node without children") + } + return NodeReference(store: store, nodeIndex: NodeStore.NodeIndex(rawValue: rawChildIndex)) + } + } +} diff --git a/Sources/Demangling/Store/NodeStore.swift b/Sources/Demangling/Store/NodeStore.swift new file mode 100644 index 0000000..df10178 --- /dev/null +++ b/Sources/Demangling/Store/NodeStore.swift @@ -0,0 +1,133 @@ +/// An immutable, memory-compact symbol database produced by +/// `NodeStoreBuilder.freeze()`. +/// +/// All nodes live in flat contiguous buffers — 12 bytes per node, 4 bytes per +/// child edge beyond two, and deduplicated UTF-8 text bytes — with no per-node +/// heap allocation, object header, or reference counting. The store is fully +/// hash-consed: within one store, structurally equal subtrees have equal +/// indices, so equality checks on `NodeReference` are O(1). +/// +/// The store is deeply immutable after freezing, so it is `Sendable` and reads +/// take no locks. See evolution proposal 0001 for the overall design. +public final class NodeStore: Sendable { + /// A stable identifier of a node within its store. + /// + /// Indices are minted by `NodeStoreBuilder` and remain valid in the + /// frozen store. They are only meaningful for the store they came from. + public struct NodeIndex: Hashable, Sendable { + @usableFromInline + let rawValue: UInt32 + + @usableFromInline + init(rawValue: UInt32) { + self.rawValue = rawValue + } + } + + @usableFromInline + let nodes: ContiguousArray + + @usableFromInline + let edges: ContiguousArray + + @usableFromInline + let textBytes: ContiguousArray + + init( + nodes: ContiguousArray, + edges: ContiguousArray, + textBytes: ContiguousArray + ) { + self.nodes = nodes + self.edges = edges + self.textBytes = textBytes + } + + // MARK: - Statistics + + /// Number of unique nodes in the store. + public var nodeCount: Int { nodes.count } + + /// Number of child-edge slots used by nodes with three or more children. + public var edgeCount: Int { edges.count } + + /// Number of deduplicated UTF-8 text bytes. + public var textByteCount: Int { textBytes.count } + + /// Total payload bytes of the flat buffers (excluding the containers' + /// own headers and growth slack). + public var storageByteCount: Int { + nodes.count * MemoryLayout.stride + + edges.count * MemoryLayout.stride + + textBytes.count + } + + // MARK: - Access + + /// Returns a reference to the node at the given index. + public func reference(at nodeIndex: NodeIndex) -> NodeReference { + precondition(Int(nodeIndex.rawValue) < nodes.count, "NodeIndex out of range for this store") + return NodeReference(store: self, nodeIndex: nodeIndex) + } + + @usableFromInline + func compactNode(at rawIndex: UInt32) -> CompactNode { + nodes[Int(rawIndex)] + } + + @usableFromInline + func text(offset: UInt32, length: UInt32) -> String { + let start = Int(offset) + let end = start + Int(length) + return String(decoding: textBytes[start ..< end], as: UTF8.self) + } + + // MARK: - Materialization + + /// Rebuilds a `Node` tree for the subtree rooted at the given raw index. + /// + /// The returned tree is freshly constructed and does not interact with the + /// global `NodeCache`. The store is hash-consed, so a subtree referenced + /// from multiple parents is a single index; the memo rebuilds each index + /// once and reuses the instance, preserving the store's DAG shape. + /// Expanding instead would multiply node count for symbols with heavy + /// substitution sharing and defeat the printer's per-instance memoization. + func materializeNode(at rawIndex: UInt32) -> Node { + var materializedByIndex: [UInt32: Node] = [:] + return materializeNode(at: rawIndex, materializedByIndex: &materializedByIndex) + } + + private func materializeNode(at rawIndex: UInt32, materializedByIndex: inout [UInt32: Node]) -> Node { + if let shared = materializedByIndex[rawIndex] { + return shared + } + let compact = compactNode(at: rawIndex) + let node: Node + switch compact.payloadKind { + case .none: + node = Node(kind: compact.kind) + case .index: + node = Node(kind: compact.kind, index: UInt64(compact.payloadWord0) | (UInt64(compact.payloadWord1) << 32)) + case .text: + node = Node(kind: compact.kind, text: text(offset: compact.payloadWord0, length: compact.payloadWord1)) + case .oneChild: + node = Node(kind: compact.kind, children: [materializeNode(at: compact.payloadWord0, materializedByIndex: &materializedByIndex)]) + case .twoChildren: + node = Node(kind: compact.kind, children: [ + materializeNode(at: compact.payloadWord0, materializedByIndex: &materializedByIndex), + materializeNode(at: compact.payloadWord1, materializedByIndex: &materializedByIndex), + ]) + case .manyChildren: + let edgesStart = Int(compact.payloadWord0) + let childCount = Int(compact.payloadWord1) + var children = [Node]() + children.reserveCapacity(childCount) + for edgeOffset in edgesStart ..< (edgesStart + childCount) { + children.append(materializeNode(at: edges[edgeOffset], materializedByIndex: &materializedByIndex)) + } + node = Node(kind: compact.kind, children: children) + } + materializedByIndex[rawIndex] = node + return node + } +} diff --git a/Sources/Demangling/Store/NodeStoreBuilder.swift b/Sources/Demangling/Store/NodeStoreBuilder.swift new file mode 100644 index 0000000..34585ed --- /dev/null +++ b/Sources/Demangling/Store/NodeStoreBuilder.swift @@ -0,0 +1,363 @@ +/// Append-only builder that constructs a `NodeStore`. +/// +/// The builder is noncopyable: exactly one owner may build at a time, and +/// `freeze()` consumes the builder, so "immutable after freezing" is enforced +/// by the type system rather than by locks or documentation. +/// +/// Every inserted node is hash-consed on entry: structurally equal subtrees +/// receive the same `NodeStore.NodeIndex`. Interior-node keys use child +/// indices, which is exact because children are always interned before their +/// parent (the same bottom-up scheme as `NodeCache.internTreeUnsafe`). +public struct NodeStoreBuilder: ~Copyable, Sendable { + private var nodes: ContiguousArray = [] + private var edges: ContiguousArray = [] + private var textBytes: ContiguousArray = [] + + // Interning tables are open-addressing slot arrays that store only node + // (or text) indices — 4 bytes per slot, no separate key storage. Keys are + // recovered from the flat buffers on comparison, so the tables add ~2 MB + // for a whole-framework build instead of the ~10 MB the dictionary-keyed + // scheme cost (proposal 0001, Phase 3 intern-table slimming). + + /// Slot sentinel for an empty open-addressing slot. + private static let emptySlot: UInt32 = .max + + /// Interning table for nodes whose 12-byte representation is already + /// canonical: leaves (text offsets are canonical because text is interned + /// first) and nodes with one or two children (child indices are canonical). + /// Slots hold node indices; the key is `nodes[slot]` itself. + private var compactSlots = ContiguousArray(repeating: emptySlot, count: 4096) + private var compactCount = 0 + + /// Interning table for nodes with three or more children, whose edges + /// offset is allocation-dependent and therefore cannot serve as a key. + /// Slots hold node indices; comparison walks the `edges` range. + private var manyChildrenSlots = ContiguousArray(repeating: emptySlot, count: 1024) + private var manyChildrenCount = 0 + + /// Interning table for text contents. Slots index into `uniqueTexts`; + /// comparison reads the `textBytes` range. + private var textSlots = ContiguousArray(repeating: emptySlot, count: 1024) + private var uniqueTexts: ContiguousArray = [] + + private struct TextLocation { + let offset: UInt32 + let length: UInt32 + } + + public init() {} + + // MARK: - Building + + /// Interns an existing `Node` tree, returning the canonical index of its root. + public mutating func intern(_ node: Node) -> NodeStore.NodeIndex { + var visitedIndices = [ObjectIdentifier: UInt32]() + return NodeStore.NodeIndex(rawValue: internRecursively(node, visitedIndices: &visitedIndices)) + } + + /// Demangles a mangled symbol and interns the resulting tree in one step. + /// + /// The intermediate `Node` tree is transient and fully cache-free: neither + /// leaves nor subtrees touch `NodeCache.shared`, so bulk demangling leaves + /// no trace in global state (proposal 0001, Phase 3). + public mutating func demangle(_ mangled: String, isType: Bool = false) throws(DemanglingError) -> NodeStore.NodeIndex { + let tree = try demangleAsNodeTransient(mangled, isType: isType) + return intern(tree) + } + + // MARK: - Direct Construction + + /// Interns a parameterless node. + public mutating func intern(kind: Node.Kind) -> NodeStore.NodeIndex { + NodeStore.NodeIndex(rawValue: internLeaf(kind: kind, contents: .none)) + } + + /// Interns a text-carrying leaf node. + public mutating func intern(kind: Node.Kind, text: String) -> NodeStore.NodeIndex { + NodeStore.NodeIndex(rawValue: internLeaf(kind: kind, contents: .text(text))) + } + + /// Interns an index-carrying leaf node. + public mutating func intern(kind: Node.Kind, index: UInt64) -> NodeStore.NodeIndex { + NodeStore.NodeIndex(rawValue: internLeaf(kind: kind, contents: .index(index))) + } + + /// Interns an interior node over already-interned children — e.g. a + /// `.type` wrapper around a stored subtree, the pattern index builders + /// use for dictionary keys. Children must be indices minted by this + /// builder; an empty child list interns a parameterless node. + /// + /// Hash-consing is shared with every other insertion route: constructing + /// a node directly and interning a structurally equal `Node` tree yield + /// the same index. + public mutating func intern(kind: Node.Kind, children: [NodeStore.NodeIndex]) -> NodeStore.NodeIndex { + let childIndices = children.map { childIndex in + precondition(Int(childIndex.rawValue) < nodes.count, "Child index does not belong to this builder") + return childIndex.rawValue + } + if childIndices.isEmpty { + return NodeStore.NodeIndex(rawValue: internLeaf(kind: kind, contents: .none)) + } + return NodeStore.NodeIndex(rawValue: internInterior(kind: kind, childIndices: childIndices)) + } + + /// Freezes the builder into an immutable, `Sendable` store. + /// + /// Consumes the builder; interning tables are dropped, only the flat + /// buffers survive. Indices minted by this builder remain valid in the + /// frozen store. + public consuming func freeze() -> NodeStore { + NodeStore(nodes: nodes, edges: edges, textBytes: textBytes) + } + + // MARK: - Statistics + + /// Number of unique nodes interned so far. + public var nodeCount: Int { nodes.count } + + // MARK: - Interning + + private mutating func internRecursively(_ node: Node, visitedIndices: inout [ObjectIdentifier: UInt32]) -> UInt32 { + let identifier = ObjectIdentifier(node) + if let existingIndex = visitedIndices[identifier] { + return existingIndex + } + + let children = node.children + let internedIndex: UInt32 + if children.isEmpty { + internedIndex = internLeaf(kind: node.kind, contents: node.contents) + } else { + var childIndices = [UInt32]() + childIndices.reserveCapacity(children.count) + for child in children { + childIndices.append(internRecursively(child, visitedIndices: &visitedIndices)) + } + internedIndex = internInterior(kind: node.kind, childIndices: childIndices) + } + + visitedIndices[identifier] = internedIndex + return internedIndex + } + + private mutating func internLeaf(kind: Node.Kind, contents: Node.Contents) -> UInt32 { + let compact: CompactNode + switch contents { + case .none: + compact = CompactNode(kind: kind, payloadKind: .none, payloadWord0: 0, payloadWord1: 0) + case .index(let indexValue): + compact = CompactNode( + kind: kind, + payloadKind: .index, + payloadWord0: UInt32(truncatingIfNeeded: indexValue), + payloadWord1: UInt32(truncatingIfNeeded: indexValue >> 32) + ) + case .text(let textValue): + let location = internText(textValue) + compact = CompactNode( + kind: kind, + payloadKind: .text, + payloadWord0: location.offset, + payloadWord1: location.length + ) + } + return internCanonicalCompact(compact) + } + + private mutating func internInterior(kind: Node.Kind, childIndices: [UInt32]) -> UInt32 { + switch childIndices.count { + case 1: + return internCanonicalCompact(CompactNode( + kind: kind, + payloadKind: .oneChild, + payloadWord0: childIndices[0], + payloadWord1: 0 + )) + case 2: + return internCanonicalCompact(CompactNode( + kind: kind, + payloadKind: .twoChildren, + payloadWord0: childIndices[0], + payloadWord1: childIndices[1] + )) + default: + return internManyChildren(kind: kind, childIndices: childIndices) + } + } + + private mutating func appendNode(_ compact: CompactNode) -> UInt32 { + precondition(nodes.count < Int(UInt32.max), "NodeStore node buffer exceeded UInt32 index space") + let newIndex = UInt32(nodes.count) + nodes.append(compact) + return newIndex + } + + // MARK: - Open-Addressing Interning Tables + + private static func mix(_ currentHash: Int, _ value: Int) -> Int { + (currentHash &* 0x9E3779B1) &+ value + } + + private static func hash(of compact: CompactNode) -> Int { + var combined = Int(compact.kindAndPayloadKind) + combined = mix(combined, Int(compact.payloadWord0)) + combined = mix(combined, Int(compact.payloadWord1)) + return mix(combined, 0) + } + + private mutating func internCanonicalCompact(_ compact: CompactNode) -> UInt32 { + if (compactCount &+ 1) &* 4 >= compactSlots.count &* 3 { + growCompactSlots() + } + let mask = compactSlots.count - 1 + var slot = Self.hash(of: compact) & mask + while true { + let existing = compactSlots[slot] + if existing == Self.emptySlot { + let newIndex = appendNode(compact) + compactSlots[slot] = newIndex + compactCount += 1 + return newIndex + } + if nodes[Int(existing)] == compact { + return existing + } + slot = (slot + 1) & mask + } + } + + private mutating func growCompactSlots() { + var grownSlots = ContiguousArray(repeating: Self.emptySlot, count: compactSlots.count * 2) + let mask = grownSlots.count - 1 + for existing in compactSlots where existing != Self.emptySlot { + var slot = Self.hash(of: nodes[Int(existing)]) & mask + while grownSlots[slot] != Self.emptySlot { + slot = (slot + 1) & mask + } + grownSlots[slot] = existing + } + compactSlots = grownSlots + } + + private static func hashOfManyChildren(kindAndPayloadKind: UInt16, childIndices: some Sequence) -> Int { + var combined = Int(kindAndPayloadKind) + for childIndex in childIndices { + combined = Self.mix(combined, Int(childIndex)) + } + return Self.mix(combined, 0) + } + + private func manyChildrenNodeMatches(_ existingIndex: UInt32, kindAndPayloadKind: UInt16, childIndices: [UInt32]) -> Bool { + let existing = nodes[Int(existingIndex)] + guard existing.kindAndPayloadKind == kindAndPayloadKind, + Int(existing.payloadWord1) == childIndices.count else { + return false + } + let edgesStart = Int(existing.payloadWord0) + return edges[edgesStart ..< edgesStart + childIndices.count].elementsEqual(childIndices) + } + + private mutating func internManyChildren(kind: Node.Kind, childIndices: [UInt32]) -> UInt32 { + if (manyChildrenCount &+ 1) &* 4 >= manyChildrenSlots.count &* 3 { + growManyChildrenSlots() + } + let kindAndPayloadKind = CompactNode(kind: kind, payloadKind: .manyChildren, payloadWord0: 0, payloadWord1: 0).kindAndPayloadKind + let mask = manyChildrenSlots.count - 1 + var slot = Self.hashOfManyChildren(kindAndPayloadKind: kindAndPayloadKind, childIndices: childIndices) & mask + while true { + let existing = manyChildrenSlots[slot] + if existing == Self.emptySlot { + precondition(edges.count + childIndices.count <= Int(UInt32.max), "NodeStore edges buffer exceeded UInt32 index space") + let edgesOffset = UInt32(edges.count) + edges.append(contentsOf: childIndices) + let newIndex = appendNode(CompactNode( + kind: kind, + payloadKind: .manyChildren, + payloadWord0: edgesOffset, + payloadWord1: UInt32(childIndices.count) + )) + manyChildrenSlots[slot] = newIndex + manyChildrenCount += 1 + return newIndex + } + if manyChildrenNodeMatches(existing, kindAndPayloadKind: kindAndPayloadKind, childIndices: childIndices) { + return existing + } + slot = (slot + 1) & mask + } + } + + private mutating func growManyChildrenSlots() { + var grownSlots = ContiguousArray(repeating: Self.emptySlot, count: manyChildrenSlots.count * 2) + let mask = grownSlots.count - 1 + for existing in manyChildrenSlots where existing != Self.emptySlot { + let compact = nodes[Int(existing)] + let edgesStart = Int(compact.payloadWord0) + let childCount = Int(compact.payloadWord1) + var slot = Self.hashOfManyChildren( + kindAndPayloadKind: compact.kindAndPayloadKind, + childIndices: edges[edgesStart ..< edgesStart + childCount] + ) & mask + while grownSlots[slot] != Self.emptySlot { + slot = (slot + 1) & mask + } + grownSlots[slot] = existing + } + manyChildrenSlots = grownSlots + } + + private static func hashOfTextBytes(_ bytes: some Sequence) -> Int { + // FNV-1a + var combined = Int(bitPattern: 0xCBF2_9CE4_8422_2325 as UInt) + for byte in bytes { + combined = (combined ^ Int(byte)) &* 0x100_0000_01B3 + } + return combined + } + + private func textLocationMatches(_ location: TextLocation, utf8Bytes: [UInt8]) -> Bool { + guard Int(location.length) == utf8Bytes.count else { return false } + let start = Int(location.offset) + return textBytes[start ..< start + utf8Bytes.count].elementsEqual(utf8Bytes) + } + + private mutating func internText(_ textValue: String) -> TextLocation { + if (uniqueTexts.count &+ 1) &* 4 >= textSlots.count &* 3 { + growTextSlots() + } + let utf8Bytes = Array(textValue.utf8) + let mask = textSlots.count - 1 + var slot = Self.hashOfTextBytes(utf8Bytes) & mask + while true { + let existing = textSlots[slot] + if existing == Self.emptySlot { + precondition(textBytes.count + utf8Bytes.count <= Int(UInt32.max), "NodeStore text buffer exceeded UInt32 offset space") + let location = TextLocation(offset: UInt32(textBytes.count), length: UInt32(utf8Bytes.count)) + textBytes.append(contentsOf: utf8Bytes) + textSlots[slot] = UInt32(uniqueTexts.count) + uniqueTexts.append(location) + return location + } + let existingLocation = uniqueTexts[Int(existing)] + if textLocationMatches(existingLocation, utf8Bytes: utf8Bytes) { + return existingLocation + } + slot = (slot + 1) & mask + } + } + + private mutating func growTextSlots() { + var grownSlots = ContiguousArray(repeating: Self.emptySlot, count: textSlots.count * 2) + let mask = grownSlots.count - 1 + for existing in textSlots where existing != Self.emptySlot { + let location = uniqueTexts[Int(existing)] + let start = Int(location.offset) + var slot = Self.hashOfTextBytes(textBytes[start ..< start + Int(location.length)]) & mask + while grownSlots[slot] != Self.emptySlot { + slot = (slot + 1) & mask + } + grownSlots[slot] = existing + } + textSlots = grownSlots + } +} diff --git a/Sources/Demangling/Utils/StackSafeExecutor.swift b/Sources/Demangling/Utils/StackSafeExecutor.swift index 8ce6dbe..3c01595 100644 --- a/Sources/Demangling/Utils/StackSafeExecutor.swift +++ b/Sources/Demangling/Utils/StackSafeExecutor.swift @@ -1,5 +1,8 @@ import Foundation +/// SPI note: exposed as `@_spi(Internals)` so deep consumers driving +/// `DemanglingPrinter` directly can reuse the same stack-safety wrapper the +/// library uses for its own print/remangle entry points. /// Executes blocks with automatic stack-size safety. /// /// On Darwin, non-main threads (including Swift Concurrency cooperative workers) @@ -7,18 +10,23 @@ import Foundation /// inside the demangler/remangler. This type detects insufficient remaining stack /// space and transparently re-dispatches the block to a dedicated 8MB-stack /// `Thread`. On non-Darwin platforms, the block runs directly. -enum StackSafeExecutor { +@_spi(Internals) +public enum StackSafeExecutor { #if canImport(Darwin) /// Minimum stack space (in bytes) required for safe recursive operations. private static let minimumRequiredStackSize = 2 * 1024 * 1024 // 2MB /// Stack size allocated for the dedicated large-stack thread. - private static let largeStackThreadSize = 8 * 1024 * 1024 // 8MB + fileprivate static let largeStackThreadSize = 8 * 1024 * 1024 // 8MB + + /// Stack space reserved below a budgeted recursion for the non-recursive + /// work that still has to run once the recursion unwinds. + private static let stackSafetyMargin = 64 * 1024 // 64KB #endif /// Executes the given block, switching to a large-stack thread if the /// current thread's remaining stack space is insufficient. - static func execute(_ block: @escaping @Sendable () -> String) -> String { + public static func execute(_ block: @escaping @Sendable () -> String) -> String { #if canImport(Darwin) if currentThreadHasSufficientStack { return block() @@ -34,7 +42,7 @@ enum StackSafeExecutor { /// Re-dispatches to a dedicated 8MB-stack `Thread` when the current thread /// is about to run out of room, and propagates typed errors across the /// thread boundary. - static func execute( + public static func execute( _ block: @escaping @Sendable () throws(Failure) -> Success ) throws(Failure) -> Success { #if canImport(Darwin) @@ -56,7 +64,7 @@ enum StackSafeExecutor { /// has enough room, the block runs inline without spawning a thread or /// suspending. Use this from async contexts when you want to avoid blocking /// a cooperative worker on an OS-level semaphore. - static func executeAsync( + public static func executeAsync( _ block: @escaping @Sendable () throws(Failure) -> Success ) async throws(Failure) -> Success { #if canImport(Darwin) @@ -64,16 +72,13 @@ enum StackSafeExecutor { return try block() } let outcome: Result = await withCheckedContinuation { continuation in - let thread = Thread { + LargeStackThreadPool.shared.submit { do throws(Failure) { continuation.resume(returning: .success(try block())) } catch { continuation.resume(returning: .failure(error)) } } - thread.stackSize = largeStackThreadSize - thread.qualityOfService = .userInitiated - thread.start() } return try outcome.get() #else @@ -81,7 +86,85 @@ enum StackSafeExecutor { #endif } + /// Runs a recursion that can bail out when it approaches the end of the + /// current thread's stack, falling back to a large-stack worker only for + /// the inputs that actually need one. + /// + /// ``execute(_:)`` has to assume the worst about every input, so on a + /// 512KB-stack thread it hands *every* call to a worker thread. Most real + /// inputs are nowhere near deep enough to need that: `printName` recursion + /// for a typical symbol is a few dozen frames. `budgetedAttempt` receives + /// the address the stack must not grow past and returns `nil` if it hit + /// that limit; only then does `unbudgetedFallback` run on a worker. + /// + /// - Parameters: + /// - budgetedAttempt: runs inline on the current thread. Must return + /// `nil` — having produced no side effects the caller depends on — + /// when the recursion reaches `stackFloorAddress`. + /// - unbudgetedFallback: re-runs the same work with no depth limit, on a + /// thread known to have room for it. + public static func executeWithinStackBudget( + budgetedAttempt: (_ stackFloorAddress: UInt) -> Success?, + unbudgetedFallback: @escaping @Sendable () -> Success + ) -> Success { + #if canImport(Darwin) + if currentThreadHasSufficientStack { + return unbudgetedFallback() + } + if let result = budgetedAttempt(stackFloorAddressForCurrentThread) { + return result + } + return executeOnLargeStackThreadReturning(unbudgetedFallback) + #else + return unbudgetedFallback() + #endif + } + + /// Throwing variant of + /// ``executeWithinStackBudget(budgetedAttempt:unbudgetedFallback:)``. + /// + /// An error thrown by `budgetedAttempt` is a genuine failure of the work + /// itself (a malformed tree), not a budget signal — budget exhaustion is + /// reported by returning `nil` — so it propagates instead of retrying on a + /// worker, which would only reproduce it. + public static func executeWithinStackBudget( + budgetedAttempt: (_ stackFloorAddress: UInt) throws(Failure) -> Success?, + unbudgetedFallback: @escaping @Sendable () throws(Failure) -> Success + ) throws(Failure) -> Success { + #if canImport(Darwin) + if currentThreadHasSufficientStack { + return try unbudgetedFallback() + } + if let result = try budgetedAttempt(stackFloorAddressForCurrentThread) { + return result + } + return try executeOnLargeStackThreadThrowing(unbudgetedFallback) + #else + return try unbudgetedFallback() + #endif + } + #if canImport(Darwin) + private static var stackFloorAddressForCurrentThread: UInt { + let stackAddress = pthread_get_stackaddr_np(pthread_self()) + let stackSize = pthread_get_stacksize_np(pthread_self()) + let stackBase = UInt(bitPattern: stackAddress - stackSize) + return stackBase + UInt(stackSafetyMargin) + } + + private static func executeOnLargeStackThreadReturning( + _ block: @escaping @Sendable () -> Success + ) -> Success { + nonisolated(unsafe) var result: Success! + let semaphore = DispatchSemaphore(value: 0) + LargeStackThreadPool.shared.submit { + result = block() + semaphore.signal() + } + semaphore.wait() + return result + } + private static var currentThreadHasSufficientStack: Bool { let stackAddress = pthread_get_stackaddr_np(pthread_self()) let stackSize = pthread_get_stacksize_np(pthread_self()) @@ -95,13 +178,10 @@ enum StackSafeExecutor { private static func executeOnLargeStackThread(_ block: @escaping @Sendable () -> String) -> String { nonisolated(unsafe) var result: String = "" let semaphore = DispatchSemaphore(value: 0) - let thread = Thread { + LargeStackThreadPool.shared.submit { result = block() semaphore.signal() } - thread.stackSize = largeStackThreadSize - thread.qualityOfService = .userInitiated - thread.start() semaphore.wait() return result } @@ -111,7 +191,7 @@ enum StackSafeExecutor { ) throws(Failure) -> Success { nonisolated(unsafe) var outcome: Result! let semaphore = DispatchSemaphore(value: 0) - let thread = Thread { + LargeStackThreadPool.shared.submit { do throws(Failure) { outcome = .success(try block()) } catch { @@ -119,11 +199,76 @@ enum StackSafeExecutor { } semaphore.signal() } - thread.stackSize = largeStackThreadSize - thread.qualityOfService = .userInitiated - thread.start() semaphore.wait() return try outcome.get() } #endif } + +#if canImport(Darwin) +/// A pool of long-lived large-stack worker threads, reused across calls. +/// +/// Every call used to create — and then join — a brand new `Thread`. Because +/// ``StackSafeExecutor/currentThreadHasSufficientStack`` demands 2MB of +/// *remaining* stack while a Swift Concurrency cooperative worker and a +/// libdispatch worker both get a 512KB stack in total, the large-stack branch +/// is taken unconditionally off the main thread: measured on this package, +/// 2000 demangles cost 64ms on the main thread (run inline) against 163ms on a +/// `DispatchQueue.global()` thread, i.e. roughly 50µs of thread setup on top of +/// a ~32µs demangle. Bulk work — demangling every symbol of a framework, or +/// printing every declaration of an interface — paid that per item. +/// +/// Threads are created on demand, reused while work keeps arriving, and retired +/// after an idle period so a burst does not leave workers resident forever. +/// +/// A worker never submits back into the pool: it runs on an 8MB stack, so +/// ``StackSafeExecutor/execute(_:)`` takes its inline branch there, and nested +/// demangle/remangle calls cannot deadlock against a saturated pool. +private final class LargeStackThreadPool: @unchecked Sendable { + static let shared = LargeStackThreadPool() + + /// How long an idle worker waits for new work before retiring. + private static let idleTimeout: TimeInterval = 30 + + private let condition = NSCondition() + private var pendingWorkItems: [@Sendable () -> Void] = [] + private var idleWorkerCount = 0 + + func submit(_ workItem: @escaping @Sendable () -> Void) { + condition.lock() + pendingWorkItems.append(workItem) + // Only spin up a worker when the queue outgrows the idle workers that + // are already parked on the condition; overshooting under a race just + // creates one extra worker, which then retires on its idle timeout. + let needsAdditionalWorker = pendingWorkItems.count > idleWorkerCount + condition.signal() + condition.unlock() + + if needsAdditionalWorker { + let thread = Thread { [self] in runWorkerLoop() } + thread.stackSize = StackSafeExecutor.largeStackThreadSize + thread.qualityOfService = .userInitiated + thread.start() + } + } + + private func runWorkerLoop() { + while true { + condition.lock() + idleWorkerCount += 1 + while pendingWorkItems.isEmpty { + if !condition.wait(until: Date(timeIntervalSinceNow: Self.idleTimeout)) { + idleWorkerCount -= 1 + condition.unlock() + return + } + } + idleWorkerCount -= 1 + let workItem = pendingWorkItems.removeFirst() + condition.unlock() + + workItem() + } + } +} +#endif diff --git a/Tests/DemanglingTests/NodeCacheTests.swift b/Tests/DemanglingTests/NodeCacheTests.swift index 3aa504b..c78c28f 100644 --- a/Tests/DemanglingTests/NodeCacheTests.swift +++ b/Tests/DemanglingTests/NodeCacheTests.swift @@ -272,7 +272,7 @@ struct NodeCacheDemangleTests { } @Test func interningDoesNotAffectPrintingOrRemangling() throws { - let mangled = "$s7SwiftUI4TextV_10FoundationE9formatterAcA20LocalizedStringStyleV_xtcSyRzlufc" + let mangled = "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC" let interned = try demangleAsNode(mangled) let uninterned = try demangleAsNode(mangled, internsSubtrees: false) diff --git a/Tests/DemanglingTests/NodePrinterScopeTests.swift b/Tests/DemanglingTests/NodePrinterScopeTests.swift new file mode 100644 index 0000000..f1d0116 --- /dev/null +++ b/Tests/DemanglingTests/NodePrinterScopeTests.swift @@ -0,0 +1,88 @@ +import Foundation +import Testing +@_spi(Internals) @testable import Demangling + +/// Guards the type-reference scope seam of `NodePrinterTarget`. +/// +/// The engine delivers the nominal reference node to +/// `pushTypeReferenceScope` lazily (autoclosure): rich targets that use +/// scope identity receive the same sequence whether printing a `Node` tree +/// or a store-backed `NodeReference` (which must materialize to service the +/// hook), while scope-ignoring targets (`String`, the default +/// implementation) never evaluate the closure and keep the store's +/// plain-text path materialization-free. +@Suite +struct NodePrinterScopeTests { + /// A target that mirrors `String`'s writing behavior exactly (so the + /// print flow is identical) while recording every scope event. Scope + /// identity is captured as the remangled string of the delivered node, + /// which is structural — equal across representations exactly when the + /// delivered subtrees are structurally equal. + private struct ScopeRecordingTarget: NodePrinterTarget { + enum ScopeEvent: Equatable, Sendable { + case push(identity: String?) + case pop + } + + var text: String = "" + var scopeEvents: [ScopeEvent] = [] + + init() {} + + var count: Int { text.count } + + mutating func write(_ content: String) { + text.write(content) + } + + mutating func append(_ other: ScopeRecordingTarget) { + text.append(other.text) + scopeEvents.append(contentsOf: other.scopeEvents) + } + + mutating func pushTypeReferenceScope(_ node: @autoclosure () -> Node?) { + let identity = node().flatMap { try? mangleAsString($0) } + scopeEvents.append(.push(identity: identity)) + } + + mutating func popTypeReferenceScope() { + scopeEvents.append(.pop) + } + } + + @Test func scopeIdentitySequenceMatchesAcrossRepresentations() throws { + let mangledStrings = [ + "$sSiD", + "$sSaySiGD", + "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC", + "$s7SwiftUI15ModifiedContentVyxq_GAA0D0AAMc", + "$sSS7cStringSSSPys4Int8VG_tcfC", + ] + for mangled in mangledStrings { + var storeBuilder = NodeStoreBuilder() + let rootIndex = try storeBuilder.demangle(mangled) + let store = storeBuilder.freeze() + let reference = store.reference(at: rootIndex) + + let nodeTree = try demangleAsNode(mangled, internsSubtrees: false) + + let nodePathTarget = StackSafeExecutor.execute { () -> ScopeRecordingTarget in + var nodePathPrinter = NodePrinter(options: .default) + return nodePathPrinter.printRoot(nodeTree) + } + let referencePathTarget = StackSafeExecutor.execute { () -> ScopeRecordingTarget in + var referencePathPrinter = DemanglingPrinter(options: .default) + return referencePathPrinter.printRoot(reference) + } + + #expect(referencePathTarget.text == nodePathTarget.text, "print divergence for \(mangled)") + #expect(referencePathTarget.scopeEvents == nodePathTarget.scopeEvents, "scope divergence for \(mangled)") + + let deliveredIdentityCount = referencePathTarget.scopeEvents.count(where: { scopeEvent in + if case .push(.some) = scopeEvent { return true } + return false + }) + #expect(deliveredIdentityCount > 0, "expected at least one nominal reference scope in \(mangled)") + } + } +} diff --git a/Tests/DemanglingTests/NodeStorePhase3AcceptanceTests.swift b/Tests/DemanglingTests/NodeStorePhase3AcceptanceTests.swift new file mode 100644 index 0000000..428e0af --- /dev/null +++ b/Tests/DemanglingTests/NodeStorePhase3AcceptanceTests.swift @@ -0,0 +1,77 @@ +import Foundation +import Testing +@testable import Demangling +@testable import DemanglingTestingSupport + +/// Acceptance measurement for proposal 0001 Phase 3 on the live dyld-cache +/// SwiftUI corpus: retained flat storage must stay under the 6 MB target and +/// the cache-free bulk build must not regress against the Node path beyond +/// the 1.2x budget (asserted generously at 2x to absorb CI noise; measured +/// numbers are recorded in the proposal's decision log). +@Suite +final class NodeStorePhase3AcceptanceTests: DyldCacheSymbolTests, @unchecked Sendable { + private static func physicalFootprint() -> Int? { + var info = task_vm_info_data_t() + var count = mach_msg_type_number_t(MemoryLayout.size / MemoryLayout.size) + let result = withUnsafeMutablePointer(to: &info) { + $0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) { + task_info(mach_task_self_, task_flavor_t(TASK_VM_INFO), $0, &count) + } + } + guard result == KERN_SUCCESS else { return nil } + return Int(info.phys_footprint) + } + + /// Synchronous on purpose so the historical (interning) sync overload of + /// `demangleAsNode` is selected as the throughput baseline. + private static func runNodePathBaseline(_ corpus: [String]) -> Int { + var failureCount = 0 + for mangled in corpus { + do { + _ = try demangleAsNode(mangled) + } catch { + failureCount += 1 + } + } + return failureCount + } + + @Test func phase3AcceptanceOnMainImageCorpus() async throws { + let corpus = try await symbols(for: .SwiftUI).map(\.stringValue) + try #require(!corpus.isEmpty, "SwiftUI corpus unavailable on this machine") + + let footprintBefore = Self.physicalFootprint() + let storeBuildStart = ContinuousClock.now + var builder = NodeStoreBuilder() + var storeFailureCount = 0 + for mangled in corpus { + do { + _ = try builder.demangle(mangled) + } catch { + storeFailureCount += 1 + } + } + let store = builder.freeze() + let storeBuildDuration = ContinuousClock.now - storeBuildStart + let footprintAfter = Self.physicalFootprint() + + let nodePathStart = ContinuousClock.now + let nodePathFailureCount = Self.runNodePathBaseline(corpus) + let nodePathDuration = ContinuousClock.now - nodePathStart + + let footprintDelta = (footprintAfter ?? 0) - (footprintBefore ?? 0) + print(""" + [phase3-acceptance] symbols=\(corpus.count) storeFailures=\(storeFailureCount) nodePathFailures=\(nodePathFailureCount) + [phase3-acceptance] uniqueNodes=\(store.nodeCount) storageBytes=\(store.storageByteCount) (nodes=\(store.nodeCount * 12) edges=\(store.edgeCount * 4) text=\(store.textByteCount)) + [phase3-acceptance] storeBuild=\(storeBuildDuration) nodePath=\(nodePathDuration) footprintDeltaDuringBuild=\(footprintDelta) + """) + + #expect(storeFailureCount == nodePathFailureCount, "Both paths should fail on exactly the same symbols") + // Corpus size varies by machine/OS build, so the storage target is + // asserted per unit: <=16 bytes per unique node (design value 12 plus + // edges/text amortization) and a per-symbol sanity budget. + #expect(store.storageByteCount <= store.nodeCount * 16, "Flat storage should stay within 16 bytes per unique node") + #expect(store.storageByteCount <= corpus.count * 64, "Flat storage should stay within 64 bytes per corpus symbol") + #expect(storeBuildDuration < nodePathDuration * 2, "Store build should stay within the throughput budget") + } +} diff --git a/Tests/DemanglingTests/NodeStoreTests.swift b/Tests/DemanglingTests/NodeStoreTests.swift new file mode 100644 index 0000000..4d59d0f --- /dev/null +++ b/Tests/DemanglingTests/NodeStoreTests.swift @@ -0,0 +1,463 @@ +import Foundation +import Testing +@_spi(Internals) @testable import Demangling + +/// Unit tests for NodeStore — arena-based compact node storage (proposal 0001, Phase 1). +@Suite +struct NodeStoreTests { + + // MARK: - Kind Ordinal Mapping + + @Test func kindOrdinalRoundTripsForAllKinds() { + for kind in Node.Kind.allCases { + let compact = CompactNode(kind: kind, payloadKind: .none, payloadWord0: 0, payloadWord1: 0) + #expect(compact.kind == kind, "Kind \(kind) should round-trip through the 9-bit ordinal") + #expect(compact.payloadKind == .none) + } + } + + @Test func compactNodeIsTwelveBytes() { + #expect(MemoryLayout.size == 12) + #expect(MemoryLayout.stride == 12) + } + + // MARK: - Import / Materialize Round-Trip + + @Test func importAndMaterializeRoundTrip() { + var builder = NodeStoreBuilder() + + let tree = Node(kind: .global, children: [ + Node(kind: .type, children: [ + Node(kind: .structure, children: [ + Node(kind: .module, text: "Swift"), + Node(kind: .identifier, text: "Int"), + ]), + ]), + Node(kind: .index, index: 42), + Node(kind: .index, index: UInt64.max), + Node(kind: .emptyList), + ]) + + let rootIndex = builder.intern(tree) + let store = builder.freeze() + let materialized = store.reference(at: rootIndex).materialize() + + #expect(materialized == tree, "Import -> materialize should reproduce a structurally equal tree") + } + + @Test func largeIndexPayloadRoundTrips() { + var builder = NodeStoreBuilder() + let indexValues: [UInt64] = [0, 1, UInt64(UInt32.max), UInt64(UInt32.max) + 1, UInt64.max] + + var rootIndices = [NodeStore.NodeIndex]() + for indexValue in indexValues { + rootIndices.append(builder.intern(Node(kind: .index, index: indexValue))) + } + let store = builder.freeze() + + for (rootIndex, indexValue) in zip(rootIndices, indexValues) { + #expect(store.reference(at: rootIndex).index == indexValue) + } + } + + // MARK: - Hash-Consing + + @Test func structurallyEqualTreesShareOneIndex() { + var builder = NodeStoreBuilder() + + func makeTree() -> Node { + Node(kind: .type, children: [ + Node(kind: .structure, children: [ + Node(kind: .module, text: "Swift"), + Node(kind: .identifier, text: "Int"), + ]), + ]) + } + + let firstIndex = builder.intern(makeTree()) + let nodeCountAfterFirst = builder.nodeCount + let secondIndex = builder.intern(makeTree()) + + #expect(firstIndex == secondIndex, "Structurally equal trees should intern to the same index") + #expect(builder.nodeCount == nodeCountAfterFirst, "Re-interning an equal tree should not add nodes") + } + + @Test func manyChildrenNodesDeduplicate() { + var builder = NodeStoreBuilder() + + func makeWideTree() -> Node { + Node(kind: .tuple, children: [ + Node(kind: .identifier, text: "a"), + Node(kind: .identifier, text: "b"), + Node(kind: .identifier, text: "c"), + Node(kind: .identifier, text: "d"), + ]) + } + + let firstIndex = builder.intern(makeWideTree()) + let secondIndex = builder.intern(makeWideTree()) + let store = builder.freeze() + + #expect(firstIndex == secondIndex, "Equal many-children trees should intern to the same index") + #expect(store.edgeCount == 4, "The edges of an equal many-children node should be stored once") + #expect(store.reference(at: firstIndex).children.count == 4) + } + + @Test func textBytesDeduplicate() { + var builder = NodeStoreBuilder() + + _ = builder.intern(Node(kind: .identifier, text: "duplicated")) + _ = builder.intern(Node(kind: .module, text: "duplicated")) + let store = builder.freeze() + + #expect(store.textByteCount == "duplicated".utf8.count, "Equal text should be stored once across kinds") + } + + // MARK: - Reference Accessors + + @Test func referenceAccessorsMatchMaterializedTree() throws { + var builder = NodeStoreBuilder() + let mangled = "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC" + let rootIndex = try builder.demangle(mangled) + let store = builder.freeze() + + let expectedTree = try demangleAsNode(mangled, internsSubtrees: false) + + func compare(_ reference: NodeReference, _ node: Node) { + #expect(reference.kind == node.kind) + #expect(reference.text == node.text) + #expect(reference.index == node.index) + #expect(reference.children.count == node.children.count) + for (childReference, childNode) in zip(reference.children, node.children) { + compare(childReference, childNode) + } + } + + compare(store.reference(at: rootIndex), expectedTree) + } + + // MARK: - Demangle Parity + + @Test func demangleParityWithNodePath() throws { + // Zero-materialization store printing must be byte-identical to the Node + // path across option sets, including generic params (dependentGenericParamType + // text synthesis) and sugared types. + let mangledSymbols = [ + "$sSiD", + "$sSaySiGD", + "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC", + "$s4main3FooVAA1P0B0fMq_", + "$s7SwiftUI4ViewP", + "$s4main1gyxxlF", // g(A) -> A + "$s7SwiftUI15ModifiedContentVyxq_GAA0D0AAMc", // ModifiedContent conformance + ] + let optionSets: [DemangleOptions] = [ + .default, + .simplified, + .default.union(.synthesizeSugarOnTypes), + ] + + var builder = NodeStoreBuilder() + var rootIndices = [NodeStore.NodeIndex]() + for mangled in mangledSymbols { + rootIndices.append(try builder.demangle(mangled)) + } + let store = builder.freeze() + + for options in optionSets { + for (rootIndex, mangled) in zip(rootIndices, mangledSymbols) { + let storePrinted = store.reference(at: rootIndex).print(using: options) + let nodePrinted = try demangleAsNode(mangled, internsSubtrees: false).print(using: options) + #expect(storePrinted == nodePrinted, "Store-backed printing should match the Node path for \(mangled)") + } + } + } + + @Test func materializePreservesSubtreeSharing() throws { + // g(A) -> A: the generic parameter type occurs as both argument and + // return type, so the hash-consed store holds one index for it. The + // materialized tree must keep that sharing as one Node instance rather + // than expanding the DAG into duplicates. + var builder = NodeStoreBuilder() + let rootIndex = try builder.demangle("$s4main1gyxxlF") + let store = builder.freeze() + + let materialized = store.reference(at: rootIndex).materialize() + let nodePathTree = try demangleAsNode("$s4main1gyxxlF", internsSubtrees: false) + #expect(materialized == nodePathTree) + + let genericParameterNodes = materialized.all(of: .dependentGenericParamType) + #expect(genericParameterNodes.count >= 2, "Expected the generic parameter to occur in multiple positions") + for genericParameterNode in genericParameterNodes.dropFirst() { + #expect(genericParameterNode === genericParameterNodes[0], "Shared store subtrees should materialize as one shared instance") + } + } + + @Test func traversalParityWithNodePath() throws { + // NodeReference's Sequence conformance and kind-lookup helpers must + // walk the store in the same order the Node path walks the class tree. + let mangledSymbols = [ + "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC", + "$s4main1gyxxlF", + "$s7SwiftUI15ModifiedContentVyxq_GAA0D0AAMc", + ] + var builder = NodeStoreBuilder() + var rootIndices = [NodeStore.NodeIndex]() + for mangled in mangledSymbols { + rootIndices.append(try builder.demangle(mangled)) + } + let store = builder.freeze() + + for (rootIndex, mangled) in zip(rootIndices, mangledSymbols) { + let nodePathTree = try demangleAsNode(mangled, internsSubtrees: false) + let reference = store.reference(at: rootIndex) + + let referenceKinds = reference.map(\.kind) + let nodeKinds = nodePathTree.map(\.kind) + #expect(referenceKinds == nodeKinds, "Preorder kind sequences should match for \(mangled)") + + let referencePostorderKinds = Array(reference.postorder().map(\.kind)) + let nodePostorderKinds = Array(nodePathTree.postorder().map(\.kind)) + #expect(referencePostorderKinds == nodePostorderKinds, "Postorder kind sequences should match for \(mangled)") + + #expect(reference.first(of: .identifier)?.text == nodePathTree.first(of: .identifier)?.text) + #expect(reference.all(of: .type).count == nodePathTree.all(of: .type).count) + #expect(reference.contains(.functionType) == nodePathTree.contains(.functionType)) + #expect(reference.identifier == nodePathTree.identifier, "identifier should match for \(mangled)") + } + } + + @Test func bridgeDemanglingLeavesNodeCacheUntouched() throws { + // Phase 3: the builder's demangle bridge must be fully cache-free. + // Cache-free demangling constructs fresh leaves every time, so two + // transient runs share no instances; the cached path interns leaves + // eagerly, so the same leaf is one canonical instance across runs. + // (Identity-based so concurrent suites interning into the global + // NodeCache cannot flake this test.) + let mangled = "$s4main27TestPhase3CacheFreeSentinelVD" + + let transientFirst = try demangleAsNodeTransient(mangled) + let transientSecond = try demangleAsNodeTransient(mangled) + let transientIdentifierFirst = try #require(transientFirst.first(of: .identifier)) + let transientIdentifierSecond = try #require(transientSecond.first(of: .identifier)) + #expect(transientIdentifierFirst.text == "TestPhase3CacheFreeSentinel") + #expect(transientIdentifierFirst !== transientIdentifierSecond, "Transient demangling should not canonicalize leaves") + + let cachedFirst = try demangleAsNode(mangled, internsSubtrees: false) + let cachedSecond = try demangleAsNode(mangled, internsSubtrees: false) + let cachedIdentifierFirst = try #require(cachedFirst.first(of: .identifier)) + let cachedIdentifierSecond = try #require(cachedSecond.first(of: .identifier)) + #expect(cachedIdentifierFirst === cachedIdentifierSecond, "Cached demangling interns leaves globally") + + // The transient tree still interns into the store correctly. + var builder = NodeStoreBuilder() + let rootIndex = try builder.demangle(mangled) + let store = builder.freeze() + #expect(store.reference(at: rootIndex).print(using: .default) == cachedFirst.print(using: .default)) + } + + @Test func directConstructionSharesHashConsingWithTreeInterning() { + // Building Swift.Int by hand and interning the equivalent Node tree + // must collapse to the same index, and print identically. + var builder = NodeStoreBuilder() + + let moduleIndex = builder.intern(kind: .module, text: "Swift") + let identifierIndex = builder.intern(kind: .identifier, text: "Int") + let structureIndex = builder.intern(kind: .structure, children: [moduleIndex, identifierIndex]) + let typeIndex = builder.intern(kind: .type, children: [structureIndex]) + + let equivalentTree = Node(kind: .type, children: [ + Node(kind: .structure, children: [ + Node(kind: .module, text: "Swift"), + Node(kind: .identifier, text: "Int"), + ]), + ]) + let internedTreeIndex = builder.intern(equivalentTree) + #expect(internedTreeIndex == typeIndex, "Direct construction and tree interning should hash-cons to one index") + + let emptyListIndex = builder.intern(kind: .emptyList) + let indexNodeIndex = builder.intern(kind: .index, index: 42) + + let store = builder.freeze() + #expect(store.reference(at: typeIndex).print(using: .default) == equivalentTree.print(using: .default)) + #expect(store.reference(at: emptyListIndex).kind == .emptyList) + #expect(store.reference(at: indexNodeIndex).index == 42) + } + + @Test func byteLevelTextWitnessesMatchNodePath() throws { + // The allocation-free isIdentifier/isSwiftModule witnesses and the + // zero-copy textUTF8 view must agree with the Node path everywhere. + let mangledSymbols = [ + "$sSaySiGD", + "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC", + ] + var builder = NodeStoreBuilder() + var rootIndices = [NodeStore.NodeIndex]() + for mangled in mangledSymbols { + rootIndices.append(try builder.demangle(mangled)) + } + let store = builder.freeze() + + for (rootIndex, mangled) in zip(rootIndices, mangledSymbols) { + let nodePathTree = try demangleAsNode(mangled, internsSubtrees: false) + for (reference, node) in zip(store.reference(at: rootIndex), nodePathTree) { + #expect(reference.isSwiftModule == node.isSwiftModule) + #expect(reference.isIdentifier(desired: "Array") == node.isIdentifier(desired: "Array")) + #expect(reference.isIdentifier(desired: "Int") == node.isIdentifier(desired: "Int")) + if let bytes = reference.textUTF8 { + #expect(Array(bytes) == Array((node.text ?? "").utf8), "textUTF8 should be the exact stored bytes") + } + } + } + } + + @Test func remangleParityWithNodePath() throws { + // Remangling a NodeReference (bridged through materialization) must + // produce the same mangled string as the Node path. + let mangledSymbols = [ + "$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC", + "$s4main1gyxxlF", + "$s7SwiftUI15ModifiedContentVyxq_GAA0D0AAMc", + "$s4main3FooVAA1P0B0fMq_", + ] + var builder = NodeStoreBuilder() + var rootIndices = [NodeStore.NodeIndex]() + for mangled in mangledSymbols { + rootIndices.append(try builder.demangle(mangled)) + } + let store = builder.freeze() + + for (rootIndex, mangled) in zip(rootIndices, mangledSymbols) { + let nodePathTree = try demangleAsNode(mangled, internsSubtrees: false) + let nodePathMangled = try mangleAsString(nodePathTree) + let storePathMangled = try mangleAsString(store.reference(at: rootIndex)) + #expect(storePathMangled == nodePathMangled, "Store-backed remangling should match the Node path for \(mangled)") + } + } + + @Test func sharedSubtreesAcrossSymbolsShareIndices() throws { + var builder = NodeStoreBuilder() + let firstRootIndex = try builder.demangle("$sSiD") + let secondRootIndex = try builder.demangle("$sSaySiGD") + let store = builder.freeze() + + func findIntStructure(_ reference: NodeReference) -> NodeReference? { + if reference.kind == .structure, reference.children.contains(where: { $0.text == "Int" }) { + return reference + } + for child in reference.children { + if let found = findIntStructure(child) { + return found + } + } + return nil + } + + let firstIntStructure = findIntStructure(store.reference(at: firstRootIndex)) + let secondIntStructure = findIntStructure(store.reference(at: secondRootIndex)) + #expect(firstIntStructure != nil) + #expect(secondIntStructure != nil) + #expect(firstIntStructure == secondIntStructure, "The Swift.Int subtree should share one index across symbols") + } + + // MARK: - Reference Identity + + @Test func referenceEqualityIsStoreAndIndexBased() { + var firstBuilder = NodeStoreBuilder() + _ = firstBuilder.intern(Node(kind: .identifier, text: "same")) + let firstStore = firstBuilder.freeze() + + var secondBuilder = NodeStoreBuilder() + _ = secondBuilder.intern(Node(kind: .identifier, text: "same")) + let secondStore = secondBuilder.freeze() + + let firstReference = firstStore.reference(at: NodeStore.NodeIndex(rawValue: 0)) + let secondReference = secondStore.reference(at: NodeStore.NodeIndex(rawValue: 0)) + + #expect(firstReference != secondReference, "References into different stores should not be equal") + #expect(firstReference == firstStore.reference(at: NodeStore.NodeIndex(rawValue: 0))) + } +} + +// MARK: - Cross-Representation Structural Equality + +@Suite +struct NodeReferenceStructuralEqualityTests { + private static let mangledSymbol = "$s7SwiftUI4ViewPAAE7paddingyQrAA4EdgeO3SetV_12CoreGraphics7CGFloatVSgtF" + + @Test func structurallyEqualsMatchesEquivalentNodeTree() throws { + var builder = NodeStoreBuilder() + let nodeIndex = try builder.demangle(Self.mangledSymbol) + let reference = builder.freeze().reference(at: nodeIndex) + + let equivalentTree = try demangleAsNode(Self.mangledSymbol) + #expect(reference.structurallyEquals(equivalentTree)) + + let differentTree = try demangleAsNode("$s7SwiftUI4ViewP") + #expect(!reference.structurallyEquals(differentTree)) + } + + @Test func structurallyEqualsDistinguishesTextAndIndexPayloads() { + var builder = NodeStoreBuilder() + let identifierIndex = builder.intern(kind: .identifier, text: "padding") + let store = builder.freeze() + let reference = store.reference(at: identifierIndex) + + #expect(reference.structurallyEquals(Node.create(kind: .identifier, text: "padding"))) + #expect(!reference.structurallyEquals(Node.create(kind: .identifier, text: "spacing"))) + #expect(!reference.structurallyEquals(Node.create(kind: .module, text: "padding"))) + #expect(!reference.structurallyEquals(Node.create(kind: .identifier))) + } + + @Test func structurallyEqualsWalksChildren() throws { + var builder = NodeStoreBuilder() + let childIndex = builder.intern(kind: .identifier, text: "padding") + let wrapperIndex = builder.intern(kind: .type, children: [childIndex]) + let store = builder.freeze() + let wrapperReference = store.reference(at: wrapperIndex) + + #expect(wrapperReference.structurallyEquals(Node.create(kind: .type, child: Node.create(kind: .identifier, text: "padding")))) + #expect(!wrapperReference.structurallyEquals(Node.create(kind: .type, child: Node.create(kind: .identifier, text: "spacing")))) + #expect(!wrapperReference.structurallyEquals(Node.create(kind: .identifier, text: "padding"))) + } +} + +// MARK: - Interning convenience (Stage 5 upstream additions) + +@Suite +struct NodeReferenceInterningConvenienceTests { + @Test func interningInitializerRoundTrip() throws { + let tree = try demangleAsNodeTransient("$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC") + let reference = NodeReference(interning: tree) + #expect(reference.structurallyEquals(tree)) + #expect(reference.print(using: .default) == tree.print(using: .default)) + } + + @Test func crossStoreStructuralEquality() throws { + let mangled = "$sSaySiGD" + var firstBuilder = NodeStoreBuilder() + let firstIndex = try firstBuilder.demangle(mangled) + let firstReference = firstBuilder.freeze().reference(at: firstIndex) + + var secondBuilder = NodeStoreBuilder() + _ = try secondBuilder.demangle("$sS2iIegyd_D") + let secondIndex = try secondBuilder.demangle(mangled) + let secondReference = secondBuilder.freeze().reference(at: secondIndex) + + #expect(firstReference != secondReference) + #expect(firstReference.structurallyEquals(secondReference)) + #expect(secondReference.structurallyEquals(firstReference)) + + let differentReference = NodeReference(interning: try demangleAsNodeTransient("$sSiD")) + #expect(!firstReference.structurallyEquals(differentReference)) + } + + @Test func sameStoreStructuralEqualityIsIndexEquality() throws { + var builder = NodeStoreBuilder() + let firstIndex = try builder.demangle("$sSaySiGD") + let secondIndex = try builder.demangle("$sSaySiGD") + let store = builder.freeze() + #expect(firstIndex == secondIndex) + #expect(store.reference(at: firstIndex).structurallyEquals(store.reference(at: secondIndex))) + } +} diff --git a/Tests/DemanglingTests/TypeDecoderTests.swift b/Tests/DemanglingTests/TypeDecoderTests.swift index a87016d..d64b7d3 100644 --- a/Tests/DemanglingTests/TypeDecoderTests.swift +++ b/Tests/DemanglingTests/TypeDecoderTests.swift @@ -208,6 +208,28 @@ struct TypeDecoderTests { func existentialMetatypes(mangled: String, expected: String) throws { #expect(try Self.decodeType(mangled) == expected) } + + // MARK: - Store-Backed Decoding Parity (proposal 0001, Phase 2) + + @Test(arguments: [ + "$sBbD", + "$sBf32_Bv4_D", + "$sSiD", + "$sSaySiGD", + "$sSDySSSiGD", + "$s17lowered_metatypes5ProtoPXmT", + ]) + func storeBackedDecodingMatchesNodePath(mangled: String) throws { + let nodePathResult = try Self.decodeType(mangled) + + var builder = NodeStoreBuilder() + let rootIndex = try builder.demangle(mangled) + let store = builder.freeze() + + let decoder = TypeDecoder(builder: StringTypeBuilder()) + let storePathResult = try decoder.decodeMangledType(node: store.reference(at: rootIndex)) + #expect(storePathResult == nodePathResult, "Store-backed decoding should match the Node path for \(mangled)") + } } // MARK: - StringTypeBuilder diff --git a/evolution/0001-node-store-arena.md b/evolution/0001-node-store-arena.md new file mode 100644 index 0000000..d388305 --- /dev/null +++ b/evolution/0001-node-store-arena.md @@ -0,0 +1,169 @@ +# 0001 - NodeStore: Arena-Based Compact Node Storage + +- **Proposal**: 0001 +- **Author**: Mx-Iris +- **Status**: Implemented(Phase 1–3 已落地;Phase 4 平铺序列化推迟) +- **Date**: 2026-07-23 +- **Last Updated**: 2026-07-27 +- **Branch**: `feature/node-store` +- **Related**: `Documentations/SubtreeInterning.md`(前置优化:全子树 hash-consing,已合入 main `5788472`) + +## Summary + +为批量 demangle 场景引入一个与现有 `Node` 类**并存**的紧凑存储层 `NodeStore`:所有节点平铺存放在连续缓冲(arena)中,节点间用 4 字节索引互指,每节点 12 字节、无对象头、无引用计数、无逐节点堆分配。公共 API 通过轻量值类型句柄 `NodeReference`(store 引用 + `UInt32` 索引)访问,架构对标 swift-syntax 的 SyntaxArena + 值类型句柄模式。分四个可独立交付的阶段渐进迁移,全程不破坏现有 `Node` API。 + +## Motivation + +内存基线(详见 `Documentations/SubtreeInterning.md` 的实测): + +- `Node` 是 `final class`,每实例 48 字节(16 字节对象头 + 2 字节 kind + 对齐 + 17 字节 `Payload` 枚举 = 41 字节,落入 48 字节 malloc 桶),逐节点独立 malloc + 引用计数; +- 全子树 hash-consing(proposal 前置工作)已把 49k 符号语料的解析驻留从 39.5 MB 降到 12.9 MB(存活实例 76 万 → 20.2 万); +- 对照 C++ `Demangle::Node`:24 字节/节点 + bump allocator。class 形态的剩余差距来自对象头(16B)、malloc 分桶取整(41→48)与逐节点分配/引用计数,**在保持 class 的前提下无法消除**; +- 全量 SwiftUI 场景(含 dyld cache local symbols)当前预计仍需 150–250 MB,目标降到 30–80 MB,并为「把整个 dyld cache 的解析结果持久化为符号数据库」铺路。 + +单纯把 `Node` 改为带 `[Node]` 的 struct 不可行:children 数组仍是逐节点堆分配(32 字节缓冲头 + CoW 引用计数),加上大 struct 拷贝,内存与 CPU 双双倒退。struct 的真正价值在于**精确布局 + 可平铺进连续缓冲**,因此正确形态是 arena + 索引。 + +## Detailed Design + +### 存储布局 + +```swift +struct CompactNode { // size 12, alignment 4, 无对象头 + var kindAndPayloadKind: UInt16 // bit 0-8: kind 序号(9 位,512 槽) + // bit 9-11: payloadKind(6 种,3 位) + // bit 12-15: 保留 + var payloadWord0: UInt32 + var payloadWord1: UInt32 +} +``` + +落地形态是 `@usableFromInline struct`(非 public、非 `@frozen`):`NodeStore` 的公共面只暴露 `NodeReference`,`CompactNode` 是内部布局细节。kind 序号取自 `Node.Kind.storeOrdinal`(`allCases` 中的位置),**仅在单次进程运行内稳定,不是序列化格式**——Phase 4 的持久化格式须自带稳定的 kind 映射表。 + +`payloadWord0/payloadWord1` 按 `payloadKind` 解释(沿用现有 `Payload` 的互斥不变量:contents 与 children 不共存): + +| payloadKind | payloadWord0 | payloadWord1 | 覆盖比例(49k 语料实测) | +|---|---|---|---| +| `none` | — | — | 与 leaf 部分重叠 | +| `index` | `UInt64` 低 32 位 | 高 32 位 | 少量 | +| `text` | 字符串表 offset | 字节长度 | ~5% | +| `oneChild` | 孩子索引 | — | ~67% | +| `twoChildren` | 孩子 0 索引 | 孩子 1 索引 | ~12% | +| `manyChildren` | edges 缓冲起点 | 孩子数量 | ~16% | + +配套缓冲(每个 store 一套): + +- `nodes: ContiguousArray` — 主 arena,`append` 即 bump 分配; +- `edges: ContiguousArray` — 仅 3+ 孩子节点使用的孩子索引连续区段; +- `textBytes: ContiguousArray` — 字符串表,全部 identifier 的 UTF-8 字节连续存放并去重; +- intern 表 — 把已实现的 hash-consing 从 `ObjectIdentifier` 键迁移为索引键:`(kindAndPayloadKind, payloadWord0, payloadWord1)` 经孩子索引规范化后即天然唯一。落地形态是三张 open-addressing 槽数组(节点 / 多孩子节点 / 文本),每槽仅 4 字节索引,键按需从平铺缓冲区取回比较——不再为键单独存一份拷贝(Phase 3 的 intern 表瘦身,见 Decision Log)。表随 `freeze()` 丢弃,不进入冻结后的 store。 + +容量边界:`UInt32` 索引上限 42.9 亿节点、字符串表 4 GB——对单个 store(哪怕整个 dyld cache)远够;越界走 `precondition` 失败而非静默截断。 + +预算核算(49k 语料,20.2 万唯一子树):`201k × 12B ≈ 2.4 MB` 节点 + edges ~0.3 MB + 字符串表 ~0.5 MB + intern 表 ~2 MB ≈ **5–6 MB**(现状 12.9 MB)。相对 C++ 的反超点:孩子引用用 4 字节索引而非 8 字节指针。 + +### 类型与 API 面 + +以下为落地签名(与实现一致): + +```swift +/// 构建期的单写者。~Copyable,consuming freeze() 交出不可变 store。 +public struct NodeStoreBuilder: ~Copyable, Sendable { + public mutating func demangle(_ mangled: String, isType: Bool = false) throws(DemanglingError) -> NodeStore.NodeIndex + public mutating func intern(_ node: Node) -> NodeStore.NodeIndex // 导入现有树(intern 拷贝) + public mutating func intern(kind: Node.Kind, children: [NodeStore.NodeIndex]) -> NodeStore.NodeIndex + public consuming func freeze() -> NodeStore +} + +/// 冻结后的不可变符号库。Sendable,读路径零锁。 +public final class NodeStore: Sendable { + public func reference(at nodeIndex: NodeIndex) -> NodeReference +} + +/// 轻量句柄:store 引用 + 索引,16 字节值类型。 +public struct NodeReference: Hashable, Sendable { + public var kind: Node.Kind { get } + public var text: String? { get } // 从字符串表解码 + public var textUTF8: ArraySlice? { get } // 借用字符串表字节,零拷贝 + public var index: UInt64? { get } + public var children: ChildrenView { get } // RandomAccessCollection + public func materialize() -> Node // 物化为现有 Node 树(互操作出口) + public func print(using options: DemangleOptions) -> String +} +``` + +解析入口最终定在 builder 而非 store(提案原稿写的是 `NodeStore.demangleAsReference`):冻结后的 store 不可变,解析必然要写入,天然属于 builder。`text` 保持 `String?` 以镜像 `Node.text` 的语义(含 `.dependentGenericParamType` 的泛型名合成,这是 printer 依赖的行为),零拷贝需求由并列的 `textUTF8` 满足。 + +- 构建期使用 `NodeStoreBuilder`(`~Copyable`):单写者约束由编译器保证,`consuming func freeze() -> NodeStore` 完成冻结——把现在靠 `NSLock` + 文档契约维持的「构建后不可变」升级为类型系统保证; +- `Hashable`/`==` 基于 (store identity, index):因为 store 内全量 hash-consed,索引相等 ⇔ 结构相等,比较从 O(树) 降为 O(1); +- 读路径后续用 `Span` / `UTF8Span`(Swift 6.2)暴露孩子区段与文本的借用视图,零分配零拷贝。 + +### 构建流程(两代空间) + +原稿设想的是让 Demangler 直写一块每符号复用的 scratch arena。落地形态改为「cache-free 临时 `Node` 树」作为第一代空间: + +1. `demangleAsNodeTransient` 以 `internsLeaves: false` 解析,构造出的临时 `Node` 树完全不碰 `NodeCache.shared`——无叶节点泄漏、无全局锁竞争; +2. `builder.intern(tree)` 从根出发把可达节点自底向上 intern 进持久 arena——**去重与垃圾回收是同一个 pass**,键规范化逻辑与已合入的 `internTreeUnsafe` 完全同构; +3. 临时树失去引用即被 ARC 回收,处理下一符号。 + +保留 `Node` 作为第一代空间,是因为 Demangler 的解析逻辑(回填、substitution 复用)建立在引用语义之上,改为直写索引式 arena 等于重写全部 ~594 个构造点。实测该取舍成本可忽略:234k 符号语料上构建期 phys_footprint 增量 9.9 MB ≈ 留存 + ~1 MB 瞬态,且 store 构建 25.3s 反而快于 interning `Node` 路径的 28.5s(详见 Decision Log 的 Phase 3 验收)。 + +并行策略:`NodeStoreBuilder` 是 `~Copyable` 单写者,天然无锁但也不可共享。多线程批量场景应每线程一个 builder,终态合并——合并 API 尚未实现,列为 Future Direction。 + +### 渐进式迁移分期 + +每个阶段独立可交付、测试全绿、`Node` API 始终不动: + +- **Phase 1 — 存储层与互操作**:`CompactNode` / `NodeStoreBuilder` / `NodeStore` / `NodeReference`;`intern(_:)` 导入现有 `Node` 树,`materialize()` 导出。打印/remangle 暂走物化慢路径。验收:任意树 导入→导出 与原树 `==`;导入两棵结构相等的树得到同一索引。 +- **Phase 2 — 零物化读路径**:将 `NodePrinter` / `Remangler` / `TypeDecoder` 的树访问抽象为协议(kind/text/index/children 四个只读需求),`Node` 与 `NodeReference` 双双 conform;打印与 remangle 直接从 store 读,不再物化。验收:全量 dyld cache 对齐测试在 `NodeReference` 路径下 0 失败。 +- **Phase 3 — cache-free 批量解析**:`Demangler` 的节点构造收敛到 `createNode(...)` seam,`internsLeaves: false` 时完全绕开 `NodeCache.shared`;批量入口 `NodeStoreBuilder.demangle(_:)` 走「cache-free 临时 `Node` 树 → intern 进 arena → 丢弃临时树」。此阶段起批量场景不再向全局缓存写入任何东西。验收:内存达标(49k 语料 ≤6 MB)、吞吐不劣于现状 1.2 倍。 +- **Phase 4(可选)— 平铺序列化**:store 的几个缓冲直接二进制序列化/反序列化(接近 memcpy 量级),支持 mmap 加载——符号数据库能力,为 RuntimeViewer 类工具缓存整个 dyld cache 的解析结果。 + +### 与现有 NodeCache 的关系 + +store 自带索引级 intern,store 路径不经过全局 `NodeCache`;批量用户迁移到 store 后,`NodeCache` 仅服务于一次性/精细操作场景,其增长压力自然消失。两者语义一致(结构相等 ⇔ 规范实例/索引相等)。 + +## Source Compatibility + +纯增量 API,无破坏性变更。`Node`、`NodeBuilder`、`NodeCache`、`demangleAsNode` 行为全部保持。Phase 2 的协议抽象对 `NodePrinter`/`Remangler` 是内部重构,公共签名不变。 + +## Performance Goals(验收标准) + +| 指标 | 目标 | +|---|---| +| 每节点存储 | ≤ 16 字节(设计值 12) | +| 49k 语料总驻留 | ≤ 6 MB(现状 12.9 MB,hash-consing 前 39.5 MB) | +| 全量 SwiftUI 场景 | ≤ 100 MB(现状预计 150–250 MB) | +| 解析吞吐 | 不劣于现状的 1.2 倍 | + +基准方法沿用既有测量工程(NodeMemBench:`class_getInstanceSize` / `malloc_size` / `phys_footprint` 三角验证),增加 store 变体对照。 + +## Alternatives Considered + +- **`ManagedBuffer` 尾分配 class 节点**(孩子内联到实例尾部,单次分配):仍保留 16 字节对象头与引用计数,加权后 ~32–48 字节,被 arena 全面支配,否决; +- **压缩 class 布局到 32 字节桶**:需手工 union 并外部化 `String`/双孩子,加权收益仅 ~1.2 倍,复杂度不成比例,否决(详见 SubtreeInterning 文档); +- **直接把 `Node` 重写为 struct 句柄**(swift-syntax 式整体替换):终态最优但破坏全部公共 API 与下游(MachOSwiftSection / RuntimeViewer),不符合渐进要求;本方案 Phase 3 完成后如需要可再评估。 + +## Future Directions + +- mmap 符号数据库格式版本化(magic + version + 缓冲布局描述); +- 分片并行 store 与终态合并; +- `InlineArray`(SE-0453)在 scratch arena 孩子暂存区的应用; +- `NodeReference` 层面的 `Node.Rewriter` 等价物(写时拷贝进新 store)。 + +## Decision Log + +| Date | Decision | Notes | +|---|---|---| +| 2026-07-23 | Created as Draft | 基于 48B class 下限与 C++ 24B 对比分析,确定 arena + 索引句柄方向;分四阶段渐进迁移,不破坏现有 `Node` API | +| 2026-07-23 | Status → In Progress,Phase 1 落地 | 用户确认迭代方向,在 worktree `feature/symbol-store` 实施。`CompactNode`(实测 size/stride = 12)、`NodeStoreBuilder`(`~Copyable` + `consuming freeze()`)、`NodeStore`、`NodeReference` + `ChildrenView` 完成,含 `intern(_ node:)` 导入与 `materialize()` 导出互操作、桥接式 `demangle(_:)`(经 `internsSubtrees: false` 的临时 `Node` 树) | +| 2026-07-23 | Phase 1 实测达标 | 49k 语料:唯一节点 201,876(与 `NodeCache` 全树 hash-consing 计数逐一吻合,交叉验证正确性);平铺存储 3.0 MB(nodes 2.4 + edges 0.43 + text 0.26),优于 ≤6 MB 目标;打印抽样 2000 条零差异;构建 0.87s,不劣于 class 路径。**新发现**:构建期高水位 ~16 MB,由 intern 表(`[CompactNode: UInt32]` 等,~10 MB 量级)与桥接路径的临时 class 节点构成,且已冻结后 dirty pages 不随 `malloc_zone_pressure_relief` 回落;两轮连建仅 +6 MB,确认内存复用、无累积。结论:①「49k ≤6 MB」按保留存储口径已达成,进程口径需 Phase 3(直写 arena,消除临时树)+ intern 表瘦身(改为指向 nodes 缓冲的 open-addressing 索引表,去掉独立 key 存储);②桥接路径会向全局 `NodeCache` 写入叶节点(实测 11k 条),Phase 3 前的批量用户建议构建后 `NodeCache.shared.clear()` | +| 2026-07-23 | Phase 2(打印)落地:零物化 store 打印 | 引入 `DemanglingNode` 只读协议(`kind`/`text`/`index`/`hasIndex`/`children` + `printCacheIdentity` 抽象缓存身份;`isSimpleType`/`needSpaceBeforeType`/`isIdentifier`/`isSwiftModule`/`print` 作为协议扩展从原语派生),`Node` 与 `NodeReference` 双双 conform。把 2179 行的 printer 引擎泛型化为 `DemanglingPrinter`(发现 printer 是纯只读消费者,全程不构造节点,泛型化干净),保留公共 `NodePrinter` 薄包装转发到 `DemanglingPrinter`——**公共 API 零破坏**。`NodeReference.print` 直接走 `DemanglingPrinter<_, NodeReference>`,不再 `materialize()`。**关键正确性点**:`NodeReference.text` 必须镜像 `Node.text` 对 `.dependentGenericParamType` 的泛型名合成(printer line 199 依赖之);`NodePrintContext.node` 是具体 `Node?`,store 路径以 `name as? Node`(NodeReference → nil)优雅降级,`String` target 无视 context 故无影响。**验证**:49k 语料 × 3 套选项(default/simplified/synthesizeSugar)store 打印与 Node 打印逐字节零差异;全量 dyld cache 对齐测试 + TypeDecoder 测试全绿 | +| 2026-07-23 | 事故与恢复 | 外部工具删除了 `.claude/worktrees/` 目录,Phase 2 的未提交改动随磁盘丢失(三个 commit 因在 git 对象库中而安全)。因 printer 改造为确定性脚本化流程(perl + 精确 Edit),已从会话记录逐字复现全部 Phase 2 变更,重建 worktree 后重跑构建/测试验证一致 | +| 2026-07-23 | Phase 2 跟进:materialize 保共享 + 派生属性单一来源 | ① `materializeNode` 增加按索引 memo:store 是 hash-consed 的 DAG,同一子树索引只物化一次并复用实例——此前朴素递归会把重度替换共享的符号指数展开成树(printCache 注释中 SwiftUI `View.Body` 量级即几十万节点),且展开树上按 `ObjectIdentifier` 键的打印缓存全部脱靶;新增测试 `materializePreservesSubtreeSharing` 断言共享位置 `===`。② 删除 `Node` 上与 `DemanglingNode` 扩展重复的 `isSimpleType`/`needSpaceBeforeType`/`isIdentifier(desired:)`/`isSwiftModule`:这些是协议扩展成员(非 requirement),泛型引擎内静态派发恒走扩展版本,两份拷贝存在静默漂移风险;收敛为单一实现后对外仍是 public API(具体 `Node` 调用解析到协议扩展),行为与逐字节输出不变,全量测试绿 | +| 2026-07-24 | 仓库形态调整 | `feature/symbol-store` 迁入独立 worktree `swift-demangling-symbol-store`(同级显式路径,避开曾被外部工具误删的 `.claude/worktrees/`),主检出切回 `main` 供 MachOSwiftSection 等路径依赖使用。合并 main 的 `7fcb0f1`(`NodePrinterTarget` type-reference scope hooks):泛型引擎以 `name as? Node` 桥接,store 路径传 nil——当前 store 打印仅 String target,无实际降级;富 target 抽象随 swift-section 迁移再设计 | +| 2026-07-24 | Phase 2 收尾:遍历 + TypeDecoder + remangle 桥 + builder 构造 API + @_spi | ① 遍历机制(preorder/inorder/postorder/levelorder、`first(of:)`/`all(of:)`/`contains`/`filter(of:)`、`identifier`)整体泛型化为单一实现,`NodeReference` conform `Sequence`(preorder 默认),parity 测试断言两种表示遍历序逐一相同。② `TypeDecoderEngine` + 公共 `TypeDecoder` facade(新增 `NodeReference` 入口);**`TypeBuilder` 协议零改动**——五个交接点经新协议 requirement `materializedNode`(`Node` 返回 self 零成本)物化小子树。③ **Remangler 决策:保持 Node 引擎**。审计确认 remangling 遍历中节点构造是承重的(`getUnspecialized` 剥泛型后回流 `mangle`、SIL box 布局 wrapper,均共享 substitution 状态,与 C++ NodeFactory 设计同构);对逐字节对齐关键组件做无构造重设计不值。`mangleAsString(some DemanglingNode)` 经 `materializedNode` 桥接(remangle 输出本就是新 String,瞬态成本,与常驻内存目标无关)。`printCacheIdentity` 改名计划放弃(未出现第二个消费者)。④ builder 新增 `intern(kind:)`/`(kind:text:)`/`(kind:index:)`/`(kind:children:)` 直接构造 API,与树 intern 共享 hash-consing(测试断言同一索引)。⑤ `DemanglingPrinter` 与 `StackSafeExecutor` 以 `@_spi(Internals)` 导出(与 MachOSwiftSection 既有 SPI 组名一致),客户端视角验证:带 SPI import 可见、不带不可见 | +| 2026-07-24 | Phase 3 落地:cache-free 批量 demangle + intern 表瘦身 | ① `Demangler` 构造 seam:全部 ~594 个构造点收敛到 `createNode(...)` 实例方法,`internsLeaves: false`(内部入口 `demangleAsNodeTransient`,builder 桥接改用之)完全绕开 `NodeCache.shared`——无叶节点泄漏、无全局锁竞争、临时树丢弃后零残留;公共入口默认行为不变。测试用叶身份断言 cache-free(并发 suite 不会 flake)。② intern 表瘦身:三张字典(键各自持有 12B compact/子索引数组/String 副本)换成 open-addressing 槽数组(4B/槽,键按需从缓冲区取回比较),FNV-1a 文本哈希 + 乘法混合节点哈希。③ **验收(本机 dyld cache SwiftUI 语料 234,232 符号,debug 构建)**:唯一节点 619,688,平铺存储 8.75 MB(14.1 B/节点 ≤16 目标,37 B/符号;nodes 7.4 + edges 0.75 + text 0.57);store 构建 25.3s vs interning Node 路径 28.5s——**快于基线**(预算允许慢 1.2×);构建期 phys_footprint 增量 9.9 MB ≈ 留存 + ~1 MB 瞬态(旧方案在 1/5 语料上高水位即 ~16 MB)。验收测试按单位口径断言(≤16 B/唯一节点、≤64 B/符号、耗时 <2× 基线)常驻于测试套件。备注:提案原稿的 `NodeStore.demangleAsReference` 定名为 builder 侧 `demangle(_:)`——冻结后的 store 不可变,解析入口天然属于 builder | +| 2026-07-24 | Phase 3 跟进(读路径 perf):零拷贝文本 | `NodeReference.textUTF8` 暴露字符串表字节的零拷贝 `ArraySlice` 视图;`isIdentifier(desired:)`/`isSwiftModule` 升为 `DemanglingNode` requirement(带派生默认实现),`NodeReference` 以字节比较见证——printer 的 sugar 检测热路径(Swift module + Optional/Array/Dictionary)在 store 路径不再每检查构造一次 String;非 ASCII needle 回退 String 比较保持 Unicode 规范等价语义。`UTF8Span` 借用视图仍列为 Future Direction | +| 2026-07-24 | 命名调整:`SymbolStore` → `NodeStore` | 库的领域概念是 `Node`(demangle 产物树节点),API 体系中并无 "Symbol" 抽象——该词仅是输入 mangled string 的口语说法。全部类型随之更名:`SymbolStore` → `NodeStore`、`SymbolStoreBuilder` → `NodeStoreBuilder`(`NodeStore.NodeIndex` 不变),与 `Node`/`NodeReference`/`CompactNode`/`NodeCache` 命名系对齐。曾考虑 `NodeFactory`(C++ 编译器中 `NodeFactory` 正是 demangle 的 slab arena 分配器,有官方先例)但放弃:本项目 `NodeFactory` 已被无参 singleton 节点集合占用(名同义异),且 C++ 版是短命裸分配器,与可 `freeze()` 的持久 hash-consing 容器语义不符——frozen 只读容器叫 Factory 会误导熟悉官方源码的读者。文件同步更名(含本提案 `0001-symbol-store-arena.md` → `0001-node-store-arena.md`);分支同步更名 `feature/symbol-store` → `feature/node-store`(本地 + origin:先核对远端 tip 一致、无关联 PR,推新名后删除旧远端分支);MachOSwiftSection 侧 `SymbolStoreMigrationPlan.md` → `NodeStoreMigrationPlan.md` 同步更新(其自有类型 `SymbolIndexStore` 不在改名范围);decision log 历史记录保留原状 | +| 2026-07-24 | 下游迁移配套:跨表示相等 + transient demangle SPI | 为 MachOSwiftSection 的 `SymbolIndexStore` → NodeStore 迁移(其 `Documentations/Internal/NodeStoreMigrationPlan.md`)新增:① `NodeReference.structurallyEquals(_ node: Node)`——零物化跨表示结构相等(语义对齐 `Node.==`:kind + contents + children 递归;text 先字节比较、Unicode 规范等价回退 String ==),服务「外部 canonical `Node` 在 `NodeReference` 字典键中查找」场景(frozen store 的 intern 表已随 `freeze()` 丢弃,不能哈希查找;name 预桶内线性结构比较足够),附 3 个单元测试;② `demangleAsNodeTransient` 以 `@_spi(Internals) public` 导出——下游批量索引在瞬态树上跑分类逻辑后 `builder.intern`,全程 cache-free(文档注明返回树非 canonical);③ `isKind(of:)`(原 `Node` 扩展)与 `children.second`(原 `Node.Children` 具体成员)上收为 `DemanglingNode`/`DemanglingNodeChildren` 协议扩展单一实现,删除具体副本;④ `NodeReference: CustomStringConvertible`(物化桥的 debug 树 dump)。迁移侧实测(SwiftUI image):构建管线换 transient+intern 后 `NodeCache` 增长归零(此前 +1.9 万叶/+56 万子树),`Storage` 释放即整镜像回收,store 本体 7 MB / 57.9 万唯一节点 | +| 2026-07-24 | Stage 5 上游配套:惰性 scope hook + transient 构造 SPI + `NodeReference` 结构性 API + 语料修正(commit `26db7a4`) | 下游(MachOSwiftSection)迁移过程中暴露的四个上游缺口,一并补齐。**① `NodePrinterTarget.pushTypeReferenceScope` 的节点参数改为 `@autoclosure () -> Node?`。** 问题:该 hook 是 main 上 `7fcb0f1` 引入的富 target 分组接缝(printer 在整段限定名打印外围推入 nominal 节点),签名收的是具体 `Node?`;store 路径要服务它就必须 `materializedNode` 物化一棵子树,而 `String` 这类忽略 scope 的 target 根本不读这个节点——等于让不用的人付物化代价,与「store 纯文本打印零分配」直接冲突。此前 07-24 的权宜做法是 store 路径一律传 `nil`(见上一条「以 `name as? Node` 桥接,store 路径传 nil」),代价是富 target 走 store 时永久丢失 scope identity。改为 autoclosure 后两难消解:默认实现与 `String` 从不求值(零成本,store 纯文本路径仍无物化),富 target 求值即拿到 `name.materializedNode`——**且只物化该 nominal reference 的小子树**,不是整棵符号树。**验证**:新增 `NodePrinterScopeTests`,其 `ScopeRecordingTarget` 精确镜像 `String` 的写入行为(保证打印流程逐字节同构)并额外记录 push/pop 事件序列;scope identity 以「被递交节点的 remangle 字符串」表达——这是结构性表征,跨表示相等当且仅当递交的子树结构相等,从而绕开 `Node` 与 `NodeReference` 无法直接比较的问题。对 5 个符号断言两条路径的输出文本与 scope 事件序列逐项相同,并断言至少递交过一个非 nil identity(防止「两边都传 nil 也能通过」的空洞绿灯)。**② `demangleAsNodeTransient` 增加 `symbolicReferenceResolver` 参数,并新增 `@_spi(Internals) Node.createTransient(...)` 工厂族。** 问题:Phase 3 的 cache-free 契约此前只覆盖 demangler 自身的构造点(全部走 `createNode(...)` + `internsLeaves: false`),但 symbolic reference 解析是**用户提供的闭包**在解析中途构造并回填节点——下游只能用公共的 `Node.create(...)`,而它必然写入 `NodeCache.shared`。结果是:一旦启用 symbolic reference(MachOSwiftSection 解析 mach-o 内嵌符号引用的常规路径),批量管线的 cache 增长归零成果当场失效。补齐后 transient 入口可直接透传 resolver,resolver 内部用 `createTransient` 构造 splice 节点,全链路不碰全局 cache、不取全局锁。`createTransient` 覆盖 `create` 的五个重载形态(contents / inlineChildren / 单孩子 / text / index),文档明确标注返回节点**非 canonical**(结构相等者是不同实例,`===` 共享假设不成立)。**③ `NodeReference` 三个结构性 API。** `init(interning:)`:把单棵外部树(瞬态 demangle 结果或手工合成树)interning 进一个**私有 mini store** 并引用其根——reference 持有 store 即保活,值自足且 `Sendable`,适合「值的生命周期长于源树」的持有场景;文档同时指明批量场景仍应直接驱动 `NodeStoreBuilder`,否则每棵树一个 arena,白丢跨符号去重。`structurallyEquals(_ other: NodeReference)`:同 store 直接比索引即得答案(hash-consing 使索引相等 ⇔ 结构相等,O(1)),跨 store 才走双树遍历,text 同样字节优先、回退 `String ==` 保 Unicode 规范等价——与既有的 `structurallyEquals(_ node: Node)` 语义完全对齐。`structuralHash(into:)`:与跨 store 结构相等自洽的结构哈希(kind + contents 判别位 + 孩子数 + 递归)。**为什么不能直接用固有 `Hashable`**:`NodeReference` 的 `hash(into:)` 组合的是 `ObjectIdentifier(store)` + 索引(store 身份基底),跨 store 的结构相等键会被劈成两个桶;下游那些「按节点结构做字典键、但内部存 reference」的值类型需要的正是这个可显式调用的结构哈希构件。**④ 语料修正:`$s7SwiftUI4TextV_10FoundationE9formatterAcA20LocalizedStringStyleV_xtcSyRzlufc` 是无效符号**(早于本分支就躺在语料里,`xcrun swift-demangle` 同样原样吐回、拒绝解析——尾部 `fc` 非分配构造器与该 extension 上下文不自洽),换成生成的真实跨模块 extension initializer `$s11ExampleBase0A4TextV0A6AddonsE9formatter7subjectAcA0A5StyleV_xtcSyRzlufC`(展开为 `(extension in ExampleAddons):ExampleBase.ExampleText.init(formatter:subject:)`),涉及 `NodeCacheTests` 与 `NodeStoreTests` 共 6 处;全部语料字面量此后统一经 `swift-demangle` 校验后再入库。**全量套件:410 tests / 19 suites 全绿** |