blob: c7c262c4801c389687fa6a80323a1205a5970e39 [file] [log] [blame]
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
package kotlinx.atomicfu.transformer
import org.objectweb.asm.*
import org.objectweb.asm.ClassReader.*
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.commons.*
import org.objectweb.asm.commons.InstructionAdapter.*
import org.objectweb.asm.tree.*
import java.io.*
import java.net.*
import java.util.*
class TypeInfo(val fuType: Type, val originalType: Type, val transformedType: Type)
private const val AFU_PKG = "kotlinx/atomicfu"
private const val JUCA_PKG = "java/util/concurrent/atomic"
private const val JLI_PKG = "java/lang/invoke"
private const val ATOMIC = "atomic"
private const val TRACE = "Trace"
private const val TRACE_BASE = "TraceBase"
private const val TRACE_FORMAT = "TraceFormat"
private val INT_ARRAY_TYPE = getType("[I")
private val LONG_ARRAY_TYPE = getType("[J")
private val BOOLEAN_ARRAY_TYPE = getType("[Z")
private val REF_ARRAY_TYPE = getType("[Ljava/lang/Object;")
private val REF_TYPE = getType("L$AFU_PKG/AtomicRef;")
private val ATOMIC_ARRAY_TYPE = getType("L$AFU_PKG/AtomicArray;")
private val AFU_CLASSES: Map<String, TypeInfo> = mapOf(
"$AFU_PKG/AtomicInt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), INT_TYPE, INT_TYPE),
"$AFU_PKG/AtomicLong" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongFieldUpdater"), LONG_TYPE, LONG_TYPE),
"$AFU_PKG/AtomicRef" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceFieldUpdater"), OBJECT_TYPE, OBJECT_TYPE),
"$AFU_PKG/AtomicBoolean" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerFieldUpdater"), BOOLEAN_TYPE, INT_TYPE),
"$AFU_PKG/AtomicIntArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), INT_ARRAY_TYPE, INT_ARRAY_TYPE),
"$AFU_PKG/AtomicLongArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicLongArray"), LONG_ARRAY_TYPE, LONG_ARRAY_TYPE),
"$AFU_PKG/AtomicBooleanArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicIntegerArray"), BOOLEAN_ARRAY_TYPE, INT_ARRAY_TYPE),
"$AFU_PKG/AtomicArray" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE),
"$AFU_PKG/AtomicFU_commonKt" to TypeInfo(getObjectType("$JUCA_PKG/AtomicReferenceArray"), REF_ARRAY_TYPE, REF_ARRAY_TYPE)
)
private val WRAPPER: Map<Type, String> = mapOf(
INT_TYPE to "java/lang/Integer",
LONG_TYPE to "java/lang/Long",
BOOLEAN_TYPE to "java/lang/Boolean"
)
private val ARRAY_ELEMENT_TYPE: Map<Type, Int> = mapOf(
INT_ARRAY_TYPE to T_INT,
LONG_ARRAY_TYPE to T_LONG,
BOOLEAN_ARRAY_TYPE to T_BOOLEAN
)
private val AFU_TYPES: Map<Type, TypeInfo> = AFU_CLASSES.mapKeys { getObjectType(it.key) }
private val METHOD_HANDLES = "$JLI_PKG/MethodHandles"
private val LOOKUP = "$METHOD_HANDLES\$Lookup"
private val VH_TYPE = getObjectType("$JLI_PKG/VarHandle")
private val STRING_TYPE = getObjectType("java/lang/String")
private val CLASS_TYPE = getObjectType("java/lang/Class")
private fun String.prettyStr() = replace('/', '.')
data class MethodId(val owner: String, val name: String, val desc: String, val invokeOpcode: Int) {
override fun toString(): String = "${owner.prettyStr()}::$name"
}
private const val GET_VALUE = "getValue"
private const val SET_VALUE = "setValue"
private const val GET_SIZE = "getSize"
private const val AFU_CLS = "$AFU_PKG/AtomicFU"
private const val TRACE_KT = "$AFU_PKG/TraceKt"
private const val TRACE_BASE_CLS = "$AFU_PKG/$TRACE_BASE"
private val TRACE_BASE_TYPE = getObjectType(TRACE_BASE_CLS)
private val TRACE_APPEND = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_APPEND_2 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_APPEND_3 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_APPEND_4 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_DEFAULT_ARGS = "I${OBJECT_TYPE.descriptor}"
private const val DEFAULT = "\$default"
private const val DELEGATE = "\$delegate"
private val TRACE_FACTORY = MethodId(TRACE_KT, TRACE, "(IL$AFU_PKG/$TRACE_FORMAT;)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
private val TRACE_PARTIAL_ARGS_FACTORY = MethodId(TRACE_KT, "$TRACE$DEFAULT", "(IL$AFU_PKG/$TRACE_FORMAT;$TRACE_DEFAULT_ARGS)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
private val FACTORIES: Set<MethodId> = setOf(
MethodId(AFU_CLS, ATOMIC, "(Ljava/lang/Object;)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(I)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(J)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(Z)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC),
MethodId("$AFU_PKG/AtomicIntArray", "<init>", "(I)V", INVOKESPECIAL),
MethodId("$AFU_PKG/AtomicLongArray", "<init>", "(I)V", INVOKESPECIAL),
MethodId("$AFU_PKG/AtomicBooleanArray", "<init>", "(I)V", INVOKESPECIAL),
MethodId("$AFU_PKG/AtomicArray", "<init>", "(I)V", INVOKESPECIAL),
MethodId("$AFU_PKG/AtomicFU_commonKt", "atomicArrayOfNulls", "(I)L$AFU_PKG/AtomicArray;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(Ljava/lang/Object;L$TRACE_BASE_CLS;)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(IL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(JL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC, "(ZL$TRACE_BASE_CLS;)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC + DEFAULT, "(Ljava/lang/Object;L$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicRef;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC + DEFAULT, "(IL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicInt;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC + DEFAULT, "(JL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicLong;", INVOKESTATIC),
MethodId(AFU_CLS, ATOMIC + DEFAULT, "(ZL$TRACE_BASE_CLS;$TRACE_DEFAULT_ARGS)L$AFU_PKG/AtomicBoolean;", INVOKESTATIC)
)
private operator fun Int.contains(bit: Int) = this and bit != 0
private inline fun code(mv: MethodVisitor, block: InstructionAdapter.() -> Unit) {
block(InstructionAdapter(mv))
}
private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
val node = MethodNode(ASM5)
block(InstructionAdapter(node))
return node.instructions
}
data class FieldId(val owner: String, val name: String, val desc: String) {
override fun toString(): String = "${owner.prettyStr()}::$name"
}
class FieldInfo(
val fieldId: FieldId,
val fieldType: Type,
val isStatic: Boolean = false
) {
val owner = fieldId.owner
val ownerType: Type = getObjectType(owner)
val typeInfo = AFU_CLASSES.getValue(fieldType.internalName)
val fuType = typeInfo.fuType
val isArray = typeInfo.originalType.sort == ARRAY
// state: updated during analysis
val accessors = mutableSetOf<MethodId>() // set of accessor method that read the corresponding atomic
var hasExternalAccess = false // accessed from different package
var hasAtomicOps = false // has atomic operations operations other than getValue/setValue
val name: String
get() = if (hasExternalAccess) mangleInternal(fieldId.name) else fieldId.name
val fuName: String
get() {
val fuName = fieldId.name + '$' + "FU"
return if (hasExternalAccess) mangleInternal(fuName) else fuName
}
val refVolatileClassName = "${owner.replace('.', '/')}$${name.capitalize()}RefVolatile"
val staticRefVolatileField = refVolatileClassName.substringAfterLast("/").decapitalize()
fun getPrimitiveType(vh: Boolean): Type = if (vh) typeInfo.originalType else typeInfo.transformedType
private fun mangleInternal(fieldName: String): String = "$fieldName\$internal"
override fun toString(): String = "${owner.prettyStr()}::$name"
}
enum class Variant { FU, VH, BOTH }
class AtomicFUTransformer(
classpath: List<String>,
inputDir: File,
outputDir: File = inputDir,
var variant: Variant = Variant.FU
) : AtomicFUTransformerBase(inputDir, outputDir) {
private val classPathLoader = URLClassLoader(
(listOf(inputDir) + (classpath.map { File(it) } - outputDir))
.map { it.toURI().toURL() }.toTypedArray()
)
private val fields = mutableMapOf<FieldId, FieldInfo>()
private val accessors = mutableMapOf<MethodId, FieldInfo>()
private val traceFields = mutableSetOf<FieldId>()
private val traceAccessors = mutableSetOf<MethodId>()
private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
private val removeMethods = mutableSetOf<MethodId>()
override fun transform() {
info("Analyzing in $inputDir")
val files = inputDir.walk().filter { it.isFile }.toList()
val needTransform = analyzeFilesForFields(files)
if (needTransform || outputDir == inputDir) {
val vh = variant == Variant.VH
// visit method bodies for external references to fields, runs all logic, fails if anything is wrong
val needsTransform = analyzeFilesForRefs(files, vh)
// perform transformation
info("Transforming to $outputDir")
files.forEach { file ->
val bytes = file.readBytes()
val outBytes = if (file.isClassFile() && file in needsTransform) transformFile(file, bytes, vh) else bytes
val outFile = file.toOutputFile()
outFile.mkdirsAndWrite(outBytes)
if (variant == Variant.BOTH && outBytes !== bytes) {
val vhBytes = transformFile(file, bytes, true)
val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
vhFile.mkdirsAndWrite(vhBytes)
}
}
} else {
info("Nothing to transform -- all classes are up to date")
}
}
// Phase 1: visit methods and fields, register all accessors, collect times
// Returns 'true' if any files are out of date
private fun analyzeFilesForFields(files: List<File>): Boolean {
var needTransform = false
files.forEach { file ->
val inpTime = file.lastModified()
val outTime = file.toOutputFile().lastModified()
if (inpTime > outTime) needTransform = true
if (file.isClassFile()) analyzeFileForFields(file)
}
if (lastError != null) throw TransformerException("Encountered errors while analyzing fields", lastError)
return needTransform
}
private fun analyzeFileForFields(file: File) {
file.inputStream().use { ClassReader(it).accept(FieldsCollectorCV(), SKIP_FRAMES) }
}
// Phase2: visit method bodies for external references to fields and
// run method analysis in "analysisMode" to see which fields need AU/VH generated for them
// Returns a set of files that need transformation
private fun analyzeFilesForRefs(files: List<File>, vh: Boolean): Set<File> {
val result = HashSet<File>()
files.forEach { file ->
if (file.isClassFile() && analyzeFileForRefs(file, vh)) result += file
}
// Batch analyze all files, report all errors, bail out only at the end
if (lastError != null) throw TransformerException("Encountered errors while analyzing references", lastError)
return result
}
private fun analyzeFileForRefs(file: File, vh: Boolean): Boolean =
file.inputStream().use { input ->
transformed = false // clear global "transformed" flag
val cv = TransformerCV(null, vh, analyzePhase2 = true)
try {
ClassReader(input).accept(cv, SKIP_FRAMES)
} catch (e: Exception) {
error("Failed to analyze: $e", cv.sourceInfo)
e.printStackTrace(System.out)
if (lastError == null) lastError = e
}
transformed // true for classes that need transformation
}
// Phase 3: Transform file (only called for files that need to be transformed)
// Returns updated byte array for class data
private fun transformFile(file: File, bytes: ByteArray, vh: Boolean): ByteArray {
transformed = false // clear global "transformed" flag
val cw = CW()
val cv = TransformerCV(cw, vh, analyzePhase2 = false)
try {
ClassReader(ByteArrayInputStream(bytes)).accept(cv, SKIP_FRAMES)
} catch (e: Throwable) {
error("Failed to transform: $e", cv.sourceInfo)
e.printStackTrace(System.out)
if (lastError == null) lastError = e
}
if (!transformed) error("Invoked transformFile on a file that does not need transformation: $file")
if (lastError != null) throw TransformerException("Encountered errors while transforming: $file", lastError)
info("Transformed $file")
return cw.toByteArray() // write transformed bytes
}
private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM5, cv) {
lateinit var className: String
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
className = name
super.visit(version, access, name, signature, superName, interfaces)
}
}
private fun registerField(field: FieldId, fieldType: Type, isStatic: Boolean): FieldInfo {
val result = fields.getOrPut(field) { FieldInfo(field, fieldType, isStatic) }
if (result.fieldType != fieldType) abort("$field type mismatch between $fieldType and ${result.fieldType}")
return result
}
private inner class FieldsCollectorCV : CV(null) {
override fun visitField(
access: Int,
name: String,
desc: String,
signature: String?,
value: Any?
): FieldVisitor? {
val fieldType = getType(desc)
if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
val field = FieldId(className, name, desc)
info("$field field found")
if (ACC_PUBLIC in access) error("$field field cannot be public")
if (ACC_FINAL !in access) error("$field field must be final")
registerField(field, fieldType, (ACC_STATIC in access))
}
return null
}
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val methodType = getMethodType(desc)
if (methodType.argumentTypes.any { it in AFU_TYPES }) {
val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
info("$methodId method to be removed")
removeMethods += methodId
}
getPotentialAccessorType(access, className, methodType)?.let { onType ->
return AccessorCollectorMV(onType.internalName, access, name, desc, signature, exceptions)
}
if (name == "<init>" || name == "<clinit>") {
// check for copying atomic values into delegate fields and register potential delegate fields
return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
}
return null
}
}
private inner class AccessorCollectorMV(
private val className: String,
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
val insns = instructions.listUseful(4)
if (insns.size == 3 &&
insns[0].isAload(0) &&
insns[1].isGetField(className) &&
insns[2].isAreturn() ||
insns.size == 2 &&
insns[0].isGetStatic(className) &&
insns[1].isAreturn()
) {
val isStatic = insns.size == 2
val fi = (if (isStatic) insns[0] else insns[1]) as FieldInsnNode
val fieldName = fi.name
val field = FieldId(className, fieldName, fi.desc)
val fieldType = getType(fi.desc)
val accessorMethod = MethodId(className, name, desc, accessToInvokeOpcode(access))
info("$field accessor $name found")
if (fieldType == TRACE_BASE_TYPE) {
traceAccessors.add(accessorMethod)
} else {
val fieldInfo = registerField(field, fieldType, isStatic)
fieldInfo.accessors += accessorMethod
accessors[accessorMethod] = fieldInfo
}
}
}
}
// returns a type on which this is a potential accessor
private fun getPotentialAccessorType(access: Int, className: String, methodType: Type): Type? {
if (methodType.returnType !in AFU_TYPES && methodType.returnType != TRACE_BASE_TYPE) return null
return if (access and ACC_STATIC != 0) {
if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty()) {
// accessor for top-level atomic
getObjectType(className)
} else {
// accessor for top-level atomic
if (methodType.argumentTypes.size == 1 && methodType.argumentTypes[0].sort == OBJECT)
methodType.argumentTypes[0] else null
}
} else {
// if it not static, then it must be final
if (access and ACC_FINAL != 0 && methodType.argumentTypes.isEmpty())
getObjectType(className) else null
}
}
private inner class DelegateFieldsCollectorMV(
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
// register delegate field and the corresponding original atomic field
// getfield a: *Atomic
// putfield a$delegate: *Atomic
instructions.forEach { insn ->
if (insn is FieldInsnNode) {
insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
val next = insn.next
(next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
if (delegateFieldId.name.endsWith(DELEGATE)) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fields[getfieldId]!!
fieldDelegates[delegateFieldId] = originalField
}
}
}
}
if (insn is MethodInsnNode) {
val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
if (methodId in FACTORIES) {
(insn.nextUseful as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
if (delegateFieldId.name.endsWith(DELEGATE)) {
// delegate field is initialized by a factory invocation
val fieldType = getType(insn.desc).returnType
// for volatile delegated properties store FieldInfo of the delegate field itself
fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType)
}
}
}
}
}
}
}
private fun descToName(desc: String): String = desc.drop(1).dropLast(1)
private fun FieldInsnNode.checkPutFieldOrPutStatic(): FieldId? {
if (opcode != PUTFIELD && opcode != PUTSTATIC) return null
val fieldId = FieldId(owner, name, desc)
return if (fieldId in fields) fieldId else null
}
private fun FieldInsnNode.checkGetFieldOrGetStatic(): FieldId? {
if (opcode != GETFIELD && opcode != GETSTATIC) return null
val fieldId = FieldId(owner, name, desc)
return if (fieldId in fields) fieldId else null
}
private inner class TransformerCV(
cv: ClassVisitor?,
private val vh: Boolean,
private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
) : CV(cv) {
private var source: String? = null
var sourceInfo: SourceInfo? = null
private var metadata: AnnotationNode? = null
private var originalClinit: MethodNode? = null
private var newClinit: MethodNode? = null
private fun newClinit() = MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null)
fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
override fun visitSource(source: String?, debug: String?) {
this.source = source
super.visitSource(source, debug)
}
override fun visitField(
access: Int,
name: String,
desc: String,
signature: String?,
value: Any?
): FieldVisitor? {
val fieldType = getType(desc)
if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
val fieldId = FieldId(className, name, desc)
// skip delegate field
if (fieldId in fieldDelegates && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
transformed = true
return null
}
val f = fields[fieldId]!!
val visibility = when {
f.hasExternalAccess -> ACC_PUBLIC
f.accessors.isEmpty() -> ACC_PRIVATE
else -> 0
}
val protection = ACC_SYNTHETIC or visibility or when {
// reference to wrapper class (primitive atomics) or reference to to j.u.c.a.Atomic*Array (atomic array)
f.isStatic && !vh -> ACC_STATIC or ACC_FINAL
// primitive type field
f.isStatic && vh -> ACC_STATIC
else -> 0
}
val primitiveType = f.getPrimitiveType(vh)
val fv = when {
// replace (top-level) Atomic*Array with (static) j.u.c.a/Atomic*Array field
f.isArray && !vh -> super.visitField(protection, f.name, f.fuType.descriptor, null, null)
// replace top-level primitive atomics with static instance of the corresponding wrapping *RefVolatile class
f.isStatic && !vh -> super.visitField(
protection,
f.staticRefVolatileField,
getObjectType(f.refVolatileClassName).descriptor,
null,
null
)
// volatile primitive type field
else -> super.visitField(protection or ACC_VOLATILE, f.name, primitiveType.descriptor, null, null)
}
if (vh) {
// VarHandle is needed for all array element accesses and for regular fields with atomic ops
if (f.hasAtomicOps || f.isArray) vhField(protection, f)
} else {
// FieldUpdater is not needed for arrays (they use AtomicArrays)
if (f.hasAtomicOps && !f.isArray) fuField(protection, f)
}
transformed = true
return fv
}
// skip trace field
if (fieldType == TRACE_BASE_TYPE) {
traceFields += FieldId(className, name, desc)
transformed = true
return null
}
return super.visitField(access, name, desc, signature, value)
}
// Generates static VarHandle field
private fun vhField(protection: Int, f: FieldInfo) {
super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, VH_TYPE.descriptor, null, null)
code(getOrCreateNewClinit()) {
if (!f.isArray) {
invokestatic(METHOD_HANDLES, "lookup", "()L$LOOKUP;", false)
aconst(getObjectType(className))
aconst(f.name)
val primitiveType = f.getPrimitiveType(vh)
if (primitiveType.sort == OBJECT) {
aconst(primitiveType)
} else {
val wrapper = WRAPPER.getValue(primitiveType)
getstatic(wrapper, "TYPE", CLASS_TYPE.descriptor)
}
val findVHName = if (f.isStatic) "findStaticVarHandle" else "findVarHandle"
invokevirtual(
LOOKUP, findVHName,
getMethodDescriptor(VH_TYPE, CLASS_TYPE, STRING_TYPE, CLASS_TYPE), false
)
putstatic(className, f.fuName, VH_TYPE.descriptor)
} else {
// create VarHandle for array
aconst(f.getPrimitiveType(vh))
invokestatic(
METHOD_HANDLES,
"arrayElementVarHandle",
getMethodDescriptor(VH_TYPE, CLASS_TYPE),
false
)
putstatic(className, f.fuName, VH_TYPE.descriptor)
}
}
}
// Generates static AtomicXXXFieldUpdater field
private fun fuField(protection: Int, f: FieldInfo) {
super.visitField(protection or ACC_FINAL or ACC_STATIC, f.fuName, f.fuType.descriptor, null, null)
code(getOrCreateNewClinit()) {
val params = mutableListOf<Type>()
params += CLASS_TYPE
if (!f.isStatic) aconst(getObjectType(className)) else aconst(getObjectType(f.refVolatileClassName))
val primitiveType = f.getPrimitiveType(vh)
if (primitiveType.sort == OBJECT) {
params += CLASS_TYPE
aconst(primitiveType)
}
params += STRING_TYPE
aconst(f.name)
invokestatic(
f.fuType.internalName,
"newUpdater",
getMethodDescriptor(f.fuType, *params.toTypedArray()),
false
)
putstatic(className, f.fuName, f.fuType.descriptor)
}
}
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
val methodId = MethodId(className, name, desc, accessToInvokeOpcode(access))
if (methodId in accessors || methodId in traceAccessors || methodId in removeMethods) {
// drop and skip the methods that were found in Phase 1
// todo: should remove those methods from kotlin metadata, too
transformed = true
return null // drop accessor
}
val sourceInfo = SourceInfo(methodId, source)
val superMV = if (name == "<clinit>" && desc == "()V") {
if (access and ACC_STATIC == 0) abort("<clinit> method not marked as static")
// defer writing class initialization method
val node = MethodNode(ASM5, access, name, desc, signature, exceptions)
if (originalClinit != null) abort("Multiple <clinit> methods found")
originalClinit = node
node
} else {
// write transformed method to class right away
super.visitMethod(access, name, desc, signature, exceptions)
}
val mv = TransformerMV(
sourceInfo, access, name, desc, signature, exceptions, superMV,
className.ownerPackageName, vh, analyzePhase2
)
this.sourceInfo = mv.sourceInfo
return mv
}
override fun visitAnnotation(desc: String?, visible: Boolean): AnnotationVisitor? {
if (desc == KOTLIN_METADATA_DESC) {
check(visible) { "Expected run-time visible $KOTLIN_METADATA_DESC annotation" }
check(metadata == null) { "Only one $KOTLIN_METADATA_DESC annotation is expected" }
return AnnotationNode(desc).also { metadata = it }
}
return super.visitAnnotation(desc, visible)
}
override fun visitEnd() {
// remove unused methods from metadata
metadata?.let {
val mt = MetadataTransformer(
removeFields = fields.keys + traceFields,
removeMethods = accessors.keys + traceAccessors + removeMethods
)
if (mt.transformMetadata(it)) transformed = true
if (cv != null) it.accept(cv.visitAnnotation(KOTLIN_METADATA_DESC, true))
}
if (analyzePhase2) return // nop in analyze phase
// collect class initialization
if (originalClinit != null || newClinit != null) {
val newClinit = newClinit
if (newClinit == null) {
// dump just original clinit
originalClinit!!.accept(cv)
} else {
// create dummy base code if needed
val originalClinit = originalClinit ?: newClinit().also {
code(it) { visitInsn(RETURN) }
}
// makes sure return is last useful instruction
val last = originalClinit.instructions.last
val ret = last.thisOrPrevUseful
if (ret == null || !ret.isReturn()) abort("Last instruction in <clinit> shall be RETURN", ret)
originalClinit.instructions.insertBefore(ret, newClinit.instructions)
originalClinit.accept(cv)
}
}
super.visitEnd()
}
}
private inner class TransformerMV(
sourceInfo: SourceInfo,
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?,
mv: MethodVisitor?,
private val packageName: String,
private val vh: Boolean,
private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
init {
this.mv = mv
}
val sourceInfo = sourceInfo.copy(insnList = instructions)
private var tempLocal = 0
private var bumpedLocals = 0
private fun bumpLocals(n: Int) {
if (bumpedLocals == 0) tempLocal = maxLocals
while (n > bumpedLocals) bumpedLocals = n
maxLocals = tempLocal + bumpedLocals
}
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
val methodId = MethodId(owner, name, desc, opcode)
val fieldInfo = accessors[methodId]
// compare owner packages
if (fieldInfo != null && methodId.owner.ownerPackageName != packageName) {
if (analyzePhase2) {
fieldInfo.hasExternalAccess = true
} else {
check(fieldInfo.hasExternalAccess) // should have been set on previous phase
}
}
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
override fun visitEnd() {
// transform instructions list
var hasErrors = false
var i = instructions.first
while (i != null)
try {
i = transform(i)
} catch (e: AbortTransform) {
error(e.message!!, sourceInfo.copy(i = e.i))
i = i.next
hasErrors = true
}
// save transformed method if not in analysis phase
if (!hasErrors && !analyzePhase2)
accept(mv)
}
private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
val fieldId = FieldId(owner, name, desc)
if (fieldId in fieldDelegates) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fieldDelegates[fieldId]!!
val getField = previous as FieldInsnNode
val next = this.next
if (!originalField.isStatic) instructions.remove(getField.previous) // no aload for static field
instructions.remove(getField)
instructions.remove(this)
return next
}
return null
}
// ld: instruction that loads atomic field (already changed to getstatic)
// iv: invoke virtual on the loaded atomic field (to be fixed)
private fun fixupInvokeVirtual(
ld: FieldInsnNode,
onArrayElement: Boolean, // true when fixing invokeVirtual on loaded array element
iv: MethodInsnNode,
f: FieldInfo
): AbstractInsnNode? {
check(f.isArray || !onArrayElement) { "Cannot fix array element access on non array fields" }
val typeInfo = if (onArrayElement) f.typeInfo else AFU_CLASSES.getValue(iv.owner)
if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
val setInsn = iv.name == SET_VALUE
if (!onArrayElement) {
val primitiveType = f.getPrimitiveType(vh)
val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
if (!vh && f.isStatic) {
val getOwnerClass = FieldInsnNode(
GETSTATIC,
f.owner,
f.staticRefVolatileField,
getObjectType(owner).descriptor
)
instructions.insert(ld, getOwnerClass)
}
instructions.remove(ld) // drop getstatic (we don't need field updater)
val j = FieldInsnNode(
when {
iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
}, owner, f.name, primitiveType.descriptor
)
instructions.set(iv, j) // replace invokevirtual with get/setfield
return j.next
} else {
var methodType = getMethodType(iv.desc)
if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
val ret = f.typeInfo.transformedType.elementType
iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
methodType = getMethodType(iv.desc)
}
iv.name = iv.name.substring(0, 3)
if (!vh) {
// map to j.u.c.a.Atomic*Array get or set
iv.owner = descToName(f.fuType.descriptor)
iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
} else {
// map to VarHandle get or set
iv.owner = descToName(VH_TYPE.descriptor)
iv.desc = getMethodDescriptor(
methodType.returnType,
f.getPrimitiveType(vh),
INT_TYPE,
*methodType.argumentTypes
)
}
return iv
}
}
if (f.isArray && iv.name == GET_SIZE) {
if (!vh) {
// map to j.u.c.a.Atomic*Array length()
iv.owner = descToName(f.fuType.descriptor)
iv.name = "length"
} else {
// replace with arraylength of the primitive type array
val arrayLength = InsnNode(ARRAYLENGTH)
instructions.insert(ld, arrayLength)
// do not need varhandle
if (!f.isStatic) {
instructions.remove(ld.previous.previous)
instructions.remove(ld.previous)
} else {
instructions.remove(ld.previous)
}
instructions.remove(iv)
return arrayLength
}
return iv
}
// An operation other than getValue/setValue is used
if (f.isArray && iv.name == "get") { // "operator get" that retrieves array element, further ops apply to it
// fixup atomic operation on this array element
return fixupLoadedArrayElement(f, ld, iv)
}
// non-trivial atomic operation
check(f.isArray == onArrayElement) { "Atomic operations can be performed on atomic elements only" }
if (analyzePhase2) {
f.hasAtomicOps = true // mark the fact that non-trivial atomic op is used here
} else {
check(f.hasAtomicOps) // should have been set on previous phase
}
// update method invocation
if (vh) {
vhOperation(iv, typeInfo, f)
} else {
fuOperation(iv, typeInfo, f)
}
if (f.isStatic && !onArrayElement) {
if (!vh) {
// getstatic *RefVolatile class
val aload = FieldInsnNode(
GETSTATIC,
f.owner,
f.staticRefVolatileField,
getObjectType(f.refVolatileClassName).descriptor
)
instructions.insert(ld, aload)
}
return iv.next
}
if (!onArrayElement) {
// insert swap after field load
val swap = InsnNode(SWAP)
instructions.insert(ld, swap)
return swap.next
}
return iv.next
}
private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
val methodType = getMethodType(iv.desc)
val args = methodType.argumentTypes
iv.owner = VH_TYPE.internalName
val params = if (!f.isArray && !f.isStatic) mutableListOf<Type>(
OBJECT_TYPE,
*args
) else if (!f.isArray && f.isStatic) mutableListOf<Type>(*args) else mutableListOf(
typeInfo.originalType,
INT_TYPE,
*args
)
val elementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
val long = elementType == LONG_TYPE
when (iv.name) {
"lazySet" -> iv.name = "setRelease"
"getAndIncrement" -> {
instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
params += elementType
iv.name = "getAndAdd"
}
"getAndDecrement" -> {
instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
params += elementType
iv.name = "getAndAdd"
}
"addAndGet" -> {
bumpLocals(if (long) 2 else 1)
instructions.insertBefore(iv, insns {
if (long) dup2() else dup()
store(tempLocal, elementType)
})
iv.name = "getAndAdd"
instructions.insert(iv, insns {
load(tempLocal, elementType)
add(elementType)
})
}
"incrementAndGet" -> {
instructions.insertBefore(iv, insns { if (long) lconst(1) else iconst(1) })
params += elementType
iv.name = "getAndAdd"
instructions.insert(iv, insns {
if (long) lconst(1) else iconst(1)
add(elementType)
})
}
"decrementAndGet" -> {
instructions.insertBefore(iv, insns { if (long) lconst(-1) else iconst(-1) })
params += elementType
iv.name = "getAndAdd"
instructions.insert(iv, insns {
if (long) lconst(-1) else iconst(-1)
add(elementType)
})
}
}
iv.desc = getMethodDescriptor(methodType.returnType, *params.toTypedArray())
}
private fun fuOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
val methodType = getMethodType(iv.desc)
val originalElementType = if (f.isArray) typeInfo.originalType.elementType else typeInfo.originalType
val transformedElementType =
if (f.isArray) typeInfo.transformedType.elementType else typeInfo.transformedType
val trans = originalElementType != transformedElementType
val args = methodType.argumentTypes
var ret = methodType.returnType
if (trans) {
args.forEachIndexed { i, type -> if (type == originalElementType) args[i] = transformedElementType }
if (iv.name == "getAndSet") ret = transformedElementType
}
if (f.isArray) {
// map to j.u.c.a.AtomicIntegerArray method
iv.owner = typeInfo.fuType.internalName
// add int argument as element index
iv.desc = getMethodDescriptor(ret, INT_TYPE, *args)
return // array operation in this mode does not use FU field
}
iv.owner = typeInfo.fuType.internalName
iv.desc = getMethodDescriptor(ret, OBJECT_TYPE, *args)
}
private fun tryEraseUncheckedCast(getter: AbstractInsnNode) {
if (getter.next.opcode == DUP && getter.next.next.opcode == IFNONNULL) {
// unchecked cast upon AtomicRef var is performed
// erase compiler check for this var being not null:
// (remove all insns from ld till the non null branch label)
val ifnonnull = (getter.next.next as JumpInsnNode)
var i: AbstractInsnNode = getter.next
while (!(i is LabelNode && i.label == ifnonnull.label.label)) {
val next = i.next
instructions.remove(i)
i = next
}
}
}
private fun fixupLoadedAtomicVar(f: FieldInfo, ld: FieldInsnNode): AbstractInsnNode? {
if (f.fieldType == REF_TYPE) tryEraseUncheckedCast(ld)
val j = FlowAnalyzer(ld.next).execute()
return fixupOperationOnAtomicVar(j, f, ld, null)
}
private fun fixupLoadedArrayElement(f: FieldInfo, ld: FieldInsnNode, getter: MethodInsnNode): AbstractInsnNode? {
if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(getter)
// contains array field load (in vh case: + swap and pure type array load) and array element index
// this array element information is only used in case the reference to this element is stored (copied and inserted at the point of loading)
val arrayElementInfo = mutableListOf<AbstractInsnNode>()
if (vh) {
if (!f.isStatic) {
arrayElementInfo.add(ld.previous.previous) // getstatic VarHandle field
arrayElementInfo.add(ld.previous) // swap
} else {
arrayElementInfo.add(ld.previous) // getstatic VarHandle field
}
}
var i: AbstractInsnNode = ld
while (i != getter) {
arrayElementInfo.add(i)
i = i.next
}
// start of array element operation arguments
val args = getter.next
// remove array element getter
instructions.remove(getter)
val arrayElementOperation = FlowAnalyzer(args).execute()
return fixupOperationOnAtomicVar(arrayElementOperation, f, ld, arrayElementInfo)
}
private fun fixupOperationOnAtomicVar(operation: AbstractInsnNode, f: FieldInfo, ld: FieldInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? {
when (operation) {
is MethodInsnNode -> {
// invoked virtual method on atomic var -- fixup & done with it
debug("invoke $f.${operation.name}", sourceInfo.copy(i = operation))
return fixupInvokeVirtual(ld, arrayElementInfo != null, operation, f)
}
is VarInsnNode -> {
val onArrayElement = arrayElementInfo != null
check(f.isArray == onArrayElement)
// was stored to local -- needs more processing:
// for class fields store owner ref into the variable instead
// for static fields store nothing, remove the local var
val v = operation.`var`
val next = operation.next
if (onArrayElement) {
// leave just owner class load insn on stack
arrayElementInfo!!.forEach { instructions.remove(it) }
} else {
instructions.remove(ld)
}
val lv = localVar(v, operation)
if (f.isStatic) instructions.remove(operation) // remove astore operation
if (lv != null) {
// Stored to a local variable with an entry in LVT (typically because of inline function)
if (lv.desc != f.fieldType.descriptor && !onArrayElement)
abort("field $f was stored to a local variable #$v \"${lv.name}\" with unexpected type: ${lv.desc}")
// correct local variable descriptor
lv.desc = f.ownerType.descriptor
lv.signature = null
// process all loads of this variable in the corresponding local variable range
forVarLoads(v, lv.start, lv.end) { otherLd ->
fixupLoad(f, ld, otherLd, arrayElementInfo)
}
} else {
// Spilled temporarily to a local variable w/o an entry in LVT -> fixup only one load
fixupLoad(f, ld, nextVarLoad(v, next), arrayElementInfo)
}
return next
}
else -> abort("cannot happen")
}
}
private fun fixupLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>?): AbstractInsnNode? {
val next = if (arrayElementInfo != null) {
fixupArrayElementLoad(f, ld, otherLd, arrayElementInfo)
} else {
fixupVarLoad(f, ld, otherLd)
}
if (f.isStatic) instructions.remove(otherLd) // remove aload instruction for static fields, nothing is stored there
return next
}
private fun fixupVarLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode): AbstractInsnNode? {
val ldCopy = ld.clone(null) as FieldInsnNode
instructions.insert(otherLd, ldCopy)
return fixupLoadedAtomicVar(f, ldCopy)
}
private fun fixupArrayElementLoad(f: FieldInfo, ld: FieldInsnNode, otherLd: VarInsnNode, arrayElementInfo: List<AbstractInsnNode>): AbstractInsnNode? {
if (f.fieldType == ATOMIC_ARRAY_TYPE) tryEraseUncheckedCast(otherLd)
// index instructions from array element info: drop owner class load instruction (in vh case together with preceding getting VH + swap)
val index = arrayElementInfo.drop(if (vh) 3 else 1)
// previously stored array element reference is loaded -> arrayElementInfo should be cloned and inserted at the point of this load
// before cloning make sure that index instructions contain just loads and simple arithmetic, without any invocations and complex data flow
for (indexInsn in index) {
checkDataFlowComplexity(indexInsn)
}
// start of atomic operation arguments
val args = otherLd.next
val operationOnArrayElement = FlowAnalyzer(args).execute()
val arrayElementInfoCopy = mutableListOf<AbstractInsnNode>()
arrayElementInfo.forEach { arrayElementInfoCopy.add(it.clone(null)) }
arrayElementInfoCopy.forEach { instructions.insertBefore(args, it) }
return fixupOperationOnAtomicVar(operationOnArrayElement, f, ld, arrayElementInfo)
}
fun checkDataFlowComplexity(i: AbstractInsnNode) {
when (i) {
is MethodInsnNode -> {
abort("No method invocations are allowed for calculation of an array element index " +
"at the point of loading the reference to this element.\n" +
"Extract index calculation to the local variable.", i)
}
is LdcInsnNode -> { /* ok loading const */ }
else -> {
when(i.opcode) {
IADD, ISUB, IMUL, IDIV, IREM, IAND, IOR, IXOR, ISHL, ISHR, IUSHR -> { /* simple arithmetics */ }
ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, ILOAD, IALOAD -> { /* int loads */ }
GETFIELD, GETSTATIC -> { /* getting fields */ }
else -> {
abort("Complex data flow is not allowed for calculation of an array element index " +
"at the point of loading the reference to this element.\n" +
"Extract index calculation to the local variable.", i)
}
}
}
}
}
private fun putPrimitiveTypeWrapper(
factoryInsn: MethodInsnNode,
initStart: AbstractInsnNode,
f: FieldInfo,
next: FieldInsnNode
): AbstractInsnNode? {
// generate wrapper class for static fields of primitive type
val factoryArg = getMethodType(factoryInsn.desc).argumentTypes[0]
generateRefVolatileClass(f, factoryArg)
// remove calling atomic factory for static field and following putstatic
val afterPutStatic = next.next
instructions.remove(factoryInsn)
instructions.remove(next)
initRefVolatile(f, factoryArg, initStart, afterPutStatic)
return afterPutStatic
}
private fun putJucaAtomicArray(
arrayfactoryInsn: MethodInsnNode,
initStart: AbstractInsnNode,
f: FieldInfo,
next: FieldInsnNode
): AbstractInsnNode? {
// replace with invoking j.u.c.a.Atomic*Array constructor
val jucaAtomicArrayDesc = f.typeInfo.fuType.descriptor
if (initStart.opcode == NEW) {
// change descriptor of NEW instruction
(initStart as TypeInsnNode).desc = descToName(jucaAtomicArrayDesc)
arrayfactoryInsn.owner = descToName(jucaAtomicArrayDesc)
} else {
// array initialisation starts from bipush size, then static array factory was called (atomicArrayOfNulls)
// add NEW j.u.c.a.Atomic*Array instruction
val newInsn = TypeInsnNode(NEW, descToName(jucaAtomicArrayDesc))
instructions.insert(initStart.previous, newInsn)
instructions.insert(newInsn, InsnNode(DUP))
val jucaArrayFactory =
MethodInsnNode(INVOKESPECIAL, descToName(jucaAtomicArrayDesc), "<init>", "(I)V", false)
instructions.set(arrayfactoryInsn, jucaArrayFactory)
}
//fix the following putfield
next.desc = jucaAtomicArrayDesc
next.name = f.name
transformed = true
return next.next
}
private fun putPureVhArray(
arrayFactoryInsn: MethodInsnNode,
initStart: AbstractInsnNode,
f: FieldInfo,
next: FieldInsnNode
): AbstractInsnNode? {
if (initStart.opcode == NEW) {
// remove dup
instructions.remove(initStart.next)
// remove NEW AFU_PKG/Atomic*Array instruction
instructions.remove(initStart)
}
// create pure array of given size and put it
val primitiveType = f.getPrimitiveType(vh)
val primitiveElementType = ARRAY_ELEMENT_TYPE[f.typeInfo.originalType]
val newArray =
if (primitiveElementType != null) IntInsnNode(NEWARRAY, primitiveElementType)
else TypeInsnNode(ANEWARRAY, descToName(primitiveType.elementType.descriptor))
instructions.set(arrayFactoryInsn, newArray)
next.desc = primitiveType.descriptor
next.name = f.name
transformed = true
return next.next
}
// removes pushing atomic factory trace arguments
// returns the first value argument push
private fun removeTraceInit(atomicFactory: MethodInsnNode, isArrayFactory: Boolean): AbstractInsnNode {
val initStart = FlowAnalyzer(atomicFactory).getInitStart(1)
if (isArrayFactory) return initStart
var lastArg = atomicFactory.previous
val valueArgInitLast = FlowAnalyzer(atomicFactory).getValueArgInitLast()
while (lastArg != valueArgInitLast) {
val prev = lastArg.previous
instructions.remove(lastArg)
lastArg = prev
}
return initStart
}
private fun removeTraceAppend(append: AbstractInsnNode): AbstractInsnNode {
// remove append trace instructions: from append invocation up to getfield Trace or accessor to Trace field
val afterAppend = append.next
var start = append
val isGetFieldTrace = { insn: AbstractInsnNode ->
insn.opcode == GETFIELD && (start as FieldInsnNode).desc == getObjectType(TRACE_BASE_CLS).descriptor }
val isTraceAccessor = { insn: AbstractInsnNode ->
if (insn is MethodInsnNode) {
val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
methodId in traceAccessors
} else false
}
while (!(isGetFieldTrace(start) || isTraceAccessor(start))) {
start = start.previous
}
// now start contains Trace getfield insn or Trace accessor
if (isTraceAccessor(start)) {
instructions.remove(start.previous.previous)
instructions.remove(start.previous)
} else {
instructions.remove(start.previous)
}
while (start != afterAppend) {
if (start is VarInsnNode) {
// remove all local store instructions
localVariables.removeIf { it.index == (start as VarInsnNode).`var` }
}
val next = start.next
instructions.remove(start)
start = next
}
return afterAppend
}
private fun transform(i: AbstractInsnNode): AbstractInsnNode? {
when (i) {
is MethodInsnNode -> {
val methodId = MethodId(i.owner, i.name, i.desc, i.opcode)
when {
methodId in FACTORIES -> {
if (name != "<init>" && name != "<clinit>") abort("factory $methodId is used outside of constructor or class initialisation")
val next = i.nextUseful
val fieldId = (next as? FieldInsnNode)?.checkPutFieldOrPutStatic()
?: abort("factory $methodId invocation must be followed by putfield")
val f = fields[fieldId]!!
val isArray = AFU_CLASSES[i.owner]?.let { it.originalType.sort == ARRAY } ?: false
// erase pushing arguments for trace initialisation
val newInitStart = removeTraceInit(i, isArray)
// in FU mode wrap values of top-level primitive atomics into corresponding *RefVolatile class
if (!vh && f.isStatic && !f.isArray) {
return putPrimitiveTypeWrapper(i, newInitStart, f, next)
}
if (f.isArray) {
return if (vh) {
putPureVhArray(i, newInitStart, f, next)
} else {
putJucaAtomicArray(i, newInitStart, f, next)
}
}
instructions.remove(i)
transformed = true
val primitiveType = f.getPrimitiveType(vh)
next.desc = primitiveType.descriptor
next.name = f.name
return next.next
}
methodId in accessors -> {
// replace INVOKESTATIC/VIRTUAL to accessor with GETSTATIC on var handle / field updater
val f = accessors[methodId]!!
val j = FieldInsnNode(
GETSTATIC, f.owner, f.fuName,
if (vh) VH_TYPE.descriptor else f.fuType.descriptor
)
// set original name for an array in FU mode
if (!vh && f.isArray) {
j.opcode = if (!f.isStatic) GETFIELD else GETSTATIC
j.name = f.name
}
instructions.set(i, j)
if (vh && f.isArray) {
return insertPureVhArray(j, f)
}
transformed = true
return fixupLoadedAtomicVar(f, j)
}
methodId == TRACE_FACTORY || methodId == TRACE_PARTIAL_ARGS_FACTORY -> {
if (methodId == TRACE_FACTORY) {
// remove trace format initialization
var checkcastTraceFormat = i
while (checkcastTraceFormat.opcode != CHECKCAST) checkcastTraceFormat = checkcastTraceFormat.previous
val astoreTraceFormat = checkcastTraceFormat.next
val tranceFormatInitStart = FlowAnalyzer(checkcastTraceFormat.previous).getInitStart(1).previous
var initInsn = checkcastTraceFormat
while (initInsn != tranceFormatInitStart) {
val prev = initInsn.previous
instructions.remove(initInsn)
initInsn = prev
}
instructions.insertBefore(astoreTraceFormat, InsnNode(ACONST_NULL))
}
// remove trace factory and following putfield
val argsSize = getMethodType(methodId.desc).argumentTypes.size
val putfield = i.next
val next = putfield.next
val depth = if (i.opcode == INVOKESPECIAL) 2 else argsSize
val initStart = FlowAnalyzer(i.previous).getInitStart(depth).previous
var lastArg = i
while (lastArg != initStart) {
val prev = lastArg.previous
instructions.remove(lastArg)
lastArg = prev
}
instructions.remove(initStart) // aload of the parent class
instructions.remove(putfield)
return next
}
methodId == TRACE_APPEND || methodId == TRACE_APPEND_2 || methodId == TRACE_APPEND_3 || methodId == TRACE_APPEND_4 -> {
return removeTraceAppend(i)
}
methodId in removeMethods -> {
abort(
"invocation of method $methodId on atomic types. " +
"Make the latter method 'inline' to use it", i
)
}
i.opcode == INVOKEVIRTUAL && i.owner in AFU_CLASSES -> {
abort("standalone invocation of $methodId that was not traced to previous field load", i)
}
}
}
is FieldInsnNode -> {
val fieldId = FieldId(i.owner, i.name, i.desc)
if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
if (fieldId in fieldDelegates && i.next.opcode == ASTORE) {
return transformDelegatedFieldAccessor(i, fieldId)
}
(i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
// Convert GETFIELD to GETSTATIC on var handle / field updater
val f = fields[fieldId]!!
val isArray = f.getPrimitiveType(vh).sort == ARRAY
// GETSTATIC for all fields except FU arrays
if (!isArray || vh) {
if (i.desc != f.fieldType.descriptor) return i.next // already converted get/setfield
i.opcode = GETSTATIC
i.name = f.fuName
}
// for FU arrays with external access change name to mangled one
if (!vh && isArray && f.hasExternalAccess) {
i.name = f.name
}
i.desc = if (vh) VH_TYPE.descriptor else f.fuType.descriptor
val prev = i.previous
if (vh && f.getPrimitiveType(vh).sort == ARRAY) {
return getInsnOrNull(from = prev, to = insertPureVhArray(i, f)) { it.isAtomicGetFieldOrGetStatic() }
}
transformed = true
// in order not to skip the transformation of atomic field loads
// check if there are any nested between the current atomic field load instruction i and it's transformed operation
// and return the first one
return getInsnOrNull(from = prev, to = fixupLoadedAtomicVar(f, i)) { it.isAtomicGetFieldOrGetStatic() }
}
}
}
return i.next
}
private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode {
val f = fieldDelegates[fieldId]!!
val astore = (i.next as? VarInsnNode) ?: abort("Method $name does not match the pattern of a delegated field accessor")
val v = astore.`var`
var cur: AbstractInsnNode = i
while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
val next = cur.next
instructions.remove(cur)
cur = next
}
val invokeVirtual = FlowAnalyzer(cur.next).execute()
instructions.remove(cur)
check(invokeVirtual.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
val accessorName = (invokeVirtual as MethodInsnNode).name.substring(0, 3)
val isGetter = accessorName == "get"
val primitiveType = f.getPrimitiveType(vh)
val j = FieldInsnNode(if (isGetter) GETFIELD else PUTFIELD, f.owner, f.name, primitiveType.descriptor)
instructions.set(invokeVirtual, j)
localVariables.removeIf {
!(getType(it.desc).internalName == f.owner ||
(!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
}
return j.next
}
private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
this is FieldInsnNode && (opcode == GETFIELD || opcode == GETSTATIC) &&
FieldId(owner, name, desc) in fields
private fun AbstractInsnNode.isAtomicGetValueOrSetValue() =
isInvokeVirtual() && (getObjectType((this as MethodInsnNode).owner) in AFU_TYPES) &&
(name == GET_VALUE || name == SET_VALUE)
private fun insertPureVhArray(getVarHandleInsn: FieldInsnNode, f: FieldInfo): AbstractInsnNode? {
val getPureArray = FieldInsnNode(GETFIELD, f.owner, f.name, f.getPrimitiveType(vh).descriptor)
if (!f.isStatic) {
// swap className reference and VarHandle
val swap = InsnNode(SWAP)
instructions.insert(getVarHandleInsn, swap)
instructions.insert(swap, getPureArray)
} else {
getPureArray.opcode = GETSTATIC
instructions.insert(getVarHandleInsn, getPureArray)
}
transformed = true
return fixupLoadedAtomicVar(f, getPureArray)
}
// generates a ref class with volatile field of primitive type inside
private fun generateRefVolatileClass(f: FieldInfo, arg: Type) {
if (analyzePhase2) return // nop
val cw = ClassWriter(0)
val visibility = if (f.hasExternalAccess) ACC_PUBLIC else 0
cw.visit(V1_6, visibility or ACC_SYNTHETIC, f.refVolatileClassName, null, "java/lang/Object", null)
//creating class constructor
val cons = cw.visitMethod(ACC_PUBLIC, "<init>", "(${arg.descriptor})V", null, null)
code(cons) {
visitVarInsn(ALOAD, 0)
invokespecial("java/lang/Object", "<init>", "()V", false)
visitVarInsn(ALOAD, 0)
load(1, arg)
putfield(f.refVolatileClassName, f.name, f.getPrimitiveType(vh).descriptor)
visitInsn(RETURN)
// stack size to fit long type
visitMaxs(3, 3)
}
//declaring volatile field of primitive type
cw.visitField(visibility or ACC_VOLATILE, f.name, f.getPrimitiveType(vh).descriptor, null, null)
val genFile = outputDir / "${f.refVolatileClassName}.class"
genFile.mkdirsAndWrite(cw.toByteArray())
}
// Initializes static instance of generated *RefVolatile class
private fun initRefVolatile(
f: FieldInfo,
argType: Type,
firstInitInsn: AbstractInsnNode,
lastInitInsn: AbstractInsnNode
) {
val new = TypeInsnNode(NEW, f.refVolatileClassName)
val dup = InsnNode(DUP)
instructions.insertBefore(firstInitInsn, new)
instructions.insertBefore(firstInitInsn, dup)
val invokespecial =
MethodInsnNode(INVOKESPECIAL, f.refVolatileClassName, "<init>", "(${argType.descriptor})V", false)
val putstatic = FieldInsnNode(
PUTSTATIC,
f.owner,
f.staticRefVolatileField,
getObjectType(f.refVolatileClassName).descriptor
)
instructions.insertBefore(lastInitInsn, invokespecial)
instructions.insert(invokespecial, putstatic)
}
}
private inner class CW : ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES) {
override fun getCommonSuperClass(type1: String, type2: String): String {
var c: Class<*> = loadClass(type1)
val d: Class<*> = loadClass(type2)
if (c.isAssignableFrom(d)) return type1
if (d.isAssignableFrom(c)) return type2
return if (c.isInterface || d.isInterface) {
"java/lang/Object"
} else {
do {
c = c.superclass
} while (!c.isAssignableFrom(d))
c.name.replace('.', '/')
}
}
}
private fun loadClass(type: String): Class<*> =
try {
Class.forName(type.replace('/', '.'), false, classPathLoader)
} catch (e: Exception) {
throw TransformerException("Failed to load class for '$type'", e)
}
}
fun main(args: Array<String>) {
if (args.size !in 1..3) {
println("Usage: AtomicFUTransformerKt <dir> [<output>] [<variant>]")
return
}
val t = AtomicFUTransformer(emptyList(), File(args[0]))
if (args.size > 1) t.outputDir = File(args[1])
if (args.size > 2) t.variant = enumValueOf(args[2].toUpperCase(Locale.US))
t.verbose = true
t.transform()
}