diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt index 337219029..649d06277 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt @@ -11,9 +11,12 @@ import scientifik.kmath.operations.Algebra * ASM Builder is a structure that abstracts building a class that unwraps [AsmExpression] to plain Java expression. * This class uses [ClassLoader] for loading the generated class, then it is able to instantiate the new class. * - * @param T the type of AsmExpression to unwrap. - * @param algebra the algebra the applied AsmExpressions use. - * @param className the unique class name of new loaded class. + * @param T the type generated [AsmCompiledExpression] operates. + * @property classOfT the [Class] of T. + * @property algebra the algebra the applied AsmExpressions use. + * @property className the unique class name of new loaded class. + * @property invokeLabel0Visitor the function applied to this object when the L0 label of invoke implementation is + * being written. */ internal class AsmBuilder internal constructor( private val classOfT: Class<*>, @@ -21,10 +24,16 @@ internal class AsmBuilder internal constructor( private val className: String, private val invokeLabel0Visitor: AsmBuilder.() -> Unit ) { + /** + * Internal classloader of [AsmBuilder] with alias to define class from byte array. + */ private class ClassLoader(parent: java.lang.ClassLoader) : java.lang.ClassLoader(parent) { internal fun defineClass(name: String?, b: ByteArray): Class<*> = defineClass(name, b, 0, b.size) } + /** + * The instance of [ClassLoader] used by this builder. + */ private val classLoader: ClassLoader = ClassLoader(javaClass.classLoader) @@ -35,14 +44,39 @@ internal class AsmBuilder internal constructor( private val T_CLASS: String = classOfT.name.replace('.', '/') private val slashesClassName: String = className.replace(oldChar = '.', newChar = '/') + + /** + * Index of `this` variable in invoke method of [AsmCompiledExpression] built subclass. + */ private val invokeThisVar: Int = 0 + + /** + * Index of `arguments` variable in invoke method of [AsmCompiledExpression] built subclass. + */ private val invokeArgumentsVar: Int = 1 + + /** + * List of constants to provide to [AsmCompiledExpression] subclass. + */ private val constants: MutableList = mutableListOf() + + /** + * Method visitor of `invoke` method of [AsmCompiledExpression] subclass. + */ private lateinit var invokeMethodVisitor: MethodVisitor + + /** + * The cache of [AsmCompiledExpression] subclass built by this builder. + */ private var generatedInstance: AsmCompiledExpression? = null + /** + * Subclasses, loads and instantiates the [AsmCompiledExpression] for given parameters. + * + * The built instance is cached. + */ @Suppress("UNCHECKED_CAST") - fun getInstance(): AsmCompiledExpression { + internal fun getInstance(): AsmCompiledExpression { generatedInstance?.let { return it } val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) { @@ -181,6 +215,9 @@ internal class AsmBuilder internal constructor( return new } + /** + * Loads a constant from + */ internal fun loadTConstant(value: T) { if (classOfT in INLINABLE_NUMBERS) { loadNumberConstant(value as Number) @@ -191,6 +228,9 @@ internal class AsmBuilder internal constructor( loadConstant(value as Any, T_CLASS) } + /** + * Loads an object constant [value] stored in [AsmCompiledExpression.constants] and casts it to [type]. + */ private fun loadConstant(value: Any, type: String) { val idx = if (value in constants) constants.indexOf(value) else constants.apply { add(value) }.lastIndex @@ -205,7 +245,12 @@ internal class AsmBuilder internal constructor( private fun loadThis(): Unit = invokeMethodVisitor.visitLoadObjectVar(invokeThisVar) - internal fun loadNumberConstant(value: Number) { + /** + * Either loads a numeric constant [value] from [AsmCompiledExpression.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). + */ + private fun loadNumberConstant(value: Number) { val clazz = value.javaClass val c = clazz.name.replace('.', '/') val sigLetter = SIGNATURE_LETTERS[clazz] @@ -225,6 +270,9 @@ internal class AsmBuilder internal constructor( loadConstant(value, c) } + /** + * Loads a variable [name] from [AsmCompiledExpression.invoke] [Map] parameter. The [defaultValue] may be provided. + */ internal fun loadVariable(name: String, defaultValue: T? = null): Unit = invokeMethodVisitor.run { visitLoadObjectVar(invokeArgumentsVar) @@ -253,6 +301,9 @@ internal class AsmBuilder internal constructor( invokeMethodVisitor.visitCheckCast(T_CLASS) } + /** + * Loads algebra from according field of [AsmCompiledExpression] and casts it to class of [algebra] provided. + */ internal fun loadAlgebra() { loadThis() @@ -265,20 +316,32 @@ internal class AsmBuilder internal constructor( invokeMethodVisitor.visitCheckCast(T_ALGEBRA_CLASS) } + /** + * Writes a method instruction of opcode with its [owner], [method] and its [descriptor]. The default opcode is + * [Opcodes.INVOKEINTERFACE], since most Algebra functions are declared in interface. [loadAlgebra] should be + * called before the arguments and this operation. + * + * The result is casted to [T] automatically. + */ internal fun invokeAlgebraOperation( owner: String, method: String, descriptor: String, - opcode: Int = Opcodes.INVOKEINTERFACE, - isInterface: Boolean = true + opcode: Int = Opcodes.INVOKEINTERFACE ) { - invokeMethodVisitor.visitMethodInsn(opcode, owner, method, descriptor, isInterface) + invokeMethodVisitor.visitMethodInsn(opcode, owner, method, descriptor, opcode == Opcodes.INVOKEINTERFACE) invokeMethodVisitor.visitCheckCast(T_CLASS) } + /** + * Writes a LDC Instruction with string constant provided. + */ internal fun loadStringConstant(string: String): Unit = invokeMethodVisitor.visitLdcInsn(string) internal companion object { + /** + * Maps JVM primitive numbers boxed types to their letters of JVM signature convention. + */ private val SIGNATURE_LETTERS: Map, String> by lazy { mapOf( java.lang.Byte::class.java to "B", @@ -290,6 +353,9 @@ internal class AsmBuilder internal constructor( ) } + /** + * Provides boxed number types values of which can be stored in JVM bytecode constant pool. + */ private val INLINABLE_NUMBERS: Set> by lazy { SIGNATURE_LETTERS.keys } internal const val FUNCTIONAL_COMPILED_EXPRESSION_CLASS = diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/optimization.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/optimization.kt index a39dba6b1..889f3accb 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/optimization.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/optimization.kt @@ -33,8 +33,7 @@ internal fun AsmBuilder.tryInvokeSpecific(context: Algebra, name: Stri owner = owner, method = aName, descriptor = sig, - opcode = Opcodes.INVOKEVIRTUAL, - isInterface = false + opcode = Opcodes.INVOKEVIRTUAL ) return true