diff --git a/compiler/aarch64/agcpugas.pas b/compiler/aarch64/agcpugas.pas index 523e279f53..131793c709 100644 --- a/compiler/aarch64/agcpugas.pas +++ b/compiler/aarch64/agcpugas.pas @@ -30,7 +30,7 @@ unit agcpugas; uses globtype,systems, - aasmtai,aasmbase, + aasmtai,aasmdata,aasmbase, assemble,aggas, cpubase,cpuinfo; @@ -50,10 +50,12 @@ unit agcpugas; TAArch64ClangGASAssembler=class(TGNUassembler) private function TargetStr:String; + procedure TransformSEHDirectives(list:TAsmList); protected function sectionflags(secflags:TSectionFlags):string;override; public function MakeCmdLine:TCmdStr; override; + procedure WriteAsmList; override; constructor CreateWithWriter(info: pasminfo; wr: TExternalAssemblerOutputFile; freewriter, smart: boolean); override; end; @@ -72,7 +74,7 @@ unit agcpugas; implementation uses - cutils,globals,verbose, + cutils,cclasses,globals,verbose, aasmcpu, itcpugas, cgbase,cgutils; @@ -121,6 +123,442 @@ unit agcpugas; end; + procedure TAArch64ClangGASAssembler.TransformSEHDirectives(list:TAsmList); + + function convert_unwinddata(list:tasmlist):tdynamicarray; + + procedure check_offset(ofs,max:dword); + begin + if ((ofs and $7)<>0) or (ofs>max) then + internalerror(2020041210); + end; + + procedure check_reg(reg:tregister;rt:TRegisterType;min:TSuperRegister); + begin + if (getregtype(reg)<>rt) or (getsupreg(reg)<min) then + internalerror(2020041211); + end; + + procedure writebyte(b:byte); inline; + begin + result.write(b,sizeof(b)); + end; + + procedure writeword(w:word); + begin + w:=NtoBE(w); + result.write(w,sizeof(w)); + end; + + procedure writedword(dw:dword); + begin + dw:=NtoBE(dw); + result.write(dw,sizeof(dw)); + end; + + const + min_int_reg = 19; + min_mm_reg = 8; + var + hp : tai; + seh : tai_seh_directive absolute hp; + begin + result:=tdynamicarray.create(0); + hp:=tai(list.last); + while assigned(hp) do + begin + if hp.typ<>ait_seh_directive then + internalerror(2020041502); + case seh.kind of + ash_stackalloc: + begin + if (seh.data.offset and $f)<>0 then + internalerror(2020041207); + if seh.data.offset<((1 shl 5)*16) then + writebyte(byte(seh.data.offset shr 4)) + else if seh.data.offset<((1 shl 11)*16) then + writeword($C000 or word(seh.data.offset shr 4)) + else if seh.data.offset<((1 shl 24)*16) then + writedword($E0000000 or (seh.data.offset shr 4)) + else begin + writeln(hexstr(seh.data.offset,8)); + internalerror(2020041209); + end; + end; + ash_addfp: + begin + check_offset(seh.data.offset,(1 shl 7)*8); + writeword($E200 or (seh.data.offset shr 3)); + end; + ash_setfp: + writebyte($E1); + ash_nop: + writebyte($E3); + ash_savefplr: + begin + check_offset(seh.data.offset,504); + writebyte($40 or (seh.data.offset shr 3)); + end; + ash_savefplr_x: + begin + check_offset(seh.data.offset,512); + writebyte($80 or (seh.data.offset shr 3)-1); + end; + ash_savereg: + begin + check_offset(seh.data.offset,504); + check_reg(seh.data.reg,R_INTREGISTER,min_int_reg); + writeword($C000 or ((getsupreg(seh.data.reg)-min_int_reg) shl 6) or (seh.data.offset shr 3)); + end; + ash_savereg_x: + begin + check_offset(seh.data.offset,256); + check_reg(seh.data.reg,R_INTREGISTER,min_int_reg); + writeword($C400 or ((getsupreg(seh.data.reg)-min_int_reg) shl 5) or ((seh.data.offset shr 3)-1)); + end; + ash_saveregp: + begin + check_offset(seh.data.offset,504); + check_reg(seh.data.reg,R_INTREGISTER,min_int_reg); + writeword($C800 or ((getsupreg(seh.data.reg)-min_int_reg) shl 6) or (seh.data.offset shr 3)); + end; + ash_saveregp_x: + begin + check_offset(seh.data.offset,512); + check_reg(seh.data.reg,R_INTREGISTER,min_int_reg); + writeword($CC00 or ((getsupreg(seh.data.reg)-min_int_reg) shl 6) or ((seh.data.offset shr 3)-1)); + end; + ash_savefreg: + begin + check_offset(seh.data.offset,504); + check_reg(seh.data.reg,R_MMREGISTER,min_mm_reg); + writeword($DC00 or ((getsupreg(seh.data.reg)-min_mm_reg) shl 6) or (seh.data.offset shr 3)); + end; + ash_savefreg_x: + begin + check_offset(seh.data.offset,256); + check_reg(seh.data.reg,R_MMREGISTER,min_mm_reg); + writeword($CE00 or ((getsupreg(seh.data.reg)-min_mm_reg) shl 5) or ((seh.data.offset shr 3)-1)); + end; + ash_savefregp: + begin + check_offset(seh.data.offset,504); + check_reg(seh.data.reg,R_MMREGISTER,min_mm_reg); + writeword($D800 or ((getsupreg(seh.data.reg)-min_mm_reg) shl 6) or (seh.data.offset shr 3)); + end; + ash_savefregp_x: + begin + check_offset(seh.data.offset,512); + check_reg(seh.data.reg,R_MMREGISTER,min_mm_reg); + writeword($DA00 or ((getsupreg(seh.data.reg)-min_int_reg) shl 6) or ((seh.data.offset shr 3)-1)); + end; + else + internalerror(2020041503); + end; + hp:=tai(hp.previous); + end; + end; + + var + unwinddata : tdynamicarray; + + procedure writebyte(b:byte); + begin + unwinddata.write(b,sizeof(b)); + end; + + var + hp,hpnext,hpdata : tai; + seh : tai_seh_directive absolute hp; + lastsym : tai_symbol; + lastsec : tai_section; + inprologue, + inhandlerdata, + deleteai : boolean; + totalcount, + instrcount, + datacount : sizeint; + handlername : tsymstr; + handlerflags : byte; + handlerdata : array of tai; + handlerdataidx : sizeint; + handlerdatacount : tai; + sehlist, + tmplist : TAsmList; + xdatasym : tasmsymbol; + unwindread, + unwindrec : longword; + begin + if not assigned(list) then + exit; + + tmplist:=nil; + sehlist:=nil; + lastsec:=nil; + instrcount:=0; + datacount:=0; + unwinddata:=nil; + inhandlerdata:=false; + inprologue:=false; + handlerdata:=nil; + handlerdataidx:=0; + handlerdatacount:=nil; + + hp:=tai(list.first); + while assigned(hp) do + begin + deleteai:=false; + case hp.typ of + ait_section: + begin + if assigned(sehlist) then + begin + if assigned(lastsec) and (tai_section(hp).name^=lastsec.name^) then + begin + { this section was only added due to the now removed SEH data } + deleteai:=true; + dec(list.section_count); + end + else + internalerror(2020041214); + end + else + lastsec:=tai_section(hp); + + if assigned(tmplist) then + begin + list.insertListBefore(hp,tmplist); + tmplist.free; + tmplist:=nil; + end; + end; + ait_symbol: + begin + if tai_symbol(hp).is_global then + lastsym:=tai_symbol(hp); + end; + ait_instruction: + if assigned(sehlist) then + inc(instrcount); + ait_const: + if assigned(sehlist) then + inc(datacount,tai_const(hp).size); + ait_seh_directive: + begin + if not assigned(sehlist) and (seh.kind<>ash_proc) then + internalerror(2020041208); + { most seh directives are removed } + deleteai:=true; + case seh.kind of + ash_proc: + begin + if not assigned(lastsec) then + internalerror(2020041203); + datacount:=0; + instrcount:=0; + handlerflags:=0; + handlername:=''; + sehlist:=tasmlist.create; + inprologue:=true; + end; + ash_endproc: + begin + if not assigned(sehlist) then + internalerror(2020041501); + if assigned(tmplist) then + internalerror(2020041302); + if not assigned(lastsym) then + internalerror(2020041303); + if inprologue then + cgmessage(asmw_e_missing_endprologue); + + unwinddata:=convert_unwinddata(sehlist); + + writebyte($E4); + + { fill up with NOPs } + while unwinddata.size mod 4<>0 do + writebyte($E3); + + { note: we can pass Nil here, because in case of a LLVM + backend this whole code shouldn't be required + anyway } + xdatasym:=current_asmdata.DefineAsmSymbol('xdata_'+lastsec.name^,AB_LOCAL,AT_DATA,nil); + + tmplist:=tasmlist.create; + new_section(tmplist,sec_pdata,lastsec.name^,0); + tmplist.concat(tai_const.Create_rva_sym(lastsym.sym)); + tmplist.concat(tai_const.Create_rva_sym(xdatasym)); + + new_section(tmplist,sec_rodata,xdatasym.name,0); + tmplist.concat(tai_symbol.Create(xdatasym,0)); + + tmplist.concat(tai_comment.Create(strpnew('instr: '+tostr(instrcount)+', data: '+tostr(datacount)+', unwind: '+tostr(unwinddata.size)))); + + {$ifdef EXTDEBUG} + comment(V_Debug,'got section: '+lastsec.name^); + comment(V_Debug,'got instructions: '+tostr(instrcount)); + comment(V_Debug,'got data: '+tostr(datacount)); + comment(V_Debug,'got unwinddata: '+tostr(unwinddata.size)); + {$endif EXTDEBUG} + + if datacount mod 4<>0 then + cgmessage(asmw_e_seh_invalid_data_size); + + totalcount:=datacount div 4+instrcount; + + { splitting to multiple pdata/xdata sections is not yet + supported, so 1 MB is our limit for now } + if totalcount>(1 shl 18) then + comment(V_Error,'Function is larger than 1 MB which is not supported for SEH currently'); + + unwindrec:=min(totalcount,(1 shl 18)-1); + if handlerflags<>0 then + unwindrec:=unwindrec or (1 shl 20); + + { currently we only have one epilog, so E needs to be + set to 1 and epilog scope index needs to be 0, no + matter if we require the extension for the unwinddata + or not } + unwindrec:=unwindrec or (1 shl 21); + + if unwinddata.size div 4<=31 then + unwindrec:=unwindrec or ((unwinddata.size div 4) shl 27); + + { exception record headers } + tmplist.concat(tai_const.Create_32bit(unwindrec)); + if cs_asm_source in init_settings.globalswitches then + tmplist.concat(tai_comment.create(strpnew(hexstr(unwindrec,8)))); + + if unwinddata.size div 4>31 then + begin + { once we're able to split a .pdata entry this can be + removed as well } + if unwinddata.size div 4>255 then + comment(V_Error,'Too many unwind codes for SEH'); + unwindrec:=(unwinddata.size div 4) shl 16; + tmplist.concat(tai_const.create_32bit(unwindrec)); + if cs_asm_source in init_settings.globalswitches then + tmplist.concat(tai_comment.create(strpnew(hexstr(unwindrec,8)))); + end; + + { unwind codes } + unwinddata.seek(0); + while unwinddata.pos<unwinddata.size do + begin + unwinddata.read(unwindrec,sizeof(longword)); + tmplist.concat(tai_const.Create_32bit(unwindrec)); + if cs_asm_source in init_settings.globalswitches then + tmplist.concat(tai_comment.create(strpnew(hexstr(unwindrec,8)))); + end; + unwinddata.free; + + if handlerflags<>0 then + begin + tmplist.concat(tai_const.Create_rva_sym(current_asmdata.RefAsmSymbol(handlername,AT_FUNCTION,false))); + if length(handlerdata)>0 then + begin + tmplist.concat(handlerdatacount); + for handlerdataidx:=0 to high(handlerdata) do + tmplist.concat(handlerdata[handlerdataidx]); + end; + end; + + handlerdata:=nil; + + sehlist.free; + sehlist:=nil; + end; + ash_endprologue: + inprologue:=false; + ash_handler: + begin + handlername:=seh.data.name^; + handlerflags:=seh.data.flags; + end; + ash_handlerdata: + begin + if handlername='' then + cgmessage(asmw_e_handlerdata_no_handler); + hpdata:=tai(hp.next); + if not assigned(hpdata) or (hpdata.typ<>ait_const) or (tai_const(hpdata).consttype<>aitconst_32bit) then + internalerror(2020041215); + handlerdatacount:=hpdata; + setlength(handlerdata,tai_const(hpdata).value*4); + handlerdataidx:=0; + hpnext:=tai(hpdata.next); + list.remove(hpdata); + hpdata:=hpnext; + while (handlerdataidx<length(handlerdata)) and assigned(hpdata) do + begin + if (hpdata.typ<>ait_const) or not (tai_const(hpdata).consttype in [aitconst_32bit,aitconst_rva_symbol]) then + internalerror(2020041212); + handlerdata[handlerdataidx]:=hpdata; + inc(handlerdataidx); + hpnext:=tai(hpdata.next); + list.remove(hpdata); + hpdata:=hpnext; + end; + if handlerdataidx<length(handlerdata) then + internalerror(2020041213); + end; + ash_stackalloc, + ash_addfp, + ash_setfp, + ash_nop, + ash_savefplr, + ash_savefplr_x, + ash_savereg, + ash_savereg_x, + ash_saveregp, + ash_saveregp_x, + ash_savefreg, + ash_savefreg_x, + ash_savefregp, + ash_savefregp_x: + begin + if not assigned(sehlist) then + internalerror(2020041504); + if not inprologue then + internalerror(2020041505); + hpdata:=hp; + hp:=tai(hp.previous); + list.Remove(hpdata); + sehlist.concat(hpdata); + { don't delete this } + deleteai:=false; + end; + else + internalerror(2020041206); + end; + end; + else + { ignore } + ; + end; + + if deleteai then + begin + hpnext:=tai(hp.next); + list.remove(hp); + hp.free; + hp:=hpnext; + end + else + hp:=tai(hp.next); + end; + + if assigned(sehlist) then + internalerror(2020041205); + + if assigned(tmplist) then + begin + list.concatlist(tmplist); + tmplist.free; + end; + end; + + function TAArch64ClangGASAssembler.sectionflags(secflags:TSectionFlags):string; begin Result:=inherited sectionflags(secflags); @@ -140,6 +578,19 @@ unit agcpugas; end; + procedure TAArch64ClangGASAssembler.WriteAsmList; + begin + { clang does not support all the directives we need, so we need to + manually transform them to pdata/xdata records } + if target_info.system=system_aarch64_win64 then + begin + TransformSEHDirectives(current_asmdata.AsmLists[al_pure_assembler]); + TransformSEHDirectives(current_asmdata.AsmLists[al_procedures]); + end; + inherited WriteAsmList; + end; + + {****************************************************************************} { Helper routines for Instruction Writer } {****************************************************************************}