optimize arithmetics

This commit is contained in:
Sergey Chernov 2026-04-04 04:01:43 +03:00
parent 161f3f74e2
commit d8454a11fc
21 changed files with 913 additions and 208 deletions

View File

@ -140,7 +140,7 @@ Open-ended ranges require an explicit step to iterate:
You can use Char as both ends of the closed range: You can use Char as both ends of the closed range:
val r = 'a' .. 'c' val r = 'a'..'c'
assert( 'b' in r) assert( 'b' in r)
assert( 'e' !in r) assert( 'e' !in r)
for( ch in r ) for( ch in r )

View File

@ -0,0 +1,62 @@
## Pi Spigot JVM Baseline
Saved on April 4, 2026 before the `List<Int>` indexed-access follow-up fix.
Benchmark target:
- [examples/pi-bench.py](/home/sergeych/dev/lyng/examples/pi-bench.py)
- [examples/pi-bench.lyng](/home/sergeych/dev/lyng/examples/pi-bench.lyng)
Execution path:
- Python: `python3 examples/pi-bench.py`
- Lyng JVM: `./gradlew :lyng:runJvm --args='/home/sergeych/dev/lyng/examples/pi-bench.lyng'`
- Constraint: do not use Kotlin/Native `lyng` CLI for perf comparisons
Baseline measurements:
- Python full script: `167 ms`
- Lyng JVM full script: `1.287097604 s`
- Python warm function average over 5 runs: `126.126 ms`
- Lyng JVM warm function average over 5 runs: about `1071.6 ms`
Baseline ratio:
- Full script: about `7.7x` slower on Lyng JVM
- Warm function only: about `8.5x` slower on Lyng JVM
Primary finding at baseline:
- The hot `reminders[j]` accesses in `piSpigot` were still lowered through boxed object index ops and boxed arithmetic.
- Newly added `GET_INDEX_INT` and `SET_INDEX_INT` only reached `pi`, not `reminders`.
- Root cause: initializer element inference handled list literals, but not `List.fill(boxes) { 2 }`, so `reminders` did not become known `List<Int>` at compile time.
## After Optimizations 1-4
Follow-up change:
- propagate inferred lambda return class into bytecode compilation
- infer `List.fill(...)` element type from the fill lambda
- lower `reminders[j]` reads and writes to `GET_INDEX_INT` and `SET_INDEX_INT`
- add primitive-backed `ObjList` storage for all-int lists
- lower `List.fill(Int) { Int }` to `LIST_FILL_INT`
- stop boxing the integer index inside `GET_INDEX_INT` / `SET_INDEX_INT`
Verification:
- `piSpigot` disassembly now contains typed ops for `reminders`, for example:
- `GET_INDEX_INT s5(reminders), s10(j), ...`
- `SET_INDEX_INT s5(reminders), s10(j), ...`
Post-change measurements using `jlyng`:
- Full script: `655.819559 ms`
- Warm 5-run total: `1.430945810 s`
- Warm average per run: about `286.2 ms`
Observed improvement vs baseline:
- Full script: about `1.96x` faster (`1.287 s -> 0.656 s`)
- Warm function: about `3.74x` faster (`1071.6 ms -> 286.2 ms`)
Residual gap vs Python baseline:
- Full script: Lyng JVM is still about `3.9x` slower than Python (`655.8 ms` vs `167 ms`)
- Warm function: Lyng JVM is still about `2.3x` slower than Python (`286.2 ms` vs `126.126 ms`)
Current benchmark-test snapshot (`n=200`, JVM test harness):
- `optimized-int-division-rval-off`: `135 ms`
- `optimized-int-division-rval-on`: `125 ms`
- `piSpigot` bytecode now contains:
- `LIST_FILL_INT` for both `pi` and `reminders`
- `GET_INDEX_INT` / `SET_INDEX_INT` for the hot indexed loop

View File

@ -1531,11 +1531,9 @@ It could be open and closed:
Descending ranges are explicit too: Descending ranges are explicit too:
(5 downTo 1).toList() assertEquals([5,4,3,2,1], (5 downTo 1).toList())
>>> [5,4,3,2,1] assertEquals([5,4,3,2], (5 downUntil 1).toList())
>>> void
(5 downUntil 1).toList()
>>> [5,4,3,2]
Ranges could be inside other ranges: Ranges could be inside other ranges:
@ -1549,7 +1547,7 @@ There are character ranges too:
and you can use ranges in for-loops: and you can use ranges in for-loops:
for( x in 'a' ..< 'c' ) println(x) for( x in 'a'..<'c' ) println(x)
>>> a >>> a
>>> b >>> b
>>> void >>> void

View File

@ -1,18 +1,18 @@
import lyng.time import lyng.time
val WORK_SIZE = 200 val WORK_SIZE = 500
val TASK_COUNT = 10 val THREADS = 1
fn piSpigot(iThread: Int, n: Int) { fn piSpigot(iThread: Int, n: Int) {
var pi = [] var piIter = 0
var pi = List.fill(n) { 0 }
val boxes = n * 10 / 3 val boxes = n * 10 / 3
var reminders = List.fill(boxes) { 2 } var reminders = List.fill(boxes) { 2 }
var heldDigits = 0 var heldDigits = 0
for (i in 0..n) { for (i in 0..<n) {
var carriedOver = 0 var carriedOver = 0
var sum = 0 var sum = 0
for (k in 1..boxes) { for (j in (boxes - 1) downTo 0) {
val j = boxes - k
val denom = j * 2 + 1 val denom = j * 2 + 1
reminders[j] *= 10 reminders[j] *= 10
sum = reminders[j] + carriedOver sum = reminders[j] + carriedOver
@ -39,29 +39,29 @@ fn piSpigot(iThread: Int, n: Int) {
} else { } else {
heldDigits = 1 heldDigits = 1
} }
pi.add(q) pi[piIter] = q
++piIter
} }
var s = "" var res = ""
for (i in (n - 8)..<n) { for (i in (n - 8)..<n) {
s += pi[i] res += pi[i]
} }
println(iThread, " - done: ", s) println(iThread.toString() + ": " + res)
res
} }
var counter = 0 for( r in 0..100 ) {
val t0 = Instant()
val t0 = Instant() println("piBench (lyng): THREADS = " + THREADS + ", WORK_SIZE = " + WORK_SIZE)
(1..TASK_COUNT).map { n -> for (i in 0..<THREADS) {
val counterState = counter piSpigot(i, WORK_SIZE)
val t = launch {
piSpigot(counterState, WORK_SIZE)
} }
++counter
t
}.forEach { (it as Deferred).await() }
val dt = Instant() - t0 val dt = Instant() - t0
println("all done, dt = ", dt) println("all done, dt = ", dt)
delay(800)
}

83
examples/pi-bench.py Normal file
View File

@ -0,0 +1,83 @@
# Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import time
from multiprocessing import Process
def piSpigot(iThread, nx):
piIter = 0
pi = [None] * nx
boxes = nx * 10 // 3
reminders = [None]*boxes
i = 0
while i < boxes:
reminders[i] = 2
i += 1
heldDigits = 0
i = 0
while i < nx:
carriedOver = 0
sum = 0
j = boxes - 1
while j >= 0:
reminders[j] *= 10
sum = reminders[j] + carriedOver
quotient = sum // (j * 2 + 1)
reminders[j] = sum % (j * 2 + 1)
carriedOver = quotient * j
j -= 1
reminders[0] = sum % 10
q = sum // 10
if q == 9:
heldDigits += 1
elif q == 10:
q = 0
k = 1
while k <= heldDigits:
replaced = pi[i - k]
if replaced == 9:
replaced = 0
else:
replaced += 1
pi[i - k] = replaced
k += 1
heldDigits = 1
else:
heldDigits = 1
pi[piIter] = q
piIter += 1
i += 1
res = ""
for i in range(len(pi)-8, len(pi), 1):
res += str(pi[i])
print(str(iThread) + ": " + res)
def createProcesses():
THREADS = 1
WORK_SIZE = 500
print("piBench (python3): THREADS = " + str(THREADS) + ", WORK_SIZE = " + str(WORK_SIZE))
pa = []
for i in range(THREADS):
p = Process(target=piSpigot, args=(i, WORK_SIZE))
p.start()
pa.append(p)
for p in pa:
p.join()
if __name__ == "__main__":
t1 = time.time()
createProcesses()
dt = time.time() - t1
print("total time: %i ms" % (dt*1000))

View File

@ -1,49 +0,0 @@
fn piSpigot(n) {
var pi = []
val boxes = n * 10 / 3
var reminders = []
for (i in 0..<boxes) {
reminders.add(2)
}
var heldDigits = 0
for (i in 0..n) {
var carriedOver = 0
var sum = 0
for (k in 1..boxes) {
val j = boxes - k
val denom = j * 2 + 1
reminders[j] *= 10
sum = reminders[j] + carriedOver
// Keep this integer-only. Real coercion here is much slower in the hot loop.
val quotient = sum / denom
reminders[j] = sum % denom
carriedOver = quotient * j
}
reminders[0] = sum % 10
var q = sum / 10
if (q == 9) {
++heldDigits
} else if (q == 10) {
q = 0
for (k in 1..heldDigits) {
var replaced = pi[i - k]
if (replaced == 9) {
replaced = 0
} else {
++replaced
}
pi[i - k] = replaced
}
heldDigits = 1
} else {
heldDigits = 1
}
pi.add(q)
}
var suffix = ""
for (i in (n - 8)..<n) {
suffix += pi[i]
}
suffix
}

View File

@ -20,9 +20,11 @@ import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.EvalSession import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
import net.sergeych.lyng.Source import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
import org.junit.Test import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
class CliDispatcherJvmTest { class CliDispatcherJvmTest {
@ -69,4 +71,34 @@ class CliDispatcherJvmTest {
session.cancelAndJoin() session.cancelAndJoin()
} }
} }
@Test
fun cliEvalInfersDeferredItTypeFromMapLambdaLocal() = runBlocking {
val session = EvalSession(Script.newScope())
try {
val result = evalOnCliDispatcher(
session,
Source(
"<cli-repro>",
"""
var sum = 0
var counter = 0
(1..3).map { n ->
val counterState = counter
val task = launch { counterState + n }
++counter
task
}.forEach { sum += it.await() }
sum
""".trimIndent()
)
)
assertEquals(9, (result as ObjInt).value)
} finally {
session.cancelAndJoin()
}
}
} }

