Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
2fa4c33
Feat: [#4] Add all required binding FFI (required detailed revision)
sesame-leaf Dec 20, 2025
0fae961
Refactor: PyObject to use platform-specific PyAutoCloseable
sesame-leaf Dec 20, 2025
6bc79b1
Feat: Further Implementation for PyType (Temporal Commit)
sesame-leaf Dec 20, 2025
02b8974
Feat: Further Implementation for PyModule (Temporal Commit)
sesame-leaf Dec 20, 2025
f33bcef
Feat: PyType caching system (Temporal Commit)
sesame-leaf Jan 28, 2026
99daa62
Merge remote-tracking branch 'origin/feat/add_new_binders' into feat/…
b-re-w Aug 10, 2026
861f4dd
Docs: Add CLAUDE.md with agent working rules
b-re-w Aug 10, 2026
e9617f2
Docs: Adopt TDD workflow and drop no-test-framework rule in CLAUDE.md
b-re-w Aug 10, 2026
0c0b169
Test: Wire up kotlin-test harness runnable on iOS simulator
b-re-w Aug 10, 2026
1cc1f4a
Docs: Document agy subagent usage and parallel-execution caveats
b-re-w Aug 10, 2026
d376092
Test: Make embedded CPython usable from the iOS simulator test binary
b-re-w Aug 10, 2026
9a38616
Test: Add API skeleton and red-phase test suite
b-re-w Aug 10, 2026
fc2148e
Feat: Implement core object model, basic types and collections
b-re-w Aug 10, 2026
5542c30
Feat: Implement conversion layer and PyModule
b-re-w Aug 10, 2026
cc9b9d9
Perf: Bind the missing container primitives and drop the workarounds
b-re-w Aug 10, 2026
a3e0be8
Docs: Add Android FFM design from PanamaPort source analysis
b-re-w Aug 10, 2026
4e7201e
Fix: Make the JVM targets compile at all
b-re-w Aug 10, 2026
8089a4d
Docs: Revise Android FFM design around a shared FfiBackend
b-re-w Aug 10, 2026
a398b4f
Docs: Add WASM feasibility analysis
b-re-w Aug 10, 2026
eff2ac5
Docs: Record agent verification discipline and a compiler constraint
b-re-w Aug 10, 2026
de6fde7
Docs: Correct the WASM framing on Kotlin targets and CPython support
b-re-w Aug 10, 2026
f086b1d
Build: Fetch CPython from Gradle instead of vendoring it
b-re-w Aug 10, 2026
f6ca355
Build: Extend the CPython acquisition pipeline to Android and iOS
b-re-w Aug 10, 2026
b7725f4
Docs: Settle the threading model on free-threading from 3.15t
b-re-w Aug 10, 2026
53d680e
Feat: Add per-thread GIL state management (mechanism and routing)
b-re-w Aug 10, 2026
0c8c3b3
Feat: Route reference counting and the remaining call sites through t…
b-re-w Aug 10, 2026
da3e416
Fix: Stop Long.toNativePointer recursing into itself on Android
b-re-w Aug 10, 2026
9a16288
Docs: Record the GraalVM Native Image constraint on the desktop backend
b-re-w Aug 10, 2026
d3e2018
Docs: Add PROJECT.md and layered design documents
b-re-w Aug 10, 2026
6d39870
Build: Verify every downloaded CPython archive against a committed lo…
b-re-w Aug 10, 2026
58ea127
Feat: Add the 14-shape downcall vocabulary and both JVM implementations
b-re-w Aug 10, 2026
bc269a5
Docs: Record why Android needs composition, not just shape trampolines
b-re-w Aug 10, 2026
79040b9
Docs: Correct the reason LLVM is unnecessary here
b-re-w Aug 10, 2026
162f061
Build: Move the native targets to CPython 3.14 and prove the Android …
b-re-w Aug 10, 2026
f2f0032
Feat: Run the desktop target for the first time and fix what that exp…
b-re-w Aug 10, 2026
c070485
Feat: Run CPython on Android via @CriticalNative, verified on API 26 …
b-re-w Aug 10, 2026
7f54764
Test: Measure the JNI transition cost, and record that it refutes the…
b-re-w Aug 10, 2026
a0e6539
Test: Confirm on hardware that @CriticalNative is the wrong path on m…
b-re-w Aug 10, 2026
4da9442
Test: Pin the @CriticalNative crossover to Android 14 by measuring fi…
b-re-w Aug 10, 2026
c961343
Feat: Select the JNI calling convention per API level, from measurement
b-re-w Aug 10, 2026
5dd13dc
Feat: Choose the JNI convention per function, with correctness outran…
b-re-w Aug 10, 2026
a0ff68c
Test: Measure desktop FFI cost and find it ~140x Android's
b-re-w Aug 11, 2026
97b4407
Perf: Cut desktop FFI from 1015.95 ns to 2.65 ns per call
b-re-w Aug 11, 2026
c6d8d48
Test: Run API 36 on an emulator to separate the API level from the ha…
b-re-w Aug 11, 2026
0d21948
Test: Measure what composing a binder operation is worth, and fix the…
b-re-w Aug 11, 2026
0a27c86
Docs: Record the layering rule, and correct the account of the PyAuto…
b-re-w Aug 11, 2026
a0f39a8
Docs: Correct again what 0fae961a changed -- lifetime was already ext…
b-re-w Aug 11, 2026
add4396
Refactor: Undo unrequested API changes to PyObject and fold the GIL h…
b-re-w Aug 11, 2026
fa49d10
Fix: Repair the cleaner path, and pin the real reason references are …
b-re-w Aug 11, 2026
38791b8
Chore: Remove scratch files committed by mistake
b-re-w Aug 11, 2026
ace6734
Test: Add reference-count balance tests, and record what is left in R…
b-re-w Aug 11, 2026
a6a99da
Test: Measure composing Python3.exec, and record that it refutes the …
b-re-w Aug 11, 2026
f52ac21
Test: Measure desktop's string-marshalling share, and close the deskt…
b-re-w Aug 11, 2026
afaf771
Docs: Put the platform rules next to the platform code
b-re-w Aug 11, 2026
f811d92
Perf: Finish the invokeExact conversion, and disable the benchmark th…
b-re-w Aug 11, 2026
80c23c7
Test: Show that fixing string marshalling beats composing, on modern …
b-re-w Aug 11, 2026
0fe54c0
Test: Interning beats composition on every API level, which unifies A…
b-re-w Aug 11, 2026
7e8b5d2
Docs: Record the string-marshalling design the measurements point at
b-re-w Aug 11, 2026
ed63c77
Docs: Write down the object-lifetime design, including how cycles get…
b-re-w Aug 11, 2026
c41bc89
Perf: Route string marshalling through interning, and find the produc…
b-re-w Aug 11, 2026
cdb74ed
Test: Split tests into low-level and assembled layers, and fix what t…
b-re-w Aug 11, 2026
9eb59b4
Docs: Record why WASM is parked and what the implementation would loo…
b-re-w Aug 11, 2026
090e4d9
Docs: Correct the Pyodide-compatibility argument, and settle WASM pac…
b-re-w Aug 11, 2026
b93e366
Docs: Correct the WASM packaging story for PEP 783
b-re-w Aug 11, 2026
bf9ae7b
Feat: Close the Android JNI surface gap and land the PyValue conversi…
b-re-w Aug 11, 2026
5f36a84
Docs: Record that the WASM JS layer is not mandatory
b-re-w Aug 11, 2026
f523d9f
Docs: Measure Kotlin/Wasm's memory ABI and correct the sharing direction
b-re-w Aug 11, 2026
39f96a8
Feat: Prove Kotlin/Wasm and Emscripten C can share one linear memory
b-re-w Aug 11, 2026
395dfb7
Docs: Bring ROADMAP up to the measured state
b-re-w Aug 11, 2026
5c91c10
Docs: Record the second Claude Code account and the real parallelism …
b-re-w Aug 11, 2026
fdee697
Docs: Mark the 2238 ns figure as historical and record the desktop gap
b-re-w Aug 11, 2026
a70c430
Docs: Require sub-agents to run as separate processes
b-re-w Aug 11, 2026
79e284f
Config: Deny in-turn sub-agents so an interrupt cannot cancel work
b-re-w Aug 11, 2026
4606dce
Docs: Add the JNI convention audit, with its headline finding corrected
b-re-w Aug 11, 2026
09bf239
Feat: Run commonTest on Android, land the upcall runtime, and split G…
b-re-w Aug 11, 2026
1411530
Docs: Reopen §2, and record that §1's diagnosis does not survive testing
b-re-w Aug 11, 2026
8653590
Docs: Investigate the GIL parking crash, and rule out the obvious exp…
b-re-w Aug 11, 2026
49ce49b
Fix: Migrate the reachable Android JNI surface, and stop the intern c…
b-re-w Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"deny": [
"Agent",
"Task"
]
}
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,8 @@ google-services.json

# Android Profiling
*.hprof
wasm-experiment/build/
wasm-experiment/.gradle/
wasm-experiment/kotlin-js-store/
wasm-experiment/native/probeA.*
wasm-experiment/native/probeB.*
242 changes: 242 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# CLAUDE.md

이 저장소에서 작업하는 에이전트가 반드시 지켜야 할 규정과, 작업에 필요한 프로젝트 컨텍스트를 정리한 문서입니다.

---

## 필수 규정 (MUST)

### 1. 커밋 메시지에 공동 저자(Co-Author)를 넣지 않는다

커밋 메시지에 `Co-Authored-By: Claude ...` 를 **절대 추가하지 않는다.**
`Generated with Claude Code` 같은 생성 도구 표기도 커밋 메시지와 PR 본문에 넣지 않는다.

기본 동작이 이 라인을 자동으로 붙이도록 되어 있더라도, 이 저장소에서는 해당 기본값보다 이 규정이 우선한다.

```
# 잘못된 예
Feat: Implement PyObject invoke operators

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> <-- 금지

# 올바른 예
Feat: Implement PyObject invoke operators
```

### 2. 작업 브랜치

`develop` 및 `release` 에 직접 커밋하지 않는다. 작업용 브랜치를 만들어 그곳에 커밋하고 푸시한다.

