optimize arithmetics
This commit is contained in:
parent
161f3f74e2
commit
d8454a11fc
62
docs/pi_spigot_perf_baseline.md
Normal file
62
docs/pi_spigot_perf_baseline.md
Normal 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
|
||||
@ -1531,11 +1531,9 @@ It could be open and closed:
|
||||
|
||||
Descending ranges are explicit too:
|
||||
|
||||
(5 downTo 1).toList()
|
||||
>>> [5,4,3,2,1]
|
||||
|
||||
(5 downUntil 1).toList()
|
||||
>>> [5,4,3,2]
|
||||
assertEquals([5,4,3,2,1], (5 downTo 1).toList())
|
||||
assertEquals([5,4,3,2], (5 downUntil 1).toList())
|
||||
>>> void
|
||||
|
||||
Ranges could be inside other ranges:
|
||||
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
import lyng.time
|
||||
|
||||
val WORK_SIZE = 200
|
||||
val TASK_COUNT = 10
|
||||
val WORK_SIZE = 500
|
||||
val THREADS = 1
|
||||
|
||||
fn piSpigot(iThread: Int, n: Int) {
|
||||
var pi = []
|
||||
var piIter = 0
|
||||
var pi = List.fill(n) { 0 }
|
||||
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 (k in 1..boxes) {
|
||||
val j = boxes - k
|
||||
for (j in (boxes - 1) downTo 0) {
|
||||
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.add(q)
|
||||
pi[piIter] = q
|
||||
++piIter
|
||||
}
|
||||
|
||||
var s = ""
|
||||
var res = ""
|
||||
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()
|
||||
(1..TASK_COUNT).map { n ->
|
||||
val counterState = counter
|
||||
val t = launch {
|
||||
piSpigot(counterState, WORK_SIZE)
|
||||
|
||||
println("piBench (lyng): THREADS = " + THREADS + ", WORK_SIZE = " + WORK_SIZE)
|
||||
for (i in 0..<THREADS) {
|
||||
piSpigot(i, WORK_SIZE)
|
||||
}
|
||||
++counter
|
||||
t
|
||||
}.forEach { (it as Deferred).await() }
|
||||
|
||||
val dt = Instant() - t0
|
||||
|
||||
println("all done, dt = ", dt)
|
||||
delay(800)
|
||||
}
|
||||
83
examples/pi-bench.py
Normal file
83
examples/pi-bench.py
Normal 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))
|
||||
@ -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
|
||||
}
|
||||
@ -20,9 +20,11 @@ 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 {
|
||||
@ -69,4 +71,34 @@ 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,9 @@
|
||||
|
||||
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 net.sergeych.lyng.Compiler
|
||||
import net.sergeych.lyng.Script
|
||||
@ -29,9 +31,9 @@ import kotlin.test.assertEquals
|
||||
class LyngNetTcpServerExampleTest {
|
||||
|
||||
@Test
|
||||
fun tcpServerExampleRoundTripsOverLoopback() = runBlocking {
|
||||
fun tcpServerExampleRoundTripsOverLoopback() = runTest {
|
||||
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()
|
||||
createNetModule(PermitAllNetAccessPolicy, scope)
|
||||
@ -60,9 +62,11 @@ class LyngNetTcpServerExampleTest {
|
||||
"${'$'}{accepted.await()}: ${'$'}reply"
|
||||
""".trimIndent()
|
||||
|
||||
val result = withTimeout(5_000) {
|
||||
val result = withContext(Dispatchers.Default) {
|
||||
withTimeout(5_000) {
|
||||
Compiler.compile(code).execute(scope).inspect(scope)
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("\"ping: echo:ping\"", result)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@ -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
|
||||
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@ -170,6 +170,20 @@ 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()
|
||||
@ -2721,7 +2735,7 @@ class Compiler(
|
||||
Token.Type.LPAREN -> {
|
||||
cc.next()
|
||||
if (shouldTreatAsClassScopeCall(left, next.value)) {
|
||||
val parsed = parseArgs(null)
|
||||
val parsed = parseArgs(null, implicitItTypeNameForMemberLambda(left, next.value))
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
isCall = true
|
||||
@ -2738,7 +2752,7 @@ class Compiler(
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val parsed = parseArgs(receiverType)
|
||||
val parsed = parseArgs(receiverType, implicitItTypeNameForMemberLambda(left, next.value))
|
||||
val args = parsed.first
|
||||
val tailBlock = parsed.second
|
||||
if (left is LocalVarRef && left.name == "scope") {
|
||||
@ -2807,9 +2821,7 @@ class Compiler(
|
||||
val receiverType = if (next.value == "apply" || next.value == "run") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val itType = if (next.value == "let" || next.value == "also") {
|
||||
inferReceiverTypeFromRef(left)
|
||||
} else null
|
||||
val itType = implicitItTypeNameForMemberLambda(left, next.value)
|
||||
val lambda = parseLambdaExpression(receiverType, implicitItType = itType)
|
||||
val argPos = next.pos
|
||||
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
|
||||
@ -3239,6 +3251,8 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3384,7 +3398,12 @@ class Compiler(
|
||||
}
|
||||
val itSlot = slotPlan["it"]
|
||||
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 {
|
||||
argsDeclaration.assignToFrame(
|
||||
@ -3414,6 +3433,7 @@ class Compiler(
|
||||
paramSlotPlan = paramSlotPlanSnapshot,
|
||||
argsDeclaration = argsDeclaration,
|
||||
captureEntries = captureEntries,
|
||||
inferredReturnClass = returnClass,
|
||||
preferredThisType = expectedReceiverType,
|
||||
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
||||
returnLabels = returnLabels,
|
||||
@ -4771,7 +4791,7 @@ class Compiler(
|
||||
val targetClass = resolveReceiverClassForMember(ref.targetRef)
|
||||
classMethodReturnTypeDecl(targetClass, "getAt")
|
||||
}
|
||||
is MethodCallRef -> methodReturnTypeDeclByRef[ref]
|
||||
is MethodCallRef -> methodReturnTypeDeclByRef[ref] ?: inferMethodCallReturnTypeDecl(ref)
|
||||
is CallRef -> callReturnTypeDeclByRef[ref] ?: inferCallReturnTypeDecl(ref)
|
||||
is BinaryOpRef -> inferBinaryOpReturnTypeDecl(ref)
|
||||
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
|
||||
@ -5012,8 +5032,7 @@ class Compiler(
|
||||
}
|
||||
|
||||
private fun inferMethodCallReturnClass(ref: MethodCallRef): ObjClass? {
|
||||
val receiverDecl = resolveReceiverTypeDecl(ref.receiver)
|
||||
val genericReturnDecl = inferMethodCallReturnTypeDecl(ref.name, receiverDecl)
|
||||
val genericReturnDecl = inferMethodCallReturnTypeDecl(ref)
|
||||
if (genericReturnDecl != null) {
|
||||
methodReturnTypeDeclByRef[ref] = genericReturnDecl
|
||||
resolveTypeDeclObjClass(genericReturnDecl)?.let { return it }
|
||||
@ -5034,7 +5053,24 @@ 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('.')
|
||||
@ -5048,6 +5084,10 @@ 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)
|
||||
@ -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? {
|
||||
val stmt = args.firstOrNull()?.value as? ExpressionStatement ?: return null
|
||||
val ref = stmt.ref
|
||||
@ -6112,7 +6225,10 @@ 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): Pair<List<ParsedArgument>, Boolean> {
|
||||
private suspend fun parseArgs(
|
||||
expectedTailBlockReceiver: String? = null,
|
||||
implicitItType: String? = null
|
||||
): Pair<List<ParsedArgument>, Boolean> {
|
||||
|
||||
val args = mutableListOf<ParsedArgument>()
|
||||
suspend fun tryParseNamedArg(): ParsedArgument? {
|
||||
@ -6169,7 +6285,7 @@ class Compiler(
|
||||
var lastBlockArgument = false
|
||||
if (end.type == Token.Type.LBRACE) {
|
||||
// last argument - callable
|
||||
val callableAccessor = parseLambdaExpression(expectedTailBlockReceiver)
|
||||
val callableAccessor = parseLambdaExpression(expectedTailBlockReceiver, implicitItType = implicitItType)
|
||||
args += ParsedArgument(
|
||||
ExpressionStatement(callableAccessor, end.pos),
|
||||
end.pos
|
||||
@ -8891,6 +9007,12 @@ 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) }
|
||||
|
||||
@ -2713,9 +2713,14 @@ 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)
|
||||
}
|
||||
noteListElementClassMutation(receiver.slot, value)
|
||||
} else {
|
||||
val nullSlot = allocSlot()
|
||||
@ -2728,7 +2733,11 @@ 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)
|
||||
}
|
||||
noteListElementClassMutation(receiver.slot, value)
|
||||
builder.mark(endLabel)
|
||||
}
|
||||
@ -3047,13 +3056,12 @@ class BytecodeCompiler(
|
||||
val current = allocSlot()
|
||||
val result = allocSlot()
|
||||
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) {
|
||||
val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null
|
||||
if (elementClass == ObjInt.type) {
|
||||
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
|
||||
if (elementClass == ObjInt.type && index.type == SlotType.INT) {
|
||||
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)
|
||||
if (rhs.type != SlotType.INT) {
|
||||
coerceToArithmeticInt(ref.value, rhs)?.let { rhs = it }
|
||||
@ -3067,9 +3075,9 @@ class BytecodeCompiler(
|
||||
else -> null
|
||||
}
|
||||
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)
|
||||
return CompiledValue(currentInt, SlotType.INT)
|
||||
return typed
|
||||
}
|
||||
}
|
||||
builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current)
|
||||
@ -3321,9 +3329,14 @@ 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)
|
||||
}
|
||||
} else {
|
||||
val recvNull = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, recvNull)
|
||||
@ -3335,7 +3348,11 @@ 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.mark(skipLabel)
|
||||
}
|
||||
}
|
||||
@ -3603,9 +3620,15 @@ 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()
|
||||
@ -4234,6 +4257,7 @@ 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()
|
||||
@ -4273,6 +4297,24 @@ 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)
|
||||
@ -4636,6 +4678,7 @@ 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
|
||||
@ -4814,6 +4857,22 @@ 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()
|
||||
@ -6119,6 +6178,42 @@ 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
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
builder = CmdBuilder()
|
||||
nextSlot = 0
|
||||
@ -8860,31 +8961,14 @@ class BytecodeCompiler(
|
||||
SlotType.OBJ -> {
|
||||
val isExactInt = isExactNonNullSlotClassOrTemp(value.slot, ObjInt.type)
|
||||
val isStableIntObj = slotObjClass[value.slot] == ObjInt.type && isStablePrimitiveSourceSlot(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)
|
||||
}
|
||||
if (!isExactInt && !isStableIntObj) return null
|
||||
val objSlot = value.slot
|
||||
val intSlot = allocSlot()
|
||||
builder.emit(Opcode.UNBOX_INT_OBJ, objSlot, intSlot)
|
||||
updateSlotType(intSlot, SlotType.INT)
|
||||
CompiledValue(intSlot, SlotType.INT)
|
||||
}
|
||||
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)
|
||||
}
|
||||
SlotType.UNKNOWN -> null
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -8980,12 +9064,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? {
|
||||
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
|
||||
return null
|
||||
}
|
||||
|
||||
private data class ScopeSlotKey(val scopeId: Int, val slot: Int)
|
||||
|
||||
@ -227,6 +227,12 @@ 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 ->
|
||||
@ -827,6 +833,9 @@ 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])
|
||||
|
||||
@ -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 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)
|
||||
@ -612,6 +615,12 @@ 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 ->
|
||||
|
||||
@ -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> {
|
||||
return if (id <= -2) {
|
||||
Pair(-id - 2, true)
|
||||
@ -3709,7 +3734,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.list[index.toInt()])
|
||||
frame.storeObjResult(dst, target.getObjAtFast(index.toInt()))
|
||||
return
|
||||
}
|
||||
val result = target.getAt(frame.ensureScope(), index)
|
||||
@ -3728,7 +3753,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.list[index.toInt()] = value
|
||||
target.setObjAtFast(index.toInt(), value)
|
||||
return
|
||||
}
|
||||
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() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
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 {
|
||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
||||
it.args = scope.args
|
||||
@ -3818,7 +3909,12 @@ class BytecodeLambdaCallable(
|
||||
}
|
||||
val itSlot = slotPlan["it"]
|
||||
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 {
|
||||
argsDeclaration.assignToFrame(
|
||||
|
||||
@ -177,7 +177,10 @@ 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),
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
* 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
|
||||
@ -28,6 +29,7 @@ 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>,
|
||||
|
||||
@ -19,8 +19,8 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
@ -29,7 +29,120 @@ import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
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 {
|
||||
if (!other.isInstanceOf(ObjIterable)) return true
|
||||
val declaredElementType = scope.declaredListElementTypeForValue(this)
|
||||
@ -48,9 +161,9 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
}
|
||||
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
|
||||
if (sizeFast() != other.sizeFast()) return false
|
||||
for (i in 0..<sizeFast()) {
|
||||
if (!getObjAtFast(i).equals(scope, other.getObjAtFast(i))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -58,31 +171,31 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||
return when (index) {
|
||||
is ObjInt -> {
|
||||
list[index.toInt()]
|
||||
getObjAtFast(index.toInt())
|
||||
}
|
||||
|
||||
is ObjRange -> {
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
ObjList(list.subList(index.start.toInt(), index.end.toInt() + 1).toMutableList())
|
||||
sliceRange(index.start.toInt(), index.end.toInt() + 1)
|
||||
else
|
||||
ObjList(list.subList(index.start.toInt(), index.end.toInt()).toMutableList())
|
||||
sliceRange(index.start.toInt(), index.end.toInt())
|
||||
}
|
||||
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
ObjList(list.subList(0, index.end!!.toInt() + 1).toMutableList())
|
||||
sliceRange(0, index.end!!.toInt() + 1)
|
||||
else
|
||||
ObjList(list.subList(0, index.end!!.toInt()).toMutableList())
|
||||
sliceRange(0, index.end!!.toInt())
|
||||
}
|
||||
|
||||
index.isOpenEnd && !index.isOpenStart -> {
|
||||
ObjList(list.subList(index.start!!.toInt(), list.size).toMutableList())
|
||||
sliceRange(index.start!!.toInt(), sizeFast())
|
||||
}
|
||||
|
||||
index.isOpenStart && index.isOpenEnd -> {
|
||||
ObjList(list.toMutableList())
|
||||
sliceRange(0, sizeFast())
|
||||
}
|
||||
|
||||
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) {
|
||||
list[index.toInt()] = newValue
|
||||
setObjAtFast(index.toInt(), newValue)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other is ObjList) {
|
||||
val mySize = list.size
|
||||
val otherSize = other.list.size
|
||||
val mySize = sizeFast()
|
||||
val otherSize = other.sizeFast()
|
||||
val commonSize = minOf(mySize, otherSize)
|
||||
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) {
|
||||
return d
|
||||
}
|
||||
@ -114,14 +227,13 @@ open class ObjList(val list: 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")
|
||||
|
||||
while (it1.hasNext()) {
|
||||
for (i in 0..<sizeFast()) {
|
||||
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 d = v1.compareTo(scope, v2)
|
||||
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 =
|
||||
when {
|
||||
other is ObjList ->
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable) -> {
|
||||
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 {
|
||||
if (other is ObjList) {
|
||||
list.addAll(other.list)
|
||||
appendAllFast(other)
|
||||
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
|
||||
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||
list.addAll(otherList)
|
||||
} else {
|
||||
list.add(other)
|
||||
appendFast(other)
|
||||
}
|
||||
return this
|
||||
}
|
||||
@ -199,6 +321,13 @@ open class ObjList(val list: 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) {
|
||||
@ -216,6 +345,13 @@ open class ObjList(val list: 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
|
||||
}
|
||||
@ -225,6 +361,8 @@ open class ObjList(val list: 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) }
|
||||
}
|
||||
|
||||
@ -256,8 +394,7 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
// check?
|
||||
return list.hashCode()
|
||||
return primitiveIntList?.contentHashCode() ?: list.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -266,16 +403,31 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
|
||||
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?) {
|
||||
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 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) })
|
||||
}
|
||||
|
||||
@ -283,10 +435,18 @@ open class ObjList(val list: 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)
|
||||
}
|
||||
}
|
||||
append("]")
|
||||
})
|
||||
}
|
||||
@ -307,7 +467,7 @@ open class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val s = (this.thisObj as ObjList).list.size
|
||||
val s = (this.thisObj as ObjList).sizeFast()
|
||||
s.toObj()
|
||||
}
|
||||
)
|
||||
|
||||
@ -16,21 +16,11 @@
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
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.*
|
||||
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 {
|
||||
@ -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
|
||||
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
|
||||
eval(
|
||||
|
||||
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -97,7 +97,7 @@ class TestCoroutines {
|
||||
counter = c + 1
|
||||
// }
|
||||
}
|
||||
}.forEach { (it as Deferred).await() }
|
||||
}.forEach { it.await() }
|
||||
println(counter)
|
||||
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
|
||||
fun testFlows() = runTest {
|
||||
eval("""
|
||||
|
||||
@ -16,25 +16,13 @@
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
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.*
|
||||
import net.sergeych.lyng.bytecode.*
|
||||
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 {
|
||||
@ -42,17 +30,11 @@ class PiSpigotBenchmarkTest {
|
||||
fun benchmarkPiSpigot() = runTest {
|
||||
if (!Benchmarks.enabled) return@runTest
|
||||
|
||||
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 source = loadPiSpigotSource()
|
||||
|
||||
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(
|
||||
@ -64,15 +46,11 @@ 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 legacy=${legacyElapsed} ms " +
|
||||
"intDiv=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
|
||||
"intDivSpeedup=${"%.2f".format(sourceSpeedup)}x " +
|
||||
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x " +
|
||||
"total=${"%.2f".format(totalSpeedup)}x"
|
||||
"[DEBUG_LOG] [BENCH] pi-spigot compare n=$digits " +
|
||||
"rvalOff=${optimizedRvalOffElapsed} ms rvalOn=${optimizedElapsed} ms " +
|
||||
"rvalSpeedup=${"%.2f".format(runtimeSpeedup)}x"
|
||||
)
|
||||
}
|
||||
|
||||
@ -91,18 +69,18 @@ class PiSpigotBenchmarkTest {
|
||||
dumpHotOps(scope, "piSpigot")
|
||||
}
|
||||
|
||||
val first = scope.eval("piSpigot($digits)") as ObjString
|
||||
val first = scope.eval("piSpigot(0, $digits)") as ObjString
|
||||
assertEquals(expectedSuffix, first.value)
|
||||
|
||||
repeat(2) {
|
||||
val warm = scope.eval("piSpigot($digits)") as ObjString
|
||||
val warm = scope.eval("piSpigot(0, $digits)") as ObjString
|
||||
assertEquals(expectedSuffix, warm.value)
|
||||
}
|
||||
|
||||
val iterations = 3
|
||||
val start = TimeSource.Monotonic.markNow()
|
||||
repeat(iterations) {
|
||||
val result = scope.eval("piSpigot($digits)") as ObjString
|
||||
val result = scope.eval("piSpigot(0, $digits)") as ObjString
|
||||
assertEquals(expectedSuffix, result.value)
|
||||
}
|
||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||
@ -135,6 +113,11 @@ 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user