View File

@ -17,7 +17,9 @@
package net.sergeych.lyng.io.net package net.sergeych.lyng.io.net
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import net.sergeych.lyng.Compiler import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script import net.sergeych.lyng.Script
@ -29,9 +31,9 @@ import kotlin.test.assertEquals
class LyngNetTcpServerExampleTest { class LyngNetTcpServerExampleTest {
@Test @Test
fun tcpServerExampleRoundTripsOverLoopback() = runBlocking { fun tcpServerExampleRoundTripsOverLoopback() = runTest {
val engine = getSystemNetEngine() val engine = getSystemNetEngine()
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runBlocking if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runTest
val scope = Script.newScope() val scope = Script.newScope()
createNetModule(PermitAllNetAccessPolicy, scope) createNetModule(PermitAllNetAccessPolicy, scope)
@ -60,8 +62,10 @@ class LyngNetTcpServerExampleTest {
"${'$'}{accepted.await()}: ${'$'}reply" "${'$'}{accepted.await()}: ${'$'}reply"
""".trimIndent() """.trimIndent()
val result = withTimeout(5_000) { val result = withContext(Dispatchers.Default) {
Compiler.compile(code).execute(scope).inspect(scope) withTimeout(5_000) {
Compiler.compile(code).execute(scope).inspect(scope)
}
} }
assertEquals("\"ping: echo:ping\"", result) assertEquals("\"ping: echo:ping\"", result)

View File

@ -1,3 +1,20 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.io.net package net.sergeych.lyng.io.net
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi

View File

@ -1,3 +1,20 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyngio package net.sergeych.lyngio
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi

View File

@ -170,6 +170,20 @@ class Compiler(
private val typeAliases: MutableMap<String, TypeAliasDecl> = mutableMapOf() private val typeAliases: MutableMap<String, TypeAliasDecl> = mutableMapOf()
private val methodReturnTypeDeclByRef: MutableMap<ObjRef, TypeDecl> = mutableMapOf() private val methodReturnTypeDeclByRef: MutableMap<ObjRef, TypeDecl> = mutableMapOf()
private val callReturnTypeDeclByRef: MutableMap<CallRef, TypeDecl> = mutableMapOf() private val callReturnTypeDeclByRef: MutableMap<CallRef, TypeDecl> = mutableMapOf()
private val iterableLikeTypeNames = setOf(
"Iterable",
"Collection",
"Array",
"List",
"ImmutableList",
"Set",
"ImmutableSet",
"Flow",
"ObservableList",
"RingBuffer",
"Range",
"IntRange"
)
private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf() private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf() private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = mutableMapOf() private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = mutableMapOf()
@ -2721,7 +2735,7 @@ class Compiler(
Token.Type.LPAREN -> { Token.Type.LPAREN -> {
cc.next() cc.next()
if (shouldTreatAsClassScopeCall(left, next.value)) { if (shouldTreatAsClassScopeCall(left, next.value)) {
val parsed = parseArgs(null) val parsed = parseArgs(null, implicitItTypeNameForMemberLambda(left, next.value))
val args = parsed.first val args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
isCall = true isCall = true
@ -2738,7 +2752,7 @@ class Compiler(
val receiverType = if (next.value == "apply" || next.value == "run") { val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left) inferReceiverTypeFromRef(left)
} else null } else null
val parsed = parseArgs(receiverType) val parsed = parseArgs(receiverType, implicitItTypeNameForMemberLambda(left, next.value))
val args = parsed.first val args = parsed.first
val tailBlock = parsed.second val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") { if (left is LocalVarRef && left.name == "scope") {
@ -2807,9 +2821,7 @@ class Compiler(
val receiverType = if (next.value == "apply" || next.value == "run") { val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left) inferReceiverTypeFromRef(left)
} else null } else null
val itType = if (next.value == "let" || next.value == "also") { val itType = implicitItTypeNameForMemberLambda(left, next.value)
inferReceiverTypeFromRef(left)
} else null
val lambda = parseLambdaExpression(receiverType, implicitItType = itType) val lambda = parseLambdaExpression(receiverType, implicitItType = itType)
val argPos = next.pos val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos)) val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
@ -3239,6 +3251,8 @@ class Compiler(
if (cls != null && itSlot != null) { if (cls != null && itSlot != null) {
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() } val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeMap[itSlot] = cls paramTypeMap[itSlot] = cls
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeDeclMap[itSlot] = TypeDecl.Simple(implicitItType, false)
} }
} }
@ -3384,7 +3398,12 @@ class Compiler(
} }
val itSlot = slotPlan["it"] val itSlot = slotPlan["it"]
if (itSlot != null) { if (itSlot != null) {
frame.frame.setObj(itSlot, itValue) when (itValue) {
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
else -> frame.frame.setObj(itSlot, itValue)
}
} }
} else { } else {
argsDeclaration.assignToFrame( argsDeclaration.assignToFrame(
@ -3414,6 +3433,7 @@ class Compiler(
paramSlotPlan = paramSlotPlanSnapshot, paramSlotPlan = paramSlotPlanSnapshot,
argsDeclaration = argsDeclaration, argsDeclaration = argsDeclaration,
captureEntries = captureEntries, captureEntries = captureEntries,
inferredReturnClass = returnClass,
preferredThisType = expectedReceiverType, preferredThisType = expectedReceiverType,
wrapAsExtensionCallable = wrapAsExtensionCallable, wrapAsExtensionCallable = wrapAsExtensionCallable,
returnLabels = returnLabels, returnLabels = returnLabels,
@ -4771,7 +4791,7 @@ class Compiler(
val targetClass = resolveReceiverClassForMember(ref.targetRef) val targetClass = resolveReceiverClassForMember(ref.targetRef)
classMethodReturnTypeDecl(targetClass, "getAt") classMethodReturnTypeDecl(targetClass, "getAt")
} }
is MethodCallRef -> methodReturnTypeDeclByRef[ref] is MethodCallRef -> methodReturnTypeDeclByRef[ref] ?: inferMethodCallReturnTypeDecl(ref)
is CallRef -> callReturnTypeDeclByRef[ref] ?: inferCallReturnTypeDecl(ref) is CallRef -> callReturnTypeDeclByRef[ref] ?: inferCallReturnTypeDecl(ref)
is BinaryOpRef -> inferBinaryOpReturnTypeDecl(ref) is BinaryOpRef -> inferBinaryOpReturnTypeDecl(ref)
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) } is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
@ -5012,8 +5032,7 @@ class Compiler(
} }
private fun inferMethodCallReturnClass(ref: MethodCallRef): ObjClass? { private fun inferMethodCallReturnClass(ref: MethodCallRef): ObjClass? {
val receiverDecl = resolveReceiverTypeDecl(ref.receiver) val genericReturnDecl = inferMethodCallReturnTypeDecl(ref)
val genericReturnDecl = inferMethodCallReturnTypeDecl(ref.name, receiverDecl)
if (genericReturnDecl != null) { if (genericReturnDecl != null) {
methodReturnTypeDeclByRef[ref] = genericReturnDecl methodReturnTypeDeclByRef[ref] = genericReturnDecl
resolveTypeDeclObjClass(genericReturnDecl)?.let { return it } resolveTypeDeclObjClass(genericReturnDecl)?.let { return it }
@ -5034,7 +5053,24 @@ class Compiler(
return inferMethodCallReturnClass(ref.name) return inferMethodCallReturnClass(ref.name)
} }
private fun inferMethodCallReturnTypeDecl(ref: MethodCallRef): TypeDecl? {
methodReturnTypeDeclByRef[ref]?.let { return it }
val inferred = inferMethodCallReturnTypeDecl(ref.name, resolveReceiverTypeDecl(ref.receiver), ref.args)
if (inferred != null) {
methodReturnTypeDeclByRef[ref] = inferred
}
return inferred
}
private fun inferMethodCallReturnTypeDecl(name: String, receiver: TypeDecl?): TypeDecl? { private fun inferMethodCallReturnTypeDecl(name: String, receiver: TypeDecl?): TypeDecl? {
return inferMethodCallReturnTypeDecl(name, receiver, emptyList())
}
private fun inferMethodCallReturnTypeDecl(
name: String,
receiver: TypeDecl?,
args: List<ParsedArgument>
): TypeDecl? {
val base = when (receiver) { val base = when (receiver) {
is TypeDecl.Generic -> receiver.name.substringAfterLast('.') is TypeDecl.Generic -> receiver.name.substringAfterLast('.')
is TypeDecl.Simple -> receiver.name.substringAfterLast('.') is TypeDecl.Simple -> receiver.name.substringAfterLast('.')
@ -5048,6 +5084,10 @@ class Compiler(
name == "next" && receiver is TypeDecl.Generic && base == "Iterator" -> { name == "next" && receiver is TypeDecl.Generic && base == "Iterator" -> {
receiver.args.firstOrNull() receiver.args.firstOrNull()
} }
name == "map" && base in iterableLikeTypeNames -> {
val mappedType = args.firstOrNull()?.let { inferCallableReturnTypeDeclFromArgument(it) } ?: TypeDecl.TypeAny
TypeDecl.Generic("List", listOf(mappedType), false)
}
name == "toImmutableList" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Array" || base == "List" || base == "ImmutableList") -> { name == "toImmutableList" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Array" || base == "List" || base == "ImmutableList") -> {
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
TypeDecl.Generic("ImmutableList", listOf(arg), false) TypeDecl.Generic("ImmutableList", listOf(arg), false)
@ -5120,6 +5160,79 @@ class Compiler(
} }
} }
private fun inferCallableReturnTypeDeclFromArgument(arg: ParsedArgument): TypeDecl? {
val stmt = arg.value as? ExpressionStatement ?: return null
val ref = stmt.ref
val fnType = inferTypeDeclFromRef(ref) as? TypeDecl.Function
if (fnType != null) {
return fnType.returnType
}
return when (ref) {
is ValueFnRef -> lambdaReturnTypeByRef[ref]?.let { TypeDecl.Simple(it.className, false) }
is ClassOperatorRef -> lambdaReturnTypeByRef[ref]?.let { TypeDecl.Simple(it.className, false) }
else -> null
}
}
private fun inferIterableElementTypeDecl(receiver: TypeDecl?): TypeDecl? {
return when (receiver) {
is TypeDecl.Generic -> {
val base = receiver.name.substringAfterLast('.')
when {
base in iterableLikeTypeNames -> receiver.args.firstOrNull() ?: TypeDecl.TypeAny
else -> null
}
}
is TypeDecl.Simple -> when (receiver.name.substringAfterLast('.')) {
"Range", "IntRange" -> TypeDecl.Simple("Int", false)
"String" -> TypeDecl.Simple("Char", false)
else -> null
}
else -> null
}
}
private fun inferIterableElementTypeDecl(receiver: ObjRef): TypeDecl? {
inferIterableElementTypeDecl(inferTypeDeclFromRef(receiver) ?: resolveReceiverTypeDecl(receiver))?.let {
return it
}
return when (receiver) {
is MethodCallRef -> if (receiver.name == "map") {
receiver.args.firstOrNull()?.let { inferCallableReturnTypeDeclFromArgument(it) }
} else {
null
}
else -> null
}
}
private fun implicitItTypeNameForMemberLambda(receiver: ObjRef, memberName: String): String? {
if (memberName == "fill" && isListTypeRef(receiver)) {
return "Int"
}
if (memberName == "let" || memberName == "also") {
return inferReceiverTypeFromRef(receiver)
}
val typeDecl = when (memberName) {
"forEach", "map" -> inferIterableElementTypeDecl(receiver)
else -> null
} ?: return null
return when (typeDecl) {
is TypeDecl.Simple -> typeDecl.name.substringAfterLast('.')
is TypeDecl.Generic -> typeDecl.name.substringAfterLast('.')
else -> resolveTypeDeclObjClass(typeDecl)?.className
}
}
private fun isListTypeRef(ref: ObjRef): Boolean {
return when (ref) {
is LocalVarRef -> ref.name == "List"
is LocalSlotRef -> ref.name == "List"
is FastLocalVarRef -> ref.name == "List"
else -> false
}
}
private fun inferEncodedPayloadClass(args: List<ParsedArgument>): ObjClass? { private fun inferEncodedPayloadClass(args: List<ParsedArgument>): ObjClass? {
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
val ref = stmt.ref val ref = stmt.ref
@ -6112,7 +6225,10 @@ class Compiler(
* Parse arguments list during the call and detect last block argument * Parse arguments list during the call and detect last block argument
* _following the parenthesis_ call: `(1,2) { ... }` * _following the parenthesis_ call: `(1,2) { ... }`
*/ */
private suspend fun parseArgs(expectedTailBlockReceiver: String? = null): Pair<List<ParsedArgument>, Boolean> { private suspend fun parseArgs(
expectedTailBlockReceiver: String? = null,
implicitItType: String? = null
): Pair<List<ParsedArgument>, Boolean> {
val args = mutableListOf<ParsedArgument>() val args = mutableListOf<ParsedArgument>()
suspend fun tryParseNamedArg(): ParsedArgument? { suspend fun tryParseNamedArg(): ParsedArgument? {
@ -6169,7 +6285,7 @@ class Compiler(
var lastBlockArgument = false var lastBlockArgument = false
if (end.type == Token.Type.LBRACE) { if (end.type == Token.Type.LBRACE) {
// last argument - callable // last argument - callable
val callableAccessor = parseLambdaExpression(expectedTailBlockReceiver) val callableAccessor = parseLambdaExpression(expectedTailBlockReceiver, implicitItType = implicitItType)
args += ParsedArgument( args += ParsedArgument(
ExpressionStatement(callableAccessor, end.pos), ExpressionStatement(callableAccessor, end.pos),
end.pos end.pos
@ -8891,6 +9007,12 @@ class Compiler(
is ListLiteralRef -> ObjList.type is ListLiteralRef -> ObjList.type
is MapLiteralRef -> ObjMap.type is MapLiteralRef -> ObjMap.type
is RangeRef -> ObjRange.type is RangeRef -> ObjRange.type
is LocalVarRef -> resolveReceiverTypeDecl(directRef)?.let { resolveTypeDeclObjClass(it) }
?: inferObjClassFromRef(directRef)
is FastLocalVarRef -> resolveReceiverTypeDecl(directRef)?.let { resolveTypeDeclObjClass(it) }
?: inferObjClassFromRef(directRef)
is LocalSlotRef -> resolveReceiverTypeDecl(directRef)?.let { resolveTypeDeclObjClass(it) }
?: inferObjClassFromRef(directRef)
is StatementRef -> { is StatementRef -> {
val decl = directRef.statement as? ClassDeclStatement val decl = directRef.statement as? ClassDeclStatement
decl?.let { resolveClassByName(it.typeName) } decl?.let { resolveClassByName(it.typeName) }

View File

@ -2713,9 +2713,14 @@ class BytecodeCompiler(
} }
if (target is IndexRef) { if (target is IndexRef) {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
if (!target.optionalRef) { if (!target.optionalRef) {
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot) if (elementSlotType == SlotType.INT && index.type == SlotType.INT && value.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, value.slot)
} else {
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
}
noteListElementClassMutation(receiver.slot, value) noteListElementClassMutation(receiver.slot, value)
} else { } else {
val nullSlot = allocSlot() val nullSlot = allocSlot()
@ -2728,7 +2733,11 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
) )
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot) if (elementSlotType == SlotType.INT && index.type == SlotType.INT && value.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, value.slot)
} else {
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
}
noteListElementClassMutation(receiver.slot, value) noteListElementClassMutation(receiver.slot, value)
builder.mark(endLabel) builder.mark(endLabel)
} }
@ -3047,13 +3056,12 @@ class BytecodeCompiler(
val current = allocSlot() val current = allocSlot()
val result = allocSlot() val result = allocSlot()
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref) var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
val elementClass = listElementClassBySlot[receiver.slot] ?: listElementClassFromReceiverRef(indexTarget.targetRef) val elementClass = indexElementClass(receiver.slot, indexTarget.targetRef)
if (!indexTarget.optionalRef) { if (!indexTarget.optionalRef) {
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
if (elementClass == ObjInt.type) { if (elementClass == ObjInt.type && index.type == SlotType.INT) {
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
val currentInt = allocSlot() val currentInt = allocSlot()
builder.emit(Opcode.UNBOX_INT_OBJ, current, currentInt) builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, currentInt)
updateSlotType(currentInt, SlotType.INT) updateSlotType(currentInt, SlotType.INT)
if (rhs.type != SlotType.INT) { if (rhs.type != SlotType.INT) {
coerceToArithmeticInt(ref.value, rhs)?.let { rhs = it } coerceToArithmeticInt(ref.value, rhs)?.let { rhs = it }
@ -3067,9 +3075,9 @@ class BytecodeCompiler(
else -> null else -> null
} }
if (typed != null && typed.type == SlotType.INT) { if (typed != null && typed.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, currentInt) builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, typed.slot)
noteListElementClassMutation(receiver.slot, typed) noteListElementClassMutation(receiver.slot, typed)
return CompiledValue(currentInt, SlotType.INT) return typed
} }
} }
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current) builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
@ -3321,9 +3329,14 @@ class BytecodeCompiler(
} }
is IndexRef -> { is IndexRef -> {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
if (!target.optionalRef) { if (!target.optionalRef) {
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot) if (elementSlotType == SlotType.INT && index.type == SlotType.INT && newValue.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, newValue.slot)
} else {
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
}
} else { } else {
val recvNull = allocSlot() val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull) builder.emit(Opcode.CONST_NULL, recvNull)
@ -3335,7 +3348,11 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel)) listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
) )
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot) if (elementSlotType == SlotType.INT && index.type == SlotType.INT && newValue.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, newValue.slot)
} else {
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
}
builder.mark(skipLabel) builder.mark(skipLabel)
} }
} }
@ -3603,9 +3620,15 @@ class BytecodeCompiler(
private fun compileIndexRef(ref: IndexRef): CompiledValue? { private fun compileIndexRef(ref: IndexRef): CompiledValue? {
val receiver = compileRefWithFallback(ref.targetRef, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(ref.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, ref.targetRef)
val dst = allocSlot() val dst = allocSlot()
if (!ref.optionalRef) { if (!ref.optionalRef) {
val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null
if (elementSlotType == SlotType.INT && index.type == SlotType.INT) {
builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, dst)
updateSlotType(dst, SlotType.INT)
return CompiledValue(dst, SlotType.INT)
}
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst) builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst)
} else { } else {
val nullSlot = allocSlot() val nullSlot = allocSlot()
@ -4234,6 +4257,7 @@ class BytecodeCompiler(
val indexTarget = ref.target as? IndexRef ?: return null val indexTarget = ref.target as? IndexRef ?: return null
val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, indexTarget.targetRef)
if (indexTarget.optionalRef) { if (indexTarget.optionalRef) {
val resultSlot = allocSlot() val resultSlot = allocSlot()
val nullSlot = allocSlot() val nullSlot = allocSlot()
@ -4273,6 +4297,24 @@ class BytecodeCompiler(
return CompiledValue(resultSlot, SlotType.OBJ) return CompiledValue(resultSlot, SlotType.OBJ)
} }
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
if (elementSlotType == SlotType.INT && index.type == SlotType.INT) {
val current = allocSlot()
builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, current)
updateSlotType(current, SlotType.INT)
val oneSlot = allocSlot()
val oneId = builder.addConst(BytecodeConst.IntVal(1))
builder.emit(Opcode.CONST_INT, oneId, oneSlot)
updateSlotType(oneSlot, SlotType.INT)
val result = allocSlot()
val op = if (ref.isIncrement) Opcode.ADD_INT else Opcode.SUB_INT
builder.emit(op, current, oneSlot, result)
updateSlotType(result, SlotType.INT)
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, result)
if (wantResult && ref.isPost) {
return CompiledValue(current, SlotType.INT)
}
return CompiledValue(result, SlotType.INT)
}
val current = allocSlot() val current = allocSlot()
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current) builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
updateSlotType(current, SlotType.OBJ) updateSlotType(current, SlotType.OBJ)
@ -4636,6 +4678,7 @@ class BytecodeCompiler(
} }
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
compileListFillIntCall(ref)?.let { return it }
val callPos = callSitePos() val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
@ -4814,6 +4857,22 @@ class BytecodeCompiler(
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
private fun compileListFillIntCall(ref: MethodCallRef): CompiledValue? {
if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null
if (ref.args.size != 2 || ref.args.any { it.isSplat || it.name != null }) return null
val lambdaRef = ((ref.args[1].value as? ExpressionStatement)?.ref as? LambdaFnRef) ?: return null
if (lambdaRef.inferredReturnClass != ObjInt.type) return null
val size = compileArgValue(ref.args[0].value) ?: return null
if (size.type != SlotType.INT) return null
val callable = ensureObjSlot(compileArgValue(ref.args[1].value) ?: return null)
val dst = allocSlot()
builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst)
updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type
listElementClassBySlot[dst] = ObjInt.type
return CompiledValue(dst, SlotType.OBJ)
}
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? { private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
val callPos = callSitePos() val callPos = callSitePos()
val receiver = compileThisRef() val receiver = compileThisRef()
@ -6119,6 +6178,42 @@ class BytecodeCompiler(
} }
return when (directRef) { return when (directRef) {
is ListLiteralRef -> listElementClassFromListLiteralRef(directRef) is ListLiteralRef -> listElementClassFromListLiteralRef(directRef)
is MethodCallRef -> listElementClassFromMethodCallRef(directRef)
else -> null
}
}
private fun listElementClassFromMethodCallRef(ref: MethodCallRef): ObjClass? {
if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null
val block = ref.args.lastOrNull() ?: return null
return supportedListElementClass(listElementClassFromFillValue(block.value))
}
private fun listElementClassFromFillValue(value: Obj): ObjClass? {
val expr = value as? ExpressionStatement ?: return null
val ref = when (val directRef = expr.ref) {
is StatementRef -> (directRef.statement as? ExpressionStatement)?.ref
else -> directRef
} ?: return null
return when (ref) {
is ConstRef -> elementClassFromConst(ref.constValue)
is LambdaFnRef -> ref.inferredReturnClass
else -> null
}
}
private fun isListTypeRef(ref: ObjRef): Boolean {
return when (ref) {
is LocalVarRef -> ref.name == "List"
is LocalSlotRef -> ref.name == "List"
is FastLocalVarRef -> ref.name == "List"
else -> false
}
}
private fun supportedListElementClass(cls: ObjClass?): ObjClass? {
return when (cls) {
ObjInt.type, ObjReal.type, ObjString.type, ObjBool.type -> cls
else -> null else -> null
} }
} }
@ -7774,6 +7869,12 @@ class BytecodeCompiler(
} }
} }
private fun indexElementClass(receiverSlot: Int, targetRef: ObjRef): ObjClass? =
listElementClassBySlot[receiverSlot] ?: listElementClassFromReceiverRef(targetRef)
private fun indexElementSlotType(receiverSlot: Int, targetRef: ObjRef): SlotType? =
slotTypeFromClass(indexElementClass(receiverSlot, targetRef))
private fun prepareCompilation(stmt: Statement) { private fun prepareCompilation(stmt: Statement) {
builder = CmdBuilder() builder = CmdBuilder()
nextSlot = 0 nextSlot = 0
@ -8860,31 +8961,14 @@ class BytecodeCompiler(
SlotType.OBJ -> { SlotType.OBJ -> {
val isExactInt = isExactNonNullSlotClassOrTemp(value.slot, ObjInt.type) val isExactInt = isExactNonNullSlotClassOrTemp(value.slot, ObjInt.type)
val isStableIntObj = slotObjClass[value.slot] == ObjInt.type && isStablePrimitiveSourceSlot(value.slot) val isStableIntObj = slotObjClass[value.slot] == ObjInt.type && isStablePrimitiveSourceSlot(value.slot)
if (!isExactInt && !isStableIntObj && !isStablePrimitiveSourceSlot(value.slot)) return null if (!isExactInt && !isStableIntObj) return null
val objSlot = if (isExactInt || isStableIntObj) { val objSlot = value.slot
value.slot
} else {
val boxed = allocSlot()
builder.emit(Opcode.BOX_OBJ, value.slot, boxed)
updateSlotType(boxed, SlotType.OBJ)
emitAssertObjSlotIsInt(boxed)
}
val intSlot = allocSlot() val intSlot = allocSlot()
builder.emit(Opcode.UNBOX_INT_OBJ, objSlot, intSlot) builder.emit(Opcode.UNBOX_INT_OBJ, objSlot, intSlot)
updateSlotType(intSlot, SlotType.INT) updateSlotType(intSlot, SlotType.INT)
CompiledValue(intSlot, SlotType.INT) CompiledValue(intSlot, SlotType.INT)
} }
SlotType.UNKNOWN -> { SlotType.UNKNOWN -> null
if (!isStablePrimitiveSourceSlot(value.slot)) return null
val boxed = allocSlot()
builder.emit(Opcode.BOX_OBJ, value.slot, boxed)
updateSlotType(boxed, SlotType.OBJ)
val checked = emitAssertObjSlotIsInt(boxed)
val intSlot = allocSlot()
builder.emit(Opcode.UNBOX_INT_OBJ, checked, intSlot)
updateSlotType(intSlot, SlotType.INT)
CompiledValue(intSlot, SlotType.INT)
}
else -> null else -> null
} }
} }
@ -8980,12 +9064,7 @@ class BytecodeCompiler(
} }
private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? { private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? {
if (rangeLocalNames.isEmpty()) return null return null
val target = if (source is BytecodeStatement) source.original else source
val expr = target as? ExpressionStatement ?: return null
val localRef = expr.ref as? LocalSlotRef ?: return null
if (localRef.isDelegated) return null
return if (rangeLocalNames.contains(localRef.name)) localRef else null
} }
private data class ScopeSlotKey(val scopeId: Int, val slot: Int) private data class ScopeSlotKey(val scopeId: Int, val slot: Int)

