aboutsummaryrefslogtreecommitdiff
path: root/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
diff options
context:
space:
mode:
Diffstat (limited to 'atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt')
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt200
1 files changed, 123 insertions, 77 deletions
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
index c7c262c..a138422 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
@@ -91,7 +91,6 @@ private val TRACE_APPEND_3 = MethodId(TRACE_BASE_CLS, "append", getMethodDescrip
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)
@@ -126,7 +125,7 @@ private inline fun code(mv: MethodVisitor, block: InstructionAdapter.() -> Unit)
}
private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
- val node = MethodNode(ASM5)
+ val node = MethodNode(ASM9)
block(InstructionAdapter(node))
return node.instructions
}
@@ -169,13 +168,13 @@ class FieldInfo(
override fun toString(): String = "${owner.prettyStr()}::$name"
}
-enum class Variant { FU, VH, BOTH }
+enum class JvmVariant { FU, VH, BOTH }
class AtomicFUTransformer(
classpath: List<String>,
inputDir: File,
outputDir: File = inputDir,
- var variant: Variant = Variant.FU
+ var jvmVariant: JvmVariant = JvmVariant.FU
) : AtomicFUTransformerBase(inputDir, outputDir) {
private val classPathLoader = URLClassLoader(
@@ -188,6 +187,7 @@ class AtomicFUTransformer(
private val traceFields = mutableSetOf<FieldId>()
private val traceAccessors = mutableSetOf<MethodId>()
private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
+ private val delegatedPropertiesAccessors = mutableMapOf<FieldId, MethodId>()
private val removeMethods = mutableSetOf<MethodId>()
override fun transform() {
@@ -195,7 +195,7 @@ class AtomicFUTransformer(
val files = inputDir.walk().filter { it.isFile }.toList()
val needTransform = analyzeFilesForFields(files)
if (needTransform || outputDir == inputDir) {
- val vh = variant == Variant.VH
+ val vh = jvmVariant == JvmVariant.VH
// visit method bodies for external references to fields, runs all logic, fails if anything is wrong
val needsTransform = analyzeFilesForRefs(files, vh)
// perform transformation
@@ -205,7 +205,7 @@ class AtomicFUTransformer(
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) {
+ if (jvmVariant == JvmVariant.BOTH && outBytes !== bytes) {
val vhBytes = transformFile(file, bytes, true)
val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
vhFile.mkdirsAndWrite(vhBytes)
@@ -280,7 +280,7 @@ class AtomicFUTransformer(
return cw.toByteArray() // write transformed bytes
}
- private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM5, cv) {
+ private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM9, cv) {
lateinit var className: String
override fun visit(
@@ -341,6 +341,10 @@ class AtomicFUTransformer(
// check for copying atomic values into delegate fields and register potential delegate fields
return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
}
+ // collect accessors of potential delegated properties
+ if (methodType.argumentTypes.isEmpty()) {
+ return DelegatedFieldAccessorCollectorMV(className, methodType.returnType, access, name, desc, signature, exceptions)
+ }
return null
}
}
@@ -348,7 +352,7 @@ class AtomicFUTransformer(
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) {
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
override fun visitEnd() {
val insns = instructions.listUseful(4)
if (insns.size == 3 &&
@@ -396,10 +400,47 @@ class AtomicFUTransformer(
}
}
- private inner class DelegateFieldsCollectorMV(
+ private inner class DelegatedFieldAccessorCollectorMV(
+ private val className: String, private val returnType: Type,
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
+ // check for pattern of a delegated property getter
+ // getfield/getstatic a$delegate: Atomic*
+ // astore_i ...
+ // aload_i
+ // invokevirtual Atomic*.getValue()
+ // ireturn
+ var cur = instructions.first
+ while (cur != null && !(cur.isGetFieldOrGetStatic() && getType((cur as FieldInsnNode).desc) in AFU_TYPES)) {
+ cur = cur.next
+ }
+ if (cur != null && cur.next.opcode == ASTORE) {
+ val fi = cur as FieldInsnNode
+ val fieldDelegate = FieldId(className, fi.name, fi.desc)
+ val atomicType = getType(fi.desc)
+ val v = (cur.next as VarInsnNode).`var`
+ while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
+ cur = cur.next
+ }
+ val invokeVirtual = cur.next
+ if (invokeVirtual.opcode == INVOKEVIRTUAL && (invokeVirtual as MethodInsnNode).name == GET_VALUE && invokeVirtual.owner == atomicType.internalName) {
+ // followed by RETURN operation
+ val next = invokeVirtual.nextUseful
+ val ret = if (next?.opcode == CHECKCAST) next.nextUseful else next
+ if (ret != null && ret.isTypeReturn(returnType)) {
+ // register delegated property accessor
+ delegatedPropertiesAccessors[fieldDelegate] = MethodId(className, name, desc, accessToInvokeOpcode(access))
+ }
+ }
+ }
+ }
+ }
+
+ private inner class DelegateFieldsCollectorMV(
+ access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
+ override fun visitEnd() {
// register delegate field and the corresponding original atomic field
// getfield a: *Atomic
// putfield a$delegate: *Atomic
@@ -408,7 +449,7 @@ class AtomicFUTransformer(
insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
val next = insn.next
(next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
- if (delegateFieldId.name.endsWith(DELEGATE)) {
+ if (getfieldId in fields && delegateFieldId in fields) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fields[getfieldId]!!
fieldDelegates[delegateFieldId] = originalField
@@ -420,11 +461,12 @@ class AtomicFUTransformer(
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)) {
+ val fieldType = getType(insn.desc).returnType
+ if (fieldType in AFU_TYPES) {
+ val isStatic = insn.nextUseful!!.opcode == PUTSTATIC
// 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)
+ fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType, isStatic)
}
}
}
@@ -447,6 +489,8 @@ class AtomicFUTransformer(
return if (fieldId in fields) fieldId else null
}
+ private fun FieldId.isFieldDelegate() = this in fieldDelegates && delegatedPropertiesAccessors.contains(this)
+
private inner class TransformerCV(
cv: ClassVisitor?,
private val vh: Boolean,
@@ -460,7 +504,7 @@ class AtomicFUTransformer(
private var originalClinit: MethodNode? = null
private var newClinit: MethodNode? = null
- private fun newClinit() = MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null)
+ private fun newClinit() = MethodNode(ASM9, ACC_STATIC, "<clinit>", "()V", null, null)
fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
override fun visitSource(source: String?, debug: String?) {
@@ -478,8 +522,8 @@ class AtomicFUTransformer(
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)) {
+ // skip field delegates except volatile delegated properties (e.g. val a: Int by atomic(0))
+ if (fieldId.isFieldDelegate() && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
transformed = true
return null
}
@@ -607,7 +651,7 @@ class AtomicFUTransformer(
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)
+ val node = MethodNode(ASM9, access, name, desc, signature, exceptions)
if (originalClinit != null) abort("Multiple <clinit> methods found")
originalClinit = node
node
@@ -673,7 +717,7 @@ class AtomicFUTransformer(
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) {
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
init {
this.mv = mv
}
@@ -715,14 +759,19 @@ class AtomicFUTransformer(
i = i.next
hasErrors = true
}
+ // make sure all kotlinx/atomicfu references removed
+ removeAtomicReferencesFromLVT()
// save transformed method if not in analysis phase
if (!hasErrors && !analyzePhase2)
accept(mv)
}
+ private fun removeAtomicReferencesFromLVT() =
+ localVariables?.removeIf { getType(it.desc) in AFU_TYPES }
+
private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
val fieldId = FieldId(owner, name, desc)
- if (fieldId in fieldDelegates) {
+ if (fieldId.isFieldDelegate()) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fieldDelegates[fieldId]!!
val getField = previous as FieldInsnNode
@@ -748,51 +797,29 @@ class AtomicFUTransformer(
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
+ if (!onArrayElement) return getPureTypeField(ld, f, iv)
+ 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 {
- 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
+ // 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) {
@@ -855,6 +882,29 @@ class AtomicFUTransformer(
return iv.next
}
+ private fun getPureTypeField(ld: FieldInsnNode, f: FieldInfo, iv: MethodInsnNode): AbstractInsnNode? {
+ 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 getfield/getstatic of the atomic field
+ 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
+ }
+
private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
val methodType = getMethodType(iv.desc)
val args = methodType.argumentTypes
@@ -1305,7 +1355,7 @@ class AtomicFUTransformer(
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) {
+ if (fieldId.isFieldDelegate() && i.next.opcode == ASTORE) {
return transformDelegatedFieldAccessor(i, fieldId)
}
(i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
@@ -1338,29 +1388,25 @@ class AtomicFUTransformer(
return i.next
}
- private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode {
+ 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
+ val v = (i.next as VarInsnNode).`var`
+ // remove instructions [astore_v .. aload_v]
+ var cur: AbstractInsnNode = i.next
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)
+ val iv = FlowAnalyzer(cur.next).execute()
+ check(iv.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
+ val isGetter = (iv as MethodInsnNode).name == GET_VALUE
+ instructions.remove(cur) // remove aload_v
localVariables.removeIf {
!(getType(it.desc).internalName == f.owner ||
(!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
}
- return j.next
+ return getPureTypeField(i, f, iv)
}
private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
@@ -1466,7 +1512,7 @@ fun main(args: Array<String>) {
}
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))
+ if (args.size > 2) t.jvmVariant = enumValueOf(args[2].toUpperCase(Locale.US))
t.verbose = true
t.transform()
}