-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathnotes.txt
3875 lines (2905 loc) · 151 KB
/
notes.txt
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
=======================================
03/17/2024
Fought this for a long time. Problem is that var uses can't tell if they're in
the definition (recursively) or not, at parse-time. So there's no breadcrumb
left-over from the Parsers' use to say if any given use should be "Fresh" or
"NonGen".
In my example below, when I parse A's expression and find "B", I don't know
(yet) if A and B are intertwined or not. Again when parsing D's expression and
find B, again don't know if D and B are intertwined.
Ponding building a variant Parser than makes an AST for stack frames (like
EXE), then discovered mut-let-rec sets, perhaps reordering the Frame AST to
topo sort mut-let-rec, THEN walks the AST for a SoN.
=======================================
03/17/2024
BUG: $dyn not counted in StructNode._nargs, but it IS an argument
ForwardRef states
- not scoped; var might promote to outer scopes
- scoped; var is defined in this scope, but mid-def
- self-defined; var is defined, but not MUTUALLY defined, e.g. is_even/is_odd examples
- mut-defined; all mutual let-rec vars are defined.
FRefs are tied to their scope/struct/frame as the direct input for that var name.
They have a defined edge, null till self-defined.
They have more mutual dependence edges to other FRefs. These extra FRef edges
are cycle-detected as self-def gets set.
Example:
A = { x -> rand ? B(x) : x };
D = { -> rand ? B(1) : C(2) };
C = { x -> A(x) };
B = { x -> C(x) };
Parse token A
- Set A FRef, scoped, not self-defined. Scope:{A-scoped}
- Parse A expr
- Discover B; insert B FRef, not-scoped Scope:{A-scoped, B-not-scoped}
- Use a FRef; add edge A-FRef to B-FRef Scope:{A-scoped(B), B-not-scoped}
- Finish A expr parse
- Change A-FRef to self-defined; has a B-FRef edge so NOT mut-defined (yet); Scope: {A-self(B),B-not}
Parse token D
- Set D FRef, scoped, not self-defined. Scope:{A-self(B), B-not, D-scoped}
- Parse D expr
- Use B FRef, add edge D-FRef to B-FRef. Scope:{A-self(B), B-not, D-scoped(B)}
- Discover C; insert C FRef, not-scoped Scope:{A-self(B), B-not, D-scoped(B), C-not}
- Use C FRef, add edge D-FRef to C-FRef. Scope:{A-self(B), B-not, D-scoped(B,C), C-not}
- Finish D expr parse
- Change D-FRef to self-defined; Scope:{A-self(B), B-not, D-self(B,C), C-not}
Parse token C
- Change C FRef to scoped: Scope:{A-self(B), B-not, D-self(B,C), C-scoped}
- Parse C expr
- Use A FRef, add edge C-FRef to A-FRef. Scope:{A-self(B), B-not, D-scoped(B,C), C-scoped(A)}
- Finish C expr parse
- CHange C-FRef to self-defined: Scope:{A-self(B), B-not, D-self(B,C), C-self(A)}
Parse token B
- Change B FRef to scoped: Scope:{A-self(B), B-scoped, D-self(B,C), C-self(A)}
- Parse B expr
- Use C FRef, add edge B-FRef to C-FRef. Scope: {A-self(B), B-scoped(C), D-self(B,C), C-self(A)}
- FInish B expr parse
- Change B-FRef to self-defined: Scope: {A-self(B), B-self(C), D-self(B,C), C-self(A)}
- - Walk B->C->A, all self/mutual.
- - Flip all to Mutual.
- - Walk backwards from {A,B,C} looking for not-mut-FRefs.
- - If so, cycle-find again on D: D->B(mut), D->C(mut) - Flip all D to mutual.
=======================================
03/12/2024
Thinking that not inserting Fresh during parse doesn't work, because lost NONGEN inputs.
Thinking instead of removing extra FRESH, but this probably requires a global pre-pass.
See testRecur12.aa.sav, TestParse.jig for examples.
So...
Parsing A= definition. Find forward ref B. GO up scope, find mid-def of A,
add "B" as a dependence.
Parsing D= def. Find fref B; go up scope; find mid-def of D, add "B" as a
dependence. Repeat for fref C; D ends up with deps {B,C}
Parsing C= def. Find fref A; add "A" as a dep.
Parsing B= def. Find fref C; add "C" as a dep.
End of struct, topo-sort by deps and find cycle. Declare {A,B,C} mutual let-rec.
Revisit Fresh loads inserted by FREF resolve, and kill extra Fresh.
Leave them in for D's
=======================================
03/10/2024
When defining a top-level frame, it is defined in parts and each part may (or
may not) be "let/in" complete.
Example, hidden/assumed top-level display/frame:
fact = {.../*fact is mid-def, NOT FRESH here */... };
x = e0; // fact is complete here, but x is not
fact(x) // fact uses let-polymorphism here
Same Example, explicit top-level frame:
FILE_SCOPE = @{
fact = {.../*fact is mid-def, NOT FRESH here */... };
x = e0; // fact is complete here, but x is not
fact(x) // fact uses let-polymorphism here, but FILE_SCOPE is not complete
... // FILE_SCOPE carries on with more variable
}
Indeed FILE_SCOPE carries on to the program end, adding vars.
Same is true for all nested scopes.
BUG: Currently finding FILE_SCOPE in the non-gen set, which recursively
includes 'fact' when fact is being used: `fact(x)` which blows its
polymorphic flavor.
So from a typing perspective looking at something like:
FILE_SCOPE_CURRENT = @{
actve, in-progress defs.
includes e.g. is_even until is_odd defines
};
FILE_SCOPE_DONE = @{
fact = {...fact...};
x = e0;
blah = fact(x);
.. more added
}
Or maybe TOP/FILE_SCOPE NOT in non-gen, but instead their parts go in non-gen
on a case by case basis.
FILE_SCOPE = @{
x = e0; // forward-ref set is empty
fact = { ...fref(fact)...}; // remove fact from fref-set, so empty, so done.
...fact(3)... // fact is done, not in non-gen so FRESH
is_even = { ...fref(is_odd)... }; // fref(is_odd, not done);
blah = {...baz...}; // fref is empty, so done
...blah... // Done, non-in-nongen, so FRESH
is_odd = { ...is_even/not done so NOT FRESH...}// picks up is_even fref set, then removes is_odd, so both are done
...
}
Issue is building up a topo DAG-sort of sets, names are not-fresh in the same
initial create, and ARE fresh in later uses.
Set: {A,B,C} // A calls B calls C calls A
Set: {D,E} // These can poly call A,B,C but not each other, D,E
Set: {F,G} // These can poly call A,B,C but not each other, F,G
Set: {H,I} // These can poly call any of the above...
Theory: late-add Fresh???
For each Closure Store, track f-ref names.
At end of closure, build SCCs and top-sort DAGS.
Within SCC, replace frefs with direct Loads, no fresh.
Not-replaced frefs replace with a Load/Fresh pair.
Nasty example; listing fref-sets:
FILE: @{
A = {B};
H = {I,A,D};
D = {E,C};
C = {A}
I = {H,E}
E = {D,C}
B = {C}
}
During parse:
If outer scope, assume Load/Def always complete here.
If current scope, ask if Load/Def is complete & flag it.
If not complete, flag Load as unknown. Add to fref-set for current scope def.
When current def completes, gather & reset fref-set.
After struct finishes, walk fref-sets, make SCCs.
Walk by SCC, all Loads in same SCC not-complete-not-fresh
Loads not in current SCC as Fresh
More Nasty Example:
Done parsing A = {B }; keep fref-set. A not defined (yet), missing B.
Done parsing H = {I,A,D}; keep fref-set. H not defined (yet), missing I,D.
Done parsing D = {E,C }; keep fref-set. D not defined (yet), missing E,C.
Done parsing C = {A }; keep fref-set. C not defined (yet), has A, but A missing B.
Done parsing I = {H,E }; keep fref-set. I not defined (yet), has I,H,A,D, missing E.
Done parsing E = {D,C }; keep fref-set. E not defined (yet), has everything except B.
Done parsing B = {C }; C uses A, A uses B. Declare mut-rec {A,B,C}.
Don't see any shortcuts here.
I still think, in the end, I'm gonna visit all fields as part of the "promote"
logic, and have to do a topo-sort with SCC discovery. All fref uses get broken
down into uses from inside the SCC and uses outside the SCC.
=======================================
12/25/2023
Pondering a "reboot" of AA Node class.
The Type classes seems to be stable; complex but very usable with strong
theoretical foundations.
The TVar classes seems to be stable; complex but very usable with strong
theoretical foundations.
Node:
- Needs better IR printing, and moved out of Node (too large).
See Simple Node printing for a better 1-line per node print.
- Folding the edge Ary<Node>s back into Node - for easier debugging experience.
Its 1-layer less, 1-click less every time I chase node edges.
- Lots of stuff in the Node class proper, I just want a chance to filter it down.
- Drop the opcode as not really helpful. Just add Yet Another Virtual call.
- Stick with the recursive peepholes during parsing; no worklist.
- Post-parse, pre-combo & post-combo, normal worklist.
- A bunch of Node bits seem suspiscous - not sure I need them, or would be
better of if moved to somewhere else. Also, there's a lot of walkers, makes
me wonder if I should use a generic walker more often.
- Looking at the walkers, I have a generic walker but its too hard to use. I
want cheap&easy input/output walkers. See TypeStruct for such walkers; I
pull one from a list, walk, return it. Have to be careful with leaks on
early exit.
- Ponder a NodeUtils for lots of stuff that's only sorta tangential to Node
- Ponder a nicer way for Parse to handle keeping partially built nodes around
- - addDef(null) use as a in-progress flag; delDef(null) to unwind
- - But still call keep/unkeep so can assert more.
- - try/resources/autoclose to wrap assertion that add/del is balanced?
- - OR: `int bal; assert bal = pushBalancedKeeps();`
- - `assert popBalancedKeeps(bal);`
- - `try(BalanceCheck b = BalanceCheck.get(); ) { ... }`
Example for problem walkers; walkerr_def:
- Passed some "Env" for collecting stuff
- visit self-check
- walk just the defs, pre-order
- - skip if def is null, XCTRL, or a FunPtr
- - pre-order walk
- POST WORK:
- - revisit defs, check no ALL on defs
- - adderr(Env) collects errors in Env
- - updates each ErrMsg order
Issues for generic pre-order walker: its not a pre-order walk! Must have a
special cutout during walking, which means calling a generic walker with
specialized per-edge-walk, which makes it much less useful.
=======================================
12/12/2023
Overloads (finally) appear to be typing/running in the EXE branch.
General theory:
- Dyn(amic) Loads, or DynLoads, take a dynamic offset instead of using a fixed
constant offset. The offset will come from a DynTable - and thus a dynamic
load requires 2 loads instead of 1.
- The DynTable is strongly typed and derived during normal H-M typing.
- DynTable entries are either a mapping from a DynLoad to a field label
(string, which at compile time turns into a constant) OR the map is from an
Apply/Call site to a new DynTable used for this call.
- All Lambdas are passed an extra DynTable argument.
- All Apply/Calls pass this argument, which they load from the extant DynTable
at a call-site specific offset.
- All DynLoads load at a fixed offset from the table, getting their offset, which they then load ptr+offset
- So the runtime cost is a load-per-call, an extra reg argument, and a 2nd load-per-DynLoad.
- The type of a DynTable is the TVDynTable, which is similar to a TVStruct, in
that it has a variable number of fields, but uses Node IDs instead of labels.
- DynTables can be empty, and will be if no DynLoad is involved. They can have
nested DynTables, which can be recursively empty. For recursive functions
(e.g. 'factorial'), the nested DynTable can refer to itself.
=======================================
10/21/2023
DynLoad has a result TV3, call it TV_dyn.
This TV_dyn maps back to the DynLoad TVPtr input, which can load() a TVStruct called TV_match
During a fresh_unify, if the TV_dyn freshes against 'that',
we need to do a trial_resolve(outie,'that',TV_match,test).
This instanceof TV_dyn made Fresh gets its own match.
I think we actually need to push forward with more mappings if fresh_unify.
So during fresh_unify, if see TV_dyn has a mapping, give mapping to 'that' as
well. Also toss on delayed_resolve worklist.
For the map TV_dyn -> TV_match, since TV_dyn can unify, might try `unify`
updates this mapping. We can assume the mapping is always correct because
`unify` corrects it.
If TV_dyn is deep, and innards change, and not already resolved, we need
to re-trial resolve. This means changes on the inside should force a
re-trial.
So here's a simpler algo:
DynLoad puts TV_dyn on the "to be resolved" list, and tags TV_dyn with the
TV_match list. Fresh_unify copies the list, unify merges the list. Anything
listed goes on the "to be resolved" list. The TBR contains the match pattern
(varies by fresh unify) and the tagged TV3.
A DelayResolve is a pair (TV3,TVStruct). Each pair resolves independently.
I need dup-removal, with simple O(n^2) search.
Once the main worklist is done, we trial_resolve the "to be resolved" list.
If any progress, we go back to combo.
Same concept can happen for delay_fresh. Suppose TV_fresh fresh_unifies with
'that'. Every TVExpanding inside of TV_fresh may later expand, and requires
another round of fresh. So TVExpanding carries a list of FreshNodes; if they
expand those FreshNodes go on the to-be-re-Freshed list. Once main worklist is
done, we go back a do more fresh_unify.
Action items: drop existing DELAY_RESOLVE lists.
* Global DelayResolve pair list (TV3,TVStruct)
* DynLoad puts a pair on.
* Fresh of a TV_dyn, puts a 'that' as a pair on: (that,TVStruct,Fresh)
* I assume both TV3 and TVStruct update during main worklist, but do not track
* At end of work, I recheck resolve.
* Resolved TV3s can be removed forever, always resolved.
* Progress restarts main worklist.
Ponder simplifying delay_fresh; this broke in HM and I don't remember how.
Possibly a self-cycle fresh/unify where a TV3 appeared both on the LHS and RHS
at the same time.
Theory: I can make delay_fresh *shallow*, each TV3 in LHS maps to a TV3 in RHS;
changes in LHS need to re-fresh in RHS. Theory: just call fresh on changed LHS
and mapped RHS. Cycles might be an issue!
=======================================
10/1/2023
Splitting normal field Load from a DynLoad (Dynamic Load) node. DynLoad will
take a field offset input (much like an array load), but the input will contain
a mapping from TV3s to field labels. The address to a DynLoad, same as a field
Load, will have a TVPtr to a TVStruct. Once exactly one of the offset TV3s
trial-unifies with the DynLoad self, the field is chosen. If this doesn't
happen during the compile, the DynLoad will do a dynamic lookup. The lookup
math can be reduced (same as a vtable) to lookup up the field offset, at a
fixed offset in the vfield array.
vfields are produced trivially by New (unsure) and by Fresh.
=======================================
09/13/2023
Summary
Introducing a new concept of dynamic loads DynLoad and a vtable for DynLoads.
DynLoads dynamically compute their field offset from the vtable. Essentially
the vtable is a mapping from DynLoad to field label (string/offset).
DynLoads are written as "._" and come as the default during operator expansion.
DynLoads use the HM field resolution and may resolve to different fields along
different execution paths. These different paths always come about from Fresh
nodes and fresh uses of identifiers (generally inside functions but I can make
function-free examples).
The execution semantics for a DynLoad just do a self-lookup in the vtable
input, get a field offset and load at the address plus offset. In the worse
case this is a fixed offset load from the vtable, then an add and load.
The vtable starts as an empty mapping, and each time is passes by a Fresh the
Parser inserts a DynLoadMap which updates the vtable for the Fresh. The Fresh,
during Combo/HM, clones its input type and this clones unresolved field types
and these are already present in TVStruct. During Combo resolution each unique
instance of an unresolved field is always tied to a Fresh and gets resolved
independently. This resolution is put in the Fresh and DynLoadMap - so the
vtable changes to include a {DynLoad -> label} mapping.
The vtable edge gradually picks up a complete {DynLoad -> label} mapping for
all paths, and each DynLoad can then compute its proper label during runtime.
DynLoads with only constant labels can convert to Loads (the DynLoad dies via
node death). Liveness can also be used to kill { DynLoad -> label } dead
mappings, which can kill some DynLoadMaps (DynLoadMap dies via Reduce).
Example:
// Using a var assignment here, so a later use of 'foo' clones via Fresh
foo = { x y z ->
// Crazy expression produces a collection of functions, all different sigs
(
{ int flt str -> ... },
{ flt str int -> ... },
{ str int flt -> ... }
)._(x,y,z); <<-- DynLoad of random expression
}
// A FRESH on FOO for every pattern
( // Gather results
foo( 1, 2.2, "abc"), // FRESH on FOO
foo( "red", 2, 1.1),
foo( 3.3, "blu", 5)
)
So really - its a vtable-per-FRESH.
Crazy concept
- extra vtable edge.
- R/M/W by FRESH, to extend/replace with self
- passed as extra parm to funcs.
- - scoped basically
- merged at Phis
- used by DynLoad to find field edge....
- can be dead if no DynLoad uses.
- Can be partially dead if a Fresh's choices not used by some DynLoad
- So really its a collection of edges - 1 per Fresh, kept in parallel to all other things, like memory edges.
- DynLoad inserts a unresolved hook in the TVStruct on address input.
- - TVStruct has normal fields
- - TVStruct also has < - ,DynLoad> -> <TV3 mapping,String>
- - TVStruct Fresh adds a <Fresh,DynLoad> -> <TV3 mapping,String>
- - Resolving fills in the String
- vtable is a mapping <DynLoad> -> String
- - Parse: carry mapping conservative (all DynLoad) -> (unresolved String)
- - Extra parm, like memory, tracks everywhere (blech)
- - Iter: DynLoadMap cannot Value update without Combo; can do Liveness at some point
- Combo computes per-Fresh DynLoad->String map
- Combo updates each Fresh with mapping
- - As fields resolved can be monotonic
- - Combo treats unresolved as high, can type-flow on the vtable mapping edge
- - vtable edge falls from <all DynLoad> -> (unresolved) to <most DynLoads> -> <Strings>
- - liveness per DynLoad
- - Combo a DynLoad getting a constant string no longer keeps vtable edge alive
- - Can thus make some Fresh DynLoad settings go dead.
- So parallel to Fresh is a DynLoadMap
- Which updates the vtable via the Fresh
- And can go dead independently from other things
BROKEN EXAMPLE:
Example, stacked dynamic dispatch
foo = { x y ->
(
{ int int -> ... },
{ int flt -> ... },
{ flt int -> ... },
{ flt flt -> ... },
)._(x,y); <<-- DynLoad of random expression, forces inlining?
}
baz = { x -> pred ? foo(x,2) : foo(x,2.2) }
bar = { -> pred ? baz(2) : baz(2.2) }
=======================================
09/11/2023
Final result: each Fresh instance of an unresolved field might require a
different label.
Either this should not type OR I can type it, but require inlining for an
efficient solution.
These can be inlined, making a specific instance per-use and thus type.
But this requires *inlining* to allow *typing*.
`sq = { x -> x*x }; (sq(2),sq(2.2))`
Here I need to resolve a load from operator _*_. The standard set is
( { int int -> int }, { int flt -> flt } ) // Integer MUL
( { flt int -> flt }, { flt flt -> flt } ) // Float MUL
`sq(2 )` needs field `0` to get `{ int int -> int }`
`sq(2.2)` needs field `1` to get `{ flt flt -> flt }`
This example types fine, except I cannot choose between fields `0` and `1`.
Stalled on having a dynamic field load resolution; one that does not depend
on code cloning in *every* case.
So here's a "bad" good solution-
- At every Fresh usage, I tag the value with a unique v-table (unique-per-Fresh).
- At the usage sute of a tagged value, I have a constant lookup.
This gets me the field offset.
- So at the `sq( 2 )` place, I take `sq` with vtable[0]=0;
- So at the `sq(2.2)` place, I take `sq` with vtable[0]=4;
- Since args are not uniform, I wrap.
- Inside of `sq`, I use the vtable of `x` to lookup `_*_` at a fixed offset.
- At the one dynamic field, I lookup vtable[0], get a field (0 or 1) or field offset (0 or 4)
- I load from tuple+offset, getting either {int int -> int} or {flt flt -> flt}.
Again for `fact`:
`fact = { x -> x <= 1 ? x : x*fact(x-1) }; (fact(2),fact(2.2))`
Here the inside `fact` has no Fresh, so this is identical to the `sq` solution.
General rule...
- if i can type at all, the unresolved field becomes resolved at some level
- last LET before it becomes unresolved takes a vtable, indexed by each usage site.
- eg ( z = ... (x = ... ( y = ... struct._ ... ))); ( ...x..y..z.., ...x..y..z.. )
- - if x & y types still have unresolved, but z does not, then x gets vtable.
- - Or maybe y gets vtable from x.
Big Picture Answer right now
- Allow mismatched field offset
- I think there's an "acceptable" fast solution
- More inlining might reduce cost to icache-bloat
- Expect to inline for true primitives anyways
=======================================
08/19/2023
About to go to BM, so dumping some state here.
Still failing to type: "fact = { x -> x <= 1 ? x : x*fact(x-1) }; (fact(2), fact(2.2))".
Issue is the FRESH type of "fact" pushes its internal Resolving fields (for the opers <=, -, *)
into the pre-FRESH types. These pre-FRESH types are already set to either INT CLZ or FLT CLZ
which then forces the internal field resolves.
This is correct for "_<=_" because I match ({A int:1 -> A}, ...) vs both
({int int -> int},{int flt -> flt})
({flt int -> int},{flt flt -> flt})
and field 0 works for both.
Same reason works for "_-_".
Fails for "x*fact(x-1)" because the match is: ({A A -> A}, ...) vs
({int int -> int},{int flt -> flt})
({flt int -> int},{flt flt -> flt})
Which requires field 0 for INTs and field 1 for FLTs.
I've known all along I need to clone `fact` for primitives.
Issue right now is: I'm getting a broken type instead of a failed typing.
Theory:
fresh_unify does NOT push its &123 Resolving fields into `that`?
Might be I never resolve inside of unify, directly.
Instead, I attempt resolve for a particular copy a TVStruct.
If it works (yes or no, not maybe), I record the answer so faster in future -
and I record the selected field in the Load. There might be several unrelated
field choices (e.g. `fact` will demand the `_*_` pick different fields).
If the Load has only 1 pick, we take it and Done!
If the load has several picks, I need to fail out for ambiguous, (and demand more cloning).
"Demand more cloning" - in "Iter" pass, if a fcn recieves mixed primitives &
ref args, clone for arg types!!!
=======================================
06/25/2023
More on overloads
( A B ) vs pattern ( 7 ) // Ambiguous, either A or B could become int or flt
( @{name:str, ... } @{ age=A } ) -vs- @{ age=B } // Ambiguous, first struct could pick up age, 2nd struct A & B could fail later
( @{name:str } @{ age=A } ) -vs- @{ age=B } // Ambiguous, first struct is a clear miss , 2nd struct A & B could fail later
( @{name:str } @{ age=A } ) -vs- @{ age=A } // OK, A & A cannot miss
( @{name:str, ... } @{ age=A } ) -vs- @{ age=A } // Ambiguous, first struct could pick up age=A
So each match has the following 3 choices
- hard no , something structural is wrong
- hard yes, all parts match, even leaf-for-leaf. No open struct in pattern.
- maybe , all parts match, except leafs. Leafs might expand later and fail.
Actions
- Hard-no: would be nice to be efficient and cut this out of the match choices.
Can't change the container (directly), but might add side-data?
Note: same match and same pattern can each be used in other contexts.
- 0-Hard-yes & 1+maybes: Wait.
- 1-Hard-yes & 1+maybes: Wait.
- 1-Hard-yes & 0-maybes: Resolve it!
- 2-Hard-yes & 0+maybes: Unify yes's with ambi error. Maybes become yes's also unify.
End of pass#1, Apply-Lift goes conservative.
End of pass#2, flip HM_AMBI.
- Between passes, 1-Hard-yes & 1+maybes: Resolve ALL of them, at once! This
can cascade resolves, and in turn break other delayed resolves.
- Otherwise SAME.
- We can argue a repeat loop here, if any progress.
End of pass#3
- 0-Hard-yes & 1+maybes: Left around. Actual resolve has to wait until module is linked against other modules.
Argues for a pass# change in Combo:
while( progress ) {
- Normal unify
- 0-Hard-yes & 1+maybes: Wait.
- 1-Hard-yes & 1+maybes: Wait.
- 1-Hard-yes & 0-maybes: Resolve it!
- 2-Hard-yes & 0+maybes: Unify yes's with ambi error. Maybes become yes's also unify.
Specifically, TVErr on the Match YESes, since 2+ hard yes's MUST be ambiguous against any pattern.
- Resolve all 1-hard-yes + some-maybes, all at once. May cause errors. Report progress.
Done in parallel to remove an ordering ambiguity.
}
Apply-quits-lifting-leaves.
Repeat above loop.
THIS MIGHT NOT WORK.... UNWOUND.
#######################
#Might also invert TVErr -
#normal TVar has a null TVErr field.
#when error, fills in the _err field, but leafs the TVar alone.
#Another TVar in error, uses the same TVErr field.
#TVErr still points back at the TVars.
#Removes the as_struct/as_ptr problems.
THIS MIGHT BE WORKING!!!
#And I can kill TVNil since unify TVPtr & TVBase?
# Still not checking polymorphic nil on map:
map( [A ] {A ->B} -> [B] ) vs
map( [A?] {A?->B} -> [B] )
----------------------
ACTION ITEMS:
*- Add TypeFunPtr BOUND vs UNBOUND check.
*- error to meet/join BOUND and UNBOUND
- Make decent defaults for BOUND/UNBOUND
*- Add TVLambda BOUND vs UNBOUND check.
*- error to unify BOUND/UNBOUND.
*- Load/Field are brainless about overloads
- - PONDER FOLDING LOAD/FIELD BACK TOGETHER
*- Lambda/Func in parser: if called from field-assign, no-bind; else bind.
*- TVErr inversion: UNWIND?
*- Back to Prim-Overs not ptrs
- Bind knows opers
- if no TFP/TVLambda, then NO-OP.
- No oper, if BOUND, then NO-OP.
- if UNBOUND, then BIND a TFP/TVLambda
- Yes oper,
- - Must be a TMP/TVPtr
- - To a TypeStruct/TVStruct
- - ForAll fields
- - - Must be UNBOUND LAMBDA;
- - - Then BIND it.
Load/Field/Bind-vs-prims & overloads
- Need a TypeFunPtr which can tell *bound* from *unbound*.
- - DSP=ANY === UNBOUND The Combo initial BOUND display is ANY.
- - DSP= all else === BOUND. The Combo initial BOUND display is XSCALAR.
- - typerror to meet/join BOUND and UNBOUND!!!!
- - tverr to unify BOUND and UNBOUND!!!!
- Bind XFER applies binding to unbound
- Bind shallow fresh unifies allowing a binding.
- - V123:( unbound Varg0 Varg1 -> Vres ) produces
- - V456:( Vdsp Varg0 Varg1 -> Vres )
- Fold together Load/Field/Bind
- - if Load becomes a TVLambda/TFP -
- - - If unbound, bind it.
- - - If bound, do not change binding.
- - if Load becomes a TVStruct/TMP
- - - Repeat for all fields
- - - - if TVLambda/TFP, assert UNbound; bind them
- - if Load is other, no binding
User writes "1._+_._(2)"
- 1 is TVPtr of int klass
- ._+_ is Load/Field/Bind, is oper, goes deep binding
- ._ is Load/Field/Bind, but pre-bound, no more binding.
Normal ".x"
- .x is Load/Field/Bind, does the 1-deep bound/unbound check and binds as needed
User writes "1._+_.0(2)"
- 1 is TVPtr of int klass
- ._+_ is Load/Field/Bind, is oper, goes deep binding
- .0 is Load/Field/Bind, but pre-bound, no more binding.
Back to Prims-Overs-as-Tuples
Patterns allowed
"1+2" - Load(1)[@{INT}] -> Field _+_ [(choices)] -> Field _ [lambda] -> Call w/dsp(1), arg(2), and lambda
"1._+_" - Load(1)[@{INT}] -> Field _+_ [(choices)] -> Bind_over [(bound_choices)]
"x.y" - Load(ptr) [struct ] -> Field y [field ] -> Bind(ptr) [lambda ? bind_lambda : field]
"x.y" - Load(str)![(bound_choices)] -> Field y [bound_choice] -> Bind(str)![bound_choice]
Still bare lambdas get Bind, so maybe not fold Load/Field with Bind.
( { a -> b } 123 ) // No load, so pre-bind to the current display.
@{ str = { -> ..._pretty_print_self... } } // Lambda is stored. No bind.
// Makeing sure binding rules are sane for scopes/structs/classes:
cnt:=0;
sq = { x -> cnt++; x*x }; // Lambda is stored; bind to current stack display
..{... // Some scopes later
sq(5); // sq is load/field from up-scope display; pre-bound when used.
..} // Close later scopes
Base = :@{
cnt := 0;
vcall = { cnt++; }
sq = { x -> vcall(); x*x; } // unbound since store in struct, dsp is typed as self-struct.
}
Child = Base:@{
vcall = { cnt += 2; }
}
b:Base = rand ? Base() : Child();
b.sq(s); // Increments cnt by 1 or 2? Binds sq here.
----------------------
Independent of Prims wanting to bind 2 layers down, I have a theory problem with early/late binding
sq = { x -> x*x }; // In closure scope, early bind.
z = @{
x = rnd // Should HM Type-Error, mixing early and late-bound functions
? sq // Early bound
: { x -> x*x*x; } // Late bound
}
q = z.x; // Should I bind z into x or not???
// Since z.x is a load, I always insert a Bind
// Load should start as "unknown binding"
// - And falls to "early bind" or "late-bind".
// I can imagine starting the Lambda there as a no-dsp/late-bind.
// Then perhaps late-bind.
// Then later, IF unifies sq-dsp with z-as-dsp & errors (badly).
z0.x0 = z .x ; // Bind?
z1.x1 = z0.x0; // Bind?
z2.x2 = z1.x1; // Bind?
z2.x2(args); // Bind? Then call...
// I claim...
Bind happens exactly once on all paths
meet/unify of bound & unbound is an error state.
- DSP goes to ALL or TVErr of the blend
So DSP is either both bound or both unbound.
Bind can use liveness to keep the fp unbound (if display is dead, still do not bind)
=======================================
05/27/2023
Back to TVMem issues -
From prior ACTION items
- Dropped TVMem, but thinking about bringing it back
- TVStructs track a CLZ field (in slot 0 and called ".", if it exists)
- TVStruct fields can be pinned, and float "up" to lowest CLZ position
// Fact is typed as a function taking an 'x' and returning an 'x'.
// 'x' is typed as anything with operators {<=,-,*}
fact = { x -> x <= 1 ? x : x*fact(x-1) };
(fact(0),fact(1),fact(2))
Middle of Combo
Have an unresolved call.
What is the "shape" of memory after such a Call?
TWO BUGS:
ONE: TVNil does not support TVClz operators. 1st fact(0) call does not resolve internally.
Reports back [[_any_]] memory until it resolves.
TWO:
There's a Load after the Call asking about Memory.
Flow-side has no mem info until Call resolves.
Call cannot resolve without operators to resolve.
Load can't find 'fact', so 2nd call never makes.
Since Load doesn't load, Field ".fact" doesn't fall past ANY never unifies.
Cannot unify ANY Nodes, since those are dead, shouldn't type.
FIXES:
- Flow side, unknown calls cannot futz with final fields.
- Flow side, pass final fields thru, other fields stay HIGH (this is Combo) in case they get set to some high thing.
- This never settles out, unless TV3 forces memory shape via as_flow
TVNIL HAS NO OPERS
- Replace TVNil with a TVPtr-struct, unknown alias (works for deep-ptrs, fails for shallow-ptrs+track-mem)
- TVPtr is may-nil.
- No more TVNil.
- TVPtr picks up oper requests.
- Optionally: use TVBase (of nil). Picks up opers same way.
*ACTION: try to pass final field flow thru - WOrking
=======================================
05/15/2023
*ACTION: DROP TVMem
*ACTION: TVStruct always slot 0 is CLZ, ALWAYS
*ACTION: TVStruct, bring back pinned fields.
ACTION: When unify drops a un-pinned field, it instead "pushes up" to the next
open clazz struct and only deletes if it hits CLZCLZ
Don't have Mem right yet.
Let-assign with Mem, then Fresh on use.
Goal is poly-morphic memory.
Might be the same as just using pointers.
But to allow assigns.
Its the magic-updates after a Store to some other memory. One option: no TV3
updates after a Store. Aliased pointers all unify, at least on stored fields.
Ex: A = @{ x=?, y="abc" };
B = @{ x=?, z=3.14 };
P = rand ? A : B; // Alias A & B, but no unifiy of field parts
P.x = 16; // Force A.x and B.x to unify with int.
fun= { A B ->
A.x = 17; // do A & B alias?
B.y = "abc";
}
fun = { fx x ->
x
? fx( fun(fx, x - 1 ) ) // Notice oper _-_ on free 'x'
: 1
};
fun(2._*_._, 99)
Type for 'x' allows e.g. 'Matrix', as long as 'Matrix - 1' yields another 'Matrix'.
Type for 'x' must be a ptr (or int or flt), with class type kept in memory.
So Fresh has to track Memory along with ID (makes a Fresh copy of Memory)?
Not sure I like this...
At the parm mem: Parm:Mem @{ _-_ ^= (&123={ [[]] FOO 1 -> [[]] FOO }, ...), ... } // Unpinned unresolved field can float up
At the parm:4: Parm *[alias]
At the klaz load: Load *[alias]@{ _-_ ^= (&123={ [[]] FOO 1 -> [[]] FOO }, ...), ... } // Unpinned unresolved field can float up
At the oper load: Field ._-_ @{ _-_ ^= (&123={ [[]] FOO 1 -> [[]] FOO }, ...), ... } // Unpinned unresolved field can float up
At the resolve : Field ._ (&123={ [[]] FOO 1 -> [[]] FOO }, ...)
At the oper call: CALL FOO 1 { [[]] FOO 1 -> [[]] FOO }
So at the call-site for 'fun' we make a fresh copy, and unify
FRESH VAL:*[unk_alias]
OTHER VAL: Base:99 --> *[PINT]
FRESH MEM: [[ unk_alias = @{ _-_ ^= (&123={ [[ ]] FOO 1 -> [[ ]] FOO }, ... ), ... } ]]
OTHER MEM: [[ PINT = @{ _-_ = ( 0={ [MEM] int int -> [MEM] int },1={int flt->flt}), other opers, closed } ]]
=======================================
05/12/2023
Got pretty far since last notes.
"fun = { fx x -> x ? fx( fun(fx,x-1) ) : 1 }; fun(2._*_._, 99)"
New notes:
"x" needs to be typed as having an operator "_-_" to compute "x-1" - but at
which class hiearchy level?
Which leads me to needing to infer class structure!
So current theory:
- All structs have a clazz in a field, which itself is a TVStruct.
"@{ . = @{CLZ}, ... }"
- Put the class with field name "." always in slot 0.
- Put a display uptick with field name as "^" in slot 1. Optional.
- Structs in AA without further clzz names go straight to CLZ_CLZ clazz.
- The "." field is a TVStruct, not a TVPtr. Sharing will keep costs down?
- - Can think about final-field (no leaf) optimizations for Fresh.
- Field unifies insert a field into a struct.
- If the field is in the current struct, unify to it
- Else
- - If the struct is closed, Field searchs the super-class chain
- - If the struct is open, Field inserts there (so first open clazz up the super-clazz chain)
- - If the super-clazz is null, go back down to the bottom and unify with miss_field
- Invariant: Following super-chain, from the Field struct to below placement
is always closed. May be open at placement struct.
- Base/prims act "as if" closed no-field struct with parent @{INT_CLZ} field.
- INT_CLZ/FLT_CLZ/STR_CLZ has e.g. CLZ_CLZ as parent. No fields in CLZ_CLZ.
This leaves things like open clazzes (not declared) in-between some lower and higher class, which
might inject a shadowing def, and this is OK.
Can drop Type string clz. Not being used.
Can drop TVClz, using just a tree in TVStruct/TVPtr.
=======================================
2/09/2023
Fresh is horribly wrong. Can't track just FunNodes, because still have to flag
Fresh unrelated var uses inside of a let-def:
z = ....; // let z = ... in
x = ...z...; // let x = ...Fresh(z,x)... // z is fresh inside of x's def, with x in nongen set
Theory:
- track nongen Nodes in Scope
- when starting a fcn scope, all parms from DSP_IDX on go in nongen set (so can find from Scope/Ctrl?)
- when starting an assignment store, first pre-assign with ForwardRef, and push these on Scope.
- These FREF's collapse on assignment
Theory#2: Fresh copys all Scope nongen inputs.
- End result: every fresh has a complete copy of all active nongen values.
- Theory#2a: i'll be choked-up with Fresh, and need heavy cleanup rules.
Theory#3:
- Fresh points to a Scope with nongen list
Theory#4:
- Scopes nest (they do), so Fresh can follow the nongen list
THeory#4a: i'll be choked-up with Fresh, and need heavy Scope cleanup rules
Scopes:
0 1 2 3 4 5 6 7 | 8...
CTL MEM REZ PTR STK XCTL XMEM XVAL | nongen....
Theory: dangerous to merge Scopes and nongen tracking...
Better to replicate at every Fresh (optimize later if we see huge redundant Fresh lists).
--------------------
Problem#next:
Structs in FunNode/scope().stk()/Frame - first nargs arguments are ParmNodes
and NONGEN. Next set of fields are in the post-Let-Def set, and not NONGEN.
Need to split structnode inputs.
=======================================
1/25/2023
What is the type of { x -> 1+x } ?
Its an overload type with a pending resolve:
: ( { 1 int -> int }, { 1 flt -> flt } )._
Notice the trailing resolve: "._"
Notice the add functions are already bound.
What is the type of { x -> x+1 } ?
Its a little more generic:
: A:@{ clz = @{ _+_ = ( { A 1 -> B } ) } }
Notice its a normal clz-based overload, this SHOULD unify with int/flt just fine.
Notice it works with any overloaded _+_ operator.
What is the type of { x y -> x+y } ?
: A:@{ clz = @{ _+_ = ( { A B -> C }* ... ) } }
Notice the 2nd argument can have many choices.... so
Notice the '* ...' which means we can clone the display and nargs, but there
can be any number of other choices. Similar to an open struct, except all
the functions added are limited to nargs and display.
I can imagine allowing any function types in an overload, as long as uses are unambiguous.
TV types add a bind-to-an-overload, which awaits resolution; basically a pair of display & lambda-choices.
TV types add a 'resolve field' instead of doing it in the Field.unify. Works thru the above Bind.
So now: