Support plugin-defined instrinsics that can be non-typeOf operation types.

This commit is contained in:
Leonid Startsev
2022-09-13 15:08:54 +02:00
committed by Space
parent 033538161e
commit 69c00785f2
5 changed files with 89 additions and 37 deletions
@@ -66,7 +66,12 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
fun reportSuspendTypeUnsupported()
fun reportNonReifiedTypeParameterWithRecursiveBoundUnsupported(typeParameterName: Name)
fun rewritePluginDefinedOperationMarker(v: InstructionAdapter, stubConstNull: AbstractInsnNode, instructions: InsnList, type: KT): Boolean =
fun rewritePluginDefinedOperationMarker(
v: InstructionAdapter,
reifiedInsn: AbstractInsnNode,
instructions: InsnList,
type: KT
): Boolean =
false
}
@@ -170,16 +175,19 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
val kotlinType = intrinsicsSupport.toKotlinType(type)
if (when (operationKind) {
OperationKind.NEW_ARRAY -> processNewArray(insn, asmType)
OperationKind.AS -> processAs(insn, instructions, kotlinType, asmType, safe = false)
OperationKind.SAFE_AS -> processAs(insn, instructions, kotlinType, asmType, safe = true)
OperationKind.IS -> processIs(insn, instructions, kotlinType, asmType)
OperationKind.JAVA_CLASS -> processJavaClass(insn, asmType)
OperationKind.ENUM_REIFIED -> processSpecialEnumFunction(insn, instructions, asmType)
OperationKind.TYPE_OF -> processTypeOfOrPlugin(insn, instructions, type)
}
) {
var processed = if (isPluginNext(insn)) processPlugin(insn, instructions, type) else false
if (!processed) processed = when (operationKind) {
OperationKind.NEW_ARRAY -> processNewArray(insn, asmType)
OperationKind.AS -> processAs(insn, instructions, kotlinType, asmType, safe = false)
OperationKind.SAFE_AS -> processAs(insn, instructions, kotlinType, asmType, safe = true)
OperationKind.IS -> processIs(insn, instructions, kotlinType, asmType)
OperationKind.JAVA_CLASS -> processJavaClass(insn, asmType)
OperationKind.ENUM_REIFIED -> processSpecialEnumFunction(insn, instructions, asmType)
OperationKind.TYPE_OF -> processTypeOfOrPlugin(insn, instructions, type)
}
if (processed) {
instructions.remove(insn.previous.previous!!) // PUSH operation ID
instructions.remove(insn.previous!!) // PUSH type parameter
instructions.remove(insn) // INVOKESTATIC marker method
@@ -267,15 +275,7 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
type: KT
) = rewriteNextTypeInsn(insn, Opcodes.ACONST_NULL) { stubConstNull: AbstractInsnNode ->
val newMethodNode = newMethodNodeWithCorrectStackSize {
if (!isPluginNext(stubConstNull) || !intrinsicsSupport.rewritePluginDefinedOperationMarker(
it,
stubConstNull,
instructions,
type,
)
) {
typeSystem.generateTypeOf(it, type, intrinsicsSupport)
}
typeSystem.generateTypeOf(it, type, intrinsicsSupport)
}
instructions.insert(insn, newMethodNode.instructions)
@@ -285,13 +285,36 @@ class ReifiedTypeInliner<KT : KotlinTypeMarker>(
return true
}
private fun processPlugin(insn: MethodInsnNode, instructions: InsnList, type: KT): Boolean {
val reifiedInsn = insn.next ?: return false
val newMethodNode = newMethodNodeWithCorrectStackSize {
if (!intrinsicsSupport.rewritePluginDefinedOperationMarker(
it,
reifiedInsn,
instructions,
type,
)
) return false
}
instructions.insert(insn, newMethodNode.instructions)
maxStackSize = max(maxStackSize, newMethodNode.maxStack)
return true
}
/** insn: INVOKESTATIC reifiedOperationMarker
* insn.next: operation to be reified
* insn.next.next: ldc(pluginMarker)
* insn.next.next.next: INVOKESTATIC voidMagicApiCall
*/
private fun isPluginNext(insn: AbstractInsnNode): Boolean {
val magicInsn = insn.next?.next ?: return false
val magicInsn = insn.next?.next?.next ?: return false
return magicInsn is MethodInsnNode && magicInsn.opcode == Opcodes.INVOKESTATIC
&& magicInsn.owner == pluginIntrinsicsMarkerOwner
&& magicInsn.name == pluginIntrinsicsMarkerMethod
&& magicInsn.desc == pluginIntrinsicsMarkerSignature
&& insn.next is LdcInsnNode
&& magicInsn.previous is LdcInsnNode
}
private inline fun rewriteNextTypeInsn(
@@ -229,7 +229,7 @@ internal fun isAnonymousClass(internalName: String) =
fun wrapWithMaxLocalCalc(methodNode: MethodNode) =
MaxStackFrameSizeAndLocalsCalculator(Opcodes.API_VERSION, methodNode.access, methodNode.desc, methodNode)
fun newMethodNodeWithCorrectStackSize(block: (InstructionAdapter) -> Unit): MethodNode {
inline fun newMethodNodeWithCorrectStackSize(block: (InstructionAdapter) -> Unit): MethodNode {
val newMethodNode = MethodNode(Opcodes.API_VERSION, "fake", "()V", null, null)
val mv = wrapWithMaxLocalCalc(newMethodNode)
block(InstructionAdapter(mv))
@@ -127,7 +127,7 @@ class IrInlineIntrinsicsSupport(
.report(JvmBackendErrors.TYPEOF_NON_REIFIED_TYPE_PARAMETER_WITH_RECURSIVE_BOUND, typeParameterName.asString())
}
override fun rewritePluginDefinedOperationMarker(v: InstructionAdapter, stubConstNull: AbstractInsnNode, instructions: InsnList, type: IrType): Boolean {
return pluginExtensions.any { it.rewritePluginDefinedOperationMarker(v, stubConstNull, instructions, type) }
override fun rewritePluginDefinedOperationMarker(v: InstructionAdapter, reifiedInsn: AbstractInsnNode, instructions: InsnList, type: IrType): Boolean {
return pluginExtensions.any { it.rewritePluginDefinedOperationMarker(v, reifiedInsn, instructions, type) }
}
}
@@ -14,17 +14,45 @@ import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
import org.jetbrains.org.objectweb.asm.tree.InsnList
interface JvmIrIntrinsicExtension : IrIntrinsicExtension {
/**
* Returns [IntrinsicMethod] that should emit specific bytecode instead of a regular bytecode for calling [symbol],
* or `null` if [symbol]'s call should not be replaced.
*/
fun getIntrinsic(symbol: IrFunctionSymbol): IntrinsicMethod?
/**
* Should return `true` if marker was processed.
* If this method returns `false`, a regular `TYPE_OF` intrinsic would be inserted.
* Allows to process plugin-defined reified operation marker.
* Reified operation marker is an INVOKESTATIC call to IntrinsicsSupport.reifiedOperationMarker(operationType, typeVariableName) followed
* by actual operation to be reified.
* For marker to be determined as plugin-defined, another special call should be inserted directly afterwards the operation.
* This call is MagicApiIntrinsics.voidMagicApiCall(object). Object should be a string loaded by LDC instruction. Contents of the string is plugin-defined
* and recommended way to pass data to a plugin.
*
* This is plugin's responsibility to remove any calls to MagicApiIntrinsics.voidMagicApiCall and its arguments.
* This function must return `true` if marker was processed.
* If this method returns `false`, other plugins would be queried, and if others also return `false`, a regular intrinsic determined by operationType would be inserted.
*
* If marker was processed, this is plugin's responsibility to remove any calls to MagicApiIntrinsics.voidMagicApiCall and its arguments.
*
* Example of plugin-defined reified operation marker:
*
* ```
* iconst(6) // operationType=6
* aconst(T) // typeParamName=T
* invokestatic(kotlin/jvm/internal/Intrinsics.reifiedOperationMarker)
* aconst(null) // This is operation to be reified. In case of operationType=6, this is typeOf<T>().
* // A KType instance would normally be generated on stack instead of null.
* aconst("pluginDataString") // arbitrary constant string
* invokestatic(kotlin/jvm/internal/MagicApiIntrinsics.voidMagicApiCall(Ljava/lang/Object;)V) // plugin marker call
* ```
*
* Such approach with two markers was chosen mainly for compatibility reasons:
* Call to voidMagicApiCall should be inserted directly after operationType-specific instruction (aconst(null) in the example with typeOf).
* If we form bytecode this way, old compilers would be able to correctly inline normal reified operation here, even if they do not know anything about plugin reifications.
* They won't remove invokestatic(voidMagicApiCall), but it is not a problem, since kotlin-stdlib has this function, and it is a no-op.
*/
fun rewritePluginDefinedOperationMarker(
v: InstructionAdapter,
stubConstNull: AbstractInsnNode,
reifiedInsn: AbstractInsnNode,
instructions: InsnList,
type: IrType
): Boolean
@@ -181,28 +181,29 @@ class SerializationJvmIrIntrinsicSupport(val jvmBackendContext: JvmBackendContex
* 4: swap // if withModule
* 5: invokestatic(kotlinx.serialization.serializer(module?, kType)
*
* We need to remove instructions from 1 to 5
* Instructions 0, -1 -2 and -3 would be removed by inliner.
* We need to remove instructions from 0 to 5
* Instructions -1, -2 and -3 would be removed by inliner.
*/
override fun rewritePluginDefinedOperationMarker(
v: InstructionAdapter,
stubConstNull: AbstractInsnNode,
reifiedInsn: AbstractInsnNode,
instructions: InsnList,
type: IrType
): Boolean {
val operationTypeStr = (stubConstNull.next as LdcInsnNode).cst as String
val operationTypeStr = (reifiedInsn.next as LdcInsnNode).cst as String
if (!operationTypeStr.startsWith(magicMarkerStringPrefix)) return false
val operationType = if (operationTypeStr.endsWith("withModule")) {
val aload = stubConstNull.next.next.next as VarInsnNode
val aload = reifiedInsn.next.next.next as VarInsnNode
val storedVar = aload.`var`
instructions.remove(aload.next)
instructions.remove(aload)
IntrinsicType.WithModule(storedVar)
} else IntrinsicType.Simple
// Remove other instructions
instructions.remove(stubConstNull.next.next.next)
instructions.remove(stubConstNull.next.next)
instructions.remove(stubConstNull.next)
instructions.remove(reifiedInsn.next.next.next)
instructions.remove(reifiedInsn.next.next)
instructions.remove(reifiedInsn.next)
instructions.remove(reifiedInsn)
// generate serializer
generateSerializerForType(type, v, operationType)
return true