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
6 changed files with 48 additions and 18 deletions
Showing only changes of commit d631c048c7 - Show all commits

View File

@ -38,7 +38,9 @@ This subproject implements the following features:
> ```
>
## Dynamic Expression Code Generation with ObjectWeb ASM
## Dynamic expression code generation
### On JVM
`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.
@ -55,19 +57,20 @@ RealField.mstInField { symbol("x") + 2 }.compile()
package kscience.kmath.asm.generated;
import java.util.Map;
import kotlin.jvm.functions.Function2;
import kscience.kmath.asm.internal.MapIntrinsics;
import kscience.kmath.expressions.Expression;
import kscience.kmath.operations.RealField;
import kscience.kmath.expressions.Symbol;
public final class AsmCompiledExpression_1073786867_0 implements Expression<Double> {
private final RealField algebra;
public final class AsmCompiledExpression_45045_0 implements Expression<Double> {
private final Object[] constants;
public final Double invoke(Map<String, ? extends Double> arguments) {
return (Double)this.algebra.add(((Double)MapIntrinsics.getOrFail(arguments, "x")).doubleValue(), 2.0D);
public final Double invoke(Map<Symbol, Double> arguments) {
return (Double)((Function2)this.constants[0]).invoke((Double)MapIntrinsics.getOrFail(arguments, "x"), 2);
}
public AsmCompiledExpression_1073786867_0(RealField algebra) {
this.algebra = algebra;
public AsmCompiledExpression_45045_0(Object[] constants) {
this.constants = constants;
}
}
@ -82,10 +85,30 @@ RealField.mstInField { symbol("x") + 2 }.compile()
RealField.expression("x+2".parseMath())
```
### Known issues
#### Known issues
- 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).
### On JS
A similar feature is also available on JS.
```kotlin
RealField.mstInField { symbol("x") + 2 }.compile()
```
The code above returns expression implemented with such a JS function:
```js
var executable = function (constants, arguments) {
return constants[1](constants[0](arguments, "x"), 2);
};
```
#### Known issues
- This feature uses `eval` which can be unavailable in several environments.

View File

@ -27,7 +27,9 @@ public object MstSpace : Space<MST>, NumericAlgebra<MST> {
public override fun add(a: MST, b: MST): MST.Binary = binaryOperation(SpaceOperations.PLUS_OPERATION)(a, b)
public override operator fun MST.unaryPlus(): MST.Unary = unaryOperation(SpaceOperations.PLUS_OPERATION)(this)
public override operator fun MST.unaryMinus(): MST.Unary = unaryOperation(SpaceOperations.MINUS_OPERATION)(this)
public override operator fun MST.minus(b: MST): MST.Binary = binaryOperation(SpaceOperations.MINUS_OPERATION)(this, b)
public override operator fun MST.minus(b: MST): MST.Binary =
binaryOperation(SpaceOperations.MINUS_OPERATION)(this, b)
public override fun multiply(a: MST, k: Number): MST.Binary =
binaryOperation(RingOperations.TIMES_OPERATION)(a, number(k))

View File

@ -2,13 +2,16 @@ package kscience.kmath.asm.internal
import kscience.kmath.asm.internal.AsmBuilder.ClassLoader
import kscience.kmath.ast.MST
import kscience.kmath.ast.mstInField
import kscience.kmath.expressions.Expression
import kscience.kmath.operations.RealField
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
import org.objectweb.asm.Type.*
import org.objectweb.asm.commons.InstructionAdapter
import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType
import java.lang.reflect.Modifier
import java.util.stream.Collectors.toMap
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@ -82,7 +85,7 @@ internal class AsmBuilder<T>(
ACC_PUBLIC or ACC_FINAL,
"invoke",
getMethodDescriptor(tType, MAP_TYPE),
"(L${MAP_TYPE.internalName}<${STRING_TYPE.descriptor}+${tType.descriptor}>;)${tType.descriptor}",
"(L${MAP_TYPE.internalName}<${SYMBOL_TYPE.descriptor}${if (Modifier.isFinal(classOfT.modifiers)) "" else "+"}${tType.descriptor}>;)${tType.descriptor}",
null,
).instructionAdapter {
invokeMethodVisitor = this
@ -159,7 +162,7 @@ internal class AsmBuilder<T>(
"<init>",
getMethodDescriptor(VOID_TYPE, *OBJECT_ARRAY_TYPE.wrapToArrayIf { hasConstants }),
null,
null
null,
).instructionAdapter {
val l0 = label()
load(0, classType)
@ -190,6 +193,7 @@ internal class AsmBuilder<T>(
}
val cls = classLoader.defineClass(className, classWriter.toByteArray())
java.io.File("dump.class").writeBytes(classWriter.toByteArray())
val l = MethodHandles.publicLookup()
if (hasConstants)
@ -334,5 +338,10 @@ internal class AsmBuilder<T>(
* ASM type for MapIntrinsics.
*/
val MAP_INTRINSICS_TYPE: Type by lazy { getObjectType("kscience/kmath/asm/internal/MapIntrinsics") }
/**
* ASM Type for [kscience.kmath.expressions.Symbol].
*/
val SYMBOL_TYPE: Type by lazy { getObjectType("kscience/kmath/expressions/Symbol") }
}
}

View File

@ -2,8 +2,6 @@ package kscience.kmath.ast
import kscience.kmath.asm.compile
import kscience.kmath.asm.expression
import kscience.kmath.ast.mstInField
import kscience.kmath.ast.parseMath
import kscience.kmath.expressions.invoke
import kscience.kmath.operations.Complex
import kscience.kmath.operations.ComplexField

View File

@ -1,7 +1,5 @@
package kscience.kmath.ast
import kscience.kmath.ast.evaluate
import kscience.kmath.ast.parseMath
import kscience.kmath.operations.Field
import kscience.kmath.operations.RealField
import kotlin.test.Test

View File

@ -275,12 +275,12 @@ public interface SpaceOperations<T> : Algebra<T> {
public companion object {
/**
* The identifier of addition and unary positive.
* The identifier of addition and unary positive operator.
*/
public const val PLUS_OPERATION: String = "+"
/**
* The identifier of subtraction and unary negation.
* The identifier of subtraction and unary negative operator.
*/
public const val MINUS_OPERATION: String = "-"
}