Skip to content

Commit 3502714

Browse files
author
jun0
committed
[rustjava-adopt-ldc-tags-real-world-generator-survey-p1] test(survey): kotlinc·scalac 를 타깃 형상으로 몰아 «기능 단위» 0 을 잰다
1 parent e94cfe9 commit 3502714

7 files changed

Lines changed: 235 additions & 0 deletions

File tree

‎REPORT.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
11
# REPORT
2+
## [2026-09-17] kotlinc·scalac 를 «타깃 형상»으로 몰았다 — ★**0. 그러나 «다른 0»** (rustjava-adopt-ldc-tags-real-world-generator-survey-p1)
3+
- 무엇을: 채택 제안 `2026-09-16-ldc-tags-real-world-generator-survey#p1`. 조사 회차가 자기 표에 **「못 쟀다」**로 적어 둔 칸을 채웠다. ★**제품 Rust 0줄**(조사 입력 2 + 스캐너 docstring).
4+
- 왜: 종전 축은 **컴파일러의 stdlib**(= 그 컴파일러 산출물)이었다. ★**stdlib 은 호환성을 위해 컴파일되지 백엔드를 훑으려고 컴파일되지 않는다** — 그래서 「이 코퍼스에서 0」과 「이 기능들에서 0」이 다르다.
5+
- ★**결과**: kotlinc **2.4.20** → 7클래스·23 ldc 자리 · ★**피연산자 15/16/17 = 0** / scalac **3.9.0** → 7·18 · ★**0**.
6+
★★**풀 수치가 이 0 을 읽을 값으로 만든다** — Kotlin `MethodHandle 7 · MethodType 6` · Scala `11 · 6` **(0 이 아니다)** ⇒ ★**indy 경로가 실제로 돌았고 상수도 만들어졌는데 `ldc` 자리에 «닿지 않는다»**. 「안 썼다」가 아니라 「썼는데 안 온다」다.
7+
★**태그 17(Dynamic)은 풀에도 0** ⇒ ★**두 컴파일러 모두 condy 를 아예 내지 않는다**(둘 중 더 센 진술).
8+
- ★**타깃 형상**: 람다 · 언바운드/바운드 메서드 참조 · SAM 변환(네이티브+Java 둘 다) · enum 주어 · 문자열 연결 · lazy · (Kotlin) reified · (Scala) eta 확장·inline def·구조적 타입. ★**플래그도 «더 많이» indy 로 보내는 쪽으로 골랐다**(`-Xlambdas=indy`·`-Xsam-conversions=indy`·`-Xstring-concat=indy-with-constants` · `scalac -release 21`).
9+
- ★**양방향**: 같은 스캐너·같은 세션에서 **양성 대조군** `test-data/ldc` → `{MethodHandle 1, MethodType 2, Dynamic 7}` ⇒ ★**이 0 은 「스캐너가 못 본다」가 아니다.** 오차 막대(불가능 피연산자) 두 실행 **0.00%**.
10+
- ★**제안의 값 전제를 다시 재서 «부분적으로 거짓»임을 찾았다**: 「JVM 툴체인이 의도적으로 없는 맥」이라 했으나 ★**openjdk 26 은 2026-03-11 부터 설치돼 있었다**(조사 회차보다 반년 앞선다) ⇒ 한계 설치는 **formula 하나씩**이었다.
11+
- ★★**대가**: **머신 상태가 바뀌었다** — `kotlin 2.4.20`·`scala 3.9.0`(+`scala-cli`) 설치. 레인 약 28개가 공유하는 맥이고, **가산적·가역**(`brew uninstall kotlin scala`)이라 **재측정 가능성을 위해 일부러 남겼다**. ★컴파일러당 프로그램 «하나»라 **기능**을 한정할 뿐 **언어**를 한정하지 않는다. ★**CI 에서 못 돈다**(`setup-java` 0건) — 사람이 돌리는 검사다.
12+
- ★**답은 제안이 예상한 그 0 이다** — 산 것은 **오차 막대**뿐이고, 그것이 이 회차 값의 정직한 회계다.
13+
214
## [2026-09-17] base 를 당겼다 — ★**막고 있던 코드 충돌은 «이미 없었다»**(rustjava-adopt-link-stringconcatfactory-p1-fix2)
315
- 무엇을: `origin/main` 당김(뒤처짐 **9**) + 그 당김이 만든 `test-data/class-file-versions.txt` **3행**. ★제품 코드 **0줄** · 픽스처 바이트 **불변**.
416
- ★★**전제가 반증됐다**: 이 회차는 「`make_indy_fixtures.py` 4구역 코드 충돌」을 풀라고 발권됐는데, 지금 당기면 그 파일은 **충돌하지 않는다**. `-p1-fix` 회차가 **14:10 에 `0f06b93f` 로 이미 합집합 해소**했고 게이트②가 **15:43 에 그 head 를 approve** 했다 — 발권 근거였던 12:12 blocked 회신이 그 사이 **낡았다**.

