added optimizations for immutable numbers

This commit is contained in:
Sergey Chernov 2025-12-23 09:09:49 +01:00
parent 3f235878c0
commit 7e8f1406b5
5 changed files with 72 additions and 63 deletions

View File

@ -1235,12 +1235,12 @@ class Compiler(
return when (t.type) {
Token.Type.INT, Token.Type.HEX -> {
val n = t.value.replace("_", "").toLong(if (t.type == Token.Type.HEX) 16 else 10)
if (isPlus) ObjInt(n) else ObjInt(-n)
if (isPlus) ObjInt.of(n) else ObjInt.of(-n)
}
Token.Type.REAL -> {
val d = t.value.toDouble()
if (isPlus) ObjReal(d) else ObjReal(-d)
if (isPlus) ObjReal.of(d) else ObjReal.of(-d)
}
else -> {
@ -2940,22 +2940,22 @@ class Compiler(
// Arithmetic for ints only (keep semantics simple at compile time)
BinOp.PLUS -> when {
a is ObjInt && b is ObjInt -> ObjInt(a.value + b.value)
a is ObjInt && b is ObjInt -> ObjInt.of(a.value + b.value)
a is ObjString && b is ObjString -> ObjString(a.value + b.value)
else -> null
}
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null
BinOp.PERCENT -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value % b.value) else null
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value - b.value) else null
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value * b.value) else null
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt.of(a.value / b.value) else null
BinOp.PERCENT -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt.of(a.value % b.value) else null
// Bitwise for ints
BinOp.BAND -> if (a is ObjInt && b is ObjInt) ObjInt(a.value and b.value) else null
BinOp.BXOR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value xor b.value) else null
BinOp.BOR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value or b.value) else null
BinOp.SHL -> if (a is ObjInt && b is ObjInt) ObjInt(a.value shl (b.value.toInt() and 63)) else null
BinOp.SHR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value shr (b.value.toInt() and 63)) else null
BinOp.BAND -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value and b.value) else null
BinOp.BXOR -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value xor b.value) else null
BinOp.BOR -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value or b.value) else null
BinOp.SHL -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value shl (b.value.toInt() and 63)) else null
BinOp.SHR -> if (a is ObjInt && b is ObjInt) ObjInt.of(a.value shr (b.value.toInt() and 63)) else null
// Non-folded / side-effecting or type-dependent ops
BinOp.EQARROW, BinOp.REF_EQ, BinOp.REF_NEQ, BinOp.MATCH, BinOp.NOTMATCH,
@ -2968,12 +2968,12 @@ class Compiler(
return when (op) {
UnaryOp.NOT -> if (a is ObjBool) if (!a.value) ObjTrue else ObjFalse else null
UnaryOp.NEGATE -> when (a) {
is ObjInt -> ObjInt(-a.value)
is ObjReal -> ObjReal(-a.value)
is ObjInt -> ObjInt.of(-a.value)
is ObjReal -> ObjReal.of(-a.value)
else -> null
}
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt.of(a.value.inv()) else null
}
}

View File

