Dev #127
@ -1,4 +1,61 @@
|
|||||||
# AST based expression representation and operations
|
# AST-based expression representation and operations (`kmath-ast`)
|
||||||
|
|
||||||
## Dynamic expression code generation
|
This subproject implements the following features:
|
||||||
Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis).
|
|
||||||
|
- Expression Language and its parser.
|
||||||
|
- MST as expression language's syntax intermediate representation.
|
||||||
|
- Type-safe builder of MST.
|
||||||
|
- Evaluating expressions by traversing MST.
|
||||||
|
|
||||||
|
## Dynamic expression code generation with OW2 ASM
|
||||||
|
|
||||||
|
`kmath-ast` JVM module supports runtime code generation to eliminate overhead of tree traversal. Code generator builds
|
||||||
|
a special implementation of `Expression<T>` with implemented `invoke` function.
|
||||||
|
|
||||||
|
For example, the following builder:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
RealField.mstInField { symbol("x") + 2 }.compile()
|
||||||
|
```
|
||||||
|
|
||||||
|
… leads to generation of bytecode, which can be decompiled to the following Java class:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package scientifik.kmath.asm.generated;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import scientifik.kmath.asm.internal.AsmCompiledExpression;
|
||||||
|
import scientifik.kmath.operations.Algebra;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Usage
|
||||||
|
|
||||||
|
This API is an extension to MST and MSTExpression APIs. You may optimize both MST and MSTExpression:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
RealField.mstInField { symbol("x") + 2 }.compile()
|
||||||
|
RealField.expression("2+2".parseMath())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Known issues
|
||||||
|
|
||||||
|
- Using numeric algebras causes boxing and calling bridge methods.
|
||||||
|
- The same classes may be generated and loaded twice, so it is recommended to cache compiled expressions to avoid
|
||||||
|
class loading overhead.
|
||||||
|
- This API is not supported by non-dynamic JVM implementations (like TeaVM and GraalVM) because of using class loaders.
|
||||||
|
|
||||||
|
Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis).
|
||||||
|
@ -4,11 +4,11 @@ import scientifik.kmath.asm.internal.AsmBuilder
|
|||||||
import scientifik.kmath.asm.internal.buildName
|
import scientifik.kmath.asm.internal.buildName
|
||||||
import scientifik.kmath.asm.internal.hasSpecific
|
import scientifik.kmath.asm.internal.hasSpecific
|
||||||
import scientifik.kmath.asm.internal.tryInvokeSpecific
|
import scientifik.kmath.asm.internal.tryInvokeSpecific
|
||||||
import scientifik.kmath.ast.MST
|
import scientifik.kmath.ast.*
|
||||||
import scientifik.kmath.ast.MSTExpression
|
|
||||||
import scientifik.kmath.expressions.Expression
|
import scientifik.kmath.expressions.Expression
|
||||||
import scientifik.kmath.operations.Algebra
|
import scientifik.kmath.operations.Algebra
|
||||||
import scientifik.kmath.operations.NumericAlgebra
|
import scientifik.kmath.operations.NumericAlgebra
|
||||||
|
import scientifik.kmath.operations.RealField
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -19,11 +19,11 @@ fun <T : Any> MST.compileWith(type: KClass<T>, algebra: Algebra<T>): Expression<
|
|||||||
when (node) {
|
when (node) {
|
||||||
is MST.Symbolic -> loadVariable(node.value)
|
is MST.Symbolic -> loadVariable(node.value)
|
||||||
is MST.Numeric -> {
|
is MST.Numeric -> {
|
||||||
val constant = if (algebra is NumericAlgebra<T>) {
|
val constant = if (algebra is NumericAlgebra<T>)
|
||||||
algebra.number(node.value)
|
algebra.number(node.value)
|
||||||
} else {
|
else
|
||||||
error("Number literals are not supported in $algebra")
|
error("Number literals are not supported in $algebra")
|
||||||
}
|
|
||||||
loadTConstant(constant)
|
loadTConstant(constant)
|
||||||
}
|
}
|
||||||
is MST.Unary -> {
|
is MST.Unary -> {
|
||||||
@ -78,4 +78,8 @@ inline fun <reified T : Any> Algebra<T>.expression(mst: MST): Expression<T> = ms
|
|||||||
/**
|
/**
|
||||||
* Optimize performance of an [MSTExpression] using ASM codegen
|
* Optimize performance of an [MSTExpression] using ASM codegen
|
||||||
*/
|
*/
|
||||||
inline fun <reified T : Any> MSTExpression<T>.compile(): Expression<T> = mst.compileWith(T::class, algebra)
|
inline fun <reified T : Any> MSTExpression<T>.compile(): Expression<T> = mst.compileWith(T::class, algebra)
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
RealField.mstInField { symbol("x") + 2 }.compile()
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ import scientifik.kmath.asm.internal.AsmBuilder.ClassLoader
|
|||||||
import scientifik.kmath.operations.Algebra
|
import scientifik.kmath.operations.Algebra
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ASM Builder is a structure that abstracts building a class that unwraps [AsmExpression] to plain Java expression.
|
* ASM Builder is a structure that abstracts building a class for unwrapping [MST] to plain Java expression.
|
||||||
* This class uses [ClassLoader] for loading the generated class, then it is able to instantiate the new class.
|
* 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.
|
* @param T the type generated [AsmCompiledExpression] operates.
|
||||||
@ -257,9 +257,12 @@ internal class AsmBuilder<T> internal constructor(
|
|||||||
|
|
||||||
if (sigLetter != null) {
|
if (sigLetter != null) {
|
||||||
when (value) {
|
when (value) {
|
||||||
|
is Byte -> invokeMethodVisitor.visitLdcOrIntConstant(value.toInt())
|
||||||
|
is Short -> invokeMethodVisitor.visitLdcOrIntConstant(value.toInt())
|
||||||
is Int -> invokeMethodVisitor.visitLdcOrIntConstant(value)
|
is Int -> invokeMethodVisitor.visitLdcOrIntConstant(value)
|
||||||
is Double -> invokeMethodVisitor.visitLdcOrDoubleConstant(value)
|
is Double -> invokeMethodVisitor.visitLdcOrDoubleConstant(value)
|
||||||
is Float -> invokeMethodVisitor.visitLdcOrFloatConstant(value)
|
is Float -> invokeMethodVisitor.visitLdcOrFloatConstant(value)
|
||||||
|
is Long -> invokeMethodVisitor.visitLdcOrLongConstant(value)
|
||||||
else -> invokeMethodVisitor.visitLdcInsn(value)
|
else -> invokeMethodVisitor.visitLdcInsn(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,7 +346,7 @@ internal class AsmBuilder<T> internal constructor(
|
|||||||
* Maps JVM primitive numbers boxed types to their letters of JVM signature convention.
|
* 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>, String> by lazy {
|
||||||
mapOf(
|
hashMapOf(
|
||||||
java.lang.Byte::class.java to "B",
|
java.lang.Byte::class.java to "B",
|
||||||
java.lang.Short::class.java to "S",
|
java.lang.Short::class.java to "S",
|
||||||
java.lang.Integer::class.java to "I",
|
java.lang.Integer::class.java to "I",
|
||||||
|
@ -11,6 +11,8 @@ internal fun MethodVisitor.visitLdcOrIntConstant(value: Int): Unit = when (value
|
|||||||
3 -> visitInsn(ICONST_3)
|
3 -> visitInsn(ICONST_3)
|
||||||
4 -> visitInsn(ICONST_4)
|
4 -> visitInsn(ICONST_4)
|
||||||
5 -> visitInsn(ICONST_5)
|
5 -> visitInsn(ICONST_5)
|
||||||
|
in -128..127 -> visitIntInsn(BIPUSH, value)
|
||||||
|
in -32768..32767 -> visitIntInsn(SIPUSH, value)
|
||||||
else -> visitLdcInsn(value)
|
else -> visitLdcInsn(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,6 +22,12 @@ internal fun MethodVisitor.visitLdcOrDoubleConstant(value: Double): Unit = when
|
|||||||
else -> visitLdcInsn(value)
|
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) {
|
internal fun MethodVisitor.visitLdcOrFloatConstant(value: Float): Unit = when (value) {
|
||||||
0f -> visitInsn(FCONST_0)
|
0f -> visitInsn(FCONST_0)
|
||||||
1f -> visitInsn(FCONST_1)
|
1f -> visitInsn(FCONST_1)
|
||||||
|
@ -3,7 +3,13 @@ package scientifik.kmath.asm.internal
|
|||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import scientifik.kmath.operations.Algebra
|
import scientifik.kmath.operations.Algebra
|
||||||
|
|
||||||
private val methodNameAdapters: Map<String, String> = mapOf("+" to "add", "*" to "multiply", "/" to "divide")
|
private val methodNameAdapters: Map<String, String> by lazy {
|
||||||
|
hashMapOf(
|
||||||
|
"+" to "add",
|
||||||
|
"*" to "multiply",
|
||||||
|
"/" to "divide"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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].
|
||||||
@ -13,7 +19,7 @@ private val methodNameAdapters: Map<String, String> = mapOf("+" to "add", "*" to
|
|||||||
internal fun <T> hasSpecific(context: Algebra<T>, name: String, arity: Int): Boolean {
|
internal fun <T> hasSpecific(context: Algebra<T>, name: String, arity: Int): Boolean {
|
||||||
val aName = methodNameAdapters[name] ?: name
|
val aName = methodNameAdapters[name] ?: name
|
||||||
|
|
||||||
context::class.java.methods.find { it.name == aName && it.parameters.size == arity }
|
context.javaClass.methods.find { it.name == aName && it.parameters.size == arity }
|
||||||
?: return false
|
?: return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -28,7 +34,7 @@ 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 {
|
internal fun <T> AsmBuilder<T>.tryInvokeSpecific(context: Algebra<T>, name: String, arity: Int): Boolean {
|
||||||
val aName = methodNameAdapters[name] ?: name
|
val aName = methodNameAdapters[name] ?: name
|
||||||
|
|
||||||
context::class.java.methods.find { it.name == aName && it.parameters.size == arity }
|
context.javaClass.methods.find { it.name == aName && it.parameters.size == arity }
|
||||||
?: return false
|
?: return false
|
||||||
|
|
||||||
val owner = context::class.java.name.replace('.', '/')
|
val owner = context::class.java.name.replace('.', '/')
|
||||||
|
Loading…
Reference in New Issue
Block a user