Provide dynamic operations currying for Algebra<T> instead of eager calls and add JS code generation support #162

Merged
CommanderTvis merged 44 commits from feature/dynamic-ops-currying into dev 2021-01-05 16:36:51 +03:00
2 changed files with 53 additions and 78 deletions
Showing only changes of commit cc45e3683b - Show all commits

View File

@ -6,6 +6,7 @@ import kscience.kmath.expressions.Expression
import kscience.kmath.operations.Algebra
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.commons.InstructionAdapter
import java.util.stream.Collectors.toMap
import kotlin.contracts.InvocationKind
@ -17,13 +18,13 @@ import kotlin.contracts.contract
*
* @property T the type of AsmExpression to unwrap.
* @property className the unique class name of new loaded class.
* @property invokeLabel0Visitor the function to apply to this object when generating invoke method, label 0.
* @property callbackAtInvokeL0 the function to apply to this object when generating invoke method, label 0.
* @author Iaroslav Postovalov
*/
internal class AsmBuilder<T> internal constructor(
internal class AsmBuilder<T>(
classOfT: Class<*>,
private val className: String,
private val invokeLabel0Visitor: AsmBuilder<T>.() -> Unit,
private val callbackAtInvokeL0: AsmBuilder<T>.() -> Unit,
) {
/**
* Internal classloader of [AsmBuilder] with alias to define class from byte array.
@ -45,7 +46,7 @@ internal class AsmBuilder<T> internal constructor(
/**
* ASM type for new class.
*/
private val classType: Type = Type.getObjectType(className.replace(oldChar = '.', newChar = '/'))!!
private val classType: Type = getObjectType(className.replace(oldChar = '.', newChar = '/'))
/**
* List of constants to provide to the subclass.
@ -57,11 +58,6 @@ internal class AsmBuilder<T> internal constructor(
*/
private lateinit var invokeMethodVisitor: InstructionAdapter
/**
* States whether this [AsmBuilder] needs to generate constants field.
*/
private var hasConstants: Boolean = true
/**
* Subclasses, loads and instantiates [Expression] for given parameters.
*
@ -69,6 +65,8 @@ internal class AsmBuilder<T> internal constructor(
*/
@Suppress("UNCHECKED_CAST")
val instance: Expression<T> by lazy {
val hasConstants: Boolean
val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) {
visit(
V1_8,
@ -82,14 +80,14 @@ internal class AsmBuilder<T> internal constructor(
visitMethod(
ACC_PUBLIC or ACC_FINAL,
"invoke",
Type.getMethodDescriptor(tType, MAP_TYPE),
getMethodDescriptor(tType, MAP_TYPE),
"(L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${tType.descriptor}>;)${tType.descriptor}",
null
).instructionAdapter {
invokeMethodVisitor = this
visitCode()
val l0 = label()
invokeLabel0Visitor()
callbackAtInvokeL0()
areturn(tType)
val l1 = label()
@ -99,7 +97,7 @@ internal class AsmBuilder<T> internal constructor(
null,
l0,
l1,
invokeThisVar
0
)
visitLocalVariable(
@ -108,7 +106,7 @@ internal class AsmBuilder<T> internal constructor(
"L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${tType.descriptor}>;",
l0,
l1,
invokeArgumentsVar
1
)
visitMaxs(0, 2)
@ -118,7 +116,7 @@ internal class AsmBuilder<T> internal constructor(
visitMethod(
ACC_PUBLIC or ACC_FINAL or ACC_BRIDGE or ACC_SYNTHETIC,
"invoke",
Type.getMethodDescriptor(OBJECT_TYPE, MAP_TYPE),
getMethodDescriptor(OBJECT_TYPE, MAP_TYPE),
null,
null
).instructionAdapter {
@ -128,7 +126,7 @@ internal class AsmBuilder<T> internal constructor(
val l0 = label()
load(thisVar, OBJECT_TYPE)
load(argumentsVar, MAP_TYPE)
invokevirtual(classType.internalName, "invoke", Type.getMethodDescriptor(tType, MAP_TYPE), false)
invokevirtual(classType.internalName, "invoke", getMethodDescriptor(tType, MAP_TYPE), false)
areturn(tType)
val l1 = label()
@ -160,32 +158,30 @@ internal class AsmBuilder<T> internal constructor(
visitMethod(
ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, *OBJECT_ARRAY_TYPE.wrapToArrayIf { hasConstants }),
getMethodDescriptor(VOID_TYPE, *OBJECT_ARRAY_TYPE.wrapToArrayIf { hasConstants }),
null,
null
).instructionAdapter {
val thisVar = 0
val constantsVar = 1
val l0 = label()
load(thisVar, classType)
invokespecial(OBJECT_TYPE.internalName, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false)
load(0, classType)
invokespecial(OBJECT_TYPE.internalName, "<init>", getMethodDescriptor(VOID_TYPE), false)
label()
load(thisVar, classType)
load(0, classType)
if (hasConstants) {
label()
load(thisVar, classType)
load(constantsVar, OBJECT_ARRAY_TYPE)
load(0, classType)
load(1, OBJECT_ARRAY_TYPE)
putfield(classType.internalName, "constants", OBJECT_ARRAY_TYPE.descriptor)
}
label()
visitInsn(RETURN)
val l4 = label()
visitLocalVariable("this", classType.descriptor, null, l0, l4, thisVar)
visitLocalVariable("this", classType.descriptor, null, l0, l4, 0)
if (hasConstants)
visitLocalVariable("constants", OBJECT_ARRAY_TYPE.descriptor, null, l0, l4, constantsVar)
visitLocalVariable("constants", OBJECT_ARRAY_TYPE.descriptor, null, l0, l4, 1)
visitMaxs(0, 3)
visitEnd()
@ -218,7 +214,7 @@ internal class AsmBuilder<T> internal constructor(
/**
* Loads `this` variable.
*/
private fun loadThis(): Unit = invokeMethodVisitor.load(invokeThisVar, classType)
private fun loadThis(): Unit = invokeMethodVisitor.load(0, classType)
/**
* Either loads a numeric constant [value] from the class's constants field or boxes a primitive
@ -230,12 +226,12 @@ internal class AsmBuilder<T> internal constructor(
if (primitive != null) {
when (primitive) {
Type.BYTE_TYPE -> invokeMethodVisitor.iconst(value.toInt())
Type.DOUBLE_TYPE -> invokeMethodVisitor.dconst(value.toDouble())
Type.FLOAT_TYPE -> invokeMethodVisitor.fconst(value.toFloat())
Type.LONG_TYPE -> invokeMethodVisitor.lconst(value.toLong())
Type.INT_TYPE -> invokeMethodVisitor.iconst(value.toInt())
Type.SHORT_TYPE -> invokeMethodVisitor.iconst(value.toInt())
BYTE_TYPE -> invokeMethodVisitor.iconst(value.toInt())
DOUBLE_TYPE -> invokeMethodVisitor.dconst(value.toDouble())
FLOAT_TYPE -> invokeMethodVisitor.fconst(value.toFloat())
LONG_TYPE -> invokeMethodVisitor.lconst(value.toLong())
INT_TYPE -> invokeMethodVisitor.iconst(value.toInt())
SHORT_TYPE -> invokeMethodVisitor.iconst(value.toInt())
}
box(primitive)
@ -254,7 +250,7 @@ internal class AsmBuilder<T> internal constructor(
invokeMethodVisitor.invokestatic(
r.internalName,
"valueOf",
Type.getMethodDescriptor(r, primitive),
getMethodDescriptor(r, primitive),
false
)
}
@ -263,13 +259,13 @@ internal class AsmBuilder<T> internal constructor(
* Loads a variable [name] from arguments [Map] parameter of [Expression.invoke].
*/
fun loadVariable(name: String): Unit = invokeMethodVisitor.run {
load(invokeArgumentsVar, MAP_TYPE)
load(1, MAP_TYPE)
aconst(name)
invokestatic(
MAP_INTRINSICS_TYPE.internalName,
"getOrFail",
Type.getMethodDescriptor(OBJECT_TYPE, MAP_TYPE, STRING_TYPE),
getMethodDescriptor(OBJECT_TYPE, MAP_TYPE, STRING_TYPE),
false
)
@ -283,100 +279,81 @@ internal class AsmBuilder<T> internal constructor(
val arity = `interface`.methods.find { it.name == "invoke" }?.parameterCount
?: error("Provided function object doesn't contain invoke method")
val type = Type.getType(`interface`)
val type = getType(`interface`)
loadObjectConstant(function, type)
parameters(this)
invokeMethodVisitor.invokeinterface(
type.internalName,
"invoke",
Type.getMethodDescriptor(OBJECT_TYPE, *Array(arity) { OBJECT_TYPE}),
getMethodDescriptor(OBJECT_TYPE, *Array(arity) { OBJECT_TYPE }),
)
invokeMethodVisitor.checkcast(tType)
}
internal companion object {
/**
* Index of `this` variable in invoke method of the built subclass.
*/
private const val invokeThisVar: Int = 0
/**
* Index of `arguments` variable in invoke method of the built subclass.
*/
private const val invokeArgumentsVar: Int = 1
/**
* Maps JVM primitive numbers boxed types to their primitive ASM types.
*/
private val SIGNATURE_LETTERS: Map<Class<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
)
}
companion object {
/**
* Maps JVM primitive numbers boxed ASM types to their primitive ASM types.
*/
private val BOXED_TO_PRIMITIVES: Map<Type, Type> by lazy { SIGNATURE_LETTERS.mapKeys { (k, _) -> k.asm } }
private val BOXED_TO_PRIMITIVES: Map<Type, Type> by lazy {
hashMapOf(
Byte::class.java.asm to BYTE_TYPE,
Short::class.java.asm to SHORT_TYPE,
Integer::class.java.asm to INT_TYPE,
Long::class.java.asm to LONG_TYPE,
Float::class.java.asm to FLOAT_TYPE,
Double::class.java.asm to DOUBLE_TYPE,
)
}
/**
* Maps JVM primitive numbers boxed ASM types to their primitive ASM types.
*/
private val PRIMITIVES_TO_BOXED: Map<Type, Type> by lazy {
BOXED_TO_PRIMITIVES.entries.stream().collect(
toMap(
Map.Entry<Type, Type>::value,
Map.Entry<Type, Type>::key
)
toMap(Map.Entry<Type, Type>::value, Map.Entry<Type, Type>::key),
)
}
/**
* ASM type for [Expression].
*/
internal val EXPRESSION_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/expressions/Expression") }
val EXPRESSION_TYPE: Type by lazy { getObjectType("kscience/kmath/expressions/Expression") }
/**
* ASM type for [java.lang.Number].
*/
internal val NUMBER_TYPE: Type by lazy { Type.getObjectType("java/lang/Number") }
val NUMBER_TYPE: Type by lazy { getObjectType("java/lang/Number") }
/**
* ASM type for [java.util.Map].
*/
internal val MAP_TYPE: Type by lazy { Type.getObjectType("java/util/Map") }
val MAP_TYPE: Type by lazy { getObjectType("java/util/Map") }
/**
* ASM type for [java.lang.Object].
*/
internal val OBJECT_TYPE: Type by lazy { Type.getObjectType("java/lang/Object") }
val OBJECT_TYPE: Type by lazy { getObjectType("java/lang/Object") }
/**
* ASM type for array of [java.lang.Object].
*/
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "RemoveRedundantQualifierName")
internal val OBJECT_ARRAY_TYPE: Type by lazy { Type.getType("[Ljava/lang/Object;") }
val OBJECT_ARRAY_TYPE: Type by lazy { getType("[Ljava/lang/Object;") }
/**
* ASM type for [Algebra].
*/
internal val ALGEBRA_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/operations/Algebra") }
val ALGEBRA_TYPE: Type by lazy { getObjectType("kscience/kmath/operations/Algebra") }
/**
* ASM type for [java.lang.String].
*/
internal val STRING_TYPE: Type by lazy { Type.getObjectType("java/lang/String") }
val STRING_TYPE: Type by lazy { getObjectType("java/lang/String") }
/**
* ASM type for MapIntrinsics.
*/
internal val MAP_INTRINSICS_TYPE: Type by lazy { Type.getObjectType("kscience/kmath/asm/internal/MapIntrinsics") }
val MAP_INTRINSICS_TYPE: Type by lazy { getObjectType("kscience/kmath/asm/internal/MapIntrinsics") }
}
}

View File

@ -91,5 +91,3 @@ internal inline fun ClassWriter.visitField(
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return visitField(access, name, descriptor, signature, value).apply(block)
}