{ 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 { TWasmObjSection } TWasmObjSection = class(TObjSection) public SegIdx: Integer; SegOfs: qword; function IsCode: Boolean; function IsData: Boolean; end; { TWasmObjData } TWasmObjData = class(TObjData) private FFuncTypes: array of TWasmFuncType; 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; procedure DeclareFuncType(ft: tai_functype); end; { TWasmObjOutput } TWasmObjOutput = class(tObjOutput) private FWasmSections: array [TWasmSectionID] 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 CopyDynamicArray(src, dest: tdynamicarray; size: QWord); procedure WriteZeros(dest: tdynamicarray; size: QWord); procedure WriteWasmResultType(dest: tdynamicarray; wrt: TWasmResultType); procedure WriteWasmBasicType(dest: tdynamicarray; wbt: TWasmBasicType); 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; {**************************************************************************** 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; end; destructor TWasmObjData.destroy; var i: Integer; begin 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); begin end; procedure TWasmObjData.DeclareFuncType(ft: tai_functype); begin { todo: check and avoid adding duplicates } SetLength(FFuncTypes,Length(FFuncTypes)+1); FFuncTypes[High(FFuncTypes)]:=TWasmFuncType.Create(ft.functype); 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.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 size