### 3. 테스트 주도 개발 (TDD)

기능 구현은 **테스트를 먼저 작성한 뒤** 진행한다. 테스트 없이 구현부터 하지 않는다.

- 테스트 목표의 밑그림은 저장소 루트의 `python_for_kotlin_binding.mermaid` 를 참고한다. 단, 이 다이어그램은 **확정 스펙이 아니다.** 타입 간 관계와 제공해야 할 기능의 범위를 참고하는 용도이며, 구체적인 메서드 이름과 시그니처는 구현하면서 달라질 수 있다.
- 동작 검증만 하지 말고 **오버헤드가 큰 지점을 드러내는 측정 테스트**를 함께 둔다. FFI 호출 1건당 비용, 포인터 박싱, 참조 카운팅 왕복, 문자열 마샬링, 컬렉션 변환이 주요 관찰 대상이다. 코드베이스의 `@HighOverheadNativeCall` 애노테이션이 이미 이 의도를 표시하고 있다.
- 구현 전 단계의 실패 테스트와, 회귀로 인한 실패를 구분할 수 있게 작성한다.

### 4. 빌드 검증 시 파이프로 종료 코드를 읽지 않는다

Gradle 출력을 `| tail`, `| grep`, `| head` 로 넘긴 뒤 종료 코드를 읽으면 **파이프 마지막 명령의 종료 코드**를 읽게 되어, 실패한 빌드를 성공으로 오인한다. 반드시 파일로 리다이렉트한 뒤 `$?` 를 확인한다.

```bash
./gradlew <task> --console=plain > /tmp/build.log 2>&1; echo "EXIT=$?"
```

컴파일 에러는 `e: ` 로 시작하는 줄이다. `w: ` 경고(특히 inlining 관련)는 다수가 기존부터 존재하던 것이므로 무시해도 된다.

