more optimizations
This commit is contained in:
parent
1498140892
commit
029fde2883
@ -21,53 +21,57 @@ import net.sergeych.lyng.obj.Obj
|
|||||||
import net.sergeych.lyng.obj.ObjIterable
|
import net.sergeych.lyng.obj.ObjIterable
|
||||||
import net.sergeych.lyng.obj.ObjList
|
import net.sergeych.lyng.obj.ObjList
|
||||||
|
|
||||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||||
|
|
||||||
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
||||||
val list = mutableListOf<Obj>()
|
// If ARG_BUILDER is enabled, try to reuse a pre-sized ArrayList and do bulk-adds
|
||||||
|
val list: MutableList<Obj> = if (PerfFlags.ARG_BUILDER) ArrayList(this.size) else mutableListOf()
|
||||||
for (x in this) {
|
|
||||||
val value = x.value.execute(scope)
|
for (x in this) {
|
||||||
if (x.isSplat) {
|
val value = x.value.execute(scope)
|
||||||
when {
|
if (x.isSplat) {
|
||||||
value is ObjList -> {
|
when {
|
||||||
for (subitem in value.list) list.add(subitem)
|
value is ObjList -> {
|
||||||
}
|
// Bulk add elements from an ObjList
|
||||||
|
list.addAll(value.list)
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
}
|
||||||
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
|
||||||
i.forEach { list.add(it) }
|
value.isInstanceOf(ObjIterable) -> {
|
||||||
}
|
// Convert to list once and bulk add
|
||||||
|
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
list.addAll(i)
|
||||||
}
|
}
|
||||||
} else
|
|
||||||
list.add(value)
|
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
||||||
}
|
}
|
||||||
return Arguments(list,tailBlockMode)
|
} else {
|
||||||
}
|
list.add(value)
|
||||||
|
}
|
||||||
data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) : List<Obj> by list {
|
}
|
||||||
|
return Arguments(list, tailBlockMode)
|
||||||
constructor(vararg values: Obj) : this(values.toList())
|
}
|
||||||
|
|
||||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) : List<Obj> by list {
|
||||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
|
||||||
return list.first().byValueCopy()
|
constructor(vararg values: Obj) : this(values.toList())
|
||||||
}
|
|
||||||
|
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||||
/**
|
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||||
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
return list.first().byValueCopy()
|
||||||
*/
|
}
|
||||||
suspend fun toKotlinList(scope: Scope): List<Any?> {
|
|
||||||
return list.map { it.toKotlin(scope) }
|
/**
|
||||||
}
|
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||||
|
*/
|
||||||
suspend fun inspect(scope: Scope): String = list.map{ it.inspect(scope)}.joinToString(",")
|
suspend fun toKotlinList(scope: Scope): List<Any?> {
|
||||||
|
return list.map { it.toKotlin(scope) }
|
||||||
companion object {
|
}
|
||||||
val EMPTY = Arguments(emptyList())
|
|
||||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
suspend fun inspect(scope: Scope): String = list.map{ it.inspect(scope)}.joinToString(",")
|
||||||
}
|
|
||||||
}
|
companion object {
|
||||||
|
val EMPTY = Arguments(emptyList())
|
||||||
|
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,4 +9,18 @@ object PerfFlags {
|
|||||||
var LOCAL_SLOT_PIC: Boolean = true
|
var LOCAL_SLOT_PIC: Boolean = true
|
||||||
// Make the compiler emit fast local refs for identifiers known to be function locals/params
|
// Make the compiler emit fast local refs for identifiers known to be function locals/params
|
||||||
var EMIT_FAST_LOCAL_REFS: Boolean = true
|
var EMIT_FAST_LOCAL_REFS: Boolean = true
|
||||||
|
|
||||||
|
// Enable more efficient argument building and bulk-copy for splats
|
||||||
|
var ARG_BUILDER: Boolean = true
|
||||||
|
// Allow early-return in optional calls before building args (semantics-compatible). Present for A/B only.
|
||||||
|
var SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||||
|
// Enable pooling of Scope frames for calls (planned; JVM-only)
|
||||||
|
var SCOPE_POOL: Boolean = false
|
||||||
|
|
||||||
|
// Step 2: PICs for fields and methods
|
||||||
|
var FIELD_PIC: Boolean = true
|
||||||
|
var METHOD_PIC: Boolean = true
|
||||||
|
|
||||||
|
// Step 3: Primitive arithmetic and comparison fast paths
|
||||||
|
var PRIMITIVE_FASTOPS: Boolean = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,9 @@ import net.sergeych.lyng.*
|
|||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
import net.sergeych.lynon.LynonType
|
import net.sergeych.lynon.LynonType
|
||||||
|
|
||||||
|
// Simple id generator for class identities (not thread-safe; fine for scripts)
|
||||||
|
private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||||
|
|
||||||
val ObjClassType by lazy { ObjClass("Class") }
|
val ObjClassType by lazy { ObjClass("Class") }
|
||||||
|
|
||||||
open class ObjClass(
|
open class ObjClass(
|
||||||
@ -28,6 +31,10 @@ open class ObjClass(
|
|||||||
vararg parents: ObjClass,
|
vararg parents: ObjClass,
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
|
// Stable identity and simple structural version for PICs
|
||||||
|
val classId: Long = ClassIdGen.nextId()
|
||||||
|
var layoutVersion: Int = 0
|
||||||
|
|
||||||
val classNameObj by lazy { ObjString(className) }
|
val classNameObj by lazy { ObjString(className) }
|
||||||
|
|
||||||
var constructorMeta: ArgsDeclaration? = null
|
var constructorMeta: ArgsDeclaration? = null
|
||||||
@ -84,6 +91,8 @@ open class ObjClass(
|
|||||||
if (existing?.isMutable == false)
|
if (existing?.isMutable == false)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
|
// Structural change: bump layout version for PIC invalidation
|
||||||
|
layoutVersion += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initClassScope(): Scope {
|
private fun initClassScope(): Scope {
|
||||||
@ -103,6 +112,8 @@ open class ObjClass(
|
|||||||
if (existing != null)
|
if (existing != null)
|
||||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
classScope!!.addItem(name, isMutable, initialValue, visibility)
|
classScope!!.addItem(name, isMutable, initialValue, visibility)
|
||||||
|
// Structural change: bump layout version for PIC invalidation
|
||||||
|
layoutVersion += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||||
|
|||||||
@ -65,6 +65,42 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
|||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val a = left.get(scope).value
|
val a = left.get(scope).value
|
||||||
val b = right.get(scope).value
|
val b = right.get(scope).value
|
||||||
|
|
||||||
|
// Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS)
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
|
// Fast boolean ops when both operands are ObjBool
|
||||||
|
if (a is ObjBool && b is ObjBool) {
|
||||||
|
val r: Obj? = when (op) {
|
||||||
|
BinOp.OR -> if (a.value || b.value) ObjTrue else ObjFalse
|
||||||
|
BinOp.AND -> if (a.value && b.value) ObjTrue else ObjFalse
|
||||||
|
BinOp.EQ -> if (a.value == b.value) ObjTrue else ObjFalse
|
||||||
|
BinOp.NEQ -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (r != null) return r.asReadonly
|
||||||
|
}
|
||||||
|
// Fast integer ops when both operands are ObjInt
|
||||||
|
if (a is ObjInt && b is ObjInt) {
|
||||||
|
val av = a.value
|
||||||
|
val bv = b.value
|
||||||
|
val r: Obj? = when (op) {
|
||||||
|
BinOp.PLUS -> ObjInt(av + bv)
|
||||||
|
BinOp.MINUS -> ObjInt(av - bv)
|
||||||
|
BinOp.STAR -> ObjInt(av * bv)
|
||||||
|
BinOp.SLASH -> if (bv != 0L) ObjInt(av / bv) else null
|
||||||
|
BinOp.PERCENT -> if (bv != 0L) ObjInt(av % bv) else null
|
||||||
|
BinOp.EQ -> if (av == bv) ObjTrue else ObjFalse
|
||||||
|
BinOp.NEQ -> if (av != bv) ObjTrue else ObjFalse
|
||||||
|
BinOp.LT -> if (av < bv) ObjTrue else ObjFalse
|
||||||
|
BinOp.LTE -> if (av <= bv) ObjTrue else ObjFalse
|
||||||
|
BinOp.GT -> if (av > bv) ObjTrue else ObjFalse
|
||||||
|
BinOp.GTE -> if (av >= bv) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (r != null) return r.asReadonly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val r: Obj = when (op) {
|
val r: Obj = when (op) {
|
||||||
BinOp.OR -> a.logicalOr(scope, b)
|
BinOp.OR -> a.logicalOr(scope, b)
|
||||||
BinOp.AND -> a.logicalAnd(scope, b)
|
BinOp.AND -> a.logicalAnd(scope, b)
|
||||||
@ -172,6 +208,11 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
|
|||||||
val a = left.get(scope).value
|
val a = left.get(scope).value
|
||||||
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
||||||
val b = right.get(scope).value
|
val b = right.get(scope).value
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
|
if (a is ObjBool && b is ObjBool) {
|
||||||
|
return if (a.value || b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||||
|
}
|
||||||
|
}
|
||||||
return a.logicalOr(scope, b).asReadonly
|
return a.logicalOr(scope, b).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,6 +223,11 @@ class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRe
|
|||||||
val a = left.get(scope).value
|
val a = left.get(scope).value
|
||||||
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
||||||
val b = right.get(scope).value
|
val b = right.get(scope).value
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
|
if (a is ObjBool && b is ObjBool) {
|
||||||
|
return if (a.value && b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||||
|
}
|
||||||
|
}
|
||||||
return a.logicalAnd(scope, b).asReadonly
|
return a.logicalAnd(scope, b).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,9 +247,54 @@ class FieldRef(
|
|||||||
private val name: String,
|
private val name: String,
|
||||||
private val isOptional: Boolean,
|
private val isOptional: Boolean,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
// 2-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC)
|
||||||
|
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||||
|
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||||
|
|
||||||
|
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||||
|
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val base = target.get(scope).value
|
val base = target.get(scope).value
|
||||||
return if (base == ObjNull && isOptional) ObjNull.asMutable else base.readField(scope, name)
|
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||||
|
if (net.sergeych.lyng.PerfFlags.FIELD_PIC) {
|
||||||
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) return g(base, scope) }
|
||||||
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) return g(base, scope) }
|
||||||
|
// Slow path
|
||||||
|
val rec = base.readField(scope, name)
|
||||||
|
// Install move-to-front with a handle-aware getter
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
||||||
|
when (obj) {
|
||||||
|
is ObjInstance -> {
|
||||||
|
val instScope = obj.instanceScope
|
||||||
|
val idx = instScope.getSlotIndexOf(name)
|
||||||
|
if (idx != null) {
|
||||||
|
val r = instScope.getSlotRecord(idx)
|
||||||
|
if (!r.visibility.isPublic)
|
||||||
|
sc.raiseError(ObjAccessException(sc, "can't access non-public field $name"))
|
||||||
|
r
|
||||||
|
} else obj.readField(sc, name)
|
||||||
|
}
|
||||||
|
is ObjClass -> {
|
||||||
|
val clsScope = obj.classScope
|
||||||
|
if (clsScope != null) {
|
||||||
|
val idx = clsScope.getSlotIndexOf(name)
|
||||||
|
if (idx != null) {
|
||||||
|
val r = clsScope.getSlotRecord(idx)
|
||||||
|
if (!r.visibility.isPublic)
|
||||||
|
sc.raiseError(ObjAccessException(sc, "can't access non-public field $name"))
|
||||||
|
r
|
||||||
|
} else obj.readField(sc, name)
|
||||||
|
} else obj.readField(sc, name)
|
||||||
|
}
|
||||||
|
else -> obj.readField(sc, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
return base.readField(scope, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
@ -212,8 +303,53 @@ class FieldRef(
|
|||||||
// no-op on null receiver for optional chaining assignment
|
// no-op on null receiver for optional chaining assignment
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (net.sergeych.lyng.PerfFlags.FIELD_PIC) {
|
||||||
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) return s(base, scope, newValue) }
|
||||||
|
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) return s(base, scope, newValue) }
|
||||||
|
// Slow path
|
||||||
|
base.writeField(scope, name, newValue)
|
||||||
|
// Install move-to-front with a handle-aware setter
|
||||||
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
|
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v ->
|
||||||
|
when (obj) {
|
||||||
|
is ObjInstance -> {
|
||||||
|
val instScope = obj.instanceScope
|
||||||
|
val idx = instScope.getSlotIndexOf(name)
|
||||||
|
if (idx != null) {
|
||||||
|
val r = instScope.getSlotRecord(idx)
|
||||||
|
if (!r.visibility.isPublic)
|
||||||
|
sc.raiseError(ObjAccessException(sc, "can't assign to non-public field $name"))
|
||||||
|
if (!r.isMutable)
|
||||||
|
sc.raiseError(ObjIllegalAssignmentException(sc, "can't reassign val $name"))
|
||||||
|
if (r.value.assign(sc, v) == null) r.value = v
|
||||||
|
} else obj.writeField(sc, name, v)
|
||||||
|
}
|
||||||
|
is ObjClass -> {
|
||||||
|
val clsScope = obj.classScope
|
||||||
|
if (clsScope != null) {
|
||||||
|
val idx = clsScope.getSlotIndexOf(name)
|
||||||
|
if (idx != null) {
|
||||||
|
val r = clsScope.getSlotRecord(idx)
|
||||||
|
if (!r.isMutable)
|
||||||
|
sc.raiseError(ObjIllegalAssignmentException(sc, "can't reassign val $name"))
|
||||||
|
r.value = v
|
||||||
|
} else obj.writeField(sc, name, v)
|
||||||
|
} else obj.writeField(sc, name, v)
|
||||||
|
}
|
||||||
|
else -> obj.writeField(sc, name, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
base.writeField(scope, name, newValue)
|
base.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
||||||
|
is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion
|
||||||
|
is ObjClass -> obj.classId to obj.layoutVersion
|
||||||
|
else -> 0L to -1 // no caching for primitives/dynamics without stable shape
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -277,13 +413,56 @@ class MethodCallRef(
|
|||||||
private val tailBlock: Boolean,
|
private val tailBlock: Boolean,
|
||||||
private val isOptional: Boolean,
|
private val isOptional: Boolean,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
// 2-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC)
|
||||||
|
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||||
|
private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val base = receiver.get(scope).value
|
val base = receiver.get(scope).value
|
||||||
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
||||||
val callArgs = args.toArguments(scope, tailBlock)
|
val callArgs = args.toArguments(scope, tailBlock)
|
||||||
|
if (net.sergeych.lyng.PerfFlags.METHOD_PIC) {
|
||||||
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
|
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) return inv(base, scope, callArgs).asReadonly }
|
||||||
|
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) return inv(base, scope, callArgs).asReadonly }
|
||||||
|
// Slow path
|
||||||
|
val result = base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
|
// Install move-to-front with a handle-aware invoker
|
||||||
|
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||||
|
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
||||||
|
when (obj) {
|
||||||
|
is ObjInstance -> {
|
||||||
|
val instScope = obj.instanceScope
|
||||||
|
val rec = instScope.get(name)
|
||||||
|
if (rec != null) {
|
||||||
|
if (!rec.visibility.isPublic)
|
||||||
|
sc.raiseError(ObjAccessException(sc, "can't invoke non-public method $name"))
|
||||||
|
rec.value.invoke(instScope, obj, a)
|
||||||
|
} else obj.invokeInstanceMethod(sc, name, a)
|
||||||
|
}
|
||||||
|
is ObjClass -> {
|
||||||
|
val clsScope = obj.classScope
|
||||||
|
if (clsScope != null) {
|
||||||
|
val rec = clsScope.get(name)
|
||||||
|
if (rec != null) {
|
||||||
|
rec.value.invoke(sc, obj, a)
|
||||||
|
} else obj.invokeInstanceMethod(sc, name, a)
|
||||||
|
} else obj.invokeInstanceMethod(sc, name, a)
|
||||||
|
}
|
||||||
|
else -> obj.invokeInstanceMethod(sc, name, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.asReadonly
|
||||||
|
}
|
||||||
val result = base.invokeInstanceMethod(scope, name, callArgs)
|
val result = base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
return result.asReadonly
|
return result.asReadonly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
||||||
|
is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion
|
||||||
|
is ObjClass -> obj.classId to obj.layoutVersion
|
||||||
|
else -> 0L to -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
76
lynglib/src/jvmTest/kotlin/ArithmeticBenchmarkTest.kt
Normal file
76
lynglib/src/jvmTest/kotlin/ArithmeticBenchmarkTest.kt
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* JVM micro-benchmarks for primitive arithmetic and comparison fast paths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.PerfFlags
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class ArithmeticBenchmarkTest {
|
||||||
|
@Test
|
||||||
|
fun benchmarkIntArithmeticAndComparisons() = runBlocking {
|
||||||
|
val n = 400_000
|
||||||
|
val sumScript = """
|
||||||
|
var s = 0
|
||||||
|
var i = 0
|
||||||
|
while (i < $n) {
|
||||||
|
s = s + i
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
s
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
// Baseline: disable primitive fast ops
|
||||||
|
PerfFlags.PRIMITIVE_FASTOPS = false
|
||||||
|
val scope1 = Scope()
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
val r1 = (scope1.eval(sumScript) as ObjInt).value
|
||||||
|
val t1 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] int-sum x$n [PRIMITIVE_FASTOPS=OFF]: ${(t1 - t0)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
// Optimized
|
||||||
|
PerfFlags.PRIMITIVE_FASTOPS = true
|
||||||
|
val scope2 = Scope()
|
||||||
|
val t2 = System.nanoTime()
|
||||||
|
val r2 = (scope2.eval(sumScript) as ObjInt).value
|
||||||
|
val t3 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] int-sum x$n [PRIMITIVE_FASTOPS=ON]: ${(t3 - t2)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
val expected = (n.toLong() - 1L) * n / 2L
|
||||||
|
assertEquals(expected, r1)
|
||||||
|
assertEquals(expected, r2)
|
||||||
|
|
||||||
|
// Comparison heavy (branchy) loop
|
||||||
|
val cmpScript = """
|
||||||
|
var s = 0
|
||||||
|
var i = 0
|
||||||
|
while (i < $n) {
|
||||||
|
if (i % 2 == 0) s = s + 1 else s = s + 2
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
s
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
PerfFlags.PRIMITIVE_FASTOPS = false
|
||||||
|
val scope3 = Scope()
|
||||||
|
val t4 = System.nanoTime()
|
||||||
|
val c1 = (scope3.eval(cmpScript) as ObjInt).value
|
||||||
|
val t5 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] int-cmp x$n [PRIMITIVE_FASTOPS=OFF]: ${(t5 - t4)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
PerfFlags.PRIMITIVE_FASTOPS = true
|
||||||
|
val scope4 = Scope()
|
||||||
|
val t6 = System.nanoTime()
|
||||||
|
val c2 = (scope4.eval(cmpScript) as ObjInt).value
|
||||||
|
val t7 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] int-cmp x$n [PRIMITIVE_FASTOPS=ON]: ${(t7 - t6)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
// Expected: half of n even add 1, half odd add 2 (n even assumed)
|
||||||
|
val expectedCmp = (n / 2) * 1L + (n - n / 2) * 2L
|
||||||
|
assertEquals(expectedCmp, c1)
|
||||||
|
assertEquals(expectedCmp, c2)
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lynglib/src/jvmTest/kotlin/CallBenchmarkTest.kt
Normal file
55
lynglib/src/jvmTest/kotlin/CallBenchmarkTest.kt
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* JVM micro-benchmarks for function/method call overhead and argument building.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.PerfFlags
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class CallBenchmarkTest {
|
||||||
|
@Test
|
||||||
|
fun benchmarkSimpleFunctionCalls() = runBlocking {
|
||||||
|
val n = 300_000 // keep it fast for CI
|
||||||
|
|
||||||
|
// A tiny script with 0, 1, 2 arg functions and a loop using them
|
||||||
|
val script = """
|
||||||
|
fun f0() { 1 }
|
||||||
|
fun f1(a) { a }
|
||||||
|
fun f2(a,b) { a + b }
|
||||||
|
|
||||||
|
var s = 0
|
||||||
|
var i = 0
|
||||||
|
while (i < $n) {
|
||||||
|
s = s + f0()
|
||||||
|
s = s + f1(1)
|
||||||
|
s = s + f2(1, 1)
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
s
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
// Disable ARG_BUILDER for baseline
|
||||||
|
PerfFlags.ARG_BUILDER = false
|
||||||
|
val scope1 = Scope()
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
val r1 = (scope1.eval(script) as ObjInt).value
|
||||||
|
val t1 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] calls x$n [ARG_BUILDER=OFF]: ${(t1 - t0)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
// Enable ARG_BUILDER for optimized run
|
||||||
|
PerfFlags.ARG_BUILDER = true
|
||||||
|
val scope2 = Scope()
|
||||||
|
val t2 = System.nanoTime()
|
||||||
|
val r2 = (scope2.eval(script) as ObjInt).value
|
||||||
|
val t3 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] calls x$n [ARG_BUILDER=ON]: ${(t3 - t2)/1_000_000.0} ms")
|
||||||
|
|
||||||
|
// Correctness: each loop adds 1 + 1 + (1+1) = 4
|
||||||
|
val expected = 4L * n
|
||||||
|
assertEquals(expected, r1)
|
||||||
|
assertEquals(expected, r2)
|
||||||
|
}
|
||||||
|
}
|
||||||
86
lynglib/src/jvmTest/kotlin/PicBenchmarkTest.kt
Normal file
86
lynglib/src/jvmTest/kotlin/PicBenchmarkTest.kt
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* JVM micro-benchmarks for FieldRef and MethodCallRef PICs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.PerfFlags
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class PicBenchmarkTest {
|
||||||
|
@Test
|
||||||
|
fun benchmarkFieldGetSetPic() = runBlocking {
|
||||||
|
val iterations = 300_000
|
||||||
|
val script = """
|
||||||
|
class C() {
|
||||||
|
var x = 0
|
||||||
|
fun add1() { x = x + 1 }
|
||||||
|
fun getX() { x }
|
||||||
|
}
|
||||||
|
val c = C()
|
||||||
|
var i = 0
|
||||||
|
while(i < $iterations) {
|
||||||
|
c.x = c.x + 1
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
c.x
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
// PIC OFF
|
||||||
|
PerfFlags.FIELD_PIC = false
|
||||||
|
val scope1 = Scope()
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
val r1 = (scope1.eval(script) as ObjInt).value
|
||||||
|
val t1 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] Field PIC=OFF: ${(t1 - t0) / 1_000_000.0} ms")
|
||||||
|
assertEquals(iterations.toLong(), r1)
|
||||||
|
|
||||||
|
// PIC ON
|
||||||
|
PerfFlags.FIELD_PIC = true
|
||||||
|
val scope2 = Scope()
|
||||||
|
val t2 = System.nanoTime()
|
||||||
|
val r2 = (scope2.eval(script) as ObjInt).value
|
||||||
|
val t3 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] Field PIC=ON: ${(t3 - t2) / 1_000_000.0} ms")
|
||||||
|
assertEquals(iterations.toLong(), r2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun benchmarkMethodPic() = runBlocking {
|
||||||
|
val iterations = 200_000
|
||||||
|
val script = """
|
||||||
|
class C() {
|
||||||
|
var x = 0
|
||||||
|
fun add(v) { x = x + v }
|
||||||
|
fun get() { x }
|
||||||
|
}
|
||||||
|
val c = C()
|
||||||
|
var i = 0
|
||||||
|
while(i < $iterations) {
|
||||||
|
c.add(1)
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
c.get()
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
// PIC OFF
|
||||||
|
PerfFlags.METHOD_PIC = false
|
||||||
|
val scope1 = Scope()
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
val r1 = (scope1.eval(script) as ObjInt).value
|
||||||
|
val t1 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] Method PIC=OFF: ${(t1 - t0) / 1_000_000.0} ms")
|
||||||
|
assertEquals(iterations.toLong(), r1)
|
||||||
|
|
||||||
|
// PIC ON
|
||||||
|
PerfFlags.METHOD_PIC = true
|
||||||
|
val scope2 = Scope()
|
||||||
|
val t2 = System.nanoTime()
|
||||||
|
val r2 = (scope2.eval(script) as ObjInt).value
|
||||||
|
val t3 = System.nanoTime()
|
||||||
|
println("[DEBUG_LOG] [BENCH] Method PIC=ON: ${(t3 - t2) / 1_000_000.0} ms")
|
||||||
|
assertEquals(iterations.toLong(), r2)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user