forked from scala/scala3
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBCodeSkelBuilder.scala
997 lines (845 loc) · 39.1 KB
/
BCodeSkelBuilder.scala
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
package dotty.tools
package backend
package jvm
import scala.language.unsafeNulls
import scala.annotation.tailrec
import scala.collection.{immutable, mutable}
import scala.tools.asm
import dotty.tools.dotc.ast.tpd
import dotty.tools.dotc.ast.TreeTypeMap
import dotty.tools.dotc.CompilationUnit
import dotty.tools.dotc.ast.Trees.SyntheticUnit
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.core.Flags.*
import dotty.tools.dotc.core.StdNames.*
import dotty.tools.dotc.core.NameKinds.*
import dotty.tools.dotc.core.Names.TermName
import dotty.tools.dotc.core.Symbols.*
import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.util.Spans.*
import dotty.tools.dotc.report
/*
*
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
* @version 1.0
*
*/
trait BCodeSkelBuilder extends BCodeHelpers {
import int.{_, given}
import DottyBackendInterface.{symExtensions, _}
import tpd.*
import bTypes.*
import coreBTypes.*
import bCodeAsmCommon.*
lazy val NativeAttr: Symbol = requiredClass[scala.native]
final class BTypesStack:
// Anecdotally, growing past 16 to 32 is common; growing past 32 is rare
private var stack = new Array[BType](32)
private var size = 0
def isEmpty: Boolean = size == 0
def push(btype: BType): Unit =
if size == stack.length then
stack = java.util.Arrays.copyOf(stack, stack.length * 2)
stack(size) = btype
size += 1
def pop(): Unit = pop(1)
def pop(count: Int): Unit =
assert(size >= count)
size -= count
def height: Int = heightBetween(0, size)
private def heightBetween(start: Int, end: Int): Int =
var result = 0
var i = start
while i != end do
result += stack(i).size
i += 1
result
def recordSize(): BTypesStack.Size = BTypesStack.intToSize(size)
def restoreSize(targetSize: BTypesStack.Size): Unit =
val targetSize1 = BTypesStack.sizeToInt(targetSize)
assert(size >= targetSize1)
size = targetSize1
def heightDiffWrt(targetSize: BTypesStack.Size): Int =
val targetSize1 = BTypesStack.sizeToInt(targetSize)
assert(size >= targetSize1)
heightBetween(targetSize1, size)
def clear(): Unit =
size = 0
def acquireFullStack(): IArray[BType] =
val res = IArray.unsafeFromArray(stack.slice(0, size))
size = 0
res
def restoreFullStack(fullStack: IArray[BType]): Unit =
assert(size == 0 && stack.length >= fullStack.length)
fullStack.copyToArray(stack)
size = fullStack.length
end BTypesStack
object BTypesStack:
opaque type Size = Int
private def intToSize(size: Int): Size = size
private def sizeToInt(size: Size): Int = size
end BTypesStack
/** The destination of a value generated by `genLoadTo`. */
enum LoadDestination:
/** The value is put on the stack, and control flows through to the next opcode. */
case FallThrough
/** The value is put on the stack, and control flow is transferred to the given `label`. */
case Jump(label: asm.Label, targetStackSize: BTypesStack.Size)
/** The value is RETURN'ed from the enclosing method. */
case Return
/** The value is ATHROW'n. */
case Throw
end LoadDestination
/*
* There's a dedicated PlainClassBuilder for each CompilationUnit,
* which simplifies the initialization of per-class data structures in `genPlainClass()` which in turn delegates to `initJClass()`
*
* The entry-point to emitting bytecode instructions is `genDefDef()` where the per-method data structures are initialized,
* including `resetMethodBookkeeping()` and `initJMethod()`.
* Once that's been done, and assuming the method being visited isn't abstract, `emitNormalMethodBody()` populates
* the ASM MethodNode instance with ASM AbstractInsnNodes.
*
* Given that CleanUp delivers trees that produce values on the stack,
* the entry-point to all-things instruction-emit is `genLoad()`.
* There, an operation taking N arguments results in recursively emitting instructions to lead each of them,
* followed by emitting instructions to process those arguments (to be found at run-time on the operand-stack).
*
* In a few cases the above recipe deserves more details, as provided in the documentation for:
* - `genLoadTry()`
* - `genSynchronized()
* - `jumpDest` , `cleanups` , `labelDefsAtOrUnder`
*/
abstract class PlainSkelBuilder(cunit: CompilationUnit)
extends BCClassGen
with BCAnnotGen
with BCInnerClassGen
with JAndroidBuilder
with BCForwardersGen
with BCPickles
with BCJGenSigGen {
// Strangely I can't find this in the asm code 255, but reserving 1 for "this"
inline val MaximumJvmParameters = 254
// current class
var cnode: ClassNode1 = null
var thisName: String = null // the internal name of the class being emitted
var claszSymbol: Symbol = null
var isCZParcelable = false
var isCZStaticModule = false
/* ---------------- idiomatic way to ask questions to typer ---------------- */
def paramTKs(app: Apply, take: Int = -1): List[BType] = app match {
case Apply(fun, _) =>
val funSym = fun.symbol
(funSym.info.firstParamTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM)
}
def symInfoTK(sym: Symbol): BType = {
toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM)
}
def tpeTK(tree: Tree): BType = { toTypeKind(tree.tpe) }
override def getCurrentCUnit(): CompilationUnit = { cunit }
/* ---------------- helper utils for generating classes and fields ---------------- */
def genPlainClass(cd0: TypeDef) = cd0 match {
case TypeDef(_, impl: Template) =>
assert(cnode == null, "GenBCode detected nested methods.")
claszSymbol = cd0.symbol
isCZParcelable = isAndroidParcelableClass(claszSymbol)
isCZStaticModule = claszSymbol.isStaticModuleClass
thisName = internalName(claszSymbol)
cnode = new ClassNode1()
initJClass(cnode)
val cd = if (isCZStaticModule) {
// Move statements from the primary constructor following the superclass constructor call to
// a newly synthesised tree representing the "<clinit>", which also assigns the MODULE$ field.
// Because the assigments to both the module instance fields, and the fields of the module itself
// are in the <clinit>, these fields can be static + final.
// Should we do this transformation earlier, say in Constructors? Or would that just cause
// pain for scala-{js, native}?
//
// @sjrd (https://github.com/lampepfl/dotty/pull/9181#discussion_r457458205):
// moving that before the back-end would make things significantly more complicated for
// Scala.js and Native. Both have a first-class concept of ModuleClass, and encode the
// singleton pattern of MODULE$ in a completely different way. In the Scala.js IR, there
// even isn't anything that corresponds to MODULE$ per se.
//
// So if you move this before the back-end, then Scala.js and Scala Native will have to
// reverse all the effects of this transformation, which would be counter-productive.
// TODO: remove `!f.name.is(LazyBitMapName)` once we change lazy val encoding
// https://github.com/lampepfl/dotty/issues/7140
//
// Lazy val encoding assumes bitmap fields are non-static
//
// See `tests/run/given-var.scala`
//
// !!! Part of this logic is duplicated in JSCodeGen.genCompilationUnit
claszSymbol.info.decls.foreach { f =>
if f.isField && !f.name.is(LazyBitMapName) && !f.name.is(LazyLocalName) then
f.setFlag(JavaStatic)
}
val (clinits, body) = impl.body.partition(stat => stat.isInstanceOf[DefDef] && stat.symbol.isStaticConstructor)
val (uptoSuperStats, remainingConstrStats) = splitAtSuper(impl.constr.rhs.asInstanceOf[Block].stats)
val clInitSymbol: TermSymbol =
if (clinits.nonEmpty) clinits.head.symbol.asTerm
else newSymbol(
claszSymbol,
nme.STATIC_CONSTRUCTOR,
JavaStatic | Method,
MethodType(Nil)(_ => Nil, _ => defn.UnitType),
privateWithin = NoSymbol,
coord = claszSymbol.coord
)
val moduleField = newSymbol(
claszSymbol,
str.MODULE_INSTANCE_FIELD.toTermName,
JavaStatic | Final,
claszSymbol.typeRef,
privateWithin = NoSymbol,
coord = claszSymbol.coord
).entered
val thisMap = new TreeMap {
override def transform(tree: Tree)(using Context) = {
val tp = tree.tpe.substThis(claszSymbol.asClass, claszSymbol.sourceModule.termRef)
tree.withType(tp) match {
case tree: This if tree.symbol == claszSymbol =>
ref(claszSymbol.sourceModule)
case tree =>
super.transform(tree)
}
}
}
def rewire(stat: Tree) = thisMap.transform(stat).changeOwner(claszSymbol.primaryConstructor, clInitSymbol)
val callConstructor = New(claszSymbol.typeRef).select(claszSymbol.primaryConstructor).appliedToTermArgs(Nil)
val assignModuleField = Assign(ref(moduleField), callConstructor)
val remainingConstrStatsSubst = remainingConstrStats.map(rewire)
val clinit = clinits match {
case (ddef: DefDef) :: _ =>
cpy.DefDef(ddef)(rhs = Block(ddef.rhs :: assignModuleField :: remainingConstrStatsSubst, unitLiteral))
case _ =>
DefDef(clInitSymbol, Block(assignModuleField :: remainingConstrStatsSubst, unitLiteral))
}
val constr2 = {
val rhs = Block(uptoSuperStats, impl.constr.rhs.asInstanceOf[Block].expr)
cpy.DefDef(impl.constr)(rhs = rhs)
}
val impl2 = cpy.Template(impl)(constr = constr2, body = clinit :: body)
cpy.TypeDef(cd0)(rhs = impl2)
} else cd0
val hasStaticCtor = isCZStaticModule || cd.symbol.info.decls.exists(_.isStaticConstructor)
if (!hasStaticCtor && isCZParcelable) fabricateStaticInitAndroid()
val optSerial: Option[Long] =
claszSymbol.getAnnotation(defn.SerialVersionUIDAnnot).flatMap { annot =>
if (claszSymbol.is(Trait)) {
report.warning("@SerialVersionUID does nothing on a trait", annot.tree.sourcePos)
None
} else {
val vuid = annot.argumentConstant(0).map(_.longValue)
if (vuid.isEmpty)
report.error("The argument passed to @SerialVersionUID must be a constant",
annot.argument(0).getOrElse(annot.tree).sourcePos)
vuid
}
}
if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)}
addClassFields()
gen(cd.rhs)
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
AsmUtils.traceClass(cnode)
cnode.innerClasses
assert(cd.symbol == claszSymbol, "Someone messed up BCodePhase.claszSymbol during genPlainClass().")
} // end of method genPlainClass()
/*
* must-single-thread
*/
private def initJClass(jclass: asm.ClassVisitor): Unit = {
val ps = claszSymbol.info.parents
val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol)
val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces.map(_.internalName)
/* To avoid deadlocks when combining objects, lambdas and multi-threading,
* lambdas in objects are compiled to instance methods of the module class
* instead of static methods (see tests/run/deadlock.scala and
* https://github.com/scala/scala-dev/issues/195 for details).
* This has worked well for us so far but this is problematic for
* serialization: serializing a lambda requires serializing all the values
* it captures, if this lambda is in an object, this means serializing the
* enclosing object, which fails if the object does not extend
* Serializable.
* Because serializing objects is basically free since #5775, it seems like
* the simplest solution is to simply make all objects Serializable, this
* certainly seems preferable to deadlocks.
* This cannot be done earlier because Scala.js would not like it (#9596).
*/
val interfaceNames =
if (claszSymbol.is(ModuleClass) && !interfaceNames0.contains("java/io/Serializable"))
interfaceNames0 :+ "java/io/Serializable"
else
interfaceNames0
val flags = javaFlags(claszSymbol)
val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner)
cnode.visit(backendUtils.classfileVersion, flags,
thisName, thisSignature,
superClass, interfaceNames.toArray)
if (emitSource) {
cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */)
}
enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match {
case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) =>
cnode.visitOuterClass(className, methodName, methodDescriptor)
case _ => ()
}
val ssa = None // TODO: inlined form `getAnnotPickle(thisName, claszSymbol)`. Should something be done on Dotty?
cnode.visitAttribute(if (ssa.isDefined) pickleMarkerLocal else pickleMarkerForeign)
emitAnnotations(cnode, claszSymbol.annotations ++ ssa)
if (!isCZStaticModule && !isCZParcelable) {
val skipStaticForwarders = (claszSymbol.is(Module) || ctx.settings.XnoForwarders.value)
if (!skipStaticForwarders) {
val lmoc = claszSymbol.companionModule
// add static forwarders if there are no name conflicts; see bugs #363 and #1735
if (lmoc != NoSymbol) {
// it must be a top level class (name contains no $s)
val isCandidateForForwarders = (lmoc.is(Module)) && lmoc.isStatic
if (isCandidateForForwarders) {
report.log(s"Adding static forwarders from '$claszSymbol' to implementations in '$lmoc'")
addForwarders(cnode, thisName, lmoc.moduleClass)
}
}
}
}
// the invoker is responsible for adding a class-static constructor.
} // end of method initJClass
/*
* must-single-thread
*/
private def fabricateStaticInitAndroid(): Unit = {
val clinit: asm.MethodVisitor = cnode.visitMethod(
GenBCodeOps.PublicStatic, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED
CLASS_CONSTRUCTOR_NAME,
"()V",
null, // no java-generic-signature
null // no throwable exceptions
)
clinit.visitCode()
legacyAddCreatorCode(clinit, cnode, thisName)
clinit.visitInsn(asm.Opcodes.RETURN)
clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments
clinit.visitEnd()
}
def addClassFields(): Unit = {
/* Non-method term members are fields, except for module members. Module
* members can only happen on .NET (no flatten) for inner traits. There,
* a module symbol is generated (transformInfo in mixin) which is used
* as owner for the members of the implementation class (so that the
* backend emits them as static).
* No code is needed for this module symbol.
*/
for (f <- claszSymbol.info.decls.filter(p => p.isTerm && !p.is(Method))) {
val javagensig = getGenericSignature(f, claszSymbol)
val flags = javaFieldFlags(f)
assert(!f.isStaticMember || !claszSymbol.isInterface || !f.is(Mutable),
s"interface $claszSymbol cannot have non-final static field $f")
val jfield = new asm.tree.FieldNode(
flags,
f.javaSimpleName,
symInfoTK(f).descriptor,
javagensig,
null // no initial value
)
cnode.fields.add(jfield)
emitAnnotations(jfield, f.annotations)
}
} // end of method addClassFields()
// current method
var mnode: MethodNode1 = null
var jMethodName: String = null
var isMethSymStaticCtor = false
var returnType: BType = null
var methSymbol: Symbol = null
// used by genLoadTry() and genSynchronized()
var earlyReturnVar: Symbol = null
var shouldEmitCleanup = false
// stack tracking
val stack = new BTypesStack
// line numbers
var lastEmittedLineNr = -1
object bc extends JCodeMethodN {
override def jmethod = PlainSkelBuilder.this.mnode
}
/* ---------------- Part 1 of program points, ie Labels in the ASM world ---------------- */
/*
* A jump is represented as a Return node whose `from` symbol denotes a Labeled's Bind node, the target of the jump.
* The `jumpDest` map is used to find the `LoadDestination` at the end of the `Labeled` block, as well as the
* corresponding expected type. The `LoadDestination` can never be `FallThrough` here.
*/
var jumpDest: immutable.Map[ /* Labeled */ Symbol, (BType, LoadDestination) ] = null
def registerJumpDest(labelSym: Symbol, expectedType: BType, dest: LoadDestination): Unit = {
assert(labelSym.is(Label), s"trying to register a jump-dest for a non-label symbol, at: ${labelSym.span}")
assert(dest != LoadDestination.FallThrough, s"trying to register a FallThrough dest for label, at: ${labelSym.span}")
assert(!jumpDest.contains(labelSym), s"trying to register a second jump-dest for label, at: ${labelSym.span}")
jumpDest += (labelSym -> (expectedType, dest))
}
def findJumpDest(labelSym: Symbol): (BType, LoadDestination) = {
assert(labelSym.is(Label), s"trying to map a non-label symbol to an asm.Label, at: ${labelSym.span}")
jumpDest.getOrElse(labelSym, {
abort(s"unknown label symbol, for label at: ${labelSym.span}")
})
}
/*
* A program point may be lexically nested (at some depth)
* (a) in the try-clause of a try-with-finally expression
* (b) in a synchronized block.
* Each of the constructs above establishes a "cleanup block" to execute upon
* both normal-exit, early-return, and abrupt-termination of the instructions it encloses.
*
* The `cleanups` LIFO queue represents the nesting of active (for the current program point)
* pending cleanups. For each such cleanup an asm.Label indicates the start of its cleanup-block.
* At any given time during traversal of the method body,
* the head of `cleanups` denotes the cleanup-block for the closest enclosing try-with-finally or synchronized-expression.
*
* `cleanups` is used:
*
* (1) upon visiting a Return statement.
* In case of pending cleanups, we can't just emit a RETURN instruction, but must instead:
* - store the result (if any) in `earlyReturnVar`, and
* - jump to the next pending cleanup.
* See `genReturn()`
*
* (2) upon emitting a try-with-finally or a synchronized-expr,
* In these cases, the targets of the above jumps are emitted,
* provided an early exit was actually encountered somewhere in the protected clauses.
* See `genLoadTry()` and `genSynchronized()`
*
* The code thus emitted for jumps and targets covers the early-return case.
* The case of abrupt (ie exceptional) termination is covered by exception handlers
* emitted for that purpose as described in `genLoadTry()` and `genSynchronized()`.
*/
var cleanups: List[asm.Label] = Nil
def registerCleanup(finCleanup: asm.Label): Unit = {
if (finCleanup != null) { cleanups = finCleanup :: cleanups }
}
def unregisterCleanup(finCleanup: asm.Label): Unit = {
if (finCleanup != null) {
assert(cleanups.head eq finCleanup,
s"Bad nesting of cleanup operations: $cleanups trying to unregister: $finCleanup")
cleanups = cleanups.tail
}
}
/* ---------------- local variables and params ---------------- */
case class Local(tk: BType, name: String, idx: Int, isSynth: Boolean)
/*
* Bookkeeping for method-local vars and method-params.
*
* TODO: use fewer slots. local variable slots are never re-used in separate blocks.
* In the following example, x and y could use the same slot.
* def foo() = {
* { val x = 1 }
* { val y = "a" }
* }
*/
object locals {
private val slots = mutable.HashMap.empty[Symbol, Local] // (local-or-param-sym -> Local(BType, name, idx, isSynth))
private var nxtIdx = -1 // next available index for local-var
def reset(isStaticMethod: Boolean): Unit = {
slots.clear()
nxtIdx = if (isStaticMethod) 0 else 1
}
def contains(locSym: Symbol): Boolean = { slots.contains(locSym) }
def apply(locSym: Symbol): Local = { slots.apply(locSym) }
/* Make a fresh local variable, ensuring a unique name.
* The invoker must make sure inner classes are tracked for the sym's tpe.
*/
def makeLocal(tk: BType, name: String, tpe: Type, pos: Span): Symbol = {
val locSym = newSymbol(methSymbol, name.toTermName, Synthetic, tpe, NoSymbol, pos)
makeLocal(locSym, tk)
locSym
}
def makeLocal(locSym: Symbol): Local = {
makeLocal(locSym, symInfoTK(locSym))
}
def getOrMakeLocal(locSym: Symbol): Local = {
// `getOrElse` below has the same effect as `getOrElseUpdate` because `makeLocal()` adds an entry to the `locals` map.
slots.getOrElse(locSym, makeLocal(locSym))
}
def reuseLocal(sym: Symbol, loc: Local): Unit =
val existing = slots.put(sym, loc)
if (existing.isDefined)
report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span))
def reuseThisSlot(sym: Symbol): Unit =
reuseLocal(sym, Local(symInfoTK(sym), sym.javaSimpleName, 0, sym.is(Synthetic)))
private def makeLocal(sym: Symbol, tk: BType): Local = {
assert(nxtIdx != -1, "not a valid start index")
val loc = Local(tk, sym.javaSimpleName, nxtIdx, sym.is(Synthetic))
val existing = slots.put(sym, loc)
if (existing.isDefined)
report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span))
assert(tk.size > 0, "makeLocal called for a symbol whose type is Unit.")
nxtIdx += tk.size
loc
}
def makeTempLocal(tk: BType): Local =
assert(nxtIdx != -1, "not a valid start index")
assert(tk.size > 0, "makeLocal called for a symbol whose type is Unit.")
val loc = Local(tk, "temp", nxtIdx, isSynth = true)
nxtIdx += tk.size
loc
// not to be confused with `fieldStore` and `fieldLoad` which also take a symbol but a field-symbol.
def store(locSym: Symbol): Unit = {
val Local(tk, _, idx, _) = slots(locSym)
bc.store(idx, tk)
}
def load(locSym: Symbol): Unit = {
val Local(tk, _, idx, _) = slots(locSym)
bc.load(idx, tk)
}
}
/* ---------------- Part 2 of program points, ie Labels in the ASM world ---------------- */
// bookkeeping the scopes of non-synthetic local vars, to emit debug info (`emitVars`).
var varsInScope: List[(Symbol, asm.Label)] = null // (local-var-sym -> start-of-scope)
// helpers around program-points.
def lastInsn: asm.tree.AbstractInsnNode = mnode.instructions.getLast
def currProgramPoint(): asm.Label = {
lastInsn match {
case labnode: asm.tree.LabelNode => labnode.getLabel
case _ =>
val pp = new asm.Label
mnode visitLabel pp
pp
}
}
def markProgramPoint(lbl: asm.Label): Unit = {
val skip = (lbl == null) || isAtProgramPoint(lbl)
if (!skip) { mnode visitLabel lbl }
}
def isAtProgramPoint(lbl: asm.Label): Boolean = {
def getNonLineNumberNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match {
case a: asm.tree.LineNumberNode => getNonLineNumberNode(a.getPrevious) // line numbers aren't part of code itself
case _ => a
}
(getNonLineNumberNode(lastInsn) match {
case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl);
case _ => false } )
}
def lineNumber(tree: Tree): Unit = {
@tailrec
def getNonLabelNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match {
case a: asm.tree.LabelNode => getNonLabelNode(a.getPrevious)
case _ => a
}
if (emitLines && tree.span.exists && !tree.hasAttachment(SyntheticUnit)) {
val nr =
val sourcePos = tree.sourcePos
(
if sourcePos.exists then sourcePos.source.positionInUltimateSource(sourcePos).line
else ctx.source.offsetToLine(tree.span.point) // fallback
) + 1
if (nr != lastEmittedLineNr) {
lastEmittedLineNr = nr
getNonLabelNode(lastInsn) match {
case lnn: asm.tree.LineNumberNode =>
// overwrite previous landmark as no instructions have been emitted for it
lnn.line = nr
case _ =>
mnode.visitLineNumber(nr, currProgramPoint())
}
}
}
}
// on entering a method
def resetMethodBookkeeping(dd: DefDef) = {
val rhs = dd.rhs
locals.reset(isStaticMethod = methSymbol.isStaticMember)
jumpDest = immutable.Map.empty
// check previous invocation of genDefDef exited as many varsInScope as it entered.
assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().")
// check previous invocation of genDefDef unregistered as many cleanups as it registered.
assert(cleanups == Nil, "Previous invocation of genDefDef didn't unregister as many cleanups as it registered.")
earlyReturnVar = null
shouldEmitCleanup = false
stack.clear()
lastEmittedLineNr = -1
}
/* ---------------- top-down traversal invoking ASM Tree API along the way ---------------- */
def gen(tree: Tree): Unit = {
tree match {
case tpd.EmptyTree => ()
case ValDef(name, tpt, rhs) => () // fields are added in `genPlainClass()`, via `addClassFields()`
case dd: DefDef =>
/* First generate a static forwarder if this is a non-private trait
* trait method. This is required for super calls to this method, which
* go through the static forwarder in order to work around limitations
* of the JVM.
*
* For the $init$ method, we must not leave it as a default method, but
* instead we must put the whole body in the static method. If we leave
* it as a default method, Java classes cannot extend Scala classes that
* extend several Scala traits, since they then inherit unrelated default
* $init$ methods. See #8599. scalac does the same thing.
*
* In theory, this would go in a separate MiniPhase, but it would have to
* sit in a MegaPhase of its own between GenSJSIR and GenBCode, so the cost
* is not worth it. We directly do it in this back-end instead, which also
* kind of makes sense because it is JVM-specific.
*/
val sym = dd.symbol
val needsStaticImplMethod =
claszSymbol.isInterface && !dd.rhs.isEmpty && !sym.isPrivate && !sym.isStaticMember
if needsStaticImplMethod then
if sym.name == nme.TRAIT_CONSTRUCTOR then
genTraitConstructorDefDef(dd)
else
genStaticForwarderForDefDef(dd)
genDefDef(dd)
else
genDefDef(dd)
case tree: Template =>
val body =
if (tree.constr.rhs.isEmpty) tree.body
else tree.constr :: tree.body
body foreach gen
case _ => abort(s"Illegal tree in gen: $tree")
}
}
/*
* must-single-thread
*/
def initJMethod(flags: Int, params: List[Symbol]): Unit = {
val jgensig = getGenericSignature(methSymbol, claszSymbol)
val (excs, others) = methSymbol.annotations.partition(_.symbol eq defn.ThrowsAnnot)
val thrownExceptions: List[String] = getExceptions(excs)
val bytecodeName =
if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME
else jMethodName
val mdesc = asmMethodType(methSymbol).descriptor
mnode = cnode.visitMethod(
flags,
bytecodeName,
mdesc,
jgensig,
mkArrayS(thrownExceptions)
).asInstanceOf[MethodNode1]
// TODO param names: (m.params map (p => javaName(p.sym)))
emitAnnotations(mnode, others)
emitParamNames(mnode, params)
emitParamAnnotations(mnode, params.map(_.annotations))
} // end of method initJMethod
private def genTraitConstructorDefDef(dd: DefDef): Unit =
val statifiedDef = makeStatifiedDefDef(dd)
genDefDef(statifiedDef)
/** Creates a copy of the given DefDef that is static and where an explicit
* self parameter represents the original `this` value.
*
* Example: from
* {{{
* trait Enclosing {
* def foo(x: Int): String = this.toString() + x
* }
* }}}
* the statified version of `foo` would be
* {{{
* static def foo($self: Enclosing, x: Int): String = $self.toString() + x
* }}}
*/
private def makeStatifiedDefDef(dd: DefDef): DefDef =
val origSym = dd.symbol.asTerm
val newSym = makeStatifiedDefSymbol(origSym, origSym.name)
tpd.DefDef(newSym, { paramRefss =>
val selfParamRef :: regularParamRefs = paramRefss.head: @unchecked
val enclosingClass = origSym.owner.asClass
new TreeTypeMap(
typeMap = _.substThis(enclosingClass, selfParamRef.symbol.termRef)
.subst(dd.termParamss.head.map(_.symbol), regularParamRefs.map(_.symbol.termRef)),
treeMap = {
case tree: This if tree.symbol == enclosingClass => selfParamRef
case tree => tree
},
oldOwners = origSym :: Nil,
newOwners = newSym :: Nil
).transform(dd.rhs)
})
private def genStaticForwarderForDefDef(dd: DefDef): Unit =
val forwarderDef = makeStaticForwarder(dd)
genDefDef(forwarderDef)
/* Generates a synthetic static forwarder for a trait method.
* For a method such as
* def foo(...args: Ts): R
* in trait X, we generate the following method:
* static def foo$($this: X, ...args: Ts): R =
* invokespecial $this.X::foo(...args)
* We force an invokespecial with the attachment UseInvokeSpecial. It is
* necessary to make sure that the call will not follow overrides of foo()
* in subtraits and subclasses, since the whole point of this forward is to
* encode super calls.
*/
private def makeStaticForwarder(dd: DefDef): DefDef =
val origSym = dd.symbol.asTerm
val name = traitSuperAccessorName(origSym).toTermName
val sym = makeStatifiedDefSymbol(origSym, name)
tpd.DefDef(sym, { paramss =>
val params = paramss.head
tpd.Apply(params.head.select(origSym), params.tail)
.withAttachment(BCodeHelpers.UseInvokeSpecial, ())
})
private def makeStatifiedDefSymbol(origSym: TermSymbol, name: TermName): TermSymbol =
val info = origSym.info match
case mt: MethodType =>
MethodType(nme.SELF :: mt.paramNames, origSym.owner.typeRef :: mt.paramInfos, mt.resType)
origSym.copy(
name = name.toTermName,
flags = Method | JavaStatic,
info = info
).asTerm
def genDefDef(dd: DefDef): Unit = {
val rhs = dd.rhs
val vparamss = dd.termParamss
// the only method whose implementation is not emitted: getClass()
if (dd.symbol eq defn.Any_getClass) { return }
assert(mnode == null, "GenBCode detected nested method.")
methSymbol = dd.symbol
jMethodName = methSymbol.javaSimpleName
returnType = asmMethodType(methSymbol).returnType
isMethSymStaticCtor = methSymbol.isStaticConstructor
resetMethodBookkeeping(dd)
// add method-local vars for params
assert(vparamss.isEmpty || vparamss.tail.isEmpty, s"Malformed parameter list: $vparamss")
val params = if (vparamss.isEmpty) Nil else vparamss.head
for (p <- params) { locals.makeLocal(p.symbol) }
// debug assert((params.map(p => locals(p.symbol).tk)) == asmMethodType(methSymbol).getArgumentTypes.toList, "debug")
val paramsSize = params.map { param =>
val tpeTym = param.symbol.info.typeSymbol
if tpeTym == defn.LongClass || tpeTym == defn.DoubleClass then 2 else 1
}.sum
if (paramsSize > MaximumJvmParameters) {
// SI-7324
val info = if paramsSize == params.length then "" else " (Long and Double count as 2)" // https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.3
report.error(em"Platform restriction: a parameter list's length cannot exceed $MaximumJvmParameters$info.", ctx.source.atSpan(methSymbol.span))
return
}
val isNative = methSymbol.hasAnnotation(NativeAttr)
val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.isInterface && ((methSymbol.is(Deferred)) || methSymbol.isClassConstructor)))
val flags =
import GenBCodeOps.addFlagIf
javaFlags(methSymbol)
.addFlagIf(isAbstractMethod, asm.Opcodes.ACC_ABSTRACT)
.addFlagIf(false /*methSymbol.isStrictFP*/, asm.Opcodes.ACC_STRICT)
.addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes
// TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize }
val paramSyms = params.map(_.symbol)
initJMethod(flags, paramSyms)
if (!isAbstractMethod && !isNative) {
// #14773 Reuse locals slots for tailrec-generated mutable vars
val trimmedRhs: Tree =
@tailrec def loop(stats: List[Tree]): List[Tree] =
stats match
case (tree @ ValDef(TailLocalName(_, _), _, _)) :: rest if tree.symbol.isAllOf(Mutable | Synthetic) =>
tree.rhs match
case This(_) =>
locals.reuseThisSlot(tree.symbol)
loop(rest)
case rhs: Ident if paramSyms.contains(rhs.symbol) =>
locals.reuseLocal(tree.symbol, locals(rhs.symbol))
loop(rest)
case _ =>
stats
case _ =>
stats
end loop
rhs match
case Block(stats, expr) =>
val trimmedStats = loop(stats)
if trimmedStats eq stats then
rhs
else
Block(trimmedStats, expr)
case _ =>
rhs
end trimmedRhs
def emitNormalMethodBody(): Unit = {
val veryFirstProgramPoint = currProgramPoint()
if trimmedRhs == tpd.EmptyTree then
report.error(
em"Concrete method has no definition: $dd${
if (ctx.settings.Ydebug.value) "(found: " + methSymbol.owner.info.decls.toList.mkString(", ") + ")"
else ""}",
ctx.source.atSpan(NoSpan)
)
else
genLoadTo(trimmedRhs, returnType, LoadDestination.Return)
if (emitVars) {
// add entries to LocalVariableTable JVM attribute
val onePastLastProgramPoint = currProgramPoint()
val hasStaticBitSet = ((flags & asm.Opcodes.ACC_STATIC) != 0)
if (!hasStaticBitSet) {
mnode.visitLocalVariable(
"this",
"L" + thisName + ";",
null,
veryFirstProgramPoint,
onePastLastProgramPoint,
0
)
}
for (p <- params) { emitLocalVarScope(p.symbol, veryFirstProgramPoint, onePastLastProgramPoint, force = true) }
}
if (isMethSymStaticCtor) { appendToStaticCtor() }
} // end of emitNormalMethodBody()
lineNumber(rhs)
emitNormalMethodBody()
// Note we don't invoke visitMax, thus there are no FrameNode among mnode.instructions.
// The only non-instruction nodes to be found are LabelNode and LineNumberNode.
}
if (AsmUtils.traceMethodEnabled && mnode.name.contains(AsmUtils.traceMethodPattern))
AsmUtils.traceMethod(mnode)
mnode = null
} // end of method genDefDef()
/*
* must-single-thread
*
* TODO document, explain interplay with `fabricateStaticInitAndroid()`
*/
private def appendToStaticCtor(): Unit = {
def insertBefore(
location: asm.tree.AbstractInsnNode,
i0: asm.tree.AbstractInsnNode,
i1: asm.tree.AbstractInsnNode): Unit = {
if (i0 != null) {
mnode.instructions.insertBefore(location, i0.clone(null))
mnode.instructions.insertBefore(location, i1.clone(null))
}
}
// collect all return instructions
var rets: List[asm.tree.AbstractInsnNode] = Nil
mnode foreachInsn { i => if (i.getOpcode() == asm.Opcodes.RETURN) { rets ::= i } }
if (rets.isEmpty) { return }
var insnParcA: asm.tree.AbstractInsnNode = null
var insnParcB: asm.tree.AbstractInsnNode = null
// android creator code
if (isCZParcelable) {
// add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator
val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor
cnode.visitField(
asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL,
"CREATOR",
andrFieldDescr,
null,
null
)
// INVOKESTATIC CREATOR(): android.os.Parcelable$Creator; -- TODO where does this Android method come from?
val callee = claszSymbol.companionModule.info.member(androidFieldName).symbol
val jowner = internalName(callee.owner)
val jname = callee.javaSimpleName
val jtype = asmMethodType(callee).descriptor
insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false)
// PUTSTATIC `thisName`.CREATOR;
insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr)
}
// insert a few instructions for initialization before each return instruction
for(r <- rets) {
insertBefore(r, insnParcA, insnParcB)
}
}
def emitLocalVarScope(sym: Symbol, start: asm.Label, end: asm.Label, force: Boolean = false): Unit = {
val Local(tk, name, idx, isSynth) = locals(sym)
if (force || !isSynth) {
mnode.visitLocalVariable(name, tk.descriptor, null, start, end, idx)
}
}
def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination): Unit
} // end of class PlainSkelBuilder
}