From 62ebda33021e9fc7dbf41e5d23ea57b0db5d9442 Mon Sep 17 00:00:00 2001 From: Iaroslav Date: Sun, 21 Jun 2020 20:23:50 +0700 Subject: [PATCH 1/3] Update readme, accident documentation-related refactor --- kmath-ast/README.md | 63 ++++++++++++++++++- .../kotlin/scientifik/kmath/asm/asm.kt | 10 ++- .../kmath/asm/internal/AsmBuilder.kt | 4 +- .../kmath/asm/internal/optimization.kt | 12 +++- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/kmath-ast/README.md b/kmath-ast/README.md index f9dbd4663..b5ca5886f 100644 --- a/kmath-ast/README.md +++ b/kmath-ast/README.md @@ -1,4 +1,61 @@ -# AST based expression representation and operations +# AST-based expression representation and operations (`kmath-ast`) -## Dynamic expression code generation -Contributed by [Iaroslav Postovalov](https://github.com/CommanderTvis). \ No newline at end of file +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` 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 { + // Plain constructor + public AsmCompiledExpression_45045_0(Algebra algebra, Object[] constants) { + super(algebra, constants); + } + + // The actual dynamic code: + public final Double invoke(Map 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). diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt index 43c1a377d..350bb95bb 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt @@ -4,11 +4,11 @@ import scientifik.kmath.asm.internal.AsmBuilder import scientifik.kmath.asm.internal.buildName import scientifik.kmath.asm.internal.hasSpecific import scientifik.kmath.asm.internal.tryInvokeSpecific -import scientifik.kmath.ast.MST -import scientifik.kmath.ast.MSTExpression +import scientifik.kmath.ast.* import scientifik.kmath.expressions.Expression import scientifik.kmath.operations.Algebra import scientifik.kmath.operations.NumericAlgebra +import scientifik.kmath.operations.RealField import kotlin.reflect.KClass /** @@ -78,4 +78,8 @@ inline fun Algebra.expression(mst: MST): Expression = ms /** * Optimize performance of an [MSTExpression] using ASM codegen */ -inline fun MSTExpression.compile(): Expression = mst.compileWith(T::class, algebra) \ No newline at end of file +inline fun MSTExpression.compile(): Expression = mst.compileWith(T::class, algebra) + +fun main() { + RealField.mstInField { symbol("x") + 2 }.compile() +} 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 649d06277..53fc4c4c1 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 @@ -8,7 +8,7 @@ import scientifik.kmath.asm.internal.AsmBuilder.ClassLoader 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. * * @param T the type generated [AsmCompiledExpression] operates. @@ -343,7 +343,7 @@ internal class AsmBuilder internal constructor( * Maps JVM primitive numbers boxed types to their letters of JVM signature convention. */ private val SIGNATURE_LETTERS: Map, String> by lazy { - mapOf( + hashMapOf( java.lang.Byte::class.java to "B", java.lang.Short::class.java to "S", java.lang.Integer::class.java to "I", 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 e81a7fd1a..efad97763 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 @@ -3,7 +3,13 @@ package scientifik.kmath.asm.internal import org.objectweb.asm.Opcodes import scientifik.kmath.operations.Algebra -private val methodNameAdapters: Map = mapOf("+" to "add", "*" to "multiply", "/" to "divide") +private val methodNameAdapters: Map 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]. @@ -13,7 +19,7 @@ private val methodNameAdapters: Map = mapOf("+" to "add", "*" to internal fun hasSpecific(context: Algebra, name: String, arity: Int): Boolean { 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 true @@ -28,7 +34,7 @@ internal fun hasSpecific(context: Algebra, name: String, arity: Int): Boo internal fun AsmBuilder.tryInvokeSpecific(context: Algebra, name: String, arity: Int): Boolean { 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 val owner = context::class.java.name.replace('.', '/') From e99f7ad360d2c0efb448c7dcdd2825dc1c9d663c Mon Sep 17 00:00:00 2001 From: Iaroslav Date: Mon, 22 Jun 2020 04:05:52 +0700 Subject: [PATCH 2/3] Optimize constant pooling --- kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt | 6 +++--- .../kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt | 1 + .../scientifik/kmath/asm/internal/methodVisitors.kt | 8 ++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt index 43c1a377d..feb3745b0 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/asm.kt @@ -19,11 +19,11 @@ fun MST.compileWith(type: KClass, algebra: Algebra): Expression< when (node) { is MST.Symbolic -> loadVariable(node.value) is MST.Numeric -> { - val constant = if (algebra is NumericAlgebra) { + val constant = if (algebra is NumericAlgebra) algebra.number(node.value) - } else { + else error("Number literals are not supported in $algebra") - } + loadTConstant(constant) } is MST.Unary -> { 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 649d06277..8b6892235 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 @@ -260,6 +260,7 @@ internal class AsmBuilder internal constructor( is Int -> invokeMethodVisitor.visitLdcOrIntConstant(value) is Double -> invokeMethodVisitor.visitLdcOrDoubleConstant(value) is Float -> invokeMethodVisitor.visitLdcOrFloatConstant(value) + is Long -> invokeMethodVisitor.visitLdcOrLongConstant(value) else -> invokeMethodVisitor.visitLdcInsn(value) } diff --git a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/methodVisitors.kt b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/methodVisitors.kt index 70fb3bd44..6ecce8833 100644 --- a/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/methodVisitors.kt +++ b/kmath-ast/src/jvmMain/kotlin/scientifik/kmath/asm/internal/methodVisitors.kt @@ -11,6 +11,8 @@ internal fun MethodVisitor.visitLdcOrIntConstant(value: Int): Unit = when (value 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) } @@ -20,6 +22,12 @@ internal fun MethodVisitor.visitLdcOrDoubleConstant(value: Double): Unit = when 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) From 29c6d2596733d25c4babc80aa90856b877153f42 Mon Sep 17 00:00:00 2001 From: Iaroslav Date: Mon, 22 Jun 2020 15:15:46 +0700 Subject: [PATCH 3/3] Optimize constant pooling for Byte and Short --- .../jvmMain/kotlin/scientifik/kmath/asm/internal/AsmBuilder.kt | 2 ++ 1 file changed, 2 insertions(+) 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 8b6892235..ff66e69b0 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 @@ -257,6 +257,8 @@ internal class AsmBuilder internal constructor( 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)