Skip to content

Commit 83ba1f3

Browse files
committed
winmd-inspect: project WinRT generic interfaces through an associated ABI type
The generic-interface rendering projected each type-variable slot onto a fixed-size raw pointer: the ABI protocol was NON-generic, a `VAR` slot erased to the opaque interface pointer, and the wrapper `unsafeBitCast` the typed value to (and the result back from) that pointer. That is size-correct only for a REFERENCE instantiation (`IVector<IFoo>`, a pointer either way); for a VALUE instantiation (`IReference<Int32>`, `IVector<Int32>`) the slot's `Element` is a 4-byte value, and a fixed-size bitcast to an 8-byte pointer TRAPS — reads garbage past the value. Replace the raw-pointer erasure of a generic slot with the windows-rs `Type::Abi` mechanism: an associated-ABI-type projection that is size-correct for both kinds. - A new bundled runtime-support source, `Resources/Support/ABIProjectable.swift`, defines the projection protocol: `ABIProjectable` with an `associatedtype ABI` and `toABI()`/`fromABI(_:)` conversions — the Swift analogue of windows-rs's `Type` trait. A value type conforms with `ABI == Self` (identity, the `CopyType`/`CloneType` case) and every WinRT-blittable primitive is made to conform; a reference type conforms via `ABIReference` with `ABI == UnsafeMutableRawPointer` (the opaque COM pointer, the `InterfaceType` case). It is emitted alongside the projected interfaces, not compiled into the tool. - The generic wrapper is now `struct IVector<Element: ABIProjectable>` and the ABI protocol is generic over the same parameters — `protocol IVectorABI<Element>` with an `associatedtype Element: ABIProjectable` — its requirements spelling each projected slot as `Element.ABI`. The wrapper forwards through `Element.toABI()` / `Element.fromABI(_:)` rather than a fixed-size `unsafeBitCast`, so a value instantiation crosses the vtable AS the value (`Element.ABI == Element`) and a reference instantiation as the opaque pointer — size-correct for both, exactly as windows-rs writes a generic vtable slot as `AbiType<T>` rather than a fixed `*mut c_void`. - `SignatureType.abi(…)` projects an UNBOUND type-level `VAR` through its declared name's `.ABI` member (the new `projection` dialect string) instead of collapsing it to the opaque pointer; a new `SignatureType.projects` reports such a slot. A CONCRETE reference (a `CLASS` named type) and a method-level `MVAR` still erase to the opaque pointer — a pointer either way, so their cast is size-safe. The argument-dependent specialisation (`substituting:`) is unchanged: a bound `VAR` resolves to its concrete argument. - `Database+SQL` gains `projects(return:)`/`projects(parameter:)` beside the existing `reference(…)`; `Shell` composes the projected slot's ABI spelling (`Element.ABI`) and the `toABI()`/`fromABI(_:)` forwarding into the render context; the `com.mustache` `{{#generic}}` arm emits the parameterised ABI protocol (with its `associatedtype` declarations) and the projecting wrapper. A generic base is inherited parameterised by the interface's own arguments (`IVectorABI<Element>: IIterableABI<Element>`). The non-generic `{{^generic}}` path is byte-identical. The projected shape is swiftc-typecheck- and run-verified end to end for BOTH a VALUE instantiation (`IVector<Int32>`: the ABI slot IS `CInt`, the wrapper does NO pointer bitcast, size-correct) and a REFERENCE instantiation (`IVector<IFoo>`: the slot is the opaque pointer, projected through `ABIReference`). The original PR219 concern-1 bug is fixed: no value slot carries a size-unsafe `unsafeBitCast`.
1 parent f160ca0 commit 83ba1f3

14 files changed

Lines changed: 1173 additions & 164 deletions

File tree