**같은 함정이 배경 실행에도 있다.** 위 예시처럼 `echo "EXIT=$?"` 로 끝내면 **스크립트 전체의 종료 코드는 `echo` 의 것(항상 0)** 이 된다. 포그라운드에서는 출력에 찍힌 `EXIT=` 값을 읽으니 문제가 없지만, `run_in_background` 로 돌리면 도구가 보고하는 것은 스크립트의 종료 코드 — 즉 `echo` 의 0 이다. **실패한 빌드가 성공으로 보고된다.** 배경 실행에서는 출력 파일의 `EXIT=` 줄을 직접 읽거나, `echo` 없이 gradle 을 마지막 명령으로 두어야 한다.

테스트 결과 집계는 종료 코드와 별개로 `build/test-results/<target>/*.xml` 을 직접 세는 것이 확실하다. 단, **집계 전에 해당 디렉터리를 지운다** — 크래시한 실행은 이전 실행의 XML 을 남겨두어 그 숫자를 보고하게 된다.

---

## 서브 에이전트 운용

### 실행 수단은 셋이다

| 수단 | 호출 | 용도 |
|---|---|---|
| 현재 세션 | — | 조율 · 검증 · 커밋. **에이전트에게 커밋을 시키지 않는다** |
| Claude Code 2번 계정 | `CLAUDE_CONFIG_DIR=~/.claude-alt claude -p "..."` | 구현 작업. 1번과 **사용량 한도가 별도** |
| `agy` | `agy -p "..." --model ...` | 분석 · 조사, 그리고 Claude 한도가 남아 있을 때의 구현 |

2번 계정(`irack000@gmail.com`)은 `~/.claude-alt` 에 로그인되어 있고, 1번(`brew.airesearch@gmail.com`)을
덮어쓰지 않는다 — 양쪽 `-p` 호출이 동시에 성공하는 것으로 확인했다. 한쪽이 한도에 걸려도 다른 쪽으로 계속 진행한다.

### 서브 에이전트는 반드시 별도 프로세스로 띄운다

조율 세션의 턴 안에서 실행되는 서브 에이전트는 **그 턴이 끊기면 함께 죽는다.** 사용자가 답변 도중
메시지를 보내는 것은 정상적인 인터럽트인데, 그것만으로 몇 시간짜리 작업 세 개가 한꺼번에 사라졌다:

```
03:00:14 A · C [Request interrupted by user]
03:13:15 D [Request interrupted by user]
```

정지 시각이 인터럽트 시각과 초 단위로 일치했다. 처음에는 사용량 한도로 오진했는데, `rate_limit` 이
grep 에 걸린 것은 프롬프트와 설정 JSON 안의 단어였다.

**진행 상황을 묻는 것만으로 작업이 날아가는 구성은 잘못된 구성이다.** 오래 걸리는 작업은 예외 없이
배경 프로세스로 띄운다.

| 조율 세션이 끊길 때 | 함께 죽나 |
|---|---|
| 작성 중이던 답변 | 예 (의도된 동작) |
| 턴 안에서 실행되는 서브 에이전트 | **예 — 쓰지 말 것** |
| 배경 프로세스 (`claude -p`, `agy -p`, gradle) | 아니오 |

배경 프로세스로 띄우면 계정 분리와 인터럽트 내성이 한꺼번에 해결된다.

### 병렬화의 실제 상한은 git 이 아니라 기계다

`git worktree` 로 격리하면 **빌드 상태 충돌**은 사라진다 — 한 에이전트가 TDD 적색 단계로 트리를 깨면
같은 트리의 다른 에이전트가 자기 변경을 검증할 수 없는데, 실제로 그 일이 일어났다. 파일 소유권을
나누는 것만으로는 막히지 않는다. **TDD·대규모 작업은 worktree 로 격리한다.**

그러나 worktree 를 늘려도 넘지 못하는 벽이 있다 (2026-08 기준 측정):

```
디스크 여유 5.6 GB 저장소 5.9 GB (.git 1.1 + build 3.3 + binary 0.5)
RAM 16 GB Gradle/Kotlin 데몬이 이미 5개
코어 8
```

