Skip to content

backend: Emit calls using the correct receiver #8499

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
308 changes: 152 additions & 156 deletions compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala

Large diffs are not rendered by default.

17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import scala.tools.asm
import scala.annotation.switch
import scala.collection.mutable
import Primitives.{NE, EQ, TestOp, ArithmeticOp}
import scala.tools.asm.tree.MethodInsnNode

/*
* A high-level facade to the ASM API for bytecode generation.
Expand Down Expand Up @@ -104,7 +105,7 @@ trait BCodeIdiomatic {
*/
abstract class JCodeMethodN {

def jmethod: asm.MethodVisitor
def jmethod: asm.tree.MethodNode

import asm.Opcodes;

Expand Down Expand Up @@ -394,21 +395,27 @@ trait BCodeIdiomatic {

// can-multi-thread
final def invokespecial(owner: String, name: String, desc: String, itf: Boolean): Unit = {
jmethod.visitMethodInsn(Opcodes.INVOKESPECIAL, owner, name, desc, itf)
emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf)
}
// can-multi-thread
final def invokestatic(owner: String, name: String, desc: String, itf: Boolean): Unit = {
jmethod.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, itf)
emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf)
}
// can-multi-thread
final def invokeinterface(owner: String, name: String, desc: String): Unit = {
jmethod.visitMethodInsn(Opcodes.INVOKEINTERFACE, owner, name, desc, true)
emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true)
}
// can-multi-thread
final def invokevirtual(owner: String, name: String, desc: String): Unit = {
jmethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false)
emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false)
}

def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean): Unit = {
val node = new MethodInsnNode(opcode, owner, name, desc, itf)
jmethod.instructions.add(node)
}


// can-multi-thread
final def goTo(label: asm.Label): Unit = { jmethod.visitJumpInsn(Opcodes.GOTO, label) }
// can-multi-thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class BTypesFromSymbols[I <: BackendInterface](val int: I) extends BTypes {
assert(
(!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) &&
(classSym != NothingClass && classSym != NullClass),
s"Cannot create ClassBType for special class symbol ${classSym.fullName}")
s"Cannot create ClassBType for special class symbol ${classSym.showFullName}")