Sources/WinMD/Signature.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,3 +525,30 @@ extension Row where Schema == Metadata.Tables.PropertyDef {
525525
}
526526
}
527527
}
528+
529+
// MARK: - TypeSpec generic base
530+
531+
/// The `TypeDefOrRef` coded-index token naming the GENERIC BASE a `TypeSpec`
532+
/// GENERICINST signature instantiates — the row-linkable value the raw `bytes`
533+
/// of a `TypeSpec.Signature` `#Blob` carry — or `nil` when the signature is not
534+
/// a generic instantiation (its base is not a named type).
535+
///
536+
/// This is the escapable, value → value form the SQL adapter's `GENERICBASE`
537+
/// scalar function reads: a caller that has copied a `TypeSpec.Signature` blob
538+
/// out of the borrowed scan decodes it here, returning the base's `rawValue` so
539+
/// SQL can split its tag/row and join to the base `TypeRef`/`TypeDef` by `Id`.
540+
/// A WinRT generic interface inheriting another (`IVector`1 : IIterable`1`)
541+
/// records the base through a `TypeSpec`, so its `bases` row is otherwise
542+
/// empty; this recovers the base name.
543+
///
544+
/// A `TypeSpec` whose signature is not a `GENERICINST` over a named base — a
545+
/// bare array/pointer spec, or a malformed blob — yields `nil` rather than
546+
/// throwing, so the SQL join simply produces no base row for it.
547+
public func base(decoding bytes: Array<UInt8>) -> TypeDefOrRef? {
548+
var decoder = SignatureDecoder(bytes.span.bytes)
549+
guard let type = try? decoder.type(),
550+
case let .instance(.named(_, reference), _) = type else {
551+
return nil
552+
}
553+
return reference
554+
}

Sources/WinMD/Storage.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,25 @@ package struct Storage: ~Escapable {
105105
return tuple
106106
}
107107

108+
/// The registered CIL table schema whose name matches `name`
109+
/// case-insensitively, whether or not the database has rows for it — the
110+
/// full ECMA-335 §II.22 table set, not just the present `tables`.
111+
///
112+
/// A file omits a table with no rows (its `Valid` bit is clear), so a query
113+
/// naming an optional-metadata relation would otherwise find no table. This
114+
/// resolves the SCHEMA by name so the SQL adapter can surface an absent one
115+
/// as an empty relation (`Table.empty(_:)`), a query referencing it thus
116+
/// resolving to zero rows rather than an unknown relation. `nil` when no
117+
/// registered schema bears the name. It is `package` so the adapter reaches
118+
/// it across the module boundary.
119+
package static func schema(named name: String) -> TableSchema.Type? {
120+
for schema in kRegisteredTables
121+
where "\(schema)".caseInsensitiveCompare(name) == .orderedSame {
122+
return schema
123+
}
124+
return nil
125+
}
126+
108127
/// The rows of `schema` whose foreign-key `column` references `target`.
109128
///
110129
/// The runtime (non-generic) sibling of `Database.referencing`: it opens the

Sources/WinMD/Table.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ public struct Table: Sendable {
9090
self.range = range
9191
}
9292

93+
/// An empty (zero-row) open table for `schema` — the form a schema-defined
94+
/// but ABSENT table takes.
95+
///
96+
/// A metadata file sets a table's `Valid` bit only when the table has rows,
97+
/// so a file with no rows for a table omits it entirely (ECMA-335
98+
/// §II.24.2.6). A query may still reference such a table — an
99+
/// optional-metadata relation a view joins in one arm — so the SQL adapter
100+
/// surfaces it as this empty relation rather than an unknown one: its
101+
/// `schema` types and names its columns as usual, and its zero `rows` yield
102+
/// no records (the `wide`/`stride` record layout is immaterial with no
103+
/// records, so both are the narrow zero).
104+
package static func empty(_ schema: TableSchema.Type) -> Table {
105+
Table(schema, rows: 0, range: 0 ..< 0, wide: 0, stride: 0)
106+
}
107+
93108
/// The byte offset of column `i` within a record.
94109
///
95110
/// Each wide index before column `i` shifts it by two bytes beyond its narrow

Sources/WinMDSynthesis/Decode.swift