@ -28,7 +28,7 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
override val longValue get() = value
override val doubleValue get() = value.toDouble()
override val toObjInt get() = this
override val toObjReal = ObjReal(doubleValue)
override val toObjReal = ObjReal.of(doubleValue)
override fun byValueCopy(): Obj = this
@ -65,28 +65,28 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
if (other is ObjInt)
of(this.value + other.value)
else
ObjReal(this.doubleValue + other.toDouble())
ObjReal.of(this.doubleValue + other.toDouble())
override suspend fun minus(scope: Scope, other: Obj): Obj =
if (other is ObjInt)
of(this.value - other.value)
else
ObjReal(this.doubleValue - other.toDouble())
ObjReal.of(this.doubleValue - other.toDouble())
override suspend fun mul(scope: Scope, other: Obj): Obj =
if (other is ObjInt) {
of(this.value * other.value)
} else ObjReal(this.value * other.toDouble())
} else ObjReal.of(this.value * other.toDouble())
override suspend fun div(scope: Scope, other: Obj): Obj =
if (other is ObjInt)
of(this.value / other.value)
else ObjReal(this.value / other.toDouble())
else ObjReal.of(this.value / other.toDouble())
override suspend fun mod(scope: Scope, other: Obj): Obj =
if (other is ObjInt)
of(this.value % other.value)
else ObjReal(this.value.toDouble() % other.toDouble())
else ObjReal.of(this.value.toDouble() % other.toDouble())
/**
* Numbers are now immutable, so we can't do in-place assignment.
@ -107,31 +107,31 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
}
override suspend fun negate(scope: Scope): Obj {
return ObjInt(-value)
return of(-value)
}
// Bitwise operations
override suspend fun bitAnd(scope: Scope, other: Obj): Obj =
if (other is ObjInt) ObjInt(this.value and other.value)
if (other is ObjInt) of(this.value and other.value)
else scope.raiseIllegalArgument("bitwise and '&' requires Int, got ${other.objClass.className}")
override suspend fun bitOr(scope: Scope, other: Obj): Obj =
if (other is ObjInt) ObjInt(this.value or other.value)
if (other is ObjInt) of(this.value or other.value)
else scope.raiseIllegalArgument("bitwise or '|' requires Int, got ${other.objClass.className}")
override suspend fun bitXor(scope: Scope, other: Obj): Obj =
if (other is ObjInt) ObjInt(this.value xor other.value)
if (other is ObjInt) of(this.value xor other.value)
else scope.raiseIllegalArgument("bitwise xor '^' requires Int, got ${other.objClass.className}")
override suspend fun shl(scope: Scope, other: Obj): Obj =
if (other is ObjInt) ObjInt(this.value shl (other.value.toInt() and 63))
if (other is ObjInt) of(this.value shl (other.value.toInt() and 63))
else scope.raiseIllegalArgument("shift left '<<' requires Int, got ${other.objClass.className}")
override suspend fun shr(scope: Scope, other: Obj): Obj =
if (other is ObjInt) ObjInt(this.value shr (other.value.toInt() and 63))
if (other is ObjInt) of(this.value shr (other.value.toInt() and 63))
else scope.raiseIllegalArgument("shift right '>>' requires Int, got ${other.objClass.className}")
override suspend fun bitNot(scope: Scope): Obj = ObjInt(this.value.inv())
override suspend fun bitNot(scope: Scope): Obj = of(this.value.inv())
override suspend fun lynonType(): LynonType = when (value) {
0L -> LynonType.Int0
@ -170,11 +170,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
val type = object : ObjClass("Int") {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
when (lynonType) {
null -> ObjInt(decoder.unpackSigned())
null -> of(decoder.unpackSigned())
LynonType.Int0 -> Zero
LynonType.IntPositive -> ObjInt(decoder.unpackUnsigned().toLong())
LynonType.IntNegative -> ObjInt(-decoder.unpackUnsigned().toLong())
LynonType.IntSigned -> ObjInt(decoder.unpackSigned())
LynonType.IntPositive -> of(decoder.unpackUnsigned().toLong())
LynonType.IntNegative -> of(-decoder.unpackUnsigned().toLong())
LynonType.IntSigned -> of(decoder.unpackSigned())
else -> scope.raiseIllegalState("illegal type code for Int: $lynonType")
}
}.apply {

View File

@ -32,10 +32,10 @@ import kotlin.math.floor
import kotlin.math.roundToLong
data class ObjReal(val value: Double) : Obj(), Numeric {
override val longValue: Long by lazy { floor(value).toLong() }
override val doubleValue: Double by lazy { value }
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
override val toObjReal: ObjReal by lazy { ObjReal(value) }
override val longValue: Long get() = floor(value).toLong()
override val doubleValue: Double get() = value
override val toObjInt: ObjInt get() = ObjInt.of(longValue)
override val toObjReal: ObjReal get() = this
override val objClass: ObjClass = type
@ -65,19 +65,19 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
}
override suspend fun plus(scope: Scope, other: Obj): Obj =
ObjReal(this.value + other.toDouble())
of(this.value + other.toDouble())
override suspend fun minus(scope: Scope, other: Obj): Obj =
ObjReal(this.value - other.toDouble())
of(this.value - other.toDouble())
override suspend fun mul(scope: Scope, other: Obj): Obj =
ObjReal(this.value * other.toDouble())
of(this.value * other.toDouble())
override suspend fun div(scope: Scope, other: Obj): Obj =
ObjReal(this.value / other.toDouble())
of(this.value / other.toDouble())
override suspend fun mod(scope: Scope, other: Obj): Obj =
ObjReal(this.value % other.toDouble())
of(this.value % other.toDouble())
/**
* Returns unboxed Double value
@ -96,7 +96,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
}
override suspend fun negate(scope: Scope): Obj {
return ObjReal(-value)
return of(-value)
}
override suspend fun lynonType(): LynonType = LynonType.Real
@ -110,9 +110,18 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
}
companion object {
val Zero = ObjReal(0.0)
val One = ObjReal(1.0)
fun of(value: Double): ObjReal = when (value) {
0.0 -> Zero
1.0 -> One
else -> ObjReal(value)
}
val type: ObjClass = object : ObjClass("Real") {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjReal(decoder.unpackDouble())
of(decoder.unpackDouble())
}.apply {
// roundToInt: number rounded to the nearest integer
addConstDoc(
@ -130,7 +139,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
ObjInt(thisAs<ObjReal>().value.toLong())
ObjInt.of(thisAs<ObjReal>().value.toLong())
}
}
}

View File

@ -132,16 +132,16 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
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.BAND -> ObjInt(av and bv)
BinOp.BXOR -> ObjInt(av xor bv)
BinOp.BOR -> ObjInt(av or bv)
BinOp.SHL -> ObjInt(av shl (bv.toInt() and 63))
BinOp.SHR -> ObjInt(av shr (bv.toInt() and 63))
BinOp.PLUS -> ObjInt.of(av + bv)
BinOp.MINUS -> ObjInt.of(av - bv)
BinOp.STAR -> ObjInt.of(av * bv)
BinOp.SLASH -> if (bv != 0L) ObjInt.of(av / bv) else null
BinOp.PERCENT -> if (bv != 0L) ObjInt.of(av % bv) else null
BinOp.BAND -> ObjInt.of(av and bv)
BinOp.BXOR -> ObjInt.of(av xor bv)
BinOp.BOR -> ObjInt.of(av or bv)
BinOp.SHL -> ObjInt.of(av shl (bv.toInt() and 63))
BinOp.SHR -> ObjInt.of(av shr (bv.toInt() and 63))
BinOp.EQ -> if (av == bv) ObjTrue else ObjFalse
BinOp.NEQ -> if (av != bv) ObjTrue else ObjFalse
BinOp.LT -> if (av < bv) ObjTrue else ObjFalse
@ -216,11 +216,11 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
val ad: Double = if (a is ObjInt) a.doubleValue else (a as ObjReal).value
val bd: Double = if (b is ObjInt) b.doubleValue else (b as ObjReal).value
val rNum: Obj? = when (op) {
BinOp.PLUS -> ObjReal(ad + bd)
BinOp.MINUS -> ObjReal(ad - bd)
BinOp.STAR -> ObjReal(ad * bd)
BinOp.SLASH -> ObjReal(ad / bd)
BinOp.PERCENT -> ObjReal(ad % bd)
BinOp.PLUS -> ObjReal.of(ad + bd)
BinOp.MINUS -> ObjReal.of(ad - bd)
BinOp.STAR -> ObjReal.of(ad * bd)
BinOp.SLASH -> ObjReal.of(ad / bd)
BinOp.PERCENT -> ObjReal.of(ad % bd)
BinOp.LT -> if (ad < bd) ObjTrue else ObjFalse
BinOp.LTE -> if (ad <= bd) ObjTrue else ObjFalse
BinOp.GT -> if (ad > bd) ObjTrue else ObjFalse

View File

@ -134,7 +134,7 @@ data class ObjString(val value: String) : Obj() {
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
ObjInt(
ObjInt.of(
thisAs<ObjString>().value.toLongOrNull()
?: raiseIllegalArgument("can't convert to int: $thisObj")
)
@ -159,7 +159,7 @@ data class ObjString(val value: String) : Obj() {
}
addConstDoc(
name = "length",
value = statement { ObjInt(thisAs<ObjString>().value.length.toLong()) },
value = statement { ObjInt.of(thisAs<ObjString>().value.length.toLong()) },
doc = "Number of UTF-16 code units in this string.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib"
@ -269,14 +269,14 @@ data class ObjString(val value: String) : Obj() {
doc = "Alias for length: the number of characters (code units) in this string.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { ObjInt(thisAs<ObjString>().value.length.toLong()) }
) { ObjInt.of(thisAs<ObjString>().value.length.toLong()) }
addFnDoc(
name = "toReal",
doc = "Parse this string as a real number (floating point).",
returns = type("lyng.Real"),
moduleName = "lyng.stdlib"
) {
ObjReal(thisAs<ObjString>().value.toDouble())
ObjReal.of(thisAs<ObjString>().value.toDouble())
}
addFnDoc(
name = "trim",