View File

@ -227,6 +227,12 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.GET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.MAKE_RANGE -> Opcode.MAKE_RANGE ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
@ -827,6 +833,9 @@ class CmdBuilder {
Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4]) Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2]) Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.GET_INDEX_INT -> CmdGetIndexInt(operands[0], operands[1], operands[2])
Opcode.SET_INDEX_INT -> CmdSetIndexInt(operands[0], operands[1], operands[2])
Opcode.LIST_FILL_INT -> CmdListFillInt(operands[0], operands[1], operands[2])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3]) Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])

View File

@ -493,6 +493,9 @@ object CmdDisassembler {
is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst) is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst) is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdGetIndexInt -> Opcode.GET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndexInt -> Opcode.SET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdListFillInt -> Opcode.LIST_FILL_INT to intArrayOf(cmd.sizeSlot, cmd.callableSlot, cmd.dst)
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst) is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst) is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot) is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
@ -612,6 +615,12 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX -> Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.GET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_MEMBER_SLOT -> Opcode.GET_MEMBER_SLOT ->

View File

@ -3352,6 +3352,31 @@ class CmdListLiteral(
} }
} }
class CmdListFillInt(
internal val sizeSlot: Int,
internal val callableSlot: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val size = frame.getInt(sizeSlot).toInt()
if (size < 0) frame.ensureScope().raiseIllegalArgument("list size must be non-negative")
val callable = frame.storedSlotObj(callableSlot)
val scope = frame.ensureScope()
val result = ObjList(LongArray(size))
for (i in 0 until size) {
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
callable.invokeImplicitIntArg(scope, i.toLong())
} else {
callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong()))))
}
val intValue = (value as? ObjInt)?.value ?: scope.raiseClassCastError("expected Int fill result")
result.setIntAtFast(i, intValue)
}
frame.storeObjResult(dst, result)
return
}
}
private fun decodeMemberId(id: Int): Pair<Int, Boolean> { private fun decodeMemberId(id: Int): Pair<Int, Boolean> {
return if (id <= -2) { return if (id <= -2) {
Pair(-id - 2, true) Pair(-id - 2, true)
@ -3709,7 +3734,7 @@ class CmdGetIndex(
val target = frame.storedSlotObj(targetSlot) val target = frame.storedSlotObj(targetSlot)
val index = frame.storedSlotObj(indexSlot) val index = frame.storedSlotObj(indexSlot)
if (target is ObjList && target::class == ObjList::class && index is ObjInt) { if (target is ObjList && target::class == ObjList::class && index is ObjInt) {
frame.storeObjResult(dst, target.list[index.toInt()]) frame.storeObjResult(dst, target.getObjAtFast(index.toInt()))
return return
} }
val result = target.getAt(frame.ensureScope(), index) val result = target.getAt(frame.ensureScope(), index)
@ -3728,7 +3753,7 @@ class CmdSetIndex(
val index = frame.storedSlotObj(indexSlot) val index = frame.storedSlotObj(indexSlot)
val value = frame.slotToObj(valueSlot) val value = frame.slotToObj(valueSlot)
if (target is ObjList && target::class == ObjList::class && index is ObjInt) { if (target is ObjList && target::class == ObjList::class && index is ObjInt) {
target.list[index.toInt()] = value target.setObjAtFast(index.toInt(), value)
return return
} }
target.putAt(frame.ensureScope(), index, value) target.putAt(frame.ensureScope(), index, value)
@ -3736,6 +3761,47 @@ class CmdSetIndex(
} }
} }
class CmdGetIndexInt(
internal val targetSlot: Int,
internal val indexSlot: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val target = frame.storedSlotObj(targetSlot)
val index = frame.getInt(indexSlot).toInt()
if (target is ObjList && target::class == ObjList::class) {
target.getIntAtFast(index)?.let {
frame.setInt(dst, it)
return
}
}
val result = target.getAt(frame.ensureScope(), ObjInt.of(index.toLong()))
if (result is ObjInt) {
frame.setInt(dst, result.value)
return
}
frame.ensureScope().raiseClassCastError("expected Int list element")
}
}
class CmdSetIndexInt(
internal val targetSlot: Int,
internal val indexSlot: Int,
internal val valueSlot: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val target = frame.storedSlotObj(targetSlot)
val index = frame.getInt(indexSlot).toInt()
if (target is ObjList && target::class == ObjList::class) {
target.setIntAtFast(index, frame.getInt(valueSlot))
return
}
val value = ObjInt.of(frame.getInt(valueSlot))
target.putAt(frame.ensureScope(), ObjInt.of(index.toLong()), value)
return
}
}
class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() { class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn
@ -3788,6 +3854,31 @@ class BytecodeLambdaCallable(
) )
} }
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = Arguments.EMPTY
}
if (captureRecords != null) {
context.captureRecords = captureRecords
context.captureNames = captureNames
} else if (captureNames.isNotEmpty()) {
closureScope.raiseIllegalState("bytecode lambda capture records missing")
}
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
paramSlotPlan["it"]?.let { itSlot ->
frame.frame.setInt(itSlot, arg)
}
}
return try {
CmdVm().execute(fn, context, Arguments.EMPTY, binder)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
}
}
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = scope.args it.args = scope.args
@ -3818,7 +3909,12 @@ class BytecodeLambdaCallable(
} }
val itSlot = slotPlan["it"] val itSlot = slotPlan["it"]
if (itSlot != null) { if (itSlot != null) {
frame.frame.setObj(itSlot, itValue) when (itValue) {
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
else -> frame.frame.setObj(itSlot, itValue)
}
} }
} else { } else {
argsDeclaration.assignToFrame( argsDeclaration.assignToFrame(

View File

@ -177,7 +177,10 @@ enum class Opcode(val code: Int) {
GET_INDEX(0xA2), GET_INDEX(0xA2),
SET_INDEX(0xA3), SET_INDEX(0xA3),
GET_INDEX_INT(0xA4),
LIST_LITERAL(0xA5), LIST_LITERAL(0xA5),
SET_INDEX_INT(0xA6),
LIST_FILL_INT(0xA7),
GET_MEMBER_SLOT(0xA8), GET_MEMBER_SLOT(0xA8),
SET_MEMBER_SLOT(0xA9), SET_MEMBER_SLOT(0xA9),
GET_CLASS_SCOPE(0xAA), GET_CLASS_SCOPE(0xAA),

View File

@ -12,6 +12,7 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*
*/ */
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
@ -28,6 +29,7 @@ class LambdaFnRef(
val paramSlotPlan: Map<String, Int>, val paramSlotPlan: Map<String, Int>,
val argsDeclaration: ArgsDeclaration?, val argsDeclaration: ArgsDeclaration?,
val captureEntries: List<LambdaCaptureEntry>, val captureEntries: List<LambdaCaptureEntry>,
val inferredReturnClass: ObjClass?,
val preferredThisType: String?, val preferredThisType: String?,
val wrapAsExtensionCallable: Boolean, val wrapAsExtensionCallable: Boolean,
val returnLabels: Set<String>, val returnLabels: Set<String>,

View File

@ -19,8 +19,8 @@ package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
@ -29,7 +29,120 @@ import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() { open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
private var boxedList: MutableList<Obj>? = null
private var primitiveIntList: LongArray? = null
init {
if (!adoptPrimitiveIntList(initialList)) {
boxedList = initialList
}
}
val list: MutableList<Obj>
get() = ensureBoxedList()
internal fun sizeFast(): Int = primitiveIntList?.size ?: boxedList?.size ?: 0
internal fun getObjAtFast(index: Int): Obj =
primitiveIntList?.let { ObjInt.of(it[index]) } ?: boxedList!![index]
internal fun getIntAtFast(index: Int): Long? =
primitiveIntList?.get(index) ?: (boxedList?.get(index) as? ObjInt)?.value
internal fun setObjAtFast(index: Int, value: Obj) {
val ints = primitiveIntList
if (ints != null) {
if (value is ObjInt) {
ints[index] = value.value
return
}
ensureBoxedList()[index] = value
return
}
boxedList!![index] = value
}
internal fun setIntAtFast(index: Int, value: Long) {
val ints = primitiveIntList
if (ints != null) {
ints[index] = value
return
}
boxedList?.let {
if (it[index] is ObjInt) {
it[index] = ObjInt.of(value)
return
}
}
ensureBoxedList()[index] = ObjInt.of(value)
}
internal fun appendFast(value: Obj) {
val ints = primitiveIntList
if (ints != null && value is ObjInt) {
primitiveIntList = ints.copyOf(ints.size + 1).also { it[ints.size] = value.value }
return
}
ensureBoxedList().add(value)
}
internal fun appendAllFast(other: ObjList) {
val ints = primitiveIntList
val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) {
primitiveIntList = LongArray(ints.size + otherInts.size).also {
ints.copyInto(it, 0, 0, ints.size)
otherInts.copyInto(it, ints.size, 0, otherInts.size)
}
return
}
ensureBoxedList().addAll(other.list)
}
private fun adoptPrimitiveIntList(items: List<Obj>): Boolean {
if (items.isEmpty()) return false
val ints = LongArray(items.size)
for (i in items.indices) {
val value = items[i] as? ObjInt ?: return false
ints[i] = value.value
}
primitiveIntList = ints
boxedList = null
return true
}
private fun ensureBoxedList(): MutableList<Obj> {
boxedList?.let { return it }
val ints = primitiveIntList
if (ints == null) {
val empty = mutableListOf<Obj>()
boxedList = empty
return empty
}
val materialized = ArrayList<Obj>(ints.size)
for (value in ints) {
materialized.add(ObjInt.of(value))
}
boxedList = materialized
primitiveIntList = null
return materialized
}
private fun sliceRange(start: Int, endExclusive: Int): Obj {
val ints = primitiveIntList
return if (ints != null) {
ObjList(ints.copyOfRange(start, endExclusive))
} else {
ObjList(list.subList(start, endExclusive).toMutableList())
}
}
internal constructor(intValues: LongArray) : this(mutableListOf()) {
primitiveIntList = intValues
boxedList = null
}
protected open fun shouldTreatAsSingleElement(scope: Scope, other: Obj): Boolean { protected open fun shouldTreatAsSingleElement(scope: Scope, other: Obj): Boolean {
if (!other.isInstanceOf(ObjIterable)) return true if (!other.isInstanceOf(ObjIterable)) return true
val declaredElementType = scope.declaredListElementTypeForValue(this) val declaredElementType = scope.declaredListElementTypeForValue(this)
@ -48,9 +161,9 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
return false return false
} }
if (list.size != other.list.size) return false if (sizeFast() != other.sizeFast()) return false
for (i in 0..<list.size) { for (i in 0..<sizeFast()) {
if (!list[i].equals(scope, other.list[i])) return false if (!getObjAtFast(i).equals(scope, other.getObjAtFast(i))) return false
} }
return true return true
} }
@ -58,31 +171,31 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
return when (index) { return when (index) {
is ObjInt -> { is ObjInt -> {
list[index.toInt()] getObjAtFast(index.toInt())
} }
is ObjRange -> { is ObjRange -> {
when { when {
index.start is ObjInt && index.end is ObjInt -> { index.start is ObjInt && index.end is ObjInt -> {
if (index.isEndInclusive) if (index.isEndInclusive)
ObjList(list.subList(index.start.toInt(), index.end.toInt() + 1).toMutableList()) sliceRange(index.start.toInt(), index.end.toInt() + 1)
else else
ObjList(list.subList(index.start.toInt(), index.end.toInt()).toMutableList()) sliceRange(index.start.toInt(), index.end.toInt())
} }
index.isOpenStart && !index.isOpenEnd -> { index.isOpenStart && !index.isOpenEnd -> {
if (index.isEndInclusive) if (index.isEndInclusive)
ObjList(list.subList(0, index.end!!.toInt() + 1).toMutableList()) sliceRange(0, index.end!!.toInt() + 1)
else else
ObjList(list.subList(0, index.end!!.toInt()).toMutableList()) sliceRange(0, index.end!!.toInt())
} }
index.isOpenEnd && !index.isOpenStart -> { index.isOpenEnd && !index.isOpenStart -> {
ObjList(list.subList(index.start!!.toInt(), list.size).toMutableList()) sliceRange(index.start!!.toInt(), sizeFast())
} }
index.isOpenStart && index.isOpenEnd -> { index.isOpenStart && index.isOpenEnd -> {
ObjList(list.toMutableList()) sliceRange(0, sizeFast())
} }
else -> { else -> {
@ -96,16 +209,16 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
open override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) { open override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
list[index.toInt()] = newValue setObjAtFast(index.toInt(), newValue)
} }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other is ObjList) { if (other is ObjList) {
val mySize = list.size val mySize = sizeFast()
val otherSize = other.list.size val otherSize = other.sizeFast()
val commonSize = minOf(mySize, otherSize) val commonSize = minOf(mySize, otherSize)
for (i in 0..<commonSize) { for (i in 0..<commonSize) {
val d = list[i].compareTo(scope, other.list[i]) val d = getObjAtFast(i).compareTo(scope, other.getObjAtFast(i))
if (d != 0) { if (d != 0) {
return d return d
} }
@ -114,14 +227,13 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return res return res
} }
if (other.isInstanceOf(ObjIterable)) { if (other.isInstanceOf(ObjIterable)) {
val it1 = this.list.iterator()
val it2 = other.invokeInstanceMethod(scope, "iterator") val it2 = other.invokeInstanceMethod(scope, "iterator")
val hasNext2 = it2.getInstanceMethod(scope, "hasNext") val hasNext2 = it2.getInstanceMethod(scope, "hasNext")
val next2 = it2.getInstanceMethod(scope, "next") val next2 = it2.getInstanceMethod(scope, "next")
while (it1.hasNext()) { for (i in 0..<sizeFast()) {
if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer
val v1 = it1.next() val v1 = getObjAtFast(i)
val v2 = next2.invoke(scope, it2) val v2 = next2.invoke(scope, it2)
val d = v1.compareTo(scope, v2) val d = v1.compareTo(scope, v2)
if (d != 0) return d if (d != 0) return d
@ -133,8 +245,18 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun plus(scope: Scope, other: Obj): Obj = override suspend fun plus(scope: Scope, other: Obj): Obj =
when { when {
other is ObjList -> other is ObjList -> {
ObjList((list + other.list).toMutableList()) val ints = primitiveIntList
val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) {
ObjList(LongArray(ints.size + otherInts.size).also {
ints.copyInto(it, 0, 0, ints.size)
otherInts.copyInto(it, ints.size, 0, otherInts.size)
})
} else {
ObjList((list + other.list).toMutableList())
}
}
!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable) -> { !shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable) -> {
val l = other.callMethod<ObjList>(scope, "toList") val l = other.callMethod<ObjList>(scope, "toList")
@ -151,12 +273,12 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
open override suspend fun plusAssign(scope: Scope, other: Obj): Obj { open override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
if (other is ObjList) { if (other is ObjList) {
list.addAll(other.list) appendAllFast(other)
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) { } else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
list.addAll(otherList) list.addAll(otherList)
} else { } else {
list.add(other) appendFast(other)
} }
return this return this
} }
@ -199,6 +321,13 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
override suspend fun contains(scope: Scope, other: Obj): Boolean { override suspend fun contains(scope: Scope, other: Obj): Boolean {
val ints = primitiveIntList
if (ints != null && other is ObjInt) {
for (value in ints) {
if (value == other.value) return true
}
return false
}
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
// Fast path: int membership in a list of ints (common case in benches) // Fast path: int membership in a list of ints (common case in benches)
if (other is ObjInt) { if (other is ObjInt) {
@ -216,6 +345,13 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
val ints = primitiveIntList
if (ints != null) {
for (value in ints) {
if (!callback(ObjInt.of(value))) break
}
return
}
for (item in list) { for (item in list) {
if (!callback(item)) break if (!callback(item)) break
} }
@ -225,6 +361,8 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
get() = type get() = type
override suspend fun toKotlin(scope: Scope): Any { override suspend fun toKotlin(scope: Scope): Any {
val ints = primitiveIntList
if (ints != null) return ints.map { it }
return list.map { it.toKotlin(scope) } return list.map { it.toKotlin(scope) }
} }
@ -256,8 +394,7 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
override fun hashCode(): Int { override fun hashCode(): Int {
// check? return primitiveIntList?.contentHashCode() ?: list.hashCode()
return list.hashCode()
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -266,16 +403,31 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
other as ObjList other as ObjList
return list == other.list val ints = primitiveIntList
val otherInts = other.primitiveIntList
return if (ints != null && otherInts != null) {
ints.contentEquals(otherInts)
} else {
list == other.list
}
} }
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) { override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeAnyList(scope,list) val ints = primitiveIntList
if (ints != null) {
encoder.encodeAnyList(scope, ints.mapTo(ArrayList(ints.size)) { ObjInt.of(it) })
return
}
encoder.encodeAnyList(scope, list)
} }
override suspend fun lynonType(): LynonType = LynonType.List override suspend fun lynonType(): LynonType = LynonType.List
override suspend fun toJson(scope: Scope): JsonElement { override suspend fun toJson(scope: Scope): JsonElement {
val ints = primitiveIntList
if (ints != null) {
return JsonArray(ints.map { ObjInt.of(it).toJson(scope) })
}
return JsonArray(list.map { it.toJson(scope) }) return JsonArray(list.map { it.toJson(scope) })
} }
@ -283,9 +435,17 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return ObjString(buildString { return ObjString(buildString {
append("[") append("[")
var first = true var first = true
for (v in list) { val ints = primitiveIntList
if (first) first = false else append(",") if (ints != null) {
append(v.toString(scope).value) for (v in ints) {
if (first) first = false else append(",")
append(v)
}
} else {
for (v in list) {
if (first) first = false else append(",")
append(v.toString(scope).value)
}
} }
append("]") append("]")
}) })
@ -307,7 +467,7 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = {
val s = (this.thisObj as ObjList).list.size val s = (this.thisObj as ObjList).sizeFast()
s.toObj() s.toObj()
} }
) )

View File

@ -16,21 +16,11 @@
*/ */
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler import net.sergeych.lyng.*
import net.sergeych.lyng.ExecutionError
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Script
import net.sergeych.lyng.ScriptError
import net.sergeych.lyng.Source
import net.sergeych.lyng.eval
import net.sergeych.lyng.toSource
import net.sergeych.lyng.bytecode.CmdDisassembler
import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue import kotlin.test.assertTrue
class BytecodeRecentOpsTest { class BytecodeRecentOpsTest {
@ -129,6 +119,79 @@ class BytecodeRecentOpsTest {
) )
} }
@Test
fun intListIndexOpsUsePrimitiveBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
var a: List<Int> = [1, 2, 3]
val s = a[1]
a[1] += 3
a[1]++
a[1] = s
a[1]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("GET_INDEX_INT"), disasm)
assertTrue(disasm.contains("SET_INDEX_INT"), disasm)
assertEquals(2, scope.eval("calc()").toInt())
}
@Test
fun listFillIntIndexOpsUsePrimitiveBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
var a = List.fill(4) { 2 }
val s = a[1]
a[1] += 3
a[1] = s
a[1]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("GET_INDEX_INT"), disasm)
assertTrue(disasm.contains("SET_INDEX_INT"), disasm)
assertEquals(2, scope.eval("calc()").toInt())
}
@Test
fun listFillIntUsesPrimitiveFillBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val xs = List.fill(5) { 2 }
xs[0] + xs[4]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("LIST_FILL_INT"), disasm)
assertEquals(4, scope.eval("calc()").toInt())
}
@Test
fun listFillIntWithIndexLambdaKeepsSemantics() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val xs = List.fill(5) { it * 3 }
xs[0] + xs[4]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("LIST_FILL_INT"), disasm)
assertEquals(12, scope.eval("calc()").toInt())
}
@Test @Test
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest { fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
eval( eval(

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -97,7 +97,7 @@ class TestCoroutines {
counter = c + 1 counter = c + 1
// } // }
} }
}.forEach { (it as Deferred).await() } }.forEach { it.await() }
println(counter) println(counter)
assert( counter < 10 ) assert( counter < 10 )
@ -105,6 +105,21 @@ class TestCoroutines {
) )
} }
@Test
fun testMapForEachDeferredInference() = runTest {
eval(
"""
var sum = 0
(1..3).map { n ->
launch { n }
}.forEach { sum += it.await() }
assertEquals(6, sum)
""".trimIndent()
)
}
@Test @Test
fun testFlows() = runTest { fun testFlows() = runTest {
eval(""" eval("""

View File

@ -16,25 +16,13 @@
*/ */
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Benchmarks import net.sergeych.lyng.*
import net.sergeych.lyng.BytecodeBodyProvider import net.sergeych.lyng.bytecode.*
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.PerfProfiles
import net.sergeych.lyng.Script
import net.sergeych.lyng.Statement
import net.sergeych.lyng.bytecode.BytecodeStatement
import net.sergeych.lyng.bytecode.CmdCallMemberSlot
import net.sergeych.lyng.bytecode.CmdFunction
import net.sergeych.lyng.bytecode.CmdGetIndex
import net.sergeych.lyng.bytecode.CmdIterPush
import net.sergeych.lyng.bytecode.CmdMakeRange
import net.sergeych.lyng.bytecode.CmdSetIndex
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.time.TimeSource import kotlin.time.TimeSource
class PiSpigotBenchmarkTest { class PiSpigotBenchmarkTest {
@ -42,17 +30,11 @@ class PiSpigotBenchmarkTest {
fun benchmarkPiSpigot() = runTest { fun benchmarkPiSpigot() = runTest {
if (!Benchmarks.enabled) return@runTest if (!Benchmarks.enabled) return@runTest
val source = Files.readString(resolveExample("pi-test.lyng")) val source = loadPiSpigotSource()
val legacySource = source.replace(
"val quotient = sum / denom",
"var quotient = floor((sum / (denom * 1.0))).toInt()"
)
assertTrue(legacySource != source, "failed to build legacy piSpigot benchmark case")
val digits = 200 val digits = 200
val expectedSuffix = "49303819" val expectedSuffix = "49303819"
val legacyElapsed = runCase("legacy-real-division", legacySource, digits, expectedSuffix, dumpBytecode = true)
val saved = PerfProfiles.snapshot() val saved = PerfProfiles.snapshot()
PerfFlags.RVAL_FASTPATH = false PerfFlags.RVAL_FASTPATH = false
val optimizedRvalOffElapsed = runCase( val optimizedRvalOffElapsed = runCase(
@ -64,15 +46,11 @@ class PiSpigotBenchmarkTest {
) )
PerfProfiles.restore(saved) PerfProfiles.restore(saved)
val optimizedElapsed = runCase("optimized-int-division-rval-on", source, digits, expectedSuffix, dumpBytecode = true) val optimizedElapsed = runCase("optimized-int-division-rval-on", source, digits, expectedSuffix, dumpBytecode = true)
val sourceSpeedup = legacyElapsed.toDouble() / optimizedRvalOffElapsed.toDouble()
val runtimeSpeedup = optimizedRvalOffElapsed.toDouble() / optimizedElapsed.toDouble() val runtimeSpeedup = optimizedRvalOffElapsed.toDouble() / optimizedElapsed.toDouble()
val totalSpeedup = legacyElapsed.toDouble() / optimizedElapsed.toDouble()
println( println(
"[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits legacy=${legacyElapsed} ms " + "[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits " +
"intDiv=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " + "rvalOff=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
"intDivSpeedup=${"%.2f".format(sourceSpeedup)}x " + "rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x"
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x " +
"total=${"%.2f".format(totalSpeedup)}x"
) )
} }
@ -91,18 +69,18 @@ class PiSpigotBenchmarkTest {
dumpHotOps(scope, "piSpigot") dumpHotOps(scope, "piSpigot")
} }
val first = scope.eval("piSpigot($digits)") as ObjString val first = scope.eval("piSpigot(0, $digits)") as ObjString
assertEquals(expectedSuffix, first.value) assertEquals(expectedSuffix, first.value)
repeat(2) { repeat(2) {
val warm = scope.eval("piSpigot($digits)") as ObjString val warm = scope.eval("piSpigot(0, $digits)") as ObjString
assertEquals(expectedSuffix, warm.value) assertEquals(expectedSuffix, warm.value)
} }
val iterations = 3 val iterations = 3
val start = TimeSource.Monotonic.markNow() val start = TimeSource.Monotonic.markNow()
repeat(iterations) { repeat(iterations) {
val result = scope.eval("piSpigot($digits)") as ObjString val result = scope.eval("piSpigot(0, $digits)") as ObjString
assertEquals(expectedSuffix, result.value) assertEquals(expectedSuffix, result.value)
} }
val elapsedMs = start.elapsedNow().inWholeMilliseconds val elapsedMs = start.elapsedNow().inWholeMilliseconds
@ -135,6 +113,11 @@ class PiSpigotBenchmarkTest {
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction() ?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
} }
private fun loadPiSpigotSource(): String {
val source = Files.readString(resolveExample("pi-bench.lyng"))
return source.substringBefore("\nval t0 = Instant()")
}
private fun resolveExample(name: String): Path { private fun resolveExample(name: String): Path {
val direct = Path.of("examples", name) val direct = Path.of("examples", name)
if (Files.exists(direct)) return direct if (Files.exists(direct)) return direct