tgobj.pas 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. {
  2. Copyright (c) 1998-2002 by Florian Klaempfl
  3. This unit implements the base object for temp. generator
  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. {#@abstract(Temporary reference allocator unit)
  18. Temporary reference allocator unit. This unit contains
  19. all which is related to allocating temporary memory
  20. space on the stack, as required, by the code generator.
  21. }
  22. unit tgobj;
  23. {$i fpcdefs.inc}
  24. interface
  25. uses
  26. cclasses,
  27. globals,globtype,
  28. symtype,
  29. cpubase,cpuinfo,cgbase,cgutils,
  30. aasmbase,aasmtai,aasmdata;
  31. type
  32. ptemprecord = ^ttemprecord;
  33. ttemprecord = record
  34. temptype : ttemptype;
  35. pos : longint;
  36. size : longint;
  37. def : tdef;
  38. next : ptemprecord;
  39. nextfree : ptemprecord; { for faster freeblock checking }
  40. {$ifdef EXTDEBUG}
  41. posinfo,
  42. releaseposinfo : tfileposinfo;
  43. {$endif}
  44. end;
  45. {# Generates temporary variables }
  46. ttgobj = class
  47. private
  48. { contains all free temps using nextfree links }
  49. tempfreelist : ptemprecord;
  50. function alloctemp(list: TAsmList; size,alignment : longint; temptype : ttemptype; def:tdef) : longint;
  51. procedure freetemp(list: TAsmList; pos:longint;temptypes:ttemptypeset);
  52. public
  53. { contains all temps }
  54. templist : ptemprecord;
  55. { Offsets of the first/last temp }
  56. firsttemp,
  57. lasttemp : longint;
  58. direction : shortint;
  59. constructor create;
  60. {# Clear and free the complete linked list of temporary memory
  61. locations. The list is set to nil.}
  62. procedure resettempgen;
  63. {# Sets the first offset from the frame pointer or stack pointer where
  64. the temporary references will be allocated. It is to note that this
  65. value should always be negative.
  66. @param(l start offset where temps will start in stack)
  67. }
  68. procedure setfirsttemp(l : longint);
  69. procedure gettemp(list: TAsmList; size : longint;temptype:ttemptype;out ref : treference);
  70. procedure gettemptyped(list: TAsmList; def:tdef;temptype:ttemptype;out ref : treference);
  71. procedure ungettemp(list: TAsmList; const ref : treference);
  72. function sizeoftemp(list: TAsmList; const ref: treference): longint;
  73. function changetemptype(list: TAsmList; const ref:treference;temptype:ttemptype):boolean;
  74. {# Returns TRUE if the reference ref is allocated in temporary volatile memory space,
  75. otherwise returns FALSE.
  76. @param(ref reference to verify)
  77. }
  78. function istemp(const ref : treference) : boolean;
  79. {# Frees a reference @var(ref) which was allocated in the volatile temporary memory space.
  80. The freed space can later be reallocated and reused. If this reference
  81. is not in the temporary memory, it is simply not freed.
  82. }
  83. procedure ungetiftemp(list: TAsmList; const ref : treference);
  84. { Allocate space for a local }
  85. procedure getlocal(list: TAsmList; size : longint;def:tdef;var ref : treference);
  86. procedure getlocal(list: TAsmList; size : longint; alignment : shortint; def:tdef;var ref : treference);
  87. procedure UnGetLocal(list: TAsmList; const ref : treference);
  88. end;
  89. var
  90. tg: ttgobj;
  91. procedure location_freetemp(list:TAsmList; const l : tlocation);
  92. implementation
  93. uses
  94. cutils,
  95. systems,verbose,
  96. procinfo,
  97. symconst
  98. ;
  99. const
  100. FreeTempTypes = [tt_free,tt_freenoreuse];
  101. {$ifdef EXTDEBUG}
  102. TempTypeStr : array[ttemptype] of string[18] = (
  103. '<none>',
  104. 'free','normal','persistant',
  105. 'noreuse','freenoreuse'
  106. );
  107. {$endif EXTDEBUG}
  108. Used2Free : array[ttemptype] of ttemptype = (
  109. tt_none,
  110. tt_none,tt_free,tt_free,
  111. tt_freenoreuse,tt_none
  112. );
  113. {*****************************************************************************
  114. Helpers
  115. *****************************************************************************}
  116. procedure location_freetemp(list:TAsmList; const l : tlocation);
  117. begin
  118. if (l.loc in [LOC_REFERENCE,LOC_CREFERENCE]) then
  119. tg.ungetiftemp(list,l.reference);
  120. end;
  121. {*****************************************************************************
  122. TTGOBJ
  123. *****************************************************************************}
  124. constructor ttgobj.create;
  125. begin
  126. tempfreelist:=nil;
  127. templist:=nil;
  128. { we could create a new child class for this but I don't if it is worth the effort (FK) }
  129. {$if defined(powerpc) or defined(powerpc64)}
  130. direction:=1;
  131. {$else}
  132. direction:=-1;
  133. {$endif}
  134. end;
  135. procedure ttgobj.resettempgen;
  136. var
  137. hp : ptemprecord;
  138. begin
  139. { Clear the old templist }
  140. while assigned(templist) do
  141. begin
  142. {$ifdef EXTDEBUG}
  143. if not(templist^.temptype in FreeTempTypes) then
  144. begin
  145. Comment(V_Warning,'tgobj: (ResetTempgen) temp at pos '+tostr(templist^.pos)+
  146. ' with size '+tostr(templist^.size)+' and type '+TempTypeStr[templist^.temptype]+
  147. ' from pos '+tostr(templist^.posinfo.line)+':'+tostr(templist^.posinfo.column)+
  148. ' not freed at the end of the procedure');
  149. end;
  150. {$endif EXTDEBUG}
  151. hp:=templist;
  152. templist:=hp^.next;
  153. dispose(hp);
  154. end;
  155. templist:=nil;
  156. tempfreelist:=nil;
  157. firsttemp:=0;
  158. lasttemp:=0;
  159. end;
  160. procedure ttgobj.setfirsttemp(l : longint);
  161. begin
  162. { this is a negative value normally }
  163. if l*direction>=0 then
  164. begin
  165. if odd(l) then
  166. inc(l,direction);
  167. end
  168. else
  169. internalerror(200204221);
  170. firsttemp:=l;
  171. lasttemp:=l;
  172. end;
  173. function ttgobj.AllocTemp(list: TAsmList; size,alignment : longint; temptype : ttemptype;def : tdef) : longint;
  174. var
  175. tl,htl,
  176. bestslot,bestprev,
  177. hprev,hp : ptemprecord;
  178. freetype : ttemptype;
  179. bestatend,
  180. fitatbegin,
  181. fitatend : boolean;
  182. begin
  183. AllocTemp:=0;
  184. bestprev:=nil;
  185. bestslot:=nil;
  186. tl:=nil;
  187. bestatend:=false;
  188. if size=0 then
  189. begin
  190. {$ifdef EXTDEBUG}
  191. Comment(V_Warning,'tgobj: (AllocTemp) temp of size 0 requested, allocating 4 bytes');
  192. {$endif}
  193. size:=4;
  194. end;
  195. freetype:=Used2Free[temptype];
  196. if freetype=tt_none then
  197. internalerror(200208201);
  198. size:=align(size,alignment);
  199. { First check the tmpfreelist, but not when
  200. we don't want to reuse an already allocated block }
  201. if assigned(tempfreelist) and
  202. (temptype<>tt_noreuse) then
  203. begin
  204. hprev:=nil;
  205. hp:=tempfreelist;
  206. while assigned(hp) do
  207. begin
  208. {$ifdef EXTDEBUG}
  209. if not(hp^.temptype in FreeTempTypes) then
  210. Comment(V_Warning,'tgobj: (AllocTemp) temp at pos '+tostr(hp^.pos)+ ' in freelist is not set to tt_free !');
  211. {$endif}
  212. { Check only slots that are
  213. - free
  214. - share the same type
  215. - contain enough space
  216. - has a correct alignment }
  217. if (hp^.temptype=freetype) and
  218. (hp^.def=def) and
  219. (hp^.size>=size) and
  220. ((hp^.pos=align(hp^.pos,alignment)) or
  221. (hp^.pos+hp^.size-size = align(hp^.pos+hp^.size-size,alignment))) then
  222. begin
  223. { Slot is the same size then leave immediatly }
  224. if (hp^.size=size) then
  225. begin
  226. bestprev:=hprev;
  227. bestslot:=hp;
  228. break;
  229. end
  230. else
  231. begin
  232. { we can fit a smaller block either at the begin or at }
  233. { the end of a block. For direction=-1 we prefer the }
  234. { end, for direction=1 we prefer the begin (i.e., }
  235. { always closest to the source). We also try to use }
  236. { the block with the worst possible alignment that }
  237. { still suffices. And we pick the block which will }
  238. { have the best alignmenment after this new block is }
  239. { substracted from it. }
  240. fitatend:=(hp^.pos+hp^.size-size)=align(hp^.pos+hp^.size-size,alignment);
  241. fitatbegin:=hp^.pos=align(hp^.pos,alignment);
  242. if assigned(bestslot) then
  243. begin
  244. fitatend:=fitatend and
  245. ((not bestatend and
  246. (direction=-1)) or
  247. (bestatend and
  248. isbetteralignedthan(abs(bestslot^.pos+hp^.size-size),abs(hp^.pos+hp^.size-size),current_settings.alignment.localalignmax)));
  249. fitatbegin:=fitatbegin and
  250. (not bestatend or
  251. (direction=1)) and
  252. isbetteralignedthan(abs(hp^.pos+size),abs(bestslot^.pos+size),current_settings.alignment.localalignmax);
  253. end;
  254. if fitatend and
  255. fitatbegin then
  256. if isbetteralignedthan(abs(hp^.pos+hp^.size-size),abs(hp^.pos+size),current_settings.alignment.localalignmax) then
  257. fitatbegin:=false
  258. else if isbetteralignedthan(abs(hp^.pos+size),abs(hp^.pos+hp^.size-size),current_settings.alignment.localalignmax) then
  259. fitatend:=false
  260. else if (direction=1) then
  261. fitatend:=false
  262. else
  263. fitatbegin:=false;
  264. if fitatend or
  265. fitatbegin then
  266. begin
  267. bestprev:=hprev;
  268. bestslot:=hp;
  269. bestatend:=fitatend;
  270. end;
  271. end;
  272. end;
  273. hprev:=hp;
  274. hp:=hp^.nextfree;
  275. end;
  276. end;
  277. { Reuse an old temp ? }
  278. if assigned(bestslot) then
  279. begin
  280. if bestslot^.size=size then
  281. begin
  282. tl:=bestslot;
  283. { Remove from the tempfreelist }
  284. if assigned(bestprev) then
  285. bestprev^.nextfree:=tl^.nextfree
  286. else
  287. tempfreelist:=tl^.nextfree;
  288. end
  289. else
  290. begin
  291. { Duplicate bestlost and the block in the list }
  292. new(tl);
  293. move(bestslot^,tl^,sizeof(ttemprecord));
  294. tl^.next:=bestslot^.next;
  295. bestslot^.next:=tl;
  296. { Now we split the block in 2 parts. Depending on the direction
  297. we need to resize the newly inserted block or the old reused block.
  298. For direction=1 we can use tl for the new block. For direction=-1 we
  299. will be reusing bestslot and resize the new block, that means we need
  300. to swap the pointers }
  301. if (direction=-1) xor
  302. bestatend then
  303. begin
  304. htl:=tl;
  305. tl:=bestslot;
  306. bestslot:=htl;
  307. { Update the tempfreelist to point to the new block }
  308. if assigned(bestprev) then
  309. bestprev^.nextfree:=bestslot
  310. else
  311. tempfreelist:=bestslot;
  312. end;
  313. if not bestatend then
  314. inc(bestslot^.pos,size)
  315. else
  316. inc(tl^.pos,tl^.size-size);
  317. { Create new block and resize the old block }
  318. tl^.size:=size;
  319. tl^.nextfree:=nil;
  320. { Resize the old block }
  321. dec(bestslot^.size,size);
  322. end;
  323. tl^.temptype:=temptype;
  324. tl^.def:=def;
  325. tl^.nextfree:=nil;
  326. end
  327. else
  328. begin
  329. { now we can create the templist entry }
  330. new(tl);
  331. tl^.temptype:=temptype;
  332. tl^.def:=def;
  333. { Extend the temp }
  334. if direction=-1 then
  335. begin
  336. lasttemp:=(-align(-lasttemp,alignment))-size;
  337. tl^.pos:=lasttemp;
  338. end
  339. else
  340. begin
  341. tl^.pos:=align(lasttemp,alignment);
  342. lasttemp:=tl^.pos+size;
  343. end;
  344. tl^.size:=size;
  345. tl^.next:=templist;
  346. tl^.nextfree:=nil;
  347. templist:=tl;
  348. end;
  349. {$ifdef EXTDEBUG}
  350. tl^.posinfo:=current_filepos;
  351. if assigned(tl^.def) then
  352. list.concat(tai_tempalloc.allocinfo(tl^.pos,tl^.size,'allocated with type '+TempTypeStr[tl^.temptype]+' for def '+tl^.def.typename))
  353. else
  354. list.concat(tai_tempalloc.allocinfo(tl^.pos,tl^.size,'allocated with type '+TempTypeStr[tl^.temptype]));
  355. {$else}
  356. list.concat(tai_tempalloc.alloc(tl^.pos,tl^.size));
  357. {$endif}
  358. AllocTemp:=tl^.pos;
  359. end;
  360. procedure ttgobj.FreeTemp(list: TAsmList; pos:longint;temptypes:ttemptypeset);
  361. var
  362. hp,hnext,hprev,hprevfree : ptemprecord;
  363. begin
  364. hp:=templist;
  365. hprev:=nil;
  366. hprevfree:=nil;
  367. while assigned(hp) do
  368. begin
  369. if (hp^.pos=pos) then
  370. begin
  371. { check if already freed }
  372. if hp^.temptype in FreeTempTypes then
  373. begin
  374. {$ifdef EXTDEBUG}
  375. Comment(V_Warning,'tgobj: (FreeTemp) temp at pos '+tostr(pos)+ ' is already free !');
  376. list.concat(tai_tempalloc.allocinfo(hp^.pos,hp^.size,'temp is already freed'));
  377. {$endif}
  378. exit;
  379. end;
  380. { check type that are allowed to be released }
  381. if not(hp^.temptype in temptypes) then
  382. begin
  383. {$ifdef EXTDEBUG}
  384. Comment(V_Debug,'tgobj: (Freetemp) temp at pos '+tostr(pos)+ ' has different type ('+TempTypeStr[hp^.temptype]+'), not releasing');
  385. list.concat(tai_tempalloc.allocinfo(hp^.pos,hp^.size,'temp has wrong type ('+TempTypeStr[hp^.temptype]+') not releasing'));
  386. {$endif}
  387. exit;
  388. end;
  389. list.concat(tai_tempalloc.dealloc(hp^.pos,hp^.size));
  390. { set this block to free }
  391. hp^.temptype:=Used2Free[hp^.temptype];
  392. { Update tempfreelist }
  393. if assigned(hprevfree) then
  394. begin
  395. { Concat blocks when the previous block is free and
  396. there is no block assigned for a tdef }
  397. if assigned(hprev) and
  398. (hp^.temptype=tt_free) and
  399. not assigned(hp^.def) and
  400. (hprev^.temptype=tt_free) and
  401. not assigned(hprev^.def) then
  402. begin
  403. inc(hprev^.size,hp^.size);
  404. if direction=1 then
  405. hprev^.pos:=hp^.pos;
  406. hprev^.next:=hp^.next;
  407. dispose(hp);
  408. hp:=hprev;
  409. end
  410. else
  411. hprevfree^.nextfree:=hp;
  412. end
  413. else
  414. begin
  415. hp^.nextfree:=tempfreelist;
  416. tempfreelist:=hp;
  417. end;
  418. { Concat blocks when the next block is free and
  419. there is no block assigned for a tdef }
  420. hnext:=hp^.next;
  421. if assigned(hnext) and
  422. (hp^.temptype=tt_free) and
  423. not assigned(hp^.def) and
  424. (hnext^.temptype=tt_free) and
  425. not assigned(hnext^.def) then
  426. begin
  427. inc(hp^.size,hnext^.size);
  428. if direction=1 then
  429. hp^.pos:=hnext^.pos;
  430. hp^.nextfree:=hnext^.nextfree;
  431. hp^.next:=hnext^.next;
  432. dispose(hnext);
  433. end;
  434. { Stop }
  435. exit;
  436. end;
  437. if (hp^.temptype=tt_free) then
  438. hprevfree:=hp;
  439. hprev:=hp;
  440. hp:=hp^.next;
  441. end;
  442. end;
  443. procedure ttgobj.gettemp(list: TAsmList; size : longint;temptype:ttemptype;out ref : treference);
  444. var
  445. varalign : shortint;
  446. begin
  447. varalign:=size_2_align(size);
  448. varalign:=used_align(varalign,current_settings.alignment.localalignmin,current_settings.alignment.localalignmax);
  449. { can't use reference_reset_base, because that will let tgobj depend
  450. on cgobj (PFV) }
  451. fillchar(ref,sizeof(ref),0);
  452. ref.base:=current_procinfo.framepointer;
  453. ref.offset:=alloctemp(list,size,varalign,temptype,nil);
  454. end;
  455. procedure ttgobj.gettemptyped(list: TAsmList; def:tdef;temptype:ttemptype;out ref : treference);
  456. var
  457. varalign : shortint;
  458. begin
  459. varalign:=def.alignment;
  460. varalign:=used_align(varalign,current_settings.alignment.localalignmin,current_settings.alignment.localalignmax);
  461. { can't use reference_reset_base, because that will let tgobj depend
  462. on cgobj (PFV) }
  463. fillchar(ref,sizeof(ref),0);
  464. ref.base:=current_procinfo.framepointer;
  465. ref.offset:=alloctemp(list,def.size,varalign,temptype,def);
  466. end;
  467. function ttgobj.istemp(const ref : treference) : boolean;
  468. begin
  469. { ref.index = R_NO was missing
  470. led to problems with local arrays
  471. with lower bound > 0 (PM) }
  472. if direction = 1 then
  473. begin
  474. istemp:=(ref.base=current_procinfo.framepointer) and
  475. (ref.index=NR_NO) and
  476. (ref.offset>=firsttemp);
  477. end
  478. else
  479. begin
  480. istemp:=(ref.base=current_procinfo.framepointer) and
  481. (ref.index=NR_NO) and
  482. (ref.offset<firsttemp);
  483. end;
  484. end;
  485. function ttgobj.sizeoftemp(list: TAsmList; const ref: treference): longint;
  486. var
  487. hp : ptemprecord;
  488. begin
  489. SizeOfTemp := -1;
  490. hp:=templist;
  491. while assigned(hp) do
  492. begin
  493. if (hp^.pos=ref.offset) then
  494. begin
  495. sizeoftemp := hp^.size;
  496. exit;
  497. end;
  498. hp := hp^.next;
  499. end;
  500. {$ifdef EXTDEBUG}
  501. comment(v_debug,'tgobj: (SizeOfTemp) temp at pos '+tostr(ref.offset)+' not found !');
  502. list.concat(tai_tempalloc.allocinfo(ref.offset,0,'temp not found'));
  503. {$endif}
  504. end;
  505. function ttgobj.ChangeTempType(list: TAsmList; const ref:treference;temptype:ttemptype):boolean;
  506. var
  507. hp : ptemprecord;
  508. begin
  509. ChangeTempType:=false;
  510. hp:=templist;
  511. while assigned(hp) do
  512. begin
  513. if (hp^.pos=ref.offset) then
  514. begin
  515. if hp^.temptype<>tt_free then
  516. begin
  517. {$ifdef EXTDEBUG}
  518. if hp^.temptype=temptype then
  519. Comment(V_Warning,'tgobj: (ChangeTempType) temp'+
  520. ' at pos '+tostr(ref.offset)+ ' is already of the correct type !');
  521. list.concat(tai_tempalloc.allocinfo(hp^.pos,hp^.size,'type changed to '+TempTypeStr[temptype]));
  522. {$endif}
  523. ChangeTempType:=true;
  524. hp^.temptype:=temptype;
  525. end
  526. else
  527. begin
  528. {$ifdef EXTDEBUG}
  529. Comment(V_Warning,'tgobj: (ChangeTempType) temp'+
  530. ' at pos '+tostr(ref.offset)+ ' is already freed !');
  531. list.concat(tai_tempalloc.allocinfo(hp^.pos,hp^.size,'temp is already freed'));
  532. {$endif}
  533. end;
  534. exit;
  535. end;
  536. hp:=hp^.next;
  537. end;
  538. {$ifdef EXTDEBUG}
  539. Comment(V_Warning,'tgobj: (ChangeTempType) temp'+
  540. ' at pos '+tostr(ref.offset)+ ' not found !');
  541. list.concat(tai_tempalloc.allocinfo(ref.offset,0,'temp not found'));
  542. {$endif}
  543. end;
  544. procedure ttgobj.UnGetTemp(list: TAsmList; const ref : treference);
  545. begin
  546. FreeTemp(list,ref.offset,[tt_normal,tt_noreuse,tt_persistent]);
  547. end;
  548. procedure ttgobj.UnGetIfTemp(list: TAsmList; const ref : treference);
  549. begin
  550. if istemp(ref) then
  551. FreeTemp(list,ref.offset,[tt_normal]);
  552. end;
  553. procedure ttgobj.getlocal(list: TAsmList; size : longint;def:tdef;var ref : treference);
  554. begin
  555. getlocal(list, size, def.alignment, def, ref);
  556. end;
  557. procedure ttgobj.getlocal(list: TAsmList; size : longint; alignment : shortint; def:tdef;var ref : treference);
  558. begin
  559. {$ifdef arm}
  560. { for ARM CPU records must be aligned in stack depending of record size }
  561. { to prevent misaligned error when the record is passed as parameter in registers }
  562. if def.typ=recorddef then
  563. if size>2 then
  564. alignment:=current_settings.alignment.localalignmax
  565. else
  566. alignment:=size;
  567. {$else}
  568. alignment:=used_align(alignment,current_settings.alignment.localalignmin,current_settings.alignment.localalignmax);
  569. {$endif arm}
  570. { can't use reference_reset_base, because that will let tgobj depend
  571. on cgobj (PFV) }
  572. fillchar(ref,sizeof(ref),0);
  573. ref.base:=current_procinfo.framepointer;
  574. ref.offset:=alloctemp(list,size,alignment,tt_persistent,nil);
  575. end;
  576. procedure ttgobj.UnGetLocal(list: TAsmList; const ref : treference);
  577. begin
  578. FreeTemp(list,ref.offset,[tt_persistent]);
  579. end;
  580. end.