worktree 하나를 추가하면 체크아웃 ~1.5 GB 에 빌드 산출물 2.5 GB 이상이 더 붙는다.

| | 동시 실행 상한 |
|---|---|
| 빌드·테스트를 도는 에이전트 | **2** (worktree 격리) |
| 읽기 · 분석 · 문서만 하는 에이전트 | 3~4 추가 가능 |
| Android / iOS 기기 테스트 | **1** — 에뮬레이터가 2대뿐이고 패키지명이 같아 동시 설치가 충돌한다 |

계정을 늘리면 **띄울 수 있는 에이전트 수가 늘어나는 것이 아니라, 띄울 수 있는 둘을 더 안정적으로
채울 수 있게 된다.**

**작업 대부분은 `agy` 에 맡긴다.** Claude 서브 에이전트는 조율이 꼭 필요한 경우로 제한하고, 기본 실행 수단은 `agy` 로 둔다.

`agy` 에는 Gemini 계열뿐 아니라 **Claude 모델도 있다** (`claude-opus-4-6-thinking`, `claude-sonnet-4-6`). 모델을 작업 성격에 맞춰 고른다.

| 작업 성격 | 권장 모델 |
|---|---|
| 까다로운 설계·리팩터링, 실수 비용이 큰 작업 | `claude-opus-4-6-thinking` |
| 일반적인 구현 작업 | `claude-sonnet-4-6` |
| 대량 코드 읽기·조사·분석 | `gemini-3.1-pro-high` |
| 단순 반복 작업 | `gemini-3.6-flash-medium` |

### agy 사용법

`~/.local/bin/agy` 에 설치되어 있다. 비대화형 실행은 `-p` (= `--print`) 를 쓴다.

```bash
agy -p "<프롬프트>" --model gemini-3.1-pro-high --print-timeout 30m
```

주요 옵션:

| 옵션 | 용도 |
|---|---|
| `-p`, `--print` | 프롬프트 1건을 비대화형으로 실행하고 결과를 출력 |
| `--model` | 모델 지정 (`agy models` 로 목록 확인) |
| `--effort` | 추론 강도 (`low` \| `medium` \| `high`) |
| `--print-timeout` | print 모드 대기 시간. **기본 5분이라 긴 작업은 반드시 늘려야 한다** |
| `--add-dir` | 워크스페이스에 디렉터리 추가 (반복 지정 가능) |
| `--dangerously-skip-permissions` | 권한 프롬프트 자동 승인 |
| `--output-format` | `text` \| `json` \| `stream-json` |

사용 가능한 모델 (`agy models`): Gemini 3.6 Flash (low/medium/high), Gemini 3.5 Flash, Gemini 3.1 Pro (low/high), Claude Sonnet 4.6, Claude Opus 4.6 (Thinking), GPT-OSS 120B.

`agy agents` 는 현재 비어 있다 — 별도 등록된 에이전트 프로필은 없다.

### 에이전트 결과는 반드시 직접 검증한다

에이전트는 지시를 어긴다. 실제로 겪은 사례:

- 커밋 금지라고 했는데 `git checkout` 으로 남의 작업을 되돌림
- 담당 파일만 만지라고 했는데 `commonMain` 을 수정하고 `build.gradle.kts` 의 소스셋 구조를 바꿈
- 테스트 수정 금지라고 했는데 테스트를 고침
- 동작하던 구현 파일을 삭제하고 트리를 깨진 채로 남김

그러므로 보고 내용을 그대로 믿지 말고, **작업 후 항상 `git status --short` 로 범위를 벗어난 변경이 없는지 확인하고 빌드·테스트를 직접 재실행한다.** 되돌릴 수 있도록 검증된 상태를 미리 커밋해 두면 `git checkout -- <path>` 로 복구할 수 있다.

### 플랫폼별 규정은 각 소스셋의 README.md 에 있다

