Merge remote-tracking branch 'origin/adv-expr' into adv-expr-improved-trigonometry

This commit is contained in:
Iaroslav 2020-06-24 21:21:39 +07:00
commit 7ddab0224a
No known key found for this signature in database
GPG Key ID: 46E15E4A31B3BCD7
10 changed files with 408 additions and 305 deletions

View File

@ -1,14 +1,15 @@
package scientifik.kmath.asm
import org.objectweb.asm.Type
import scientifik.kmath.asm.internal.AsmBuilder
import scientifik.kmath.asm.internal.buildExpectationStack
import scientifik.kmath.asm.internal.buildName
import scientifik.kmath.asm.internal.hasSpecific
import scientifik.kmath.asm.internal.tryInvokeSpecific
import scientifik.kmath.ast.*
import scientifik.kmath.ast.MST
import scientifik.kmath.ast.MSTExpression
import scientifik.kmath.expressions.Expression
import scientifik.kmath.operations.Algebra
import scientifik.kmath.operations.NumericAlgebra
import scientifik.kmath.operations.RealField
import kotlin.reflect.KClass
/**
@ -18,6 +19,7 @@ fun <T : Any> MST.compileWith(type: KClass<T>, algebra: Algebra<T>): Expression<
fun AsmBuilder<T>.visit(node: MST) {
when (node) {
is MST.Symbolic -> loadVariable(node.value)
is MST.Numeric -> {
val constant = if (algebra is NumericAlgebra<T>)
algebra.number(node.value)
@ -26,48 +28,49 @@ fun <T : Any> MST.compileWith(type: KClass<T>, algebra: Algebra<T>): Expression<
loadTConstant(constant)
}
is MST.Unary -> {
loadAlgebra()
if (!hasSpecific(algebra, node.operation, 1)) loadStringConstant(node.operation)
if (!buildExpectationStack(algebra, node.operation, 1)) loadStringConstant(node.operation)
visit(node.value)
if (!tryInvokeSpecific(algebra, node.operation, 1)) {
invokeAlgebraOperation(
owner = AsmBuilder.ALGEBRA_CLASS,
if (!tryInvokeSpecific(algebra, node.operation, 1)) invokeAlgebraOperation(
owner = AsmBuilder.ALGEBRA_TYPE.internalName,
method = "unaryOperation",
descriptor = "(L${AsmBuilder.STRING_CLASS};" +
"L${AsmBuilder.OBJECT_CLASS};)" +
"L${AsmBuilder.OBJECT_CLASS};"
descriptor = Type.getMethodDescriptor(
AsmBuilder.OBJECT_TYPE,
AsmBuilder.STRING_TYPE,
AsmBuilder.OBJECT_TYPE
),
tArity = 1
)
}
}
is MST.Binary -> {
loadAlgebra()
if (!hasSpecific(algebra, node.operation, 2))
loadStringConstant(node.operation)
if (!buildExpectationStack(algebra, node.operation, 2)) loadStringConstant(node.operation)
visit(node.left)
visit(node.right)
if (!tryInvokeSpecific(algebra, node.operation, 2)) {
invokeAlgebraOperation(
owner = AsmBuilder.ALGEBRA_CLASS,
if (!tryInvokeSpecific(algebra, node.operation, 2)) invokeAlgebraOperation(
owner = AsmBuilder.ALGEBRA_TYPE.internalName,
method = "binaryOperation",
descriptor = "(L${AsmBuilder.STRING_CLASS};" +
"L${AsmBuilder.OBJECT_CLASS};" +
"L${AsmBuilder.OBJECT_CLASS};)" +
"L${AsmBuilder.OBJECT_CLASS};"
descriptor = Type.getMethodDescriptor(
AsmBuilder.OBJECT_TYPE,
AsmBuilder.STRING_TYPE,
AsmBuilder.OBJECT_TYPE,
AsmBuilder.OBJECT_TYPE
),
tArity = 2
)
}
}
}
}
return AsmBuilder(type.java, algebra, buildName(this)) { visit(this@compileWith) }.getInstance()
return AsmBuilder(type, algebra, buildName(this)) { visit(this@compileWith) }.getInstance()
}
/**
@ -79,7 +82,3 @@ inline fun <reified T : Any> Algebra<T>.expression(mst: MST): Expression<T> = ms
* Optimize performance of an [MSTExpression] using ASM codegen
*/
inline fun <reified T : Any> MSTExpression<T>.compile(): Expression<T> = mst.compileWith(T::class, algebra)
fun main() {
RealField.mstInField { symbol("x") + 2 }.compile()
}

