diff options
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.kt | 200 |
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() } |