`python-multiplatform/src/<sourceSet>/README.md` 에 그 플랫폼에서 반드시 지켜야 할 것들이
측정 근거와 함께 정리되어 있다. **해당 소스셋을 건드리기 전에 읽는다.**

| 소스셋 | 핵심 규정 |
|---|---|
| `commonMain` | 계층 규약(객체 모델은 `bindings` 참조 금지), 모든 C API 호출에 GIL, 참조 규약 명시 |
| `desktopMain` | **`invoke` 금지, 무조건 `invokeExact`**. 포인터는 `ADDRESS` 아닌 `JAVA_LONG` |
| `androidMain` | `RegisterNatives` 로 바인딩, 규약을 API 레벨 + 함수별로 선택, 경계는 원시 타입만 |
| `artMain` | JNI 는 여기에만(`nativeMain` 은 iOS 와 공유), 조합 함수의 자리 |
| `nativeMain` | iOS 와 공유되므로 Android 전용 코드 금지, 경계가 없으므로 조합 불필요 |
| `iosMain` | 프레임워크에 stdlib 없음 → `PYTHONHOME` 필요, `SIMCTL_CHILD_` 접두사 |

미완료 작업과 그 근거는 저장소 루트의 `ROADMAP.md` 에 있다.

### 알려진 컴파일러 제약

`EmbedAPI.kt` 에는 `expect inline fun` 이 다수 있다. 이를 중간 소스셋(`jvmMain`)의 `expect`/`actual` 과 조합하면 Kotlin 2.0.20 에서 **`Internal error in file lowering`** 컴파일러 크래시가 발생한 사례가 있다. JVM 계열 통합을 설계할 때 이 제약을 먼저 확인할 것.

### 병렬 실행 시 주의

- **작업을 디렉터리 단위로 분할한다.** 여러 에이전트가 같은 파일을 만지면 서로 덮어쓴다.
- **동시에 도는 에이전트에게 커밋을 시키지 않는다.** git 인덱스 락이 충돌한다. 각자 작업만 하게 하고 커밋은 조율하는 쪽에서 한 번에 처리하거나, git worktree 로 격리한다.
- Gradle 데몬은 하나를 공유하므로 동시 빌드는 직렬화되어 느려진다. 감안하고 분배한다.

### agy 호출 시 주의

- **Claude 계열 모델에는 `--effort` 를 붙이면 안 된다.** `claude-opus-4-6-thinking` 과 `claude-sonnet-4-6` 모두
붙이는 즉시 실행이 실패한다 (`invalid model selection: --effort is not supported for model "..."`).
`--effort` 는 Gemini 계열에만 준다.
- 실행 후 로그 앞부분을 반드시 확인한다. 인자 오류는 즉시 종료되는데, 배경 실행이면 성공처럼 보인다.
- **사용량 한도는 Gemini 계열과 Claude 계열이 별도로 집계된다.** 한쪽이
`Individual quota reached` 로 막혀도 다른 쪽은 그대로 쓸 수 있으니, 작업을 멈추지 말고 남은 계열로 계속 진행한다.

### 이 환경의 제약

- `timeout` 명령이 없다 (zsh, coreutils 미설치). 대신 각 도구의 자체 타임아웃 옵션을 쓴다.

---

## 프로젝트 개요

CPython 3.13 을 Kotlin Multiplatform 에 임베딩하여 Kotlin ↔ Python 상호운용을 제공하는 라이브러리.

- `python-multiplatform/` — 라이브러리 모듈
- `sample/` — Compose Multiplatform 데모 앱
- `binary/` — 플랫폼별 CPython 배포본 아카이브 원본

### 소스셋 구조

`commonMain` 을 최상위로, `jvmMain`(→ `androidMain`, `desktopMain`) 과 `nativeMain`(→ `iosMain`, `artMain` → androidNative) 두 갈래로 나뉜다. 소스셋 계층은 `python-multiplatform/build.gradle.kts` 의 `sourceSets` 블록에서 수동으로 구성한다.

### FFI 계층

