From afbd6e45b95f222f0884e70c39e85a103a602da4 Mon Sep 17 00:00:00 2001 From: sergeych Date: Thu, 5 Feb 2026 17:56:01 +0300 Subject: [PATCH] Fix numeric fast-paths for Obj arithmetic --- .../net/sergeych/lyng/bytecode/CmdRuntime.kt | 55 +++++++++++++++---- lynglib/src/commonTest/kotlin/TypesTest.kt | 50 +++++++++++++++++ 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 17e972c..4d4dfa8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -871,14 +871,21 @@ class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) frame.setInt(dst, frame.frame.getInt(la) + frame.frame.getInt(lb)) return } - if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val aNumeric = ta == SlotType.INT.code || ta == SlotType.REAL.code + val bNumeric = tb == SlotType.INT.code || tb == SlotType.REAL.code + if (aNumeric && bNumeric && (ta == SlotType.REAL.code || tb == SlotType.REAL.code)) { val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() frame.setReal(dst, av + bv) return } } - frame.setObj(dst, frame.slotToObj(a).plus(frame.ensureScope(), frame.slotToObj(b))) + val result = frame.slotToObj(a).plus(frame.ensureScope(), frame.slotToObj(b)) + when (result) { + is ObjInt -> frame.setInt(dst, result.value) + is ObjReal -> frame.setReal(dst, result.value) + else -> frame.setObj(dst, result) + } return } } @@ -895,14 +902,21 @@ class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) frame.setInt(dst, frame.frame.getInt(la) - frame.frame.getInt(lb)) return } - if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val aNumeric = ta == SlotType.INT.code || ta == SlotType.REAL.code + val bNumeric = tb == SlotType.INT.code || tb == SlotType.REAL.code + if (aNumeric && bNumeric && (ta == SlotType.REAL.code || tb == SlotType.REAL.code)) { val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() frame.setReal(dst, av - bv) return } } - frame.setObj(dst, frame.slotToObj(a).minus(frame.ensureScope(), frame.slotToObj(b))) + val result = frame.slotToObj(a).minus(frame.ensureScope(), frame.slotToObj(b)) + when (result) { + is ObjInt -> frame.setInt(dst, result.value) + is ObjReal -> frame.setReal(dst, result.value) + else -> frame.setObj(dst, result) + } return } } @@ -919,14 +933,21 @@ class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) frame.setInt(dst, frame.frame.getInt(la) * frame.frame.getInt(lb)) return } - if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val aNumeric = ta == SlotType.INT.code || ta == SlotType.REAL.code + val bNumeric = tb == SlotType.INT.code || tb == SlotType.REAL.code + if (aNumeric && bNumeric && (ta == SlotType.REAL.code || tb == SlotType.REAL.code)) { val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() frame.setReal(dst, av * bv) return } } - frame.setObj(dst, frame.slotToObj(a).mul(frame.ensureScope(), frame.slotToObj(b))) + val result = frame.slotToObj(a).mul(frame.ensureScope(), frame.slotToObj(b)) + when (result) { + is ObjInt -> frame.setInt(dst, result.value) + is ObjReal -> frame.setReal(dst, result.value) + else -> frame.setObj(dst, result) + } return } } @@ -943,14 +964,21 @@ class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) frame.setInt(dst, frame.frame.getInt(la) / frame.frame.getInt(lb)) return } - if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val aNumeric = ta == SlotType.INT.code || ta == SlotType.REAL.code + val bNumeric = tb == SlotType.INT.code || tb == SlotType.REAL.code + if (aNumeric && bNumeric && (ta == SlotType.REAL.code || tb == SlotType.REAL.code)) { val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() frame.setReal(dst, av / bv) return } } - frame.setObj(dst, frame.slotToObj(a).div(frame.ensureScope(), frame.slotToObj(b))) + val result = frame.slotToObj(a).div(frame.ensureScope(), frame.slotToObj(b)) + when (result) { + is ObjInt -> frame.setInt(dst, result.value) + is ObjReal -> frame.setReal(dst, result.value) + else -> frame.setObj(dst, result) + } return } } @@ -967,14 +995,21 @@ class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) frame.setInt(dst, frame.frame.getInt(la) % frame.frame.getInt(lb)) return } - if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val aNumeric = ta == SlotType.INT.code || ta == SlotType.REAL.code + val bNumeric = tb == SlotType.INT.code || tb == SlotType.REAL.code + if (aNumeric && bNumeric && (ta == SlotType.REAL.code || tb == SlotType.REAL.code)) { val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() frame.setReal(dst, av % bv) return } } - frame.setObj(dst, frame.slotToObj(a).mod(frame.ensureScope(), frame.slotToObj(b))) + val result = frame.slotToObj(a).mod(frame.ensureScope(), frame.slotToObj(b)) + when (result) { + is ObjInt -> frame.setInt(dst, result.value) + is ObjReal -> frame.setReal(dst, result.value) + else -> frame.setObj(dst, result) + } return } } diff --git a/lynglib/src/commonTest/kotlin/TypesTest.kt b/lynglib/src/commonTest/kotlin/TypesTest.kt index 9026c14..2c75463 100644 --- a/lynglib/src/commonTest/kotlin/TypesTest.kt +++ b/lynglib/src/commonTest/kotlin/TypesTest.kt @@ -96,4 +96,54 @@ class TypesTest { assertNotEquals(Point(0,1), p3) """.trimIndent()) } + + @Test + fun testNumericInference() = runTest { + eval(""" + val x = 1 + var y = 2.0 + assert( x is Int ) + assert( y is Real ) + assert( x + y is Real ) + assert( abs(x+y) is Real ) + assert( abs(x/y) is Real ) + """.trimIndent()) + } + @Test + fun testNumericInferenceBug1() = runTest { + eval(""" + fun findSumLimit(f) { + var sum = 0.0 + for( n in 1..100 ) { + val s0 = sum + sum += f(n) + assert( sum is Real ) + assert( s0 is Real ) + val delta = abs(sum - s0) / abs(sum) + assert( delta is Real ) + println("abs(%g - %g) = %g"(sum, s0, abs(sum-s0))) + if( s0 != 0 ) + assert( abs(sum-s0) < abs(sum) ) + println("abs(%g) = %g"(sum, abs(sum))) + println( "delta calc: %g"(delta) ) +// if( n > 3 ) assert( delta < 1.0 ) + if( delta < 1.0e-4 ) { + println("limit reached after "+n+" rounds") + break sum + } + else + println("%g, delta=%g"(sum, delta)) + n++ + } + else { + println("limit not reached") + null + } + } + + val limit = findSumLimit { n -> 1.0/n/n } + assert( limit != null ) + println("Result: "+limit) + """.trimIndent()) + } }