convertedClasses.getOrElse(classSym, {
val internalName = classSym.javaBinaryName
Expand Down
15 changes: 10 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/BackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,10 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
}

abstract class SymbolHelper {
def exists: Boolean

// names
def fullName(sep: Char): String
def fullName: String
def simpleName: Name
def showFullName: String
def javaSimpleName: String
def javaBinaryName: String
def javaClassName: String
Expand All @@ -460,10 +460,11 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
/** Does this symbol actually correspond to an interface that will be emitted?
* In the backend, this should be preferred over `isInterface` because it
* also returns true for the symbols of the fake companion objects we
* create for Java-defined classes.
* create for Java-defined classes as well as for Java annotations
* which we represent as classes.
*/
final def isEmittedInterface: Boolean = isInterface ||
isJavaDefined && isModuleClass && companionClass.isInterface
isJavaDefined && (isAnnotation || isModuleClass && companionClass.isInterface)

// tests
def isClass: Boolean
Expand Down Expand Up @@ -491,6 +492,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def getsJavaPrivateFlag: Boolean
def isFinal: Boolean
def getsJavaFinalFlag: Boolean
def isScalaStatic: Boolean
def isStaticMember: Boolean
def isBottomClass: Boolean
def isBridge: Boolean
Expand All @@ -508,6 +510,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def shouldEmitForwarders: Boolean
def isJavaDefaultMethod: Boolean
def isClassConstructor: Boolean
def isAnnotation: Boolean
def isSerializable: Boolean
def isEnum: Boolean

Expand Down Expand Up @@ -536,6 +539,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
def enclosingClassSym: Symbol
def originalLexicallyEnclosingClass: Symbol
def nextOverriddenSymbol: Symbol
def allOverriddenSymbols: List[Symbol]


// members
Expand Down Expand Up @@ -605,6 +609,7 @@ abstract class BackendInterface extends BackendInterfaceDefinitions {
*/
def sortedMembersBasedOnFlags(required: Flags, excluded: Flags): List[Symbol]
def members: List[Symbol]
def decl(name: Name): Symbol
def decls: List[Symbol]
def underlying: Type
def parents: List[Type]
Expand Down
17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,10 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
}

implicit def symHelper(sym: Symbol): SymbolHelper = new SymbolHelper {
def exists: Boolean = sym.exists

// names
def fullName(sep: Char): String = sym.showFullName
def fullName: String = sym.showFullName
def simpleName: Name = sym.name
def showFullName: String = sym.showFullName
def javaSimpleName: String = toDenot(sym).name.mangledString // addModuleSuffix(simpleName.dropLocal)
def javaBinaryName: String = javaClassName.replace('.', '/') // TODO: can we make this a string? addModuleSuffix(fullNameInternal('/'))
def javaClassName: String = toDenot(sym).fullName.mangledString // addModuleSuffix(fullNameInternal('.')).toString
Expand Down Expand Up @@ -678,11 +678,13 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
isPrivate || (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass)

def isFinal: Boolean = sym.is(Flags.Final)
def isScalaStatic: Boolean =
toDenot(sym).hasAnnotation(ctx.definitions.ScalaStaticAnnot)
def isStaticMember: Boolean = (sym ne NoSymbol) &&
(sym.is(Flags.JavaStatic) || toDenot(sym).hasAnnotation(ctx.definitions.ScalaStaticAnnot))
(sym.is(Flags.JavaStatic) || isScalaStatic)
// guard against no sumbol cause this code is executed to select which call type(static\dynamic) to use to call array.clone

def isBottomClass: Boolean = (sym ne defn.NullClass) && (sym ne defn.NothingClass)
def isBottomClass: Boolean = (sym eq defn.NullClass) || (sym eq defn.NothingClass)
def isBridge: Boolean = sym.is(Flags.Bridge)
def isArtifact: Boolean = sym.is(Flags.Artifact)
def hasEnumFlag: Boolean = sym.isAllOf(Flags.JavaEnumTrait)
Expand All @@ -700,6 +702,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
def isEnum = sym.is(Flags.Enum)

def isClassConstructor: Boolean = toDenot(sym).isClassConstructor
def isAnnotation: Boolean = toDenot(sym).isAnnotation
def isSerializable: Boolean = toDenot(sym).isSerializable

/**
Expand Down Expand Up @@ -761,12 +764,14 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma
toDenot(sym)(shiftedContext).lexicallyEnclosingClass(shiftedContext)
} else NoSymbol
def nextOverriddenSymbol: Symbol = toDenot(sym).nextOverriddenSymbol
def allOverriddenSymbols: List[Symbol] = toDenot(sym).allOverriddenSymbols.toList

// members
def primaryConstructor: Symbol = toDenot(sym).primaryConstructor

/** For currently compiled classes: All locally defined classes including local classes.
* The empty list for classes that are not currently compiled.

*/
def nestedClasses: List[Symbol] = definedClasses(ctx.flattenPhase)

Expand Down Expand Up @@ -878,6 +883,8 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma

def memberInfo(s: Symbol): Type = tp.memberInfo(s)

def decl(name: Name): Symbol = tp.decl(name).symbol

def decls: List[Symbol] = tp.decls.toList

def members: List[Symbol] = tp.allMembers.map(_.symbol).toList
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/backend/jvm/GenBCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ class GenBCodePipeline(val int: DottyBackendInterface)(implicit val ctx: Context
if (claszSymbol.companionClass == NoSymbol) {
mirrorCodeGen.genMirrorClass(claszSymbol, cunit)
} else {
ctx.log(s"No mirror class for module with linked class: ${claszSymbol.fullName}")
ctx.log(s"No mirror class for module with linked class: ${claszSymbol.showFullName}")
null
}
} else null
Expand Down
16 changes: 11 additions & 5 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,21 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
}
}

def compileFromStrings(sourceCodes: String*): Unit = {
val sourceFiles = sourceCodes.map {sourceCode =>
val virtualFile = new VirtualFile(s"compileFromString-${java.util.UUID.randomUUID().toString}")
def compileFromStrings(scalaSources: List[String], javaSources: List[String] = Nil): Unit = {
def sourceFile(source: String, isJava: Boolean): SourceFile = {
val uuid = java.util.UUID.randomUUID().toString
val ext = if (isJava) ".java" else ".scala"
val virtualFile = new VirtualFile(s"compileFromString-$uuid.$ext")
val writer = new BufferedWriter(new OutputStreamWriter(virtualFile.output, "UTF-8")) // buffering is still advised by javadoc
writer.write(sourceCode)
writer.write(source)
writer.close()
new SourceFile(virtualFile, Codec.UTF8)
}
compileSources(sourceFiles.toList)
val sources =
scalaSources.map(sourceFile(_, isJava = false)) ++
javaSources.map(sourceFile(_, isJava = true))

compileSources(sources)
}

/** Print summary; return # of errors encountered */
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,10 @@ object SymDenotations {
*/
def derivesFrom(base: Symbol)(implicit ctx: Context): Boolean = false

/** Is this a Scala or Java annotation ? */
def isAnnotation(implicit ctx: Context): Boolean =
isClass && derivesFrom(defn.AnnotationClass)

/** Is this symbol a class that extends `java.io.Serializable` ? */
def isSerializable(implicit ctx: Context): Boolean =
isClass && derivesFrom(defn.JavaSerializableClass)
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ object Types {
case tp: ClassInfo => tp.cls
case tp: SingletonType => NoSymbol
case tp: TypeProxy => tp.underlying.typeSymbol
case _: JavaArrayType => defn.ArrayClass
case _ => NoSymbol
}

Expand Down Expand Up @@ -448,6 +449,8 @@ object Types {
else NoSymbol
case tp: OrType =>
tp.join.classSymbol
case _: JavaArrayType =>
defn.ArrayClass
case _ =>
NoSymbol
}
Expand Down
3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/semanticdb/Scala3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,6 @@ object Scala3:
def isSyntheticWithIdent(using Context): Boolean =
sym.is(Synthetic) && !sym.isAnonymous && !sym.name.isEmptyNumbered

def isAnnotation(using Context): Boolean =
sym.derivesFrom(defn.AnnotationClass)

// end SymbolOps

object LocalSymbol:
Expand Down
2 changes: 1 addition & 1 deletion compiler/test/dotty/tools/DottyTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ trait DottyTest extends ContextEscapeDetection {
def checkCompile(checkAfterPhase: String, source: String)(assertion: (tpd.Tree, Context) => Unit): Context = {
val c = compilerWithChecker(checkAfterPhase)(assertion)
val run = c.newRun
run.compileFromStrings(source)
run.compileFromStrings(List(source))
run.runContext
}

Expand Down
32 changes: 30 additions & 2 deletions compiler/test/dotty/tools/backend/jvm/DottyBytecodeTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.io.{File => JFile, InputStream}
trait DottyBytecodeTest {
import AsmNode._
import ASMConverters._
import DottyBytecodeTest._

protected object Opcode {
val newarray = 188
Expand Down Expand Up @@ -52,13 +53,16 @@ trait DottyBytecodeTest {
ctx0.setSetting(ctx0.settings.outputDir, outputDir)
}

def checkBCode(scalaSource: String)(checkOutput: AbstractFile => Unit): Unit =
checkBCode(List(scalaSource))(checkOutput)

/** Checks source code from raw strings */
def checkBCode(sources: String*)(checkOutput: AbstractFile => Unit): Unit = {
def checkBCode(scalaSources: List[String], javaSources: List[String] = Nil)(checkOutput: AbstractFile => Unit): Unit = {
implicit val ctx: Context = initCtx

val compiler = new Compiler
val run = compiler.newRun
compiler.newRun.compileFromStrings(sources: _*)
compiler.newRun.compileFromStrings(scalaSources, javaSources)

checkOutput(ctx.settings.outputDir.value)
}
Expand All @@ -78,6 +82,24 @@ trait DottyBytecodeTest {
classNode.fields.asScala.find(_.name == name) getOrElse
sys.error(s"Didn't find field '$name' in class '${classNode.name}'")

def getInstructions(c: ClassNode, name: String): List[Instruction] =
instructionsFromMethod(getMethod(c, name))

def assertSameCode(method: MethodNode, expected: List[Instruction]): Unit =
assertSameCode(instructionsFromMethod(method).dropNonOp, expected)
def assertSameCode(actual: List[Instruction], expected: List[Instruction]): Unit = {
assert(actual === expected, s"\nExpected: $expected\nActual : $actual")
}

def assertInvoke(m: MethodNode, receiver: String, method: String): Unit =
assertInvoke(instructionsFromMethod(m), receiver, method)
def assertInvoke(l: List[Instruction], receiver: String, method: String): Unit = {
assert(l.exists {
case Invoke(_, `receiver`, `method`, _, _) => true
case _ => false
}, l.stringLines)
}

def diffInstructions(isa: List[Instruction], isb: List[Instruction]): String = {
val len = Math.max(isa.length, isb.length)
val sb = new StringBuilder
Expand Down Expand Up @@ -191,3 +213,9 @@ trait DottyBytecodeTest {
)
}
}
object DottyBytecodeTest {
implicit class listStringLines[T](val l: List[T]) extends AnyVal {
def stringLines = l.mkString("\n")
}
}

Loading