- `python/native/ffi/EmbedAPI.kt` — CPython Stable ABI 에 대한 `expect` 선언 (약 313개)
- 플랫폼별 `EmbedAPI.<platform>.kt` + `bindings.kt` 가 `actual` 구현
- `NativePointer` (value class) 로 플랫폼 포인터 표현을 통일

### 객체 모델 계층

`python/multiplatform/ffi/` 아래에 `PyObject` 를 최상위로 하는 Kotlin 래퍼 타입들이 위치한다. 설계 의도는 저장소 루트의 `python_for_kotlin_binding.mermaid` 클래스 다이어그램을 따른다.

---

## 빌드 및 검증

### 검증용 타깃

| 타깃 | 커버 범위 | 비고 |
|---|---|---|
| `:python-multiplatform:compileKotlinAndroidNativeArm64` | commonMain + nativeMain + artMain | Xcode 불필요. 가장 빠른 기본 검증 루프 |
| `:python-multiplatform:compileKotlinIosSimulatorArm64` | commonMain + nativeMain + iosMain | Xcode 라이선스 동의 필요 |
| `:python-multiplatform:compileKotlinDesktop` | commonMain + jvmMain + desktopMain | |

### 환경 참고사항

- 테스트는 `kotlin-test` 기반으로 각 소스셋의 `*Test` 에 둔다.
- 기본 JDK 는 버전이 바뀔 수 있다. Desktop FFI 는 특정 JDK 버전에 종속되지 않도록 작성한다.
- PanamaPort 를 의존성으로 추가하지 않는다.
- Android `Cleaner` (`java.lang.ref.Cleaner`) 는 API 33+ 에서만 제공되므로, 그 미만 버전을 지원하는 한 `PhantomReference` 기반 대체 경로가 필요하다.
98 changes: 98 additions & 0 deletions PROJECT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Python Multiplatform — 프로젝트 개요

Kotlin Multiplatform 에 CPython 을 임베딩해, **Kotlin 과 Python 이 서로의 라이브러리를 양방향으로
사용**할 수 있게 하는 것이 목표다. 모든 지원 플랫폼에서 동작하고, 경계를 넘는 오버헤드를 최소화한다.

이 문서는 전체 그림과 문서 지도다. 각 주제의 상세는 `docs/` 아래 계층 문서를 본다.

---

## 두 방향

이 프로젝트의 모든 설계는 두 방향으로 갈린다. 둘은 **직교**하며 메커니즘이 완전히 다르다.

| 방향 | 부르는 이름 | 메커니즘 | 상세 |
|---|---|---|---|
| Kotlin → Python | **다운콜** | CPython Stable ABI 함수 호출 | [`docs/downcall-design.md`](docs/downcall-design.md) |
| Python → Kotlin | **업콜** | 빌드 타임 생성 함수 테이블 + 진입점 1개 | [`docs/upcall-design.md`](docs/upcall-design.md) |

```
┌──────────────────────────────────────────┐
Kotlin │ PyObject / PyType / PyList / ... │
코드 │ ↓ 다운콜 │
│ expect fun PyList_Size(...) (약 330개) │
└──────────────────────────────────────────┘
↕
┌──────────────────────────────────────────┐
Python │ 프록시 객체 → 업콜 진입점 1개 → 함수 테이블 │
코드 │ ↑ 업콜 │
└──────────────────────────────────────────┘
```

---

## 플랫폼별 구현 수단

| 플랫폼 | Kotlin 실행 환경 | 다운콜 | 업콜 진입 |
|---|---|---|---|
| iOS | Kotlin/Native | cinterop (직접 호출) | `@CName` 심볼 + `ctypes.CDLL(None)` |
| androidNative | Kotlin/Native | cinterop (직접 호출) | 동상 |
| Desktop | JVM (HotSpot) | Panama (FFM) | FFM upcall stub |
| Android | ART | shape 트램폴린 | FFM/JNI upcall |
| WASM | Kotlin/Wasm | JS 경유 (미구현) | JS 경유 (미구현) |

**진입점 뒤는 모든 플랫폼이 같은 함수 테이블을 쓴다.** 플랫폼별 Python→Kotlin 바인딩 코드는 없다.

