Delete AsmCompiledExpression abstract class, implement dynamic field generation to reduce quantity of cast instructions, minor refactor and renaming of internal APIs

This commit is contained in:
Commander Tvis 2020-06-25 10:07:36 +07:00
parent 7ddab0224a
commit e47ec1aeb9
No known key found for this signature in database
GPG Key ID: 70D5F4DCB0972F1B
8 changed files with 140 additions and 147 deletions

View File

@ -24,20 +24,20 @@ For example, the following builder:
package scientifik.kmath.asm.generated;
import java.util.Map;
import scientifik.kmath.asm.internal.AsmCompiledExpression;
import scientifik.kmath.operations.Algebra;
import scientifik.kmath.expressions.Expression;
import scientifik.kmath.operations.RealField;
// The class's name is build with MST's hash-code and collision fixing number.
public final class AsmCompiledExpression_45045_0 extends AsmCompiledExpression<Double> {
// Plain constructor
public AsmCompiledExpression_45045_0(Algebra algebra, Object[] constants) {
super(algebra, constants);
public final class AsmCompiledExpression_1073786867_0 implements Expression<Double> {
private final RealField algebra;
private final Object[] constants;
public AsmCompiledExpression_1073786867_0(RealField algebra, Object[] constants) {
this.algebra = algebra;
this.constants = constants;
}
// The actual dynamic code:
public final Double invoke(Map<String, ? extends Double> arguments) {
return (Double)((RealField)super.algebra).add((Double)arguments.get("x"), (Double)2.0D);
return (Double)this.algebra.add(((Double)arguments.get("x")).doubleValue(), 2.0D);
}
}
```

View File

@ -6,6 +6,7 @@ import org.objectweb.asm.Opcodes.RETURN
import org.objectweb.asm.commons.InstructionAdapter
import scientifik.kmath.asm.internal.AsmBuilder.ClassLoader
import scientifik.kmath.ast.MST
import scientifik.kmath.expressions.Expression
import scientifik.kmath.operations.Algebra
import java.util.*
import kotlin.reflect.KClass
@ -36,32 +37,27 @@ internal class AsmBuilder<T> internal constructor(
*/
private val classLoader: ClassLoader = ClassLoader(javaClass.classLoader)
@Suppress("PrivatePropertyName")
private val T_ALGEBRA_TYPE: Type = algebra::class.asm
@Suppress("PrivatePropertyName")
internal val T_TYPE: Type = classOfT.asm
@Suppress("PrivatePropertyName")
private val CLASS_TYPE: Type = Type.getObjectType(className.replace(oldChar = '.', newChar = '/'))!!
private val tAlgebraType: Type = algebra::class.asm
internal val tType: Type = classOfT.asm
private val classType: Type = Type.getObjectType(className.replace(oldChar = '.', newChar = '/'))!!
/**
* Index of `this` variable in invoke method of [AsmCompiledExpression] built subclass.
* Index of `this` variable in invoke method of the built subclass.
*/
private val invokeThisVar: Int = 0
/**
* Index of `arguments` variable in invoke method of [AsmCompiledExpression] built subclass.
* Index of `arguments` variable in invoke method of the built subclass.
*/
private val invokeArgumentsVar: Int = 1
/**
* List of constants to provide to [AsmCompiledExpression] subclass.
* List of constants to provide to the subclass.
*/
private val constants: MutableList<Any> = mutableListOf()
/**
* Method visitor of `invoke` method of [AsmCompiledExpression] subclass.
* Method visitor of `invoke` method of the subclass.
*/
private lateinit var invokeMethodVisitor: InstructionAdapter
internal var primitiveMode = false
@ -72,78 +68,92 @@ internal class AsmBuilder<T> internal constructor(
@Suppress("PropertyName")
internal var PRIMITIVE_MASK_BOXED: Type = OBJECT_TYPE
private val typeStack = Stack<Type>()
internal val expectationStack = Stack<Type>().apply { push(T_TYPE) }
internal val expectationStack: Stack<Type> = Stack<Type>().apply { push(tType) }
/**
* The cache of [AsmCompiledExpression] subclass built by this builder.
* The cache for instance built by this builder.
*/
private var generatedInstance: AsmCompiledExpression<T>? = null
private var generatedInstance: Expression<T>? = null
/**
* Subclasses, loads and instantiates the [AsmCompiledExpression] for given parameters.
* Subclasses, loads and instantiates [Expression] for given parameters.
*
* The built instance is cached.
*/
@Suppress("UNCHECKED_CAST")
fun getInstance(): AsmCompiledExpression<T> {
fun getInstance(): Expression<T> {
generatedInstance?.let { return it }
if (SIGNATURE_LETTERS.containsKey(classOfT.java)) {
if (SIGNATURE_LETTERS.containsKey(classOfT)) {
primitiveMode = true
PRIMITIVE_MASK = SIGNATURE_LETTERS.getValue(classOfT.java)
PRIMITIVE_MASK_BOXED = T_TYPE
PRIMITIVE_MASK = SIGNATURE_LETTERS.getValue(classOfT)
PRIMITIVE_MASK_BOXED = tType
}
val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) {
visit(
Opcodes.V1_8,
Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL or Opcodes.ACC_SUPER,
CLASS_TYPE.internalName,
"L${ASM_COMPILED_EXPRESSION_TYPE.internalName}<${T_TYPE.descriptor}>;",
ASM_COMPILED_EXPRESSION_TYPE.internalName,
arrayOf()
classType.internalName,
"${OBJECT_TYPE.descriptor}L${EXPRESSION_TYPE.internalName}<${tType.descriptor}>;",
OBJECT_TYPE.internalName,
arrayOf(EXPRESSION_TYPE.internalName)
)
visitField(
access = Opcodes.ACC_PRIVATE or Opcodes.ACC_FINAL,
name = "algebra",
descriptor = tAlgebraType.descriptor,
signature = null,
value = null,
block = FieldVisitor::visitEnd
)
visitField(
access = Opcodes.ACC_PRIVATE or Opcodes.ACC_FINAL,
name = "constants",
descriptor = OBJECT_ARRAY_TYPE.descriptor,
signature = null,
value = null,
block = FieldVisitor::visitEnd
)
visitMethod(
Opcodes.ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, ALGEBRA_TYPE, OBJECT_ARRAY_TYPE),
Type.getMethodDescriptor(Type.VOID_TYPE, tAlgebraType, OBJECT_ARRAY_TYPE),
null,
null
).instructionAdapter {
val thisVar = 0
val algebraVar = 1
val constantsVar = 2
val l0 = Label()
visitLabel(l0)
load(thisVar, CLASS_TYPE)
load(algebraVar, ALGEBRA_TYPE)
val l0 = label()
load(thisVar, classType)
invokespecial(OBJECT_TYPE.internalName, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false)
label()
load(thisVar, classType)
load(algebraVar, tAlgebraType)
putfield(classType.internalName, "algebra", tAlgebraType.descriptor)
label()
load(thisVar, classType)
load(constantsVar, OBJECT_ARRAY_TYPE)
invokespecial(
ASM_COMPILED_EXPRESSION_TYPE.internalName,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, ALGEBRA_TYPE, OBJECT_ARRAY_TYPE),
false
)
val l1 = Label()
visitLabel(l1)
putfield(classType.internalName, "constants", OBJECT_ARRAY_TYPE.descriptor)
label()
visitInsn(RETURN)
val l2 = Label()
visitLabel(l2)
visitLocalVariable("this", CLASS_TYPE.descriptor, null, l0, l2, thisVar)
val l4 = label()
visitLocalVariable("this", classType.descriptor, null, l0, l4, thisVar)
visitLocalVariable(
"algebra",
ALGEBRA_TYPE.descriptor,
"L${ALGEBRA_TYPE.internalName}<${T_TYPE.descriptor}>;",
tAlgebraType.descriptor,
null,
l0,
l2,
l4,
algebraVar
)
visitLocalVariable("constants", OBJECT_ARRAY_TYPE.descriptor, null, l0, l2, constantsVar)
visitLocalVariable("constants", OBJECT_ARRAY_TYPE.descriptor, null, l0, l4, constantsVar)
visitMaxs(0, 3)
visitEnd()
}
@ -151,22 +161,20 @@ internal class AsmBuilder<T> internal constructor(
visitMethod(
Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL,
"invoke",
Type.getMethodDescriptor(T_TYPE, MAP_TYPE),
"(L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${T_TYPE.descriptor}>;)${T_TYPE.descriptor}",
Type.getMethodDescriptor(tType, MAP_TYPE),
"(L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${tType.descriptor}>;)${tType.descriptor}",
null
).instructionAdapter {
invokeMethodVisitor = this
visitCode()
val l0 = Label()
visitLabel(l0)
val l0 = label()
invokeLabel0Visitor()
areturn(T_TYPE)
val l1 = Label()
visitLabel(l1)
areturn(tType)
val l1 = label()
visitLocalVariable(
"this",
CLASS_TYPE.descriptor,
classType.descriptor,
null,
l0,
l1,
@ -176,7 +184,7 @@ internal class AsmBuilder<T> internal constructor(
visitLocalVariable(
"arguments",
MAP_TYPE.descriptor,
"L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${T_TYPE.descriptor}>;",
"L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${tType.descriptor}>;",
l0,
l1,
invokeArgumentsVar
@ -196,18 +204,16 @@ internal class AsmBuilder<T> internal constructor(
val thisVar = 0
val argumentsVar = 1
visitCode()
val l0 = Label()
visitLabel(l0)
val l0 = label()
load(thisVar, OBJECT_TYPE)
load(argumentsVar, MAP_TYPE)
invokevirtual(CLASS_TYPE.internalName, "invoke", Type.getMethodDescriptor(T_TYPE, MAP_TYPE), false)
areturn(T_TYPE)
val l1 = Label()
visitLabel(l1)
invokevirtual(classType.internalName, "invoke", Type.getMethodDescriptor(tType, MAP_TYPE), false)
areturn(tType)
val l1 = label()
visitLocalVariable(
"this",
CLASS_TYPE.descriptor,
classType.descriptor,
null,
l0,
l1,
@ -225,7 +231,7 @@ internal class AsmBuilder<T> internal constructor(
.defineClass(className, classWriter.toByteArray())
.constructors
.first()
.newInstance(algebra, constants.toTypedArray()) as AsmCompiledExpression<T>
.newInstance(algebra, constants.toTypedArray()) as Expression<T>
generatedInstance = new
return new
@ -235,21 +241,21 @@ internal class AsmBuilder<T> internal constructor(
* Loads a constant from
*/
internal fun loadTConstant(value: T) {
if (classOfT.java in INLINABLE_NUMBERS) {
if (classOfT in INLINABLE_NUMBERS) {
val expectedType = expectationStack.pop()!!
val mustBeBoxed = expectedType.sort == Type.OBJECT
loadNumberConstant(value as Number, mustBeBoxed)
if (mustBeBoxed) typeStack.push(T_TYPE) else typeStack.push(PRIMITIVE_MASK)
if (mustBeBoxed) typeStack.push(tType) else typeStack.push(PRIMITIVE_MASK)
return
}
loadConstant(value as Any, T_TYPE)
loadConstant(value as Any, tType)
}
private fun box(): Unit = invokeMethodVisitor.invokestatic(
T_TYPE.internalName,
tType.internalName,
"valueOf",
Type.getMethodDescriptor(T_TYPE, PRIMITIVE_MASK),
Type.getMethodDescriptor(tType, PRIMITIVE_MASK),
false
)
@ -263,16 +269,16 @@ internal class AsmBuilder<T> internal constructor(
private fun loadConstant(value: Any, type: Type): Unit = invokeMethodVisitor.run {
val idx = if (value in constants) constants.indexOf(value) else constants.apply { add(value) }.lastIndex
loadThis()
getfield(CLASS_TYPE.internalName, "constants", OBJECT_ARRAY_TYPE.descriptor)
getfield(classType.internalName, "constants", OBJECT_ARRAY_TYPE.descriptor)
iconst(idx)
visitInsn(AALOAD)
checkcast(type)
}
private fun loadThis(): Unit = invokeMethodVisitor.load(invokeThisVar, CLASS_TYPE)
private fun loadThis(): Unit = invokeMethodVisitor.load(invokeThisVar, classType)
/**
* Either loads a numeric constant [value] from [AsmCompiledExpression] constants field or boxes a primitive
* Either loads a numeric constant [value] from the class's constants field or boxes a primitive
* constant from the constant pool (some numbers with special opcodes like [Opcodes.ICONST_0] aren't even loaded
* from it).
*/
@ -292,7 +298,7 @@ internal class AsmBuilder<T> internal constructor(
if (mustBeBoxed) {
box()
invokeMethodVisitor.checkcast(T_TYPE)
invokeMethodVisitor.checkcast(tType)
}
return
@ -300,11 +306,11 @@ internal class AsmBuilder<T> internal constructor(
loadConstant(value, boxed)
if (!mustBeBoxed) unbox()
else invokeMethodVisitor.checkcast(T_TYPE)
else invokeMethodVisitor.checkcast(tType)
}
/**
* Loads a variable [name] from [AsmCompiledExpression.invoke] [Map] parameter. The [defaultValue] may be provided.
* Loads a variable [name] arguments [Map] parameter of [Expression.invoke]. The [defaultValue] may be provided.
*/
internal fun loadVariable(name: String, defaultValue: T? = null): Unit = invokeMethodVisitor.run {
load(invokeArgumentsVar, OBJECT_ARRAY_TYPE)
@ -319,7 +325,7 @@ internal class AsmBuilder<T> internal constructor(
Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE)
)
invokeMethodVisitor.checkcast(T_TYPE)
invokeMethodVisitor.checkcast(tType)
return
}
@ -331,11 +337,11 @@ internal class AsmBuilder<T> internal constructor(
Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE)
)
invokeMethodVisitor.checkcast(T_TYPE)
invokeMethodVisitor.checkcast(tType)
val expectedType = expectationStack.pop()!!
if (expectedType.sort == Type.OBJECT)
typeStack.push(T_TYPE)
typeStack.push(tType)
else {
unbox()
typeStack.push(PRIMITIVE_MASK)
@ -343,15 +349,11 @@ internal class AsmBuilder<T> internal constructor(
}
/**
* Loads algebra from according field of [AsmCompiledExpression] and casts it to class of [algebra] provided.
* Loads algebra from according field of the class and casts it to class of [algebra] provided.
*/
internal fun loadAlgebra() {
loadThis()
invokeMethodVisitor.run {
getfield(ASM_COMPILED_EXPRESSION_TYPE.internalName, "algebra", ALGEBRA_TYPE.descriptor)
checkcast(T_ALGEBRA_TYPE)
}
invokeMethodVisitor.getfield(classType.internalName, "algebra", tAlgebraType.descriptor)
}
/**
@ -368,7 +370,12 @@ internal class AsmBuilder<T> internal constructor(
tArity: Int,
opcode: Int = Opcodes.INVOKEINTERFACE
) {
repeat(tArity) { if (!typeStack.empty()) typeStack.pop() }
run loop@{
repeat(tArity) {
if (typeStack.empty()) return@loop
typeStack.pop()
}
}
invokeMethodVisitor.visitMethodInsn(
opcode,
@ -378,12 +385,12 @@ internal class AsmBuilder<T> internal constructor(
opcode == Opcodes.INVOKEINTERFACE
)
invokeMethodVisitor.checkcast(T_TYPE)
invokeMethodVisitor.checkcast(tType)
val isLastExpr = expectationStack.size == 1
val expectedType = expectationStack.pop()!!
if (expectedType.sort == Type.OBJECT || isLastExpr)
typeStack.push(T_TYPE)
typeStack.push(tType)
else {
unbox()
typeStack.push(PRIMITIVE_MASK)
@ -399,27 +406,18 @@ internal class AsmBuilder<T> internal constructor(
/**
* Maps JVM primitive numbers boxed types to their letters of JVM signature convention.
*/
private val SIGNATURE_LETTERS: Map<Class<out Any>, Type> by lazy {
private val SIGNATURE_LETTERS: Map<KClass<out Any>, Type> by lazy {
hashMapOf(
java.lang.Byte::class.java to Type.BYTE_TYPE,
java.lang.Short::class.java to Type.SHORT_TYPE,
java.lang.Integer::class.java to Type.INT_TYPE,
java.lang.Long::class.java to Type.LONG_TYPE,
java.lang.Float::class.java to Type.FLOAT_TYPE,
java.lang.Double::class.java to Type.DOUBLE_TYPE
java.lang.Byte::class to Type.BYTE_TYPE,
java.lang.Short::class to Type.SHORT_TYPE,
java.lang.Integer::class to Type.INT_TYPE,
java.lang.Long::class to Type.LONG_TYPE,
java.lang.Float::class to Type.FLOAT_TYPE,
java.lang.Double::class to Type.DOUBLE_TYPE
)
}
private val BOXED_TO_PRIMITIVES: Map<Type, Type> by lazy {
hashMapOf(
java.lang.Byte::class.asm to Type.BYTE_TYPE,
java.lang.Short::class.asm to Type.SHORT_TYPE,
java.lang.Integer::class.asm to Type.INT_TYPE,
java.lang.Long::class.asm to Type.LONG_TYPE,
java.lang.Float::class.asm to Type.FLOAT_TYPE,
java.lang.Double::class.asm to Type.DOUBLE_TYPE
)
}
private val BOXED_TO_PRIMITIVES: Map<Type, Type> by lazy { SIGNATURE_LETTERS.mapKeys { (k, _) -> k.asm } }
private val NUMBER_CONVERTER_METHODS: Map<Type, String> by lazy {
hashMapOf(
@ -435,15 +433,15 @@ internal class AsmBuilder<T> internal constructor(
/**
* Provides boxed number types values of which can be stored in JVM bytecode constant pool.
*/
private val INLINABLE_NUMBERS: Set<Class<out Any>> by lazy { SIGNATURE_LETTERS.keys }
internal val ASM_COMPILED_EXPRESSION_TYPE: Type = AsmCompiledExpression::class.asm
internal val NUMBER_TYPE: Type = java.lang.Number::class.asm
internal val MAP_TYPE: Type = java.util.Map::class.asm
internal val OBJECT_TYPE: Type = java.lang.Object::class.asm
private val INLINABLE_NUMBERS: Set<KClass<out Any>> by lazy { SIGNATURE_LETTERS.keys }
internal val EXPRESSION_TYPE: Type by lazy { Expression::class.asm }
internal val NUMBER_TYPE: Type by lazy { java.lang.Number::class.asm }
internal val MAP_TYPE: Type by lazy { java.util.Map::class.asm }
internal val OBJECT_TYPE: Type by lazy { java.lang.Object::class.asm }
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "RemoveRedundantQualifierName")
internal val OBJECT_ARRAY_TYPE: Type = Array<java.lang.Object>::class.asm
internal val ALGEBRA_TYPE: Type = Algebra::class.asm
internal val STRING_TYPE: Type = java.lang.String::class.asm
internal val OBJECT_ARRAY_TYPE: Type by lazy { Array<java.lang.Object>::class.asm }
internal val ALGEBRA_TYPE: Type by lazy { Algebra::class.asm }
internal val STRING_TYPE: Type by lazy { java.lang.String::class.asm }
}
}

View File

@ -1,18 +0,0 @@
package scientifik.kmath.asm.internal
import scientifik.kmath.expressions.Expression
import scientifik.kmath.operations.Algebra
/**
* [Expression] partial implementation to have it subclassed by actual implementations. Provides unified storage for
* objects needed to implement the expression.
*
* @property algebra the algebra to delegate calls.
* @property constants the constants array to have persistent objects to reference in [invoke].
*/
internal abstract class AsmCompiledExpression<T> internal constructor(
@JvmField protected val algebra: Algebra<T>,
@JvmField protected val constants: Array<Any>
) : Expression<T> {
abstract override fun invoke(arguments: Map<String, T>): T
}

View File

@ -1,9 +1,10 @@
package scientifik.kmath.asm.internal
import scientifik.kmath.ast.MST
import scientifik.kmath.expressions.Expression
/**
* Creates a class name for [AsmCompiledExpression] subclassed to implement [mst] provided.
* Creates a class name for [Expression] subclassed to implement [mst] provided.
*
* This methods helps to avoid collisions of class name to prevent loading several classes with the same name. If there
* is a colliding class, change [collision] parameter or leave it `0` to check existing classes recursively.

View File

@ -1,15 +1,17 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.FieldVisitor
import org.objectweb.asm.MethodVisitor
internal inline fun ClassWriter(flags: Int, block: ClassWriter.() -> Unit): ClassWriter = ClassWriter(flags).apply(block)
internal inline fun ClassWriter(flags: Int, block: ClassWriter.() -> Unit): ClassWriter =
ClassWriter(flags).apply(block)
internal inline fun ClassWriter.visitMethod(
internal inline fun ClassWriter.visitField(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array<String>?,
block: MethodVisitor.() -> Unit
): MethodVisitor = visitMethod(access, name, descriptor, signature, exceptions).apply(block)
value: Any?,
block: FieldVisitor.() -> Unit
): FieldVisitor = visitField(access, name, descriptor, signature, value).apply(block)

View File

@ -0,0 +1,10 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.Label
import org.objectweb.asm.commons.InstructionAdapter
internal fun InstructionAdapter.label(): Label {
val l = Label()
visitLabel(l)
return l
}

View File

@ -3,7 +3,7 @@ package scientifik.kmath.asm.internal
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.commons.InstructionAdapter
fun MethodVisitor.instructionAdapter(): InstructionAdapter = InstructionAdapter(this)
internal fun MethodVisitor.instructionAdapter(): InstructionAdapter = InstructionAdapter(this)
fun MethodVisitor.instructionAdapter(block: InstructionAdapter.() -> Unit): InstructionAdapter =
internal fun MethodVisitor.instructionAdapter(block: InstructionAdapter.() -> Unit): InstructionAdapter =
instructionAdapter().apply(block)

View File

@ -22,7 +22,7 @@ internal fun <T> AsmBuilder<T>.buildExpectationStack(context: Algebra<T>, name:
val aName = methodNameAdapters[name] ?: name
val hasSpecific = context.javaClass.methods.find { it.name == aName && it.parameters.size == arity } != null
val t = if (primitiveMode && hasSpecific) PRIMITIVE_MASK else T_TYPE
val t = if (primitiveMode && hasSpecific) PRIMITIVE_MASK else tType
repeat(arity) { expectationStack.push(t) }
return hasSpecific