-
Notifications
You must be signed in to change notification settings - Fork 360
Expand file tree
/
Copy pathWasmLifter.swift
More file actions
2390 lines (2152 loc) · 102 KB
/
WasmLifter.swift
File metadata and controls
2390 lines (2152 loc) · 102 KB
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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Foundation
/// Represents the type identifiers for each code section according to the wasm
/// spec.
private enum WasmSection: UInt8 {
case custom = 0
case type = 1
case `import`
case function
case table
case memory
case global
case export
case start
case element
case code
case data
case datacount
case tag
}
private enum Prefix: UInt8 {
case GC = 0xFB
case Numeric = 0xFC
case Simd = 0xFD
case Atomic = 0xFE
}
// This maps ILTypes to their respective binary encoding.
private let ILTypeMapping: [ILType: Data] = [
.wasmi32: Data([0x7F]),
.wasmi64: Data([0x7E]),
.wasmf32: Data([0x7D]),
.wasmf64: Data([0x7C]),
.wasmSimd128: Data([0x7B]),
.wasmPackedI8: Data([0x78]),
.wasmPackedI16: Data([0x77]),
.bigint: Data([0x7E]), // Maps to .wasmi64
.jsAnything: Data([0x6F]), // Maps to .wasmExternRef
.integer: Data([0x7F]), // Maps to .wasmi32
.number: Data([0x7D]), // Maps to .wasmf32
]
/// This is the main compiler for Wasm instructions.
/// This lifter collects all wasm instructions during lifting
/// (The JavaScriptLifter passes them to this instance) and then it compiles them
/// at the end of the block when we see a EndWasmModule instruction.
/// This way the WasmLifter has full information before it actually emits any bytes.
public class WasmLifter {
/// This enum describes various failure cases that might arise from mutation
/// in the JS part of the sample, which can invalidate some of the Wasm code.
public enum CompileError: Error {
// If we invalidated some import from JavaScript, see `buildImportSection`.
case unknownImportType
// If we fail to lookup the index in one of the sections, see `resolveIdx`.
case failedIndexLookUp
// If the signature is not found, see `getSignatureIdx`.
case failedSignatureLookUp
// If the branch target is invalid.
case invalidBranch
// If type information is not available where we need it.
case missingTypeInformation
// If we fail to find a variable during import linking
case failedRetrieval
// This means likely some input has been reassigned in JS, which means it is not of the expected type in Wasm, this is similar to the unknownImportType
case invalidInput
// Any kind of error that should result in a fatal error instead of gracefully catching it.
// (Use this over fatalError() to get better error reporting about the crashing program.
case fatalError(String)
}
indirect enum Export {
// The associated data can be nil, which means that this is a re-export of an import.
case function(FunctionInfo?)
// This should only be an import, this is always of type .import(.suspendingObject, variable, signature)
case suspendingObject
case table(Instruction?)
case memory(Instruction?)
case global(Instruction?)
case tag(Instruction?)
// This import case is special.
//
// We only expect a single level of "indirectness". I.e. if the export is an import, the type can never be import, it can only be other enum cases.
// Additionally, if it is an import, the type's associated data should always be nil.
// We do this because the exports array will serve multiple purposes:
// - It tracks the exports and the types we define in the Wasm module
// - It tracks the imports we see while traversing the module's code
// - It keeps the ordering of seen variables correct, so that when we refer to variables in instructions `resolveIdx` can only look at the exports array and return the correct index
// - The `importAnalysis` function should add these entries, exports and imports, to the array in the order it sees them, that keeps the indexes correct.
//
// The variable that is associated with this import is later used to pass the variables back to the JavaScript lifter such that it can get the right expressions for the needed imports.
// Imported functions also have signatures, we need these as we might call a function through two different WasmJSCall instructions.
// We then cannot distinguish them and we need two different type entries and two different imports.
case `import`(type: Export, variable: Variable, signature: WasmSignature?)
// These accessors below don't look into the imports.
// This is by design, it allows us to easily traverse the exports array to build the sections without filtering out the imports.
// If we need access to the imports, we can always do
// `exports.compactMap({$0.getImport()})`
// Which will now "unwrap" these imports essentially.
// It will drop all non-imported entries and return a list of `(type, variable, signature)` for all imports.
// This can further be filtered for only e.g. tag imports by doing this.
// `exports.compactMap({$0.getImport()}).filter({$0.type.isFunction})`
var isFunction: Bool {
if case .function(_) = self {
return true
} else {
return false
}
}
var isTable: Bool {
if case .table(_) = self {
return true
} else {
return false
}
}
var isMemory: Bool {
if case .memory(_) = self {
return true
} else {
return false
}
}
var isGlobal: Bool {
if case .global(_) = self {
return true
} else {
return false
}
}
var isTag: Bool {
if case .tag(_) = self {
return true
} else {
return false
}
}
var isSuspendingObject: Bool {
if case .suspendingObject = self {
return true
} else {
return false
}
}
func getImport() -> (type: Self, variable: Variable, signature: WasmSignature?)? {
if case .import(let export, let variable, let signature) = self {
return (export, variable, signature)
}
return nil
}
func getDefInstr() -> Instruction? {
switch self {
case .function(_),
.import(_, _, _),
.suspendingObject:
return nil
case .global(let instr),
.table(let instr),
.memory(let instr),
.tag(let instr):
return instr!
}
}
func groupName() -> String {
switch self {
case .function,
.import,
.suspendingObject:
// Functions and imports don't have group names, this is used for getting imports of that type.
fatalError("unreachable")
case .table:
return "WasmTable"
case .memory:
return "WasmMemory"
case .global:
return "WasmGlobal"
case .tag:
return "WasmTag"
}
}
func exportName(forIdx idx: Int) -> String {
return switch self {
case .function,
.suspendingObject:
WasmLifter.nameOfFunction(idx)
case .table:
WasmLifter.nameOfTable(idx)
case .memory:
WasmLifter.nameOfMemory(idx)
case .global:
WasmLifter.nameOfGlobal(idx)
case .tag:
WasmLifter.nameOfTag(idx)
case .import(let exp, _, _):
"i\(exp.exportName(forIdx: idx))"
}
}
func exportTypeByte() -> Int {
switch self {
case .function,
.suspendingObject:
return 0x0
case .table:
return 0x1
case .memory:
return 0x2
case .global:
return 0x3
case .tag:
return 0x4
case .import(let exp, _, _):
return exp.exportTypeByte()
}
}
}
private var logger = Logger(withLabel: "WasmLifter")
// The actual bytecode we compile.
private var bytecode: Data = Data()
// The level of verboseness
private var verbose: Bool = false
// The string that is given to the script writer
private var out: String = ""
// This typer holds information from the "outside" JS world.
// It is created during lifting of JavaScript and the JavaScriptLifter passes it into the WasmLifter.
private var typer: JSTyper
// This contains the instructions that we need to lift.
private var instructionBuffer: Code = Code()
// TODO(cffsmith): we could do some checking here that the function is actually defined, at that point it would not be static anymore though.
private static func nameOfFunction(_ idx: Int) -> String {
return "w\(idx)"
}
// TODO(cffsmith): we could do some checking here that the global is actually defined, at that point it would not be static anymore though.
private static func nameOfGlobal(_ idx: Int) -> String {
return "wg\(idx)"
}
private static func nameOfTable(_ idx: Int) -> String {
return "wt\(idx)"
}
private static func nameOfTag(_ idx: Int) -> String {
return "wex\(idx)"
}
private static func nameOfMemory(_ idx: Int) -> String {
return "wm\(idx)"
}
// This tracks instructions that create exports of the specific ExportType.
// This is later used to build parts of the export section.
// The order here matches the order of the exports as seen by the ProgramBuilder, this is necessary so that we use the correct indices when emitting instructions.
private var exports: [Export] = []
private var dataSegments: [Instruction] = []
private var elementSegments: [Instruction] = []
// // The tags associated with this module.
// private var tags: VariableMap<[ILType]> = VariableMap()
private var typeGroups: Set<Int> = []
private var typeDescToIndex: [WasmTypeDescription: Int] = [:]
private var userDefinedTypesCount = 0
// The function index space
private var functionIdxBase = 0
// The signature index space.
private var signatures: [WasmSignature] = []
private var signatureIndexMap: [WasmSignature: Int] = [:]
// This tracks in which order we have seen globals, this can probably be unified with the .globals and .imports properties, as they should match in their keys.
private var globalOrder: [Variable] = []
private var tagOrder: [Variable] = []
public init(withTyper typer: JSTyper, withWasmCode instrs: Code) {
self.typer = typer
self.instructionBuffer = instrs
}
private class WasmExprWriter {
// This maps variables to its bytecode.
// If we see an instruction we can just push it onto here, and if we actually see the variable as an input, we can then
// Emit a load.
// This either contains the rawByte code to construct the output, i.e. const.i64
// Or it contains a load to a local variable if the instr has multiple inputs.
private var varMap: VariableMap<Data> = VariableMap()
// Tracks variables that we have emitted at least once.
private var emittedVariables: Set<Variable> = []
func addExpr(for variable: Variable, bytecode: Data) {
self.varMap[variable] = bytecode
}
func getExpr(for variable: Variable) -> Data? {
let expr = self.varMap[variable]
emittedVariables.insert(variable)
return expr
}
public var isEmpty: Bool {
return varMap.isEmpty
}
}
private var writer = WasmExprWriter()
var isEmpty: Bool {
return instructionBuffer.isEmpty && self.bytecode.isEmpty && self.exports.isEmpty
}
// TODO: maybe we can do some analysis based on blocks.
// Get the type index or something or infer the value type that it tries to return? With BlockInfo class or something like that?
private func updateVariableAnalysis(forInstruction wasmInstruction: Instruction) {
// Only analyze an instruction if we are inside a function definition.
if let currentFunction = currentFunction {
currentFunction.variableAnalyzer.analyze(wasmInstruction)
}
}
// Holds various information for the functions in a wasm module.
class FunctionInfo {
var signature: WasmSignature
var code: Data
var branchHints: [(hint: WasmBranchHint, offset: Int)] = []
var outputVariable: Variable? = nil
// Locals that we spill to, this maps from the ordering to the stack.
var localsInfo: [(Variable, ILType)]
var variableAnalyzer = VariableAnalyzer()
weak var lifter: WasmLifter?
// Tracks the labels and the branch depth they've been emitted at. This is needed to calculate how far "out" we have to branch to
// Whenever we start something that emits a label, we need to track the branch depth here.
// This should be local to a function
public var labelBranchDepthMapping: VariableMap<Int> = VariableMap()
// Expects the withArguments array to contain the variables of the innerOutputs, they should map directly to the local indices.
init(
_ signature: WasmSignature, _ code: Data, for lifter: WasmLifter,
withArguments arguments: [Variable]
) {
// Infer the first few locals from this signature.
self.signature = signature
self.code = code
self.localsInfo = [(Variable, ILType)]()
self.lifter = lifter
assert(signature.parameterTypes.count + 1 == arguments.count)
// Populate the localsInfo with the parameter types.
for (idx, argVar) in arguments.dropFirst().enumerated() {
self.localsInfo.append((argVar, signature.parameterTypes[idx]))
// Emit the expressions for the parameters such that we can accesss them if we need them.
self.lifter!.writer.addExpr(
for: argVar,
bytecode: Data([0x20]) + Leb128.unsignedEncode(self.localsInfo.count - 1))
}
}
func appendToCode(_ code: Data) {
self.code += code
}
// This collects the variables that we spill.
func spillLocal(forVariable variable: Variable) {
self.localsInfo.append((variable, lifter!.typer.type(of: variable)))
assert(lifter!.typer.type(of: variable).Is(.wasmPrimitive))
// Do a local.set on the stack slot
self.code += Data([0x21]) + Leb128.unsignedEncode(localsInfo.count - 1)
}
func isLocal(_ variable: Variable) -> Bool {
self.localsInfo.contains(where: { $0.0 == variable })
}
func getStackSlot(for variable: Variable) -> Int? {
return self.localsInfo.firstIndex(where: { $0.0 == variable })
}
// This loads a variable from the stack. This is designed for arguments of functions
func addStackLoad(for variable: Variable) {
// We expect to do this for innerOutputs.
assert(isLocal(variable) && getStackSlot(for: variable) != nil)
// This emits a local.get for the function argument.
self.code += Data([0x20]) + Leb128.unsignedEncode(getStackSlot(for: variable)!)
}
func addBranchHint(_ hint: WasmBranchHint) {
if hint != .None {
self.branchHints.append((hint: hint, offset: self.code.count))
}
}
}
private var currentFunction: FunctionInfo? = nil
public func lift(binaryOutPath path: String? = nil) throws -> (Data, [Variable]) {
// Lifting currently happens in three stages.
// 1. Collect all necessary information to build all sections later on.
// - For now this only the importAnalysis, which needs to know how many imported vs internally defined types exist.
// 2. Lift each instruction within its local context using all information needed from the previous analysis inside of a given function
// 3. Use the already lifted functions to emit the whole Wasm byte buffer.
// Step 1:
// Collect all information that we need to later wire up the imports correctly, this means we look at instructions that can potentially import any variable that originated outside the Wasm module.
try importAnalysis()
// Todo: maybe add a def-use pass here to figure out where we need stack spills etc? e.g. if we have one use, we can omit the stack spill
//
// Step 1 Done
//
//
// Step 2:
//
// Lift each instruction individually into a byte buffer. This happens sequentially, you better have all the information you will need here.
// Collect function/type/signature information.
for instr in self.instructionBuffer {
let needsByteEmission = updateLifterState(wasmInstruction: instr)
// TODO: Check if we are in a .wasmFunction context and if so, update variableAnalysis.
updateVariableAnalysis(forInstruction: instr)
if needsByteEmission {
// If we require inputs for this instruction, we probably need to emit them now, either inline the corresponding instruction, iff this is a single use, or load the stack slot or the variable. TODO: not all of this is implemented.
emitInputLoadsIfNecessary(forInstruction: instr)
// Emit the actual bytes that correspond to this instruction to the corresponding function byte array.
try emitBytesForInstruction(forInstruction: instr)
// If this instruction produces any outputs, we might need to explicitly spill to the stack.
emitStackSpillsIfNecessary(forInstruction: instr)
}
}
//
// Step 2 Done
// All functions should have associated byte code at this point.
//
//
// Step 3: Lift the whole module and put everything together.
//
if verbose {
print("Got the following functions")
for case .function(let functionInfo) in self.exports {
print("\(String(describing: functionInfo))")
}
}
// Build the header section which includes the Wasm version first
self.buildHeader()
try self.buildTypeSection()
try self.buildImportSection()
try self.buildFunctionSection()
try self.buildTableSection()
self.buildMemorySection()
try self.buildTagSection()
try self.buildGlobalSection()
// Export all functions by default.
try self.buildExportedSection()
// Build element segments for defined tables.
try self.buildElementSection()
try self.buildDataCountSection()
// The actual bytecode of the functions.
try self.buildCodeSection(self.instructionBuffer)
try self.buildDataSection()
// Write the bytecode as file to the given path for debugging purposes.
if let path = path {
let url = URL(fileURLWithPath: path)
try? bytecode.write(to: url)
}
//
// Step 3 done
//
return (bytecode, exports.compactMap { $0.getImport()?.variable })
}
private func buildHeader() {
// Build the magic and the version of wasm we compile to.
self.bytecode += [0x0]
self.bytecode += "asm".data(using: .ascii)!
// LE encoded 1 as the wasm version.
self.bytecode += [0x1, 0x0, 0x0, 0x0]
}
private func encodeAbstractHeapType(_ heapTypeInfo: HeapTypeInfo) -> Data {
// Base of v8 implementation. See
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/wasm-constants.h?q=symbol:ValueTypeCode
let sharedFlagPrefix: [UInt8] = heapTypeInfo.shared ? [0x65] : []
let opCode: UInt8 =
switch heapTypeInfo.heapType {
case .WasmExtern: 0x6F
case .WasmFunc: 0x70
case .WasmAny: 0x6E
case .WasmEq: 0x6D
case .WasmI31: 0x6C
case .WasmStruct: 0x6B
case .WasmArray: 0x6A
case .WasmExn: 0x69
case .WasmNone: 0x71
case .WasmNoExtern: 0x72
case .WasmNoFunc: 0x73
case .WasmNoExn: 0x74
}
return Data(sharedFlagPrefix + [opCode])
}
private func encodeWasmGCType(_ description: WasmTypeDescription?) throws -> Data {
guard let description else {
throw WasmLifter.CompileError.missingTypeInformation
}
return Leb128.unsignedEncode(typeDescToIndex[description]!)
}
private func encodeType(_ type: ILType, defaultType: ILType? = nil) throws -> Data {
if let refType = type.wasmReferenceType {
let isNullable = refType.nullability
let nullabilityByte: UInt8 = isNullable ? 0x63 : 0x64
return try Data([nullabilityByte]) + encodeHeapType(refType.kind)
}
// HINT: If you crash here, you might not have specified an encoding for your new type in `ILTypeMapping`.
return ILTypeMapping[type] ?? ILTypeMapping[defaultType!]!
}
private func encodeHeapType(_ refKind: WasmReferenceType.Kind) throws -> Data {
switch refKind {
case .Index(let description):
return try encodeWasmGCType(description.get())
case .Abstract(let heapTypeInfo):
return encodeAbstractHeapType(heapTypeInfo)
}
}
// Convienience method to avoid casting to wasmReferenceType on callers side.
private func encodeHeapType(_ type: ILType) throws -> Data {
if let refType = type.wasmReferenceType {
return try encodeHeapType(refType.kind)
}
fatalError("This function supports only wasmReferenceType.")
}
// Helper method in cases we have either abstract type represented by ILType or an index type Variable
private func encodeReferenceType(
_ refType: WasmReferenceType, instr: Instruction, typeInput: Int
) throws -> Data {
switch refType.kind {
case .Abstract(let heapTypeInfo):
return encodeAbstractHeapType(heapTypeInfo)
case .Index(_):
return try encodeWasmGCType(typer.getTypeDescription(of: instr.input(typeInput)))
}
}
private func buildTypeEntry(for desc: WasmTypeDescription, data: inout Data) throws {
if let arrayDesc = desc as? WasmArrayTypeDescription {
data += [0x5E]
data += try encodeType(arrayDesc.elementType)
data += [arrayDesc.mutability ? 1 : 0]
} else if let structDesc = desc as? WasmStructTypeDescription {
data += [0x5F]
data += Leb128.unsignedEncode(structDesc.fields.count)
for field in structDesc.fields {
data += try encodeType(field.type)
data += [field.mutability ? 1 : 0]
}
} else if let signatureDesc = desc as? WasmSignatureTypeDescription {
data += [0x60]
data += Leb128.unsignedEncode(signatureDesc.signature.parameterTypes.count)
for parameterType in signatureDesc.signature.parameterTypes {
data += try encodeType(parameterType)
}
data += Leb128.unsignedEncode(signatureDesc.signature.outputTypes.count)
for outputType in signatureDesc.signature.outputTypes {
data += try encodeType(outputType)
}
} else {
fatalError("Unsupported WasmTypeDescription!")
}
}
private func buildTypeSection() throws {
// Collect all signatures of imported functions, suspendable objects or tags.
// See importAnalysis for more details.
for signature in self.exports.compactMap({ $0.getImport()?.signature }) {
registerSignature(signature)
}
// Special handling for defined functions
for case .function(let functionInfo) in self.exports {
registerSignature(functionInfo!.signature)
}
let typeCount = self.signatures.count + typeGroups.count
if typeCount == 0 {
return
}
self.bytecode += [WasmSection.type.rawValue]
var temp = Leb128.unsignedEncode(typeCount)
// TODO(mliedtke): Integrate this with the whole signature mechanism as
// these signatures could contain wasm-gc types.
for typeGroupIndex in typeGroups.sorted() {
let typeGroup = typer.getTypeGroup(typeGroupIndex)
temp += [0x4E]
temp += Leb128.unsignedEncode(typeGroup.count)
for typeDef in typeGroup {
try buildTypeEntry(for: typer.getTypeDescription(of: typeDef), data: &temp)
}
}
for signature in self.signatures {
temp += [0x60]
temp += Leb128.unsignedEncode(signature.parameterTypes.count)
for paramType in signature.parameterTypes {
temp += try encodeType(paramType)
}
temp += Leb128.unsignedEncode(signature.outputTypes.count)
for outputType in signature.outputTypes {
temp += try encodeType(outputType, defaultType: .wasmExternRef())
}
}
if verbose {
print("Type section is")
for byte in temp {
print(String(format: "%02X ", byte))
}
}
// Append the length of the section and the section contents itself.
self.bytecode.append(Leb128.unsignedEncode(temp.count))
self.bytecode.append(temp)
}
private func registerSignature(_ signature: WasmSignature) {
assert(signatures.count == signatureIndexMap.count)
if signatureIndexMap[signature] != nil {
return
}
let signatureIndex = userDefinedTypesCount + signatures.count
signatures.append(signature)
signatureIndexMap[signature] = signatureIndex
assert(signatures.count == signatureIndexMap.count)
}
private func getSignatureIndex(_ signature: WasmSignature) throws -> Int {
if let idx = signatureIndexMap[signature] {
return idx
}
throw WasmLifter.CompileError.failedSignatureLookUp
}
private func getSignatureIndexStrict(_ signature: WasmSignature) -> Int {
return signatureIndexMap[signature]!
}
private func buildImportSection() throws {
if self.exports.compactMap({ $0.getImport() }).isEmpty {
return
}
self.bytecode += [WasmSection.import.rawValue]
var temp = Data()
temp += Leb128.unsignedEncode(self.exports.count { $0.getImport() != nil })
// Build the import components of this vector that consist of mod:name, nm:name, and d:importdesc
for (idx, (_, importVariable, signature)) in self.exports.compactMap({ $0.getImport() })
.enumerated()
{
if verbose {
print(importVariable)
}
// Append the name as a vector
temp += Leb128.unsignedEncode("imports".count)
temp += "imports".data(using: .utf8)!
var importName: String
importName = "import_\(idx)_\(importVariable)"
temp += Leb128.unsignedEncode(importName.count)
temp += importName.data(using: .utf8)!
let type = typer.type(of: importVariable)
// This is a temporary workaround for functions that have been marked as suspendable.
if type.Is(.function()) || type.Is(.object(ofGroup: "WasmSuspendingObject")) {
if verbose {
print(functionIdxBase)
}
temp += [0x0] + Leb128.unsignedEncode(try getSignatureIndex(signature!))
// Update the index space, these indices have to be set before the exports are set
functionIdxBase += 1
continue
}
if type.Is(.object(ofGroup: "WasmMemory")) {
// Emit import type.
temp += Data([0x2])
let mem = type.wasmMemoryType!
let limits_byte: UInt8 =
(mem.isMemory64 ? 4 : 0) | (mem.isShared ? 2 : 0)
| (mem.limits.max != nil ? 1 : 0)
temp += Data([limits_byte])
temp += Data(Leb128.unsignedEncode(mem.limits.min))
if let maxPages = mem.limits.max {
temp += Data(Leb128.unsignedEncode(maxPages))
}
continue
}
if type.Is(.object(ofGroup: "WasmTable")) {
// Emit import type.
temp += Data([0x1])
let table = type.wasmTableType!
temp += try encodeType(table.elementType)
let limits_byte: UInt8 =
(table.isTable64 ? 4 : 0) | (table.limits.max != nil ? 1 : 0)
temp += Data([limits_byte])
temp += Data(Leb128.unsignedEncode(table.limits.min))
if let maxSize = table.limits.max {
temp += Data(Leb128.unsignedEncode(maxSize))
}
continue
}
if type.Is(.object(ofGroup: "WasmGlobal")) {
let valueType = type.wasmGlobalType!.valueType
let mutability = type.wasmGlobalType!.isMutable
temp += [0x3]
temp += try encodeType(valueType)
temp += mutability ? [0x1] : [0x0]
continue
}
if type.Is(.object(ofGroup: "WasmTag")) && signature != nil {
temp += [0x4, 0x0] + Leb128.unsignedEncode(try getSignatureIndex(signature!))
continue
}
throw WasmLifter.CompileError.unknownImportType
}
self.bytecode.append(Leb128.unsignedEncode(temp.count))
self.bytecode.append(temp)
if verbose {
print("import section is")
for byte in temp {
print(String(format: "%02X ", byte))
}
}
}
private func buildFunctionSection() throws {
let functionCount = self.exports.count { $0.isFunction }
if functionCount == 0 {
return
}
self.bytecode += [WasmSection.function.rawValue]
// The number of functions we have, as this is a vector of type idxs.
// TODO(cffsmith): functions can share type indices. This could be an optimization later on.
var temp = Leb128.unsignedEncode(functionCount)
for case .function(let functionInfo) in self.exports {
temp.append(Leb128.unsignedEncode(try getSignatureIndex(functionInfo!.signature)))
}
// Append the length of the section and the section contents itself.
self.bytecode.append(Leb128.unsignedEncode(temp.count))
self.bytecode.append(temp)
if verbose {
print("function section is")
for byte in temp {
print(String(format: "%02X ", byte))
}
}
}
private func buildTableSection() throws {
let tableCount = self.exports.count { $0.isTable }
if tableCount == 0 {
return
}
self.bytecode += [WasmSection.table.rawValue]
var temp = Leb128.unsignedEncode(tableCount)
for case .table(let instruction) in self.exports {
let op = instruction!.op as! WasmDefineTable
let elementType = op.elementType
let minSize = op.limits.min
let maxSize = op.limits.max
let isTable64 = op.isTable64
temp += try encodeType(elementType)
let limits_byte: UInt8 = (isTable64 ? 4 : 0) | (maxSize != nil ? 1 : 0)
temp += Data([limits_byte])
temp += Data(Leb128.unsignedEncode(minSize))
if let maxSize {
temp += Data(Leb128.unsignedEncode(maxSize))
}
}
// Append the length of the section and the section contents itself.
self.bytecode.append(Leb128.unsignedEncode(temp.count))
self.bytecode.append(temp)
if verbose {
print("table section is")
for byte in temp {
print(String(format: "%02X ", byte))
}
}
}
// Only supports:
// - passive & active segments
// - with custom table id
// - function-indices-as-elements (i.e. case 1 && 2 of the spec: https://webassembly.github.io/spec/core/binary/modules.html#element-section)
// - one segment per table (assumes entries are continuous)
// - constant starting index.
private func buildElementSection() throws {
let numDefinedTablesWithEntries = self.exports.count {
if case .table(let instruction) = $0 {
return !(instruction!.op as! WasmDefineTable).definedEntries.isEmpty
} else {
return false
}
}
if numDefinedTablesWithEntries == 0 && elementSegments.count == 0 { return }
self.bytecode += [WasmSection.element.rawValue]
var temp = Data()
// Element segment count.
temp += Leb128.unsignedEncode(numDefinedTablesWithEntries + elementSegments.count)
// Passive element segments. They go first so that their indexes start with 0.
for instruction in self.elementSegments {
let size = (instruction.op as! WasmDefineElementSegment).size
assert(size == instruction.numInputs)
// Element segment case 1 definition.
temp += [0x01]
// elemkind
temp += [0x00]
// Elements
temp += Leb128.unsignedEncode(instruction.numInputs)
for f in instruction.inputs {
let functionIdx = try resolveIdx(ofType: .function, for: f)
temp += Leb128.unsignedEncode(functionIdx)
}
}
// Active element segments
for case .table(let instruction) in self.exports {
let table = instruction!.op as! WasmDefineTable
let definedEntries = table.definedEntries
assert(definedEntries.count == instruction!.inputs.count)
if definedEntries.isEmpty { continue }
// Element segment case 2 definition.
temp += [0x02]
let tableIndex = try self.resolveIdx(ofType: .table, for: instruction!.output)
temp += Leb128.unsignedEncode(tableIndex)
// Starting index. Assumes all entries are continuous.
temp += table.isTable64 ? [0x42] : [0x41]
temp += Leb128.unsignedEncode(definedEntries[0].indexInTable)
temp += [0x0B] // end
// elemkind
temp += [0x00]
// entry count
temp += Leb128.unsignedEncode(definedEntries.count)
// entries
for entry in instruction!.inputs {
let functionIdx = try resolveIdx(ofType: .function, for: entry)
temp += Leb128.unsignedEncode(functionIdx)
}
}
self.bytecode.append(Leb128.unsignedEncode(temp.count))
self.bytecode.append(temp)
if verbose {
print("element section is")
for byte in temp {
print(String(format: "%02X ", byte))
}
}
}
private func buildCodeSection(_ instructions: Code) throws {
let functionCount = self.exports.count { $0.isFunction }
if functionCount == 0 {
return
}
// Build the contents of the section
var temp = Leb128.unsignedEncode(functionCount)
var functionBranchHints = [Data]()
let functions = self.exports.filter({ $0.isFunction })
let importedFunctionCount = self.exports.compactMap({ $0.getImport() }).count {
$0.type.isFunction
}
for (defIndex, export) in functions.enumerated() {
guard case .function(let functionInfo) = export else {
fatalError("unreachable")
}
if verbose {
print("code is:")
for byte in functionInfo!.code {
print(String(format: "%02X", byte))
}
print("end of code")
}
var funcTemp = Data()
// TODO: this should be encapsulated more nicely. There should be an interface that gets the locals without the parameters. As this is currently mainly used to get the slots info.
// Encode number of locals
funcTemp += Leb128.unsignedEncode(
functionInfo!.localsInfo.count - functionInfo!.signature.parameterTypes.count)
for (_, type) in functionInfo!.localsInfo[
functionInfo!.signature.parameterTypes.count...]
{
// Encode the locals
funcTemp += Leb128.unsignedEncode(1)
funcTemp += try encodeType(type)
}
let localsDefSizeInBytes = funcTemp.count
// append the actual code and the end marker
funcTemp += functionInfo!.code
funcTemp += [0x0B]
// Append the function object to the section
temp += Leb128.unsignedEncode(funcTemp.count)
temp += funcTemp
// Encode the branch hint section entry for this function.
if !functionInfo!.branchHints.isEmpty {
// The function entry is the function index, followed by the counts of branch hints
// for this function and then the bytes containing the actual branch hints.
let functionIndex = defIndex + importedFunctionCount
let hintsEncoded =
Leb128.unsignedEncode(functionIndex)