---

## 문서 지도

| 문서 | 내용 |
|---|---|
| [`docs/architecture.md`](docs/architecture.md) | 소스셋 계층, 타입 래퍼 구조, 검증 수단 |
| [`docs/downcall-design.md`](docs/downcall-design.md) | 시그니처 shape 14종, 플랫폼별 다운콜 메커니즘 |
| [`docs/upcall-design.md`](docs/upcall-design.md) | 함수 테이블, 프록시 핸들 캐싱, KSP, 모듈 수집 |
| [`docs/binding-policy.md`](docs/binding-policy.md) | 무엇을 Python 에 노출하는가 (블랙리스트 원칙) |
| [`docs/threading-and-abi.md`](docs/threading-and-abi.md) | GIL, free-threading, `abi3t`, 스레드 상태 관리 |
| [`docs/android-ffm-design.md`](docs/android-ffm-design.md) | Android FFM, PanamaPort 분석, GraalVM Native Image 제약 |
| [`docs/wasm-design.md`](docs/wasm-design.md) | WASM 실현 가능성과 제약 |
| [`docs/python-version-acquisition.md`](docs/python-version-acquisition.md) | CPython 바이너리 취득 파이프라인 |

작업 규칙과 환경 제약은 [`CLAUDE.md`](CLAUDE.md) 에 있다.

---

## 현재 상태

### 동작하는 것

- CPython Stable ABI 약 330개 바인딩 (`expect`/`actual`, 4개 플랫폼)
- 타입 래퍼: `PyObject`, `PyType`, `PyException`, 기본 타입 6종, 컬렉션 5종, 이터레이터, `PyModule`, 변환 계층
- **테스트 97개 전부 통과** — iOS 시뮬레이터에서 실제 CPython 을 구동해 검증
- Gradle 이 CPython 을 플랫폼별로 내려받아 검증·추출 (인터프리터를 저장소에 벤더링하지 않음)
- 네 타깃 컴파일: `compileKotlinDesktop`, `compileKotlinAndroidNativeArm64`, `compileDebugKotlinAndroid`, `iosSimulatorArm64Test`

### 진행 중

- 스레드 상태 관리: 메커니즘과 라우팅은 완료. **초기화 후 GIL 해제는 아직 켜지 않았다** — 라우팅 커버리지가 완전함을 먼저 증명해야 한다. 상세는 [`docs/threading-and-abi.md`](docs/threading-and-abi.md)

### 미착수

- 업콜 전체 (함수 테이블, KSP 프로세서, 프록시 타입)
- JVM shape 통합 (Desktop + Android 다운콜 일원화)
- Desktop `invokeExact` 전환 — 현재 305곳이 `MethodHandle.invoke` 라 호출마다 박싱한다
- WASM
- 양방향 객체 수명 관리 — Kotlin GC 와 CPython 참조 카운팅이 서로를 모른다

---

## 큰 결정들

| 결정 | 근거 |
|---|---|
| 병렬성은 **free-threading**, 멀티 인터프리터 아님 | 인터프리터별 GIL 은 C 확장이 `Py_mod_multiple_interpreters` 를 선언해야 하고, 안 한 확장은 import 자체가 실패한다 |
| free-threading 은 **3.15t 부터** | 3.13/3.14 free-threaded 빌드에는 Limited API 가 없다. `abi3t`(PEP 803, Final)가 3.15 부터다 |
| Python→Kotlin 은 **런타임 리플렉션 아님** | Kotlin/Native 에 리플렉션이 사실상 없고, GraalVM Native Image 는 closed-world 다 |
| 노출은 **블랙리스트** | 모든 `public` 자동 등록, `@PythonInternal` 로 명시적 제외 |
| PanamaPort **미사용** | 라이선스(GPLv2+CE 벤더링)와 ART 내부 구조 의존에 따른 유지보수 부채 |
| LLVM JIT **불필요** | 우리 시그니처엔 구조체 값 전달·가변인자가 0개라, 참조 구현조차 스텁 없이 직접 패치하는 경로를 탄다 |
Loading