‎STATE.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
(둘 다 이것보다 오래됐고 MERGEABLE/CONFLICTING 처분이 이미 걸려 있다). 겹침은 전부 **append 형 합집합**이라 해소는 기계적이다)
88

99
## 완료
10+
- [rustjava-adopt-ldc-tags-real-world-generator-survey-p1] ★★**kotlinc·scalac 타깃 형상 조사 — 0. 그러나 «다른 0».** 채택 제안 `…-survey#p1`(worklog json `adoptedProposals` 기록) · ★**제품 Rust 0줄**.
11+
★조사 회차가 **「못 쟀다」로 비워 둔 칸**을 채웠다 — 종전 축은 stdlib(호환성용 컴파일)이라 「이 코퍼스에서 0」과 「이 기능들에서 0」이 달랐다.
12+
★**kotlinc 2.4.20 → 0 · scalac 3.9.0 → 0**. ★★**풀 수치가 그 0 을 읽을 값으로 만든다**(Kotlin `MH 7·MT 6` · Scala `11·6` = **indy 가 실제로 돌았다**) ⇒ 「안 썼다」가 아니라 **「썼는데 `ldc` 자리에 안 온다」**.
13+
★**태그 17 은 풀에도 0** ⇒ 두 컴파일러 다 **condy 를 아예 안 낸다**.
14+
★**양방향**: 양성 대조군 `test-data/ldc` → `{MH 1, MT 2, Dynamic 7}` ⇒ 「스캐너가 못 본다」 배제 · 오차막대 **0.00%**.
15+
★**제안의 값 전제가 부분적으로 거짓**이었다 — openjdk 26 은 **2026-03-11 부터** 설치돼 있었다(조사보다 반년 전).
16+
★★**대가**: 머신에 `kotlin`·`scala` **설치됨**(가역 · `brew uninstall kotlin scala` · 재측정 위해 남겼다) · 컴파일러당 프로그램 1개 · **CI 불가**.
1017
- [rustjava-adopt-link-stringconcatfactory-p1-fix2] ★★**base 당김 — 그런데 막고 있던 코드 충돌은 «이미 없었다»(PR #60).**
1118
★**전제 반증**: 「`make_indy_fixtures.py` 4구역 충돌」은 `-p1-fix` 가 **14:10 `0f06b93f`** 로 합집합 해소했고 게이트②가 **15:43 그 head 를 approve** 했다.
1219
발권 근거(12:12 blocked 회신)가 그 사이 낡은 것이다. ★**재발 불가**도 확인 — 뒤진 9커밋 중 그 파일을 만진 것 **0건**.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"schema": "worklog/v1",
3+
"date": "2026-09-17",
4+
"taskId": "rustjava-adopt-ldc-tags-real-world-generator-survey-p1",
5+
"summary": "Adopted: drove kotlinc and scalac over targeted source shapes instead of only their stdlib output, closing the gap the survey named about itself. Result is the same zero the proposal expected — but it is now a zero about features rather than about one corpus, and the constant pool counts prove the invokedynamic paths were actually exercised. Tag 17 (Dynamic) is absent from the pool entirely in both compilers, which is the stronger finding: neither emits condy at all.",
6+
"changes": [
7+
"scripts/ldc-tag-survey-targets/Targets.kt: Kotlin shapes — lambda, unbound and bound callable references, SAM conversion to a Kotlin fun interface and to a Java interface, enum `when`, string templates, lazy delegate, reified type parameter",
8+
"scripts/ldc-tag-survey-targets/Targets.scala: Scala 3 shapes — lambda, eta-expansion, SAM conversion to a Scala trait and to a Java interface, enum match, string interpolation, lazy val, inline def, structural type",
9+
"scripts/survey-ldc-constant-tags.py: docstring records the commands, the compiler versions, the numbers, the positive control and what the measurement does not cover"
10+
],
11+
"verification": [
12+
"kotlinc-jvm 2.4.20 (JRE 26): 7 classes / 23 ldc sites / tags 15,16,17 as an ldc operand: 0; same tags in the pool: MethodHandle 7, MethodType 6",
13+
"Scala 3.9.0: 7 classes / 18 ldc sites / tags 15,16,17 as an ldc operand: 0; same tags in the pool: MethodHandle 11, MethodType 6",
14+
"the pool counts are the non-vacuity check: they are non-zero, so the invokedynamic machinery really ran and the handles/types really were built — they simply never reach an ldc operand",
15+
"tag 17 (Dynamic) is 0 in the pool as well as at ldc sites for both compilers, so neither emits condy at all",
16+
"POSITIVE CONTROL, same scanner, same session: `python3 scripts/survey-ldc-constant-tags.py test-data/ldc` -> {MethodHandle 1, MethodType 2, Dynamic 7}. So the 0 above is not 'the scanner cannot see these tags'.",
17+
"scanner error bar on both targeted runs: impossible operands 0 (0.00%)",
18+
"flags chosen to route *more* through invokedynamic (-Xlambdas=indy, -Xsam-conversions=indy, -Xstring-concat=indy-with-constants; scalac -release 21). Measuring without them would test a compiler configured away from the question.",
19+
"cargo test --all: 578 passed / 0 failed / 1 ignored — unchanged, this round adds no Rust",
20+
"the proposal's cost premise was re-measured and is partly false: it says the machine is 'deliberately kept free of JVM toolchains', but openjdk 26 has been installed via homebrew since 2026-03-11 (INSTALL_RECEIPT epoch 1784711495), months before the survey. The marginal install was therefore one formula each: brew deps are kotlin->openjdk, scala->openjdk+scala-cli."
21+
],
22+
"issues": [
23+
"MACHINE STATE CHANGED: kotlin 2.4.20 and scala 3.9.0 (plus scala-cli) are now installed via homebrew on a machine shared by ~28 agent lanes. Additive and reversible — `brew uninstall kotlin scala` — and left in place deliberately so the measurement can be re-run, but it is a change nobody else asked for.",
24+
"One program per compiler. This bounds the listed features, not the languages — the survey's own framing of the corpus axis applies here in the other direction.",
25+
"Not runnable in CI: no setup-java step exists, and now two more compilers would be needed. Like test-data/src/verify-javac-fixtures.sh this is a check a person runs.",
26+
"The answer is the same zero the proposal predicted. What was bought is the error bar, exactly as its tradeoff said — and that is the honest accounting of the round's value."
27+
],
28+
"adoptedProposals": [
29+
"2026-09-16-ldc-tags-real-world-generator-survey#p1"
30+
],
31+
"proposals": []
32+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# 2026-09-17 — kotlinc·scalac 를 «타깃 형상»으로 몰았다 (rustjava-adopt-ldc-tags-real-world-generator-survey-p1)
2+
3+
채택 제안 `2026-09-16-ldc-tags-real-world-generator-survey#p1`.
4+
조사 회차는 **컴파일러의 stdlib**(= 그 컴파일러 자신의 산출물)을 쟀고, ★**「그 컴파일러에게 «문제의 기능»을
5+
직접 컴파일시키지는 않았다」를 자기 표에 「못 쟀다」로 적어 두었다.** 그 칸을 채운다.
6+
7+
## 결과 — ★**0. 그러나 «다른 0» 이다**
8+
9+
| 컴파일러 | 버전 | 클래스 | ldc 자리 | ★**ldc 피연산자 15/16/17** | 풀에 «존재» |
10+
|---|---|---|---|---|---|
11+
| Kotlin | kotlinc-jvm **2.4.20** (JRE 26) | 7 | 23 | ★**0** | MethodHandle **7** · MethodType **6** |
12+
| Scala 3 | scalac **3.9.0** | 7 | 18 | ★**0** | MethodHandle **11** · MethodType **6** |
13+
14+
★★**풀 수치가 이 0 을 읽을 값으로 만든다** — 0 이 아니다. 즉 **indy 경로가 실제로 돌았고 상수도 실제로 만들어졌다.**
15+
다만 그것들이 **`ldc` 피연산자에 닿지 않는다.** ⇒ 「안 썼다」가 아니라 ★**「썼는데도 그 자리에 안 온다」**이다.
16+
17+
★**태그 17(Dynamic)은 풀에도 0** 이다 — ⇒ ★**두 컴파일러 모두 condy 를 «아예» 내지 않는다.** 둘 중 더 센 진술이다.
18+
19+
## 무엇을 «타깃 형상»으로 골랐나
20+
21+
두 파일(`scripts/ldc-tag-survey-targets/Targets.{kt,scala}`) — 태그 15·16 이 컴파일러 산출물에서
22+
**사는 유일한 자리**가 invokedynamic 이므로, **indy 로 가는 기능을 전부** 넣었다:
23+
24+
람다 · **언바운드/바운드 메서드 참조** · SAM 변환(**네이티브 인터페이스 + Java 인터페이스 둘 다**) ·
25+
enum 주어(`when`/`match`) · 문자열 연결 · lazy · (Kotlin) reified 타입 파라미터 · (Scala) **eta 확장 · inline def · 구조적 타입**.
26+
27+
★**플래그도 «더 많이» indy 로 보내는 쪽으로 골랐다**:
28+
```
29+
kotlinc -jvm-target 21 -Xlambdas=indy -Xsam-conversions=indy -Xstring-concat=indy-with-constants -d <out> …/Targets.kt
30+
scalac -release 21 -d <out> …/Targets.scala
31+
python3 scripts/survey-ldc-constant-tags.py <out>
32+
```
33+
★**이 플래그를 빼고 재면 「질문에서 비껴 설정된 컴파일러」를 재는 것**이고, 그것이 코퍼스 축이 피할 수 없던 약점이다
34+
(stdlib 은 **호환성**을 위해 컴파일되지 백엔드를 훑으려고 컴파일되지 않는다).
35+
36+
## ★양방향 — 이 0 이 「스캐너가 못 본다」가 아님을 먼저 보였다
37+
38+
```
39+
python3 scripts/survey-ldc-constant-tags.py test-data/ldc
40+
★ tags 15/16/17 as an ldc operand: {'MethodHandle': 1, 'MethodType': 2, 'Dynamic': 7}
41+
```
42+
같은 스캐너 · 같은 세션에서 **우리 픽스처는 «보인다»** ↔ **타깃 형상은 0**.
43+
스캐너 오차 막대(불가능 피연산자)는 두 타깃 실행 모두 **0 (0.00%)**.
44+
45+
## ★제안의 «값» 전제를 다시 쟀다 — 부분적으로 거짓이었다
46+
47+
제안의 `tradeoff` 는 「**JVM 툴체인이 의도적으로 없는 맥**에 컴파일러 둘을 설치하는 값」이라 했다.
48+
실측: ★**openjdk 26 은 2026-03-11 부터 homebrew 로 설치돼 있었다**(`INSTALL_RECEIPT` epoch `1784711495`) —
49+
조사 회차(**09-16**)보다 **반년 앞선다**. ⇒ 한계 설치 비용은 **formula 하나씩**이었다
50+
(`kotlin → openjdk` · `scala → openjdk, scala-cli`).
51+
52+
## ★대가 — 숨기지 않는다
53+
54+
- ★★**머신 상태가 바뀌었다**: `kotlin 2.4.20` · `scala 3.9.0`(+`scala-cli`)이 **설치됐다**.
55+
에이전트 레인 약 28개가 공유하는 맥이다. **가산적이고 되돌릴 수 있다** — `brew uninstall kotlin scala`.
56+
★**일부러 남겼다**(재측정 가능성). 그러나 **아무도 따로 요청하지 않은 변경**이라 여기 적는다.
57+
- **컴파일러당 프로그램 «하나»** ⇒ 위에 나열한 **기능들**을 한정할 뿐 **언어**를 한정하지 않는다.
58+
- ★**CI 에서 못 돈다** — `setup-java` **0건**인데 이제 컴파일러가 둘 더 필요하다.
59+
`test-data/src/verify-javac-fixtures.sh` 와 같은 계급: **사람이 돌리는 검사**다.
60+
- ★**답은 제안이 예상한 그 0 이다.** 산 것은 **오차 막대**뿐이고, 그것이 이 회차 값의 정직한 회계다.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Kotlin shapes most likely to put CONSTANT_MethodHandle (15), MethodType (16) or Dynamic (17)
2+
// at an `ldc` site. Compiled with every flag that routes a feature through invokedynamic, because
3+
// that is where those three tags live in compiler output at all.
4+
//
5+
// kotlinc -jvm-target 21 -Xlambdas=indy -Xsam-conversions=indy \
6+
// -Xstring-concat=indy-with-constants -d <out> Targets.kt
7+
import java.util.function.Function
8+
import java.util.function.Supplier
9+
10+
fun interface Op {
11+
fun apply(x: Int): Int
12+
}
13+
14+
enum class Color { RED, GREEN, BLUE }
15+
16+
class Holder(val n: Int) {
17+
fun twice(): Int = n * 2
18+
}
19+
20+
fun topLevel(x: Int): Int = x + 1
21+
22+
val lazyValue: String by lazy { "computed" }
23+
24+
inline fun <reified T> typeName(): String = T::class.java.name
25+
26+
fun main() {
27+
val h = Holder(21)
28+
val lambda: (Int) -> Int = { it * 3 } // lambda
29+
val unbound: (Int) -> Int = ::topLevel // unbound callable reference
30+
val bound: () -> Int = h::twice // bound callable reference
31+
val sam: Op = Op { it - 1 } // SAM conversion, Kotlin fun interface
32+
val jfun: Function<String, Int> = Function { it.length } // SAM conversion, Java interface
33+
val sup: Supplier<String> = Supplier { "s" }
34+
val c = Color.GREEN
35+
val w = when (c) { // enum subject
36+
Color.RED -> "r"
37+
Color.GREEN -> "g"
38+
Color.BLUE -> "b"
39+
}
40+
// string templates: the one construct javac routes through invokedynamic by default
41+
val s = "a=${lambda(1)} b=${unbound(2)} c=${bound()} d=${sam.apply(5)} " +
42+
"e=${jfun.apply("ab")} f=${sup.get()} g=$w h=${typeName<Holder>()} i=$lazyValue"
43+
println(s + Holder::class.java.simpleName)
44+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Scala 3 shapes most likely to put CONSTANT_MethodHandle (15), MethodType (16) or Dynamic (17)
2+
// at an `ldc` site. Scala routes lambdas, SAM conversion and structural calls through
3+
// invokedynamic, which is where those tags appear in compiler output at all.
4+
//
5+
// scalac -release 21 -d <out> Targets.scala
6+
import java.util.function.{Function => JFunction, Supplier}
7+
import scala.reflect.Selectable.reflectiveSelectable
8+
9+
trait Op:
10+
def apply(x: Int): Int
11+
12+
enum Color:
13+
case Red, Green, Blue
14+
15+
class Holder(val n: Int):
16+
def twice: Int = n * 2
17+
18+
object Targets:
19+
def topLevel(x: Int): Int = x + 1
20+
inline def inlined(x: Int): Int = x * 2
21+
lazy val lazyValue: String = "computed"
22+
23+
type Named = { def twice: Int }
24+
25+
def main(args: Array[String]): Unit =
26+
val h = Holder(21)
27+
val lambda: Int => Int = _ * 3 // lambda
28+
val eta: Int => Int = topLevel // eta-expansion
29+
val bound: () => Int = () => h.twice
30+
val sam: Op = (x: Int) => x - 1 // SAM conversion, Scala trait
31+
val jfun: JFunction[String, Integer] = (s: String) => s.length // SAM conversion, Java interface
32+
val sup: Supplier[String] = () => "s"
33+
val c: Color = Color.Green
34+
val w = c match // enum subject
35+
case Color.Red => "r"
36+
case Color.Green => "g"
37+
case Color.Blue => "b"
38+
val struct: Named = h // structural type -> reflective call site
39+
val s = s"a=${lambda(1)} b=${eta(2)} c=${bound()} d=${sam.apply(5)} " +
40+
s"e=${jfun.apply("ab")} f=${sup.get()} g=$w h=${struct.twice} i=${inlined(4)} j=$lazyValue"
41+
println(s)

‎scripts/survey-ldc-constant-tags.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,45 @@
1313
legally take. That last number is the scanner's own error bar — an instruction walk that loses
1414
alignment lands on nonsense, so a non-zero share there is a reason to distrust the rest. It is
1515
*detectable* misdecodes only: a misdecode landing on a plausible tag stays invisible.
16+
17+
## Targeted shapes, not just corpora (2026-09-17)
18+
19+
The first survey measured what kotlinc and scalac had already produced — their own stdlibs — and
20+
said outright that it had not asked either compiler to compile the features most likely to emit
21+
these constants. That gap is closed: `ldc-tag-survey-targets/` holds one file per language
22+
exercising lambdas, unbound and bound method references, SAM conversion to both a native and a
23+
Java interface, an enum subject, string concatenation, a lazy value, and (Scala) eta-expansion,
24+
an inline def and a structural type.
25+
26+
kotlinc -jvm-target 21 -Xlambdas=indy -Xsam-conversions=indy \
27+
-Xstring-concat=indy-with-constants -d <out> scripts/ldc-tag-survey-targets/Targets.kt
28+
scalac -release 21 -d <out> scripts/ldc-tag-survey-targets/Targets.scala
29+
python3 scripts/survey-ldc-constant-tags.py <out>
30+
31+
Measured with kotlinc-jvm 2.4.20 and Scala 3.9.0 (both on JRE 26):
32+
33+
Kotlin 7 classes · 23 ldc sites · tags 15/16/17 as an ldc operand: 0
34+
same tags in the pool: MethodHandle 7, MethodType 6
35+
Scala 7 classes · 18 ldc sites · tags 15/16/17 as an ldc operand: 0
36+
same tags in the pool: MethodHandle 11, MethodType 6
37+
38+
The pool counts are what make that 0 worth reading: they are non-zero, so the invokedynamic paths
39+
really were exercised and the constants really were built — they just never reach an `ldc`
40+
operand. Tag 17 (Dynamic) is absent from the pool in both, so neither compiler emits condy at all,
41+
which is the stronger statement of the two.
42+
43+
The flags are deliberately the ones that route *more* through invokedynamic. A run without them
44+
measures a compiler configured away from the shapes in question, which is the mistake the corpus
45+
axis could not avoid: a stdlib is compiled for compatibility, not to exercise the backend.
46+
47+
Positive control, so the 0 is not "the scanner cannot see it":
48+
49+
python3 scripts/survey-ldc-constant-tags.py test-data/ldc
50+
-> tags 15/16/17 as an ldc operand: {MethodHandle 1, MethodType 2, Dynamic 7}
51+
52+
Not covered, said plainly: this is one program per compiler, so it bounds the *features listed
53+
above* and not the languages. And it needs kotlinc and scalac installed — CI has neither, so like
54+
`test-data/src/verify-javac-fixtures.sh` this is a check a person runs, not one that runs itself.
1655
"""
1756

1857
import sys

0 commit comments

Comments
 (0)