1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
|
namespace run;
include rt;
// the compiler pokes at headers and raw memory on purpose and runs at full speed: no index bounds checks
unbounded;
// ============================================================================
// compiler.zc: the resident zc compiler. it lives in the genome next to the
// runtime, compiles source straight to vm words in memory, and is visible to
// the programs it compiles: every function and global here can be called or
// patched by name, and parse words (immediate fns) run inside it.
// ============================================================================
// how this file is organized, in the order a source file travels through it:
//
// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a
// kind (tk), a value (tv: number or interned name) and a
// place (line, offset, length). sources nest: stdin, a
// token list for macros, a string for eval.
// 2 symbols every name is interned once in a string pool; symbols
// (functions, globals, constants, keywords, types) hang
// off the interned name and shadow one another.
// 3 types records in the type area, hash-consed, so comparing two
// types is comparing two words. typing is optional: an
// untyped program is all "word".
// 4 analyzer the ownership/lifetime pass. it runs while the parser
// emits code, tracking for every local whether it owns an
// object in this block (m), moves one up and out (u), or
// moves one down into a lower scope (d). borrowing is
// tracked separately. the pass inserts frees, and joins
// merge that knowledge at
// if/else, loops and switches.
// 5 expressions precedence climbing over the operator tables, which a
// program can extend at compile time.
// 6 statements blocks, control flow, switch, asm blocks, quote/grab.
// 7 functions signatures, prologues, closures, methods, generics,
// fattening (typed new) and the top level.
// 8 setup the keyword table, built-in names, entry point.
// 9 check mode the same compiler run over an in-memory document for the
// language server: errors and hints become records.
//
// the compiler emits vm words directly into memory at "here" (there is no
// intermediate tree). a forward reference emits a placeholder literal and
// remembers the site on a patch chain; defining the name patches the chain.
// the compiler is itself written in zc and lives in the genome next to rt.zc,
// so a program being compiled can call any function in this file, and
// immediate fns (parse words) run in the middle of compilation and can read
// tokens, emit code, or add operators. a few conventions that recur:
// - "plain word": most values are untyped 64-bit words. a pointer is just a
// word that happens to be a heap address.
// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op.
// - names ending in an s_ prefix are interned spellings the parser compares
// against by pointer, which is why intern exists.
// - serr reports and carries on; die reports and skips input to the next
// ; or }. nothing in here raises or unwinds.
// - a backtick outside a comment or a literal ends the source (srcend). zc0,
// the c bootstrap, reads the same way, so a backtick in a comment is fine.
//
// ---- compiler data region (@map-cc) ----
// src/tools/genmap computes these addresses from the compiler table sizes for each
// build. later regions (the type area, local types, exit records) are computed
// from these names.
// @map-cc
const IHN = 0x8000;
const R_IH = 0x90100;
const R_POOL = 0x98100;
const R_POOLEND = 0xD8100;
const NS = 0x4000;
const R_SYM = 0xD8200;
const sname_BASE = 0xD8200;
const skind_BASE = 0xDC200;
const sval_BASE = 0xE0200;
const ssig_BASE = 0xE4200;
const sflag_BASE = 0xE8200;
const sfix_BASE = 0xEC200;
const snext_BASE = 0xF0200;
const NL = 0x200;
const lname_BASE = 0xF4200;
const lslot_BASE = 0xF4400;
const lhome_BASE = 0xF4600;
const lstate_BASE = 0xF4800;
const lowner_BASE = 0xF4A00;
const lused_BASE = 0xF4C00;
const larr_BASE = 0xF4E00;
const lown_BASE = 0xF5000;
const NF = 0x8000;
const fsite_BASE = 0xF5200;
const fnext_BASE = 0xFD200;
const R_SNAP_BASE = 0x105200;
const R_QBUF_BASE = 0x109200;
const R_SRC_BASE = 0x10D200;
const R_SEQ_BASE = 0x10D600;
const bname_BASE = 0x10DE00;
const bprec_BASE = 0x10DE40;
const bseq_BASE = 0x10DE80;
const blen_BASE = 0x10DEC0;
const uname_BASE = 0x10DF00;
const useq_BASE = 0x10DF20;
const ulen_BASE = 0x10DF40;
const aname_BASE = 0x10DF60;
const abin_BASE = 0x10DFA0;
const kops_BASE = 0x10E000;
const R_CTX_BASE = 0x10E200;
const R_LOOP_BASE = 0x10EA00;
const R_ASM = 0x10EE00;
const ASM_LABELS = 0x40;
const ASM_REFS = 0x100;
const ASM_WORDS = 0x400;
const EXIT_WORDS = 0xA00;
const ENUM_MEMBERS = 0x80;
const TAGGED_ENUMS = 0x10;
const STRUCT_METHODS = 0x20;
const GENERATED_WORDS = 0x2000;
const RANGE_FACTS = 0x20;
const JOIN_PATHS = 0x40;
const O3_REGIONS = 0xA;
// @end-map
// table bases start in the generated map; growth copies columns without changing indices.
// interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id.
char** sname = sname_BASE;
// kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC.
word skind = skind_BASE;
// value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind.
word sval = sval_BASE;
// packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags.
word ssig = ssig_BASE;
// definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC.
word sflag = sflag_BASE;
// head of unresolved code references for each symbol; references are patched when the symbol is defined.
word sfix = sfix_BASE;
// previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain.
word snext = snext_BASE;
// retained source metadata for functions that can be transformed at runtime.
word fnmeta;
int syntax_host_floor, syntax_host_from; word syntax_host_symbols;
// interned name of each currently active local variable or parameter.
char** lname = lname_BASE;
// frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame.
word lslot = lslot_BASE;
// scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks.
word lhome = lhome_BASE;
// ownership state of each local: plain, m, u, d, dead, child or borrowed.
word lstate = lstate_BASE;
// owner local of a borrowed or container-owned local; lroot(i) follows this chain.
word lowner = lowner_BASE;
// source line of each local's last tracked use; zero means it has not been used.
word lused = lused_BASE;
// nonzero for locals stored in place as fixed arrays or structs; their expression value is their address.
word larr = larr_BASE;
// nonzero when a local is responsible for releasing its owned object.
word lown = lown_BASE;
// code location to patch for each unresolved reference record.
word fsite = fsite_BASE;
// next unresolved reference record, stored as index plus one; zero ends a patch chain.
word fnext = fnext_BASE;
int symcap = NS, loccap = NL, fixcap = NF;
const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP;
// interned text and type records never move: only their index vectors grow.
char** namehash = R_IH; word poolend = R_POOLEND;
word poolblocks, typeblocks;
int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL;
word typelist = R_TY, tyend = R_TYEND;
int typecap = 1024;
// growing scratch regions retain offsets used by nested parser frames.
word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE;
word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE;
word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE;
word R_SEQ = R_SEQ_BASE; int R_SEQ_cap = bname_BASE - R_SEQ_BASE;
word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE;
word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE;
word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
// operator columns grow independently of their initial map reservations.
char** bname = bname_BASE;
word bprec = bprec_BASE;
word bseq = bseq_BASE;
word blen = blen_BASE;
char** uname = uname_BASE;
word useq = useq_BASE;
word ulen = ulen_BASE;
char** aname = aname_BASE;
word abin = abin_BASE;
word kops = kops_BASE;
int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE;
// ---- token kinds, symbol kinds and flags ----
const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6;
const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8;
const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768;
// variable states for the lifetime analyzer
const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6;
// expression classes: plain word, a new object, or a bare variable (C_VAR + index)
const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2;
// lvalue kinds
const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4;
// opcodes
const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7;
const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20;
const int XOP0 = 22; // first enhancement opcode
const int OP_FLIT = 600, OP_FADD = 601, OP_FSUB = 602, OP_FMUL = 603, OP_FDIV = 604;
const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609;
const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal
const int SEQREF = -2; // in an operator sequence: the next word names a symbol
// ---- state ----
int chk_heap;
int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree;
char* module_name; word module_imports;
int nbin, nun, nasg, nkop, seqtop;
char* mnem = "q@!$_~cjzr,?.+-*&|^><%";
int check_on = 1; // set to 0 to switch the analyzer off for what follows
int bound_on = 1; // set to 0 by unbounded; to switch index bounds checks off for what follows
int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot;
// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended
int toff = -1, toklen, tpend = -1, tpline;
int ntok; // tokens taken so far, so a parse word that reads nothing can be caught
int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal
char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100;
// doc comments: the comment lines directly above a declaration (no blank line
// between them or before the declaration) are its documentation. the lexer
// collects them in dbuf, dlast is the line of the last one, and dline the
// line of the token that follows them. only when docs are on.
int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string
// line tables: lines on; (the default) records, for each function compiled, its entry, its end and the
// source line of each statement, as a line block among the code that rt.line_blocks chains (see
// rt.zc). lt_buf holds the pairs of the functions being compiled, innermost last, each as a frame
// [outer frame, entry, name, file, 0, offset << 32 | line ...]; lt_base is the innermost frame.
// lt_head is the newest block of an image being built, whose own rt.line_blocks it will become.
int lines_on = 1;
// //:lines NAME (see mark_line): the markers the lexer has met, [lines before the file, NAME] each, and the
// one in force, which a top-level item applies when it is reached
word lt_marks; int lt_nmarks, lt_markcap, lt_next_mark; char* lt_mark_file; int lt_mark_base;
word lt_buf, lt_head, lt_file_at; int lt_top, lt_cap, lt_base = 0 - 1, lt_file_img; char* lt_file;
char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect;
char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300;
int pk_tk, pk_tv, pq_n; word[4] pq_tk; word[4] pq_tv; word[4] pq_gl; word[4] pq_ln; // up to 4 tokens of lookahead
int here, hdelta, hlimit, ptop;
int code_chunk = rt.CODE_END;
word image_buffer, image_words; int image_active, image_org, image_capacity, image_size;
int nloc, lbase, fdepth, fslots, fchk, fbnd, bnxt, fret, live, bxdepth, epichain, fframe, infn;
// a global that borrows a local owner through g = m*x. each entry is the global's
// symbol, the local, and the block depth of the store; the lend ends when the same
// global is assigned again in that block, and is an error if the block can end first.
word lendrec; int nlend, lendcap, xlend = -1;
int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u)
int fn_return_type; // a consistent return type inferred from the function body
// effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative.
const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64;
word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base;
word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy;
int borrow_pending, borrow_depth, borrow_jump = -1, borrow_entry; word borrow_slots;
int argorigin = -1, argtemporary, argowner = -1; word argowner_fact;
int stubgen; // 1 while an extern's generated stub is compiled: the stub gets no hints
word xprojection; int af_mutation_tick;
int xbor = -1; // local a pointer value is borrowed from, or -1
int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on
int lsp, loopbase, ctxsp, snaptop;
// defer: the statements registered and not yet run, DFR_W words each (the block
// depth of the defer, its token list, whether the analyzer was on, whether compiling
// it failed, so the error is reported once, and the locals there were at the defer),
// newest last. dfr_base is where the current function's entries start. dfr_on is set
// while a deferred statement is compiled; dfr_lsp and dfr_ctx say where its own loops
// and function begin.
const int DFR_MAX = 256, DFR_W = 5;
word[1280] dfr; int ndfr, dfr_base, dfr_on, dfr_lsp, dfr_ctx;
int lv, lvk, lvi, kw_expr, xcls, fty;
// interned spellings the parser compares against
int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec;
int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror;
int s_dot, s_fn, s_var, s_const, s_else, s_as, s_unchecked, s_unbounded, s_immediate, s_m, s_d, s_u;
int s_q, s_case, s_default, s_fovf, s_start, s_main, s_free, s_recycle, s_own, s_qnew, s_enhance, s_micro, s_vm;
int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_frame_grow, s_frame_leave;
int s_copy_w;
int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static;
// ---- types: kinds, tables and parser state (see the types section) ----
const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6;
// dyn: T[?], a = element, n = 1 if fixed
const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13;
const int NTL = 1024; // the first NTL words list every record
// type per local, declared tag per local, type per symbol
const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL;
word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE;
int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr;
int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name
int tc_on; // the optional typechecker, off unless a program says typecheck on;
// type, literal 0, float, byte string, dry runs (typeof), unique giveaway
word xty; int xzero, xfloat, xstr, dry, xgive;
// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts
const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768;
int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict;
word if_edges, if_value, if_local;
int ifrcap = NIF, ifxcap = IFN, iftcap = IFT;
int fldst, fldi; // the one struct a bare address's field name resolved to
char*[8] tpn; int ntp; // type parameters in scope
// reading a return type: unknown names are type parameters declared after the function's name
int tvauto;
// the typedefs of the blocks being compiled: three words each (the symbol, the block depth and the
// compile context it was made in), newest last. a block's end unlinks them from their names.
word tdstk; int ntd, tdcap;
word readonly_types;
int dty, dtag; // result of dtype()
int s_typedef;
int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_lines, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t;
int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob, s_bchk, s_bnd;
int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32;
// ---- o3 region state ----
// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer.
int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0;
// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten;
// operator names used by numeric type checks.
word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval;
int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_ck, ft_nops, fs_ok, xstamp;
// what each live loop's body may change, and its hoisted checks (one 56-word
// record per loop depth, the first 64 depths; see body_scan and hoist_guard)
const int LPI_N = 64, LPI_W = 60;
word lpinfo; // allocated at the first loop
// --explain-checks: on, the unit's source (to turn offsets into columns), its
// file name and the function being compiled
int xc_on; word xc_src; char* xc_fn = "<eval>"; char* xc_file = "<eval>"; int xc_hook;
// names body_scan looks for
int sx_push, sx_pop, sx_resize, sx_defer, sx_asm, sx_quote, sx_new, sx_int, sx_char, sx_bool, sx_float, sx_if, sx_else, sx_while, sx_for, sx_switch, sx_do;
// local lower bound / len(array); one proof record per live loop
int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial;
int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s;
int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled
word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...)
// generic functions. the template (type parameters stay variables) is compiled once, as before,
// and its tokens are kept; a call whose type arguments are all known gets an instance, the same
// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
// inside it mean the real type. instances are compiled after the item that asked for them.
int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read
int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined
int fn_method_compile, fnpatchseq;
word gtab; int gt_n, gtcap = 64, gicap = 128; word gt_sym, gt_tok, gt_np, gt_module, gt_imports, gt_source; int gt_nm; // the columns are allocated at the first generic function
int gi_n; word gi_t, gi_sym, gi_ar, gi_done;
int ginst; word[8] gnm; word[8] gib; int gng, ginsn; // compiling an instance: the names and what they stand for
int s_this;
const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */
const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */
// ---- closure compilation state ----
// closures being compiled, innermost last: the enclosing function's locals
// (lo .. hi), how many variables the body has captured so far (names and the
// enclosing local each came from), and the body's own hidden env parameter
int nclo, closure_cap = NCLO, capture_width = NCAP;
word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial;
word[NCLO] clo_hi_initial; word clo_hi = clo_hi_initial;
word[NCLO] clo_n_initial; word clo_n = clo_n_initial;
word[NCLO] clo_env_initial; word clo_env = clo_env_initial;
word[NCLO] clo_ret_initial; word clo_ret = clo_ret_initial;
char*[256] clo_name_initial; char** clo_name = clo_name_initial;
word[256] clo_loc_initial; word clo_loc = clo_loc_initial;
word[256] clo_via_initial; word clo_via = clo_via_initial;
// ---- errors ----
// the compiler reports up to 20 errors before it stops. a syntax error
// (die) skips the token it happened on and sets panic, which skips ahead to
// the next ; or } (or the next top-level item) and keeps quiet until then.
// other errors (serr) just report: the parse is still in step.
//
// check mode (cmode) is for the language server, which runs the real compiler
// over a document it holds in memory. nothing is printed and nothing halts:
// each error is written into dgbuf as a record [severity, line, offset,
// length, chars...] and giveup raises chkabort, which makes the lexer return
// end of file so every parsing loop winds down by itself.
int nerr, panic;
// bits kept in a local's ltag for the hints: assigned after its declaration,
// read, declared with let, initialized with a literal; the size of the object
// a new gave it (bits 16 to 39); and how many times it was used (from bit 56)
const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
const int NEWTYPED = 0x800000;
word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address
// storage facts are shared by aliases. they describe one allocation, not a
// variable slot: [length, lifetime, kind, allocation place, end place, owner].
// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle.
word[NL] lafact_initial; word lafact = lafact_initial;
word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact;
// value previews are separate from optimization facts. each reference keeps
// the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes.
word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv;
// local name lookup keeps a head for each spelling and a shadow link per slot.
word[NL] lprev_initial; word lprev = lprev_initial;
word[256] lkeys_initial; word lkeys = lkeys_initial;
word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn;
int s_new_k, s_recycle_leaf;
// dgprev: the next record sits at the end of the previous token
// chk_hook, when set, is called after each top-level item of the document in check mode: the language
// server streams diagnostics and drops a check whose source has been replaced
word chk_hook;
int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode;
// expand a diagnostic in progress, keeping its output offset within the buffer.
int dg_expand() {
dgbuf = chk_resize(dgbuf, dgpos + 5 + rt.outn, dgcap * 2);
dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5;
return 0;
}
// diagnostic records for check mode. dg_open starts a record [severity, line,
// offset, length, text length, text...] at the current token (or just past the
// previous one when dgprev is set) and redirects the error text into it with
// outmode 2; dg_close seals it with the length of the text and restores output.
// severity 1 is an error, 2 a warning, and 4 a hint.
// completion: the language server asks what is in scope at a place in the document. probe_off is that place
// (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or
// before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after
// a dot, what is before the dot. the check stops as soon as it has what was asked for
int probe_off = -1, probe_has, probe_n, probe_cap, probe_static;
char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem;
char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include
int dg_close() {
if (!dgopen) return 0;
if (!dgdrop) { dgbuf[dgpos + 4] = rt.outn; dgpos = dgpos + 5 + rt.outn; dgn++; }
dgdrop = 0;
dgopen = 0; rt.outmode = dgmode; rt.outgrow = dggrow;
return 0;
}
// open a check-mode diagnostic record of the given severity.
int dg_open(int sev) {
dg_close();
if (dgpos + 205 > dgcap) {
int cap = dgcap; while (cap < dgpos + 205) cap = cap * 2;
dgbuf = chk_resize(dgbuf, dgpos, cap); dgcap = cap;
}
if (dgprev && tpend >= 0) {
dgbuf[dgpos + 1] = tpline;
dgbuf[dgpos + 2] = tpend - 1;
dgbuf[dgpos + 3] = 1;
}
else { dgbuf[dgpos + 1] = tline; dgbuf[dgpos + 2] = toff; dgbuf[dgpos + 3] = toklen; }
dgbuf[dgpos] = sev;
// inside an included file the document has no place for the finding: hints and warnings about a library are
// left out, and an error is shown on the include that brought the file in, with where it really is
if (incdepth > 0) {
dgbuf[dgpos + 1] = incplace & 0xFFFFFF; dgbuf[dgpos + 2] = ((incplace >> 40) & 0x1FFFFF) - 1; dgbuf[dgpos + 3] = (incplace >> 24) & 0xFFFF;
if (sev != 1) dgdrop = 1;
}
rt.outbuf = dgbuf + dgpos + 5; rt.outn = 0; rt.outcap = dgcap - dgpos - 5;
dggrow = rt.outgrow; rt.outgrow = dg_expand;
rt.outmode = 2; dgopen = 1;
if (incdepth > 0 && sev == 1) { rt.put_s("in "); rt.put_s(curinc); rt.put_s(", line "); rt.put_n(tline); rt.put_s(": "); }
return 0;
}
word[256] dgseen_initial; word dgseen = dgseen_initial; int dgseen_cap = 256, dgseen_n;
// start and stop a check run. chk_begin points the diagnostics at a buffer
// the caller owns; chk_end flushes any open record and restores output.
int chk_begin(int buf, int cap) {
cmode = 1; chkabort = 0; chkfuel = 0; dgbuf = buf; dgcap = cap; dgpos = 0; dgn = 0; dgopen = 0;
nerr = 0; panic = 0; dgmode = rt.outmode; rt.fill_w(dgseen, 0, dgseen_cap); dgseen_n = 0;
return 0;
}
// end check mode and put the output back the way it was.
int chk_end() {
dg_close();
cmode = 0;
rt.outmode = dgmode;
return 0;
}
// a hint is a finding that isn't an error: the program still compiles, and
// only check mode reports it. hint_begin(place) opens the record at a packed
// place (see tpack) and says whether to write the text; dg_close ends it.
// one hint per source position. a growing set keeps large documents linear
// instead of rescanning every previous diagnostic for each local.
int dg_hash(int key, int cap) { return ((key ^ (key >> 21)) * 0x9E37) & (cap - 1); }
int dg_dup() {
int key = ((toff + 1) << 24) | tline, i, k, cap; word old;
if (dgseen_n + dgseen_n >= dgseen_cap) {
old = dgseen; cap = dgseen_cap * 2; dgseen = chk_resize(0, 0, cap);
i = 0;
while (i < dgseen_cap) {
if (old[i]) { k = dg_hash(old[i], cap); while (dgseen[k]) k = (k + 1) & (cap - 1); dgseen[k] = old[i]; }
i++;
}
dgseen_cap = cap;
}
i = dg_hash(key, dgseen_cap);
while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); }
dgseen[i] = key; dgseen_n++; return 0;
}
// hints on; hints small; hints off; set how many hints the code after it gets:
// on uses informational ranges, small uses compact hint markers, and off
// suppresses hints. each directive is kept with its byte
// offset, so a hint is judged by where its code is, not where it was reported.
const int HINT_SMALL = 1, HINT_ALL = 2;
word hl_off, hl_lev; int hl_n, hl_cap = 16;
int hint_set(int level, int at) {
if (!cmode || dry || isp != 0 || chkind != 3) return 0;
if (hl_n && hl_off[hl_n - 1] >= at) return 0;
if (!hl_off) { hl_cap = 16; hl_off = chk_resize(0, 0, hl_cap); hl_lev = chk_resize(0, 0, hl_cap); }
if (hl_n >= hl_cap) { hl_off = chk_resize(hl_off, hl_n, hl_cap * 2); hl_lev = chk_resize(hl_lev, hl_n, hl_cap * 2); hl_cap = hl_cap * 2; }
hl_off[hl_n] = at; hl_lev[hl_n] = level; hl_n++;
return 0;
}
// the hint level at byte offset o: 2 all, 1 small, 0 off
int hint_level(int o) {
int k = hl_n;
while (k) { k--; if (hl_off[k] <= o) return hl_lev[k]; }
return 2;
}
// start a check-mode hint at token position p, or say no when hints are off or
// this kind (HINT_SMALL or HINT_ALL) is not wanted there.
int hint_begin(int p, int kind) {
int sv, ok;
if (!cmode || dry || panic || chkabort || stubgen) return 0;
int level = hint_level(((p >> 40) & 0x1FFFFF) - 1);
if (!level) return 0;
sv = tpack(); tunpack(p);
ok = !dg_dup();
if (ok) { if (level == 1) dg_open(4); else dg_open(3); ok = dgopen; }
tunpack(sv);
return ok;
}
// error plumbing. errat prints the "zc: line N:" prefix (or opens a record in
// check mode) and counts the error; errend ends the line and gives up after
// 20; giveup prints the total and halts (check mode: sets chkabort, which
// makes the lexer return end of file so the parse unwinds). fatal reports and
// stops at once; serr just reports; die reports and also skips input to the
// next ; or } (panic) so one mistake does not become a screen of errors.
int errat() {
if (cmode) {
dg_open(1);
} else {
rt.put_s("zc: line ");
rt.put_n(tline);
rt.put_s(": ");
}
nerr++;
return 0;
}
// stop with exit status 1 after compile errors (a runtime trap keeps rt.halt).
int errexit() { rt.syscall(rt.SYS_EXIT, 1, 0, 0); return 0; }
// finish an error line; the compiler stops after 20 errors.
int errend() {
if (cmode) { dg_close(); if (nerr >= 20) chkabort = 1; return 0; }
rt.put_c(10);
if (nerr >= 20) { rt.put_s("zc: too many errors, stopping\n"); errexit(); }
return 0;
}
// stop compiling after errors and report how many there were.
int giveup() {
if (cmode) { dg_close(); chkabort = 1; return 0; }
rt.put_s("zc: "); rt.put_n(nerr); if (nerr == 1) rt.put_s(" error\n"); else rt.put_s(" errors\n"); errexit();
return 0;
}
// report a fatal error and stop, or record it in check mode.
int fatal(char* msg) {
errat(); rt.put_s(msg);
if (cmode) { dg_close(); chkabort = 1; return 0; }
rt.put_c(10); errexit();
return 0;
}
// report a syntax error at the current token, once per recovery.
int serr(char* msg) {
if (!panic && !dry) {
errat();
rt.put_s(msg);
errend();
}
return 0;
}
// report an error that names something, then the message.
int serrn(char* name, char* msg) {
if (!panic && !dry) {
errat();
rt.put_s(name);
rt.put_s(": ");
rt.put_s(msg);
errend();
}
return 0;
}
// report an error and skip to the next top-level item.
int die(char* msg) {
serr(msg);
panic = 1;
if (tk == T_EOF) giveup();
next();
return 0;
}
// what the parser is looking at, for the "found" part of a message
int tokshow() {
if (tk == T_EOF) { rt.put_s("the end of the file"); return 0; }
if (tk == T_NUM) { rt.put_s("the number "); rt.put_n(tv); return 0; }
if (tk == T_STR) { rt.put_s("a string"); return 0; }
if (tk == T_BSTR) { rt.put_s("a byte string"); return 0; }
if (tk == T_FLT) { rt.put_s("a float"); return 0; }
rt.put_s("'"); rt.put_s(tv); rt.put_s("'");
return 0;
}
// like die, and says what was found instead
int diefound(char* msg) {
if (!panic && !dry) {
errat();
rt.put_s(msg); rt.put_s(", found "); tokshow();
errend();
}
panic = 1;
if (tk == T_EOF) giveup();
next();
return 0;
}
// die with a name shown before the message.
int dien(char* name, char* msg) {
serrn(name, msg);
panic = 1;
if (tk == T_EOF) giveup();
next();
return 0;
}
// four classification bits live next to each interned spelling. this works
// for names in later pool chunks too, without indexing by a fixed pool offset.
const int LSW = 1;
word lsets;
int lsmark(int set, int n) {
if (lsets) n[-2] = n[-2] | (1 << set);
return 0;
}
word lsget(int set, int n) {
if (!lsets) return 0;
return (n[-2] >> set) & 1;
}
// name resolution for the language server. while a check runs over the document
// at rsbase, every name a use or a declaration resolves to is written into rsrec
// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD),
// declaration or symbol, and storage facts at that position. a local names its declaration by offset
// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the
// compiler's own scopes decide, so the highlighter and hover see locals and
// parameters first, then globals and built-ins, the way the compiler does.
const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13;
int lt_on, lt_n, ltcap = 256, ltxtcap = 2048;
word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn;
int ht_n, htcap = 256; word ht_start, ht_end, ht_type;
int fieldn, fieldcap = 128; word fieldtypes, fieldoffs; char** fieldnames;
int field_note(word t, char* n, int off) {
if (!lt_on || isp != 0 || dry) return 0;
if (!fieldtypes || fieldn == fieldcap) {
if (fieldtypes) fieldcap = fieldcap * 2;
fieldtypes = chk_resize(fieldtypes, fieldn, fieldcap);
fieldnames = chk_resize(fieldnames, fieldn, fieldcap);
fieldoffs = chk_resize(fieldoffs, fieldn, fieldcap);
}
fieldtypes[fieldn] = t; fieldnames[fieldn] = n; fieldoffs[fieldn] = off; fieldn++;
return 0;
}
int field_decl(word t, char* n) {
int k = 0;
while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; }
return 0 - 1;
}
// where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1
// (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document)
word sdoff, sdsrc;
// includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the
// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor
// sent (a host with no files of its own), incres the path an include was found at
char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done;
word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff;
// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when
// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from.
// transfers are logged as events, EV_W words each: the local's declaration offset, what
// happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one.
const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4;
word evrec; int evn, evcap = 64;
// resize compiler tables. these buffers are collector-managed: m* relinquishes
// scope cleanup, and their local or global pointers keep them visible to gc.
word chk_resize(word old, int count, int capacity) {
word p = m*rt.new(capacity);
p[-4] = p[-4] | rt.F_INTERNAL;
rt.fill_w(p, 0, capacity);
if (old && count) rt.copy_w(p, old, count);
return p;
}
// lexer and documentation buffers grow before storing the next character.
int token_char(int n, int c) {
if (n + 2 > tbufcap) { tbuf = chk_resize(tbuf, n, tbufcap * 2); tbufcap = tbufcap * 2; }
tbuf[n] = c; return n + 1;
}
int doc_char(int c) {
if (dn + 2 > docbufcap) { dbuf = chk_resize(dbuf, dn, docbufcap * 2); docbufcap = docbufcap * 2; }
dbuf[dn] = c; dn++; return 0;
}
int doc_name_room(int need) {
if (need <= docnamecap) return 0;
while (docnamecap < need) docnamecap = docnamecap * 2;
dnmb = chk_resize(dnmb, 0, docnamecap); return 0;
}
// reserve compiler columns at execution time. indices remain valid after a copy.
int reserve_symbols(int need) {
int cap = symcap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
sname = chk_resize(sname, nsym, cap);
skind = chk_resize(skind, nsym, cap);
sval = chk_resize(sval, nsym, cap);
ssig = chk_resize(ssig, nsym, cap);
sflag = chk_resize(sflag, nsym, cap);
sfix = chk_resize(sfix, nsym, cap);
snext = chk_resize(snext, nsym, cap);
stype = chk_resize(stype, nsym, cap);
if (effect_symbols) effect_symbols = chk_resize(effect_symbols, nsym, cap);
if (fnmeta) fnmeta = chk_resize(fnmeta, nsym, cap);
if (ifs) ifs = chk_resize(ifs, symcap, cap);
if (svkind) { svkind = chk_resize(svkind, nsym, cap); svvalue = chk_resize(svvalue, nsym, cap); }
if (sdoff) { sdoff = chk_resize(sdoff, nsym, cap); sdsrc = chk_resize(sdsrc, nsym, cap); }
symcap = cap; return cap;
}
int reserve_locals(int need) {
int cap = loccap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
lname = chk_resize(lname, nloc, cap);
lslot = chk_resize(lslot, nloc, cap);
lhome = chk_resize(lhome, nloc, cap);
lstate = chk_resize(lstate, nloc, cap);
lowner = chk_resize(lowner, nloc, cap);
lused = chk_resize(lused, nloc, cap);
larr = chk_resize(larr, nloc, cap);
lown = chk_resize(lown, nloc, cap);
ltype = chk_resize(ltype, nloc, cap);
ltag = chk_resize(ltag, nloc, cap);
lpos = chk_resize(lpos, nloc, cap);
lreadpos = chk_resize(lreadpos, nloc, cap);
lnsz = chk_resize(lnsz, nloc, cap);
lleaf = chk_resize(lleaf, nloc, cap);
lrsoff = chk_resize(lrsoff, nloc, cap);
lrskind = chk_resize(lrskind, nloc, cap);
lptr = chk_resize(lptr, nloc, cap);
if (if_local) if_local = chk_resize(if_local, nloc, cap);
if (local_origin) local_origin = chk_resize(local_origin, nloc, cap);
if (local_origin_mask) local_origin_mask = chk_resize(local_origin_mask, nloc, cap);
if (local_cell) local_cell = chk_resize(local_cell, nloc, cap);
if (local_captured) local_captured = chk_resize(local_captured, nloc, cap);
if (local_ended) local_ended = chk_resize(local_ended, nloc, cap);
if (local_end_info) local_end_info = chk_resize(local_end_info, nloc, cap);
lafact = chk_resize(lafact, nloc, cap); laaddr = chk_resize(laaddr, nloc, cap); lprev = chk_resize(lprev, nloc, cap);
if (lvkind) { lvkind = chk_resize(lvkind, nloc, cap); lvvalue = chk_resize(lvvalue, nloc, cap); lvescaped = chk_resize(lvescaped, nloc, cap); }
o3cache = chk_resize(o3cache, nloc, cap);
loccap = cap; return cap;
}
int reserve_fixes(int need) {
int cap = fixcap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
fsite = chk_resize(fsite, fixcap, cap);
fnext = chk_resize(fnext, fixcap, cap);
fixcap = cap; return cap;
}
const word R_EXIT_BASE = STYPE_BASE + NS, R_EXITEND = R_EXIT_BASE + EXIT_WORDS;
word R_EXIT = R_EXIT_BASE; int R_EXIT_cap = EXIT_WORDS;
int reserve_R_SNAP(int need) {
int cap = R_SNAP_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_SNAP = chk_resize(R_SNAP, R_SNAP_cap, cap); R_SNAP_cap = cap;
return cap;
}
int reserve_R_QBUF(int need) {
int cap = R_QBUF_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_QBUF = chk_resize(R_QBUF, R_QBUF_cap, cap); R_QBUF_cap = cap;
return cap;
}
int reserve_R_SRC(int need) {
int cap = R_SRC_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_SRC = chk_resize(R_SRC, R_SRC_cap, cap); R_SRC_cap = cap;
return cap;
}
int reserve_R_SEQ(int need) {
int cap = R_SEQ_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_SEQ = chk_resize(R_SEQ, R_SEQ_cap, cap); R_SEQ_cap = cap;
return cap;
}
int reserve_R_CTX(int need) {
int cap = R_CTX_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_CTX = chk_resize(R_CTX, R_CTX_cap, cap); R_CTX_cap = cap;
return cap;
}
int reserve_R_LOOP(int need) {
int cap = R_LOOP_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_LOOP = chk_resize(R_LOOP, R_LOOP_cap, cap); R_LOOP_cap = cap;
if (cap / 8 > loopfacts_cap) { lpfact = chk_resize(lpfact, loopfacts_cap, cap / 8); loopfacts_cap = cap / 8; }
return cap;
}
int reserve_R_EXIT(int need) {
int cap = R_EXIT_cap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
R_EXIT = chk_resize(R_EXIT, R_EXIT_cap, cap); R_EXIT_cap = cap;
return cap;
}
int reserve_binary(int need) {
int cap = bincap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
bname = chk_resize(bname, nbin, cap);
bprec = chk_resize(bprec, nbin, cap);
bseq = chk_resize(bseq, nbin, cap);
blen = chk_resize(blen, nbin, cap);
bincap = cap; return cap;
}
int reserve_unary(int need) {
int cap = uncap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
uname = chk_resize(uname, nun, cap);
useq = chk_resize(useq, nun, cap);
ulen = chk_resize(ulen, nun, cap);
uncap = cap; return cap;
}
int reserve_assignment(int need) {
int cap = asgcap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
aname = chk_resize(aname, nasg, cap);
abin = chk_resize(abin, nasg, cap);
asgcap = cap; return cap;
}
int reserve_operators(int need) {
int cap = kopcap;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
kops = chk_resize(kops, nkop, cap);
kopcap = cap; return cap;
}
// write a record for the name at byte offset off. ref is what a local names (its
// declaration's offset, or -1); sym is the symbol a RS_SYM record names, or -1.
int rs_put(int off, int kind, int ref, int sym) {
int at;
if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || off < 0) return 0;
if (rsn >= rscap) {
rsrec = chk_resize(rsrec, rsn * RS_W, rscap * 2 * RS_W);
rstmp = chk_resize(0, 0, rscap * 2 * RS_W);
rscap = rscap * 2;
}
at = rsn * RS_W;
rsrec[at] = off; rsrec[at + 1] = kind; rsrec[at + 2] = ref; rsrec[at + 3] = sym;
rt.fill_w(rsrec + at + 4, 0, RS_W - 4);
rsn++;
return 0;
}
// the ownership of local i at the record for the name at off
int os_at(int off, int i) {
int at, r, k = rsn;
if (!rsn || !cmode || dry || i < 0 || chkind != 3 || chbase != rsbase) return 0;
while (k && rsrec[(k - 1) * RS_W] != off) k--;
if (!k) return 0;
at = (k - 1) * RS_W;
rsrec[at + 11] = lstate[i] + 1;
if (lown[i]) rsrec[at + 11] = rsrec[at + 11] + 16;
if (lstate[i] == S_CHILD) { r = lroot(i); if (lrsoff[r] >= 0) rsrec[at + 12] = lrsoff[r] + 1; }
return 0;
}
// log a transfer of local i; the last few events are searched so a loop body is not logged twice
int ev_put(int i, int kind, int target) {
int k = evn, n = 0, t = 0;
if (!rsrec || !cmode || dry || chkind != 3 || chbase != rsbase || i < 0 || lrsoff[i] < 0) return 0;
if (target >= 0 && lrsoff[target] >= 0) t = lrsoff[target] + 1;
while (k && n < 8) {
k--; n++;
if (evrec[k * EV_W] == lrsoff[i] && evrec[k * EV_W + 1] == kind && evrec[k * EV_W + 2] == tline) return 0;
}
if (!evrec) { evcap = 64; evrec = chk_resize(0, 0, evcap * EV_W); }
if (evn >= evcap) { evrec = chk_resize(evrec, evn * EV_W, evcap * 2 * EV_W); evcap = evcap * 2; }
evrec[evn * EV_W] = lrsoff[i]; evrec[evn * EV_W + 1] = kind; evrec[evn * EV_W + 2] = tline; evrec[evn * EV_W + 3] = t;
evn++; return 0;
}
// write a record for the name token that is current.
int rs_note(int kind, int ref) { return rs_put(toff, kind, ref, 0 - 1); }
// the flags of every symbol record, read again once the check has defined the
// whole document (a function used before its definition is defined by now).
int rs_fix() {
int i = 0;
while (i < rsn) {
if (rsrec[i * RS_W + 1] == RS_SYM && rsrec[i * RS_W + 3] >= 0) {
int s = rsrec[i * RS_W + 3]; rsrec[i * RS_W + 2] = rs_sym(s);
if (skind[s] == K_CONST) vf_at(i, 1, sval[s]);
}
i++;
}
return 0;
}
// what a symbol is, as a record keeps it: its kind, plus 16 for a symbol the
// document declares (not a built-in) and 32 when that symbol has a definition
int rs_sym(int s) {
int v = skind[s];
if (s >= ubase) v = v | 16;
if (sflag[s] & F_DEF) v = v | 32;
return v;
}
// copies record a of from to record b of to (the merge sort moves records this way).
int rs_move(word from, int a, word to, int b) {
rt.copy_w(to + b * RS_W, from + a * RS_W, RS_W);
return 0;
}
// sort the records by offset: a bottom-up merge sort through rstmp, so a long
// document costs n log n, whatever order its names come in.
int rs_sort() {
int w = 1, lo, mid, hi, i, j, k, n = rsn;
if (n < 2) return 0;
while (w < n) {
lo = 0;
while (lo < n) {
mid = lo + w; if (mid > n) mid = n;
hi = lo + w * 2; if (hi > n) hi = n;
i = lo; j = mid; k = lo;
while (i < mid && j < hi) {
if (rsrec[j * RS_W] < rsrec[i * RS_W]) { rs_move(rsrec, j, rstmp, k); j++; }
else { rs_move(rsrec, i, rstmp, k); i++; }
k++;
}
while (i < mid) { rs_move(rsrec, i, rstmp, k); i++; k++; }
while (j < hi) { rs_move(rsrec, j, rstmp, k); j++; k++; }
lo = hi;
}
i = 0;
while (i < n * RS_W) { rsrec[i] = rstmp[i]; i++; }
w = w * 2;
}
return 0;
}
// analyzer errors only count in checked, reachable code
int cerr(char* msg) {
if (fchk && live) serr(msg);
return 0;
}
// report an analyzer error about local i, named in the message.
int cerrn(int i, char* msg) {
int root = i, cause = 0; word info = 0;
if (fchk && live) {
if (rt.eq_w(msg, "used after it was given away")) {
root = lroot(i); if (local_end_info) info = local_end_info[i];
if (!info && local_end_info) info = local_end_info[root];
if (info) cause = info[2]; else if (local_ended) cause = local_ended[root];
}
if (cause && !panic && !dry) {
errat(); rt.put_s(lname[i]); rt.put_s(": "); rt.put_s(msg);
rt.put_s("; owner "); if (info) rt.put_s(info[0]); else rt.put_s(lname[root]);
rt.put_s(" was transferred on line "); rt.put_n(cause & 0xFFFFFF);
if (info && info[1]) { rt.put_s(" (declared on line "); rt.put_n(info[1] & 0xFFFFFF); rt.put_c(')'); }
errend();
} else serrn(lname[i], msg);
}
return 0;
}
// ---- interning ----
// the pool: an open addressing hash table (R_IH) of pointers into a string
// pool (R_POOL). each spelling has two spare words: its symbol-chain head at
// p[-1] and editor classification bits at p[-2]. equal text returns the same
// stable handle, which lookup can use directly.
int hashs(char* s) {
int h = 0;
while (*s) { h = h * 31 + *s; s++; }
return h & (hashcap - 1);
}
// rehash the index without moving strings or their symbol-chain heads.
int reserve_names(int need) {
int cap = hashcap, previous = hashcap, i = 0, h; char* p; char** old;
if (need <= cap) return cap;
while (cap < need) cap = cap * 2;
old = namehash;
namehash = chk_resize(0, 0, cap);
hashcap = cap;
while (i < previous) {
p = old[i];
if (p) { h = hashs(p); while (namehash[h]) h = (h + 1) & (hashcap - 1); namehash[h] = p; }
i++;
}
return cap;
}
// return the interned copy of a spelling. new chunks preserve every old handle.
char* intern(char* s) {
int h; word p; int n;
if ((namecount + 1) * 2 >= hashcap) reserve_names(hashcap * 2);
h = hashs(s);
while (1) {
p = namehash[h];
if (!p) break;
if (p[0] == s[0] && rt.eq_w(p, s)) return p;
h = (h + 1) & (hashcap - 1);
}
n = rt.len_w(s);
if (pooltop + n + 3 > poolend) {
if (poolchunk < n + 3) poolchunk = n + 3;
else if (poolchunk < 0x20000000) poolchunk = poolchunk * 2;
p = chk_resize(0, 0, poolchunk + 1); p[0] = poolblocks; poolblocks = p;
pooltop = p + 1; poolend = pooltop + poolchunk;
}
pooltop[0] = 0; pooltop[1] = 0;
p = pooltop + 2;
rt.copy_w(p, s, n + 1);
pooltop = pooltop + n + 3;
namehash[h] = p; namecount++;
return p;
}
// ---- symbols ----
// each interned name keeps the index (+1) of its newest symbol in p[-1], and
// each symbol links to the one it shadows, so lookup is one step.
// remember where symbol s is defined, when the prescan made it before its definition was read
int def_note(int s, int off) {
if (cmode && !dry && !ginst && sdoff && off >= 0 && !sdoff[s]) { sdoff[s] = off + 1; sdsrc[s] = curinc; }
return 0;
}
int addsym(char* name, int kind, int val) {
int i = nsym;
if (module_name && kind != K_KW) name = module_symbol(name);
if (nsym >= symcap) reserve_symbols(nsym + 1);
if (!fnmeta) fnmeta = chk_resize(0, 0, symcap);
nsym++;
sname[i] = name;
skind[i] = kind;
sval[i] = val;
ssig[i] = 0;
sflag[i] = F_DEF;
sfix[i] = 0;
fnmeta[i] = 0;
stype[i] = 0;
snext[i] = name[-1];
name[-1] = i + 1;
if (cmode && !dry && !sdoff) { sdoff = chk_resize(0, 0, symcap); sdsrc = chk_resize(0, 0, symcap); }
if (sdoff) {
sdoff[i] = 0; sdsrc[i] = 0;
if (cmode && !dry && !ginst && nameoff >= 0) { sdoff[i] = nameoff + 1; sdsrc[i] = curinc; }
}
return i;
}
// while an image builds, names below vislo are hidden, except keywords
int lookup_raw(char* name) {
int i = name[-1] - 1, resident;
if (syntax_host_floor && i >= syntax_host_floor && i < syntax_host_from) {
resident = i;
while (resident >= syntax_host_floor) resident = snext[resident] - 1;
if (resident >= 0) return resident;
if (skind[i] == K_GVAR || skind[i] == K_GARR) serr("a syntax handler cannot access emitted program globals");
}
while (i >= 0) {
if (i >= vislo || skind[i] == K_KW || skind[i] == K_TYPE) return i;
i = snext[i] - 1;
}
return -1;
}
// qualify a declaration within its file namespace; generated method names retain their prefix.
char* module_symbol(char* name) {
int i = 0, j; char* b; char* q;
while (name[i]) { if (name[i] == '.') return name; i++; }
if (!module_name) return name;
if (((name[0] == 'd' && name[1] == 'o' && name[2] == 'c') || (name[0] == 's' && name[1] == 'i' && name[2] == 'g')) && name[3] == 32) {
q = methname(module_name, name + 4); b = chk_resize(0, 0, rt.len_w(q) + 5);
rt.copy_w(b, name, 4);
j = 0; while (q[j]) { b[j + 4] = q[j]; j++; } b[j + 4] = 0;
return intern(b);
}
return methname(module_name, name);
}
int lookup(char* name) {
int s, i = 0;
if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; }
s = lookup_raw(name); if (s >= 0) return s;
while (name[i]) { if (name[i] == '.') return -1; i++; }
return lookup_raw(methname("rt", name));
}
// modules loaded from source are tracked independently of each file's imports.
word included_modules;
int module_loaded(char* name) {
word p = included_modules;
while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; }
return 0;
}
int module_known(char* name) {
return module_loaded(name) || rt.eq_w(name, "rt") || rt.eq_w(name, "run") || rt.eq_w(name, "core") || rt.eq_w(name, "lsp") || rt.eq_w(name, "std") || rt.eq_w(name, "icon");
}
int module_imported(char* name) {
word p = module_imports;
if (module_name && rt.eq_w(name, module_name)) return 1;
while (p) { if (rt.eq_w(p[1], name)) return 1; p = p[0]; }
return 0;
}
// modules reported missing in this unit, so their names are not reported again as undefined
word missing_imports;
// true when name is qualified by a module this unit was already told to include.
int missing_import_name(char* name) {
int i = 0; word p = missing_imports;
while (name[i] && name[i] != '.') i++;
if (!name[i]) return 0;
while (p) {
char* module = p[1]; int k = 0;
while (k < i && module[k] == name[k]) k++;
if (k == i && !module[k]) return 1;
p = p[0];
}
return 0;
}
// source references require their module's explicit import; compiler-generated calls use lookup directly.
int source_lookup(char* name) {
int i = 0, s; char* owner;
while (name[i] && name[i] != '.') i++;
if (name[i]) {
owner = chk_resize(0, 0, i + 1); rt.copy_w(owner, name, i); owner[i] = 0;
if ((module_known(owner) || module_loaded(owner)) && !module_imported(owner)) {
errat(); rt.put_s("module access requires `include "); rt.put_s(owner); rt.put_s(";`"); errend();
word link = chk_resize(0, 0, 2); link[0] = missing_imports; link[1] = owner; missing_imports = link;
// the unit already fails; resolving the name anyway keeps later diagnostics about the real code
}
}
if (module_name) { s = lookup_raw(module_symbol(name)); if (s >= 0) return s; }
return lookup_raw(name);
}
// drop the symbols above index to, which ends a scope.
int popsyms(int to) {
while (nsym > to) { nsym--; sname[nsym][-1] = snext[nsym]; }
while (ntd && tdstk[ntd * 3 - 3] >= to) ntd--; // local typedefs made above to went with them
return 0;
}
// look a name up from a plain string.
int lookups(char* s) { return lookup(intern(s)); }
// patch nodes
int fixnew(int site, int link) {
int i = fixfree;
if (i) fixfree = fnext[i];
else { nfixu++; i = nfixu; if (i >= fixcap) reserve_fixes(i + 1); }
fsite[i] = site; fnext[i] = link;
return i;
}
// patch every lit in a chain to point at v, and recycle the nodes
int fixall(int head, int v) {
int n;
while (head) {
setlit(fsite[head], v);
n = fnext[head];
fnext[head] = fixfree;
fixfree = head;
head = n;
}
return 0;
}
// ---- code emission ----
// code goes straight into memory at "here". hdelta shifts where it is written
// (an image build emits at one address and stores at another, see nested
// compiles), and dry turns emission off entirely, which typeof and similar
// queries use to type an expression without producing code.
// reserve contiguous words before recording an address or writing a data block.
// new runtime segments are linked by jumps; earlier code and globals stay put.
int reserve_code(int need) {
word p; int cap, top;
if (dry) return 0;
if (need < 0 || need > 0x7fffffff - 8) fatal("bad code reservation");
if (image_active) {
if (need <= hlimit - here) return hlimit - here;
cap = image_capacity;
while (cap < here - image_org + need) {
if (cap > (0x7fffffff - image_org) / 2) fatal("image too big");
cap = cap * 2;
}
image_buffer = chk_resize(image_buffer, here - image_org, cap);
image_capacity = cap; hdelta = image_buffer - image_org; hlimit = image_org + cap;
return hlimit - here;
}
if (need <= hlimit - here - 2) return hlimit - here - 2;
if (here + 1 >= hlimit) fatal("no room for a code link");
cap = code_chunk;
if (cap < need + 5) cap = need + 5;
p = chk_resize(0, 0, cap); top = p + p[-5] - rt.HDR;
if (rt.syscall(9, 9, top, 0) < top) fatal("code address reservation failed");
p[0] = rt.code_blocks; p[1] = p + 3; p[2] = top; rt.code_blocks = p;
*here = (p + 3) * 2 + 1; here[1] = O_JMP * 2;
here = p + 3; hlimit = top;
if (cap < 0x20000000) code_chunk = cap * 2;
return hlimit - here - 2;
}
// a region handed to an optimizer must fit in one contiguous segment.
int code_contiguous(int lo, int hi) {
word p = rt.code_blocks;
if (image_active || (lo >= 0 && hi <= rt.CODE_END)) return 1;
while (p) { if (lo >= p[1] && hi <= p[2] - 2) return 1; p = p[0]; }
return 0;
}
int emit(int w) {
if (dry) return 0;
if (w == OP_FLIT * 2) reserve_code(2); else reserve_code(1);
*(here + hdelta) = w;
here++;
return 0;
}
// a vm code word with the low bit set pushes the value in its upper bits
// (v + v + 1); an even word is an opcode (k + k). jmp pushes a target and jumps.
int lit(int v) {
if ((v >> 62) != 0 && (v >> 62) != -1) {
reserve_code(5);
emit((v >> 32) * 2 + 1); emit(0x100000000 * 2 + 1); op(15);
emit((v & 0xFFFFFFFF) * 2 + 1); op(17);
} else emit(v + v + 1);
return 0;
}
// emit one vm opcode.
int op(int k) { emit(k + k); return 0; }
// fill the literal placeholder at address at with value v.
int setlit(int at, int v) {
if (dry) return 0;
if ((v >> 62) != 0 && (v >> 62) != -1) fatal("literal patch too large");
*(at + hdelta) = v + v + 1;
return 0;
}
// emit a jump to the address t.
int jmp(int t) {
lit(t);
op(O_JMP);
return 0;
}
// a lit that will be patched later; returns its address
int hole() {
int a;
reserve_code(1); a = here;
lit(rt.MW - 1);
return a;
}
// ---- line tables ----
// make room for n more words of pairs.
int lt_room(int n) {
int cap = lt_cap;
if (lt_top + n <= cap) return 0;
if (cap < 256) cap = 256;
while (lt_top + n > cap) cap = cap * 2;
lt_buf = chk_resize(lt_buf, lt_top, cap); lt_cap = cap;
return 0;
}
// start recording the function whose code begins at entry, named name, with its header on line.
// returns the frame to give lt_end, or -1 when no table is being made (lines off, a dry run,
// check mode).
int lt_begin(int entry, char* name, int line) {
int b;
if (!lines_on || dry || cmode) return 0 - 1;
lt_room(8);
b = lt_top;
lt_buf[b] = lt_base; lt_buf[b + 1] = entry; lt_buf[b + 2] = name; lt_buf[b + 3] = curinc; lt_buf[b + 4] = 0;
if (!curinc) { lt_buf[b + 3] = lt_mark_file; lt_buf[b + 4] = lt_mark_base; } // the main source may be marked as a file of its own
lt_top = b + 5; lt_base = b;
lt_buf[lt_top] = line - lt_buf[b + 4]; lt_top++; // the code before the first statement belongs to the header's line
return b;
}
// a statement begins at the current token, and its code at here.
int lt_stmt() {
int off, n;
if (lt_base < 0 || dry) return 0;
off = here - lt_buf[lt_base + 1];
if (off < 0) return 0;
lt_room(2);
if (lt_top > lt_base + 5 && (lt_buf[lt_top - 1] >> 32) == off) lt_top--; // same place: the later statement is the one
n = tline - lt_buf[lt_base + 4];
if (lt_top > lt_base + 5 && (lt_buf[lt_top - 1] & 4294967295) == n) return 0;
lt_buf[lt_top] = off * 4294967296 + n; lt_top++;
return 0;
}
// the file name in a block: the include's path, or <stdin> for the main source. one copy of the
// bytes is shared by the blocks that follow it.
int lt_file_data(char* f) {
if (!f) f = "<stdin>";
if (lt_file_at && lt_file_img == image_active && rt.eq_w(lt_file, f)) return lt_file_at;
lt_file_at = bstrdata(f); lt_file_img = image_active;
lt_file = chk_resize(0, 0, rt.len_w(f) + 1); rt.copy_w(lt_file, f, rt.len_w(f) + 1);
return lt_file_at;
}
// finish the function begun at frame b, whose code ends at end: put its line block among the code
// and chain it. a block is [next, entry, end, name, file, count, pairs...].
int lt_end(int b, int end) {
int entry, count, i = 0, name, file, blk, need; word head; char* nm; char* fl;
if (b < 0) return 0;
entry = lt_buf[b + 1]; nm = lt_buf[b + 2]; fl = lt_buf[b + 3]; count = lt_top - (b + 5);
if (!nm) nm = "<fn>";
if (!fl) fl = "<stdin>";
if (end >= entry && !dry) {
need = 8 + count + ((rt.len_w(nm) + 7) >> 3) + 2 + ((rt.len_w(fl) + 7) >> 3) + 2;
reserve_code(need);
name = bstrdata(nm); file = lt_file_data(fl);
if (image_active) head = lt_head; else head = rt.line_blocks;
blk = here;
emit(head); emit(entry); emit(end); emit(name); emit(file); emit(count);
while (i < count) { emit(lt_buf[b + 5 + i]); i++; }
if (image_active) lt_head = blk; else rt.line_blocks = blk;
}
lt_top = b; lt_base = lt_buf[b];
return 0;
}
// at the top level, a marker that comes before the item takes effect: lines are on, in its file.
int lt_apply_marks() {
if (incdepth || ginst) return 0;
while (lt_next_mark < lt_nmarks && lt_marks[lt_next_mark * 2] < tline) {
lt_mark_base = lt_marks[lt_next_mark * 2]; lt_mark_file = lt_marks[lt_next_mark * 2 + 1];
lines_on = 1; lt_next_mark++;
}
return 0;
}
// forward references. calling a function that is not defined yet creates a
// symbol with F_DEF clear whose value is the place of its first use (for the
// "never defined" error). each call site emits a placeholder literal and adds
// itself to the symbol's chain (sfix); defining the function runs fixall to
// patch every site. MW-1 is a jump target that halts, so an unpatched call
// stops the machine instead of running garbage.
int fwdfn_at(char* name, int p) {
int s = addsym(name, K_FN, p);
sflag[s] = 0;
return s;
}
// the place of the first use (line, and offset above bit 24), until it's defined
int fwdfn(char* name) {
return fwdfn_at(name, tpack());
}
// emit a reference to function s, patched later if it is not defined yet.
int fnref(int s) {
if (syntax_host_floor && s >= syntax_host_floor && s < syntax_host_from) {
s = syntax_host_function(s);
if (s < 0) { lit(rt.MW - 1); return 0; }
}
if (sflag[s] & F_DEF) { lit(sval[s]); return 0; }
if (dry) return 0;
reserve_code(1); sfix[s] = fixnew(here, sfix[s]);
lit(rt.MW - 1); // a jump to MW-1 halts if it ever runs unpatched
return 0;
}
// emit a call to the function with this name, making a forward slot if it is not defined.
int callname(char* name) {
int s = lookup(name);
if (s < 0) { name = methname("rt", name); s = lookup(name); }
if (s < 0) s = fwdfn(name);
fnref(s);
op(O_CALL);
return 0;
}
// the address of local slot k: the frame pointer lives in the word ZFP, and a
// local is fp + k. (o3 locals live in cpu registers instead; see vaddr.)
int laddr(int k) {
lit(rt.ZFP);
op(O_LD);
if (k) { lit(k); op(O_ADD); }
return 0;
}
// an o3 variable's address: lfa(k) is fp + k as one enhancement, which the
// jit specializes on the literal k
word vaddr(int i) {
int s;
if (ltag[i] & 16) {
s = lookup(s_lfa);
if (s >= 0 && skind[s] == K_ENH) {
lit(lslot[i]);
op(XOP0 + sval[s]);
return 0;
}
}
laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0;
}
// registers for an o3 region's variables: the cpu has 7. a variable takes the
// one its region freed last (when a scope closed), or the next unused one
// ---- o3: the optimizing region compiler's bookkeeping ----
// code inside an o3 region (or a function marked o3) keeps hot locals in the
// cpu's registers. the compiler does not change what it emits for them; it
// records which frame slot maps to which register and hands the finished
// region to the core through install(). the core compiles the whole region
// with those slots held in registers and writes them back to the frame when
// control leaves it. there are 7 registers, so locals past that stay in
// memory.
int o3reset() {
no3free = 0;
o3nreg = 1;
no3sl = 0;
return 0;
}
// a freed register keeps its class. a float home is a d register and an int
// home is an x register, and one var index cannot be both, so a number that
// was a float is only handed out again as a float.
int o3take(int fp) {
int i = 0, k, w = -1;
while (i < no3free) {
k = o3free[i];
if (fp && k < 0) w = i;
if (!fp && k > 0) w = i;
i++;
}
if (w >= 0) {
k = o3free[w];
no3free--;
o3free[w] = o3free[no3free];
if (k < 0) return 0 - k;
return k;
}
if (o3nreg < 8) { o3nreg++; return o3nreg - 1; }
return 0;
}
// true when a type is a plain number that o3 can keep in a register.
int o3plain(word t) {
if (!t) return 0;
if (t[0] == TY_INT || t[0] == TY_CHAR || t[0] == TY_BOOL || t[0] == TY_FLOAT) return 1;
return 0;
}
// move a local into an o3 register when the region can keep it there.
int o3promote(int i) {
int r, fp;
ltag[i] = ltag[i] - (ltag[i] & 1792);
if (!o3on || larr[i] || no3sl >= 32) return 0;
fp = isfl(ltype[i]);
r = o3take(fp);
if (!r) return 0;
ltag[i] = ltag[i] + r * 256;
o3sl[no3sl] = lslot[i];
if (o3plain(ltype[i])) r = r + 16; // 16 marks a plain number: the core need not write it back before a call
if (fp) r = 0 - r; // the install word is negative when the home is a d register
o3rg[no3sl] = r; no3sl++;
return 0;
}
// a fixed T[?] can't change its items or its length, so o3 code keeps both in
// two hidden variables, loaded when the array variable is set (or when the
// region starts), and indexes through them
int o3mkcache(int i) {
int k;
if (!o3on || ltype[i][0] != TY_DYN || !ltype[i][3] || tbytes(ltype[i][4])) return 0;
k = newloc(s_hitems); fslots++; ltag[k] = 16; o3promote(k);
newloc(s_hlen); fslots++; ltag[k + 1] = 16; o3promote(k + 1);
o3cache[i] = k;
return 0;
}
// reload the o3 register cache of a local from its memory slot.
int o3refresh(int i) {
int k = o3cache[i];
if (!k) return 0;
vaddr(i); op(O_LD); lit(2); op(O_ADD); op(O_LD); vaddr(k); op(O_ST);
vaddr(i); op(O_LD); op(O_LD); vaddr(k + 1); op(O_ST);
return 0;
}
// hands a region to the cpu: install kind 2 is [-, 2, lo, hi, n, slot, register, ..., fslots, argc, fnstart]
// fslots, argc and the prologue address sit in o3fs, o3na, o3fn0. a bare o3 block leaves the prologue at -1, so a function that starts at address 0 still binds.
int o3region(int lo, int hi, word sl, word rg, int n) {
int i = 0;
if (hdelta || lo >= hi || !code_contiguous(lo, hi)) return 0;
o3dsc[0] = 0; o3dsc[1] = 2; o3dsc[2] = lo; o3dsc[3] = hi; o3dsc[4] = n;
while (i < n) { o3dsc[5 + i + i] = sl[i]; o3dsc[6 + i + i] = rg[i]; i++; }
o3dsc[5 + n + n] = o3fs; o3dsc[6 + n + n] = o3na; o3dsc[7 + n + n] = o3fn0;
rt.install(o3dsc);
return 0;
}
// o3 blocks go to the cpu once their function is done, so every jump in them is final
int o3flush() {
int i = 0; word p;
o3fs = 0; o3na = 0; o3fn0 = -1; /* -1 is not a function; a prologue at address 0 is */
while (i < no3pend) { p = o3pend + i * 67; o3region(p[0], p[1], p + 3, p + 35, p[2]); i++; }
no3pend = 0;
return 0;
}
// a string literal: the characters (one per word, then a 0) are emitted into
// the code stream behind a jump, and the literal's value is their address
int strlit(char* s) {
int j = hole(), a;
op(O_JMP);
reserve_code(rt.len_w(s) + 1); a = here;
while (*s) { emit(*s); s++; }
emit(0);
setlit(j, here);
lit(a);
return 0;
}
|