{ Copyright (c) 2021 by Nikolay Nikolov Contains the WebAssembly binary module format reader and writer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. **************************************************************************** } unit ogwasm; {$i fpcdefs.inc} interface uses { common } cclasses,globtype, { target } systems,cpubase, { assembler } aasmbase,assemble,aasmcpu, { WebAssembly module format definitions } wasmbase, { output } ogbase, owbase; type TWasmObjSymbolExtraData = class; { TWasmObjSymbol } TWasmObjSymbol = class(TObjSymbol) ImportIndex: Integer; FuncIndex: Integer; SymbolIndex: Integer; AliasOf: string; ExtraData: TWasmObjSymbolExtraData; constructor create(AList:TFPHashObjectList;const AName:string); function ImportOrFuncIndex: Integer; function IsAlias: Boolean; end; { TWasmObjRelocation } TWasmObjRelocation = class(TObjRelocation) end; { TWasmObjSymbolExtraData } TWasmObjSymbolExtraData = class(TFPHashObject) TypeIdx: Integer; ImportModule: string; ImportName: string; Locals: array of TWasmBasicType; constructor Create(HashObjectList: TFPHashObjectList; const s: TSymStr); procedure AddLocal(bastyp: TWasmBasicType); end; { TWasmObjSection } TWasmObjSection = class(TObjSection) public SegIdx: Integer; SegOfs: qword; FileSectionOfs: qword; function IsCode: Boolean; function IsData: Boolean; end; { TWasmObjData } TWasmObjData = class(TObjData) private FFuncTypes: array of TWasmFuncType; FObjSymbolsExtraDataList: TFPHashObjectList; FLastFuncName: string; function is_smart_section(atype:TAsmSectiontype):boolean; function sectionname_gas(atype:TAsmSectiontype;const aname:string;aorder:TAsmSectionOrder):string; public constructor create(const n:string);override; destructor destroy; override; function sectionname(atype:TAsmSectiontype;const aname:string;aorder:TAsmSectionOrder):string;override; procedure writeReloc(Data:TRelocDataInt;len:aword;p:TObjSymbol;Reloctype:TObjRelocationType);override; function AddOrCreateObjSymbolExtraData(const symname:TSymStr): TWasmObjSymbolExtraData; function AddFuncType(wft: TWasmFuncType): integer; procedure DeclareFuncType(ft: tai_functype); procedure DeclareImportModule(aim: tai_import_module); procedure DeclareImportName(ain: tai_import_name); procedure DeclareLocal(al: tai_local); procedure symbolpairdefine(akind: TSymbolPairKind;const asym, avalue: string);override; end; { TWasmObjOutput } TWasmObjOutput = class(tObjOutput) private FData: TWasmObjData; FWasmRelocationCodeTable: tdynamicarray; FWasmRelocationCodeTableEntriesCount: Integer; FWasmRelocationDataTable: tdynamicarray; FWasmRelocationDataTableEntriesCount: Integer; FWasmSymbolTable: tdynamicarray; FWasmSymbolTableEntriesCount: Integer; FWasmSections: array [TWasmSectionID] of tdynamicarray; FWasmCustomSections: array [TWasmCustomSectionType] of tdynamicarray; FWasmLinkingSubsections: array [low(TWasmLinkingSubsectionType)..high(TWasmLinkingSubsectionType)] of tdynamicarray; procedure WriteUleb(d: tdynamicarray; v: uint64); procedure WriteUleb(w: TObjectWriter; v: uint64); procedure WriteSleb(d: tdynamicarray; v: int64); procedure WriteByte(d: tdynamicarray; b: byte); procedure WriteName(d: tdynamicarray; const s: string); procedure WriteWasmSection(wsid: TWasmSectionID); procedure WriteWasmCustomSection(wcst: TWasmCustomSectionType); procedure CopyDynamicArray(src, dest: tdynamicarray; size: QWord); procedure WriteZeros(dest: tdynamicarray; size: QWord); procedure WriteWasmResultType(dest: tdynamicarray; wrt: TWasmResultType); procedure WriteWasmBasicType(dest: tdynamicarray; wbt: TWasmBasicType); function IsExternalFunction(sym: TObjSymbol): Boolean; procedure WriteFunctionLocals(dest: tdynamicarray; ed: TWasmObjSymbolExtraData); procedure WriteFunctionCode(dest: tdynamicarray; objsym: TObjSymbol); procedure WriteSymbolTable; procedure WriteRelocationCodeTable(CodeSectionIndex: Integer); procedure WriteRelocationDataTable(DataSectionIndex: Integer); procedure WriteLinkingSubsection(wlst: TWasmLinkingSubsectionType); procedure DoRelocations; procedure WriteRelocations; protected function writeData(Data:TObjData):boolean;override; public constructor create(AWriter:TObjectWriter);override; destructor destroy;override; end; { TWasmAssembler } TWasmAssembler = class(tinternalassembler) constructor create(info: pasminfo; smart:boolean);override; end; implementation uses verbose; procedure WriteUleb5(d: tdynamicarray; v: uint64); var b: byte; i: Integer; begin for i:=1 to 5 do begin b:=byte(v) and 127; v:=v shr 7; if i<>5 then b:=b or 128; d.write(b,1); end; end; procedure WriteUleb5(d: tobjsection; v: uint64); var b: byte; i: Integer; begin for i:=1 to 5 do begin b:=byte(v) and 127; v:=v shr 7; if i<>5 then b:=b or 128; d.write(b,1); end; end; procedure WriteSleb5(d: tdynamicarray; v: int64); var b: byte; i: Integer; begin for i:=1 to 5 do begin b:=byte(v) and 127; v:=SarInt64(v,7); if i<>5 then b:=b or 128; d.write(b,1); end; end; procedure WriteSleb5(d: tobjsection; v: int64); var b: byte; i: Integer; begin for i:=1 to 5 do begin b:=byte(v) and 127; v:=SarInt64(v,7); if i<>5 then b:=b or 128; d.write(b,1); end; end; function ReadUleb(d: tdynamicarray): uint64; var b: byte; shift:integer; begin b:=0; result:=0; shift:=0; repeat d.read(b,1); result:=result or (uint64(b and 127) shl shift); inc(shift,7); until (b and 128)=0; end; function ReadSleb(d: tdynamicarray): int64; var b: byte; shift:integer; begin b:=0; result:=0; shift:=0; repeat d.read(b,1); result:=result or (uint64(b and 127) shl shift); inc(shift,7); until (b and 128)=0; if (b and 64)<>0 then result:=result or (high(uint64) shl shift); end; procedure AddSleb5(d: tdynamicarray; v: int64); var q: Int64; p: LongWord; begin p:=d.Pos; q:=ReadSleb(d); q:=q+v; d.seek(p); WriteSleb5(d,q); end; procedure AddUleb5(d: tdynamicarray; v: int64); var q: UInt64; p: LongWord; begin p:=d.Pos; q:=ReadUleb(d); q:=q+v; d.seek(p); WriteUleb5(d,q); end; {**************************************************************************** TWasmObjSymbol ****************************************************************************} constructor TWasmObjSymbol.create(AList: TFPHashObjectList; const AName: string); begin inherited create(AList,AName); ImportIndex:=-1; FuncIndex:=-1; SymbolIndex:=-1; AliasOf:=''; ExtraData:=nil; end; function TWasmObjSymbol.ImportOrFuncIndex: Integer; begin if ImportIndex<>-1 then result:=ImportIndex else if FuncIndex<>-1 then result:=FuncIndex else internalerror(2021092601); end; function TWasmObjSymbol.IsAlias: Boolean; begin result:=AliasOf<>''; end; {**************************************************************************** TWasmObjSymbolExtraData ****************************************************************************} constructor TWasmObjSymbolExtraData.Create(HashObjectList: TFPHashObjectList; const s: TSymStr); begin inherited Create(HashObjectList,s); TypeIdx:=-1; end; procedure TWasmObjSymbolExtraData.AddLocal(bastyp: TWasmBasicType); begin SetLength(Locals,Length(Locals)+1); Locals[High(Locals)]:=bastyp; end; {**************************************************************************** TWasmObjSection ****************************************************************************} function TWasmObjSection.IsCode: Boolean; const CodePrefix = '.text'; begin result:=(Length(Name)>=Length(CodePrefix)) and (Copy(Name,1,Length(CodePrefix))=CodePrefix); end; function TWasmObjSection.IsData: Boolean; begin result:=not IsCode; end; {**************************************************************************** TWasmObjData ****************************************************************************} function TWasmObjData.is_smart_section(atype: TAsmSectiontype): boolean; begin { For bss we need to set some flags that are target dependent, it is easier to disable it for smartlinking. It doesn't take up filespace } result:=not(target_info.system in systems_darwin) and create_smartlink_sections and (atype<>sec_toc) and (atype<>sec_user) and { on embedded systems every byte counts, so smartlink bss too } ((atype<>sec_bss) or (target_info.system in (systems_embedded+systems_freertos))); end; function TWasmObjData.sectionname_gas(atype: TAsmSectiontype; const aname: string; aorder: TAsmSectionOrder): string; const secnames : array[TAsmSectiontype] of string[length('__DATA, __datacoal_nt,coalesced')] = ('','', '.text', '.data', { why doesn't .rodata work? (FK) } { sometimes we have to create a data.rel.ro instead of .rodata, e.g. for } { vtables (and anything else containing relocations), otherwise those are } { not relocated properly on e.g. linux/ppc64. g++ generates there for a } { vtable for a class called Window: } { .section .data.rel.ro._ZTV6Window,"awG",@progbits,_ZTV6Window,comdat } { TODO: .data.ro not yet working} {$if defined(arm) or defined(riscv64) or defined(powerpc)} '.rodata', {$else defined(arm) or defined(riscv64) or defined(powerpc)} '.data', {$endif defined(arm) or defined(riscv64) or defined(powerpc)} '.rodata', '.bss', '.threadvar', '.pdata', '', { stubs } '__DATA,__nl_symbol_ptr', '__DATA,__la_symbol_ptr', '__DATA,__mod_init_func', '__DATA,__mod_term_func', '.stab', '.stabstr', '.idata$2','.idata$4','.idata$5','.idata$6','.idata$7','.edata', '.eh_frame', '.debug_frame','.debug_info','.debug_line','.debug_abbrev','.debug_aranges','.debug_ranges', '.fpc', '.toc', '.init', '.fini', '.objc_class', '.objc_meta_class', '.objc_cat_cls_meth', '.objc_cat_inst_meth', '.objc_protocol', '.objc_string_object', '.objc_cls_meth', '.objc_inst_meth', '.objc_cls_refs', '.objc_message_refs', '.objc_symbols', '.objc_category', '.objc_class_vars', '.objc_instance_vars', '.objc_module_info', '.objc_class_names', '.objc_meth_var_types', '.objc_meth_var_names', '.objc_selector_strs', '.objc_protocol_ext', '.objc_class_ext', '.objc_property', '.objc_image_info', '.objc_cstring_object', '.objc_sel_fixup', '__DATA,__objc_data', '__DATA,__objc_const', '.objc_superrefs', '__DATA, __datacoal_nt,coalesced', '.objc_classlist', '.objc_nlclasslist', '.objc_catlist', '.obcj_nlcatlist', '.objc_protolist', '.stack', '.heap', '.gcc_except_table', '.ARM.attributes' ); var sep : string[3]; secname : string; begin secname:=secnames[atype]; if (atype=sec_fpc) and (Copy(aname,1,3)='res') then begin result:=secname+'.'+aname; exit; end; if atype=sec_threadvar then begin if (target_info.system in (systems_windows+systems_wince)) then secname:='.tls' else if (target_info.system in systems_linux) then secname:='.tbss'; end; { go32v2 stub only loads .text and .data sections, and allocates space for .bss. Thus, data which normally goes into .rodata and .rodata_norel sections must end up in .data section } if (atype in [sec_rodata,sec_rodata_norel]) and (target_info.system in [system_i386_go32v2,system_m68k_palmos]) then secname:='.data'; { Windows correctly handles reallocations in readonly sections } if (atype=sec_rodata) and (target_info.system in systems_all_windows+systems_nativent-[system_i8086_win16]) then secname:='.rodata'; { section type user gives the user full controll on the section name } if atype=sec_user then secname:=aname; if is_smart_section(atype) and (aname<>'') then begin case aorder of secorder_begin : sep:='.b_'; secorder_end : sep:='.z_'; else sep:='.n_'; end; result:=secname+sep+aname end else result:=secname; end; constructor TWasmObjData.create(const n: string); begin inherited; CObjSection:=TWasmObjSection; CObjSymbol:=TWasmObjSymbol; FObjSymbolsExtraDataList:=TFPHashObjectList.Create; end; destructor TWasmObjData.destroy; var i: Integer; begin FObjSymbolsExtraDataList.Free; for i:=low(FFuncTypes) to high(FFuncTypes) do begin FFuncTypes[i].free; FFuncTypes[i]:=nil; end; inherited destroy; end; function TWasmObjData.sectionname(atype: TAsmSectiontype; const aname: string; aorder: TAsmSectionOrder): string; begin if (atype=sec_fpc) or (atype=sec_threadvar) then atype:=sec_data; Result:=sectionname_gas(atype, aname, aorder); end; procedure TWasmObjData.writeReloc(Data: TRelocDataInt; len: aword; p: TObjSymbol; Reloctype: TObjRelocationType); const leb_zero: array[0..4] of byte=($80,$80,$80,$80,$00); var objreloc: TWasmObjRelocation; begin if CurrObjSec=nil then internalerror(200403072); objreloc:=nil; case Reloctype of RELOC_FUNCTION_INDEX_LEB: begin if Data<>0 then internalerror(2021092502); if len<>5 then internalerror(2021092503); if not assigned(p) then internalerror(2021092504); objreloc:=TWasmObjRelocation.CreateSymbol(CurrObjSec.Size,p,Reloctype); CurrObjSec.ObjRelocations.Add(objreloc); writebytes(leb_zero,5); end; RELOC_MEMORY_ADDR_LEB, RELOC_MEMORY_ADDR_SLEB: begin if (Reloctype=RELOC_MEMORY_ADDR_LEB) and (Data<0) then internalerror(2021092602); if len<>5 then internalerror(2021092503); if not assigned(p) then internalerror(2021092504); objreloc:=TWasmObjRelocation.CreateSymbol(CurrObjSec.Size,p,Reloctype); CurrObjSec.ObjRelocations.Add(objreloc); if RelocType=RELOC_MEMORY_ADDR_LEB then WriteUleb5(CurrObjSec,Data) else WriteSleb5(CurrObjSec,Data); end; RELOC_ABSOLUTE: begin if len<>4 then internalerror(2021092607); if not assigned(p) then internalerror(2021092608); objreloc:=TWasmObjRelocation.CreateSymbol(CurrObjSec.Size,p,Reloctype); CurrObjSec.ObjRelocations.Add(objreloc); Data:=NtoLE(Data); writebytes(Data,4); end; else internalerror(2021092501); end; end; function TWasmObjData.AddOrCreateObjSymbolExtraData(const symname: TSymStr): TWasmObjSymbolExtraData; begin result:=TWasmObjSymbolExtraData(FObjSymbolsExtraDataList.Find(symname)); if not assigned(result) then result:=TWasmObjSymbolExtraData.Create(FObjSymbolsExtraDataList,symname); end; function TWasmObjData.AddFuncType(wft: TWasmFuncType): integer; var i: Integer; begin for i:=low(FFuncTypes) to high(FFuncTypes) do if wft.Equals(FFuncTypes[i]) then exit(i); result:=Length(FFuncTypes); SetLength(FFuncTypes,result+1); FFuncTypes[result]:=TWasmFuncType.Create(wft); end; procedure TWasmObjData.DeclareFuncType(ft: tai_functype); var i: Integer; ObjSymExtraData: TWasmObjSymbolExtraData; begin FLastFuncName:=ft.funcname; i:=AddFuncType(ft.functype); ObjSymExtraData:=AddOrCreateObjSymbolExtraData(ft.funcname); ObjSymExtraData.TypeIdx:=i; end; procedure TWasmObjData.DeclareImportModule(aim: tai_import_module); var ObjSymExtraData: TWasmObjSymbolExtraData; begin ObjSymExtraData:=AddOrCreateObjSymbolExtraData(aim.symname); ObjSymExtraData.ImportModule:=aim.importmodule; end; procedure TWasmObjData.DeclareImportName(ain: tai_import_name); var ObjSymExtraData: TWasmObjSymbolExtraData; begin ObjSymExtraData:=AddOrCreateObjSymbolExtraData(ain.symname); ObjSymExtraData.ImportName:=ain.importname; end; procedure TWasmObjData.DeclareLocal(al: tai_local); var ObjSymExtraData: TWasmObjSymbolExtraData; begin ObjSymExtraData:=TWasmObjSymbolExtraData(FObjSymbolsExtraDataList.Find(FLastFuncName)); ObjSymExtraData.AddLocal(al.bastyp); end; procedure TWasmObjData.symbolpairdefine(akind: TSymbolPairKind; const asym, avalue: string); var valsym: TObjSymbol; aliassym: TWasmObjSymbol; begin valsym:=CreateSymbol(avalue); aliassym:=TWasmObjSymbol(symboldefine(asym,valsym.bind,valsym.typ)); aliassym.AliasOf:=valsym.Name; end; {**************************************************************************** TWasmObjOutput ****************************************************************************} procedure TWasmObjOutput.WriteUleb(d: tdynamicarray; v: uint64); var b: byte; begin repeat b:=byte(v) and 127; v:=v shr 7; if v<>0 then b:=b or 128; d.write(b,1); until v=0; end; procedure TWasmObjOutput.WriteUleb(w: TObjectWriter; v: uint64); var b: byte; begin repeat b:=byte(v) and 127; v:=v shr 7; if v<>0 then b:=b or 128; w.write(b,1); until v=0; end; procedure TWasmObjOutput.WriteSleb(d: tdynamicarray; v: int64); var b: byte; Done: Boolean=false; begin repeat b:=byte(v) and 127; v:=SarInt64(v,7); if ((v=0) and ((b and 64)=0)) or ((v=-1) and ((b and 64)<>0)) then Done:=true else b:=b or 128; d.write(b,1); until Done; end; procedure TWasmObjOutput.WriteByte(d: tdynamicarray; b: byte); begin d.write(b,1); end; procedure TWasmObjOutput.WriteName(d: tdynamicarray; const s: string); begin WriteUleb(d,Length(s)); d.writestr(s); end; procedure TWasmObjOutput.WriteWasmSection(wsid: TWasmSectionID); var b: byte; begin b:=ord(wsid); Writer.write(b,1); WriteUleb(Writer,FWasmSections[wsid].size); Writer.writearray(FWasmSections[wsid]); end; procedure TWasmObjOutput.WriteWasmCustomSection(wcst: TWasmCustomSectionType); var b: byte; begin b:=0; Writer.write(b,1); WriteUleb(Writer,FWasmCustomSections[wcst].size); Writer.writearray(FWasmCustomSections[wcst]); end; procedure TWasmObjOutput.CopyDynamicArray(src, dest: tdynamicarray; size: QWord); var buf: array [0..4095] of byte; bs: Integer; begin while size>0 do begin if size0 do begin if sizenil); end; procedure TWasmObjOutput.WriteFunctionLocals(dest: tdynamicarray; ed: TWasmObjSymbolExtraData); var i, rle_entries, cnt: Integer; lasttype: TWasmBasicType; begin if Length(ed.Locals)=0 then begin WriteUleb(dest,0); exit; end; rle_entries:=1; for i:=low(ed.Locals)+1 to high(ed.Locals) do if ed.Locals[i]<>ed.Locals[i-1] then inc(rle_entries); WriteUleb(dest,rle_entries); lasttype:=ed.Locals[Low(ed.Locals)]; cnt:=1; for i:=low(ed.Locals)+1 to high(ed.Locals) do if ed.Locals[i]=ed.Locals[i-1] then inc(cnt) else begin WriteUleb(dest,cnt); WriteWasmBasicType(dest,lasttype); lasttype:=ed.Locals[i]; cnt:=1; end; WriteUleb(dest,cnt); WriteWasmBasicType(dest,lasttype); end; procedure TWasmObjOutput.WriteFunctionCode(dest: tdynamicarray; objsym: TObjSymbol); var encoded_locals: tdynamicarray; ObjSymExtraData: TWasmObjSymbolExtraData; codelen: LongWord; ObjSection: TWasmObjSection; codeexprlen: QWord; begin ObjSymExtraData:=TWasmObjSymbolExtraData(FData.FObjSymbolsExtraDataList.Find(objsym.Name)); ObjSection:=TWasmObjSection(objsym.objsection); ObjSection.Data.seek(objsym.address); codeexprlen:=ObjSection.Size-objsym.address; encoded_locals:=tdynamicarray.Create(64); WriteFunctionLocals(encoded_locals,ObjSymExtraData); codelen:=encoded_locals.size+codeexprlen+1; WriteUleb(dest,codelen); encoded_locals.seek(0); CopyDynamicArray(encoded_locals,dest,encoded_locals.size); ObjSection.FileSectionOfs:=dest.size-objsym.offset; CopyDynamicArray(ObjSection.Data,dest,codeexprlen); WriteByte(dest,$0B); encoded_locals.Free; end; procedure TWasmObjOutput.WriteSymbolTable; begin WriteUleb(FWasmLinkingSubsections[WASM_SYMBOL_TABLE],FWasmSymbolTableEntriesCount); FWasmSymbolTable.seek(0); CopyDynamicArray(FWasmSymbolTable,FWasmLinkingSubsections[WASM_SYMBOL_TABLE],FWasmSymbolTable.size); end; procedure TWasmObjOutput.WriteRelocationCodeTable(CodeSectionIndex: Integer); begin WriteUleb(FWasmCustomSections[wcstRelocCode],CodeSectionIndex); WriteUleb(FWasmCustomSections[wcstRelocCode],FWasmRelocationCodeTableEntriesCount); FWasmRelocationCodeTable.seek(0); CopyDynamicArray(FWasmRelocationCodeTable,FWasmCustomSections[wcstRelocCode],FWasmRelocationCodeTable.size); end; procedure TWasmObjOutput.WriteRelocationDataTable(DataSectionIndex: Integer); begin WriteUleb(FWasmCustomSections[wcstRelocData],DataSectionIndex); WriteUleb(FWasmCustomSections[wcstRelocData],FWasmRelocationDataTableEntriesCount); FWasmRelocationDataTable.seek(0); CopyDynamicArray(FWasmRelocationDataTable,FWasmCustomSections[wcstRelocData],FWasmRelocationDataTable.size); end; procedure TWasmObjOutput.WriteLinkingSubsection(wlst: TWasmLinkingSubsectionType); begin if FWasmLinkingSubsections[wlst].size>0 then begin WriteByte(FWasmCustomSections[wcstLinking],Ord(wlst)); WriteUleb(FWasmCustomSections[wcstLinking],FWasmLinkingSubsections[wlst].size); FWasmLinkingSubsections[wlst].seek(0); CopyDynamicArray(FWasmLinkingSubsections[wlst],FWasmCustomSections[wcstLinking],FWasmLinkingSubsections[wlst].size); end; end; procedure TWasmObjOutput.DoRelocations; var si, ri: Integer; objsec: TWasmObjSection; objrel: TWasmObjRelocation; begin for si:=0 to FData.ObjSectionList.Count-1 do begin objsec:=TWasmObjSection(FData.ObjSectionList[si]); for ri:=0 to objsec.ObjRelocations.Count-1 do begin objrel:=TWasmObjRelocation(objsec.ObjRelocations[ri]); case objrel.typ of RELOC_FUNCTION_INDEX_LEB: begin if not assigned(objrel.symbol) then internalerror(2021092509); objsec.Data.seek(objrel.DataOffset); WriteUleb5(objsec.Data,TWasmObjSymbol(objrel.symbol).ImportOrFuncIndex); end; RELOC_MEMORY_ADDR_SLEB: begin if not assigned(objrel.symbol) then internalerror(2021092605); if objrel.symbol.bind<>AB_EXTERNAL then begin objsec.Data.seek(objrel.DataOffset); AddSleb5(objsec.Data,objrel.symbol.offset+TWasmObjSection(objrel.symbol.objsection).SegOfs); end; end; RELOC_MEMORY_ADDR_LEB: begin if not assigned(objrel.symbol) then internalerror(2021092606); if objrel.symbol.bind<>AB_EXTERNAL then begin objsec.Data.seek(objrel.DataOffset); AddUleb5(objsec.Data,objrel.symbol.offset+TWasmObjSection(objrel.symbol.objsection).SegOfs); end; end; RELOC_ABSOLUTE: ; else internalerror(2021092510); end; end; end; end; procedure TWasmObjOutput.WriteRelocations; var si, ri: Integer; objsec: TWasmObjSection; objrel: TWasmObjRelocation; relout: tdynamicarray; relcount: PInteger; begin for si:=0 to FData.ObjSectionList.Count-1 do begin objsec:=TWasmObjSection(FData.ObjSectionList[si]); if objsec.IsCode then begin relout:=FWasmRelocationCodeTable; relcount:=@FWasmRelocationCodeTableEntriesCount; end else begin relout:=FWasmRelocationDataTable; relcount:=@FWasmRelocationDataTableEntriesCount; end; for ri:=0 to objsec.ObjRelocations.Count-1 do begin objrel:=TWasmObjRelocation(objsec.ObjRelocations[ri]); case objrel.typ of RELOC_FUNCTION_INDEX_LEB: begin if not assigned(objrel.symbol) then internalerror(2021092508); Inc(relcount^); WriteByte(relout,Ord(R_WASM_FUNCTION_INDEX_LEB)); WriteUleb(relout,objrel.DataOffset+objsec.FileSectionOfs); WriteUleb(relout,TWasmObjSymbol(objrel.symbol).SymbolIndex); end; RELOC_MEMORY_ADDR_LEB: begin if not assigned(objrel.symbol) then internalerror(2021092603); Inc(relcount^); WriteByte(relout,Ord(R_WASM_MEMORY_ADDR_LEB)); WriteUleb(relout,objrel.DataOffset+objsec.FileSectionOfs); WriteUleb(relout,TWasmObjSymbol(objrel.symbol).SymbolIndex); WriteUleb(relout,0); { addend to add to the address } end; RELOC_MEMORY_ADDR_SLEB: begin if not assigned(objrel.symbol) then internalerror(2021092604); Inc(relcount^); WriteByte(relout,Ord(R_WASM_MEMORY_ADDR_SLEB)); WriteUleb(relout,objrel.DataOffset+objsec.FileSectionOfs); WriteUleb(relout,TWasmObjSymbol(objrel.symbol).SymbolIndex); WriteUleb(relout,0); { addend to add to the address } end; RELOC_ABSOLUTE: begin if not assigned(objrel.symbol) then internalerror(2021092604); if IsExternalFunction(objrel.symbol) or (objrel.symbol.typ=AT_FUNCTION) then begin Inc(relcount^); WriteByte(relout,Ord(R_WASM_TABLE_INDEX_I32)); WriteUleb(relout,objrel.DataOffset+objsec.FileSectionOfs); WriteUleb(relout,TWasmObjSymbol(objrel.symbol).SymbolIndex); end else internalerror(2021092609); end; else internalerror(2021092507); end; end; end; end; function TWasmObjOutput.writeData(Data:TObjData):boolean; var i: Integer; objsec: TWasmObjSection; segment_count: Integer = 0; cur_seg_ofs: qword = 0; types_count, imports_count, NextImportFunctionIndex, NextFunctionIndex: Integer; import_functions_count: Integer = 0; functions_count: Integer = 0; objsym: TWasmObjSymbol; cust_sec: TWasmCustomSectionType; begin FData:=TWasmObjData(Data); { each custom sections starts with its name } for cust_sec in TWasmCustomSectionType do WriteName(FWasmCustomSections[cust_sec],WasmCustomSectionName[cust_sec]); WriteUleb(FWasmCustomSections[wcstLinking],2); { linking metadata version } for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); if IsExternalFunction(objsym) then Inc(import_functions_count); if (objsym.typ=AT_FUNCTION) and not objsym.IsAlias then Inc(functions_count); end; types_count:=Length(FData.FFuncTypes); WriteUleb(FWasmSections[wsiType],types_count); for i:=0 to types_count-1 do with FData.FFuncTypes[i] do begin WriteByte(FWasmSections[wsiType],$60); WriteWasmResultType(FWasmSections[wsiType],params); WriteWasmResultType(FWasmSections[wsiType],results); end; for i:=0 to Data.ObjSectionList.Count-1 do begin objsec:=TWasmObjSection(Data.ObjSectionList[i]); if objsec.IsCode then objsec.SegIdx:=-1 else begin objsec.SegIdx:=segment_count; objsec.SegOfs:=cur_seg_ofs; Inc(segment_count); Inc(cur_seg_ofs,objsec.Size); end; end; WriteUleb(FWasmSections[wsiData],segment_count); WriteUleb(FWasmSections[wsiDataCount],segment_count); WriteUleb(FWasmLinkingSubsections[WASM_SEGMENT_INFO],segment_count); for i:=0 to Data.ObjSectionList.Count-1 do begin objsec:=TWasmObjSection(Data.ObjSectionList[i]); if objsec.IsData then begin WriteName(FWasmLinkingSubsections[WASM_SEGMENT_INFO],objsec.Name); WriteUleb(FWasmLinkingSubsections[WASM_SEGMENT_INFO],BsrQWord(objsec.SecAlign)); WriteUleb(FWasmLinkingSubsections[WASM_SEGMENT_INFO],0); { flags } WriteByte(FWasmSections[wsiData],0); WriteByte(FWasmSections[wsiData],$41); WriteSleb(FWasmSections[wsiData],objsec.SegOfs); WriteByte(FWasmSections[wsiData],$0b); WriteUleb(FWasmSections[wsiData],objsec.Size); objsec.FileSectionOfs:=FWasmSections[wsiData].size; if oso_Data in objsec.SecOptions then begin objsec.Data.seek(0); CopyDynamicArray(objsec.Data,FWasmSections[wsiData],objsec.Size); end else begin WriteZeros(FWasmSections[wsiData],objsec.Size); end; end; end; imports_count:=3+import_functions_count; WriteUleb(FWasmSections[wsiImport],imports_count); { import[0] } WriteName(FWasmSections[wsiImport],'env'); WriteName(FWasmSections[wsiImport],'__linear_memory'); WriteByte(FWasmSections[wsiImport],$02); { mem } WriteByte(FWasmSections[wsiImport],$00); { min } WriteUleb(FWasmSections[wsiImport],1); { 1 page } { import[1] } WriteName(FWasmSections[wsiImport],'env'); WriteName(FWasmSections[wsiImport],'__stack_pointer'); WriteByte(FWasmSections[wsiImport],$03); { global } WriteByte(FWasmSections[wsiImport],$7F); { i32 } WriteByte(FWasmSections[wsiImport],$01); { var } { import[2]..import[imports_count-2] } NextImportFunctionIndex:=0; for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); if IsExternalFunction(objsym) then begin objsym.ImportIndex:=NextImportFunctionIndex; Inc(NextImportFunctionIndex); objsym.ExtraData:=TWasmObjSymbolExtraData(FData.FObjSymbolsExtraDataList.Find(objsym.Name)); if objsym.ExtraData.ImportModule<>'' then WriteName(FWasmSections[wsiImport],objsym.ExtraData.ImportModule) else WriteName(FWasmSections[wsiImport],'env'); WriteName(FWasmSections[wsiImport],objsym.Name); WriteByte(FWasmSections[wsiImport],$00); { func } WriteUleb(FWasmSections[wsiImport],TWasmObjSymbolExtraData(FData.FObjSymbolsExtraDataList.Find(objsym.Name)).TypeIdx); end; end; { import[imports_count-1] } WriteName(FWasmSections[wsiImport],'env'); WriteName(FWasmSections[wsiImport],'__indirect_function_table'); WriteByte(FWasmSections[wsiImport],$01); { table } WriteByte(FWasmSections[wsiImport],$70); { funcref } WriteByte(FWasmSections[wsiImport],$00); { min } WriteUleb(FWasmSections[wsiImport],1); { 1 } WriteUleb(FWasmSections[wsiFunction],functions_count); NextFunctionIndex:=NextImportFunctionIndex; for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); if (objsym.typ=AT_FUNCTION) and not objsym.IsAlias then begin objsym.FuncIndex:=NextFunctionIndex; Inc(NextFunctionIndex); WriteUleb(FWasmSections[wsiFunction],TWasmObjSymbolExtraData(FData.FObjSymbolsExtraDataList.Find(objsym.Name)).TypeIdx); end; end; for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); if IsExternalFunction(objsym) then begin objsym.SymbolIndex:=FWasmSymbolTableEntriesCount; Inc(FWasmSymbolTableEntriesCount); WriteByte(FWasmSymbolTable,Ord(SYMTAB_FUNCTION)); if objsym.ExtraData.ImportModule<>'' then begin WriteUleb(FWasmSymbolTable,WASM_SYM_UNDEFINED or WASM_SYM_EXPLICIT_NAME); WriteUleb(FWasmSymbolTable,objsym.ImportIndex); WriteName(FWasmSymbolTable,objsym.Name); end else begin WriteUleb(FWasmSymbolTable,WASM_SYM_UNDEFINED); WriteUleb(FWasmSymbolTable,objsym.ImportIndex); end; end else if objsym.typ=AT_FUNCTION then begin objsym.SymbolIndex:=FWasmSymbolTableEntriesCount; Inc(FWasmSymbolTableEntriesCount); WriteByte(FWasmSymbolTable,Ord(SYMTAB_FUNCTION)); if objsym.IsAlias then begin WriteUleb(FWasmSymbolTable,WASM_SYM_EXPLICIT_NAME or WASM_SYM_NO_STRIP); WriteUleb(FWasmSymbolTable,TWasmObjSymbol(Data.ObjSymbolList.Find(objsym.AliasOf)).FuncIndex); end else begin WriteUleb(FWasmSymbolTable,0); WriteUleb(FWasmSymbolTable,objsym.FuncIndex); end; WriteName(FWasmSymbolTable,objsym.Name); end else if (objsym.typ=AT_DATA) or ((objsym.typ=AT_NONE) and (objsym.bind=AB_EXTERNAL)) then begin objsym.SymbolIndex:=FWasmSymbolTableEntriesCount; Inc(FWasmSymbolTableEntriesCount); WriteByte(FWasmSymbolTable,Ord(SYMTAB_DATA)); if objsym.bind=AB_GLOBAL then WriteUleb(FWasmSymbolTable,0) else if objsym.bind=AB_LOCAL then WriteUleb(FWasmSymbolTable,WASM_SYM_BINDING_LOCAL) else if objsym.bind=AB_EXTERNAL then WriteUleb(FWasmSymbolTable,WASM_SYM_UNDEFINED) else internalerror(2021092506); WriteName(FWasmSymbolTable,objsym.Name); if objsym.bind<>AB_EXTERNAL then begin WriteUleb(FWasmSymbolTable,TWasmObjSection(objsym.objsection).SegIdx); WriteUleb(FWasmSymbolTable,objsym.offset); WriteUleb(FWasmSymbolTable,objsym.size); end; end; end; DoRelocations; WriteUleb(FWasmSections[wsiCode],functions_count); for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); if (objsym.typ=AT_FUNCTION) and not objsym.IsAlias then WriteFunctionCode(FWasmSections[wsiCode],objsym); end; WriteRelocations; WriteSymbolTable; WriteLinkingSubsection(WASM_SYMBOL_TABLE); WriteLinkingSubsection(WASM_SEGMENT_INFO); WriteRelocationCodeTable(4); { code section is section #4 } WriteRelocationDataTable(5); { code section is section #5 } Writer.write(WasmModuleMagic,SizeOf(WasmModuleMagic)); Writer.write(WasmVersion,SizeOf(WasmVersion)); WriteWasmSection(wsiType); { section #0 } WriteWasmSection(wsiImport); { section #1 } WriteWasmSection(wsiFunction); { section #2 } WriteWasmSection(wsiDataCount); { section #3 } WriteWasmSection(wsiCode); { section #4 } WriteWasmSection(wsiData); { section #5 } WriteWasmCustomSection(wcstLinking); { section #6 } WriteWasmCustomSection(wcstRelocCode); { section #7 } WriteWasmCustomSection(wcstRelocData); { section #8 } Writeln('ObjSymbolList:'); for i:=0 to Data.ObjSymbolList.Count-1 do begin objsym:=TWasmObjSymbol(Data.ObjSymbolList[i]); Write(objsym.Name, ' bind=', objsym.Bind, ' typ=', objsym.typ, ' address=', objsym.address, ' objsection='); if assigned(objsym.objsection) then Write(objsym.objsection.Name) else Write('nil'); Writeln; end; Writeln('ObjSectionList:'); for i:=0 to Data.ObjSectionList.Count-1 do begin objsec:=TWasmObjSection(Data.ObjSectionList[i]); Writeln(objsec.Name, ' IsCode=', objsec.IsCode, ' IsData=', objsec.IsData, ' Size=', objsec.Size, ' MemPos=', objsec.MemPos, ' DataPos=', objsec.DataPos, ' SegIdx=', objsec.SegIdx); end; result:=true; end; constructor TWasmObjOutput.create(AWriter: TObjectWriter); var i: TWasmSectionID; j: TWasmCustomSectionType; k: TWasmLinkingSubsectionType; begin inherited; cobjdata:=TWasmObjData; for i in TWasmSectionID do FWasmSections[i] := tdynamicarray.create(SectionDataMaxGrow); for j in TWasmCustomSectionType do FWasmCustomSections[j] := tdynamicarray.create(SectionDataMaxGrow); for k:=low(TWasmLinkingSubsectionType) to high(TWasmLinkingSubsectionType) do FWasmLinkingSubsections[k] := tdynamicarray.create(SectionDataMaxGrow); FWasmSymbolTable:=tdynamicarray.create(SectionDataMaxGrow); FWasmSymbolTableEntriesCount:=0; FWasmRelocationCodeTable:=tdynamicarray.create(SectionDataMaxGrow); FWasmRelocationCodeTableEntriesCount:=0; FWasmRelocationDataTable:=tdynamicarray.create(SectionDataMaxGrow); FWasmRelocationDataTableEntriesCount:=0; end; destructor TWasmObjOutput.destroy; var i: TWasmSectionID; j: TWasmCustomSectionType; k: TWasmLinkingSubsectionType; begin for i in TWasmSectionID do FWasmSections[i].Free; for j in TWasmCustomSectionType do FWasmCustomSections[j].Free; for k:=low(TWasmLinkingSubsectionType) to high(TWasmLinkingSubsectionType) do FWasmLinkingSubsections[k].Free; FWasmSymbolTable.Free; FWasmRelocationCodeTable.Free; FWasmRelocationDataTable.Free; inherited destroy; end; {**************************************************************************** TWasmAssembler ****************************************************************************} constructor TWasmAssembler.Create(info: pasminfo; smart:boolean); begin inherited; CObjOutput:=TWasmObjOutput; end; {***************************************************************************** Initialize *****************************************************************************} {$ifdef wasm32} const as_wasm32_wasm_info : tasminfo = ( id : as_wasm32_wasm; idtxt : 'OMF'; asmbin : ''; asmcmd : ''; supported_targets : [system_wasm32_embedded,system_wasm32_wasi]; flags : [af_outputbinary,af_smartlink_sections]; labelprefix : '..@'; labelmaxlen : -1; comment : '; '; dollarsign: '$'; ); {$endif wasm32} initialization {$ifdef wasm32} RegisterAssembler(as_wasm32_wasm_info,TWasmAssembler); {$endif wasm32} end.