Skip to content

Commit cd0211c

Browse files
committed
SI-9315 Desugar string concat to java.lang.StringBuilder ...
... instead of scala.collection.mutable.StringBuilder to benefit from JVM optimizations. Unfortunately primitives are already boxed in erasure when they end up in this part of the backend.
1 parent bbd890b commit cd0211c

File tree

6 files changed

+101
-21
lines changed

6 files changed

+101
-21
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ abstract class BCodeIdiomatic extends SubComponent {
4040
if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0
4141
)
4242

43-
val StringBuilderClassName = "scala/collection/mutable/StringBuilder"
43+
lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName
4444

4545
val EMPTY_STRING_ARRAY = Array.empty[String]
4646
val EMPTY_INT_ARRAY = Array.empty[Int]
@@ -184,10 +184,10 @@ abstract class BCodeIdiomatic extends SubComponent {
184184
* can-multi-thread
185185
*/
186186
final def genStartConcat(pos: Position): Unit = {
187-
jmethod.visitTypeInsn(Opcodes.NEW, StringBuilderClassName)
187+
jmethod.visitTypeInsn(Opcodes.NEW, JavaStringBuilderClassName)
188188
jmethod.visitInsn(Opcodes.DUP)
189189
invokespecial(
190-
StringBuilderClassName,
190+
JavaStringBuilderClassName,
191191
INSTANCE_CONSTRUCTOR_NAME,
192192
"()V",
193193
pos
@@ -198,21 +198,23 @@ abstract class BCodeIdiomatic extends SubComponent {
198198
* can-multi-thread
199199
*/
200200
final def genStringConcat(el: BType, pos: Position): Unit = {
201-
202-
val jtype =
203-
if (el.isArray || el.isClass) ObjectRef
204-
else el
201+
val jtype = el match {
202+
case ct: ClassBType if ct.isSubtypeOf(StringRef).get => StringRef
203+
case ct: ClassBType if ct.isSubtypeOf(jlStringBufferRef).get => jlStringBufferRef
204+
case ct: ClassBType if ct.isSubtypeOf(jlCharSequenceRef).get => jlCharSequenceRef
205+
case rt: RefBType => ObjectRef
206+
case pt: PrimitiveBType => pt // Currently this ends up being boxed in erasure
207+
}
205208

206209
val bt = MethodBType(List(jtype), jlStringBuilderRef)
207-
208-
invokevirtual(StringBuilderClassName, "append", bt.descriptor, pos)
210+
invokevirtual(JavaStringBuilderClassName, "append", bt.descriptor, pos)
209211
}
210212

211213
/*
212214
* can-multi-thread
213215
*/
214216
final def genEndConcat(pos: Position): Unit = {
215-
invokevirtual(StringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
217+
invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;", pos)
216218
}
217219

218220
/*

src/compiler/scala/tools/nsc/backend/jvm/CoreBTypes.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes: BTFS) {
9595
lazy val ObjectRef : ClassBType = classBTypeFromSymbol(ObjectClass)
9696
lazy val StringRef : ClassBType = classBTypeFromSymbol(StringClass)
9797
lazy val PredefRef : ClassBType = classBTypeFromSymbol(PredefModule.moduleClass)
98-
lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(StringBuilderClass)
98+
lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(JavaStringBuilderClass)
99+
lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(JavaStringBufferClass)
100+
lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(JavaCharSequenceClass)
99101
lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(ThrowableClass)
100102
lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(JavaCloneableClass) // java/lang/Cloneable
101103
lazy val jiSerializableRef : ClassBType = classBTypeFromSymbol(JavaSerializableClass) // java/io/Serializable
@@ -343,6 +345,8 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: Global]](val bTypes:
343345
def StringRef : ClassBType = _coreBTypes.StringRef
344346
def PredefRef : ClassBType = _coreBTypes.PredefRef
345347
def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef
348+
def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef
349+
def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef
346350
def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef
347351
def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef
348352
def jiSerializableRef : ClassBType = _coreBTypes.jiSerializableRef

src/compiler/scala/tools/nsc/backend/jvm/opt/BoxUnbox.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class BoxUnbox[BT <: BTypes](val btypes: BT) {
2121
import backendUtils._
2222

2323
/**
24-
* Eliminate box-unbox paris within `method`. Such appear commonly after closure elimination:
24+
* Eliminate box-unbox pairs within `method`. Such appear commonly after closure elimination:
2525
*
2626
* def t2 = {
2727
* val f = (b: Byte, i: Int) => i + b // no specialized variant for this function type
@@ -767,7 +767,7 @@ class BoxUnbox[BT <: BTypes](val btypes: BT) {
767767
private def isSpecializedTupleClass(tupleClass: InternalName) = specializedTupleClassR.pattern.matcher(tupleClass).matches
768768

769769
private val specializedTupleGetterR = "_[12]\\$mc[IJDCZ]\\$sp".r
770-
private def isSpecializedTupleGetter(mi: MethodInsnNode) = specializedTupleGetterR.pattern.matcher(mi.name)matches
770+
private def isSpecializedTupleGetter(mi: MethodInsnNode) = specializedTupleGetterR.pattern.matcher(mi.name).matches
771771

772772
private val tupleGetterR = "_\\d\\d?".r
773773
private def isTupleGetter(mi: MethodInsnNode) = tupleGetterR.pattern.matcher(mi.name).matches

src/reflect/scala/reflect/internal/Definitions.scala

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -428,13 +428,15 @@ trait Definitions extends api.StandardDefinitions {
428428
def elementType(container: Symbol, tp: Type): Type = elementExtract(container, tp)
429429

430430
// collections classes
431-
lazy val ConsClass = requiredClass[scala.collection.immutable.::[_]]
432-
lazy val IteratorClass = requiredClass[scala.collection.Iterator[_]]
433-
lazy val IterableClass = requiredClass[scala.collection.Iterable[_]]
434-
lazy val ListClass = requiredClass[scala.collection.immutable.List[_]]
435-
lazy val SeqClass = requiredClass[scala.collection.Seq[_]]
436-
lazy val StringBuilderClass = requiredClass[scala.collection.mutable.StringBuilder]
437-
lazy val TraversableClass = requiredClass[scala.collection.Traversable[_]]
431+
lazy val ConsClass = requiredClass[scala.collection.immutable.::[_]]
432+
lazy val IteratorClass = requiredClass[scala.collection.Iterator[_]]
433+
lazy val IterableClass = requiredClass[scala.collection.Iterable[_]]
434+
lazy val ListClass = requiredClass[scala.collection.immutable.List[_]]
435+
lazy val SeqClass = requiredClass[scala.collection.Seq[_]]
436+
lazy val JavaStringBuilderClass = requiredClass[java.lang.StringBuilder]
437+
lazy val JavaStringBufferClass = requiredClass[java.lang.StringBuffer]
438+
lazy val JavaCharSequenceClass = requiredClass[java.lang.CharSequence]
439+
lazy val TraversableClass = requiredClass[scala.collection.Traversable[_]]
438440

439441
lazy val ListModule = requiredModule[scala.collection.immutable.List.type]
440442
def List_apply = getMemberMethod(ListModule, nme.apply)

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
266266
definitions.IterableClass
267267
definitions.ListClass
268268
definitions.SeqClass
269-
definitions.StringBuilderClass
269+
definitions.JavaStringBuilderClass
270+
definitions.JavaStringBufferClass
271+
definitions.JavaCharSequenceClass
270272
definitions.TraversableClass
271273
definitions.ListModule
272274
definitions.NilModule
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package scala.tools.nsc
2+
package backend.jvm
3+
4+
import org.junit.runner.RunWith
5+
import org.junit.runners.JUnit4
6+
import org.junit.Test
7+
import scala.tools.asm.Opcodes._
8+
import org.junit.Assert._
9+
10+
import scala.tools.testing.AssertUtil._
11+
12+
import CodeGenTools._
13+
import scala.tools.partest.ASMConverters
14+
import ASMConverters._
15+
import scala.tools.testing.ClearAfterClass
16+
17+
object StringConcatTest extends ClearAfterClass.Clearable {
18+
var compiler = newCompiler()
19+
def clear(): Unit = { compiler = null }
20+
}
21+
22+
@RunWith(classOf[JUnit4])
23+
class StringConcatTest extends ClearAfterClass {
24+
ClearAfterClass.stateToClear = StringConcatTest
25+
val compiler = StringConcatTest.compiler
26+
27+
val commonPreInstructions = List(Label(0), LineNumber(1, Label(0)), TypeOp(NEW, "java/lang/StringBuilder"), Op(DUP), Invoke(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false), Ldc(LDC, "abc"), Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false), VarOp(ALOAD, 0))
28+
29+
val commonPostInstructions = List(Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false), Op(ARETURN), Label(12))
30+
31+
def instructionsWithCommonParts(instructions: List[Instruction]) = commonPreInstructions ++ instructions ++ commonPostInstructions
32+
33+
def instructionsForResultMethod(code: String): List[Instruction] = {
34+
val methods = compileMethods(compiler)(code)
35+
val resultMethod = methods.find(_.name == "result").get
36+
instructionsFromMethod(resultMethod)
37+
}
38+
39+
@Test
40+
def concatStringToStringBuilder: Unit = {
41+
val code = """ def string = "def"; def result = "abc" + string """
42+
val actualInstructions = instructionsForResultMethod(code)
43+
val expectedInstructions = instructionsWithCommonParts(List(Invoke(INVOKEVIRTUAL, "C", "string", "()Ljava/lang/String;", false), Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)))
44+
assertSameCode(actualInstructions, expectedInstructions)
45+
}
46+
47+
@Test
48+
def concatStringBufferToStringBuilder: Unit = {
49+
val code = """ def stringBuffer = new java.lang.StringBuffer("def"); def result = "abc" + stringBuffer """
50+
val actualInstructions = instructionsForResultMethod(code)
51+
val expectedInstructions = instructionsWithCommonParts(List(Invoke(INVOKEVIRTUAL, "C", "stringBuffer", "()Ljava/lang/StringBuffer;", false), Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/StringBuffer;)Ljava/lang/StringBuilder;", false)))
52+
assertSameCode(actualInstructions, expectedInstructions)
53+
}
54+
55+
@Test
56+
def concatCharSequenceToStringBuilder: Unit = {
57+
val code = """ def charSequence: CharSequence = "def"; def result = "abc" + charSequence """
58+
val actualInstructions = instructionsForResultMethod(code)
59+
val expectedInstructions = instructionsWithCommonParts(List(Invoke(INVOKEVIRTUAL, "C", "charSequence", "()Ljava/lang/CharSequence;", false), Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/CharSequence;)Ljava/lang/StringBuilder;", false)))
60+
assertSameCode(actualInstructions, expectedInstructions)
61+
}
62+
63+
@Test
64+
def concatIntToStringBuilder: Unit = {
65+
val code = """ def int = 123; def result = "abc" + int """
66+
val actualInstructions = instructionsForResultMethod(code)
67+
val expectedInstructions = instructionsWithCommonParts(List(Invoke(INVOKEVIRTUAL, "C", "int", "()I", false), Invoke(INVOKESTATIC, "scala/runtime/BoxesRunTime", "boxToInteger", "(I)Ljava/lang/Integer;", false), Invoke(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false)))
68+
assertSameCode(actualInstructions, expectedInstructions)
69+
}
70+
}

0 commit comments

Comments
 (0)