Lines changed: 128 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ public struct Dialect: Sendable {
3535
/// The `VAR`/`MVAR` generic-parameter scope prefixes (`T`/`M`).
3636
public let variable: (type: String, method: String)
3737

38+
/// The associated-ABI-type projection an unbound generic slot crosses the ABI
39+
/// through — the member spelling appended to a `VAR`'s declared name so the
40+
/// slot reads `Element.ABI` (the `.ABI` suffix), the windows-rs `Type::Abi`
41+
/// mechanism. It is size-correct for BOTH a value and a reference argument,
42+
/// unlike a fixed-size raw-pointer erasure, so an unbound type variable
43+
/// projects through it rather than collapsing to `opaque`.
44+
public let projection: String
45+
3846
/// The spelling an unresolvable named type (and a function pointer) degrades
3947
/// to (`UnsafeMutableRawPointer`).
4048
public let opaque: String
@@ -59,6 +67,7 @@ public struct Dialect: Sendable {
5967
optional: String,
6068
generic: (open: String, close: String),
6169
variable: (type: String, method: String),
70+
projection: String,
6271
opaque: String,
6372
guid: (iid: String, clsid: String),
6473
known: Dictionary<Identity, String>,
@@ -68,6 +77,7 @@ public struct Dialect: Sendable {
6877
self.optional = optional
6978
self.generic = generic
7079
self.variable = variable
80+
self.projection = projection
7181
self.opaque = opaque
7282
self.guid = guid
7383
self.known = known
@@ -194,28 +204,85 @@ extension SignatureType {
194204
/// A modifier is transparent, and so is indirection: a BYREF, pointer, array,
195205
/// or matrix classifies as its (recursively unwrapped) ELEMENT, so a byref or
196206
/// array of a class reference is itself a reference (its element erases),
197-
/// while a pointer or array of a value stays a value. A `VAR`/`MVAR` generic
198-
/// variable stands for an as-yet-unknown argument and is conservatively a
199-
/// value (its erased spelling falls through to `decode`).
207+
/// while a pointer or array of a value stays a value.
208+
///
209+
/// A `VAR`/`MVAR` generic type variable is ARGUMENT-DEPENDENT. WinRT erases a
210+
/// generic parameter's ABI by its concrete argument's kind — `IVector<Int32>`
211+
/// carries an `Int32` value (4 bytes), `IVector<IFoo>` an interface pointer
212+
/// (8 bytes) — so a type variable cannot be classified in isolation. When the
213+
/// binding `arguments` of the enclosing instantiation are supplied and a
214+
/// type-level `VAR`'s operand indexes them, the variable classifies as its
215+
/// BOUND argument (a value argument keeps its value ABI, a reference argument
216+
/// erases). Absent a binding — the generic DEFINITION render, whose wrapper
217+
/// is itself Swift-generic over the unknown parameter — the variable is a
218+
/// reference for a fixed classification; `abi(…)` PROJECTS such an unbound
219+
/// type-level slot through its element's associated ABI type
220+
/// (`Element.ABI`, size-correct for a value AND a reference argument) rather
221+
/// than collapsing it to the opaque pointer, and `projects` reports it so the
222+
/// wrapper forwards through `ABIProjectable` rather than a fixed-size cast. A
223+
/// method-level `MVAR` is never substituted (only type-level bindings thread
224+
/// here) and so stays a reference.
200225
///
201226
/// This is the classification half of the ABI-erasure keystone; `abi(…)`
202227
/// produces the matching erased spelling.
203-
public var classification: ABI {
228+
public func classification(substituting arguments: Array<SignatureType>?
229+
= nil) -> ABI {
204230
switch self {
205231
case .named(kind: .class, _), .primitive(.object):
206232
.reference
207233
case .named(kind: .value, _), .primitive, .function:
208234
.value
209-
case .variable:
210-
.value
211-
case let .instance(base, _):
212-
base.classification
235+
case let .variable(scope, index):
236+
// A bound type-level variable classifies as its concrete argument; an
237+
// unbound one (the definition render) or a method-level `MVAR` erases.
238+
if case .type = scope, let arguments, arguments.indices.contains(index) {
239+
arguments[index].classification()
240+
} else {
241+
.reference
242+
}
243+
case let .instance(base, arguments):
244+
// A GENERICINST's own kind follows its base, and its arguments become the
245+
// bindings a variable in the base's slots substitutes against.
246+
base.classification(substituting: arguments)
247+
case let .modified(inner, _),
248+
let .pointer(inner),
249+
let .reference(inner),
250+
let .array(inner),
251+
let .matrix(inner, _):
252+
inner.classification(substituting: arguments)
253+
}
254+
}
255+
256+
/// The ABI classification of `self` with no binding — the generic-definition
257+
/// spelling, where a type variable erases as a reference. Argument-dependent
258+
/// callers use `classification(substituting:)`.
259+
public var classification: ABI {
260+
classification()
261+
}
262+
263+
/// Whether `self` is an unbound generic slot that crosses the ABI through its
264+
/// element's ASSOCIATED ABI type (`Element.ABI`) rather than a fixed erasure
265+
/// — an unbound type-level `VAR` (recursively, under indirection or a
266+
/// modifier). The generic-definition wrapper projects such a slot through the
267+
/// `ABIProjectable` conformance (`toABI()`/`fromABI(_:)`), not a fixed-size
268+
/// `unsafeBitCast`, so it is size-correct for a value AND a reference
269+
/// instantiation. A concrete reference (a `CLASS` named type) is NOT
270+
/// projected — its erased pointer is a pointer either way, so its cast is
271+
/// size-safe — and a value is not projected either. A bound variable (the
272+
/// specialisation path) resolves to its concrete argument, so it never
273+
/// projects.
274+
public var projects: Bool {
275+
switch self {
276+
case let .variable(scope, _):
277+
if case .type = scope { true } else { false }
213278
case let .modified(inner, _),
214279
let .pointer(inner),
215280
let .reference(inner),
216281
let .array(inner),
217282
let .matrix(inner, _):
218-
inner.classification
283+
inner.projects
284+
case .named, .primitive, .instance, .function:
285+
false
219286
}
220287
}
221288

@@ -256,18 +323,49 @@ extension SignatureType {
256323
/// value is unchanged (`pointer(int)` → `UnsafeMutablePointer<CInt>`), its
257324
/// erased element being its own `decode(…)`. A `.modified` type is
258325
/// transparent to its inner type, exactly as `classification` treats it.
326+
/// When the binding `substituting` arguments of the enclosing instantiation
327+
/// are supplied, a type-level `VAR` slot erases by its BOUND argument: a
328+
/// value argument keeps its own value ABI (`IVector<Int32>.GetAt -> Int32`
329+
/// spells `CInt`, no pointer erasure), a reference argument erases to the
330+
/// opaque pointer. Absent a binding — the generic DEFINITION render — an
331+
/// unbound type-level `VAR` PROJECTS through its declared name's associated
332+
/// ABI type (`Element.ABI`, the `projection` suffix), which is `Element`
333+
/// itself for a value instantiation and the opaque pointer for a reference
334+
/// one, so the slot is size-correct either way and the wrapper projects
335+
/// through `ABIProjectable` rather than a fixed-size cast.
259336
public func abi(parameter: String? = nil, generics: Array<String>? = nil,
337+
substituting arguments: Array<SignatureType>? = nil,
260338
with resolver: Resolver, dialect: Dialect) -> String {
261339
switch self {
262340
case let .pointer(element), let .reference(element),
263341
let .array(element), let .matrix(element, _):
264-
element.spelling(parameter: parameter, generics: generics, const: false,
265-
erase: true, with: resolver, dialect: dialect)
342+
element.spelling(parameter: parameter, generics: generics,
343+
substituting: arguments, const: false, erase: true,
344+
with: resolver, dialect: dialect)
266345
case let .modified(inner, _):
267-
inner.abi(parameter: parameter, generics: generics, with: resolver,
268-
dialect: dialect)
269-
case .primitive, .named, .instance, .variable, .function:
270-
switch classification {
346+
inner.abi(parameter: parameter, generics: generics,
347+
substituting: arguments, with: resolver, dialect: dialect)
348+
case let .variable(scope, index):
349+
// A bound type-level variable erases as its concrete argument (a value
350+
// keeps its own ABI, a reference erases). An UNBOUND type-level variable
351+
// — the generic DEFINITION render — projects through its declared name's
352+
// associated ABI type (`Element.ABI`, the windows-rs `Type::Abi`
353+
// mechanism): size-correct for a value AND a reference instantiation,
354+
// unlike a fixed-size raw-pointer erasure. A method-level `MVAR` (never
355+
// substituted here) and an out-of-range operand have no declared name to
356+
// project, so they still collapse to the opaque pointer.
357+
if case .type = scope, let arguments, arguments.indices.contains(index) {
358+
arguments[index].abi(parameter: parameter, with: resolver,
359+
dialect: dialect)
360+
} else if case .type = scope, let generics,
361+
generics.indices.contains(index) {
362+
scope.spelling(index, generics: generics, dialect: dialect)
363+
+ dialect.projection
364+
} else {
365+
dialect.opaque
366+
}
367+
case .primitive, .named, .instance, .function:
368+
switch classification(substituting: arguments) {
271369
case .reference:
272370
dialect.opaque
273371
case .value:
@@ -337,6 +435,7 @@ extension SignatureType {
337435
/// pointer, not the named type. The `void` collapses are already raw ABI
338436
/// forms, so `erase` leaves them untouched; only the wrapped leaf differs.
339437
fileprivate func spelling(parameter: String?, generics: Array<String>?,
438+
substituting arguments: Array<SignatureType>? = nil,
340439
const: Bool, erase: Bool, with resolver: Resolver,
341440
dialect: Dialect) -> String {
342441
switch self {
@@ -353,16 +452,19 @@ extension SignatureType {
353452
case .pointer:
354453
// A non-`void` pointer-to-pointer: the inner pointer slot is itself
355454
// nullable, so mark the leaf element optional (as the `void**` cases).
356-
wrap(leaf(parameter: parameter, generics: generics, erase: erase,
357-
with: resolver, dialect: dialect) + dialect.optional,
455+
wrap(leaf(parameter: parameter, generics: generics,
456+
substituting: arguments, erase: erase, with: resolver,
457+
dialect: dialect) + dialect.optional,
358458
const: const, dialect: dialect)
359459
case let .modified(inner, modifiers):
360460
inner.spelling(parameter: parameter, generics: generics,
461+
substituting: arguments,
361462
const: modifiers.constant(with: resolver), erase: erase,
362463
with: resolver, dialect: dialect)
363464
default:
364-
wrap(leaf(parameter: parameter, generics: generics, erase: erase,
365-
with: resolver, dialect: dialect),
465+
wrap(leaf(parameter: parameter, generics: generics,
466+
substituting: arguments, erase: erase, with: resolver,
467+
dialect: dialect),
366468
const: const, dialect: dialect)
367469
}
368470
}
@@ -371,10 +473,14 @@ extension SignatureType {
371473
/// `abi(…)` when `erase` is set, otherwise its plain `decode(…)`. A wrapped
372474
/// class reference thus erases to the opaque pointer under `erase`, while a
373475
/// value is spelled identically either way (its `abi(…)` is its `decode(…)`).
374-
private func leaf(parameter: String?, generics: Array<String>?, erase: Bool,
476+
/// Under `erase` the binding `arguments` thread on so a wrapped type variable
477+
/// erases by its bound argument (a byref of a value-bound `VAR` wraps the
478+
/// value ABI, not the opaque pointer).
479+
private func leaf(parameter: String?, generics: Array<String>?,
480+
substituting arguments: Array<SignatureType>?, erase: Bool,
375481
with resolver: Resolver, dialect: Dialect) -> String {
376-
erase ? abi(parameter: parameter, generics: generics, with: resolver,
377-
dialect: dialect)
482+
erase ? abi(parameter: parameter, generics: generics,
483+
substituting: arguments, with: resolver, dialect: dialect)
378484
: decode(parameter: parameter, generics: generics, with: resolver,
379485
dialect: dialect)
380486
}

0 commit comments

Comments
 (0)