-
Notifications
You must be signed in to change notification settings - Fork 68
/
StackToRegisterMappingCogit.class.st
4683 lines (4138 loc) · 177 KB
/
StackToRegisterMappingCogit.class.st
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
"
StackToRegisterMappingCogit is an optimizing code generator that eliminates a lot of stack operations and inlines some special selector arithmetic. It does so by a simple stack-to-register mapping scheme based on deferring the generation of code to produce operands until operand-consuming operations. The operations that consume operands are sends, stores and returns. This scheme was first conceived by L. Peter Deutch in the HPS Smalltalk VM (the VisualWorks VM). Thank you, Peter.
See methods in the class-side documentation protocol for more detail.
Instance Variables
compilationPass: <Integer>
currentCallCleanUpSize: <Integer>
ceCall0ArgsPIC: <Integer>
ceCall1ArgsPIC: <Integer>
ceCall2ArgsPIC: <Integer>
ceCallCogCodePopReceiverArg0Regs: <Integer>
ceCallCogCodePopReceiverArg1Arg0Regs: <Integer>
deadCode <Boolean>
debugBytecodePointers: <Set of Integer>
debugFixupBreaks: <Set of Integer>
debugStackPointers: <CArrayAccessor of (Integer|nil)>
hasNativeFrame <Boolean>
methodAbortTrampolines: <CArrayAccessor of Integer>
methodOrBlockNumTemps: <Integer>
numPushNilsFunction: <Symbol>
picAbortTrampolines: <CArrayAccessor of Integer>
picMissTrampolines: <CArrayAccessor of Integer>
pushNilSizeFunction: <Symbol>
realCECallCogCodePopReceiverArg0Regs: <Integer>
realCECallCogCodePopReceiverArg1Arg0Regs: <Integer>
regArgsHaveBeenPushed: <Boolean>
simNativeSpillBase: <Integer>
simNativeStack: <CArrayAccessor of CogSimStackNativeEntry>
simNativeStackPtr: <Integer>
simNativeStackSize: <Integer>
simSpillBase: <Integer>
simStack: <CArrayAccessor of CogSimStackEntry>
simStackPtr: <Integer>
traceSimStack: <Integer>
useTwoPaths <Boolean>
compilationPass
- counter indicating whether on the first pass through bytecodes in a V3-style embedded block or not. The V3 closure implementation uses pushNil to initialize temporary variables and this makes an initial pushNil ambiguous. With the V3 bytecode set, the JIT must compile to the end of the block to discover if a pushNil is for initializing a temp or to produce an operand.
currentCallCleanUpSize
- the number of bytes to remove from the stack in a Lowcode call.
ceCall0ArgsPIC ceCall1ArgsPIC ceCall2ArgsPIC
- the trampoline for entering an N-arg PIC
ceCallCogCodePopReceiverArg0Regs ceCallCogCodePopReceiverArg1Arg0Regs
- the trampoline for invokinging a method with N register args
debugBytecodePointers
- a Set of bytecode pcs for setting breakpoints (simulation only)
deadCode
- set to true to indicate that the next bytecode (up to the next fixup) is not reachable. Used to avoid generating dead code.
debugFixupBreaks
- a Set of fixup indices for setting breakpoints (simulation only)
debugStackPointers
- an Array of stack depths for each bytecode for code verification (simulation only)
hasNativeFrame
- set to true when Lowcode creates a native stack frame for Lowcode callouts.
methodAbortTrampolines
- a CArrayAccessor of abort trampolines for 0, 1, 2 and N args
methodOrBlockNumTemps
- the number of method or block temps (including args) in the current compilation unit (method or block)
numPushNilsFunction
- the function used to determine the number of push nils at the beginning of a block. This abstracts away from the specific bytecode set(s).
picAbortTrampolines
- a CArrayAccessor of abort trampolines for 0, 1, 2 and N args
picMissTrampolines
- a CArrayAccessor of abort trampolines for 0, 1, 2 and N args
pushNilSizeFunction
- the function used to determine the number of bytes in the push nils bytecode(s) at the beginning of a block. This abstracts away from the specific bytecode set(s).
realCECallCogCodePopReceiverArg0Regs realCECallCogCodePopReceiverArg1Arg0Regs
- the real trampolines for invoking machine code with N reg args when in the Debug regime
regArgsHaveBeenPushed
- whether the register args have been pushed before frame build (e.g. when an interpreter primitive is called)
simNativeSpillBase
- the variable tracking how much of the Lowcode simulation stack has been spilled to the real stack
simNativeStack
- the Lowcode simulation stack itself
simNativeStackPtr
- the pointer to the top of the Lowcode simulation stack
simNativeStackSize
- the size of the Lowcode stack so far
simSpillBase
- the variable tracking how much of the simulation stack has been spilled to the real stack
simStack
- the simulation stack itself, comprising the receiver, arguments, temporaries, and volatile stack contents. The receiver is the 0'th entry, and the 1st is that of the first argument, etc.
simStackPtr
- the pointer to the top of the simulation stack
useTwoPaths
- a variable controlling whether to create two paths through a method based on the existence of inst var stores. With immutability this causes a frameless path to be generated if an otherwise frameless method is frameful simply because of inst var stores. In this case the test to take the first frameless path is if the receiver is not immutable. Without immutability, if a frameless method contains two or more inst var stores, the first path will be code with no store check, chosen by a single check for the receiver being in new space.
"
Class {
#name : #StackToRegisterMappingCogit,
#superclass : #SimpleStackBasedCogit,
#instVars : [
'numPushNilsFunction',
'pushNilSizeFunction',
'methodOrBlockNumTemps',
'regArgsHaveBeenPushed',
'simStack',
'simStackPtr',
'simSpillBase',
'ceCallCogCodePopReceiverArg0Regs',
'ceCallCogCodePopReceiverArg1Arg0Regs',
'methodAbortTrampolines',
'picAbortTrampolines',
'picMissTrampolines',
'ceCall0ArgsPIC',
'ceCall1ArgsPIC',
'ceCall2ArgsPIC',
'debugStackPointers',
'debugFixupBreaks',
'realCECallCogCodePopReceiverArg0Regs',
'realCECallCogCodePopReceiverArg1Arg0Regs',
'deadCode',
'useTwoPaths',
'currentCallCleanUpSize',
'compilationPass'
],
#classVars : [
'NeedsMergeFixupFlag',
'NeedsNonMergeFixupFlag'
],
#pools : [
'CogCompilationConstants',
'VMMethodCacheConstants',
'VMObjectIndices',
'VMStackFrameOffsets'
],
#classInstVars : [
'numPushNilsFunction',
'pushNilSizeFunction'
],
#category : #'VMMaker-JIT'
}
{ #category : #translation }
StackToRegisterMappingCogit class >> ancilliaryClasses [
^super ancilliaryClasses,
{ self basicNew simStackEntryClass. self basicNew bytecodeFixupClass. CogSSOptStatus }
]
{ #category : #'class initialization' }
StackToRegisterMappingCogit class >> bytecodeTable [
^ #(
"1 byte bytecodes"
"pushes"
(1 0 15 genPushReceiverVariableBytecode isInstVarRef needsFrameNever: 1)
(1 16 31 genPushLiteralVariable16CasesBytecode needsFrameNever: 1)
(1 32 63 genPushLiteralConstantBytecode needsFrameNever: 1)
(1 64 75 genPushTemporaryVariableBytecode needsFrameIfMod16GENumArgs: 1)
(1 76 76 genPushReceiverBytecode needsFrameNever: 1)
(1 77 77 genPushConstantTrueBytecode needsFrameNever: 1)
(1 78 78 genPushConstantFalseBytecode needsFrameNever: 1)
(1 79 79 genPushConstantNilBytecode needsFrameNever: 1)
(1 80 80 genPushConstantZeroBytecode needsFrameNever: 1)
(1 81 81 genPushConstantOneBytecode needsFrameNever: 1)
(1 82 82 genExtPushPseudoVariable)
(1 83 83 duplicateTopBytecode needsFrameNever: 1)
(1 84 87 unknownBytecode)
"returns"
(1 88 88 genReturnReceiver return needsFrameIfInBlock: isMappedInBlock 0)
(1 89 89 genReturnTrue return needsFrameIfInBlock: isMappedInBlock 0)
(1 90 90 genReturnFalse return needsFrameIfInBlock: isMappedInBlock 0)
(1 91 91 genReturnNil return needsFrameIfInBlock: isMappedInBlock 0)
(1 92 92 genReturnTopFromMethod return needsFrameIfInBlock: isMappedInBlock -1)
(1 93 93 genReturnNilFromBlock return needsFrameNever: -1)
(1 94 94 genReturnTopFromBlock return needsFrameNever: -1)
(1 95 95 genExtNopBytecode needsFrameNever: 0)
"sends"
(1 96 96 genSpecialSelectorArithmetic isMapped AddRR)
(1 97 97 genSpecialSelectorArithmetic isMapped SubRR)
(1 98 98 genSpecialSelectorComparison isMapped JumpLess)
(1 99 99 genSpecialSelectorComparison isMapped JumpGreater)
(1 100 100 genSpecialSelectorComparison isMapped JumpLessOrEqual)
(1 101 101 genSpecialSelectorComparison isMapped JumpGreaterOrEqual)
(1 102 102 genSpecialSelectorComparison isMapped JumpZero)
(1 103 103 genSpecialSelectorComparison isMapped JumpNonZero)
(1 104 109 genSpecialSelectorSend isMapped) " #* #/ #\\ #@ #bitShift: //"
(1 110 110 genSpecialSelectorArithmetic isMapped AndRR)
(1 111 111 genSpecialSelectorArithmetic isMapped OrRR)
(1 112 117 genSpecialSelectorSend isMapped) "#at: #at:put: #size #next #nextPut: #atEnd"
(1 118 118 genSpecialSelectorEqualsEquals needsFrameNever: notMapped -1) "not mapped because it is directly inlined (for now)"
(1 119 119 genSpecialSelectorClass needsFrameIfStackGreaterThanOne: notMapped 0) "not mapped because it is directly inlined (for now)"
(1 120 120 genSpecialSelectorNotEqualsEquals needsFrameNever: notMapped -1) "not mapped because it is directly inlined (for now)"
(1 121 127 genSpecialSelectorSend isMapped) "#value #value: #do: #new #new: #x #y"
(1 128 143 genSendLiteralSelector0ArgsBytecode isMapped)
(1 144 159 genSendLiteralSelector1ArgBytecode isMapped)
(1 160 175 genSendLiteralSelector2ArgsBytecode isMapped)
"jumps"
(1 176 183 genShortUnconditionalJump branch v3:ShortForward:Branch:Distance:)
(1 184 191 genShortJumpIfTrue branch isBranchTrue isMapped "because of mustBeBoolean"
v3:ShortForward:Branch:Distance:)
(1 192 199 genShortJumpIfFalse branch isBranchFalse isMapped "because of mustBeBoolean"
v3:ShortForward:Branch:Distance:)
"stores"
(1 200 207 genStoreAndPopReceiverVariableBytecode isInstVarRef is1ByteInstVarStore isMappedIfImmutability needsFrameIfImmutability: -1)
(1 208 215 genStoreAndPopTemporaryVariableBytecode)
(1 216 216 genPopStackBytecode needsFrameNever: -1)
(1 217 217 genUnconditionalTrapBytecode isMapped)
(1 218 223 unknownBytecode)
"2 byte bytecodes"
(2 224 224 extABytecode extension)
(2 225 225 extBBytecode extension)
"pushes"
(2 226 226 genExtPushReceiverVariableBytecode isInstVarRef) "Needs a frame for context inst var access"
(2 227 227 genExtPushLiteralVariableBytecode needsFrameNever: 1)
(2 228 228 genExtPushLiteralBytecode needsFrameNever: 1)
(2 229 229 genLongPushTemporaryVariableBytecode)
(2 230 230 unknownBytecode)
(2 231 231 genPushNewArrayBytecode)
(2 232 232 genExtPushIntegerBytecode needsFrameNever: 1)
(2 233 233 genExtPushCharacterBytecode needsFrameNever: 1)
"returns"
"sends"
(2 234 234 genExtSendBytecode isMapped)
(2 235 235 genExtSendSuperBytecode isMapped)
"sista bytecodes"
(2 236 236 genCallMappedInlinedPrimitive isMapped)
"jumps"
(2 237 237 genExtUnconditionalJump branch isMapped "because of interrupt check" v4:Long:Branch:Distance:)
(2 238 238 genExtJumpIfTrue branch isBranchTrue isMapped "because of mustBeBoolean" v4:Long:Branch:Distance:)
(2 239 239 genExtJumpIfFalse branch isBranchFalse isMapped "because of mustBeBoolean" v4:Long:Branch:Distance:)
"stores"
(2 240 240 genExtStoreAndPopReceiverVariableBytecode isInstVarRef isMappedIfImmutability)
(2 241 241 genExtStoreAndPopLiteralVariableBytecode isMappedIfImmutability)
(2 242 242 genLongStoreAndPopTemporaryVariableBytecode)
(2 243 243 genExtStoreReceiverVariableBytecode isInstVarRef isMappedIfImmutability)
(2 244 244 genExtStoreLiteralVariableBytecode isMappedIfImmutability)
(2 245 245 genLongStoreTemporaryVariableBytecode)
(2 246 247 unknownBytecode)
"3 byte bytecodes"
(3 248 248 genCallPrimitiveBytecode hasUnsafeJump)
(3 249 249 genExtPushFullClosureBytecode)
(3 250 250 unknownBytecode) "was genExtPushClosureBytecode"
(3 251 251 genPushRemoteTempLongBytecode)
(3 252 252 genStoreRemoteTempLongBytecode)
(3 253 253 genStoreAndPopRemoteTempLongBytecode)
(3 254 255 unknownBytecode))
]
{ #category : #documentation }
StackToRegisterMappingCogit class >> callingConvention [
"The Smalltalk-to-Smalltalk calling convention aims to trade simplicity of compilation against
effectiveness of optimization. Most Smalltalk methods, and certainly most performance-
critical primitives have two or less arguments. So arranging that the receiver and up to two
args are in registers means that performance-critical primitives can access their arguments
in registers. So if the argument count is <= numRegArgs nothing is passed on the stack and
everything is passed in ReceiverResultReg, Arg0Reg et al. Above numRegArgs everything is
passed on the stack.
To save the CoInterpreter from change we shuffle the retpc and push the register args in
the prolog so that the frame format is unchanged by register args. Also, the trampolines for
unlinked sends do the same, as does the code preceding an interpreter primitive. It turns
out that this protocol is faster than always pushing arguments. Comparing benchFib with the
shuffling protocol against an always-push protocol on a 2.66 GHz Core i7 (MacBook Pro) , the
shuffling protocol is 6.3% faster than the always push protocol.
Not shuffling the stack and pushing register arguments after frame build is faster yet again,
5.8% faster that the stack shuffle. So it might be worth-while to change the CoInterpreter's
frame management to allow numArgs <= numRegArgs frames to push receiver and arguments
after saving the return pc. This implies changes in stack-to-context mapping, GC,
interpreter-to-machine code frame conversion and no doubt else where.
Hence the calling convention is
- if the number of arguments is less than or equal to numRegArgs then the receiver and
arguments are passed in registers. numRegArgs is 1 for V3, and 2 for Spur. The receiver
is passed in ReceiverResultReg, the first argument in Arg0Reg (esi on x86) and the second
argument (if numRegArgs = 2) in Arg1Reg (edi on x86).
- if the number of arguments is greater than numRegArgs then the calling convention is as
for SimpleStackBasedCogIt; ReceiverResultReg contains the receiver, and the receiver and
arguments are all on the stack, receiver furthest from top-of-stack. If the argument count
is > 2 then argument count is passed in SendNumArgsReg (for the benefit of the run-time
linking routines; it is ignored in linked sends).
On return the result is in ReceiverResultReg. The callee removes arguments from the stack
(Pascal convention).
Note that if a machine code method contains a call to an interpreter primitive it will push any
register arguments (and if on a RISC, the return pc from the LinReg) on the stack before calling
the primitive so that to the primitive the stack looks the same as it does in the interpreter.
Within all machine code primitives except genPrimitiveClosureValue and genPrimitivePerform all
arguments are taken from registers since no machine code primitive has more than numRegArgs
arguments. genPrimitiveClosureValue pushes its register arguments immedately only for laziness
to be able to reuse SimpleStackBasedCogit's code. genPrimitivePerform adjusts its arguments
as required by special-purpose code.
Within machine code methods with interpreter primitives the register arguments are pushed
before calling the interpreter primitive. In normal methods and if not already done so in primitive
code, the register arguments are pushed during frame build. If a method is compiled frameless
it will access its arguments in registers."
]
{ #category : #translation }
StackToRegisterMappingCogit class >> declareCVarsIn: aCodeGen [
aCodeGen
var: #methodAbortTrampolines
declareC: 'sqInt methodAbortTrampolines[4]';
var: #picAbortTrampolines
declareC: 'sqInt picAbortTrampolines[4]';
var: #picMissTrampolines
declareC: 'sqInt picMissTrampolines[4]';
var: 'ceCall0ArgsPIC'
declareC: 'void (*ceCall0ArgsPIC)(void)';
var: 'ceCall1ArgsPIC'
declareC: 'void (*ceCall1ArgsPIC)(void)';
var: 'ceCall2ArgsPIC'
declareC: 'void (*ceCall2ArgsPIC)(void)';
var: #ceCallCogCodePopReceiverArg0Regs
declareC: 'void (*ceCallCogCodePopReceiverArg0Regs)(void)';
var: #realCECallCogCodePopReceiverArg0Regs
declareC: 'void (*realCECallCogCodePopReceiverArg0Regs)(void)';
var: #ceCallCogCodePopReceiverArg1Arg0Regs
declareC: 'void (*ceCallCogCodePopReceiverArg1Arg0Regs)(void)';
var: #realCECallCogCodePopReceiverArg1Arg0Regs
declareC: 'void (*realCECallCogCodePopReceiverArg1Arg0Regs)(void)';
var: 'simStack'
declareC: 'SimStackEntry simStack[', self simStackSlots asString, ']';
var: 'simSelf'
type: #CogSimStackEntry;
var: #optStatus
type: #CogSSOptStatus;
var: 'prevBCDescriptor'
type: #'BytecodeDescriptor *'.
self numPushNilsFunction ifNotNil:
[aCodeGen
var: 'numPushNilsFunction'
declareC: 'sqInt (* const numPushNilsFunction)(struct _BytecodeDescriptor *,sqInt,sqInt,sqInt) = ', (aCodeGen cFunctionNameFor: self numPushNilsFunction);
var: 'pushNilSizeFunction'
declareC: 'sqInt (* const pushNilSizeFunction)(sqInt,sqInt) = ', (aCodeGen cFunctionNameFor: self pushNilSizeFunction)].
]
{ #category : #'class initialization' }
StackToRegisterMappingCogit class >> initializeBytecodeTableForSistaV1 [
"StackToRegisterMappingCogit initializeBytecodeTableForSistaV1"
numPushNilsFunction := #sistaV1:Num:Push:Nils:.
pushNilSizeFunction := #sistaV1PushNilSize:numInitialNils:.
BytecodeSetHasDirectedSuperSend := true.
BytecodeSetHasExtensions := true.
FirstSpecialSelector := 96.
NumSpecialSelectors := 32.
self flag:
'Special selector send class must be inlined to agree with the interpreter, which
inlines class. If class is sent to e.g. a general instance of ProtoObject then unless
class is inlined there will be an MNU. It must be that the Cointerpreter and Cogit
have identical semantics. We get away with not hardwiring the other special
selectors either because in the Cointerpreter they are not inlined or because they
are inlined only to instances of classes for which there will always be a method.'.
self generatorTableFrom: self bytecodeTable
]
{ #category : #'class initialization' }
StackToRegisterMappingCogit class >> initializeSimStackConstants [
"The simulation stack is used to delay code generation until operands are consumed by
some operation, thereby avoiding pushing operands to the real stack and enabling
mapping stack contents to registers, and cheaply apply various peephole optimizations.
The simulation stack is an array of CogSimStackEntry structs. Each entry defines the
object on the virtual stack (Smalltalk context stack) as compilation proceeds. See
stackToRegisterMapping in this class for documentation."
SSBaseOffset := 1.
SSConstant := 2.
SSRegister := 3.
SSSpill := 4.
SSVectorRegister := 18.
]
{ #category : #'class initialization' }
StackToRegisterMappingCogit class >> initializeWithOptions: optionsDictionary [
super initializeWithOptions: optionsDictionary.
self initializeSimStackConstants
]
{ #category : #translation }
StackToRegisterMappingCogit class >> isAcceptableAncilliaryClass: aClass [
^(aClass includesBehavior: CogBytecodeFixup)
ifTrue: [aClass == self basicNew bytecodeFixupClass]
ifFalse:
[(aClass includesBehavior: CogSimStackEntry)
ifTrue: [aClass == self basicNew simStackEntryClass]
ifFalse: [true]]
]
{ #category : #translation }
StackToRegisterMappingCogit class >> mustBeGlobal: var [
"Answer if a variable must be global and exported. Used for inst vars that are accessed from VM support code."
^(super mustBeGlobal: var)
or: [#('ceCallCogCodePopReceiverArg0Regs' 'ceCallCogCodePopReceiverArg1Arg0Regs'
'realCECallCogCodePopReceiverArg0Regs' 'realCECallCogCodePopReceiverArg1Arg0Regs'
'ceCall0ArgsPIC' 'ceCall1ArgsPIC' 'ceCall2ArgsPIC') includes: var]
]
{ #category : #accessing }
StackToRegisterMappingCogit class >> numPushNilsFunction [
"Answer the value of numPushNilsFunction"
^numPushNilsFunction
]
{ #category : #accessing }
StackToRegisterMappingCogit class >> numTrampolines [
^super numTrampolines + 12 "includes register args aborts"
"Cogit withAllSubclasses collect: [:c| {c. (c instVarNames select: [:ea| ea beginsWith: 'ce']) size}]"
"self instVarNames select: [:ea| ea beginsWith: 'ce']"
]
{ #category : #accessing }
StackToRegisterMappingCogit class >> pushNilSizeFunction [
"Answer the value of pushNilSizeFunction"
^ pushNilSizeFunction
]
{ #category : #translation }
StackToRegisterMappingCogit class >> requiredMethodNames: options [
^(super requiredMethodNames: options)
add: self numPushNilsFunction;
add: self pushNilSizeFunction;
yourself
]
{ #category : #translation }
StackToRegisterMappingCogit class >> shouldGenerateTypedefFor: aStructClass [
"Hack to work-around mutliple definitions. Sometimes a type has been defined in an include."
^aStructClass ~~ CogBytecodeFixup "overridden by CogSSBytecodeFixup"
and: [super shouldGenerateTypedefFor: aStructClass]
]
{ #category : #translation }
StackToRegisterMappingCogit class >> simNativeStackSlots [
^ self basicNew simNativeStackSlots
]
{ #category : #translation }
StackToRegisterMappingCogit class >> simStackSlots [
^ self basicNew simStackSlots
]
{ #category : #documentation }
StackToRegisterMappingCogit class >> stackToRegisterMapping [
"Stack to register mapping is enabled via a simulation stack { simStack. simStackPtr, simSpillBase } of
operand descriptors (CogSimStackEntry) which serve
- to avoid pushing operands to the actual stack by deferring operand manipulation until an
operand-consuming operation (send, store, run-time call)
- to record operand type information for constants to avoid unnecessary type checks (e.g. tag checks)
- as a simple register allocator since any live registers are recorded in descriptors on the stack.
The operand types are
SSBaseOffset - a value in memory at an offset relative to some register. For method receiver args
and temps the base register is FPReg (in a frameful method). For indirect temps
the register could be any unassigned register.
SSConstant - a method literal, hence a Smalltalk object
SSRegister - the result of an expression assigned to a register
SSSpill - a value spilled to the actual stack
The special descriptor simSelf defines self in the current method, relative to FPReg in frameful
methods and in a register in frameless methods.
The register allocator aspect allocates registers by searching for SSBaseOffset and SSRegister
descriptors, computing the set of live registers, and then enumerating to find unused ones.
Simulation stack contents must be spilled to the actual stack
- at a send (since at a suspension point the actual stack must be valid),
- to make a register available if the code generator needs it
- at a control flow join (since the two control flows could compute different stack contents and
we choose to avoid the complexity of saving stack contents to allow merging at join points).
At a control-flow join we must discard type information for values pushed to the stack in either
arm of the control-flow, but need /not/ for items pushed before the control flow diverged. e.g. in
self at: 1 put: (expr ifTrue: [v1] ifFalse: [v2]).
the 1 is still valid after the control flow join for (expr ifTrue: [v1] ifFalse: [v2]). So at a conditional
branch we record simStackPtr in the target fixup and only void types between it and the
simStackPtr at the join point. This type voiding operation is called merge:. For now we simply throw
away all type info but would like to implement the baove scheme soon.
We can determine the stack depth at a conditional branch (if), but how do we determine the stack
depth following an unconditional jump (else)? There are essentially three cases
e ifTrue: [u] ifFalse: [v],
e ifTrue: [^u] ifFalse: [v],
e ifTrue: [u] ifFalse: [^v]
1 expr
2 jumpCond L1
3 push
4 jump L2
5 L1:
6 push
7 L2:
1 expr
2 jumpCond L1
3 ret
4 L1:
5 push
1 expr
2 jumpCond L1
3 push
4 jump L2
5 L1:
6 ret
7 L2:
In the first case we can know the merge base at L2 by propagating the merge base from 4 jump L2, which
precedes the target of 2 jumpCond L1. i.e. the merge base at 7 L2 is the stack pointer at 4 jump L2, which
precedes the target of 2 jumpCond L1. So at 2 jumpCond L1 we copy the stack pointer to the merge base
at 5 L1, /and/ to the preceding 4 jump L2, and when we reach 4 jump L2, propagate the merge base to 7 L2.
Since we're conscious of JIT performance we restrict the live register search range by maintaining
simSpillBase, which is the index of the unspilled entry furthest from the end of simulation stack.
Only entries from simSpillBase to simStackPtr can contain unspilled, and hence live and volatile
registers (the FPReg is not volatile).
We further optimize by maintaining a simple optimization status for register contents.
We record whether ReceiverResultReg contains the receiver or an indirect temp vector
and merge this status at control-flow joins."
]
{ #category : #'primitive generators' }
StackToRegisterMappingCogit >> adjustArgumentsForPerform: numArgs [
"Generate code to adjust the possibly stacked arguments immediately
before jumping to a method looked up by a perform primitive."
self assert: self numRegArgs <= 2.
self assert: numArgs >= 1.
numArgs <= self numRegArgs ifTrue:
[numArgs = 2 ifTrue:
[self MoveR: Arg1Reg R: Arg0Reg].
^self].
"If the arity is one more than the max numRegArgs, the receiver and all arguments have to be removed from the stack."
self numRegArgs + 1 = numArgs ifTrue:
[backEnd hasLinkRegister
ifTrue:
[self numRegArgs = 2
ifTrue:
[self MoveMw: 0 r: SPReg R: Arg1Reg.
self MoveMw: objectMemory wordSize r: SPReg R: Arg0Reg]
ifFalse:
[self MoveMw: 0 r: SPReg R: Arg0Reg].
self AddCq: numArgs + 1 * objectMemory wordSize R: SPReg]
ifFalse:
[self MoveMw: 0 r: SPReg R: TempReg. "save retpc"
self numRegArgs = 2
ifTrue:
[self MoveMw: objectMemory wordSize r: SPReg R: Arg1Reg.
self MoveMw: objectMemory wordSize * 2 r: SPReg R: Arg0Reg]
ifFalse:
[self MoveMw: objectMemory wordSize r: SPReg R: Arg0Reg].
self AddCq: numArgs + 1 * objectMemory wordSize R: SPReg.
self MoveR: TempReg Mw: 0 r: SPReg "Overwrite pushed receiver; ReceiverResultReg already contains receiver."].
^self].
"e.g. Receiver Receiver
Selector/Arg0 => Arg1
Arg1 Arg2
Arg2 sp-> Arg3
sp-> Arg3"
super adjustArgumentsForPerform: numArgs
]
{ #category : #'bytecode generator support' }
StackToRegisterMappingCogit >> allocateEqualsEqualsRegistersArgNeedsReg: argNeedsReg rcvrNeedsReg: rcvrNeedsReg into: binaryBlock [
| argReg rcvrReg |
self assert: (argNeedsReg or: [ rcvrNeedsReg ]).
argReg := rcvrReg := NoReg.
argNeedsReg
ifTrue: [
rcvrNeedsReg
ifTrue: [
self allocateRegForStackTopTwoEntriesInto: [ :rTop :rNext |
argReg := rTop.
rcvrReg := rNext ].
self ssTop copyToReg: argReg.
(self ssValue: 1) copyToReg: rcvrReg ]
ifFalse: [
argReg := self allocateRegForStackEntryAt: 0.
self ssTop copyToReg: argReg ] ]
ifFalse: [
self assert: rcvrNeedsReg.
self deny: self ssTop spilled.
rcvrReg := self allocateRegForStackEntryAt: 1.
(self ssValue: 1) copyToReg: rcvrReg ].
self deny: (argNeedsReg and: [ argReg = NoReg ]).
self deny: (rcvrNeedsReg and: [ rcvrReg = NoReg ]).
binaryBlock value: rcvrReg value: argReg
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateFloatRegNotConflictingWith: regMask [
| reg |
"if there's a free register, use it"
reg := backEnd availableFloatRegisterOrNoneFor: (self liveFloatRegisters bitOr: regMask).
reg = NoReg ifTrue: "No free register, choose one that does not conflict with regMask"
[reg := self freeAnyFloatRegNotConflictingWith: regMask].
^ reg
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegForStackEntryAt: index [
"If the stack entry is already in a register, answers it,
else allocate a new register for it"
<inline: true>
^ self allocateRegForStackEntryAt: index notConflictingWith: 0
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegForStackEntryAt: index notConflictingWith: regMask [
"If the stack entry is already in a register not conflicting with regMask, answers it,
else allocate a new register not conflicting with reg mask"
<var: #stackEntry type: #'CogSimStackEntry *'>
| stackEntry mask |
stackEntry := self ssValue: index.
mask := stackEntry registerMaskOrNone.
(mask ~= 0 and: [mask noMask: regMask]) ifTrue:
[self flag: #TODO.
"When one does pushDup on a SSRegister
followed by an operation reusing the register
but mutating the value of the register, then the value that was
dup is now refering to the register with a mutated value which
is incorrect. This problem is serious as it can happen also with
multiple unsafe instructions (no dup involved)
This is not a problem if the operation reusing the register is not
mutating the value of if all the dup values are used in the same
operation.
=> I think we should introduce allocateRegMutatingStackEntryAt:
notConflictingWith:upThrough:, used by operations mutating the reg
value and flushing partially the stack if somewhere between simSpill
and the ssEntries used by the operation someone uses also the same
register."
^stackEntry registerOrNone].
^self allocateRegNotConflictingWith: regMask
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegForStackTopThreeEntriesInto: trinaryBlock thirdIsReceiver: thirdIsReceiver [
"Answers registers for the 3 top values on stack. If the values are already in registers, answers
these registers, else allocate registers not conflicting with each others.
If thirdIsReceiver is true, allocate ReceiverResultReg for stackTop - 2 (for ceStoreCheck)."
<inline: true>
| topRegistersMask rTop rNext rThird |
topRegistersMask := 0.
rTop := rNext := rThird := NoReg.
(self ssTop registerOrNone ~= NoReg and: [ thirdIsReceiver not or: [ self ssTop registerOrNone ~= ReceiverResultReg ] ]) ifTrue:
[ topRegistersMask := self registerMaskFor: (rTop := self ssTop registerOrNone)].
((self ssValue: 1) registerOrNone ~= NoReg and: [ thirdIsReceiver not or: [ (self ssValue: 1) registerOrNone ~= ReceiverResultReg ] ]) ifTrue:
[ topRegistersMask := topRegistersMask bitOr: (self registerMaskFor: (rNext := (self ssValue: 1) registerOrNone))].
((self ssValue: 2) registerOrNone ~= NoReg and: [thirdIsReceiver not or: [ (self ssValue: 2) registerOrNone = ReceiverResultReg ] ]) ifTrue:
[ topRegistersMask := topRegistersMask bitOr: (self registerMaskFor: (rThird := (self ssValue: 2) registerOrNone))].
rThird = NoReg ifTrue:
[ thirdIsReceiver
ifTrue:
[ rThird := ReceiverResultReg. "Free ReceiverResultReg if it was not free"
self ssAllocateRequiredReg: ReceiverResultReg.
self voidReceiverResultRegContainsSelf ]
ifFalse: [ rThird := self allocateRegNotConflictingWith: topRegistersMask ].
topRegistersMask := topRegistersMask bitOr: (self registerMaskFor: rThird) ].
rTop = NoReg ifTrue:
[ rTop := self allocateRegNotConflictingWith: topRegistersMask.
topRegistersMask := topRegistersMask bitOr: (self registerMaskFor: rTop) ].
rNext = NoReg ifTrue:
[ rNext := self allocateRegNotConflictingWith: topRegistersMask ].
self deny: (rTop = NoReg or: [rNext = NoReg or: [rThird = NoReg]]).
^ trinaryBlock value: rTop value: rNext value: rThird
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegForStackTopTwoEntriesInto: binaryBlock [
"Answers registers for the 2 top values on stack. If the values are already in registers, answers
these registers, else allocate registers not conflicting with each others."
| topRegistersMask rTop rNext |
topRegistersMask := 0.
rTop := rNext := NoReg.
self ssTop registerOrNone ~= NoReg ifTrue:
[ rTop := self ssTop registerOrNone].
(self ssValue: 1) registerOrNone ~= NoReg ifTrue:
[ topRegistersMask := self registerMaskFor: (rNext := (self ssValue: 1) registerOrNone)].
rTop = NoReg ifTrue:
[ rTop := self allocateRegNotConflictingWith: topRegistersMask ].
rNext = NoReg ifTrue:
[ rNext := self allocateRegNotConflictingWith: (self registerMaskFor: rTop) ].
self deny: (rTop = NoReg or: [rNext = NoReg]).
^ binaryBlock value: rTop value: rNext
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegNotConflictingWith: regMask [
"Allocate a register not conflicting with the given mask.
If there are no free registers satisfying a condition, spill one register.
If spill fails, panic! as we the client expects a register"
^ self
allocateRegNotConflictingWith: regMask
ifNone: [ self error: 'Could not allocate register'. 0 "to keep slang happy" ]
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateRegNotConflictingWith: regMask ifNone: aBlock [
"Allocate a register not conflicting with the given mask.
If there are no free registers satisfying a condition, spill one register.
If spill fails, call the argument block"
| reg |
"if there's a free register, use it"
reg := backEnd availableRegisterOrNoneFor: (self liveRegisters bitOr: regMask).
"No free register, choose one that does not conflict with regMask"
reg = NoReg ifTrue: [ reg := self freeAnyRegNotConflictingWith: regMask ].
"Couldn't spill, call the fallback block"
reg = NoReg ifTrue: [
self flag: #todo. "The line below should return".
"But currently slang does not accept it"
aBlock value ].
reg = ReceiverResultReg ifTrue: "If we've allocated RcvrResultReg, it's not live anymore"
[self voidReceiverResultRegContainsSelf].
^ reg
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateVectorRegForStackEntryAt: index notConflictingWith: regMask [
"If the stack entry is already in a register not conflicting with regMask, answers it,
else allocate a new register not conflicting with reg mask"
<var: #stackEntry type: #'CogSimStackEntry *'>
| stackEntry mask |
stackEntry := self ssValue: index.
mask := stackEntry registerMaskOrNone.
(mask ~= 0 and: [mask noMask: regMask]) ifTrue:
[self flag: #TODO.
"When one does pushDup on a SSRegister
followed by an operation reusing the register
but mutating the value of the register, then the value that was
dup is now refering to the register with a mutated value which
is incorrect. This problem is serious as it can happen also with
multiple unsafe instructions (no dup involved)
This is not a problem if the operation reusing the register is not
mutating the value of if all the dup values are used in the same
operation.
=> I think we should introduce allocateRegMutatingStackEntryAt:
notConflictingWith:upThrough:, used by operations mutating the reg
value and flushing partially the stack if somewhere between simSpill
and the ssEntries used by the operation someone uses also the same
register."
^stackEntry registerOrNone].
^self allocateVectorRegNotConflictingWith: regMask
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> allocateVectorRegNotConflictingWith: regMask [
| reg |
"if there's a free register, use it"
reg := backEnd availableVectorRegisterOrNoneFor: (self liveVectorRegisters bitOr: regMask).
"reg = NoReg ifTrue: ""No free register, choose one that does not conflict with regMask""
[reg := self freeAnyRegNotConflictingWith: regMask]."
^ reg
]
{ #category : #'bytecode generator support' }
StackToRegisterMappingCogit >> annotateInstructionForBytecode [
"Annotate the current instruction as having a bytecode pc. If the current instruction
is already annotated, insert a nop and annotate that. We cannot have the same instruction
address carry multiple annotations."
<inline: true>
self annotateBytecode: (self prevInstIsPCAnnotated
ifTrue: [self Nop]
ifFalse: [self Label]).
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> anyReferencesToRegister: reg inAllButTopNItems: n [
| regMask |
regMask := self registerMaskFor: reg.
simStackPtr - n to: 0 by: -1 do:
[:i|
((self simStackAt: i) registerMask anyMask: regMask) ifTrue:
[^true]].
^false
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> anyReferencesToRegister: reg inTopNItems: n [
| regMask |
regMask := self registerMaskFor: reg.
simStackPtr to: simStackPtr - n + 1 by: -1 do:
[:i|
((self simStackAt: i) registerMask anyMask: regMask) ifTrue:
[^true]].
^false
]
{ #category : #'compile abstract instructions' }
StackToRegisterMappingCogit >> assertCorrectSimStackPtr [
<inline: true>
"Would like to assert simply simSpillBase > methodOrBlockNumTemps but can't because
of the initialNils hack for nested blocks in SqueakV3PlusClosures"
self assert: (simSpillBase >= methodOrBlockNumTemps).
(needsFrame and: [simSpillBase > 0]) ifTrue:
[self assert: (self simStackAt: simSpillBase - 1) spilled == true.
self assert: (simSpillBase > simStackPtr or: [(self simStackAt: simSpillBase) spilled == false])].
self cCode: '' inSmalltalk:
[deadCode ifFalse:
[self assert: simStackPtr + (needsFrame ifTrue: [0] ifFalse: [1])
= (self debugStackPointerFor: bytecodePC)]].
]
{ #category : #'simulation stack' }
StackToRegisterMappingCogit >> availableRegOrNoneNotConflictingWith: regMask [
<inline: true>
"If there's a free register, answer it, otherwise answer NoReg."
^backEnd availableRegisterOrNoneFor: (self liveRegisters bitOr: regMask)
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> bytecodeFixupClass [
<doNotGenerate>
^CogSSBytecodeFixup
]
{ #category : #trampolines }
StackToRegisterMappingCogit >> cPICMissTrampolineFor: numArgs [
^picMissTrampolines at: (numArgs min: self numRegArgs + 1)
]
{ #category : #debugging }
StackToRegisterMappingCogit >> callCogCodePopReceiverArg0Regs [
"This is a static version of ceCallCogCodePopReceiverArg0Regs
for break-pointing when debugging in C."
<api>
<inline: false>
"This exists only for break-pointing."
self cCode: [self realCECallCogCodePopReceiverArg0Regs]
inSmalltalk: [self ceCallCogCodePopReceiverArg0Regs]
]
{ #category : #debugging }
StackToRegisterMappingCogit >> callCogCodePopReceiverArg1Arg0Regs [
"This is a static version of ceCallCogCodePopReceiverArg1Arg0Regs
for break-pointing when debugging in C."
<api>
<inline: false>
"This exists only for break-pointing."
self cCode: [self realCECallCogCodePopReceiverArg1Arg0Regs]
inSmalltalk: [self ceCallCogCodePopReceiverArg1Arg0Regs]
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceCall0ArgsPIC [
<api: 'extern void (*ceCall0ArgsPIC)()'>
<doNotGenerate>
self simulateEnilopmart: ceCall0ArgsPIC numArgs: 1
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceCall1ArgsPIC [
<api: 'extern void (*ceCall1ArgsPIC)()'>
<doNotGenerate>
self simulateEnilopmart: ceCall1ArgsPIC numArgs: 1
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceCall2ArgsPIC [
<api: 'extern void (*ceCall2ArgsPIC)()'>
<doNotGenerate>
self simulateEnilopmart: ceCall2ArgsPIC numArgs: 1
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceCallCogCodePopReceiverArg0Regs [
<api: 'extern void (*ceCallCogCodePopReceiverArg0Regs)()'>
<doNotGenerate>
self simulateEnilopmart: ceCallCogCodePopReceiverArg0Regs numArgs: 2
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceCallCogCodePopReceiverArg1Arg0Regs [
<api: 'extern void (*ceCallCogCodePopReceiverArg1Arg0Regs)()'>
<doNotGenerate>
self simulateEnilopmart: ceCallCogCodePopReceiverArg1Arg0Regs numArgs: 3
]
{ #category : #accessing }
StackToRegisterMappingCogit >> ceMethodAbortTrampoline: trampoline [
<doNotGenerate>
super ceMethodAbortTrampoline: trampoline.
^ methodAbortTrampolines at: 0 put: trampoline
]
{ #category : #'simulation only' }
StackToRegisterMappingCogit >> ceShortCutTraceStore: aProcessorSimulationTrap [
<doNotGenerate>
self shortcutTrampoline: aProcessorSimulationTrap
to: [coInterpreter
ceTraceStoreOf: (processor registerAt: TempReg)
into: (processor registerAt: ReceiverResultReg)]
]
{ #category : #accessing }
StackToRegisterMappingCogit >> ceStoreCheckTrampoline [
<doNotGenerate>
^ self objectRepresentation ceStoreCheckTrampoline
]
{ #category : #'compile abstract instructions' }
StackToRegisterMappingCogit >> compileAbstractInstructionsFrom: start through: end [
"Loop over bytecodes, dispatching to the generator for each bytecode, handling fixups in due course."
| nextOpcodeIndex descriptor nExts fixup result |
<var: #descriptor type: #'BytecodeDescriptor *'>
<var: #fixup type: #'BytecodeFixup *'>
self traceSimStack.
bytecodePC := start.
nExts := result := 0.
descriptor := nil.
deadCode := false.
[self maybeHaltIfDebugPC.
self mergeWithFixupIfRequired: (fixup := self fixupAt: bytecodePC).
descriptor := self loadBytesAndGetDescriptor.
nextOpcodeIndex := opcodeIndex.
result := deadCode
ifTrue: [self mapDeadDescriptorIfNeeded: descriptor]
ifFalse: [self perform: descriptor generator].
result = 0 ifTrue: [self assertExtsAreConsumed: descriptor].
self traceDescriptor: descriptor; traceSimStack.
self patchFixupTargetIfNeeded: fixup nextOpcodeIndex: nextOpcodeIndex.
self maybeDumpLiterals: descriptor.
bytecodePC := self nextBytecodePCFor: descriptor exts: nExts.
result = 0 and: [bytecodePC <= end]] whileTrue:
[nExts := descriptor isExtension ifTrue: [nExts + 1] ifFalse: [0]].
self checkEnoughOpcodes.
^result
]