File: CanonicalABI.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (385 lines) | stat: -rw-r--r-- 14,135 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
import Foundation

public enum CanonicalABI {
    public enum CoreType: Equatable {
        case i32, i64, f32, f64
    }

    struct RawArgument {
        let name: String
        let type: CoreType
    }

    /// The direction of cross-component function call
    /// A cross-component function call is proxied by host runtime, so there are
    /// two call directions, caller component to host and host to callee component.
    public enum CallDirection {
        /// A component is calling a function imported from another component.
        /// Component-level values are being lowered to core-level types.
        case lower
        /// An exported function defined in a component is called from another component.
        /// Lowered core-level values are being lifted to component-level types.
        case lift
    }

    public typealias LabelPath = [String]

    public struct SignatureSegment {
        public var label: LabelPath
        public var type: CoreType

        func prepending(label: String) -> SignatureSegment {
            return SignatureSegment(label: [label] + self.label, type: type)
        }
    }

    public struct CoreSignature {
        public var parameters: [SignatureSegment]
        public var results: [SignatureSegment]
        public var isIndirectResult: Bool
    }

    /// Flatten the given WIT function signature into core function signature
    public static func flattenSignature(
        function: FunctionSyntax,
        typeResolver: (TypeReprSyntax) throws -> WITType
    ) throws -> CoreSignature {
        let parameters = try function.parameters.enumerated().map { i, param in
            let type = try typeResolver(param.type)
            return (param.name.text, type)
        }
        let results = try {
            switch function.results {
            case .named(let parameterList):
                return try parameterList.enumerated().map { i, result in
                    let type = try typeResolver(result.type)
                    return (result.name.text, type)
                }
            case .anon(let typeReprSyntax):
                let type = try typeResolver(typeReprSyntax)
                return [("ret", type)]
            }
        }()
        return CanonicalABI.flatten(
            parameters: parameters,
            results: results,
            direction: .lift
        )
    }

    /// https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening
    public static func flatten(
        parameters: some Sequence<(label: String, type: WITType)>,
        results: some Sequence<(label: String, type: WITType)>,
        direction: CallDirection
    ) -> CoreSignature {
        var flatParameters = parameters.flatMap { (label, type) in
            flatten(type: type).map { $0.prepending(label: label) }
        }
        let MAX_FLAT_PARAMS = 16
        if flatParameters.count > MAX_FLAT_PARAMS {
            flatParameters = [.init(label: ["args"], type: .i32)]
        }

        var flatResults = results.flatMap { (label, type) in
            flatten(type: type).map { $0.prepending(label: label) }
        }
        // To be practical, Canonical ABI specifies not to use multi-value returns
        var indirectResult: Bool = false
        let MAX_FLAT_RESULTS = 1
        if flatResults.count > MAX_FLAT_RESULTS {
            indirectResult = true
            // To reduce `realloc`/`free` calls, caller side is
            // responsible to allocate return value space and tell
            // the address to the intermediate host runtime, then
            // host runtime performs a copy from the address returned
            // by the callee into caller specified return pointer.
            // Note that this cross-component copy is required due to
            // shared-nothing property.
            switch direction {
            case .lower:
                flatParameters.append(.init(label: ["ret"], type: .i32))
                flatResults = []
            case .lift:
                flatResults = [.init(label: ["ret"], type: .i32)]
            }
        }
        return CoreSignature(
            parameters: flatParameters,
            results: flatResults,
            isIndirectResult: indirectResult
        )
    }

    public static func flatten(type: WITType) -> [SignatureSegment] {
        switch type {
        case .bool, .u8, .u16, .u32, .s8, .s16, .s32, .char:
            return [.init(label: [], type: .i32)]
        case .u64, .s64: return [.init(label: [], type: .i64)]
        case .float32: return [.init(label: [], type: .f32)]
        case .float64: return [.init(label: [], type: .f64)]
        case .string:
            return [.init(label: ["ptr"], type: .i32), .init(label: ["len"], type: .i32)]
        case .list:
            return [.init(label: ["ptr"], type: .i32), .init(label: ["len"], type: .i32)]
        case .handleOwn, .handleBorrow:
            return [.init(label: [], type: .i32)]
        case .tuple(let types):
            return types.enumerated().flatMap { i, type in
                flatten(type: type).map { $0.prepending(label: i.description) }
            }
        case .record(let record):
            return record.fields.flatMap { field in
                flatten(type: field.type).map { $0.prepending(label: field.name) }
            }
        case .option(let type):
            return flatten(variants: [type, nil])
        case .result(let ok, let error):
            return flatten(variants: [ok, error])
        case .union(let union):
            return flatten(variants: union.cases.map(\.type))
        case .variant(let variant):
            return flatten(variants: variant.cases.map(\.type))
        case .enum(let `enum`):
            return flatten(variants: `enum`.cases.map { _ in nil })
        case .future: return [.init(label: [], type: .i32)]
        case .stream: return [.init(label: [], type: .i32)]
        case .flags(let flags):
            return Array(repeating: CoreType.i32, count: numberOfInt32(flagsCount: flags.flags.count)).enumerated().map {
                SignatureSegment(label: [$0.description], type: $1)
            }
        case .resource:
            fatalError("TODO: resource type is not supported yet")
        }
    }

    static func flatten(variants: [WITType?]) -> [SignatureSegment] {
        let discriminant = flatten(type: discriminantType(numberOfCases: UInt32(variants.count)).asWITType)
        return discriminant.map { $0.prepending(label: "disc") }
            + flattenVariantPayload(variants: variants).enumerated().map {
                SignatureSegment(label: [$0.description], type: $1)
            }
    }

    /// Flatten the given WIT variant type into core types.
    public static func flattenVariantPayload(variants: [WITType?]) -> [CoreType] {
        var results: [CoreType] = []
        for variantType in variants {
            guard let variantType else { continue }
            for (i, flatten) in flatten(type: variantType).enumerated() {
                if i < results.count {
                    results[i] = join(results[i], flatten.type)
                } else {
                    results.append(flatten.type)
                }
            }
        }
        return results
        /// Return a minimum sized type that fits for two parameter types
        func join(_ a: CoreType, _ b: CoreType) -> CoreType {
            switch (a, b) {
            case (.i32, .f32), (.f32, .i32),
                (.f32, .f32), (.i32, .i32):
                return .i32
            case (_, .i64), (.i64, _),
                (_, .f64), (.f64, _):
                return .i64
            }
        }
    }

    /// A type that represents the discriminant of a variant/enum type.
    public enum DiscriminantType {
        case u8, u16, u32

        public var asWITType: WITType {
            switch self {
            case .u8: return .u8
            case .u16: return .u16
            case .u32: return .u32
            }
        }
    }

    /// Return the smallest integer type that can represent the given number of cases.
    public static func discriminantType(numberOfCases: UInt32) -> DiscriminantType {
        switch Int(ceil(log2(Double(numberOfCases)) / 8)) {
        case 0: return .u8
        case 1: return .u8
        case 2: return .u16
        case 3: return .u32
        default: fatalError("`ceil(log2(UInt32)) / 8` cannot be greater than 3")
        }
    }

    static func numberOfInt32(flagsCount: Int) -> Int {
        return Int(ceil(Double(flagsCount) / 32))
    }

    static func alignUp(_ offset: Int, to align: Int) -> Int {
        let mask = align &- 1
        return (offset &+ mask) & ~mask
    }

    static func payloadOffset(cases: [WITType?]) -> Int {
        let discriminantType = Self.discriminantType(numberOfCases: UInt32(cases.count))
        let discriminantSize = Self.size(type: discriminantType.asWITType)
        let payloadAlign = maxCaseAlignment(cases: cases)
        return alignUp(discriminantSize, to: payloadAlign)
    }

    public static func fieldOffsets(fields: [WITType]) -> [(WITType, Int)] {
        var current = 0
        return fields.map { field in
            let aligned = alignUp(current, to: alignment(type: field))
            current = aligned + size(type: field)
            return (field, aligned)
        }
    }

