Primitive Types: Boolean: 1 byte Char: 1 byte Byte: 1 byte UByte: 1 byte i7: 1 byte (MSB is a flag, the lower 7 bits represent a number) Short: 2 bytes Int: 4 bytes UInt: 4 bytes Long: 8 bytes ULong: 8 bytes Data (has no TypeId field): // Data elements are not serializable on their own. List: size: Int elements: { n * Element } String = List // Alias WasmOp: opcode: Short WasmImmediate: ConstU8: value: UByte ConstI32: value: Int ConstI64: value: Long ConstF32: rawBits: UInt ConstF64: rawBits: ULong SymbolI32: value: WasmSymbol MemArg: align: UInt offset: UInt BlockType: Function: type: WasmFunction Value: type: WasmType FunctionIdx: value: WasmSymbol LocalIdx: value: WasmSymbol GlobalIdx: value: WasmSymbol TypeIdx: value: WasmSymbolReadOnly ValTypeVector: value: List MemoryIdx: value: Int DataIdx: value: WasmSymbol TableIdx: value: WasmSymbolReadOnly LabelIdx: value: Int TagIdx: value: Int LabelIdxVector: value: List ElemIdx: value: WasmElement GcType: value: WasmSymbol StructFieldIdx: value: WasmSymbol HeapType value: WasmHeapType Catch: type: Byte immediates: Immediates ConstString: value: String Immediates: 1, 2, 3, or 4 WasmImmediates, depending on the context // Not using a list. In other words, not prefixed by a size WasmLimits: minSize: UInt maxSize: Uint (null = UInt(-1) = UINT_MAX) WasmImportDescriptor: moduleName: String declarationName: String Structs: Important Notes: TypeId: - Any struct has a TypeId field - The TypeId field of structs is a constant number that identifies the type of the struct. - TypeId constants are assigned arbitrarily. - TypeIds are prepended to the struct in the process of serialization so that the type of the object to construct is known when deserializing. - The MSB of a TypeId is used to determine if a struct is null or not. - A null-struct takes only 1 byte for the TypeId field Linking: - Assume for now that there is no late binding and that the module as a whole is being serialized/deserialized after linking. - The design of the structs takes linking into consideration so that changes needed after keys become serializable are minimal. - For now, WasmSymbol = WasmSymbolReadOnly = T, but later, they will include keys, and only these two structs will need to be modified. TypeId: id: i7 // MSB = (struct == null) File: elementsTypeId: TypeId // Common base struct TypeId for elements of the list content: List WasmSymbolReadOnly: T WasmSymbol: T WasmModule: typeId: TypeId functionTypes: List recGroupTypes: List importsInOrder: List importedFunctions: List importedMemories: List importedTables: List importedGlobals: List importedTags: List definedFunctions: List tables: List memories: List globals: List exports: List elements: List tags: List startFunction: WasmFunction data: List dataCount: Boolean WasmType: WasmUnreachableType: typeId: TypeId WasmI32: typeId: TypeId WasmI64: typeId: TypeId WasmF32: typeId: TypeId WasmF64: typeId: TypeId WasmV128: typeId: TypeId WasmI8: typeId: TypeId WasmI16: typeId: TypeId WasmFuncRef: typeId: TypeId WasmExternRef: typeId: TypeId WasmAnyRef: typeId: TypeId WasmEqRef: typeId: TypeId WasmRefNullrefType: typeId: TypeId WasmRefNullExternrefType: typeId: TypeId WasmExnRefType: typeId: TypeId WasmNullExnRefType: typeId: TypeId WasmRefNullType: typeId: TypeId WasmRefType: typeId: TypeId WasmI31Ref: typeId: TypeId WasmStructRef: typeId: TypeId WasmHeapType: Simple: Func: typeId: TypeId Extern: typeId: TypeId Any: typeId: TypeId Eq: typeId: TypeId Struct: typeId: TypeId None: typeId: TypeId NoExtern: typeId: TypeId Type: typeId: TypeId type: WasmSymbolReadOnly // This is not used in the code WasmBlockType: Function: typeId: TypeId type: WasmFunctionType Value: typeId: TypeId type: WasmType WasmDataMode: Active: typeId: TypeId memoryIdx: Int offset: List Passive: typeId: TypeId WasmElement.Mode: Passive: typeId: TypeId Active: typeId: TypeId table: WasmTable offset: List Declarative: typeId: TypeId WasmTable.Value: Function: typeId: TypeId function: WasmSymbol Expression: typeId: TypeId expr: List WasmLocal: typeId: TypeId id: Int name: String type: WasmType isParameter: Boolean WasmNamedModuleField: Id: id: Int (null = -1) WasmFunction: Defined: typeId: TypeId id: Id name: String type: WasmSymbolReadOnly locals: List instructions: List Imported: typeId: TypeId id: Id name: String type: WasmSymbolReadOnly importPair: WasmImportDescriptor WasmMemory: typeId: TypeId id: Id name: String limits: WasmLimits importPair: WasmImportDescriptor WasmData: typeId: TypeId id: Id name: String mode: WasmDataMode bytes: List WasmTable: typeId: TypeId id: Id name: String limits: WasmLimits elementType: WasmType importPair: WasmImportDescriptor WasmElement: typeId: TypeId id: Id name: String type: WasmType values: List mode: WasmElement.Mode WasmTag: typeId: TypeId id: Id name: String type: WasmFunctionType importPair: WasmImportDescriptor WasmGlobal: typeId: TypeId id: Id name: String type: WasmType isMutable: Boolean init: List importPair: WasmImportDescriptor WasmTypeDeclaration: WasmFunctionType: typeId: TypeId id: Id name: String parameterTypes: List resultTypes: List WasmStructDeclaration: typeId: TypeId id: Id name: String fields: List superType: WasmSymbolReadOnly isFinal: Boolean WasmArrayDeclaration: typeId: TypeId id: Id name: String field: WasmStructFieldDeclaration WasmExport.Function: typeId: TypeId name: String field: WasmFunction WasmExport.Table: typeId: TypeId name: String field: WasmTable WasmExport.Memory: typeId: TypeId name: String field: WasmMemory WasmExport.Global: typeId: TypeId name: String field: WasmGlobal WasmExport.Tag: typeId: TypeId name: String field: WasmTag WasmStructFieldDeclaration: typeId: TypeId name: String type: WasmType isMutable: Boolean SourceLocation: NoLocation: typeId: TypeId Location: typeId: TypeId file: String line: Int column: Int IgnoredLocation: typeId: TypeId file: String line: Int column: Int WasmInstr: // Comments (, etc...) are discarded WasmInstrWithLocation: typeId: TypeId opcode: WasmOp immediates: Immediates location: SourceLocation WasmInstrWithoutLocation: typeId: TypeId immediates: Immediates Notes: 1 - When late binding is accounted for, the size of the binary file will be much bigger, since unique, stable symbols will be stored, which will not be short for sure. 2 - TypeIds can't be extracted from list elements to save space. This is needed to enable polymorphism. 3 - TypeIds are needed even in non-polymorphic structs such as WasmModule and WasmLocal, so that any struct is serializable, independent of other structs. 4 - Since the result binary file is used as a temporary file, backward compatibility is not taken into account (is it?). 5 - WasmExport: kind and keyword fields are constants, thus not included in the serialization process. Hacks: 1 - Currently, when creating a WasmModule struct, both importsInOrder and importedFunctions fields are passed the same list. We can have two different TypeIds for the WasmModule struct. One takes all fields, and the other omits one of the fields on serialization, and duplicates it on deserialization. 2 - The id field of WasmNamedModuleField is not used up until the linkage and may be discarded when serializing. Todo: 1 - Account for alignment 2 - Can we represent WasmOp in a better way, instead of always taking 2 bytes (some variable length encoding)?