system.pp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. unit System;
  2. interface
  3. { The heap for MSDOS is implemented
  4. in tinyheap.inc include file,
  5. but it uses default SysGetMem names }
  6. {$define HAS_MEMORYMANAGER}
  7. { define TEST_FPU_INT10 to force keeping local int10,
  8. for testing purpose only }
  9. {$DEFINE FPC_INCLUDE_SOFTWARE_MUL}
  10. {$DEFINE FPC_INCLUDE_SOFTWARE_MOD_DIV}
  11. {$DEFINE FPC_USE_SMALL_DEFAULTSTACKSIZE}
  12. { To avoid warnings in thread.inc code,
  13. but value must be really given after
  14. systemh.inc is included otherwise the
  15. $mode switch is not effective }
  16. { Use Ansi Char for files }
  17. {$define FPC_ANSI_TEXTFILEREC}
  18. {$define FPC_STDOUT_TRUE_ALIAS}
  19. {$ifdef NO_WIDESTRINGS}
  20. { Do NOT use wide Char for files }
  21. {$undef FPC_HAS_FEATURE_WIDESTRINGS}
  22. {$endif NO_WIDESTRINGS}
  23. {$I systemh.inc}
  24. {$I tnyheaph.inc}
  25. {$I portsh.inc}
  26. const
  27. LineEnding = #13#10;
  28. { LFNSupport is a variable here, defined below!!! }
  29. DirectorySeparator = '\';
  30. DriveSeparator = ':';
  31. ExtensionSeparator = '.';
  32. PathSeparator = ';';
  33. AllowDirectorySeparators : set of char = ['\','/'];
  34. AllowDriveSeparators : set of char = [':'];
  35. { FileNameCaseSensitive and FileNameCasePreserving are defined separately below!!! }
  36. maxExitCode = 255;
  37. MaxPathLen = 256;
  38. const
  39. { Default filehandles }
  40. UnusedHandle = $ffff;{ instead of -1, as it is a word value}
  41. StdInputHandle = 0;
  42. StdOutputHandle = 1;
  43. StdErrorHandle = 2;
  44. FileNameCaseSensitive : boolean = false;
  45. FileNameCasePreserving: boolean = false;
  46. CtrlZMarksEOF: boolean = true; (* #26 is considered as end of file *)
  47. sLineBreak = LineEnding;
  48. DefaultTextLineBreakStyle : TTextLineBreakStyle = tlbsCRLF;
  49. { Default memory segments (Tp7 compatibility) }
  50. seg0040: Word = $0040;
  51. segA000: Word = $A000;
  52. segB000: Word = $B000;
  53. segB800: Word = $B800;
  54. { The value that needs to be added to the segment to move the pointer by
  55. 64K bytes (BP7 compatibility) }
  56. SelectorInc: Word = $1000;
  57. var
  58. { Mem[] support }
  59. mem : array[0..$7fff-1] of byte absolute $0:$0;
  60. memw : array[0..($7fff div sizeof(word))-1] of word absolute $0:$0;
  61. meml : array[0..($7fff div sizeof(longint))-1] of longint absolute $0:$0;
  62. { C-compatible arguments and environment }
  63. argc:smallint; //!! public name 'operatingsystem_parameter_argc';
  64. argv:PPchar; //!! public name 'operatingsystem_parameter_argv';
  65. { The DOS Program Segment Prefix segment (TP7 compatibility) }
  66. PrefixSeg:Word;public name '__fpc_PrefixSeg';
  67. SaveInt00: FarPointer;public name '__SaveInt00';
  68. SaveInt10: FarPointer;public name '__SaveInt10';
  69. SaveInt75: FarPointer;public name '__SaveInt75';
  70. fpu_status: word;public name '__fpu_status';
  71. AllFilesMask: string [3];
  72. {$ifndef RTLLITE}
  73. { System info }
  74. LFNSupport : boolean;
  75. {$ELSE RTLLITE}
  76. const
  77. LFNSupport = false;
  78. {$endif RTLLITE}
  79. implementation
  80. procedure DebugWrite(const S: string); forward;
  81. procedure DebugWriteLn(const S: string); forward;
  82. const
  83. fCarry = 1;
  84. { used for an offset fixup for accessing the proc parameters in asm routines
  85. that use nostackframe. We can't use the parameter name directly, because
  86. i8086 doesn't support sp relative addressing. }
  87. {$ifdef FPC_X86_CODE_FAR}
  88. extra_param_offset = 2;
  89. {$else FPC_X86_CODE_FAR}
  90. extra_param_offset = 0;
  91. {$endif FPC_X86_CODE_FAR}
  92. {$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
  93. extra_data_offset = 2;
  94. {$else}
  95. extra_data_offset = 0;
  96. {$endif}
  97. type
  98. PFarByte = ^Byte;far;
  99. PFarChar = ^Char;far;
  100. PFarWord = ^Word;far;
  101. PPFarChar = ^PFarChar;
  102. var
  103. __stktop : pointer;public name '__stktop';
  104. __stkbottom : pointer;public name '__stkbottom';
  105. __nearheap_start: pointer;public name '__nearheap_start';
  106. __nearheap_end: pointer;public name '__nearheap_end';
  107. dos_version:Word;public name 'dos_version';
  108. dos_env_count:smallint;public name '__dos_env_count';
  109. dos_argv0 : PFarChar;public name '__fpc_dos_argv0';
  110. {$I registers.inc}
  111. procedure Intr(IntNo: Byte; var Regs: Registers); external name 'FPC_INTR';
  112. procedure MsDos(var Regs: Registers); external name 'FPC_MSDOS';
  113. { invokes int 21h with the carry flag set on entry; used for the LFN functions
  114. to ensure that the carry flag is set on exit on older DOS versions which don't
  115. support them }
  116. procedure MsDos_Carry(var Regs: Registers); external name 'FPC_MSDOS_CARRY';
  117. procedure InstallInterruptHandlers; external name 'FPC_INSTALL_INTERRUPT_HANDLERS';
  118. procedure RestoreInterruptHandlers; external name 'FPC_RESTORE_INTERRUPT_HANDLERS';
  119. function CheckNullArea: Boolean; external name 'FPC_CHECK_NULLAREA';
  120. var
  121. test_fpu_jmpbuf : jmp_buf;
  122. Procedure InterceptInvalidInstruction;
  123. begin
  124. longjmp(test_fpu_jmpbuf, 1);
  125. end;
  126. { Use msdos int21 set/get Interrupt address
  127. to check if coprocessor is present }
  128. {$define FPC_SYSTEM_HAS_SYSINITFPU}
  129. Procedure SysInitFPU;
  130. const
  131. CR0_NE = $20;
  132. CR0_NOT_NE = $FFFF - CR0_NE;
  133. var
  134. { these locals are so we don't have to hack pic code in the assembler }
  135. localfpucw: word;
  136. prevInt06 : FarPointer;
  137. _newcr0_lw : word;
  138. restore_old_int10 : boolean;
  139. begin
  140. restore_old_int10:=false;
  141. localfpucw:=Default8087CW;
  142. asm
  143. fninit
  144. fldcw localfpucw
  145. fwait
  146. end;
  147. if Test8087 < 3 then { i8087/i80287 do not have "native" exception mode (CR0:NE) }
  148. begin
  149. restore_old_int10:=true;
  150. end
  151. else
  152. begin
  153. asm
  154. push es
  155. push ds
  156. { Get previous interrupt 06 handler }
  157. mov ax, $3506
  158. int $21
  159. mov word [prevInt06],bx
  160. mov dx,es
  161. mov word [prevInt06+2],dx
  162. { Install local interrupt 06 handler }
  163. {$ifdef FPC_MM_TINY}
  164. { Do not use SEG here, as this introduces a relocation that
  165. is incompatible with COM executable generation }
  166. mov dx, cs
  167. {$else FPC_MM_TINY}
  168. mov dx, SEG InterceptInvalidInstruction
  169. {$endif FPC_MM_TINY}
  170. mov ds, dx
  171. mov dx, Offset InterceptInvalidInstruction
  172. mov ax, $2506
  173. int $21
  174. pop ds
  175. pop es
  176. end;
  177. if setjmp(test_fpu_jmpbuf)=0 then
  178. begin
  179. asm
  180. db $0f, $20, $c0 { mov eax,cr0 }
  181. { Reset CR0 Numeric Error bit,
  182. to trigger IRQ13 - interrupt $75,
  183. and thus avoid the generation of a $10 trap
  184. which iterferes with video interrupt handler }
  185. and ax,CR0_NOT_NE
  186. db $0f, $22, $c0 { mov cr0,eax }
  187. end;
  188. //writeln(stderr,'Change of cr0 succeeded');
  189. // Test that NE bit is indeed reset
  190. asm
  191. db $0f, $20, $c0 { mov eax,cr0 }
  192. mov _newcr0_lw, ax
  193. end;
  194. if (_newcr0_lw and CR0_NE) = 0 then
  195. restore_old_int10:=true;
  196. end
  197. else
  198. begin
  199. //writeln(stderr,'Change of cr0 failed');
  200. end;
  201. { Restore previous interrupt 06 handler }
  202. asm
  203. push ds
  204. mov ax, $2506
  205. lds dx,[prevInt06]
  206. int $21
  207. pop ds
  208. end;
  209. end;
  210. { Special handler of interrupt $10
  211. not needed anymore
  212. Restore previous interrupt $10 handler }
  213. {$ifndef TEST_FPU_INT10}
  214. if restore_old_int10 then
  215. asm
  216. push ds
  217. mov ax, $2510
  218. lds dx,[SaveInt10]
  219. int $21
  220. pop ds
  221. end;
  222. {$endif ndef TEST_FPU_INT10}
  223. end;
  224. {$I system.inc}
  225. {$I tinyheap.inc}
  226. {$I ports.inc}
  227. procedure DebugWrite(const S: string);
  228. begin
  229. asm
  230. {$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
  231. push ds
  232. lds si, S
  233. {$else}
  234. mov si, S
  235. {$endif}
  236. {$ifdef FPC_ENABLED_CLD}
  237. cld
  238. {$endif FPC_ENABLED_CLD}
  239. lodsb
  240. mov cl, al
  241. xor ch, ch
  242. jcxz @@zero_length
  243. mov ah, 2
  244. @@1:
  245. lodsb
  246. mov dl, al
  247. int 21h
  248. loop @@1
  249. @@zero_length:
  250. {$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
  251. pop ds
  252. {$endif}
  253. end ['ax','bx','cx','dx','si','di'];
  254. end;
  255. procedure DebugWriteLn(const S: string);
  256. begin
  257. DebugWrite(S);
  258. DebugWrite(#13#10);
  259. end;
  260. {*****************************************************************************
  261. ParamStr/Randomize
  262. *****************************************************************************}
  263. var
  264. internal_envp : PPFarChar = nil;
  265. procedure setup_environment;
  266. var
  267. env_count : smallint;
  268. cp, dos_env: PFarChar;
  269. begin
  270. env_count:=0;
  271. dos_env:=Ptr(MemW[PrefixSeg:$2C], 0);
  272. cp:=dos_env;
  273. while cp^<>#0 do
  274. begin
  275. inc(env_count);
  276. while (cp^ <> #0) do
  277. inc(cp); { skip to NUL }
  278. inc(cp); { skip to next character }
  279. end;
  280. internal_envp := getmem((env_count+1) * sizeof(PFarChar));
  281. cp:=dos_env;
  282. env_count:=0;
  283. while cp^<>#0 do
  284. begin
  285. internal_envp[env_count] := cp;
  286. inc(env_count);
  287. while (cp^ <> #0) do
  288. inc(cp); { skip to NUL }
  289. inc(cp); { skip to next character }
  290. end;
  291. internal_envp[env_count]:=nil;
  292. dos_env_count := env_count;
  293. if dos_version >= $300 then
  294. begin
  295. if cp=dos_env then
  296. inc(cp);
  297. inc(cp, 3);
  298. dos_argv0 := cp;
  299. end
  300. else
  301. dos_argv0 := nil;
  302. end;
  303. function envp:PPFarChar;public name '__fpc_envp';
  304. begin
  305. if not assigned(internal_envp) then
  306. setup_environment;
  307. envp:=internal_envp;
  308. end;
  309. procedure setup_arguments;
  310. var
  311. I: SmallInt;
  312. pc: PChar;
  313. pfc: PFarChar;
  314. quote: Char;
  315. count: SmallInt;
  316. arglen, argv0len: SmallInt;
  317. argblock: PChar;
  318. arg: PChar;
  319. doscmd : string[129]; { Dos commandline copied from PSP, max is 128 chars +1 for terminating zero }
  320. begin
  321. { force environment to be setup so dos_argv0 is loaded }
  322. envp;
  323. { load commandline from psp }
  324. SetLength(doscmd, Mem[PrefixSeg:$80]);
  325. for I := 1 to length(doscmd) do
  326. doscmd[I] := Chr(Mem[PrefixSeg:$80+I]);
  327. doscmd[length(doscmd)+1]:=#0;
  328. {$IfDef SYSTEM_DEBUG_STARTUP}
  329. Writeln(stderr,'Dos command line is #',doscmd,'# size = ',length(doscmd));
  330. {$EndIf }
  331. { find argv0len }
  332. argv0len:=0;
  333. if dos_argv0<>nil then
  334. begin
  335. pfc:=dos_argv0;
  336. while pfc^<>#0 do
  337. begin
  338. Inc(argv0len);
  339. Inc(pfc);
  340. end;
  341. end;
  342. { parse dos commandline }
  343. pc:=@doscmd[1];
  344. count:=1;
  345. { calc total arguments length and count }
  346. arglen:=argv0len+1;
  347. while pc^<>#0 do
  348. begin
  349. { skip leading spaces }
  350. while pc^ in [#1..#32] do
  351. inc(pc);
  352. if pc^=#0 then
  353. break;
  354. { calc argument length }
  355. quote:=' ';
  356. while (pc^<>#0) do
  357. begin
  358. case pc^ of
  359. #1..#32 :
  360. begin
  361. if quote<>' ' then
  362. inc(arglen)
  363. else
  364. break;
  365. end;
  366. '"' :
  367. begin
  368. if quote<>'''' then
  369. begin
  370. if pchar(pc+1)^<>'"' then
  371. begin
  372. if quote='"' then
  373. quote:=' '
  374. else
  375. quote:='"';
  376. end
  377. else
  378. inc(pc);
  379. end
  380. else
  381. inc(arglen);
  382. end;
  383. '''' :
  384. begin
  385. if quote<>'"' then
  386. begin
  387. if pchar(pc+1)^<>'''' then
  388. begin
  389. if quote='''' then
  390. quote:=' '
  391. else
  392. quote:='''';
  393. end
  394. else
  395. inc(pc);
  396. end
  397. else
  398. inc(arglen);
  399. end;
  400. else
  401. inc(arglen);
  402. end;
  403. inc(pc);
  404. end;
  405. inc(arglen); { for the null terminator }
  406. inc(count);
  407. end;
  408. { set argc and allocate argv }
  409. argc:=count;
  410. argv:=AllocMem((count+1)*SizeOf(PChar));
  411. { allocate a single memory block for all arguments }
  412. argblock:=GetMem(arglen);
  413. { create argv[0] }
  414. argv[0]:=argblock;
  415. arg:=argblock;
  416. if dos_argv0<>nil then
  417. begin
  418. pfc:=dos_argv0;
  419. while pfc^<>#0 do
  420. begin
  421. arg^:=pfc^;
  422. Inc(arg);
  423. Inc(pfc);
  424. end;
  425. end;
  426. arg^:=#0;
  427. Inc(arg);
  428. pc:=@doscmd[1];
  429. count:=1;
  430. while pc^<>#0 do
  431. begin
  432. { skip leading spaces }
  433. while pc^ in [#1..#32] do
  434. inc(pc);
  435. if pc^=#0 then
  436. break;
  437. { copy argument }
  438. argv[count]:=arg;
  439. quote:=' ';
  440. while (pc^<>#0) do
  441. begin
  442. case pc^ of
  443. #1..#32 :
  444. begin
  445. if quote<>' ' then
  446. begin
  447. arg^:=pc^;
  448. inc(arg);
  449. end
  450. else
  451. break;
  452. end;
  453. '"' :
  454. begin
  455. if quote<>'''' then
  456. begin
  457. if pchar(pc+1)^<>'"' then
  458. begin
  459. if quote='"' then
  460. quote:=' '
  461. else
  462. quote:='"';
  463. end
  464. else
  465. inc(pc);
  466. end
  467. else
  468. begin
  469. arg^:=pc^;
  470. inc(arg);
  471. end;
  472. end;
  473. '''' :
  474. begin
  475. if quote<>'"' then
  476. begin
  477. if pchar(pc+1)^<>'''' then
  478. begin
  479. if quote='''' then
  480. quote:=' '
  481. else
  482. quote:='''';
  483. end
  484. else
  485. inc(pc);
  486. end
  487. else
  488. begin
  489. arg^:=pc^;
  490. inc(arg);
  491. end;
  492. end;
  493. else
  494. begin
  495. arg^:=pc^;
  496. inc(arg);
  497. end;
  498. end;
  499. inc(pc);
  500. end;
  501. arg^:=#0;
  502. Inc(arg);
  503. {$IfDef SYSTEM_DEBUG_STARTUP}
  504. Writeln(stderr,'dos arg ',count,' #',strlen(argv[count]),'#',argv[count],'#');
  505. {$EndIf SYSTEM_DEBUG_STARTUP}
  506. inc(count);
  507. end;
  508. end;
  509. function paramcount : longint;
  510. begin
  511. if argv=nil then
  512. setup_arguments;
  513. paramcount := argc - 1;
  514. end;
  515. function paramstr(l : longint) : string;
  516. begin
  517. if argv=nil then
  518. setup_arguments;
  519. if (l>=0) and (l+1<=argc) then
  520. paramstr:=strpas(argv[l])
  521. else
  522. paramstr:='';
  523. end;
  524. procedure randomize;
  525. var
  526. hl : longint;
  527. regs : Registers;
  528. begin
  529. regs.AH:=$2C;
  530. MsDos(regs);
  531. hl:=regs.DX;
  532. randseed:=hl*$10000+ regs.CX;
  533. end;
  534. {*****************************************************************************
  535. System Dependent Exit code
  536. *****************************************************************************}
  537. procedure system_exit;
  538. var
  539. h : byte;
  540. begin
  541. RestoreInterruptHandlers;
  542. for h:=0 to max_files-1 do
  543. if openfiles[h] then
  544. begin
  545. {$ifdef SYSTEMDEBUG}
  546. writeln(stderr,'file ',h,' "',opennames[h],'" not closed at exit');
  547. {$endif SYSTEMDEBUG}
  548. if h>=5 then
  549. do_close(h);
  550. end;
  551. {$ifndef FPC_MM_TINY}
  552. if not CheckNullArea then
  553. writeln(stderr, 'Nil pointer assignment');
  554. {$endif FPC_MM_TINY}
  555. asm
  556. mov al, byte [exitcode]
  557. mov ah, 4Ch
  558. int 21h
  559. end;
  560. end;
  561. {*****************************************************************************
  562. SystemUnit Initialization
  563. *****************************************************************************}
  564. procedure InitDosHeap;
  565. type
  566. {$if defined(FPC_X86_DATA_FAR) or defined(FPC_X86_DATA_HUGE)}
  567. TPointerArithmeticType = HugePointer;
  568. {$else}
  569. TPointerArithmeticType = Pointer;
  570. {$endif}
  571. begin
  572. RegisterTinyHeapBlock_Simple_Prealigned(__nearheap_start, TPointerArithmeticType(__nearheap_end) - TPointerArithmeticType(__nearheap_start));
  573. end;
  574. function CheckLFN:boolean;
  575. var
  576. regs : Registers;
  577. RootName : pchar;
  578. buf : array [0..31] of char;
  579. begin
  580. { Check LFN API on drive c:\ }
  581. RootName:='C:\';
  582. { Call 'Get Volume Information' ($71A0) }
  583. regs.AX:=$71a0;
  584. regs.ES:=Seg(buf);
  585. regs.DI:=Ofs(buf);
  586. regs.CX:=32;
  587. regs.DS:=Seg(RootName^);
  588. regs.DX:=Ofs(RootName^);
  589. MsDos_Carry(regs);
  590. { If carryflag=0 and LFN API bit in ebx is set then use Long file names }
  591. CheckLFN:=(regs.Flags and fCarry=0) and (regs.BX and $4000=$4000);
  592. end;
  593. procedure SysInitStdIO;
  594. begin
  595. OpenStdIO(Input,fmInput,StdInputHandle);
  596. OpenStdIO(Output,fmOutput,StdOutputHandle);
  597. OpenStdIO(ErrOutput,fmOutput,StdErrorHandle);
  598. {$ifndef FPC_STDOUT_TRUE_ALIAS}
  599. OpenStdIO(StdOut,fmOutput,StdOutputHandle);
  600. OpenStdIO(StdErr,fmOutput,StdErrorHandle);
  601. {$endif FPC_STDOUT_TRUE_ALIAS}
  602. end;
  603. function GetProcessID: SizeUInt;
  604. begin
  605. GetProcessID := PrefixSeg;
  606. end;
  607. function CheckInitialStkLen(stklen : SizeUInt) : SizeUInt;
  608. begin
  609. result := stklen;
  610. end;
  611. begin
  612. StackBottom := __stkbottom;
  613. StackLength := __stktop - __stkbottom;
  614. InstallInterruptHandlers;
  615. DetectFPU;
  616. if Test8087>0 then
  617. SysInitFPU;
  618. { To be set if this is a GUI or console application }
  619. IsConsole := TRUE;
  620. {$ifdef FPC_HAS_FEATURE_DYNLIBS}
  621. { If dynlibs feature is disabled,
  622. IsLibrary is a constant, which can thus not be set to a value }
  623. { To be set if this is a library and not a program }
  624. IsLibrary := FALSE;
  625. {$endif def FPC_HAS_FEATURE_DYNLIBS}
  626. { Setup heap }
  627. InitDosHeap;
  628. SysInitExceptions;
  629. {$ifdef FPC_HAS_FEATURE_UNICODESTRINGS}
  630. initunicodestringmanager;
  631. {$endif def FPC_HAS_FEATURE_UNICODESTRINGS}
  632. { Setup stdin, stdout and stderr }
  633. SysInitStdIO;
  634. { Setup environment and arguments }
  635. { Done on request only Setup_Environment; }
  636. { Done on request only Setup_Arguments; }
  637. {$ifndef RTLLITE}
  638. { Use LFNSupport LFN }
  639. LFNSupport:=CheckLFN;
  640. if LFNSupport then
  641. begin
  642. FileNameCasePreserving:=true;
  643. AllFilesMask := '*';
  644. end
  645. else
  646. {$endif ndef RTLLITE}
  647. AllFilesMask := '*.*';
  648. { Reset IO Error }
  649. InOutRes:=0;
  650. {$ifdef FPC_HAS_FEATURE_THREADING}
  651. InitSystemThreads;
  652. {$endif}
  653. end.