agbinaryen.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. {
  2. Copyright (c) 2017 by Karoly Balogh
  3. This unit implements the Binaryen assembler writer
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. ****************************************************************************
  16. }
  17. unit agbinaryen;
  18. {$i fpcdefs.inc}
  19. interface
  20. uses
  21. cclasses,systems,
  22. globtype,globals,
  23. symconst,symbase,symdef,symsym,
  24. aasmbase,aasmtai,aasmdata,aasmcpu,
  25. assemble;
  26. type
  27. TBinaryenAssemblerOutputFile=class(TExternalAssemblerOutputFile)
  28. procedure RemoveAsm; override;
  29. end;
  30. TBinaryenInstrWriter = class;
  31. {# This is a derived class which is used to write
  32. Binaryen-styled assembler.
  33. }
  34. { TBinaryenAssembler }
  35. TBinaryenAssembler=class(texternalassembler)
  36. protected
  37. jasminjar: tcmdstr;
  38. asmfiles: TCmdStrList;
  39. procedure WriteExtraHeader(obj: tabstractrecorddef);
  40. procedure WriteInstruction(hp: tai);
  41. function CreateNewAsmWriter: TExternalAssemblerOutputFile; override;
  42. public
  43. constructor CreateWithWriter(info: pasminfo; wr: TExternalAssemblerOutputFile; freewriter, smart: boolean); override;
  44. procedure WriteTree(p:TAsmList);override;
  45. procedure WriteAsmList;override;
  46. destructor destroy; override;
  47. protected
  48. InstrWriter: TBinaryenInstrWriter;
  49. end;
  50. {# This is the base class for writing instructions.
  51. The WriteInstruction() method must be overridden
  52. to write a single instruction to the assembler
  53. file.
  54. }
  55. { TBinaryenInstrWriter }
  56. TBinaryenInstrWriter = class
  57. constructor create(_owner: TBinaryenAssembler);
  58. procedure WriteInstruction(hp : tai); virtual;
  59. protected
  60. owner: TBinaryenAssembler;
  61. end;
  62. implementation
  63. uses
  64. SysUtils,
  65. cutils,cfileutl,cscript,
  66. fmodule,finput,verbose,
  67. symtype,symcpu,symtable,
  68. itcpuwasm,cpubase,cpuinfo,cgutils,
  69. widestr
  70. ;
  71. const
  72. line_length = 70;
  73. type
  74. t64bitarray = array[0..7] of byte;
  75. t32bitarray = array[0..3] of byte;
  76. {****************************************************************************}
  77. { Support routines }
  78. {****************************************************************************}
  79. function fixline(s:string):string;
  80. {
  81. return s with all leading and ending spaces and tabs removed
  82. }
  83. var
  84. i,j,k : integer;
  85. begin
  86. i:=length(s);
  87. while (i>0) and (s[i] in [#9,' ']) do
  88. dec(i);
  89. j:=1;
  90. while (j<i) and (s[j] in [#9,' ']) do
  91. inc(j);
  92. for k:=j to i do
  93. if s[k] in [#0..#31,#127..#255] then
  94. s[k]:='.';
  95. fixline:=Copy(s,j,i-j+1);
  96. end;
  97. function constastr(p: pchar; len: longint): ansistring;
  98. var
  99. i,runstart,runlen: longint;
  100. procedure flush;
  101. begin
  102. if runlen>0 then
  103. begin
  104. setlength(result,length(result)+runlen);
  105. move(p[runstart],result[length(result)-runlen+1],runlen);
  106. runlen:=0;
  107. end;
  108. end;
  109. begin
  110. result:='"';
  111. runlen:=0;
  112. runstart:=0;
  113. for i:=0 to len-1 do
  114. begin
  115. { escape control codes }
  116. case p[i] of
  117. { LF and CR must be escaped specially, because \uXXXX parsing
  118. happens in the pre-processor, so it's the same as actually
  119. inserting a newline in the middle of a string constant }
  120. #10:
  121. begin
  122. flush;
  123. result:=result+'\n';
  124. end;
  125. #13:
  126. begin
  127. flush;
  128. result:=result+'\r';
  129. end;
  130. '"','\':
  131. begin
  132. flush;
  133. result:=result+'\'+p[i];
  134. end
  135. else if p[i]<#32 then
  136. begin
  137. flush;
  138. result:=result+'\u'+hexstr(ord(p[i]),4);
  139. end
  140. else if p[i]<#127 then
  141. begin
  142. if runlen=0 then
  143. runstart:=i;
  144. inc(runlen);
  145. end
  146. else
  147. begin
  148. { see comments in njvmcon }
  149. flush;
  150. result:=result+'\u'+hexstr(ord(p[i]),4)
  151. end;
  152. end;
  153. end;
  154. flush;
  155. result:=result+'"';
  156. end;
  157. {****************************************************************************}
  158. { Binaryen Output File }
  159. {****************************************************************************}
  160. procedure TBinaryenAssemblerOutputFile.RemoveAsm;
  161. var
  162. g : file;
  163. begin
  164. inherited;
  165. if cs_asm_leave in current_settings.globalswitches then
  166. exit;
  167. while not TBinaryenAssembler(owner).asmfiles.empty do
  168. begin
  169. if cs_asm_extern in current_settings.globalswitches then
  170. AsmRes.AddDeleteCommand(TBinaryenAssembler(owner).asmfiles.GetFirst)
  171. else
  172. begin
  173. assign(g,TBinaryenAssembler(owner).asmfiles.GetFirst);
  174. {$I-}
  175. erase(g);
  176. {$I+}
  177. if ioresult<>0 then;
  178. end;
  179. end;
  180. end;
  181. {****************************************************************************}
  182. { Binaryen Assembler writer }
  183. {****************************************************************************}
  184. destructor TBinaryenAssembler.Destroy;
  185. begin
  186. InstrWriter.free;
  187. asmfiles.free;
  188. inherited destroy;
  189. end;
  190. procedure TBinaryenAssembler.WriteTree(p:TAsmList);
  191. var
  192. ch : char;
  193. hp : tai;
  194. hp1 : tailineinfo;
  195. s : ansistring;
  196. i,pos : longint;
  197. InlineLevel : longint;
  198. do_line : boolean;
  199. begin
  200. if not assigned(p) then
  201. exit;
  202. InlineLevel:=0;
  203. { lineinfo is only needed for al_procedures (PFV) }
  204. do_line:=(cs_asm_source in current_settings.globalswitches);
  205. hp:=tai(p.first);
  206. while assigned(hp) do
  207. begin
  208. prefetch(pointer(hp.next)^);
  209. if not(hp.typ in SkipLineInfo) then
  210. begin
  211. hp1 := hp as tailineinfo;
  212. current_filepos:=hp1.fileinfo;
  213. { no line info for inlined code }
  214. if do_line and (inlinelevel=0) then
  215. begin
  216. { load infile }
  217. if lastfileinfo.fileindex<>hp1.fileinfo.fileindex then
  218. begin
  219. infile:=current_module.sourcefiles.get_file(hp1.fileinfo.fileindex);
  220. if assigned(infile) then
  221. begin
  222. { open only if needed !! }
  223. if (cs_asm_source in current_settings.globalswitches) then
  224. infile.open;
  225. end;
  226. { avoid unnecessary reopens of the same file !! }
  227. lastfileinfo.fileindex:=hp1.fileinfo.fileindex;
  228. { be sure to change line !! }
  229. lastfileinfo.line:=-1;
  230. end;
  231. { write source }
  232. if (cs_asm_source in current_settings.globalswitches) and
  233. assigned(infile) then
  234. begin
  235. if (infile<>lastinfile) then
  236. begin
  237. writer.AsmWriteLn(asminfo^.comment+'['+infile.name+']');
  238. if assigned(lastinfile) then
  239. lastinfile.close;
  240. end;
  241. if (hp1.fileinfo.line<>lastfileinfo.line) and
  242. ((hp1.fileinfo.line<infile.maxlinebuf) or (InlineLevel>0)) then
  243. begin
  244. if (hp1.fileinfo.line<>0) and
  245. ((infile.linebuf^[hp1.fileinfo.line]>=0) or (InlineLevel>0)) then
  246. writer.AsmWriteLn(asminfo^.comment+'['+tostr(hp1.fileinfo.line)+'] '+
  247. fixline(infile.GetLineStr(hp1.fileinfo.line)));
  248. { set it to a negative value !
  249. to make that is has been read already !! PM }
  250. if (infile.linebuf^[hp1.fileinfo.line]>=0) then
  251. infile.linebuf^[hp1.fileinfo.line]:=-infile.linebuf^[hp1.fileinfo.line]-1;
  252. end;
  253. end;
  254. lastfileinfo:=hp1.fileinfo;
  255. lastinfile:=infile;
  256. end;
  257. end;
  258. case hp.typ of
  259. ait_comment :
  260. Begin
  261. writer.AsmWrite(asminfo^.comment);
  262. writer.AsmWritePChar(tai_comment(hp).str);
  263. writer.AsmLn;
  264. End;
  265. ait_regalloc :
  266. begin
  267. if (cs_asm_regalloc in current_settings.globalswitches) then
  268. begin
  269. writer.AsmWrite(#9+asminfo^.comment+'Register ');
  270. repeat
  271. writer.AsmWrite(std_regname(Tai_regalloc(hp).reg));
  272. if (hp.next=nil) or
  273. (tai(hp.next).typ<>ait_regalloc) or
  274. (tai_regalloc(hp.next).ratype<>tai_regalloc(hp).ratype) then
  275. break;
  276. hp:=tai(hp.next);
  277. writer.AsmWrite(',');
  278. until false;
  279. writer.AsmWrite(' ');
  280. writer.AsmWriteLn(regallocstr[tai_regalloc(hp).ratype]);
  281. end;
  282. end;
  283. ait_tempalloc :
  284. begin
  285. if (cs_asm_tempalloc in current_settings.globalswitches) then
  286. begin
  287. {$ifdef EXTDEBUG}
  288. if assigned(tai_tempalloc(hp).problem) then
  289. writer.AsmWriteLn(asminfo^.comment+'Temp '+tostr(tai_tempalloc(hp).temppos)+','+
  290. tostr(tai_tempalloc(hp).tempsize)+' '+tai_tempalloc(hp).problem^)
  291. else
  292. {$endif EXTDEBUG}
  293. writer.AsmWriteLn(asminfo^.comment+'Temp '+tostr(tai_tempalloc(hp).temppos)+','+
  294. tostr(tai_tempalloc(hp).tempsize)+' '+tempallocstr[tai_tempalloc(hp).allocation]);
  295. end;
  296. end;
  297. ait_align :
  298. begin
  299. end;
  300. ait_section :
  301. begin
  302. end;
  303. ait_datablock :
  304. begin
  305. // internalerror(2010122701);
  306. end;
  307. ait_const:
  308. begin
  309. writer.AsmWriteln('constant');
  310. // internalerror(2010122702);
  311. end;
  312. ait_realconst :
  313. begin
  314. internalerror(2010122703);
  315. end;
  316. ait_string :
  317. begin
  318. pos:=0;
  319. for i:=1 to tai_string(hp).len do
  320. begin
  321. if pos=0 then
  322. begin
  323. writer.AsmWrite(#9'strconst: '#9'"');
  324. pos:=20;
  325. end;
  326. ch:=tai_string(hp).str[i-1];
  327. case ch of
  328. #0, {This can't be done by range, because a bug in FPC}
  329. #1..#31,
  330. #128..#255 : s:='\'+tostr(ord(ch) shr 6)+tostr((ord(ch) and 63) shr 3)+tostr(ord(ch) and 7);
  331. '"' : s:='\"';
  332. '\' : s:='\\';
  333. else
  334. s:=ch;
  335. end;
  336. writer.AsmWrite(s);
  337. inc(pos,length(s));
  338. if (pos>line_length) or (i=tai_string(hp).len) then
  339. begin
  340. writer.AsmWriteLn('"');
  341. pos:=0;
  342. end;
  343. end;
  344. end;
  345. ait_label :
  346. begin
  347. if (tai_label(hp).labsym.is_used) then
  348. begin
  349. writer.AsmWrite(tai_label(hp).labsym.name);
  350. writer.AsmWriteLn(':');
  351. end;
  352. end;
  353. ait_symbol :
  354. begin
  355. if (tai_symbol(hp).sym.typ = AT_FUNCTION) then
  356. begin
  357. end
  358. else
  359. begin
  360. writer.AsmWrite('data symbol: ');
  361. writer.AsmWriteln(tai_symbol(hp).sym.name);
  362. // internalerror(2010122706);
  363. end;
  364. end;
  365. ait_symbol_end :
  366. begin
  367. end;
  368. ait_instruction :
  369. begin
  370. WriteInstruction(hp);
  371. end;
  372. ait_force_line,
  373. ait_function_name : ;
  374. ait_cutobject :
  375. begin
  376. end;
  377. ait_marker :
  378. if tai_marker(hp).kind=mark_NoLineInfoStart then
  379. inc(InlineLevel)
  380. else if tai_marker(hp).kind=mark_NoLineInfoEnd then
  381. dec(InlineLevel);
  382. ait_directive :
  383. begin
  384. { the CPU directive is probably not supported by the JVM assembler,
  385. so it's commented out }
  386. if tai_directive(hp).directive=asd_cpu then
  387. writer.AsmWrite(asminfo^.comment);
  388. writer.AsmWrite('.'+directivestr[tai_directive(hp).directive]+' ');
  389. if tai_directive(hp).name<>'' then
  390. writer.AsmWrite(tai_directive(hp).name);
  391. writer.AsmLn;
  392. end;
  393. else
  394. internalerror(2010122707);
  395. end;
  396. hp:=tai(hp.next);
  397. end;
  398. end;
  399. procedure TBinaryenAssembler.WriteExtraHeader(obj: tabstractrecorddef);
  400. begin
  401. end;
  402. procedure TBinaryenAssembler.WriteInstruction(hp: tai);
  403. begin
  404. InstrWriter.WriteInstruction(hp);
  405. end;
  406. function TBinaryenAssembler.CreateNewAsmWriter: TExternalAssemblerOutputFile;
  407. begin
  408. Result:=TBinaryenAssemblerOutputFile.Create(self);
  409. end;
  410. constructor TBinaryenAssembler.CreateWithWriter(info: pasminfo; wr: TExternalAssemblerOutputFile; freewriter, smart: boolean);
  411. begin
  412. inherited;
  413. InstrWriter:=TBinaryenInstrWriter.Create(self);
  414. asmfiles:=TCmdStrList.Create;
  415. end;
  416. procedure TBinaryenAssembler.WriteAsmList;
  417. var
  418. hal : tasmlisttype;
  419. begin
  420. writer.MarkEmpty;
  421. WriteExtraHeader(nil);
  422. for hal:=low(TasmlistType) to high(TasmlistType) do
  423. begin
  424. if not (current_asmdata.asmlists[hal].empty) then
  425. begin
  426. writer.AsmWriteLn(asminfo^.comment+'Begin asmlist '+AsmlistTypeStr[hal]);
  427. writetree(current_asmdata.asmlists[hal]);
  428. writer.AsmWriteLn(asminfo^.comment+'End asmlist '+AsmlistTypeStr[hal]);
  429. end;
  430. end;
  431. writer.AsmLn;
  432. end;
  433. {****************************************************************************}
  434. { Binaryen Instruction Writer }
  435. {****************************************************************************}
  436. constructor TBinaryenInstrWriter.create(_owner: TBinaryenAssembler);
  437. begin
  438. inherited create;
  439. owner := _owner;
  440. end;
  441. function getreferencestring(var ref : treference) : ansistring;
  442. begin
  443. { if (ref.arrayreftype<>art_none) or
  444. (ref.index<>NR_NO) then
  445. internalerror(2010122809);}
  446. if assigned(ref.symbol) then
  447. begin
  448. // global symbol or field -> full type and name
  449. // ref.base can be <> NR_NO in case an instance field is loaded.
  450. // This register is not part of this instruction, it will have
  451. // been placed on the stack by the previous one.
  452. if (ref.offset<>0) then
  453. internalerror(2010122811);
  454. result:=ref.symbol.name;
  455. end
  456. else
  457. begin
  458. // local symbol -> stack slot, stored in offset
  459. if ref.base<>NR_STACK_POINTER_REG then
  460. internalerror(2010122810);
  461. result:=tostr(ref.offset);
  462. end;
  463. end;
  464. function getopstr(const o:toper) : ansistring;
  465. begin
  466. case o.typ of
  467. top_reg:
  468. // should have been translated into a memory location by the
  469. // register allocator)
  470. if (cs_no_regalloc in current_settings.globalswitches) then
  471. getopstr:=std_regname(o.reg)
  472. else
  473. internalerror(2010122803);
  474. top_const:
  475. str(o.val,result);
  476. top_ref:
  477. getopstr:=getreferencestring(o.ref^);
  478. else
  479. internalerror(2010122802);
  480. end;
  481. end;
  482. procedure TBinaryenInstrWriter.WriteInstruction(hp: tai);
  483. var
  484. s: ansistring;
  485. i: byte;
  486. sep: ansistring;
  487. begin
  488. s:=#9+wasm_op2str[taicpu(hp).opcode];
  489. if taicpu(hp).ops<>0 then
  490. begin
  491. sep:=#9;
  492. for i:=0 to taicpu(hp).ops-1 do
  493. begin
  494. s:=s+sep+getopstr(taicpu(hp).oper[i]^);
  495. sep:=' ';
  496. end;
  497. end;
  498. owner.writer.AsmWriteLn(s);
  499. end;
  500. {****************************************************************************}
  501. { Binaryen Instruction Writer }
  502. {****************************************************************************}
  503. const
  504. as_wasm_binaryen_info : tasminfo =
  505. (
  506. id : as_wasm32_binaryen;
  507. idtxt : 'BINARYEN';
  508. asmbin : 'wasm-as';
  509. asmcmd : '$ASM $EXTRAOPT';
  510. supported_targets : [system_wasm32_embedded,system_wasm32_wasi];
  511. flags : [];
  512. labelprefix : 'L';
  513. labelmaxlen : -1;
  514. comment : ';; ';
  515. dollarsign : '$';
  516. );
  517. initialization
  518. RegisterAssembler(as_wasm_binaryen_info,TBinaryenAssembler);
  519. end.