Compare commits

..

No commits in common. "90718c3c17aeb541574f22cb2aacad199693e323" and "161f3f74e20dee765c5301b7c36b377f4030745b" have entirely different histories.

28 changed files with 265 additions and 1113 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:
val r = 'a'..'c'
val r = 'a' .. 'c'
assert( 'b' in r)
assert( 'e' !in r)
for( ch in r )

View File

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

View File

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

View File

@ -1,83 +0,0 @@
# 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))

49
examples/pi-test.lyng Normal file
View File

@ -0,0 +1,49 @@
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,11 +20,9 @@ import kotlinx.coroutines.runBlocking
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Script
import net.sergeych.lyng.Source
import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.obj.ObjString
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
class CliDispatcherJvmTest {
@ -71,34 +69,4 @@ class CliDispatcherJvmTest {
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,9 +17,7 @@
package net.sergeych.lyng.io.net
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script
@ -31,9 +29,9 @@ import kotlin.test.assertEquals
class LyngNetTcpServerExampleTest {
@Test
fun tcpServerExampleRoundTripsOverLoopback() = runTest {
fun tcpServerExampleRoundTripsOverLoopback() = runBlocking {
val engine = getSystemNetEngine()
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runTest
if (!engine.isSupported || !engine.isTcpAvailable || !engine.isTcpServerAvailable) return@runBlocking
val scope = Script.newScope()
createNetModule(PermitAllNetAccessPolicy, scope)
@ -62,10 +60,8 @@ class LyngNetTcpServerExampleTest {
"${'$'}{accepted.await()}: ${'$'}reply"
""".trimIndent()
val result = withContext(Dispatchers.Default) {
withTimeout(5_000) {
Compiler.compile(code).execute(scope).inspect(scope)
}
val result = withTimeout(5_000) {
Compiler.compile(code).execute(scope).inspect(scope)
}
assertEquals("\"ping: echo:ping\"", result)

View File

@ -1,20 +1,3 @@
/*
* 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
import kotlinx.coroutines.DelicateCoroutinesApi

View File

@ -1,20 +1,3 @@
/*
* 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
import kotlinx.coroutines.DelicateCoroutinesApi

View File

@ -17,8 +17,6 @@
package net.sergeych.lyng.bytecode
internal actual val vmIterDebugEnabled: Boolean = false
internal actual fun vmIterDebugWrite(message: String, error: Throwable?) {
internal actual fun vmIterDebug(message: String, error: Throwable?) {
// no-op on Android
}

View File

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

View File

@ -2713,14 +2713,9 @@ class BytecodeCompiler(
}
if (target is IndexRef) {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
if (!target.optionalRef) {
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
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)
}
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
noteListElementClassMutation(receiver.slot, value)
} else {
val nullSlot = allocSlot()
@ -2733,11 +2728,7 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
)
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
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)
}
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot)
noteListElementClassMutation(receiver.slot, value)
builder.mark(endLabel)
}
@ -3056,12 +3047,13 @@ class BytecodeCompiler(
val current = allocSlot()
val result = allocSlot()
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
val elementClass = indexElementClass(receiver.slot, indexTarget.targetRef)
val elementClass = listElementClassBySlot[receiver.slot] ?: listElementClassFromReceiverRef(indexTarget.targetRef)
if (!indexTarget.optionalRef) {
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
if (elementClass == ObjInt.type && index.type == SlotType.INT) {
if (elementClass == ObjInt.type) {
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
val currentInt = allocSlot()
builder.emit(Opcode.GET_INDEX_INT, receiver.slot, index.slot, currentInt)
builder.emit(Opcode.UNBOX_INT_OBJ, current, currentInt)
updateSlotType(currentInt, SlotType.INT)
if (rhs.type != SlotType.INT) {
coerceToArithmeticInt(ref.value, rhs)?.let { rhs = it }
@ -3075,9 +3067,9 @@ class BytecodeCompiler(
else -> null
}
if (typed != null && typed.type == SlotType.INT) {
builder.emit(Opcode.SET_INDEX_INT, receiver.slot, index.slot, typed.slot)
builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, currentInt)
noteListElementClassMutation(receiver.slot, typed)
return typed
return CompiledValue(currentInt, SlotType.INT)
}
}
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
@ -3329,14 +3321,9 @@ class BytecodeCompiler(
}
is IndexRef -> {
val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, target.targetRef)
if (!target.optionalRef) {
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
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.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
} else {
val recvNull = allocSlot()
builder.emit(Opcode.CONST_NULL, recvNull)
@ -3348,11 +3335,7 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(recvCmp), CmdBuilder.Operand.LabelRef(skipLabel))
)
val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null
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.emit(Opcode.SET_INDEX, receiver.slot, index.slot, newValue.slot)
builder.mark(skipLabel)
}
}
@ -3620,15 +3603,9 @@ class BytecodeCompiler(
private fun compileIndexRef(ref: IndexRef): CompiledValue? {
val receiver = compileRefWithFallback(ref.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, ref.targetRef)
val dst = allocSlot()
if (!ref.optionalRef) {
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)
} else {
val nullSlot = allocSlot()
@ -4257,7 +4234,6 @@ class BytecodeCompiler(
val indexTarget = ref.target as? IndexRef ?: return null
val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null
val elementSlotType = indexElementSlotType(receiver.slot, indexTarget.targetRef)
if (indexTarget.optionalRef) {
val resultSlot = allocSlot()
val nullSlot = allocSlot()
@ -4297,24 +4273,6 @@ class BytecodeCompiler(
return CompiledValue(resultSlot, SlotType.OBJ)
}
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()
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
updateSlotType(current, SlotType.OBJ)
@ -4678,7 +4636,6 @@ class BytecodeCompiler(
}
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
compileListFillIntCall(ref)?.let { return it }
val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
@ -4857,22 +4814,6 @@ class BytecodeCompiler(
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? {
val callPos = callSitePos()
val receiver = compileThisRef()
@ -6178,42 +6119,6 @@ class BytecodeCompiler(
}
return when (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
}
}
@ -7869,12 +7774,6 @@ 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) {
builder = CmdBuilder()
nextSlot = 0
@ -8961,14 +8860,31 @@ class BytecodeCompiler(
SlotType.OBJ -> {
val isExactInt = isExactNonNullSlotClassOrTemp(value.slot, ObjInt.type)
val isStableIntObj = slotObjClass[value.slot] == ObjInt.type && isStablePrimitiveSourceSlot(value.slot)
if (!isExactInt && !isStableIntObj) return null
val objSlot = value.slot
if (!isExactInt && !isStableIntObj && !isStablePrimitiveSourceSlot(value.slot)) return null
val objSlot = if (isExactInt || isStableIntObj) {
value.slot
} else {
val boxed = allocSlot()
builder.emit(Opcode.BOX_OBJ, value.slot, boxed)
updateSlotType(boxed, SlotType.OBJ)
emitAssertObjSlotIsInt(boxed)
}
val intSlot = allocSlot()
builder.emit(Opcode.UNBOX_INT_OBJ, objSlot, intSlot)
updateSlotType(intSlot, SlotType.INT)
CompiledValue(intSlot, SlotType.INT)
}
SlotType.UNKNOWN -> null
SlotType.UNKNOWN -> {
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
}
}
@ -9064,7 +8980,12 @@ class BytecodeCompiler(
}
private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? {
return null
if (rangeLocalNames.isEmpty()) 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)

View File

@ -227,12 +227,6 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
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 ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL ->
@ -833,9 +827,6 @@ class CmdBuilder {
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.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.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])

View File

@ -493,9 +493,6 @@ object CmdDisassembler {
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 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 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)
@ -615,12 +612,6 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
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 ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_MEMBER_SLOT ->

View File

@ -34,24 +34,28 @@ class CmdVm {
frame.applyCaptureRecords()
binder?.invoke(frame, args)
val cmds = fn.cmds
while (true) {
try {
while (result == null) {
val cmd = cmds[frame.ip]
frame.ip += 1
if (cmd.isFast) {
cmd.performFast(frame)
} else {
cmd.perform(frame)
try {
while (result == null) {
try {
while (result == null) {
val cmd = cmds[frame.ip]
frame.ip += 1
if (cmd.isFast) {
cmd.performFast(frame)
} else {
cmd.perform(frame)
}
}
} catch (e: Throwable) {
if (!frame.handleException(e)) {
frame.cancelIterators()
throw e
}
}
break
} catch (e: Throwable) {
if (!frame.handleException(e)) {
frame.cancelIterators()
throw e
}
}
} catch (e: Throwable) {
frame.cancelIterators()
throw e
}
frame.cancelIterators()
return result ?: ObjVoid
@ -67,7 +71,6 @@ sealed class Cmd {
open fun performFast(frame: CmdFrame) {
error("fast command not supported: ${this::class.simpleName}")
}
open suspend fun perform(frame: CmdFrame) {
error("slow command not supported: ${this::class.simpleName}")
}
@ -176,7 +179,6 @@ class CmdConstObj(internal val constId: Int, internal val dst: Int) : Cmd() {
else -> frame.setObj(dst, obj)
}
}
is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value))
else -> error("CONST_OBJ expects ObjRef/StringVal at $constId")
}
@ -253,10 +255,7 @@ class CmdLoadThisVariant(
if (candidate.isInstanceOf(typeName)) return@run candidate
if (typeClass != null) {
val inst = candidate as? net.sergeych.lyng.obj.ObjInstance
if (inst != null && (inst.objClass === typeClass || inst.objClass.allParentsSet.contains(
typeClass
))
) {
if (inst != null && (inst.objClass === typeClass || inst.objClass.allParentsSet.contains(typeClass))) {
return@run inst
}
}
@ -285,10 +284,7 @@ class CmdMakeRange(
val descending = frame.slotToObj(descendingSlot).toBool()
val stepObj = frame.slotToObj(stepSlot)
val step = if (stepObj.isNull) null else stepObj
frame.storeObjResult(
dst,
ObjRange(start, end, isEndInclusive = inclusive, isDescending = descending, step = step)
)
frame.storeObjResult(dst, ObjRange(start, end, isEndInclusive = inclusive, isDescending = descending, step = step))
return
}
}
@ -376,7 +372,6 @@ class CmdCheckIs(internal val objSlot: Int, internal val typeSlot: Int, internal
val rightDecl = typeDeclFromObj(frame.ensureScope(), typeObj) ?: return frame.setBool(dst, false)
typeDeclIsSubtype(frame.ensureScope(), leftDecl, rightDecl)
}
typeObj is ObjTypeExpr -> matchesTypeDecl(frame.ensureScope(), obj, typeObj.typeDecl)
typeObj is ObjClass -> obj.isInstanceOf(typeObj)
else -> false
@ -398,7 +393,6 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd()
)
}
}
is ObjTypeExpr -> {
if (!matchesTypeDecl(frame.ensureScope(), obj, typeObj.typeDecl)) {
frame.ensureScope().raiseClassCastError(
@ -406,7 +400,6 @@ class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd()
)
}
}
else -> frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.ensureScope())} is not the class instance"
)
@ -435,7 +428,6 @@ class CmdMakeQualifiedView(
base
}
}
is ObjTypeExpr -> base
else -> frame.ensureScope().raiseClassCastError(
"${typeObj.inspect(frame.ensureScope())} is not the class instance"
@ -467,13 +459,11 @@ class CmdRangeIntBounds(
val start = (range.start as ObjInt).value
val end = (range.end as ObjInt).value
frame.setInt(startSlot, start)
frame.setInt(
endSlot, if (range.isDescending) {
if (range.isEndInclusive) end - 1 else end
} else {
if (range.isEndInclusive) end + 1 else end
}
)
frame.setInt(endSlot, if (range.isDescending) {
if (range.isEndInclusive) end - 1 else end
} else {
if (range.isEndInclusive) end + 1 else end
})
frame.setBool(descendingSlot, range.isDescending)
frame.setBool(okSlot, true)
return
@ -984,7 +974,6 @@ class CmdInvIntLocal(internal val src: Int, internal val dst: Int) : Cmd() {
return
}
}
class CmdCmpEqInt(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setBool(dst, frame.getInt(a) == frame.getInt(b))
@ -3129,7 +3118,6 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
assignDestructurePattern(frame, ref, value, pos)
return
}
is LocalSlotRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = ref.captureOwnerScopeId != null)
if (index != null) {
@ -3144,7 +3132,6 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
return
}
}
is LocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
if (index != null) {
@ -3159,7 +3146,6 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
return
}
}
is FastLocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
if (index != null) {
@ -3174,7 +3160,6 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
return
}
}
else -> {}
}
ref.setAt(pos, frame.ensureScope(), value)
@ -3228,8 +3213,7 @@ class CmdDeclExtProperty(internal val constId: Int, internal val slot: Int) : Cm
if (decl.property.setter != null) {
val setterName = extensionPropertySetterName(decl.extTypeName, decl.property.name)
val setterWrapper = ObjExtensionPropertySetterCallable(decl.property.name, decl.property)
frame.ensureScope()
.addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
frame.ensureScope().addItem(setterName, false, setterWrapper, decl.visibility, recordType = ObjRecord.Type.Fun)
val setterLocal = resolveLocalSlotIndex(frame.fn, setterName, preferCapture = false)
if (setterLocal != null) {
frame.setObjUnchecked(frame.fn.scopeSlotCount + setterLocal, setterWrapper)
@ -3357,7 +3341,6 @@ class CmdListLiteral(
list.ensureCapacity(list.size + value.list.size)
list.addAll(value.list)
}
else -> frame.ensureScope().raiseError("Spread element must be list")
}
} else {
@ -3369,31 +3352,6 @@ 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> {
return if (id <= -2) {
Pair(-id - 2, true)
@ -3432,9 +3390,7 @@ class CmdGetMemberSlot(
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
val fieldRec = if (fieldIdResolved >= 0) {
when {
inst != null -> inst.fieldRecordForId(fieldIdResolved)
?: inst.objClass.fieldRecordForId(fieldIdResolved)
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
cls != null -> cls.fieldRecordForId(fieldIdResolved)
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
@ -3443,10 +3399,7 @@ class CmdGetMemberSlot(
val rec = fieldRec ?: run {
if (methodIdResolved >= 0) {
when {
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(
methodIdResolved
)
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
cls != null -> cls.methodRecordForId(methodIdResolved)
else -> receiver.objClass.methodRecordForId(methodIdResolved)
@ -3478,15 +3431,9 @@ class CmdGetMemberSlot(
} else {
rawName
}
suspend fun autoCallIfMethod(resolved: ObjRecord, recv: Obj): Obj {
return if (resolved.type == ObjRecord.Type.Fun && !resolved.isAbstract) {
resolved.value.invoke(
frame.ensureScope(),
resolved.receiver ?: recv,
Arguments.EMPTY,
resolved.declaringClass
)
resolved.value.invoke(frame.ensureScope(), resolved.receiver ?: recv, Arguments.EMPTY, resolved.declaringClass)
} else {
resolved.value
}
@ -3517,9 +3464,7 @@ class CmdSetMemberSlot(
val (methodIdResolved, methodOnObjClass) = decodeMemberId(methodId)
val fieldRec = if (fieldIdResolved >= 0) {
when {
inst != null -> inst.fieldRecordForId(fieldIdResolved)
?: inst.objClass.fieldRecordForId(fieldIdResolved)
inst != null -> inst.fieldRecordForId(fieldIdResolved) ?: inst.objClass.fieldRecordForId(fieldIdResolved)
cls != null && fieldOnObjClass -> cls.objClass.fieldRecordForId(fieldIdResolved)
cls != null -> cls.fieldRecordForId(fieldIdResolved)
else -> receiver.objClass.fieldRecordForId(fieldIdResolved)
@ -3528,10 +3473,7 @@ class CmdSetMemberSlot(
val rec = fieldRec ?: run {
if (methodIdResolved >= 0) {
when {
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(
methodIdResolved
)
inst != null -> inst.methodRecordForId(methodIdResolved) ?: inst.objClass.methodRecordForId(methodIdResolved)
cls != null && methodOnObjClass -> cls.objClass.methodRecordForId(methodIdResolved)
cls != null -> cls.methodRecordForId(methodIdResolved)
else -> receiver.objClass.methodRecordForId(methodIdResolved)
@ -3725,12 +3667,10 @@ class CmdCallMemberSlot(
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, receiver, decl)
else scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun -> {
val callScope = inst?.instanceScope ?: scope
rec.value.invoke(callScope, receiver, callArgs, decl)
}
ObjRecord.Type.Delegated -> {
val delegate = when (receiver) {
is ObjInstance -> {
@ -3742,13 +3682,9 @@ class CmdCallMemberSlot(
if (del != null) break
}
}
del
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
del ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
}
is ObjClass -> rec.delegate
?: scope.raiseError("Internal error: delegated member $name has no delegate")
is ObjClass -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
else -> rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
}
val allArgs = (listOf(receiver, ObjString(name)) + callArgs.list).toTypedArray()
@ -3757,7 +3693,6 @@ class CmdCallMemberSlot(
propVal.invoke(scope, receiver, callArgs, decl)
})
}
else -> frame.ensureScope().raiseError("member $name is not callable")
}
frame.storeObjResult(dst, result)
@ -3774,7 +3709,7 @@ class CmdGetIndex(
val target = frame.storedSlotObj(targetSlot)
val index = frame.storedSlotObj(indexSlot)
if (target is ObjList && target::class == ObjList::class && index is ObjInt) {
frame.storeObjResult(dst, target.getObjAtFast(index.toInt()))
frame.storeObjResult(dst, target.list[index.toInt()])
return
}
val result = target.getAt(frame.ensureScope(), index)
@ -3793,7 +3728,7 @@ class CmdSetIndex(
val index = frame.storedSlotObj(indexSlot)
val value = frame.slotToObj(valueSlot)
if (target is ObjList && target::class == ObjList::class && index is ObjInt) {
target.setObjAtFast(index.toInt(), value)
target.list[index.toInt()] = value
return
}
target.putAt(frame.ensureScope(), index, value)
@ -3801,47 +3736,6 @@ 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() {
override suspend fun perform(frame: CmdFrame) {
val lambdaConst = frame.fn.constants.getOrNull(id) as? BytecodeConst.LambdaFn
@ -3894,31 +3788,6 @@ 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 {
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = scope.args
@ -3949,12 +3818,7 @@ class BytecodeLambdaCallable(
}
val itSlot = slotPlan["it"]
if (itSlot != null) {
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)
}
frame.frame.setObj(itSlot, itValue)
}
} else {
argsDeclaration.assignToFrame(
@ -3979,12 +3843,11 @@ class BytecodeLambdaCallable(
?: context.parent?.get(name)
?: context.get(name)
?: continue
val value =
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
context.resolve(record, name)
} else {
record.value
}
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
context.resolve(record, name)
} else {
record.value
}
frame.frame.setObj(i, value)
}
}
@ -4041,7 +3904,6 @@ class CmdFrame(
private var scopeDepth = 0
private var virtualDepth = 0
private val iterStack = ArrayDeque<Obj>()
internal data class TryHandler(
val exceptionSlot: Int,
val catchIp: Int,
@ -4049,7 +3911,6 @@ class CmdFrame(
val iterDepthAtPush: Int,
var inCatch: Boolean = false
)
internal val tryStack = ArrayDeque<TryHandler>()
private var pendingThrowable: Throwable? = null
@ -4088,7 +3949,6 @@ class CmdFrame(
else -> obj
}
}
else -> {
val obj = frame.getObj(localIndex)
when (obj) {
@ -4099,7 +3959,6 @@ class CmdFrame(
}
}
}
internal fun isFastLocalSlot(slot: Int): Boolean {
if (slot < fn.scopeSlotCount) return false
val localIndex = slot - fn.scopeSlotCount
@ -4240,7 +4099,6 @@ class CmdFrame(
}
}
}
CaptureOwnerFrameKind.MODULE -> {
val slotId = entry.slotIndex
val target = moduleScope
@ -4371,9 +4229,9 @@ class CmdFrame(
suspend fun handleException(t: Throwable): Boolean {
val handler = tryStack.lastOrNull() ?: return false
vmIterDebug {
vmIterDebug(
"handleException fn=${fn.name} throwable=${t::class.simpleName} message=${t.message} catchIp=${handler.catchIp} finallyIp=${handler.finallyIp} iterDepth=${iterStack.size}"
}
)
val finallyIp = handler.finallyIp
if (t is ReturnException || t is LoopBreakContinueException) {
if (finallyIp >= 0) {
@ -4499,50 +4357,42 @@ class CmdFrame(
fun pushIterator(iter: Obj) {
iterStack.addLast(iter)
if (iter.objClass.className == "FlowIterator") {
vmIterDebug { "pushIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}" }
vmIterDebug("pushIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}")
}
}
fun popIterator() {
val iter = iterStack.lastOrNull()
if (iter != null && iter.objClass.className == "FlowIterator") {
vmIterDebug { "popIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}" }
vmIterDebug("popIterator fn=${fn.name} depth=${iterStack.size} iterClass=${iter.objClass.className}")
}
iterStack.removeLastOrNull()
}
suspend fun cancelTopIterator() {
val iter = iterStack.removeLastOrNull() ?: return
vmIterDebug { "cancelTopIterator fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" }
vmIterDebug("cancelTopIterator fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}")
iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
}
suspend fun cancelIterators() {
while (iterStack.isNotEmpty()) {
val iter = iterStack.removeLast()
vmIterDebug { "cancelIterators fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}" }
try {
iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
} catch (e: Throwable) {
vmIterDebug(e) {
"cancelIterators: cancelIteration failed fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}"
}
}
vmIterDebug("cancelIterators fn=${fn.name} depthAfter=${iterStack.size} iterClass=${iter.objClass.className}")
iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
}
}
private suspend fun cancelIteratorsToDepth(depth: Int, reason: String) {
while (iterStack.size > depth) {
val iter = iterStack.removeLast()
vmIterDebug {
vmIterDebug(
"cancelIteratorsToDepth fn=${fn.name} reason=$reason targetDepth=$depth depthAfter=${iterStack.size} iterClass=${iter.objClass.className}"
}
)
try {
iter.invokeInstanceMethod(ensureScope(), "cancelIteration") { ObjVoid }
} catch (e: Throwable) {
vmIterDebug(e) {
"cancelIteratorsToDepth: cancelIteration failed fn=${fn.name} reason=$reason"
}
vmIterDebug("cancelIteratorsToDepth: cancelIteration failed fn=${fn.name} reason=$reason", e)
}
}
}
@ -4603,12 +4453,10 @@ class CmdFrame(
existing.write(value)
return
}
is RecordSlotRef -> {
existing.write(value)
return
}
else -> {}
}
}
@ -4685,12 +4533,10 @@ class CmdFrame(
existing.write(ObjInt.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjInt.of(value))
return
}
else -> {}
}
}
@ -4717,12 +4563,10 @@ class CmdFrame(
existing.write(ObjInt.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjInt.of(value))
return
}
else -> {}
}
}
@ -4772,12 +4616,10 @@ class CmdFrame(
existing.write(ObjReal.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjReal.of(value))
return
}
else -> {}
}
}
@ -4798,12 +4640,10 @@ class CmdFrame(
existing.write(ObjReal.of(value))
return
}
is RecordSlotRef -> {
existing.write(ObjReal.of(value))
return
}
else -> {}
}
}
@ -4847,12 +4687,10 @@ class CmdFrame(
existing.write(if (value) ObjTrue else ObjFalse)
return
}
is RecordSlotRef -> {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
else -> {}
}
}
@ -4873,12 +4711,10 @@ class CmdFrame(
existing.write(if (value) ObjTrue else ObjFalse)
return
}
is RecordSlotRef -> {
existing.write(if (value) ObjTrue else ObjFalse)
return
}
else -> {}
}
}
@ -4971,7 +4807,6 @@ class CmdFrame(
null -> ObjNull
else -> raw
}
else -> frame.getRawObj(local) ?: ObjNull
}
}
@ -4998,12 +4833,10 @@ class CmdFrame(
existing.write(value)
return
}
is RecordSlotRef -> {
existing.write(value)
return
}
else -> {}
}
}
@ -5089,18 +4922,15 @@ class CmdFrame(
}
namedSeen = true
}
value is ObjList -> {
if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments")
positional.addAll(value.list)
}
value.isInstanceOf(ObjIterable) -> {
if (namedSeen) scope.raiseIllegalArgument("positional splat cannot follow named arguments")
val list = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
positional.addAll(list)
}
else -> scope.raiseClassCastError("expected list of objects for splat argument")
}
} else {
@ -5149,7 +4979,6 @@ class CmdFrame(
else -> obj
}
}
else -> {
val obj = frame.getObj(localIndex)
when (obj) {

View File

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

View File

@ -17,18 +17,4 @@
package net.sergeych.lyng.bytecode
internal expect val vmIterDebugEnabled: Boolean
internal expect fun vmIterDebugWrite(message: String, error: Throwable? = null)
internal inline fun vmIterDebug(message: () -> String) {
if (vmIterDebugEnabled) {
vmIterDebugWrite(message())
}
}
internal inline fun vmIterDebug(error: Throwable, message: () -> String) {
if (vmIterDebugEnabled) {
vmIterDebugWrite(message(), error)
}
}
internal expect fun vmIterDebug(message: String, error: Throwable? = null)

View File

@ -12,7 +12,6 @@
* 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.obj
@ -29,7 +28,6 @@ class LambdaFnRef(
val paramSlotPlan: Map<String, Int>,
val argsDeclaration: ArgsDeclaration?,
val captureEntries: List<LambdaCaptureEntry>,
val inferredReturnClass: ObjClass?,
val preferredThisType: String?,
val wrapAsExtensionCallable: Boolean,
val returnLabels: Set<String>,

View File

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

View File

@ -16,11 +16,21 @@
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.*
import net.sergeych.lyng.Compiler
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 kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class BytecodeRecentOpsTest {
@ -119,79 +129,6 @@ 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
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
eval(

View File

@ -1,5 +1,5 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
* Copyright 2025 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.
@ -97,7 +97,7 @@ class TestCoroutines {
counter = c + 1
// }
}
}.forEach { it.await() }
}.forEach { (it as Deferred).await() }
println(counter)
assert( counter < 10 )
@ -105,21 +105,6 @@ 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
fun testFlows() = runTest {
eval("""

View File

@ -131,7 +131,7 @@ class ScriptTest {
}
// --- Helpers to test iterator cancellation semantics ---
class ObjTestIterable(private val throwOnCancel: Boolean = false) : Obj() {
class ObjTestIterable : Obj() {
var cancelCount: Int = 0
@ -145,13 +145,6 @@ class ScriptTest {
addFn("cancelCount") { thisAs<ObjTestIterable>().cancelCount.toObj() }
}
}
internal fun onCancel() {
cancelCount += 1
if (throwOnCancel) {
throw IllegalStateException("cancel failed")
}
}
}
class ObjTestIterator(private val owner: ObjTestIterable) : Obj() {
@ -161,7 +154,7 @@ class ScriptTest {
private fun hasNext(): Boolean = i < 5
private fun next(): Obj = ObjInt((++i).toLong())
private fun cancelIteration() {
owner.onCancel()
owner.cancelCount += 1
}
companion object {
@ -228,33 +221,6 @@ class ScriptTest {
assertEquals(1, ti.cancelCount)
}
@Test
fun testVmCancelsAllIteratorsWhenOneCancelFails() = runTest {
val scope = Script.newScope()
val outer = ObjTestIterable()
val inner = ObjTestIterable(throwOnCancel = true)
scope.addConst("outer", outer)
scope.addConst("inner", inner)
try {
scope.eval(
"""
for (o in outer) {
for (i in inner) {
throw "boom"
}
}
""".trimIndent()
)
fail("Exception expected")
} catch (_: Exception) {
// ignore
}
assertEquals(1, inner.cancelCount)
assertEquals(1, outer.cancelCount)
}
@Test
fun parseNewlines() {
fun check(expected: String, type: Token.Type, row: Int, col: Int, src: String, offset: Int = 0) {

View File

@ -17,8 +17,6 @@
package net.sergeych.lyng.bytecode
internal actual val vmIterDebugEnabled: Boolean = false
internal actual fun vmIterDebugWrite(message: String, error: Throwable?) {
internal actual fun vmIterDebug(message: String, error: Throwable?) {
// no-op on JS
}

View File

@ -20,18 +20,6 @@ package net.sergeych.lyng.bytecode
import java.io.File
import java.time.Instant
private fun parseEnabledFlag(value: String?): Boolean {
return when (value?.lowercase()) {
"1", "true", "yes", "on" -> true
else -> false
}
}
internal actual val vmIterDebugEnabled: Boolean = run {
parseEnabledFlag(System.getProperty("LYNG_VM_ITER_DEBUG"))
|| parseEnabledFlag(System.getenv("LYNG_VM_ITER_DEBUG"))
}
private val vmIterLogFilePath: String =
System.getenv("LYNG_VM_DEBUG_LOG")
?.takeIf { it.isNotBlank() }
@ -39,7 +27,7 @@ private val vmIterLogFilePath: String =
private val vmIterLogLock = Any()
internal actual fun vmIterDebugWrite(message: String, error: Throwable?) {
internal actual fun vmIterDebug(message: String, error: Throwable?) {
runCatching {
val line = buildString {
append(Instant.now().toString())

View File

@ -16,13 +16,25 @@
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.*
import net.sergeych.lyng.bytecode.*
import net.sergeych.lyng.Benchmarks
import net.sergeych.lyng.BytecodeBodyProvider
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 java.nio.file.Files
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.time.TimeSource
class PiSpigotBenchmarkTest {
@ -30,11 +42,17 @@ class PiSpigotBenchmarkTest {
fun benchmarkPiSpigot() = runTest {
if (!Benchmarks.enabled) return@runTest
val source = loadPiSpigotSource()
val source = Files.readString(resolveExample("pi-test.lyng"))
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 expectedSuffix = "49303819"
val legacyElapsed = runCase("legacy-real-division", legacySource, digits, expectedSuffix, dumpBytecode = true)
val saved = PerfProfiles.snapshot()
PerfFlags.RVAL_FASTPATH = false
val optimizedRvalOffElapsed = runCase(
@ -46,11 +64,15 @@ class PiSpigotBenchmarkTest {
)
PerfProfiles.restore(saved)
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 totalSpeedup = legacyElapsed.toDouble() / optimizedElapsed.toDouble()
println(
"[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits " +
"rvalOff=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x"
"[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits legacy=${legacyElapsed} ms " +
"intDiv=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
"intDivSpeedup=${"%.2f".format(sourceSpeedup)}x " +
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x " +
"total=${"%.2f".format(totalSpeedup)}x"
)
}
@ -69,18 +91,18 @@ class PiSpigotBenchmarkTest {
dumpHotOps(scope, "piSpigot")
}
val first = scope.eval("piSpigot(0, $digits)") as ObjString
val first = scope.eval("piSpigot($digits)") as ObjString
assertEquals(expectedSuffix, first.value)
repeat(2) {
val warm = scope.eval("piSpigot(0, $digits)") as ObjString
val warm = scope.eval("piSpigot($digits)") as ObjString
assertEquals(expectedSuffix, warm.value)
}
val iterations = 3
val start = TimeSource.Monotonic.markNow()
repeat(iterations) {
val result = scope.eval("piSpigot(0, $digits)") as ObjString
val result = scope.eval("piSpigot($digits)") as ObjString
assertEquals(expectedSuffix, result.value)
}
val elapsedMs = start.elapsedNow().inWholeMilliseconds
@ -113,11 +135,6 @@ class PiSpigotBenchmarkTest {
?: (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 {
val direct = Path.of("examples", name)
if (Files.exists(direct)) return direct

View File

@ -17,8 +17,6 @@
package net.sergeych.lyng.bytecode
internal actual val vmIterDebugEnabled: Boolean = false
internal actual fun vmIterDebugWrite(message: String, error: Throwable?) {
internal actual fun vmIterDebug(message: String, error: Throwable?) {
// no-op on Native
}

View File

@ -17,8 +17,6 @@
package net.sergeych.lyng.bytecode
internal actual val vmIterDebugEnabled: Boolean = false
internal actual fun vmIterDebugWrite(message: String, error: Throwable?) {
internal actual fun vmIterDebug(message: String, error: Throwable?) {
// no-op on wasmJs
}