View File

@ -1,25 +1,25 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.AALOAD
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.operations.Algebra
import java.util.*
import kotlin.reflect.KClass
/**
* ASM Builder is a structure that abstracts building a class for unwrapping [MST] to plain Java expression.
* ASM Builder is a structure that abstracts building a class designated to unwrap [MST] 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 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.
* @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.
*/
internal class AsmBuilder<T> internal constructor(
private val classOfT: Class<*>,
private val classOfT: KClass<*>,
private val algebra: Algebra<T>,
private val className: String,
private val invokeLabel0Visitor: AsmBuilder<T>.() -> Unit
@ -34,16 +34,16 @@ internal class AsmBuilder<T> internal constructor(
/**
* The instance of [ClassLoader] used by this builder.
*/
private val classLoader: ClassLoader =
ClassLoader(javaClass.classLoader)
private val classLoader: ClassLoader = ClassLoader(javaClass.classLoader)
@Suppress("PrivatePropertyName")
private val T_ALGEBRA_CLASS: String = algebra.javaClass.name.replace(oldChar = '.', newChar = '/')
private val T_ALGEBRA_TYPE: Type = algebra::class.asm
@Suppress("PrivatePropertyName")
private val T_CLASS: String = classOfT.name.replace('.', '/')
internal val T_TYPE: Type = classOfT.asm
private val slashesClassName: String = className.replace(oldChar = '.', newChar = '/')
@Suppress("PrivatePropertyName")
private val CLASS_TYPE: Type = Type.getObjectType(className.replace(oldChar = '.', newChar = '/'))!!
/**
* Index of `this` variable in invoke method of [AsmCompiledExpression] built subclass.
@ -63,7 +63,16 @@ internal class AsmBuilder<T> internal constructor(
/**
* Method visitor of `invoke` method of [AsmCompiledExpression] subclass.
*/
private lateinit var invokeMethodVisitor: MethodVisitor
private lateinit var invokeMethodVisitor: InstructionAdapter
internal var primitiveMode = false
@Suppress("PropertyName")
internal var PRIMITIVE_MASK: Type = OBJECT_TYPE
@Suppress("PropertyName")
internal var PRIMITIVE_MASK_BOXED: Type = OBJECT_TYPE
private val typeStack = Stack<Type>()
internal val expectationStack = Stack<Type>().apply { push(T_TYPE) }
/**
* The cache of [AsmCompiledExpression] subclass built by this builder.
@ -76,81 +85,88 @@ internal class AsmBuilder<T> internal constructor(
* The built instance is cached.
*/
@Suppress("UNCHECKED_CAST")
internal fun getInstance(): AsmCompiledExpression<T> {
fun getInstance(): AsmCompiledExpression<T> {
generatedInstance?.let { return it }
if (SIGNATURE_LETTERS.containsKey(classOfT.java)) {
primitiveMode = true
PRIMITIVE_MASK = SIGNATURE_LETTERS.getValue(classOfT.java)
PRIMITIVE_MASK_BOXED = T_TYPE
}
val classWriter = ClassWriter(ClassWriter.COMPUTE_FRAMES) {
visit(
Opcodes.V1_8,
Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL or Opcodes.ACC_SUPER,
slashesClassName,
"L$FUNCTIONAL_COMPILED_EXPRESSION_CLASS<L$T_CLASS;>;",
FUNCTIONAL_COMPILED_EXPRESSION_CLASS,
CLASS_TYPE.internalName,
"L${ASM_COMPILED_EXPRESSION_TYPE.internalName}<${T_TYPE.descriptor}>;",
ASM_COMPILED_EXPRESSION_TYPE.internalName,
arrayOf()
)
visitMethod(
access = Opcodes.ACC_PUBLIC,
name = "<init>",
descriptor = "(L$ALGEBRA_CLASS;[L$OBJECT_CLASS;)V",
signature = null,
exceptions = null
) {
Opcodes.ACC_PUBLIC,
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, ALGEBRA_TYPE, OBJECT_ARRAY_TYPE),
null,
null
).instructionAdapter {
val thisVar = 0
val algebraVar = 1
val constantsVar = 2
val l0 = Label()
visitLabel(l0)
visitLoadObjectVar(thisVar)
visitLoadObjectVar(algebraVar)
visitLoadObjectVar(constantsVar)
load(thisVar, CLASS_TYPE)
load(algebraVar, ALGEBRA_TYPE)
load(constantsVar, OBJECT_ARRAY_TYPE)
visitInvokeSpecial(
FUNCTIONAL_COMPILED_EXPRESSION_CLASS,
invokespecial(
ASM_COMPILED_EXPRESSION_TYPE.internalName,
"<init>",
"(L$ALGEBRA_CLASS;[L$OBJECT_CLASS;)V"
Type.getMethodDescriptor(Type.VOID_TYPE, ALGEBRA_TYPE, OBJECT_ARRAY_TYPE),
false
)
val l1 = Label()
visitLabel(l1)
visitReturn()
visitInsn(RETURN)
val l2 = Label()
visitLabel(l2)
visitLocalVariable("this", "L$slashesClassName;", null, l0, l2, thisVar)
visitLocalVariable("this", CLASS_TYPE.descriptor, null, l0, l2, thisVar)
visitLocalVariable(
"algebra",
"L$ALGEBRA_CLASS;",
"L$ALGEBRA_CLASS<L$T_CLASS;>;",
ALGEBRA_TYPE.descriptor,
"L${ALGEBRA_TYPE.internalName}<${T_TYPE.descriptor}>;",
l0,
l2,
algebraVar
)
visitLocalVariable("constants", "[L$OBJECT_CLASS;", null, l0, l2, constantsVar)
visitLocalVariable("constants", OBJECT_ARRAY_TYPE.descriptor, null, l0, l2, constantsVar)
visitMaxs(0, 3)
visitEnd()
}
visitMethod(
access = Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL,
name = "invoke",
descriptor = "(L$MAP_CLASS;)L$T_CLASS;",
signature = "(L$MAP_CLASS<L$STRING_CLASS;+L$T_CLASS;>;)L$T_CLASS;",
exceptions = null
) {
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}",
null
).instructionAdapter {
invokeMethodVisitor = this
visitCode()
val l0 = Label()
visitLabel(l0)
invokeLabel0Visitor()
visitReturnObject()
areturn(T_TYPE)
val l1 = Label()
visitLabel(l1)
visitLocalVariable(
"this",
"L$slashesClassName;",
CLASS_TYPE.descriptor,
null,
l0,
l1,
@ -159,8 +175,8 @@ internal class AsmBuilder<T> internal constructor(
visitLocalVariable(
"arguments",
"L$MAP_CLASS;",
"L$MAP_CLASS<L$STRING_CLASS;+L$T_CLASS;>;",
MAP_TYPE.descriptor,
"L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${T_TYPE.descriptor}>;",
l0,
l1,
invokeArgumentsVar
@ -171,28 +187,28 @@ internal class AsmBuilder<T> internal constructor(
}
visitMethod(
access = Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL or Opcodes.ACC_BRIDGE or Opcodes.ACC_SYNTHETIC,
name = "invoke",
descriptor = "(L$MAP_CLASS;)L$OBJECT_CLASS;",
signature = null,
exceptions = null
) {
Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL or Opcodes.ACC_BRIDGE or Opcodes.ACC_SYNTHETIC,
"invoke",
Type.getMethodDescriptor(OBJECT_TYPE, MAP_TYPE),
null,
null
).instructionAdapter {
val thisVar = 0
val argumentsVar = 1
visitCode()
val l0 = Label()
visitLabel(l0)
visitLoadObjectVar(thisVar)
visitLoadObjectVar(argumentsVar)
visitInvokeVirtual(owner = slashesClassName, name = "invoke", descriptor = "(L$MAP_CLASS;)L$T_CLASS;")
visitReturnObject()
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)
visitLocalVariable(
"this",
"L$slashesClassName;",
T_CLASS,
CLASS_TYPE.descriptor,
null,
l0,
l1,
thisVar
@ -219,89 +235,111 @@ internal class AsmBuilder<T> internal constructor(
* Loads a constant from
*/
internal fun loadTConstant(value: T) {
if (classOfT in INLINABLE_NUMBERS) {
loadNumberConstant(value as Number)
invokeMethodVisitor.visitCheckCast(T_CLASS)
if (classOfT.java 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)
return
}
loadConstant(value as Any, T_CLASS)
loadConstant(value as Any, T_TYPE)
}
/**
* Loads an object constant [value] stored in [AsmCompiledExpression.constants] and casts it to [type].
*/
private fun loadConstant(value: Any, type: String) {
private fun box(): Unit = invokeMethodVisitor.invokestatic(
T_TYPE.internalName,
"valueOf",
Type.getMethodDescriptor(T_TYPE, PRIMITIVE_MASK),
false
)
private fun unbox(): Unit = invokeMethodVisitor.invokevirtual(
NUMBER_TYPE.internalName,
NUMBER_CONVERTER_METHODS.getValue(PRIMITIVE_MASK),
Type.getMethodDescriptor(PRIMITIVE_MASK),
false
)
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
invokeMethodVisitor.run {
loadThis()
visitGetField(owner = slashesClassName, name = "constants", descriptor = "[L$OBJECT_CLASS;")
visitLdcOrIntConstant(idx)
visitGetObjectArrayElement()
invokeMethodVisitor.visitCheckCast(type)
}
getfield(CLASS_TYPE.internalName, "constants", OBJECT_ARRAY_TYPE.descriptor)
iconst(idx)
visitInsn(AALOAD)
checkcast(type)
}
private fun loadThis(): Unit = invokeMethodVisitor.visitLoadObjectVar(invokeThisVar)
private fun loadThis(): Unit = invokeMethodVisitor.load(invokeThisVar, CLASS_TYPE)
/**
* Either loads a numeric constant [value] from [AsmCompiledExpression.constants] field or boxes a primitive
* 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]
private fun loadNumberConstant(value: Number, mustBeBoxed: Boolean) {
val boxed = value::class.asm
val primitive = BOXED_TO_PRIMITIVES[boxed]
if (sigLetter != null) {
when (value) {
is Byte -> invokeMethodVisitor.visitLdcOrIntConstant(value.toInt())
is Short -> invokeMethodVisitor.visitLdcOrIntConstant(value.toInt())
is Int -> invokeMethodVisitor.visitLdcOrIntConstant(value)
is Double -> invokeMethodVisitor.visitLdcOrDoubleConstant(value)
is Float -> invokeMethodVisitor.visitLdcOrFloatConstant(value)
is Long -> invokeMethodVisitor.visitLdcOrLongConstant(value)
else -> invokeMethodVisitor.visitLdcInsn(value)
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())
}
if (mustBeBoxed) {
box()
invokeMethodVisitor.checkcast(T_TYPE)
}
invokeMethodVisitor.visitInvokeStatic(c, "valueOf", "($sigLetter)L${c};")
return
}
loadConstant(value, c)
loadConstant(value, boxed)
if (!mustBeBoxed) unbox()
else invokeMethodVisitor.checkcast(T_TYPE)
}
/**
* 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)
load(invokeArgumentsVar, OBJECT_ARRAY_TYPE)
if (defaultValue != null) {
visitLdcInsn(name)
loadStringConstant(name)
loadTConstant(defaultValue)
visitInvokeInterface(
owner = MAP_CLASS,
name = "getOrDefault",
descriptor = "(L$OBJECT_CLASS;L$OBJECT_CLASS;)L$OBJECT_CLASS;"
invokeinterface(
MAP_TYPE.internalName,
"getOrDefault",
Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE)
)
invokeMethodVisitor.visitCheckCast(T_CLASS)
invokeMethodVisitor.checkcast(T_TYPE)
return
}
visitLdcInsn(name)
loadStringConstant(name)
visitInvokeInterface(
owner = MAP_CLASS,
name = "get",
descriptor = "(L$OBJECT_CLASS;)L$OBJECT_CLASS;"
invokeinterface(
MAP_TYPE.internalName,
"get",
Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE)
)
invokeMethodVisitor.visitCheckCast(T_CLASS)
invokeMethodVisitor.checkcast(T_TYPE)
val expectedType = expectationStack.pop()!!
if (expectedType.sort == Type.OBJECT)
typeStack.push(T_TYPE)
else {
unbox()
typeStack.push(PRIMITIVE_MASK)
}
}
/**
@ -310,13 +348,10 @@ internal class AsmBuilder<T> internal constructor(
internal fun loadAlgebra() {
loadThis()
invokeMethodVisitor.visitGetField(
owner = FUNCTIONAL_COMPILED_EXPRESSION_CLASS,
name = "algebra",
descriptor = "L$ALGEBRA_CLASS;"
)
invokeMethodVisitor.visitCheckCast(T_ALGEBRA_CLASS)
invokeMethodVisitor.run {
getfield(ASM_COMPILED_EXPRESSION_TYPE.internalName, "algebra", ALGEBRA_TYPE.descriptor)
checkcast(T_ALGEBRA_TYPE)
}
}
/**
@ -330,29 +365,70 @@ internal class AsmBuilder<T> internal constructor(
owner: String,
method: String,
descriptor: String,
tArity: Int,
opcode: Int = Opcodes.INVOKEINTERFACE
) {
invokeMethodVisitor.visitMethodInsn(opcode, owner, method, descriptor, opcode == Opcodes.INVOKEINTERFACE)
invokeMethodVisitor.visitCheckCast(T_CLASS)
repeat(tArity) { if (!typeStack.empty()) typeStack.pop() }
invokeMethodVisitor.visitMethodInsn(
opcode,
owner,
method,
descriptor,
opcode == Opcodes.INVOKEINTERFACE
)
invokeMethodVisitor.checkcast(T_TYPE)
val isLastExpr = expectationStack.size == 1
val expectedType = expectationStack.pop()!!
if (expectedType.sort == Type.OBJECT || isLastExpr)
typeStack.push(T_TYPE)
else {
unbox()
typeStack.push(PRIMITIVE_MASK)
}
}
/**
* Writes a LDC Instruction with string constant provided.
*/
internal fun loadStringConstant(string: String): Unit = invokeMethodVisitor.visitLdcInsn(string)
internal fun loadStringConstant(string: String): Unit = invokeMethodVisitor.aconst(string)
internal companion object {
/**
* Maps JVM primitive numbers boxed types to their letters of JVM signature convention.
*/
private val SIGNATURE_LETTERS: Map<Class<out Any>, String> by lazy {
private val SIGNATURE_LETTERS: Map<Class<out Any>, Type> by lazy {
hashMapOf(
java.lang.Byte::class.java to "B",
java.lang.Short::class.java to "S",
java.lang.Integer::class.java to "I",
java.lang.Long::class.java to "J",
java.lang.Float::class.java to "F",
java.lang.Double::class.java to "D"
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
)
}
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 NUMBER_CONVERTER_METHODS: Map<Type, String> by lazy {
hashMapOf(
Type.BYTE_TYPE to "byteValue",
Type.SHORT_TYPE to "shortValue",
Type.INT_TYPE to "intValue",
Type.LONG_TYPE to "longValue",
Type.FLOAT_TYPE to "floatValue",
Type.DOUBLE_TYPE to "doubleValue"
)
}
@ -360,13 +436,14 @@ 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
internal const val FUNCTIONAL_COMPILED_EXPRESSION_CLASS =
"scientifik/kmath/asm/internal/AsmCompiledExpression"
internal const val MAP_CLASS = "java/util/Map"
internal const val OBJECT_CLASS = "java/lang/Object"
internal const val ALGEBRA_CLASS = "scientifik/kmath/operations/Algebra"
internal const val STRING_CLASS = "java/lang/String"
@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
}
}

View File

@ -16,4 +16,3 @@ internal abstract class AsmCompiledExpression<T> internal constructor(
) : Expression<T> {
abstract override fun invoke(arguments: Map<String, T>): T
}

View File

@ -0,0 +1,7 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.Type
import kotlin.reflect.KClass
internal val KClass<*>.asm: Type
get() = Type.getType(java)

View File

@ -1,60 +1,9 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.commons.InstructionAdapter
internal fun MethodVisitor.visitLdcOrIntConstant(value: Int): Unit = when (value) {
-1 -> visitInsn(ICONST_M1)
0 -> visitInsn(ICONST_0)
1 -> visitInsn(ICONST_1)
2 -> visitInsn(ICONST_2)
3 -> visitInsn(ICONST_3)
4 -> visitInsn(ICONST_4)
5 -> visitInsn(ICONST_5)
in -128..127 -> visitIntInsn(BIPUSH, value)
in -32768..32767 -> visitIntInsn(SIPUSH, value)
else -> visitLdcInsn(value)
}
fun MethodVisitor.instructionAdapter(): InstructionAdapter = InstructionAdapter(this)
internal fun MethodVisitor.visitLdcOrDoubleConstant(value: Double): Unit = when (value) {
0.0 -> visitInsn(DCONST_0)
1.0 -> visitInsn(DCONST_1)
else -> visitLdcInsn(value)
}
internal fun MethodVisitor.visitLdcOrLongConstant(value: Long): Unit = when (value) {
0L -> visitInsn(LCONST_0)
1L -> visitInsn(LCONST_1)
else -> visitLdcInsn(value)
}
internal fun MethodVisitor.visitLdcOrFloatConstant(value: Float): Unit = when (value) {
0f -> visitInsn(FCONST_0)
1f -> visitInsn(FCONST_1)
2f -> visitInsn(FCONST_2)
else -> visitLdcInsn(value)
}
internal fun MethodVisitor.visitInvokeInterface(owner: String, name: String, descriptor: String): Unit =
visitMethodInsn(INVOKEINTERFACE, owner, name, descriptor, true)
internal fun MethodVisitor.visitInvokeVirtual(owner: String, name: String, descriptor: String): Unit =
visitMethodInsn(INVOKEVIRTUAL, owner, name, descriptor, false)
internal fun MethodVisitor.visitInvokeStatic(owner: String, name: String, descriptor: String): Unit =
visitMethodInsn(INVOKESTATIC, owner, name, descriptor, false)
internal fun MethodVisitor.visitInvokeSpecial(owner: String, name: String, descriptor: String): Unit =
visitMethodInsn(INVOKESPECIAL, owner, name, descriptor, false)
internal fun MethodVisitor.visitCheckCast(type: String): Unit = visitTypeInsn(CHECKCAST, type)
internal fun MethodVisitor.visitGetField(owner: String, name: String, descriptor: String): Unit =
visitFieldInsn(GETFIELD, owner, name, descriptor)
internal fun MethodVisitor.visitLoadObjectVar(`var`: Int): Unit = visitVarInsn(ALOAD, `var`)
internal fun MethodVisitor.visitGetObjectArrayElement(): Unit = visitInsn(AALOAD)
internal fun MethodVisitor.visitReturn(): Unit = visitInsn(RETURN)
internal fun MethodVisitor.visitReturnObject(): Unit = visitInsn(ARETURN)
fun MethodVisitor.instructionAdapter(block: InstructionAdapter.() -> Unit): InstructionAdapter =
instructionAdapter().apply(block)

View File

@ -1,6 +1,7 @@
package scientifik.kmath.asm.internal
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import scientifik.kmath.operations.Algebra
private val methodNameAdapters: Map<String, String> by lazy {
@ -12,17 +13,19 @@ private val methodNameAdapters: Map<String, String> by lazy {
}
/**
* Checks if the target [context] for code generation contains a method with needed [name] and [arity].
* Checks if the target [context] for code generation contains a method with needed [name] and [arity], also builds
* type expectation stack for needed arity.
*
* @return `true` if contains, else `false`.
*/
internal fun <T> hasSpecific(context: Algebra<T>, name: String, arity: Int): Boolean {
internal fun <T> AsmBuilder<T>.buildExpectationStack(context: Algebra<T>, name: String, arity: Int): Boolean {
val aName = methodNameAdapters[name] ?: name
context.javaClass.methods.find { it.name == aName && it.parameters.size == arity }
?: return false
val hasSpecific = context.javaClass.methods.find { it.name == aName && it.parameters.size == arity } != null
val t = if (primitiveMode && hasSpecific) PRIMITIVE_MASK else T_TYPE
repeat(arity) { expectationStack.push(t) }
return true
return hasSpecific
}
/**
@ -34,22 +37,23 @@ internal fun <T> hasSpecific(context: Algebra<T>, name: String, arity: Int): Boo
internal fun <T> AsmBuilder<T>.tryInvokeSpecific(context: Algebra<T>, name: String, arity: Int): Boolean {
val aName = methodNameAdapters[name] ?: name
context.javaClass.methods.find { it.name == aName && it.parameters.size == arity }
?: return false
val method =
context.javaClass.methods.find {
var suitableSignature = it.name == aName && it.parameters.size == arity
if (primitiveMode && it.isBridge)
suitableSignature = false
suitableSignature
} ?: return false
val owner = context::class.java.name.replace('.', '/')
val sig = buildString {
append('(')
repeat(arity) { append("L${AsmBuilder.OBJECT_CLASS};") }
append(')')
append("L${AsmBuilder.OBJECT_CLASS};")
}
invokeAlgebraOperation(
owner = owner,
method = aName,
descriptor = sig,
descriptor = Type.getMethodDescriptor(PRIMITIVE_MASK_BOXED, *Array(arity) { PRIMITIVE_MASK }),
tArity = arity,
opcode = Opcodes.INVOKEVIRTUAL
)

View File

@ -1,66 +1,110 @@
package scietifik.kmath.asm
//
//class TestAsmAlgebras {
// @Test
// fun space() {
// val res = ByteRing.asmInRing {
// binaryOperation(
// "+",
//
// unaryOperation(
// "+",
// 3.toByte() - (2.toByte() + (multiply(
// add(number(1), number(1)),
// 2
// ) + 1.toByte()) * 3.toByte() - 1.toByte())
// ),
//
// number(1)
// ) + symbol("x") + zero
// }("x" to 2.toByte())
//
// assertEquals(16, res)
// }
//
// @Test
// fun ring() {
// val res = ByteRing.asmInRing {
// binaryOperation(
// "+",
//
// unaryOperation(
// "+",
// (3.toByte() - (2.toByte() + (multiply(
// add(const(1), const(1)),
// 2
// ) + 1.toByte()))) * 3.0 - 1.toByte()
// ),
//
// number(1)
// ) * const(2)
// }()
//
// assertEquals(24, res)
// }
//
// @Test
// fun field() {
// val res = RealField.asmInField {
// +(3 - 2 + 2*(number(1)+1.0)
//
// unaryOperation(
// "+",
// (3.0 - (2.0 + (multiply(
// add((1.0), const(1.0)),
// 2
// ) + 1.0))) * 3 - 1.0
// )+
//
// number(1)
// ) / 2, const(2.0)) * one
// }()
//
// assertEquals(3.0, res)
// }
//}
import scientifik.kmath.asm.compile
import scientifik.kmath.ast.mstInField
import scientifik.kmath.ast.mstInRing
import scientifik.kmath.ast.mstInSpace
import scientifik.kmath.expressions.invoke
import scientifik.kmath.operations.ByteRing
import scientifik.kmath.operations.RealField
import kotlin.test.Test
import kotlin.test.assertEquals
class TestAsmAlgebras {
@Test
fun space() {
val res1 = ByteRing.mstInSpace {
binaryOperation(
"+",
unaryOperation(
"+",
number(3.toByte()) - (number(2.toByte()) + (multiply(
add(number(1), number(1)),
2
) + number(1.toByte()) * 3.toByte() - number(1.toByte())))
),
number(1)
) + symbol("x") + zero
}("x" to 2.toByte())
val res2 = ByteRing.mstInSpace {
binaryOperation(
"+",
unaryOperation(
"+",
number(3.toByte()) - (number(2.toByte()) + (multiply(
add(number(1), number(1)),
2
) + number(1.toByte()) * 3.toByte() - number(1.toByte())))
),
number(1)
) + symbol("x") + zero
}.compile()("x" to 2.toByte())
assertEquals(res1, res2)
}
@Test
fun ring() {
val res1 = ByteRing.mstInRing {
binaryOperation(
"+",
unaryOperation(
"+",
(symbol("x") - (2.toByte() + (multiply(
add(number(1), number(1)),
2
) + 1.toByte()))) * 3.0 - 1.toByte()
),
number(1)
) * number(2)
}("x" to 3.toByte())
val res2 = ByteRing.mstInRing {
binaryOperation(
"+",
unaryOperation(
"+",
(symbol("x") - (2.toByte() + (multiply(
add(number(1), number(1)),
2
) + 1.toByte()))) * 3.0 - 1.toByte()
),
number(1)
) * number(2)
}.compile()("x" to 3.toByte())
assertEquals(res1, res2)
}
@Test
fun field() {
val res1 = RealField.mstInField {
+(3 - 2 + 2 * number(1) + 1.0) + binaryOperation(
"+",
(3.0 - (symbol("x") + (multiply(add(number(1.0), number(1.0)), 2) + 1.0))) * 3 - 1.0
+ number(1),
1 / 2 + number(2.0) * one
)
}("x" to 2.0)
val res2 = RealField.mstInField {
+(3 - 2 + 2 * number(1) + 1.0) + binaryOperation(
"+",
(3.0 - (symbol("x") + (multiply(add(number(1.0), number(1.0)), 2) + 1.0))) * 3 - 1.0
+ number(1),
1 / 2 + number(2.0) * one
)
}.compile()("x" to 2.0)
assertEquals(res1, res2)
}
}

View File

@ -16,6 +16,13 @@ class TestAsmExpressions {
assertEquals(-2.0, res)
}
@Test
fun testBinaryOperationInvocation() {
val expression = RealField.mstInSpace { -symbol("x") + number(1.0) }.compile()
val res = expression("x" to 2.0)
assertEquals(-1.0, res)
}
@Test
fun testConstProductInvocation() {
val res = RealField.mstInField { symbol("x") * 2 }("x" to 2.0)

View File

@ -1,7 +1,10 @@
package scietifik.kmath.ast
import scientifik.kmath.ast.evaluate
import scientifik.kmath.asm.compile
import scientifik.kmath.asm.expression
import scientifik.kmath.ast.mstInField
import scientifik.kmath.ast.parseMath
import scientifik.kmath.expressions.invoke
import scientifik.kmath.operations.Complex
import scientifik.kmath.operations.ComplexField
import kotlin.test.Test
@ -9,9 +12,15 @@ import kotlin.test.assertEquals
class AsmTest {
@Test
fun parsedExpression() {
fun `compile MST`() {
val mst = "2+2*(2+2)".parseMath()
val res = ComplexField.evaluate(mst)
val res = ComplexField.expression(mst)()
assertEquals(Complex(10.0, 0.0), res)
}
@Test
fun `compile MSTExpression`() {
val res = ComplexField.mstInField { number(2) + number(2) * (number(2) + number(2)) }.compile()()
assertEquals(Complex(10.0, 0.0), res)
}
}

View File

@ -1,17 +1,25 @@
package scietifik.kmath.ast
import scientifik.kmath.ast.evaluate
import scientifik.kmath.ast.mstInField
import scientifik.kmath.ast.parseMath
import scientifik.kmath.expressions.invoke
import scientifik.kmath.operations.Complex
import scientifik.kmath.operations.ComplexField
import kotlin.test.assertEquals
import kotlin.test.Test
import kotlin.test.assertEquals
internal class ParserTest {
@Test
fun parsedExpression() {
fun `evaluate MST`() {
val mst = "2+2*(2+2)".parseMath()
val res = ComplexField.evaluate(mst)
assertEquals(Complex(10.0, 0.0), res)
}
@Test
fun `evaluate MSTExpression`() {
val res = ComplexField.mstInField { number(2) + number(2) * (number(2) + number(2)) }()
assertEquals(Complex(10.0, 0.0), res)
}
}