Merge remote-tracking branch 'mipt-npm/adv-expr' into adv-expr-lconst

This commit is contained in:
Iaroslav 2020-06-23 03:08:25 +07:00
commit 7c7065542a
No known key found for this signature in database
GPG Key ID: 46E15E4A31B3BCD7
4 changed files with 78 additions and 11 deletions

View File

@ -1,4 +1,61 @@
# AST based expression representation and operations # AST-based expression representation and operations (`kmath-ast`)
This subproject implements the following features:
- 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.
## Dynamic expression code generation
Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis). Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis).

View File

@ -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
/** /**
@ -79,3 +79,7 @@ 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()
}

View File

@ -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.
@ -346,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",

View File

@ -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('.', '/')