added new performance flags, extended PIC handling to size 4, introduced fast paths for ObjFastIntRangeIterator, small-arity arguments, index access, and unary operations
This commit is contained in:
parent
0798bbee9b
commit
882df67909
@ -10,15 +10,35 @@ Optimizations are controlled by runtime‑mutable flags in `net.sergeych.lyng.Pe
|
|||||||
|
|
||||||
All flags are `var` and can be flipped at runtime (e.g., from tests or host apps) for A/B comparisons.
|
All flags are `var` and can be flipped at runtime (e.g., from tests or host apps) for A/B comparisons.
|
||||||
|
|
||||||
|
### Workload presets (JVM‑first)
|
||||||
|
|
||||||
|
To simplify switching between recommended flag sets for different workloads, use `net.sergeych.lyng.PerfProfiles`:
|
||||||
|
|
||||||
|
```
|
||||||
|
val snap = PerfProfiles.apply(PerfProfiles.Preset.BENCH) // or BASELINE / BOOKS
|
||||||
|
// ... run workload ...
|
||||||
|
PerfProfiles.restore(snap) // restore previous flags
|
||||||
|
```
|
||||||
|
|
||||||
|
- BASELINE: restores platform defaults from `PerfDefaults` (good rollback point).
|
||||||
|
- BENCH: expression‑heavy micro‑bench focus (aggressive R‑value and PIC optimizations on JVM).
|
||||||
|
- BOOKS: documentation workloads (prefers simpler paths; disables some PIC/arg builder features shown neutral/negative for this load in A/B).
|
||||||
|
|
||||||
## Key flags
|
## Key flags
|
||||||
|
|
||||||
- `LOCAL_SLOT_PIC` — Runtime cache in `LocalVarRef` to avoid repeated name→slot lookups per frame (ON JVM default).
|
- `LOCAL_SLOT_PIC` — Runtime cache in `LocalVarRef` to avoid repeated name→slot lookups per frame (ON JVM default).
|
||||||
- `EMIT_FAST_LOCAL_REFS` — Compiler emits `FastLocalVarRef` for identifiers known to be locals/params (ON JVM default).
|
- `EMIT_FAST_LOCAL_REFS` — Compiler emits `FastLocalVarRef` for identifiers known to be locals/params (ON JVM default).
|
||||||
- `ARG_BUILDER` — Efficient argument building: small‑arity no‑alloc and pooled builder on JVM (ON JVM default).
|
- `ARG_BUILDER` — Efficient argument building: small‑arity no‑alloc and pooled builder on JVM (ON JVM default).
|
||||||
|
- `ARG_SMALL_ARITY_12` — Extends small‑arity no‑alloc call paths from 0–8 to 0–12 arguments (JVM‑first exploration; OFF by default). Use for codebases with many 9–12 arg calls; A/B before enabling.
|
||||||
- `SKIP_ARGS_ON_NULL_RECEIVER` — Early return on optional‑null receivers before building args (semantics‑compatible). A/B only.
|
- `SKIP_ARGS_ON_NULL_RECEIVER` — Early return on optional‑null receivers before building args (semantics‑compatible). A/B only.
|
||||||
- `SCOPE_POOL` — Scope frame pooling for calls (JVM, per‑thread ThreadLocal pool). ON by default on JVM; togglable at runtime.
|
- `SCOPE_POOL` — Scope frame pooling for calls (JVM, per‑thread ThreadLocal pool). ON by default on JVM; togglable at runtime.
|
||||||
- `FIELD_PIC` — 2‑entry polymorphic inline cache for field reads/writes keyed by `(classId, layoutVersion)` (ON JVM default).
|
- `FIELD_PIC` — 2‑entry polymorphic inline cache for field reads/writes keyed by `(classId, layoutVersion)` (ON JVM default).
|
||||||
- `METHOD_PIC` — 2‑entry PIC for instance method calls keyed by `(classId, layoutVersion)` (ON JVM default).
|
- `METHOD_PIC` — 2‑entry PIC for instance method calls keyed by `(classId, layoutVersion)` (ON JVM default).
|
||||||
|
- `FIELD_PIC_SIZE_4` — Increases Field PIC size from 2 to 4 entries (JVM-first tuning; OFF by default). Use for sites with >2 receiver shapes.
|
||||||
|
- `METHOD_PIC_SIZE_4` — Increases Method PIC size from 2 to 4 entries (JVM-first tuning; OFF by default).
|
||||||
|
- `PIC_ADAPTIVE_2_TO_4` — Adaptive growth of Field/Method PICs from 2→4 entries per-site when miss rate >20% over ≥256 accesses (JVM-first; OFF by default).
|
||||||
|
- `INDEX_PIC` — Enables polymorphic inline cache for indexing (e.g., `a[i]`) and related fast paths. Defaults to follow `FIELD_PIC` on init; can be toggled independently.
|
||||||
|
- `INDEX_PIC_SIZE_4` — Increases Index PIC size from 2 to 4 entries (JVM-first tuning). Default: ON for JVM; OFF elsewhere by default.
|
||||||
- `PIC_DEBUG_COUNTERS` — Enable lightweight hit/miss counters via `PerfStats` (OFF by default).
|
- `PIC_DEBUG_COUNTERS` — Enable lightweight hit/miss counters via `PerfStats` (OFF by default).
|
||||||
- `PRIMITIVE_FASTOPS` — Fast paths for `(ObjInt, ObjInt)` arithmetic/comparisons and `(ObjBool, ObjBool)` logic (ON JVM default).
|
- `PRIMITIVE_FASTOPS` — Fast paths for `(ObjInt, ObjInt)` arithmetic/comparisons and `(ObjBool, ObjBool)` logic (ON JVM default).
|
||||||
- `RVAL_FASTPATH` — Bypass `ObjRecord` in pure expression evaluation via `ObjRef.evalValue` (ON JVM default, OFF elsewhere).
|
- `RVAL_FASTPATH` — Bypass `ObjRecord` in pure expression evaluation via `ObjRef.evalValue` (ON JVM default, OFF elsewhere).
|
||||||
@ -28,11 +48,21 @@ See `src/commonMain/kotlin/net/sergeych/lyng/PerfFlags.kt` and `PerfDefaults.*.k
|
|||||||
## Where optimizations apply
|
## Where optimizations apply
|
||||||
|
|
||||||
- Locals: `FastLocalVarRef`, `LocalVarRef` per‑frame cache (PIC).
|
- Locals: `FastLocalVarRef`, `LocalVarRef` per‑frame cache (PIC).
|
||||||
- Calls: small‑arity zero‑alloc paths (0–8 args), pooled builder (JVM), and child frame pooling (optional).
|
- Calls: small‑arity zero‑alloc paths (0–8 args; optionally 0–12 with `ARG_SMALL_ARITY_12`), pooled builder (JVM), and child frame pooling (optional).
|
||||||
- Properties/methods: Field/Method PICs with receiver shape `(classId, layoutVersion)` and handle‑aware caches.
|
- Properties/methods: Field/Method PICs with receiver shape `(classId, layoutVersion)` and handle‑aware caches; configurable 2→4 entries under flags.
|
||||||
- Expressions: R‑value fast paths in hot nodes (`UnaryOpRef`, `BinaryOpRef`, `ElvisRef`, logical ops, `RangeRef`, `IndexRef` read, `FieldRef` receiver eval, `ListLiteralRef` elements, `CallRef` callee, `MethodCallRef` receiver, assignment RHS).
|
- Expressions: R‑value fast paths in hot nodes (`UnaryOpRef`, `BinaryOpRef`, `ElvisRef`, logical ops, `RangeRef`, `IndexRef` read, `FieldRef` receiver eval, `ListLiteralRef` elements, `CallRef` callee, `MethodCallRef` receiver, assignment RHS).
|
||||||
- Primitives: Direct boolean/int ops where safe.
|
- Primitives: Direct boolean/int ops where safe.
|
||||||
|
|
||||||
|
### Compiler constant folding (conservative)
|
||||||
|
- The compiler folds a safe subset of literal‑only expressions at compile time to reduce runtime work:
|
||||||
|
- Integer arithmetic: `+ - * / %` (division/modulo only when divisor ≠ 0).
|
||||||
|
- Bitwise integer ops: `& ^ | << >>`.
|
||||||
|
- Comparisons and equality for ints/strings/chars: `== != < <= > >=`.
|
||||||
|
- Boolean logic for literal booleans: `|| &&` and unary `!`.
|
||||||
|
- String concatenation of literal strings: `"a" + "b"`.
|
||||||
|
- Non‑literal operands or side‑effecting constructs are not folded.
|
||||||
|
- Semantics remain unchanged; tests verify parity.
|
||||||
|
|
||||||
## Running JVM micro‑benchmarks
|
## Running JVM micro‑benchmarks
|
||||||
|
|
||||||
Each benchmark prints timings with `[DEBUG_LOG]` and includes correctness assertions to prevent dead‑code elimination.
|
Each benchmark prints timings with `[DEBUG_LOG]` and includes correctness assertions to prevent dead‑code elimination.
|
||||||
@ -46,8 +76,11 @@ Run individual tests to avoid multiplatform matrices:
|
|||||||
./gradlew :lynglib:jvmTest --tests CallSplatBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests CallSplatBenchmarkTest
|
||||||
./gradlew :lynglib:jvmTest --tests PicBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests PicBenchmarkTest
|
||||||
./gradlew :lynglib:jvmTest --tests PicInvalidationJvmTest
|
./gradlew :lynglib:jvmTest --tests PicInvalidationJvmTest
|
||||||
|
./gradlew :lynglib:jvmTest --tests PicAdaptiveABTest
|
||||||
./gradlew :lynglib:jvmTest --tests ArithmeticBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests ArithmeticBenchmarkTest
|
||||||
./gradlew :lynglib:jvmTest --tests ExpressionBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests ExpressionBenchmarkTest
|
||||||
|
./gradlew :lynglib:jvmTest --tests IndexPicABTest
|
||||||
|
./gradlew :lynglib:jvmTest --tests IndexWritePathABTest
|
||||||
./gradlew :lynglib:jvmTest --tests CallPoolingBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests CallPoolingBenchmarkTest
|
||||||
./gradlew :lynglib:jvmTest --tests MethodPoolingBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests MethodPoolingBenchmarkTest
|
||||||
./gradlew :lynglib:jvmTest --tests MixedBenchmarkTest
|
./gradlew :lynglib:jvmTest --tests MixedBenchmarkTest
|
||||||
@ -62,6 +95,18 @@ Typical output (example):
|
|||||||
|
|
||||||
Lower time is better. Run the same bench with a flag OFF vs ON to compare.
|
Lower time is better. Run the same bench with a flag OFF vs ON to compare.
|
||||||
|
|
||||||
|
### Optional JFR allocation profiling (JVM)
|
||||||
|
|
||||||
|
When running end‑to‑end “book” workloads or heavier benches, you can enable JFR to capture allocation and GC details:
|
||||||
|
|
||||||
|
```
|
||||||
|
./gradlew :lynglib:jvmTest --tests BookAllocationProfileTest -Dlyng.jfr=true \
|
||||||
|
-Dlyng.profile.warmup=1 -Dlyng.profile.repeats=3 -Dlyng.profile.shuffle=true
|
||||||
|
```
|
||||||
|
|
||||||
|
- Dumps are saved to `lynglib/build/jfr_*.jfr` if the JVM supports Flight Recorder.
|
||||||
|
- The test also records GC counts/time and median time/heap deltas to `lynglib/build/book_alloc_profile.txt`.
|
||||||
|
|
||||||
## Toggling flags in tests
|
## Toggling flags in tests
|
||||||
|
|
||||||
Flags are mutable at runtime, e.g.:
|
Flags are mutable at runtime, e.g.:
|
||||||
@ -88,17 +133,46 @@ Available counters in `PerfStats`:
|
|||||||
|
|
||||||
- Field PIC: `fieldPicHit`, `fieldPicMiss`, `fieldPicSetHit`, `fieldPicSetMiss`
|
- Field PIC: `fieldPicHit`, `fieldPicMiss`, `fieldPicSetHit`, `fieldPicSetMiss`
|
||||||
- Method PIC: `methodPicHit`, `methodPicMiss`
|
- Method PIC: `methodPicHit`, `methodPicMiss`
|
||||||
|
- Index PIC: `indexPicHit`, `indexPicMiss`
|
||||||
- Locals: `localVarPicHit`, `localVarPicMiss`, `fastLocalHit`, `fastLocalMiss`
|
- Locals: `localVarPicHit`, `localVarPicMiss`, `fastLocalHit`, `fastLocalMiss`
|
||||||
- Primitive ops: `primitiveFastOpsHit`
|
- Primitive ops: `primitiveFastOpsHit`
|
||||||
|
|
||||||
Print a summary at the end of a bench/test as needed. Remember to turn counters OFF after the test.
|
Print a summary at the end of a bench/test as needed. Remember to turn counters OFF after the test.
|
||||||
|
|
||||||
|
## A/B scenarios and guidance (JVM)
|
||||||
|
|
||||||
|
### Adaptive PIC (fields/methods)
|
||||||
|
- Flags: `FIELD_PIC=true`, `METHOD_PIC=true`, `FIELD_PIC_SIZE_4=false`, `METHOD_PIC_SIZE_4=false`, toggle `PIC_ADAPTIVE_2_TO_4` OFF vs ON.
|
||||||
|
- Benchmarks: `PicBenchmarkTest`, `MixedBenchmarkTest`, `PicAdaptiveABTest`.
|
||||||
|
- Expect wins at sites with >2 receiver shapes; counters should show fewer misses with adaptivity ON.
|
||||||
|
|
||||||
|
### Index PIC and size
|
||||||
|
- Flags: toggle `INDEX_PIC` OFF vs ON; then `INDEX_PIC_SIZE_4` OFF vs ON.
|
||||||
|
- Benchmarks: `ExpressionBenchmarkTest` (list indexing) and `IndexPicABTest` (string/map indexing).
|
||||||
|
- Expect wins when the same index shape recurs; counters should show higher `indexPicHit`.
|
||||||
|
|
||||||
|
### Index WRITE paths (Map and List)
|
||||||
|
- Flags: toggle `INDEX_PIC` OFF vs ON; then `INDEX_PIC_SIZE_4` OFF vs ON.
|
||||||
|
- Benchmark: `IndexWritePathABTest` (Map[String] put, List[Int] set) — writes results to `lynglib/build/index_write_ab_results.txt`.
|
||||||
|
- Direct fast‑paths are used on R‑value paths where safe and semantics‑preserving (e.g., optional‑chaining no‑ops on null receivers; bounds exceptions unchanged).
|
||||||
|
|
||||||
## Guidance per flag (JVM)
|
## Guidance per flag (JVM)
|
||||||
|
|
||||||
- Keep `RVAL_FASTPATH = true` unless debugging a suspected expression‑semantics issue.
|
- Keep `RVAL_FASTPATH = true` unless debugging a suspected expression‑semantics issue.
|
||||||
- Use `SCOPE_POOL = true` only for benchmarks or once pooling passes the deep stress tests and broader validation; currently OFF by default.
|
- Use `SCOPE_POOL = true` only for benchmarks or once pooling passes the deep stress tests and broader validation; currently OFF by default.
|
||||||
- `FIELD_PIC` and `METHOD_PIC` should remain ON; they are validated with invalidation tests.
|
- `FIELD_PIC` and `METHOD_PIC` should remain ON; they are validated with invalidation tests.
|
||||||
|
- Consider enabling `FIELD_PIC_SIZE_4`/`METHOD_PIC_SIZE_4` for sites with 3–4 receiver shapes; measure first.
|
||||||
|
- `PIC_ADAPTIVE_2_TO_4` is useful on polymorphic sites and may outperform fixed size 2 on mixed-shape workloads. Validate with `PicAdaptiveABTest`.
|
||||||
|
- `INDEX_PIC` is generally beneficial on JVM; leave ON when measuring index‑heavy workloads.
|
||||||
|
- `INDEX_PIC_SIZE_4` is ON by default on JVM as A/B showed consistent wins on String[Int] and Map[String] workloads. You can disable it by setting `PerfFlags.INDEX_PIC_SIZE_4 = false` if needed.
|
||||||
- `ARG_BUILDER` should remain ON; switch OFF only to get a baseline.
|
- `ARG_BUILDER` should remain ON; switch OFF only to get a baseline.
|
||||||
|
- `ARG_SMALL_ARITY_12` is experimental and OFF by default. Enable it only if your workload frequently calls functions with 9–12 arguments and A/B shows consistent wins.
|
||||||
|
|
||||||
|
### Workload‑specific recommendations (JVM)
|
||||||
|
|
||||||
|
- “Books”/documentation loads (BookTest): prefer simpler paths; in A/B these often benefit from the BOOKS preset (e.g., `ARG_BUILDER=false`, `SCOPE_POOL=false`, `INDEX_PIC=false`). Use `PerfProfiles.apply(PerfProfiles.Preset.BOOKS)` before the run and `restore(...)` after.
|
||||||
|
- Expression‑heavy benches: use the BENCH preset (PICs and R‑value fast‑paths enabled, `INDEX_PIC_SIZE_4=true`).
|
||||||
|
- Always verify with local A/B on your environment; rollback is a flag flip or applying BASELINE.
|
||||||
|
|
||||||
## Notes on correctness & safety
|
## Notes on correctness & safety
|
||||||
|
|
||||||
@ -111,6 +185,12 @@ Print a summary at the end of a bench/test as needed. Remember to turn counters
|
|||||||
- Non‑JVM defaults keep `RVAL_FASTPATH=false` for now; other low‑risk flags may be ON.
|
- Non‑JVM defaults keep `RVAL_FASTPATH=false` for now; other low‑risk flags may be ON.
|
||||||
- Once JVM path is fully validated and measured, add lightweight benches for JS/Wasm/Native and enable flags incrementally.
|
- Once JVM path is fully validated and measured, add lightweight benches for JS/Wasm/Native and enable flags incrementally.
|
||||||
|
|
||||||
|
## Range fast iteration (experimental)
|
||||||
|
|
||||||
|
- Flag: `RANGE_FAST_ITER` (default OFF).
|
||||||
|
- When enabled and applicable, simple ascending integer ranges (`0..n`, `0..<n`) use a specialized non‑allocating iterator (`ObjFastIntRangeIterator`).
|
||||||
|
- Benchmark: `RangeIterationBenchmarkTest` records OFF/ON timings for inclusive, exclusive, reversed, negative, and empty ranges. Semantics are preserved; non‑int or complex ranges fall back to the generic iterator.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- If a benchmark shows regressions, flip related flags OFF to isolate the source (e.g., `ARG_BUILDER`, `RVAL_FASTPATH`, `FIELD_PIC`, `METHOD_PIC`).
|
- If a benchmark shows regressions, flip related flags OFF to isolate the source (e.g., `ARG_BUILDER`, `RVAL_FASTPATH`, `FIELD_PIC`, `METHOD_PIC`).
|
||||||
|
|||||||
@ -27,6 +27,11 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
@ -34,4 +39,7 @@ actual object PerfDefaults {
|
|||||||
actual val RVAL_FASTPATH: Boolean = true
|
actual val RVAL_FASTPATH: Boolean = true
|
||||||
// Regex caching aligns with JVM behavior on Android (Dalvik/ART)
|
// Regex caching aligns with JVM behavior on Android (Dalvik/ART)
|
||||||
actual val REGEX_CACHE: Boolean = true
|
actual val REGEX_CACHE: Boolean = true
|
||||||
|
actual val ARG_SMALL_ARITY_12: Boolean = false
|
||||||
|
actual val INDEX_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val RANGE_FAST_ITER: Boolean = false
|
||||||
}
|
}
|
||||||
@ -26,12 +26,13 @@ import net.sergeych.lyng.obj.ObjList
|
|||||||
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
||||||
// Small-arity fast path (no splats) to reduce allocations
|
// Small-arity fast path (no splats) to reduce allocations
|
||||||
if (PerfFlags.ARG_BUILDER) {
|
if (PerfFlags.ARG_BUILDER) {
|
||||||
|
val limit = if (PerfFlags.ARG_SMALL_ARITY_12) 12 else 8
|
||||||
var hasSplat = false
|
var hasSplat = false
|
||||||
var count = 0
|
var count = 0
|
||||||
for (pa in this) {
|
for (pa in this) {
|
||||||
if (pa.isSplat) { hasSplat = true; break }
|
if (pa.isSplat) { hasSplat = true; break }
|
||||||
count++
|
count++
|
||||||
if (count > 8) break
|
if (count > limit) break
|
||||||
}
|
}
|
||||||
if (!hasSplat && count == this.size) {
|
if (!hasSplat && count == this.size) {
|
||||||
val quick = when (count) {
|
val quick = when (count) {
|
||||||
@ -93,6 +94,60 @@ import net.sergeych.lyng.obj.ObjList
|
|||||||
val a7 = this.elementAt(7).value.execute(scope)
|
val a7 = this.elementAt(7).value.execute(scope)
|
||||||
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7), tailBlockMode)
|
||||||
}
|
}
|
||||||
|
9 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
|
val a0 = this.elementAt(0).value.execute(scope)
|
||||||
|
val a1 = this.elementAt(1).value.execute(scope)
|
||||||
|
val a2 = this.elementAt(2).value.execute(scope)
|
||||||
|
val a3 = this.elementAt(3).value.execute(scope)
|
||||||
|
val a4 = this.elementAt(4).value.execute(scope)
|
||||||
|
val a5 = this.elementAt(5).value.execute(scope)
|
||||||
|
val a6 = this.elementAt(6).value.execute(scope)
|
||||||
|
val a7 = this.elementAt(7).value.execute(scope)
|
||||||
|
val a8 = this.elementAt(8).value.execute(scope)
|
||||||
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8), tailBlockMode)
|
||||||
|
} else null
|
||||||
|
10 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
|
val a0 = this.elementAt(0).value.execute(scope)
|
||||||
|
val a1 = this.elementAt(1).value.execute(scope)
|
||||||
|
val a2 = this.elementAt(2).value.execute(scope)
|
||||||
|
val a3 = this.elementAt(3).value.execute(scope)
|
||||||
|
val a4 = this.elementAt(4).value.execute(scope)
|
||||||
|
val a5 = this.elementAt(5).value.execute(scope)
|
||||||
|
val a6 = this.elementAt(6).value.execute(scope)
|
||||||
|
val a7 = this.elementAt(7).value.execute(scope)
|
||||||
|
val a8 = this.elementAt(8).value.execute(scope)
|
||||||
|
val a9 = this.elementAt(9).value.execute(scope)
|
||||||
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9), tailBlockMode)
|
||||||
|
} else null
|
||||||
|
11 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
|
val a0 = this.elementAt(0).value.execute(scope)
|
||||||
|
val a1 = this.elementAt(1).value.execute(scope)
|
||||||
|
val a2 = this.elementAt(2).value.execute(scope)
|
||||||
|
val a3 = this.elementAt(3).value.execute(scope)
|
||||||
|
val a4 = this.elementAt(4).value.execute(scope)
|
||||||
|
val a5 = this.elementAt(5).value.execute(scope)
|
||||||
|
val a6 = this.elementAt(6).value.execute(scope)
|
||||||
|
val a7 = this.elementAt(7).value.execute(scope)
|
||||||
|
val a8 = this.elementAt(8).value.execute(scope)
|
||||||
|
val a9 = this.elementAt(9).value.execute(scope)
|
||||||
|
val a10 = this.elementAt(10).value.execute(scope)
|
||||||
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10), tailBlockMode)
|
||||||
|
} else null
|
||||||
|
12 -> if (PerfFlags.ARG_SMALL_ARITY_12) {
|
||||||
|
val a0 = this.elementAt(0).value.execute(scope)
|
||||||
|
val a1 = this.elementAt(1).value.execute(scope)
|
||||||
|
val a2 = this.elementAt(2).value.execute(scope)
|
||||||
|
val a3 = this.elementAt(3).value.execute(scope)
|
||||||
|
val a4 = this.elementAt(4).value.execute(scope)
|
||||||
|
val a5 = this.elementAt(5).value.execute(scope)
|
||||||
|
val a6 = this.elementAt(6).value.execute(scope)
|
||||||
|
val a7 = this.elementAt(7).value.execute(scope)
|
||||||
|
val a8 = this.elementAt(8).value.execute(scope)
|
||||||
|
val a9 = this.elementAt(9).value.execute(scope)
|
||||||
|
val a10 = this.elementAt(10).value.execute(scope)
|
||||||
|
val a11 = this.elementAt(11).value.execute(scope)
|
||||||
|
Arguments(listOf(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11), tailBlockMode)
|
||||||
|
} else null
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
if (quick != null) return quick
|
if (quick != null) return quick
|
||||||
|
|||||||
@ -1845,6 +1845,92 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var lastPriority = 0
|
private var lastPriority = 0
|
||||||
|
// Helpers for conservative constant folding (literal-only). Only pure, side-effect-free ops.
|
||||||
|
private fun constOf(r: ObjRef): Obj? = (r as? ConstRef)?.constValue
|
||||||
|
|
||||||
|
private fun foldBinary(op: BinOp, aRef: ObjRef, bRef: ObjRef): Obj? {
|
||||||
|
val a = constOf(aRef) ?: return null
|
||||||
|
val b = constOf(bRef) ?: return null
|
||||||
|
return when (op) {
|
||||||
|
// Boolean logic
|
||||||
|
BinOp.OR -> if (a is ObjBool && b is ObjBool) if (a.value || b.value) ObjTrue else ObjFalse else null
|
||||||
|
BinOp.AND -> if (a is ObjBool && b is ObjBool) if (a.value && b.value) ObjTrue else ObjFalse else null
|
||||||
|
|
||||||
|
// Equality and comparisons for ints/strings/chars
|
||||||
|
BinOp.EQ -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value == b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value == b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value == b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.NEQ -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value != b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.LT -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value < b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.LTE -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value <= b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.GT -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value > b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.GTE -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> if (a.value >= b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjString && b is ObjString -> if (a.value >= b.value) ObjTrue else ObjFalse
|
||||||
|
a is ObjChar && b is ObjChar -> if (a.value >= b.value) ObjTrue else ObjFalse
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arithmetic for ints only (keep semantics simple at compile time)
|
||||||
|
BinOp.PLUS -> when {
|
||||||
|
a is ObjInt && b is ObjInt -> ObjInt(a.value + b.value)
|
||||||
|
a is ObjString && b is ObjString -> ObjString(a.value + b.value)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
BinOp.MINUS -> if (a is ObjInt && b is ObjInt) ObjInt(a.value - b.value) else null
|
||||||
|
BinOp.STAR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value * b.value) else null
|
||||||
|
BinOp.SLASH -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value / b.value) else null
|
||||||
|
BinOp.PERCENT -> if (a is ObjInt && b is ObjInt && b.value != 0L) ObjInt(a.value % b.value) else null
|
||||||
|
|
||||||
|
// Bitwise for ints
|
||||||
|
BinOp.BAND -> if (a is ObjInt && b is ObjInt) ObjInt(a.value and b.value) else null
|
||||||
|
BinOp.BXOR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value xor b.value) else null
|
||||||
|
BinOp.BOR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value or b.value) else null
|
||||||
|
BinOp.SHL -> if (a is ObjInt && b is ObjInt) ObjInt(a.value shl (b.value.toInt() and 63)) else null
|
||||||
|
BinOp.SHR -> if (a is ObjInt && b is ObjInt) ObjInt(a.value shr (b.value.toInt() and 63)) else null
|
||||||
|
|
||||||
|
// Non-folded / side-effecting or type-dependent ops
|
||||||
|
BinOp.EQARROW, BinOp.REF_EQ, BinOp.REF_NEQ, BinOp.MATCH, BinOp.NOTMATCH,
|
||||||
|
BinOp.IN, BinOp.NOTIN, BinOp.IS, BinOp.NOTIS, BinOp.SHUTTLE -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun foldUnary(op: UnaryOp, aRef: ObjRef): Obj? {
|
||||||
|
val a = constOf(aRef) ?: return null
|
||||||
|
return when (op) {
|
||||||
|
UnaryOp.NOT -> if (a is ObjBool) if (!a.value) ObjTrue else ObjFalse else null
|
||||||
|
UnaryOp.NEGATE -> when (a) {
|
||||||
|
is ObjInt -> ObjInt(-a.value)
|
||||||
|
is ObjReal -> ObjReal(-a.value)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
UnaryOp.BITNOT -> if (a is ObjInt) ObjInt(a.value.inv()) else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val allOps = listOf(
|
val allOps = listOf(
|
||||||
// assignments, lowest priority
|
// assignments, lowest priority
|
||||||
Operator(Token.Type.ASSIGN, lastPriority) { pos, a, b ->
|
Operator(Token.Type.ASSIGN, lastPriority) { pos, a, b ->
|
||||||
@ -1867,6 +1953,7 @@ class Compiler(
|
|||||||
},
|
},
|
||||||
// logical 1
|
// logical 1
|
||||||
Operator(Token.Type.OR, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.OR, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.OR, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
LogicalOrRef(a, b)
|
LogicalOrRef(a, b)
|
||||||
},
|
},
|
||||||
// logical 2
|
// logical 2
|
||||||
@ -1875,12 +1962,15 @@ class Compiler(
|
|||||||
},
|
},
|
||||||
// bitwise or/xor/and (tighter than &&, looser than equality)
|
// bitwise or/xor/and (tighter than &&, looser than equality)
|
||||||
Operator(Token.Type.BITOR, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.BITOR, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.BOR, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.BOR, a, b)
|
BinaryOpRef(BinOp.BOR, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.BITXOR, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.BITXOR, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.BXOR, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.BXOR, a, b)
|
BinaryOpRef(BinOp.BXOR, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.BITAND, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.BITAND, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.BAND, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.BAND, a, b)
|
BinaryOpRef(BinOp.BAND, a, b)
|
||||||
},
|
},
|
||||||
// equality/not equality and related
|
// equality/not equality and related
|
||||||
@ -1888,9 +1978,11 @@ class Compiler(
|
|||||||
BinaryOpRef(BinOp.EQARROW, a, b)
|
BinaryOpRef(BinOp.EQARROW, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.EQ, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.EQ, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.EQ, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.EQ, a, b)
|
BinaryOpRef(BinOp.EQ, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.NEQ, lastPriority) { _, a, b ->
|
Operator(Token.Type.NEQ, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.NEQ, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.NEQ, a, b)
|
BinaryOpRef(BinOp.NEQ, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.REF_EQ, lastPriority) { _, a, b ->
|
Operator(Token.Type.REF_EQ, lastPriority) { _, a, b ->
|
||||||
@ -1907,15 +1999,19 @@ class Compiler(
|
|||||||
},
|
},
|
||||||
// relational <=,...
|
// relational <=,...
|
||||||
Operator(Token.Type.LTE, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.LTE, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.LTE, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.LTE, a, b)
|
BinaryOpRef(BinOp.LTE, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.LT, lastPriority) { _, a, b ->
|
Operator(Token.Type.LT, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.LT, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.LT, a, b)
|
BinaryOpRef(BinOp.LT, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.GTE, lastPriority) { _, a, b ->
|
Operator(Token.Type.GTE, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.GTE, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.GTE, a, b)
|
BinaryOpRef(BinOp.GTE, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.GT, lastPriority) { _, a, b ->
|
Operator(Token.Type.GT, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.GT, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.GT, a, b)
|
BinaryOpRef(BinOp.GT, a, b)
|
||||||
},
|
},
|
||||||
// in, is:
|
// in, is:
|
||||||
@ -1942,25 +2038,32 @@ class Compiler(
|
|||||||
},
|
},
|
||||||
// shifts (tighter than shuttle, looser than +/-)
|
// shifts (tighter than shuttle, looser than +/-)
|
||||||
Operator(Token.Type.SHL, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.SHL, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.SHL, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.SHL, a, b)
|
BinaryOpRef(BinOp.SHL, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.SHR, lastPriority) { _, a, b ->
|
Operator(Token.Type.SHR, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.SHR, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.SHR, a, b)
|
BinaryOpRef(BinOp.SHR, a, b)
|
||||||
},
|
},
|
||||||
// arithmetic
|
// arithmetic
|
||||||
Operator(Token.Type.PLUS, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.PLUS, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.PLUS, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.PLUS, a, b)
|
BinaryOpRef(BinOp.PLUS, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.MINUS, lastPriority) { _, a, b ->
|
Operator(Token.Type.MINUS, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.MINUS, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.MINUS, a, b)
|
BinaryOpRef(BinOp.MINUS, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.STAR, ++lastPriority) { _, a, b ->
|
Operator(Token.Type.STAR, ++lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.STAR, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.STAR, a, b)
|
BinaryOpRef(BinOp.STAR, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.SLASH, lastPriority) { _, a, b ->
|
Operator(Token.Type.SLASH, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.SLASH, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.SLASH, a, b)
|
BinaryOpRef(BinOp.SLASH, a, b)
|
||||||
},
|
},
|
||||||
Operator(Token.Type.PERCENT, lastPriority) { _, a, b ->
|
Operator(Token.Type.PERCENT, lastPriority) { _, a, b ->
|
||||||
|
foldBinary(BinOp.PERCENT, a, b)?.let { return@Operator ConstRef(it.asReadonly) }
|
||||||
BinaryOpRef(BinOp.PERCENT, a, b)
|
BinaryOpRef(BinOp.PERCENT, a, b)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@ -32,6 +32,14 @@ expect object PerfDefaults {
|
|||||||
|
|
||||||
val FIELD_PIC: Boolean
|
val FIELD_PIC: Boolean
|
||||||
val METHOD_PIC: Boolean
|
val METHOD_PIC: Boolean
|
||||||
|
// Optional larger PICs and adaptivity (JVM-first)
|
||||||
|
val FIELD_PIC_SIZE_4: Boolean
|
||||||
|
val METHOD_PIC_SIZE_4: Boolean
|
||||||
|
val PIC_ADAPTIVE_2_TO_4: Boolean
|
||||||
|
// Enable adaptivity only for methods (independent of fields)
|
||||||
|
val PIC_ADAPTIVE_METHODS_ONLY: Boolean
|
||||||
|
// Enable per-site heuristic to revert/avoid promotion when it shows no benefit
|
||||||
|
val PIC_ADAPTIVE_HEURISTIC: Boolean
|
||||||
|
|
||||||
val PIC_DEBUG_COUNTERS: Boolean
|
val PIC_DEBUG_COUNTERS: Boolean
|
||||||
|
|
||||||
@ -40,4 +48,13 @@ expect object PerfDefaults {
|
|||||||
|
|
||||||
// Regex caching (JVM-first): small LRU for compiled patterns
|
// Regex caching (JVM-first): small LRU for compiled patterns
|
||||||
val REGEX_CACHE: Boolean
|
val REGEX_CACHE: Boolean
|
||||||
|
|
||||||
|
// Argument builder extended arity (JVM-first exploration)
|
||||||
|
val ARG_SMALL_ARITY_12: Boolean
|
||||||
|
|
||||||
|
// Index PIC sizing (JVM-first)
|
||||||
|
val INDEX_PIC_SIZE_4: Boolean
|
||||||
|
|
||||||
|
// Specialized non-allocating integer range iteration in hot loops
|
||||||
|
val RANGE_FAST_ITER: Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,8 @@ object PerfFlags {
|
|||||||
|
|
||||||
// Enable more efficient argument building and bulk-copy for splats
|
// Enable more efficient argument building and bulk-copy for splats
|
||||||
var ARG_BUILDER: Boolean = PerfDefaults.ARG_BUILDER
|
var ARG_BUILDER: Boolean = PerfDefaults.ARG_BUILDER
|
||||||
|
// Extend small-arity no-alloc argument paths to 9..12 (JVM-first; default via PerfDefaults)
|
||||||
|
var ARG_SMALL_ARITY_12: Boolean = PerfDefaults.ARG_SMALL_ARITY_12
|
||||||
// Allow early-return in optional calls before building args (semantics-compatible). Present for A/B only.
|
// Allow early-return in optional calls before building args (semantics-compatible). Present for A/B only.
|
||||||
var SKIP_ARGS_ON_NULL_RECEIVER: Boolean = PerfDefaults.SKIP_ARGS_ON_NULL_RECEIVER
|
var SKIP_ARGS_ON_NULL_RECEIVER: Boolean = PerfDefaults.SKIP_ARGS_ON_NULL_RECEIVER
|
||||||
// Enable pooling of Scope frames for calls (may be JVM-only optimization)
|
// Enable pooling of Scope frames for calls (may be JVM-only optimization)
|
||||||
@ -39,6 +41,23 @@ object PerfFlags {
|
|||||||
var FIELD_PIC: Boolean = PerfDefaults.FIELD_PIC
|
var FIELD_PIC: Boolean = PerfDefaults.FIELD_PIC
|
||||||
var METHOD_PIC: Boolean = PerfDefaults.METHOD_PIC
|
var METHOD_PIC: Boolean = PerfDefaults.METHOD_PIC
|
||||||
|
|
||||||
|
// Optional: expand PIC size for fields/methods from 2 to 4 entries (JVM-first tuning)
|
||||||
|
// Initialized from platform defaults; still runtime-togglable.
|
||||||
|
var FIELD_PIC_SIZE_4: Boolean = PerfDefaults.FIELD_PIC_SIZE_4
|
||||||
|
var METHOD_PIC_SIZE_4: Boolean = PerfDefaults.METHOD_PIC_SIZE_4
|
||||||
|
// Adaptive growth from 2→4 entries per-site for field/method PICs when polymorphism is high (JVM-first)
|
||||||
|
var PIC_ADAPTIVE_2_TO_4: Boolean = PerfDefaults.PIC_ADAPTIVE_2_TO_4
|
||||||
|
// Adaptive growth for methods only (independent of fields)
|
||||||
|
var PIC_ADAPTIVE_METHODS_ONLY: Boolean = PerfDefaults.PIC_ADAPTIVE_METHODS_ONLY
|
||||||
|
// Heuristic to avoid or revert promotion when it shows no benefit (experimental)
|
||||||
|
var PIC_ADAPTIVE_HEURISTIC: Boolean = PerfDefaults.PIC_ADAPTIVE_HEURISTIC
|
||||||
|
|
||||||
|
// Index PIC/fast paths (JVM-first). By default follow FIELD_PIC enablement to avoid extra flags churn.
|
||||||
|
// Host apps/tests can flip independently if needed.
|
||||||
|
var INDEX_PIC: Boolean = FIELD_PIC
|
||||||
|
// Optional 4-entry PIC for IndexRef (JVM-first tuning); initialized from platform defaults
|
||||||
|
var INDEX_PIC_SIZE_4: Boolean = PerfDefaults.INDEX_PIC_SIZE_4
|
||||||
|
|
||||||
// Debug/observability for PICs and fast paths (JVM-first)
|
// Debug/observability for PICs and fast paths (JVM-first)
|
||||||
var PIC_DEBUG_COUNTERS: Boolean = PerfDefaults.PIC_DEBUG_COUNTERS
|
var PIC_DEBUG_COUNTERS: Boolean = PerfDefaults.PIC_DEBUG_COUNTERS
|
||||||
|
|
||||||
@ -50,4 +69,7 @@ object PerfFlags {
|
|||||||
|
|
||||||
// Regex: enable small LRU cache for compiled patterns (JVM-first usage)
|
// Regex: enable small LRU cache for compiled patterns (JVM-first usage)
|
||||||
var REGEX_CACHE: Boolean = PerfDefaults.REGEX_CACHE
|
var REGEX_CACHE: Boolean = PerfDefaults.REGEX_CACHE
|
||||||
|
|
||||||
|
// Specialized non-allocating integer range iteration in hot loops
|
||||||
|
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
|
||||||
}
|
}
|
||||||
|
|||||||
@ -41,6 +41,10 @@ object PerfStats {
|
|||||||
// Primitive fast ops
|
// Primitive fast ops
|
||||||
var primitiveFastOpsHit: Long = 0
|
var primitiveFastOpsHit: Long = 0
|
||||||
|
|
||||||
|
// Index PIC
|
||||||
|
var indexPicHit: Long = 0
|
||||||
|
var indexPicMiss: Long = 0
|
||||||
|
|
||||||
fun resetAll() {
|
fun resetAll() {
|
||||||
fieldPicHit = 0
|
fieldPicHit = 0
|
||||||
fieldPicMiss = 0
|
fieldPicMiss = 0
|
||||||
@ -53,5 +57,7 @@ object PerfStats {
|
|||||||
fastLocalHit = 0
|
fastLocalHit = 0
|
||||||
fastLocalMiss = 0
|
fastLocalMiss = 0
|
||||||
primitiveFastOpsHit = 0
|
primitiveFastOpsHit = 0
|
||||||
|
indexPicHit = 0
|
||||||
|
indexPicMiss = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -161,6 +161,18 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
}
|
}
|
||||||
addFn("iterator") {
|
addFn("iterator") {
|
||||||
val self = thisAs<ObjRange>()
|
val self = thisAs<ObjRange>()
|
||||||
|
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
|
||||||
|
val s = self.start
|
||||||
|
val e = self.end
|
||||||
|
if (s is ObjInt && e is ObjInt) {
|
||||||
|
val start = s.value.toInt()
|
||||||
|
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
|
||||||
|
// Only for ascending simple ranges; fall back otherwise
|
||||||
|
if (start <= endExclusive) {
|
||||||
|
return@addFn ObjFastIntRangeIterator(start, endExclusive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ObjRangeIterator(self).apply { init() }
|
ObjRangeIterator(self).apply { init() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
|
||||||
class ObjRangeIterator(val self: ObjRange) : Obj() {
|
class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||||
@ -66,3 +67,27 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fast iterator for simple integer ranges (step +1). Returned only when
|
||||||
|
* [PerfFlags.RANGE_FAST_ITER] is enabled and the range is an ascending int range.
|
||||||
|
*/
|
||||||
|
class ObjFastIntRangeIterator(private val start: Int, private val endExclusive: Int) : Obj() {
|
||||||
|
|
||||||
|
private var cur: Int = start
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
|
fun hasNext(): Boolean = cur < endExclusive
|
||||||
|
|
||||||
|
fun next(scope: Scope): Obj =
|
||||||
|
if (cur < endExclusive) ObjInt(cur++.toLong())
|
||||||
|
else scope.raiseError(ObjIterationFinishedException(scope))
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type = ObjClass("FastIntRangeIterator", ObjIterator).apply {
|
||||||
|
addFn("hasNext") { thisAs<ObjFastIntRangeIterator>().hasNext().toObj() }
|
||||||
|
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(this) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -62,7 +62,23 @@ enum class BinOp {
|
|||||||
/** R-value reference for unary operations. */
|
/** R-value reference for unary operations. */
|
||||||
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val v = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) a.evalValue(scope) else a.get(scope).value
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
|
val v = if (fastRval) a.evalValue(scope) else a.get(scope).value
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||||
|
val rFast: Obj? = when (op) {
|
||||||
|
UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null
|
||||||
|
UnaryOp.NEGATE -> when (v) {
|
||||||
|
is ObjInt -> ObjInt(-v.value)
|
||||||
|
is ObjReal -> ObjReal(-v.value)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
UnaryOp.BITNOT -> if (v is ObjInt) ObjInt(v.value.inv()) else null
|
||||||
|
}
|
||||||
|
if (rFast != null) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.primitiveFastOpsHit++
|
||||||
|
return rFast.asReadonly
|
||||||
|
}
|
||||||
|
}
|
||||||
val r = when (op) {
|
val r = when (op) {
|
||||||
UnaryOp.NOT -> v.logicalNot(scope)
|
UnaryOp.NOT -> v.logicalNot(scope)
|
||||||
UnaryOp.NEGATE -> v.negate(scope)
|
UnaryOp.NEGATE -> v.negate(scope)
|
||||||
@ -303,8 +319,9 @@ class IncDecRef(
|
|||||||
/** Elvis operator reference: a ?: b */
|
/** Elvis operator reference: a ?: b */
|
||||||
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val a = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) left.evalValue(scope) else left.get(scope).value
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
val r = if (a != ObjNull) a else if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) right.evalValue(scope) else right.get(scope).value
|
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||||
|
val r = if (a != ObjNull) a else if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||||
return r.asReadonly
|
return r.asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,10 +329,12 @@ class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
|||||||
/** Logical OR with short-circuit: a || b */
|
/** Logical OR with short-circuit: a || b */
|
||||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val a = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) left.evalValue(scope) else left.get(scope).value
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
|
val fastPrim = net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS
|
||||||
|
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||||
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
||||||
val b = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) right.evalValue(scope) else right.get(scope).value
|
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
if (fastPrim) {
|
||||||
if (a is ObjBool && b is ObjBool) {
|
if (a is ObjBool && b is ObjBool) {
|
||||||
return if (a.value || b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
return if (a.value || b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||||
}
|
}
|
||||||
@ -347,6 +366,8 @@ class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRe
|
|||||||
*/
|
*/
|
||||||
class ConstRef(private val record: ObjRecord) : ObjRef {
|
class ConstRef(private val record: ObjRecord) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord = record
|
override suspend fun get(scope: Scope): ObjRecord = record
|
||||||
|
// Expose constant value for compiler constant folding (pure, read-only)
|
||||||
|
val constValue: Obj get() = record.value
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -373,6 +394,48 @@ class FieldRef(
|
|||||||
// Transient per-step cache to optimize read-then-write sequences within the same frame
|
// Transient per-step cache to optimize read-then-write sequences within the same frame
|
||||||
private var tKey: Long = 0L; private var tVer: Int = -1; private var tFrameId: Long = -1L; private var tRecord: ObjRecord? = null
|
private var tKey: Long = 0L; private var tVer: Int = -1; private var tFrameId: Long = -1L; private var tRecord: ObjRecord? = null
|
||||||
|
|
||||||
|
// Adaptive PIC (2→4) for reads/writes
|
||||||
|
private var rAccesses: Int = 0; private var rMisses: Int = 0; private var rPromotedTo4: Boolean = false
|
||||||
|
private var wAccesses: Int = 0; private var wMisses: Int = 0; private var wPromotedTo4: Boolean = false
|
||||||
|
private inline fun size4ReadsEnabled(): Boolean =
|
||||||
|
net.sergeych.lyng.PerfFlags.FIELD_PIC_SIZE_4 ||
|
||||||
|
(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 && rPromotedTo4)
|
||||||
|
private inline fun size4WritesEnabled(): Boolean =
|
||||||
|
net.sergeych.lyng.PerfFlags.FIELD_PIC_SIZE_4 ||
|
||||||
|
(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 && wPromotedTo4)
|
||||||
|
private fun noteReadHit() {
|
||||||
|
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
|
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
rAccesses = a
|
||||||
|
}
|
||||||
|
private fun noteReadMiss() {
|
||||||
|
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
|
val a = (rAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
rAccesses = a
|
||||||
|
rMisses = (rMisses + 1).coerceAtMost(1_000_000)
|
||||||
|
if (!rPromotedTo4 && a >= 256) {
|
||||||
|
// promote if miss rate > 20%
|
||||||
|
if (rMisses * 100 / a > 20) rPromotedTo4 = true
|
||||||
|
// reset counters after decision
|
||||||
|
rAccesses = 0; rMisses = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun noteWriteHit() {
|
||||||
|
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
|
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
wAccesses = a
|
||||||
|
}
|
||||||
|
private fun noteWriteMiss() {
|
||||||
|
if (!net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4) return
|
||||||
|
val a = (wAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
wAccesses = a
|
||||||
|
wMisses = (wMisses + 1).coerceAtMost(1_000_000)
|
||||||
|
if (!wPromotedTo4 && a >= 256) {
|
||||||
|
if (wMisses * 100 / a > 20) wPromotedTo4 = true
|
||||||
|
wAccesses = 0; wMisses = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
||||||
@ -383,6 +446,7 @@ class FieldRef(
|
|||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
noteReadHit()
|
||||||
val rec0 = g(base, scope)
|
val rec0 = g(base, scope)
|
||||||
if (base is ObjClass) {
|
if (base is ObjClass) {
|
||||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||||
@ -392,6 +456,7 @@ class FieldRef(
|
|||||||
} }
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
noteReadHit()
|
||||||
// move-to-front: promote 2→1
|
// move-to-front: promote 2→1
|
||||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
@ -403,8 +468,9 @@ class FieldRef(
|
|||||||
} else { tRecord = null }
|
} else { tRecord = null }
|
||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
noteReadHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -417,8 +483,9 @@ class FieldRef(
|
|||||||
} else { tRecord = null }
|
} else { tRecord = null }
|
||||||
return rec0
|
return rec0
|
||||||
} }
|
} }
|
||||||
rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
noteReadHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
@ -434,6 +501,7 @@ class FieldRef(
|
|||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicMiss++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicMiss++
|
||||||
|
noteReadMiss()
|
||||||
val rec = try {
|
val rec = try {
|
||||||
base.readField(scope, name)
|
base.readField(scope, name)
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
@ -444,9 +512,11 @@ class FieldRef(
|
|||||||
rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
// Install move-to-front with a handle-aware getter (shift 1→2→3→4; put new at 1)
|
// Install move-to-front with a handle-aware getter; honor PIC size flag
|
||||||
|
if (size4ReadsEnabled()) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
}
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
when (base) {
|
when (base) {
|
||||||
is ObjClass -> {
|
is ObjClass -> {
|
||||||
@ -499,18 +569,21 @@ class FieldRef(
|
|||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
||||||
|
noteWriteHit()
|
||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
||||||
|
noteWriteHit()
|
||||||
// move-to-front: promote 2→1
|
// move-to-front: promote 2→1
|
||||||
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
||||||
|
noteWriteHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = wKey3; val tV = wVer3; val tS = wSetter3
|
val tK = wKey3; val tV = wVer3; val tS = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
@ -518,8 +591,9 @@ class FieldRef(
|
|||||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||||
return s(base, scope, newValue)
|
return s(base, scope, newValue)
|
||||||
} }
|
} }
|
||||||
wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetHit++
|
||||||
|
noteWriteHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = wKey4; val tV = wVer4; val tS = wSetter4
|
val tK = wKey4; val tV = wVer4; val tS = wSetter4
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
@ -530,10 +604,13 @@ class FieldRef(
|
|||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetMiss++
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicSetMiss++
|
||||||
|
noteWriteMiss()
|
||||||
base.writeField(scope, name, newValue)
|
base.writeField(scope, name, newValue)
|
||||||
// Install move-to-front with a handle-aware setter (shift 1→2→3→4; put new at 1)
|
// Install move-to-front with a handle-aware setter; honor PIC size flag
|
||||||
|
if (size4WritesEnabled()) {
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
|
}
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
when (base) {
|
when (base) {
|
||||||
is ObjClass -> {
|
is ObjClass -> {
|
||||||
@ -566,6 +643,63 @@ class FieldRef(
|
|||||||
is ObjClass -> obj.classId to obj.layoutVersion
|
is ObjClass -> obj.classId to obj.layoutVersion
|
||||||
else -> 0L to -1 // no caching for primitives/dynamics without stable shape
|
else -> 0L to -1 // no caching for primitives/dynamics without stable shape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
|
// Mirror get(), but return raw Obj to avoid transient ObjRecord on R-value paths
|
||||||
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
|
val fieldPic = net.sergeych.lyng.PerfFlags.FIELD_PIC
|
||||||
|
val picCounters = net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS
|
||||||
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
|
if (fieldPic) {
|
||||||
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
return g(base, scope).value
|
||||||
|
} }
|
||||||
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||||
|
return g(base, scope).value
|
||||||
|
} }
|
||||||
|
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
val tK = rKey3; val tV = rVer3; val tG = rGetter3
|
||||||
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||||
|
return g(base, scope).value
|
||||||
|
} }
|
||||||
|
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicHit++
|
||||||
|
val tK = rKey4; val tV = rVer4; val tG = rGetter4
|
||||||
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||||
|
return g(base, scope).value
|
||||||
|
} }
|
||||||
|
if (picCounters) net.sergeych.lyng.PerfStats.fieldPicMiss++
|
||||||
|
val rec = base.readField(scope, name)
|
||||||
|
// install primary generic getter for this shape
|
||||||
|
when (base) {
|
||||||
|
is ObjClass -> {
|
||||||
|
rKey1 = base.classId; rVer1 = base.layoutVersion; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
|
}
|
||||||
|
is ObjInstance -> {
|
||||||
|
val cls = base.objClass
|
||||||
|
rKey1 = cls.classId; rVer1 = cls.layoutVersion; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
rKey1 = 0L; rVer1 = -1; rGetter1 = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rec.value
|
||||||
|
}
|
||||||
|
return base.readField(scope, name).value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -605,6 +739,17 @@ class IndexRef(
|
|||||||
// Bounds checks are enforced by the underlying list access; exceptions propagate as before
|
// Bounds checks are enforced by the underlying list access; exceptions propagate as before
|
||||||
return base.list[i].asMutable
|
return base.list[i].asMutable
|
||||||
}
|
}
|
||||||
|
// String[Int] fast path
|
||||||
|
if (base is ObjString && idx is ObjInt) {
|
||||||
|
val i = idx.toInt()
|
||||||
|
return ObjChar(base.value[i]).asMutable
|
||||||
|
}
|
||||||
|
// Map[String] fast path (common case); return ObjNull if absent
|
||||||
|
if (base is ObjMap && idx is ObjString) {
|
||||||
|
val v = base.map[idx] ?: ObjNull
|
||||||
|
return v.asMutable
|
||||||
|
}
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for other common shapes
|
// Polymorphic inline cache for other common shapes
|
||||||
val (key, ver) = when (base) {
|
val (key, ver) = when (base) {
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
@ -612,21 +757,27 @@ class IndexRef(
|
|||||||
else -> 0L to -1
|
else -> 0L to -1
|
||||||
}
|
}
|
||||||
if (key != 0L) {
|
if (key != 0L) {
|
||||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) return g(base, scope, idx).asMutable }
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
|
return g(base, scope, idx).asMutable
|
||||||
|
} }
|
||||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
@ -635,17 +786,92 @@ class IndexRef(
|
|||||||
return g(base, scope, idx).asMutable
|
return g(base, scope, idx).asMutable
|
||||||
} }
|
} }
|
||||||
// Miss: resolve and install generic handler
|
// Miss: resolve and install generic handler
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicMiss++
|
||||||
val v = base.getAt(scope, idx)
|
val v = base.getAt(scope, idx)
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
}
|
||||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||||
return v.asMutable
|
return v.asMutable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return base.getAt(scope, idx).asMutable
|
return base.getAt(scope, idx).asMutable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
|
if (base == ObjNull && isOptional) return ObjNull
|
||||||
|
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||||
|
if (fastRval) {
|
||||||
|
// Fast list[int] path
|
||||||
|
if (base is ObjList && idx is ObjInt) {
|
||||||
|
val i = idx.toInt()
|
||||||
|
return base.list[i]
|
||||||
|
}
|
||||||
|
// String[Int] fast path
|
||||||
|
if (base is ObjString && idx is ObjInt) {
|
||||||
|
val i = idx.toInt()
|
||||||
|
return ObjChar(base.value[i])
|
||||||
|
}
|
||||||
|
// Map[String] fast path
|
||||||
|
if (base is ObjMap && idx is ObjString) {
|
||||||
|
return base.map[idx] ?: ObjNull
|
||||||
|
}
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
||||||
|
// PIC path analogous to get(), but returning raw Obj
|
||||||
|
val (key, ver) = when (base) {
|
||||||
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
|
is ObjClass -> base.classId to base.layoutVersion
|
||||||
|
else -> 0L to -1
|
||||||
|
}
|
||||||
|
if (key != 0L) {
|
||||||
|
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
|
return g(base, scope, idx)
|
||||||
|
} }
|
||||||
|
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
|
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
|
return g(base, scope, idx)
|
||||||
|
} }
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
|
val tk = rKey3; val tv = rVer3; val tg = rGetter3
|
||||||
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
|
return g(base, scope, idx)
|
||||||
|
} }
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicHit++
|
||||||
|
val tk = rKey4; val tv = rVer4; val tg = rGetter4
|
||||||
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||||
|
return g(base, scope, idx)
|
||||||
|
} }
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.indexPicMiss++
|
||||||
|
val v = base.getAt(scope, idx)
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
|
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||||
|
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||||
|
}
|
||||||
|
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||||
|
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.getAt(scope, idx)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||||
@ -661,6 +887,12 @@ class IndexRef(
|
|||||||
base.list[i] = newValue
|
base.list[i] = newValue
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Direct write fast path for ObjMap + ObjString
|
||||||
|
if (base is ObjMap && idx is ObjString) {
|
||||||
|
base.map[idx] = newValue
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC) {
|
||||||
// Polymorphic inline cache for index write
|
// Polymorphic inline cache for index write
|
||||||
val (key, ver) = when (base) {
|
val (key, ver) = when (base) {
|
||||||
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
is ObjInstance -> base.objClass.classId to base.objClass.layoutVersion
|
||||||
@ -675,14 +907,14 @@ class IndexRef(
|
|||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
s(base, scope, idx, newValue); return
|
s(base, scope, idx, newValue); return
|
||||||
} }
|
} }
|
||||||
wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||||
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||||
s(base, scope, idx, newValue); return
|
s(base, scope, idx, newValue); return
|
||||||
} }
|
} }
|
||||||
wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||||
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
@ -692,13 +924,16 @@ class IndexRef(
|
|||||||
} }
|
} }
|
||||||
// Miss: perform write and install generic handler
|
// Miss: perform write and install generic handler
|
||||||
base.putAt(scope, idx, newValue)
|
base.putAt(scope, idx, newValue)
|
||||||
|
if (net.sergeych.lyng.PerfFlags.INDEX_PIC_SIZE_4) {
|
||||||
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
wKey4 = wKey3; wVer4 = wVer3; wSetter4 = wSetter3
|
||||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||||
|
}
|
||||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
base.putAt(scope, idx, newValue)
|
base.putAt(scope, idx, newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -752,6 +987,63 @@ class MethodCallRef(
|
|||||||
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||||
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||||
|
|
||||||
|
// Adaptive PIC (2→4) for methods
|
||||||
|
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false
|
||||||
|
// Heuristic: windowed miss-rate tracking and temporary freeze back to size=2
|
||||||
|
private var mFreezeWindowsLeft: Int = 0
|
||||||
|
private var mWindowAccesses: Int = 0
|
||||||
|
private var mWindowMisses: Int = 0
|
||||||
|
private inline fun size4MethodsEnabled(): Boolean =
|
||||||
|
net.sergeych.lyng.PerfFlags.METHOD_PIC_SIZE_4 ||
|
||||||
|
((net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY) && mPromotedTo4 && mFreezeWindowsLeft == 0)
|
||||||
|
private fun noteMethodHit() {
|
||||||
|
if (!(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
||||||
|
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
mAccesses = a
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
||||||
|
// Windowed tracking
|
||||||
|
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
if (mWindowAccesses >= 256) endHeuristicWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun noteMethodMiss() {
|
||||||
|
if (!(net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_2_TO_4 || net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_METHODS_ONLY)) return
|
||||||
|
val a = (mAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
mAccesses = a
|
||||||
|
mMisses = (mMisses + 1).coerceAtMost(1_000_000)
|
||||||
|
if (!mPromotedTo4 && mFreezeWindowsLeft == 0 && a >= 256) {
|
||||||
|
if (mMisses * 100 / a > 20) mPromotedTo4 = true
|
||||||
|
mAccesses = 0; mMisses = 0
|
||||||
|
}
|
||||||
|
if (net.sergeych.lyng.PerfFlags.PIC_ADAPTIVE_HEURISTIC) {
|
||||||
|
mWindowAccesses = (mWindowAccesses + 1).coerceAtMost(1_000_000)
|
||||||
|
mWindowMisses = (mWindowMisses + 1).coerceAtMost(1_000_000)
|
||||||
|
if (mWindowAccesses >= 256) endHeuristicWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun endHeuristicWindow() {
|
||||||
|
// Called only when PIC_ADAPTIVE_HEURISTIC is true
|
||||||
|
val accesses = mWindowAccesses
|
||||||
|
val misses = mWindowMisses
|
||||||
|
// Reset window
|
||||||
|
mWindowAccesses = 0
|
||||||
|
mWindowMisses = 0
|
||||||
|
// Count down freeze if active
|
||||||
|
if (mFreezeWindowsLeft > 0) {
|
||||||
|
mFreezeWindowsLeft = (mFreezeWindowsLeft - 1).coerceAtLeast(0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If promoted, but still high miss rate, freeze back to 2 for a few windows
|
||||||
|
if (mPromotedTo4 && accesses >= 256) {
|
||||||
|
val rate = misses * 100 / accesses
|
||||||
|
if (rate >= 25) {
|
||||||
|
mPromotedTo4 = false
|
||||||
|
mFreezeWindowsLeft = 4 // freeze next 4 windows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
val fastRval = net.sergeych.lyng.PerfFlags.RVAL_FASTPATH
|
||||||
val methodPic = net.sergeych.lyng.PerfFlags.METHOD_PIC
|
val methodPic = net.sergeych.lyng.PerfFlags.METHOD_PIC
|
||||||
@ -763,14 +1055,17 @@ class MethodCallRef(
|
|||||||
val (key, ver) = receiverKeyAndVersion(base)
|
val (key, ver) = receiverKeyAndVersion(base)
|
||||||
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) {
|
mInvoker1?.let { inv -> if (key == mKey1 && ver == mVer1) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
||||||
|
noteMethodHit()
|
||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) {
|
mInvoker2?.let { inv -> if (key == mKey2 && ver == mVer2) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
||||||
|
noteMethodHit()
|
||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
mInvoker3?.let { inv -> if (key == mKey3 && ver == mVer3) {
|
if (size4MethodsEnabled()) mInvoker3?.let { inv -> if (key == mKey3 && ver == mVer3) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
||||||
|
noteMethodHit()
|
||||||
// move-to-front: promote 3→1
|
// move-to-front: promote 3→1
|
||||||
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
|
val tK = mKey3; val tV = mVer3; val tI = mInvoker3
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||||
@ -778,8 +1073,9 @@ class MethodCallRef(
|
|||||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||||
return inv(base, scope, callArgs).asReadonly
|
return inv(base, scope, callArgs).asReadonly
|
||||||
} }
|
} }
|
||||||
mInvoker4?.let { inv -> if (key == mKey4 && ver == mVer4) {
|
if (size4MethodsEnabled()) mInvoker4?.let { inv -> if (key == mKey4 && ver == mVer4) {
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
if (picCounters) net.sergeych.lyng.PerfStats.methodPicHit++
|
||||||
|
noteMethodHit()
|
||||||
// move-to-front: promote 4→1
|
// move-to-front: promote 4→1
|
||||||
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
|
val tK = mKey4; val tV = mVer4; val tI = mInvoker4
|
||||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
||||||
@ -790,6 +1086,7 @@ class MethodCallRef(
|
|||||||
} }
|
} }
|
||||||
// Slow path
|
// Slow path
|
||||||
if (picCounters) net.sergeych.lyng.PerfStats.methodPicMiss++
|
if (picCounters) net.sergeych.lyng.PerfStats.methodPicMiss++
|
||||||
|
noteMethodMiss()
|
||||||
val result = try {
|
val result = try {
|
||||||
base.invokeInstanceMethod(scope, name, callArgs)
|
base.invokeInstanceMethod(scope, name, callArgs)
|
||||||
} catch (e: ExecutionError) {
|
} catch (e: ExecutionError) {
|
||||||
@ -800,9 +1097,11 @@ class MethodCallRef(
|
|||||||
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
// Install move-to-front with a handle-aware invoker: shift 1→2→3→4, put new at 1
|
// Install move-to-front with a handle-aware invoker; honor PIC size flag
|
||||||
|
if (size4MethodsEnabled()) {
|
||||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
||||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||||
|
}
|
||||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||||
when (base) {
|
when (base) {
|
||||||
is ObjInstance -> {
|
is ObjInstance -> {
|
||||||
@ -889,6 +1188,18 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
return scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
return scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
|
scope.pos = atPos
|
||||||
|
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||||
|
scope.getSlotIndexOf(name)?.let { return scope.getSlotRecord(it).value }
|
||||||
|
return (scope[name] ?: scope.raiseError("symbol not defined: '$name'")).value
|
||||||
|
}
|
||||||
|
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
||||||
|
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||||
|
if (slot >= 0) return scope.getSlotRecord(slot).value
|
||||||
|
return (scope[name] ?: scope.raiseError("symbol not defined: '$name'")).value
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||||
@ -929,6 +1240,11 @@ class BoundLocalVarRef(
|
|||||||
return scope.getSlotRecord(slot)
|
return scope.getSlotRecord(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
|
scope.pos = atPos
|
||||||
|
return scope.getSlotRecord(slot).value
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
val rec = scope.getSlotRecord(slot)
|
val rec = scope.getSlotRecord(slot)
|
||||||
@ -989,6 +1305,15 @@ class FastLocalVarRef(
|
|||||||
return actualOwner.getSlotRecord(slot)
|
return actualOwner.getSlotRecord(slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun evalValue(scope: Scope): Obj {
|
||||||
|
scope.pos = atPos
|
||||||
|
val ownerValid = isOwnerValidFor(scope)
|
||||||
|
val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
||||||
|
val actualOwner = cachedOwnerScope
|
||||||
|
if (slot < 0 || actualOwner == null) scope.raiseError("local '$name' is not available in this scope")
|
||||||
|
return actualOwner.getSlotRecord(slot).value
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
val owner = if (isOwnerValidFor(scope)) cachedOwnerScope else null
|
val owner = if (isOwnerValidFor(scope)) cachedOwnerScope else null
|
||||||
|
|||||||
@ -27,6 +27,11 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
@ -35,4 +40,7 @@ actual object PerfDefaults {
|
|||||||
actual val RVAL_FASTPATH: Boolean = false
|
actual val RVAL_FASTPATH: Boolean = false
|
||||||
// Regex caching: disabled by default on JS until validated
|
// Regex caching: disabled by default on JS until validated
|
||||||
actual val REGEX_CACHE: Boolean = false
|
actual val REGEX_CACHE: Boolean = false
|
||||||
|
actual val ARG_SMALL_ARITY_12: Boolean = false
|
||||||
|
actual val INDEX_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val RANGE_FAST_ITER: Boolean = false
|
||||||
}
|
}
|
||||||
@ -27,6 +27,11 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
@ -35,4 +40,13 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
// Regex caching (JVM-first): enabled by default on JVM
|
// Regex caching (JVM-first): enabled by default on JVM
|
||||||
actual val REGEX_CACHE: Boolean = true
|
actual val REGEX_CACHE: Boolean = true
|
||||||
|
|
||||||
|
// Extended small-arity calls 9..12 (experimental; keep OFF by default)
|
||||||
|
actual val ARG_SMALL_ARITY_12: Boolean = false
|
||||||
|
|
||||||
|
// Index PIC size (beneficial on JVM in A/B): enable size=4 by default
|
||||||
|
actual val INDEX_PIC_SIZE_4: Boolean = true
|
||||||
|
|
||||||
|
// Range fast-iteration (experimental; OFF by default)
|
||||||
|
actual val RANGE_FAST_ITER: Boolean = false
|
||||||
}
|
}
|
||||||
@ -27,6 +27,11 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
@ -35,4 +40,7 @@ actual object PerfDefaults {
|
|||||||
actual val RVAL_FASTPATH: Boolean = false
|
actual val RVAL_FASTPATH: Boolean = false
|
||||||
// Regex caching: keep OFF by default on Native until benchmarks validate it
|
// Regex caching: keep OFF by default on Native until benchmarks validate it
|
||||||
actual val REGEX_CACHE: Boolean = false
|
actual val REGEX_CACHE: Boolean = false
|
||||||
|
actual val ARG_SMALL_ARITY_12: Boolean = false
|
||||||
|
actual val INDEX_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val RANGE_FAST_ITER: Boolean = false
|
||||||
}
|
}
|
||||||
@ -27,6 +27,11 @@ actual object PerfDefaults {
|
|||||||
|
|
||||||
actual val FIELD_PIC: Boolean = true
|
actual val FIELD_PIC: Boolean = true
|
||||||
actual val METHOD_PIC: Boolean = true
|
actual val METHOD_PIC: Boolean = true
|
||||||
|
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_METHODS_ONLY: Boolean = false
|
||||||
|
actual val PIC_ADAPTIVE_HEURISTIC: Boolean = false
|
||||||
|
|
||||||
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
actual val PIC_DEBUG_COUNTERS: Boolean = false
|
||||||
|
|
||||||
@ -35,4 +40,7 @@ actual object PerfDefaults {
|
|||||||
actual val RVAL_FASTPATH: Boolean = false
|
actual val RVAL_FASTPATH: Boolean = false
|
||||||
// Regex caching: disabled by default on WasmJS until validated
|
// Regex caching: disabled by default on WasmJS until validated
|
||||||
actual val REGEX_CACHE: Boolean = false
|
actual val REGEX_CACHE: Boolean = false
|
||||||
|
actual val ARG_SMALL_ARITY_12: Boolean = false
|
||||||
|
actual val INDEX_PIC_SIZE_4: Boolean = false
|
||||||
|
actual val RANGE_FAST_ITER: Boolean = false
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user