Repository navigation
Expand file tree
/
Copy pathBytecodeSimulatorForJS.ns
More file actions
1575 lines (1488 loc) · 67 KB
/
Copy pathBytecodeSimulatorForJS.ns
File metadata and controls
1575 lines (1488 loc) · 67 KB
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
Newspeak3
'Root'
class BytecodeSimulatorForJS usingPlatform: platform simulation: simulation = Object new (
(* The V5 Simulator execution machine for the JS platform: steps the MM bytecode records emitted by dual compilation, over live NS2JS runtime objects. Ported from the upstream Simulator (upstream-V5Simulator-338f0bb3.ns.txt) per the machine mapping in CODEGEN_V5_PORT_PLAN_2026-07-14.md section 5 step 5.
UNDER CONSTRUCTION, first working slice: ordinary, self and super sends over the runtime-mixin metadata with upstream's access-modifier rules; local and non-local returns; closures created and activated in simulation; and the full-speed escape - a method found without a bytecode record (a _js-intrinsic body, an edited class) is invoked as its compiled JS function, so simulation seamlessly degrades to real execution method by method. Real closures receiving value in simulation resolve to their _js-intrinsic methods and therefore escape automatically; simulated closures are intercepted before lookup.
Not yet ported, failing with descriptive errors: eventual sends, cannotReturn:/nonBooleanReceiver: reification, and simulated closures escaping to full-speed code.
Sentinel NLR (M4) interplay: a sentinel record surfacing from a full-speed escape cannot target any simulated frame (activation ids are JS-frame ids), and it propagates out of the simulation AUTOMATICALLY -- the simulator is itself M4-compiled, so the checked send at each escape site (Message sendTo: and friends) returns the record straight out of escape:, step, and the driver loop to the simulation's real caller, which propagates it onward. KNOWN GAP: simulated unwind-protect frames (SimulationSupport surrogates) pending on the simulated stack do NOT run their protectors during such a flythrough; the abandoned simulated frames are heap data and simply become garbage. Accepted for now; revisit if a real scenario hits it. Exception handling IS simulated: on:do: rewrites to the run:protectedBy:handler: surrogate; any signal (stepped code and full-speed escapes alike throw through the step loop, since simulated frames are data) routes to the nearest matching handler frame by method identity, running intervening unwind protectors first. A stepping driver is included: start:send:with:, step, stepOver, stepThrough, stackDo:, atEnd, result. Unwind protection (ensure:/ifCurtailed:) IS ported: sends on simulated closures rewrite to the SimulationSupport surrogates, whose frames the non-local-return walk recognizes by method identity. Not yet wired into any runtime. *)
|
private List = platform collections List.
private InstructionStream = simulation InstructionStream.
private Message = platform kernel Message.
private Exception = platform kernel Exception.
|
) (
public class SimulatedActivation new = Object new (
(* One simulated frame: the upstream VM Activation, reified. Temps and the operand stack share one one-origin indexed area, upstream-style: parameters, then locals, then operands. *)
|
public sender <SimulatedActivation | nil>
public bci <Integer> ::= 1.
public method <SimulatedMethod>
public closure <SimulatedClosure | nil>
public receiver <Object>
public isDead <Boolean> ::= false.
public unwindProtectorRan <Boolean> ::= false.
frame <List> = List new.
stackPointer <Integer> ::= 0.
(* Statement-level restart support (transplanted frames): the bci of the top-level statement containing the failure, computed at transplant from the parsed source. 1 = unresolved: Continue then degrades to a full restart from the method start. *)
public restartBci <Integer> ::= 1.
|
) (
public describe ^<String> = (
^'in ' , (nil = method ifTrue: [ '<root>' ] ifFalse: [ method selector ]) , ' at bci ' , bci printString , ' (frame ' , frame size printString , ', sp ' , stackPointer printString , ')'
)
public outerContext ^<SimulatedActivation | nil> = (
(* The lexically enclosing activation, for block frames; nil for method frames. Part of the DebugMapper context protocol (NewspeakBytecodeCompilation DebugInfo). *)
nil = closure ifTrue: [ ^nil ].
^closure definingActivation
)
public pop = (
| value |
stackPointer < 1 ifTrue: [
Error signal: 'Simulated operand stack underflow ' , describe
].
value:: frame at: stackPointer.
frame at: stackPointer put: nil.
stackPointer:: stackPointer - 1.
^value
)
public push: value = (
stackPointer:: stackPointer + 1.
stackPointer > frame size ifTrue: [ frame add: value ]
ifFalse: [ frame at: stackPointer put: value ]
)
public resetForRestart = (
(* Reset this frame to its initial state, psoup Thread restart:withMethod: semantics: arguments (and, for closures, re-pushed copied values) survive; locals are cleared; the operand stack is emptied. *)
| numArgs |
nil = closure ifTrue: [
numArgs:: method numArgs.
bci:: 1.
numArgs + 1 to: frame size do: [:i | frame at: i put: nil ].
stackPointer:: method numTemps
]
ifFalse: [
bci:: closure initialBCI.
closure numArgs + 1 to: frame size do: [:i | frame at: i put: nil ].
stackPointer:: closure numArgs.
1 to: closure copied size do: [:i | push: (closure copied at: i) ]
]
)
public resetForStatementRestart = (
(* Re-enter at the failing statement's start with the locals AS CAPTURED (restored at transplant) and an empty operand stack - top-level statement boundaries hold no operands and no live control-flow context, so entry there is sound. Without a resolved statement (restartBci 1), or for a closure frame, degrade to the full restart (locals cleared): re-running from the method start must see nil locals, and the initializer statements will re-run. *)
(1 = restartBci or: [ (nil = closure) not ]) ifTrue: [ ^resetForRestart ].
bci:: restartBci.
stackPointer:: method numTemps
)
public size ^<Integer> = (
(* Current extent of the temp/operand area; DebugMapper context protocol. *)
^stackPointer
)
public size: numTemps <Integer> = (
(* Pre-establish the temp area, nil-filled, upstream Activation size: semantics. *)
[ frame size < numTemps ] whileTrue: [ frame add: nil ].
stackPointer:: numTemps
)
public tempAt: index <Integer> ^<Object> = (
(index between: 1 and: frame size) ifFalse: [
Error signal: 'Simulated temp index ' , index printString , ' out of range ' , describe
].
^frame at: index
)
public tempAt: index <Integer> put: value <Object> = (
(index between: 1 and: frame size) ifFalse: [
Error signal: 'Simulated temp index ' , index printString , ' out of range ' , describe
].
frame at: index put: value
)
public terminate = (
isDead:: true.
sender:: nil
)
public top ^<Object> = (
stackPointer < 1 ifTrue: [
Error signal: 'Simulated operand stack underflow ' , describe
].
^frame at: stackPointer
)
) : ()
public class SimulatedClosure in: definingActivation_ initialBCI: initialBCI_ numArgs: numArgs_ numCopied: numCopied_ = Object new (
(* The reification of a block created DURING simulation (upstream: VM primitive 145). Deliberately not interchangeable with real closures yet: passing one to full-speed code is an unported seam. *)
|
public definingActivation <SimulatedActivation> = definingActivation_.
public initialBCI <Integer> = initialBCI_.
public numArgs <Integer> = numArgs_.
(* A real sized JS array: List new: n is an EMPTY list of capacity n, so indexed stores into it fail. *)
public copied = _js call: (_js ident: 'Array') with: {numCopied_. }.
|
) (
public isKindOfSimulatedClosure ^<Boolean> = (
^true
)
) : ()
public class SimulatedMethod record: r mixin: m selector: s accessModifier: am = Object new (
(* An MM bytecode record paired with its owning runtime mixin (records do not carry their mixin; it is captured at lookup time) under the decoder's method protocol: bytecode, literals. The record layout is [bytes, literals, numArgs, numLocals, maxStack, bciMap, sourceStart], per Newspeak2JSCompilation encodeBytecodeOf:. The record is nil for JS-only methods: activation of such a method is the full-speed escape. *)
|
public record = r.
public mixin = m.
public selector <Symbol> = s.
public accessModifier <Symbol> = am.
(* The MM metadata entry itself, for the source index. *)
public metadata
(* Direct source text for methods with no MM entry (runtime-compiled doIts). *)
public sourceText
(* The V5 DebugInfo computed when this method was compiled at runtime (doIts): local names and bci mappings without recompilation, psoup-style (psoup stores DebugInfo as the doIt method's metadata). *)
public debugInfo
|
) (
public bciMap = (
(* Flat [bci, start, end, ...] triples, unit-relative offsets. *)
^record at: 6
)
public bytecode = (
^record at: 1
)
public literals = (
^record at: 2
)
public maxStack ^<Integer> = (
^record at: 5
)
public numArgs ^<Integer> = (
^record at: 3
)
public numLocals ^<Integer> = (
(* The compiler's maxLocals: parameters INCLUDED (psoup Method numTemps semantics). *)
^record at: 4
)
public numTemps ^<Integer> = (
(* numLocals already includes the parameters; adding numArgs again misshapes the frame. *)
^numLocals
)
public sourceStart ^<Integer> = (
(* Records predating the seventh field answer 0: rebasing then degrades to unit-relative. *)
record size < 7 ifTrue: [ ^0 ].
^record at: 7
)
) : ()
public class SimulatedThread over: sim <Simulator> = Object new (
(* A suspended/steppable computation over one Simulator: the psoup Thread (MirrorsForPrimordialSoup) ported onto the JS simulator. States: #suspended (frames live, steppable), #broken (an unhandled signal; frames live at the raise point), #fulfilled (completed; result holds the value), #terminated. Where psoup's resume jumps to full speed via a primitive, resuming here runs the simulator to completion (psoup's resumeSlowly) - the only kind of resume compiled JS frames allow, and the right one for a debugging session. *)
|
public state ::= #suspended.
public result
public simulator <Simulator> = sim.
|
) (
guard: a <SimulatedActivation> = (
(hasSuspended: a) ifFalse: [ Error signal: 'Activation is not in thread' ]
)
hasSuspended: a <SimulatedActivation> ^<Boolean> = (
| s |
s:: suspendedActivation.
[ nil = s ] whileFalse: [
s == a ifTrue: [ ^true ].
s:: s sender
].
^false
)
isBottom: a <SimulatedActivation> ^<Boolean> = (
nil = a sender ifTrue: [ ^true ].
^a sender == simulator rootActivation
)
public isKindOfSimulatedThread ^<Boolean> = (
^true
)
normalizeBrokenReason: e = (
(* Newspeak exceptions pass through; raw JS errors (no newspeakClass) are wrapped in a kernel Exception whose text carries the JS stack, so the debugger's result display can print them. *)
| c text wrapped |
c:: _js propertyOf: e at: (_js literal: 'newspeakClass').
(_js operator: '==' with: c and: (_js ident: 'null')) ifFalse: [ ^e ].
text:: _js propertyOf: e at: (_js literal: 'stack').
(_js operator: '==' with: text and: (_js ident: 'null')) ifTrue: [
text:: 'a raw JS error'
].
wrapped:: Exception new.
wrapped messageText: text.
^wrapped
)
public restart: a <SimulatedActivation> withLookup: doLookup <Boolean> = (
guard: a.
doLookup ifTrue: [
^Error signal: 'restartWithLookup is not yet supported on the JS platform'
].
unwindTo: a.
a resetForRestart.
state:: #suspended
)
public resume = (
(* Continue. psoup resumes a broken thread at the raise point (its exceptions are VM-resumable); a JS broken stack is a restart-quality transplant whose continuation is gone, so Continue on a broken thread means: re-run the outermost frame FROM ITS FAILING STATEMENT - locals restored from the shadow capture, earlier statements' effects preserved - degrading to a full re-run from the method start when no statement was resolved. An explicit user action, typically after fixing the failure's cause; the failing statement's own side effects repeat. *)
| bottom |
#broken = state ifTrue: [
bottom:: suspendedActivation.
nil = bottom ifTrue: [ ^self ].
[ isBottom: bottom ] whileFalse: [ bottom:: bottom sender ].
guard: bottom.
unwindTo: bottom.
bottom resetForStatementRestart.
state:: #suspended
].
stepUntil: [:top | false ]
)
public return: returnValue from: a <SimulatedActivation> = (
guard: a.
(isBottom: a) ifTrue: [
unwindTo: a.
a terminate.
simulator activation: simulator rootActivation.
result:: returnValue.
state:: #fulfilled.
^self
].
unwindTo: a sender.
simulator activation push: returnValue.
state:: #suspended
)
public stepInto: a <SimulatedActivation> = (
guard: a.
stepUntil: [:top | true ]
)
public stepOut: a <SimulatedActivation> = (
| goal |
guard: a.
(isBottom: a) ifTrue: [ ^resume ].
goal:: a sender.
stepUntil: [:top | simulator reached: goal ]
)
public stepOver: a <SimulatedActivation> = (
guard: a.
stepUntil: [:top | simulator reached: a ]
)
public stepThrough: a <SimulatedActivation> = (
| goal |
guard: a.
goal:: simulator home: a.
stepUntil: [:top | simulator reachedHome: goal ]
)
stepUntil: predicate <[:SimulatedActivation | Boolean]> = (
(* Step (at least once) until the predicate holds at the current activation, the simulation completes, or an unhandled signal escapes. Faithful to psoup Thread stepUntil: except for the guard: psoup steps a BROKEN thread by resuming the raise point (its exceptions are resumable); until Stage 2 brings that to the JS platform, stepping a broken or finished thread is a graceful no-op -- the UI's step buttons stay live on the displayed frames, and raising here would crash the button action (seen 2026-07-18). restart: or return:from: re-suspend a broken thread for real stepping. *)
#suspended = state ifFalse: [ ^self ].
state:: #running.
(* The escape hatch is a RAW catch, not on:do:: an unhandled signal leaves handleSignal: as a raw JS throw, and since Stage 2 protectedBodyOf: rethrows thrown NS exceptions past every on:do: (already past every matching opportunity) - a kernel on:do: here never fires, leaving the thread stuck at #running while the throw escaped to the UI. The simulator's activation still stands at the raise point - exactly the stack the debugger wants. Reconstruction state is cleared defensively (a hook-armed unhandled signal sets it before throwing); raw JS errors (TypeError etc.) have no Newspeak protocol and crash the result display, so the reason is normalized. *)
_js try: (_js block: {[
simulator step.
simulator atEnd or: [ predicate value: simulator activation ]
] whileFalse. })
catch: 'nsRunErr'
with: (_js block: {_js assign: (_js ident: 'NSReconstructing')
toBe: (_js literal: false). state:: #broken. result:: normalizeBrokenReason: (_js ident: 'nsRunErr'). _js return: self. }).
simulator atEnd ifTrue: [
state:: #fulfilled.
result:: simulator result
]
ifFalse: [ state:: #suspended ]
)
public suspendedActivation ^<SimulatedActivation | nil> = (
(#suspended = state or: [ #broken = state ]) ifFalse: [ ^nil ].
^simulator activation
)
public terminate = (
| bottom |
nil = suspendedActivation ifTrue: [ ^Error signal: 'Nothing to terminate' ].
bottom:: suspendedActivation.
[ isBottom: bottom ] whileFalse: [ bottom:: bottom sender ].
unwindTo: bottom.
bottom terminate.
simulator activation: simulator rootActivation.
state:: #terminated.
result:: nil
)
unwindTo: target <SimulatedActivation> = (
(* Discard frames from the suspended activation down to (but excluding) target. psoup parity: refuses to cross a pending unwind protector. *)
| unwind sender |
unwind:: suspendedActivation.
[ unwind == target ] whileFalse: [
(simulator isProtectorFrame: unwind) ifTrue: [
^Error signal: 'Unwinding across an unwind-protect frame is not yet supported'
].
unwind:: unwind sender
].
unwind:: suspendedActivation.
[ unwind == target ] whileFalse: [
sender:: unwind sender.
unwind terminate.
unwind:: sender
].
simulator activation: target
)
) : ()
public class SimulationSupport new = Object new (
(* Kernel-method surrogates the Simulator activates and recognizes BY METHOD IDENTITY - the capability-clean analogue of upstream's primitive-number markers (162 etc.). These are plain Newspeak, so dual emission gives them bytecode records: the Simulator rewrites closure-protocol sends on simulated closures into activations of these methods, their normal control flow is simply stepped, and the non-local-return walk recognizes run:ensure:/run:ifCurtailed: frames as unwind protection. The probe methods at the bottom exercise the machinery from a workspace. *)
|
public protectorRan <Boolean> ::= false.
|
) (
public loop: body = (
[ body value ] repeat
)
public probeCaughtSignal = (
^[ Error signal: 'boom' ] on: Exception do: [:e | 99 ]
)
public probeEnsureDuringUnwind = (
protectorRan:: false.
^[ [ Error signal: 'x' ] ensure: [ protectorRan:: true ] ] on: Exception
do: [:e | 7 ]
)
public probeEnsureNormal = (
protectorRan:: false.
^[ 7 ] ensure: [ protectorRan:: true ]
)
public probeNLRThroughEnsure = (
protectorRan:: false.
[ ^42 ] ensure: [ protectorRan:: true ].
^0
)
public probeUncaught = (
(* The handler class never matches (Message is not in any exception's class chain), so the signal must propagate out of the simulation to the caller as a real exception. *)
^[ Error signal: 'escaped' ] on: Message do: [:e | 0 ]
)
public repeatUntil: condition = (
[ condition value ] whileFalse.
^nil
)
public repeatWhile: condition = (
[ condition value ] whileTrue.
^nil
)
public run: body ensure: protector = (
| result |
result:: body value.
protector value.
^result
)
public run: body ifCurtailed: protector = (
^body value
)
public run: body protectedBy: exceptionClass handler: handler = (
(* The on:do: surrogate: its frame carries the exception class and handler in temps 2 and 3; the Simulator's signal machinery recognizes the frame by method identity and runs the handler itself, so this body only evaluates the protected block. *)
^body value
)
public while: condition do: body = (
[ condition value ] whileTrue: [ body value ].
^nil
)
public whileNot: condition do: body = (
[ condition value ] whileFalse: [ body value ].
^nil
)
) : ()
public class Simulator new = InstructionStream new (
|
public activation <SimulatedActivation>
root <SimulatedActivation>
support = SimulationSupport new.
supportMixin = runtimeMixinOf: (behaviorOf: support).
(* The kernel Exception mixin, to recognize Exception>>signal by method identity: since Stage 2 the kernel signal is plain Newspeak (handler-stack walk, resumable) for FULL-SPEED code, but simulation keeps the pre-Stage-2 semantics -- a simulated signal is rerouted to a JS throw, which the step guard converts to the surrogate-frame handler walk. *)
exceptionMixin = runtimeMixinOf: (behaviorOf: Exception new).
|
) (
aboutToReturn: result through: unwindFrame = (
(* An unwind-protect frame sits between the returning activation and its home: run the protector to completion, mark the frame so the re-walk passes it, then resume the non-local return. Upstream reaches this via Activation aboutToReturn:through:. *)
unwindFrame unwindProtectorRan: true.
runToCompletion: (unwindFrame tempAt: 2).
^returnNonLocal: result
)
activate: simulatedMethod receiver: newReceiver arguments: arguments = (
| newActivation |
(simulatedMethod selector = #signal and: [
_js operator: '===' with: simulatedMethod mixin and: exceptionMixin
]) ifTrue: [
(* Pre-Stage-2 semantics in simulation: throw to the step guard, whose handleSignal walks the SIMULATED handler frames (the on:do: surrogate). See the on:do: interception for the unification status. A statement throw, never ^-returned: `return throw x` is a JS syntax error. *)
_js throw: newReceiver
].
nil = simulatedMethod record ifTrue: [
^escape: simulatedMethod receiver: newReceiver arguments: arguments
].
newActivation:: SimulatedActivation new.
newActivation sender: activation.
newActivation method: simulatedMethod.
newActivation receiver: newReceiver.
newActivation size: simulatedMethod numTemps.
1 to: arguments size
do: [:index | newActivation tempAt: index put: (arguments at: index) ].
activation:: newActivation
)
activateClosure: closure arguments: arguments = (
| newActivation |
newActivation:: SimulatedActivation new.
newActivation sender: activation.
newActivation bci: closure initialBCI.
newActivation method: closure definingActivation method.
newActivation closure: closure.
newActivation receiver: closure definingActivation receiver.
arguments size = closure numArgs ifFalse: [
^simulatorLimitation: 'closure arity mismatch'
].
arguments do: [:argument | newActivation push: argument ].
1 to: closure copied size
do: [:index | newActivation push: (closure copied at: index) ].
activation:: newActivation
)
public adoptBrokenStack: innermost <SimulatedActivation> = (
(* Adopt a transplanted stack: a fresh root sentinel beneath the bottom frame, the innermost as the current activation. The owning SimulatedThread presents it broken; restart:/return:from: re-suspend it for stepping. *)
| bottom |
root:: SimulatedActivation new.
bottom:: innermost.
[ nil = bottom sender ] whileFalse: [ bottom:: bottom sender ].
bottom sender: root.
activation:: innermost
)
public applicationOf: runtimeMixin startingAt: behavior = (
(* Public access for the activation mirrors: the runtime class on the behavior's chain applying the given runtime mixin (whose newspeakClass identifies the defining NS class for a frame's method). *)
^findApplicationOf: runtimeMixin startingAt: behavior
)
public atEnd ^<Boolean> = (
^sameActivation: activation as: root
)
public bci = (
^activation bci
)
public bci: value = (
activation bci: value
)
public bciAtOrAfterSourceStart: offset in: m <SimulatedMethod> ^<Integer> = (
(* The earliest bciMap entry whose source interval starts at or after [offset]: positions a BLOCK frame at the block's first mapped instruction when send-level derivation found nothing (the block marker is the block's source start; its body's entries are the first ones at or beyond it). 0 when none. *)
| map i s best bestStart |
nil = m ifTrue: [ ^0 ].
nil = m record ifTrue: [ ^0 ].
map:: m bciMap.
best:: 0.
bestStart:: nil.
i:: 1.
[ i + 2 <= map size ] whileTrue: [
s:: map at: i + 1.
(s >= offset and: [ nil = bestStart or: [ s < bestStart ] ]) ifTrue: [
bestStart:: s.
best:: map at: i
].
i:: i + 3
].
^best
)
bciAtSourceStart: offset in: m <SimulatedMethod> ^<Integer> = (
(* The bci of the bciMap entry whose source interval starts exactly at [offset] - the coordinate the compiler's site stores carry (message start, the same position the V5 DebugInfo maps). 0 when absent. *)
| map i |
map:: m bciMap.
i:: 1.
[ i + 2 <= map size ] whileTrue: [
offset = (map at: i + 1) ifTrue: [ ^map at: i ].
i:: i + 3
].
^0
)
behavior: behavior hasSelector: selector = (
| mms |
mms:: runtimeMethodsOf: behavior.
nil = mms ifTrue: [ ^false ].
mms do: [:mm |
(_js propertyOf: mm at: (_js literal: 'name')) = selector ifTrue: [ ^true ]
].
^false
)
behaviorNamed: mixinName startingAt: startBehavior = (
| behavior rm |
behavior:: startBehavior.
[ nil = behavior ] whileFalse: [
rm:: runtimeMixinOf: behavior.
nil = rm ifFalse: [ (mixinNameOf: rm) = mixinName ifTrue: [ ^behavior ] ].
behavior:: behaviorOf: behavior
].
^simulatorLimitation: 'enclosing mixin application not found: ' , mixinName
)
behaviorOf: obj = (
(* The runtime class of any value: its JS prototype. Primitives box (their patched prototypes carry the kernel methods); the chain top normalizes to nil. *)
| proto = _js call: (_js ident: 'Object.getPrototypeOf') with: {obj. }. |
(_js operator: '===' with: proto and: (_js ident: 'null')) ifTrue: [ ^nil ].
^proto
)
booleanControl: selector on: aBoolean arguments: arguments ^<Boolean> = (
(* The boolean control protocol, interpreted in-simulator. On psoup these are ordinary bytecode methods the upstream Simulator steps; on the JS platform they are _js-intrinsic (JS-only), and escaping them would pass simulated closures into full-speed code. Answers whether the selector was handled. *)
selector = #and: ifTrue: [
aBoolean ifTrue: [ valueInSimulation: (arguments at: 1) ]
ifFalse: [ activation push: false ].
^true
].
selector = #or: ifTrue: [
aBoolean ifTrue: [ activation push: true ]
ifFalse: [ valueInSimulation: (arguments at: 1) ].
^true
].
selector = #ifTrue: ifTrue: [
aBoolean ifTrue: [ valueInSimulation: (arguments at: 1) ]
ifFalse: [ activation push: nil ].
^true
].
selector = #ifFalse: ifTrue: [
aBoolean ifTrue: [ activation push: nil ]
ifFalse: [ valueInSimulation: (arguments at: 1) ].
^true
].
selector = #ifTrue:ifFalse: ifTrue: [
valueInSimulation: (arguments at: (aBoolean ifTrue: [ 1 ] ifFalse: [ 2 ])).
^true
].
selector = #ifFalse:ifTrue: ifTrue: [
valueInSimulation: (arguments at: (aBoolean ifTrue: [ 2 ] ifFalse: [ 1 ])).
^true
].
^false
)
dispatchOrdinary: selector receiver: messageReceiver arguments: arguments = (
(* Upstream ordinarySend: lookup: public finds, protected on the receiver class means DNU for an ordinary send. Interception of simulated closures happens before lookup, since they are not instances of the real Closure class. *)
| receiverBehavior lookupBehavior found |
(isSimulatedClosure: messageReceiver) ifTrue: [
(isValueSelector: selector arity: arguments size) ifTrue: [
^activateClosure: messageReceiver arguments: arguments
].
selector = #ensure: ifTrue: [
^dispatchOrdinary: #run:ensure:
receiver: support
arguments: {messageReceiver. arguments at: 1. }
].
selector = #ifCurtailed: ifTrue: [
^dispatchOrdinary: #run:ifCurtailed:
receiver: support
arguments: {messageReceiver. arguments at: 1. }
].
selector = #whileTrue: ifTrue: [
^dispatchOrdinary: #while:do:
receiver: support
arguments: {messageReceiver. arguments at: 1. }
].
selector = #whileFalse: ifTrue: [
^dispatchOrdinary: #whileNot:do:
receiver: support
arguments: {messageReceiver. arguments at: 1. }
].
selector = #whileTrue ifTrue: [
^dispatchOrdinary: #repeatWhile:
receiver: support
arguments: {messageReceiver. }
].
selector = #whileFalse ifTrue: [
^dispatchOrdinary: #repeatUntil:
receiver: support
arguments: {messageReceiver. }
].
selector = #repeat ifTrue: [
^dispatchOrdinary: #loop:
receiver: support
arguments: {messageReceiver. }
].
selector = #on:do: ifTrue: [
^
(* Simulation keeps its OWN (pre-Stage-2) exception semantics for now: the surrogate frame + handleSignal, with simulated signals rerouted to throws (see activate:). Full unification with the kernel's resumable handler stack is designed but deferred: a handler shell invoked from a full-speed signal would need its wrapper's NLR to unwind across a NESTED simulation loop into the outer chain, which the runToCompletion walk cannot do yet. *)
dispatchOrdinary: #run:protectedBy:handler:
receiver: support
arguments: {messageReceiver. arguments at: 1. arguments at: 2. }
]
].
(true = messageReceiver or: [ false = messageReceiver ]) ifTrue: [
(booleanControl: selector on: messageReceiver arguments: arguments) ifTrue: [
^self
]
].
receiverBehavior:: behaviorOf: messageReceiver.
lookupBehavior:: receiverBehavior.
[ nil = lookupBehavior ] whileFalse: [
found:: methodFor: selector in: lookupBehavior.
nil = found ifFalse: [
#public = found accessModifier ifTrue: [
^activate: found receiver: messageReceiver arguments: arguments
].
#protected = found accessModifier ifTrue: [
^escapeDnu: selector receiver: messageReceiver arguments: arguments
]
].
lookupBehavior:: behaviorOf: lookupBehavior
].
^escapeDnu: selector receiver: messageReceiver arguments: arguments
)
dup = (
activation push: activation top
)
enclosingMixinNameOf: qualifiedName = (
(* The parent in the nesting tree, by qualified name: everything before the last backquote; nil at top level. *)
| last |
last:: 0.
1 to: qualifiedName size
do: [:i | (qualifiedName copyFrom: i to: i) = '`' ifTrue: [ last:: i ] ].
last = 0 ifTrue: [ ^nil ].
^qualifiedName copyFrom: 1 to: last - 1
)
enclosingMixinOf: runtimeMixin boundIn: receiver = (
(* The runtime mixins carry no parent pointer; recover the parent application from its qualified name on the enclosing receiver's class chain. *)
| parentName = enclosingMixinNameOf: (mixinNameOf: runtimeMixin). |
nil = parentName ifTrue: [ ^nil ].
^runtimeMixinOf: (behaviorNamed: parentName
startingAt: (behaviorOf: receiver))
)
enclosingObjectOf: behavior = (
(* The depth-1 enclosing instance: the runtime class carries the enclosing-objects array at a per-class synthetic property, indexed from the immediate enclosing object outward (self is not in the array). *)
| prop = mangleQualified: 'enclosingObjects`' , (mixinNameOf: (runtimeMixinOf: behavior)). |
^(_js propertyOf: behavior at: prop) at: 1
)
escape: simulatedMethod <SimulatedMethod> receiver: receiver arguments: arguments = (
(* The full-speed escape: run a method that has no bytecode record as its compiled JS function and push the result. Public sends re-dispatch through the kernel Message (whose mangler is authoritative, binary selectors included); protected targets are fetched at the protected mangling on the receiver's prototype chain. PRIVATE members are NOT installed on prototypes at all: they live as functions on the DEFINING MIXIN object, taking the receiver as their first parameter (the compiler's early-bound $N[...](self) convention) -- fetching them off the receiver DNUs (bug seen 2026-07-18: an enclosing module's private slot accessor, record-less because synthetic, broke stepping through Workspace>>platform). Exceptions propagate through the normal signal path. Simulated closures crossing INTO full speed are wrapped in re-entrant shells (fullSpeedValue:) so the escaped code can invoke them like any closure. *)
| message shelled result |
shelled:: arguments collect: [:a | fullSpeedValue: a ].
message:: Message selector: simulatedMethod selector arguments: shelled.
#public = simulatedMethod accessModifier ifTrue: [
result:: message sendTo: (fullSpeedValue: receiver)
]
ifFalse: [
#private = simulatedMethod accessModifier ifTrue: [
| f = _js propertyOf: simulatedMethod mixin at: message mangledSelector. |
result:: _js call: (_js propertyOf: f at: (_js literal: 'apply'))
with: {_js ident: 'null'. {receiver. } , shelled. }
]
ifFalse: [
| f = _js propertyOf: receiver at: '_' , message mangledSelector. |
result:: _js call: (_js propertyOf: f at: (_js literal: 'apply'))
with: {receiver. shelled. }
]
].
activation push: result
)
escapeDnu: selector receiver: receiver arguments: arguments = (
(* Lookup failed in simulation: real dispatch implements the same rules and raises the real doesNotUnderstand:, so delegate wholesale. *)
activation push: ((Message selector: selector
arguments: (arguments collect: [:a | fullSpeedValue: a ])) sendTo: receiver)
)
eventualSend: selector numArgs: numArgs = (
simulatorLimitation: 'eventual sends are not yet simulated'
)
exceptionMatches: signaled class: exceptionClass ^<Boolean> = (
(* The kernel on:do: matching rule: walk the signaled object's class chain comparing identity; raw JS errors carry no newspeakClass and match as plain Exception. *)
| c |
c:: normalizeNull: (_js propertyOf: signaled
at: (_js literal: 'newspeakClass')).
nil = c ifTrue: [ c:: Exception ].
[ nil = c ] whileFalse: [
(_js operator: '===' with: c and: exceptionClass) ifTrue: [ ^true ].
c:: c superclass
].
^false
)
findApplicationOf: runtimeMixin startingAt: startingBehavior = (
| behavior |
behavior:: startingBehavior.
[ nil = behavior ] whileFalse: [
(_js operator: '===' with: (runtimeMixinOf: behavior) and: runtimeMixin) ifTrue: [
^behavior
].
behavior:: behaviorOf: behavior
].
^simulatorLimitation: 'mixin application not found on the receiver class chain'
)
public fullSpeedValue: value = (
(* A value crossing from simulation into full-speed code: simulated closures become re-entrant shells (real compiled closures that re-enter simulation when invoked); everything else passes through. *)
(isSimulatedClosure: value) ifTrue: [ ^shellFor: value ].
^value
)
guarded: aBlock = (
(* The signal guard around simulation driving. BOTH layers are required since Stage 2. The on:do: catches exceptions SIGNALED at full speed inside escapes (Stage 2 matching: the handler runs at the raise point and unwinds here). The raw catch takes what on:do: can no longer see: NS exceptions THROWN rather than signaled - the simulator's own signal interception (see activate:) and the kernel's unhandled-signal fallback - which protectedBodyOf: deliberately rethrows past every on:do: as already past every matching opportunity. Before Stage 2 the plain on:do: caught those throws too; after it they sailed through to the expatriate boundary as uncaught console errors (probeCaughtSignal, 2026-07-18). The raw catch clears the Stage 3 reconstruction state: a signal unhandled at FULL SPEED below a simulation belongs to the SIMULATED handler frames handleSignal: walks, not to the debugger boundary. *)
_js try: (_js block: {_js return: ([ aBlock value ] on: Exception
do: [:e | handleSignal: e ]). })
catch: 'nsSimErr'
with: (_js block: {_js assign: (_js ident: 'NSReconstructing')
toBe: (_js literal: false). handleSignal: (_js ident: 'nsSimErr'). })
)
handleSignal: signaled = (
(* A signal surfaced during a step (all stepped code and every full-speed escape throws through the step loop, since simulated frames are data, not JS frames). Find the nearest live handler frame below the current activation, run the intervening unwind protectors innermost-first, run the handler at full simulation, and return its result from the handler frame. No matching frame: rethrow to the real caller of the simulation. *)
| frame handler result sender zap next |
frame:: activation.
[ nil = frame ] whileFalse: [
(isLiveHandlerFrame: frame for: signaled) ifTrue: [
frame unwindProtectorRan: true.
runProtectorsFrom: activation to: frame.
handler:: frame tempAt: 3.
result:: runToCompletion: handler withArgument: signaled.
sender:: frame sender.
zap:: activation.
[ sameActivation: zap as: sender ] whileFalse: [
next:: zap sender.
zap terminate.
zap:: next
].
sender push: result.
activation:: sender.
^self
].
frame:: frame sender
].
_js throw: signaled
)
public home: frame ^<SimulatedActivation> = (
(* The method activation a block activation lexically belongs to; a method frame is its own home. *)
nil = frame closure ifTrue: [ ^frame ].
^home: frame closure definingActivation
)
implicitReceiverSend: selector numArgs: numArgs = (
(* Upstream's outward walk: at each lexical level, if the level's declarations include the selector, the send binds there (lexical rules); past the top level it degrades to an ordinary protected send on the original receiver. *)
| arguments candidateReceiver candidateMixin candidateApp nextMixin |
arguments:: popArguments: numArgs.
candidateReceiver:: activation receiver.
candidateMixin:: activation method mixin.
[
candidateApp:: findApplicationOf: candidateMixin
startingAt: (behaviorOf: candidateReceiver).
(behavior: candidateApp hasSelector: selector) ifTrue: [
^sendLexical: selector
to: candidateReceiver
arguments: arguments
wrt: candidateMixin
].
nil = (enclosingMixinNameOf: (mixinNameOf: candidateMixin)) ifTrue: [
^sendProtected: selector
to: activation receiver
arguments: arguments
startingAt: (behaviorOf: activation receiver)
].
candidateReceiver:: enclosingObjectOf: candidateApp.
nextMixin:: enclosingMixinOf: candidateMixin boundIn: candidateReceiver.
candidateMixin:: nextMixin
] repeat
)
isLiveHandlerFrame: frame for: signaled ^<Boolean> = (
nil = frame method ifTrue: [ ^false ].
frame unwindProtectorRan ifTrue: [ ^false ].
(_js operator: '===' with: frame method mixin and: supportMixin) ifFalse: [
^false
].
frame method selector = #run:protectedBy:handler: ifFalse: [ ^false ].
^exceptionMatches: signaled class: (frame tempAt: 2)
)
public isProtectorFrame: frame ^<Boolean> = (
(* Public access for SimulatedThread's unwind checks. *)
^isUnwindProtectFrame: frame
)
isPurePushOpcode: op copiedNibble: byte2 ^<Boolean> = (
(* Instructions that push exactly one value and consume none, safe inside a statement-entry run: parameter/local/literal/constant pushes, wide forms, enclosing-object push, and a closure with NO copied values (numCopied is the high nibble of its second byte; copied values would pop operands the run did not produce). *)
(op >= 112 and: [ op <= 127 ]) ifTrue: [ ^true ].
(op >= 144 and: [ op <= 156 ]) ifTrue: [ ^true ].
(op >= 160 and: [ op <= 163 ]) ifTrue: [ ^true ].
op = 222 ifTrue: [ ^true ].
op = 228 ifTrue: [ ^true ].
op = 229 ifTrue: [ ^true ].
op = 233 ifTrue: [ ^true ].
op = 248 ifTrue: [ ^true ].
op = 249 ifTrue: [ ^true ].
op = 255 ifTrue: [ ^(byte2 bitAnd: 240) = 0 ].
^false
)
isSimulatedClosure: obj = (
(* A structural probe would misfire on arbitrary objects via dnuCatchers; compare the runtime class directly. *)
^_js operator: '==='
with: (_js call: (_js ident: 'Object.getPrototypeOf') with: {obj. })
and: (_js call: (_js ident: 'Object.getPrototypeOf')
with: {SimulatedClosure in: nil initialBCI: 1 numArgs: 0 numCopied: 0. })
)
isUnwindProtectFrame: frame ^<Boolean> = (
(* Method identity: the frame runs one of the SimulationSupport unwind surrogates and its protector has not run yet. *)
nil = frame method ifTrue: [ ^false ].
frame unwindProtectorRan ifTrue: [ ^false ].
(_js operator: '===' with: frame method mixin and: supportMixin) ifFalse: [
^false
].
^frame method selector = #run:ensure: or: [
frame method selector = #run:ifCurtailed:
]
)
isValueSelector: selector arity: arity = (
selector = #value ifTrue: [ ^0 = arity ].
selector = #value: ifTrue: [ ^1 = arity ].
selector = #value:value: ifTrue: [ ^2 = arity ].
selector = #value:value:value: ifTrue: [ ^3 = arity ].
selector = #valueWithArguments: ifTrue: [ ^1 = arity ].
^false
)
jump: delta = (
activation bci: activation bci + delta
)
localToTemp: offset = (
^(nil = activation closure ifTrue: [ activation method numArgs ]
ifFalse: [ activation closure numArgs ]) + offset + 1
)
mangleQualified: name = (
(* names mangleSynthetic: semantics: a dollar prefix, with colons and backquotes becoming dollars. *)
| s |
s:: '$'.
1 to: name size
do: [:i |
| c = name copyFrom: i to: i. |
(c = ':' or: [ c = '`' ]) ifTrue: [ s:: s , '$' ] ifFalse: [ s:: s , c ]
].
^s
)
public method = (
^activation method
)
methodFor: selector in: behavior = (
(* Find the MM entry for selector in the behavior's runtime mixin and wrap it with its provenance. Behaviors outside the Newspeak world (patched JS prototypes, the implementation base) have no newspeakClass and contribute nothing. *)
| mms |
mms:: runtimeMethodsOf: behavior.
nil = mms ifTrue: [ ^nil ].
mms do: [:mm |
(_js propertyOf: mm at: (_js literal: 'name')) = selector ifTrue: [
^(SimulatedMethod record: (normalizeNull: (_js propertyOf: mm
at: (_js literal: 'bytecode')))
mixin: (runtimeMixinOf: behavior)
selector: selector
accessModifier: (_js propertyOf: mm at: (_js literal: 'accessModifier'))) metadata: mm;
yourself
]
].
^nil
)
mixinNameOf: runtimeMixin = (
(* Guarded: a runtime mixin without a name (mis-wired reflective structures) must surface as a descriptive limitation, not a TypeError deep in the enclosing walk. *)
| n |
n:: normalizeNull: (_js propertyOf: runtimeMixin at: (_js literal: 'name')).
nil = n ifTrue: [
simulatorLimitation: 'runtime mixin without a name in the lexical walk'
].
^n
)
newArraySized: size = (
(* A real JS array (the runtime representation of Array), since these values flow into escaped full-speed code. Bare Array does not resolve on the JS platform. *)
^_js call: (_js ident: 'Array') with: {size. }
)
public newspeakClassApplying: runtimeMixin startingAt: startBehavior ^<NSClass | nil> = (
(* Presentation support (ActivationMirror definingClassMirror): the NS class whose runtime class on [startBehavior]'s chain applies [runtimeMixin]. Falls back to the newspeakClass superclass chain for receivers whose prototypes carry the kernel FLAT (primitives - no materialized applications). Answers nil rather than raising: displaying a transplanted frame must never fail on an unresolvable mixin. *)
| b nsClass rm |
b:: startBehavior.
[ nil = b ] whileFalse: [
(_js operator: '===' with: (runtimeMixinOf: b) and: runtimeMixin) ifTrue: [
^normalizeNull: (_js propertyOf: b at: (_js literal: 'newspeakClass'))
].
b:: behaviorOf: b
].
nil = startBehavior ifTrue: [ ^nil ].
nsClass:: normalizeNull: (_js propertyOf: startBehavior
at: (_js literal: 'newspeakClass')).
[ nil = nsClass ] whileFalse: [
rm:: normalizeNull: (_js propertyOf: (_js propertyOf: nsClass
at: (_js literal: '$mixin$slot'))
at: (_js literal: 'runtimeMixin')).
(_js operator: '===' with: rm and: runtimeMixin) ifTrue: [ ^nsClass ].
nsClass:: nsClass superclass
].
^nil
)
nonBooleanReceiver: nonBoolean = (
simulatorLimitation: 'branch on a non-boolean'
)
normalizeNull: value = (
(* Loose equality: JS null AND undefined both normalize to Newspeak nil. Undefined arises for absent properties (e.g. newspeakClass at the top of the runtime-class chain, ImplementationBase); strict === null let it through and the next property read exploded. *)
(_js operator: '==' with: value and: (_js ident: 'null')) ifTrue: [ ^nil ].
^value
)
ordinarySend: selector numArgs: numArgs = (
| arguments messageReceiver |
arguments:: popArguments: numArgs.
messageReceiver:: activation pop.
^dispatchOrdinary: selector receiver: messageReceiver arguments: arguments
)
ordinarySendSitesOf: m <SimulatedMethod> ^<List> = (
(* {bci. selector.} pairs for the ordinary-family sends (ordinary/self/implicit short and ext, super, outer) in the method's V5 bytecode, blocks included (their code shares the method's bytes). Common-selector, eventual and branch pseudo-sends are excluded - the same scope as the compiler's send-site instrumentation. bci = one-based offset of the instruction's first byte, the bciMap coordinate. *)
| bytes lits n sites i op raw idx len |
bytes:: m bytecode.
lits:: m literals.
n:: _js propertyOf: bytes at: (_js literal: 'length').
sites:: List new.
i:: 0.
[ i < n ] whileTrue: [
op:: _js propertyOf: bytes at: i.
raw:: nil.
(op >= 64 and: [ op <= 111 ]) ifTrue: [
raw:: _js propertyOf: lits at: (op bitAnd: 7)
].
(op >= 250 and: [ op <= 254 ]) ifTrue: [
idx:: (_js propertyOf: bytes at: i + 1) + (((_js propertyOf: bytes
at: i + 2) bitAnd: 15) * 256).
raw:: _js propertyOf: lits at: idx
].
(_js operator: '==' with: raw and: (_js ident: 'null')) ifFalse: [
sites add: {i + 1. raw. }
].
len:: op <= 215 ifTrue: [ 1 ]
ifFalse: [
op <= 237 ifTrue: [ 2 ]
ifFalse: [ op <= 253 ifTrue: [ 3 ] ifFalse: [ 4 ] ]
].
i:: i + len
].
^sites
)
outerSend: selector numArgs: numArgs depth: depth = (
| arguments receiver targetMixin count app |
arguments:: popArguments: numArgs.
receiver:: activation receiver.
targetMixin:: activation method mixin.
count:: 0.
[ count < depth ] whileTrue: [
count:: count + 1.
app:: findApplicationOf: targetMixin startingAt: (behaviorOf: receiver).
receiver:: enclosingObjectOf: app.
targetMixin:: enclosingMixinOf: targetMixin boundIn: receiver
].
^sendLexical: selector to: receiver arguments: arguments wrt: targetMixin