    public static func size(type: WITType) -> Int {
        switch type {
        case .bool, .u8, .s8: return 1
        case .u16, .s16: return 2
        case .u32, .s32: return 4
        case .u64, .s64: return 8
        case .float32: return 4
        case .float64: return 8
        case .char: return 4
        case .string: return 8
        case .list: return 8
        case .handleOwn, .handleBorrow:
            return 4
        case .tuple(let types):
            return size(fields: types)
        case .option(let type):
            return size(cases: [type, nil])
        case .result(let ok, let error):
            return size(cases: [ok, error])
        case .future:
            return 4
        case .stream:
            return 4
        case .record(let record):
            return size(fields: record.fields.map(\.type))
        case .flags(let flags):
            switch rawType(ofFlags: flags.flags.count) {
            case .u8: return 1
            case .u16: return 2
            case .u32: return 4
            }
        case .enum(let enumType):
            return size(cases: enumType.cases.map { _ in nil })
        case .variant(let variant):
            return size(cases: variant.cases.map(\.type))
        case .resource:
            fatalError("TODO: resource types are not supported yet")
        case .union:
            fatalError("FIXME: union types has been removed from the Component Model spec")
        }
    }

    static func size(fields: [WITType]) -> Int {
        var size = 0
        for field in fields {
            let fieldSize = Self.size(type: field)
            let fieldAlign = alignment(type: field)
            size = alignUp(size, to: fieldAlign) + fieldSize
        }
        return alignUp(size, to: alignment(fields: fields))
    }

    static func size(cases: [WITType?]) -> Int {
        var maxSize = 0
        for case .some(let caseType) in cases {
            maxSize = max(maxSize, size(type: caseType))
        }
        return alignUp(payloadOffset(cases: cases) + maxSize, to: alignment(cases: cases))
    }

    public static func alignment(type: WITType) -> Int {
        switch type {
        case .bool, .u8, .s8: return 1
        case .u16, .s16: return 2
        case .u32, .s32: return 4
        case .u64, .s64: return 8
        case .float32: return 4
        case .float64: return 8
        case .char: return 4
        case .string: return 4
        case .list: return 4
        case .handleOwn, .handleBorrow:
            return 4
        case .tuple(let types):
            return alignment(fields: types)
        case .option(let type):
            return alignment(cases: [type, nil])
        case .result(let ok, let error):
            return alignment(cases: [ok, error])
        case .future:
            return 4
        case .stream:
            return 4
        case .record(let record):
            return alignment(fields: record.fields.map(\.type))
        case .flags(let flags):
            switch rawType(ofFlags: flags.flags.count) {
            case .u8: return 1
            case .u16: return 2
            case .u32: return 4
            }
        case .enum(let enumType):
            return alignment(cases: enumType.cases.map { _ in nil })
        case .variant(let variant):
            return alignment(cases: variant.cases.map(\.type))
        case .resource:
            fatalError("TODO: resource type is not supported yet")
        case .union:
            fatalError("FIXME: union type is already removed from the spec")
        }
    }

    static func alignment(cases: [WITType?]) -> Int {
        max(
            alignment(type: discriminantType(numberOfCases: UInt32(cases.count)).asWITType),
            maxCaseAlignment(cases: cases)
        )
    }

    static func alignment(fields: [WITType]) -> Int {
        fields.map(Self.alignment(type:)).max() ?? 1
    }

    static func maxCaseAlignment(cases: [WITType?]) -> Int {
        var alignment = 1
        for caseType in cases {
            guard let caseType else { continue }
            alignment = max(alignment, Self.alignment(type: caseType))
        }
        return alignment
    }

    public enum FlagsRawRepresentation {
        case u8, u16
        case u32(Int)

        public var numberOfInt32: Int {
            switch self {
            case .u8, .u16: return 1
            case .u32(let v): return v
            }
        }
    }

    public static func rawType(ofFlags: Int) -> FlagsRawRepresentation {
        if ofFlags == 0 {
            return .u32(0)
        } else if ofFlags <= 8 {
            return .u8
        } else if ofFlags <= 16 {
            return .u16
        } else {
            return .u32(CanonicalABI.numberOfInt32(flagsCount: ofFlags))
        }
    }
}