From b1ce1927022d374198441afafeb27199fc8a052a Mon Sep 17 00:00:00 2001 From: eunhhu Date: Thu, 24 Sep 2026 17:14:54 +0100 Subject: Establish native micro runtime checkpoint --- docs/design/002-memory-lifetime.md | 135 +++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/design/002-memory-lifetime.md (limited to 'docs/design/002-memory-lifetime.md') diff --git a/docs/design/002-memory-lifetime.md b/docs/design/002-memory-lifetime.md new file mode 100644 index 0000000..a706a36 --- /dev/null +++ b/docs/design/002-memory-lifetime.md @@ -0,0 +1,135 @@ +# 메모리와 데이터 수명 초안 0.1 + +상태: **제안, 실행기 미구현**. 현재 Workbench에는 바이트 버퍼·비동기 완료 큐가 없다. +이 문서는 [연산 의미](001-operation-semantics.md)를 구현할 때의 불변조건과 회수 규칙을 정한다. + +## 1. 예산의 소유자 + +각 실행 영역에 바이트 예산과 객체·요청 개수 예산을 부여한다. 영역의 소유자는 런타임이다. +첫 구현 후보는 고정 크기 청크 풀과 세대 번호를 가진 핸들 테이블이다. +장기 상태·스트림 문맥·작업 임시 공간은 수명이 달라 별도 항목으로 센다. +이 정책이 최적이라고 가정하지 않으며 일반 할당 방식과 같은 작업으로 비교한다. + +| 계측할 공간 | 반드시 포함할 것 | +|---|---| +| 확보한 backing 용량 | 사용 중인 청크와 비어 있지만 보유 중인 청크, 정렬·단편화 | +| 메타데이터 | 핸들·대여·문맥·작업·완료·대기열 슬롯 | +| 살아 있는 데이터 | 앱·소비자·진행 중 작업이 여전히 참조하는 실제 버퍼 | +| 모듈 내부 공간 | 코덱 윈도·사전·파싱 상태·작업 공간·라이브러리의 자체 할당 | +| 교체 중 공간 | 이전 코드·문맥·진행 중 작업과 다음 프로그램의 준비 공간 | +| 외부 자원 | 별도 호스트 힙·OS·GPU 자원과 추적할 수 없는 할당 | + +같은 물리 버퍼를 여러 독자가 대여해도 물리 용량은 한 번만 센다. +물리 용량과 사용량은 포함 관계이므로 합산하지 않는다. 모듈이 예산 밖에서 할당하면 +그 공간을 보고서에서 제외하지 않고 외부·미계측으로 표시하며 전체 상한 보장을 중단한다. +요청별 예산을 만들었다고 라이브러리의 숨은 할당이 자동 통제되는 것은 아니다. +풀에서 할당한 모듈 작업 공간·메타데이터는 그 풀 확보 용량의 세부 귀속 항목이다. +풀 용량에 다시 더하지 않는다. 확보 용량은 할당한 backing의 크기이며 OS의 상주 RAM 측정값과 다르다. + +현재 64 KiB는 Wasm 선형 메모리의 초기·최대 용량이다. JS 호스트·컴파일러·렌더러까지 +포함한 전체 예산이 아니다. 새 버퍼 풀을 그 공간 밖에 만들면 별도로 포함해야 한다. + +## 2. 버퍼 식별과 범위 + +`BufferHandle = (slot:u32, generation:u32)`를 첫 후보로 정한다. 포인터·파일 오프셋이 아니다. +세대 0은 무효, 처음 발급은 1이다. 슬롯 재사용마다 세대를 증가시키고 최댓값에 도달한 슬롯은 +회수 후 영구 퇴역한다. 순환으로 오래된 핸들이 다시 유효해지지 않게 한다. +핸들은 발급한 실행 영역 안에서만 의미가 있다. 다른 영역으로 숫자만 복사해 전달할 수 없다. + +구간은 핸들과 `offset:u32, length:u32`다. 런타임은 다음을 검사한다. + +- 현재 세대·소유자·요청자 권한·접근 모드가 일치한다. +- 읽기는 현재 유효 데이터 길이, 쓰기는 예약 용량 안에 있다. +- `offset <= bound`를 먼저 확인하고 `length <= bound - offset`을 검사해 덧셈 오버플로를 피한다. +- 정렬 요구를 충족한다. v0 바이트 프로파일의 기본 정렬은 1이며 더 큰 정렬은 모듈 설명에 명시한다. +- v0 출력 버퍼는 현재 유효 길이 0, 출력 구간 오프셋 0이어야 한다. 접수 때 암묵적으로 + 이전 데이터를 버리지 않는다. 소비가 끝난 뒤 대여가 없는 소유자가 명시적으로 유효 길이를 0으로 + 줄여 재사용한다. 유효 길이를 늘리는 일은 검증된 쓰기 완료·유효 출력 공개로만 허용한다. + +같은 프로세스의 임의 네이티브 코드로부터 메모리를 격리하는 기능은 아니다. +이 계약의 첫 대상은 계약을 지키는 내부 모듈이다. 불신 모듈의 격리는 별도 실행 경계가 필요하다. + +## 3. 소유권과 대여 + +한 버퍼에는 소유자 하나가 있으며 런타임이 대여 기록을 관리한다. +복사 없는 전달의 후보는 버퍼 내용 대신 이 소유권 또는 읽기 대여를 넘기는 것이다. + +| 상태 | 허용하는 일 | 금지하는 일 | +|---|---|---| +| 소유, 대여 없음 | 읽기·독점 쓰기·소유권 이전·반납 | 범위 밖 접근 | +| 읽기 대여 1개 이상 | 대여받은 독자의 읽기, 추가 읽기 대여 | 쓰기·소유권 이전·재사용·반납 | +| 독점 쓰기 대여 1개 | 지정된 작성자의 범위 내 쓰기 | 다른 읽기·쓰기·이전·재사용·반납 | +| 반납 완료 | 같은 슬롯의 새로운 세대로 재할당 가능 | 이전 핸들 접근 | + +첫 구현은 **서로 다른 구간이라도 버퍼 전체 단위로 대여**한다. 구간별 동시 쓰기나 +동적 겹침 증명은 후속 연구다. 읽기·쓰기 대여가 동시에 존재하지 않는다는 조건을 단순하게 유지한다. + +모듈 호출을 위해 해석한 실제 포인터는 허용된 접근 기간에만 유효하다. 동기 백엔드는 함수 반환 후 +포인터를 사용하지 않는다. 비동기 백엔드는 최종 완료 게시 전에 접근을 끝낸다. +런타임에 접수된 요청의 대여는 백엔드의 동기·비동기 여부와 관계없이 **완료 회수까지 유지**한다. +모듈이 계속 데이터를 보유해야 한다면 +자체 문맥에 예산을 잡아 복사하거나, 별도의 추적 가능한 장기 대여가 필요하다. +첫 프로파일은 호출 종료 뒤 입력·출력 포인터를 숨겨 보유하는 것을 금지한다. + +`반납`은 대여가 있으면 `BUSY`로 실패하고 소유권을 유지한다. 암묵적 지연 반납을 만들지 않는다. +비밀 데이터 버퍼의 지우기는 마지막 접근이 끝난 뒤 수행하며 사용한 라이브러리의 보장·한계와 +복사본의 수명까지 별도로 확인한다. 단순히 슬롯 번호를 지우는 것으로 데이터 삭제를 주장하지 않는다. + +## 4. 비동기 요청의 수명 + +첫 계약은 한 실행 영역의 직렬 제어 흐름을 사용한다. 모듈의 worker는 완료를 게시하고 +상태·핸들 테이블의 회수는 영역 소유자가 수행한다. 제어 API의 재진입을 허용하지 않는다. +스트림 문맥 하나에는 진행 중 호출을 최대 하나만 허용한다. + +```text +준비 → 런타임 접수 거절 : 예약 해제, 티켓 없음 +준비 → 런타임 접수/확정 → 백엔드 제출 → 완료 게시 → 완료 회수 + → 취소 요청 → 완료 게시 → 완료 회수 +``` + +- 접수 전 요청 슬롯·대여 슬롯·완료 보관 슬롯을 함께 확보한다. 실패하면 `WOULD_BLOCK` 또는 `LIMIT`이다. +- 런타임 접수 전 거절은 입력·출력·문맥을 바꾸지 않는다. 접수된 요청에는 세대가 붙은 티켓 하나를 발급한다. + 이벤트 안의 접수는 준비된 관리 상태와 함께 확정한다. 실제 백엔드 제출은 그 이후다. + 백엔드 제출 거절은 기존 티켓의 실패 완료로 보고하며 티켓 없는 거절로 되돌리지 않는다. +- 모든 접수된 요청은 성공·실패·취소 중 하나의 최종 완료로 정리해야 한다. + 실제 백엔드가 응답하지 않을 수 있으므로 유한 시간 안에 완료된다는 보장은 별도다. +- 즉시 완료해도 제출 호출 안에서 앱 콜백을 재진입하지 않고 완료 큐로 전달한다. +- 취소 호출은 **요청**이다. 거절되거나 정상 완료와 경합할 수 있다. 버퍼를 반납할 권한을 주지 않는다. +- 완료 게시 뒤에도 앱이 완료를 회수하기 전에는 해당 요청의 대여·슬롯을 보유한다. +- 완료 회수는 티켓·모듈 세대·길이·상태를 검증하고 유효 출력 길이와 문맥 상태를 반영한 뒤 + 대여·요청 슬롯을 한 번만 해제한다. 중복·오래된 완료는 진단하고 두 번 반납하지 않는다. + v0은 출력 버퍼의 유효 길이를 해당 요청의 `produced`로 설정한다. 실패·취소도 동일하며 + 물리적으로 썼지만 검증되지 않은 꼬리 부분은 공개하지 않는다. +- 중복 완료를 무시하는 것만으로 잘못된 백엔드의 추가 메모리 쓰기가 안전해지는 것은 아니다. + 모듈은 완료 게시 전에 작업·worker·장치의 접근 종료와 필요한 동기화를 보장해야 한다. +- 제한 시간 초과만으로 메모리를 재사용하지 않는다. 실제 접근이 종료됐다는 백엔드의 보장까지 보유한다. + +libuv도 취소 요청 후 완료 콜백 전에는 요청 관련 메모리를 해제하면 안 된다고 명시한다. +이 원칙을 Pure의 대여·티켓 상태에 반영했다. 취소에 외부 효과의 롤백을 포함하지 않는다. +[libuv 요청·취소 계약](https://docs.libuv.org/en/v1.x/request.html#c.uv_cancel) + +## 5. 포화·느린 소비자·교체 + +요청·완료·버퍼 슬롯이 없으면 생산자에게 역압력을 반환한다. 완료를 조용히 버리거나 +대기 요청을 무제한 늘리지 않는다. 앱은 재시도 시점·거절·명시적 버리기 정책을 선택한다. +대여를 오래 유지하는 소비자는 메모리를 붙잡는다. 복사량뿐 아니라 유지 용량과 바이트×시간을 센다. +소유권 전달을 했다는 사실만으로 GPU·드라이버·프로세스 경계의 복사가 없어졌다고 주장하지 않는다. + +프로그램·모듈 교체의 첫 정책은 **관련 진행 작업·대여·열린 문맥이 있으면 `BUSY`로 거절**하는 것이다. +작업을 배출하고 명시적으로 문맥을 닫은 뒤 교체한다. 시간 초과로 강제 언로드하지 않는다. +다중 버전을 동시에 유지하는 무중단 교체는 첫 계약 밖이다. +현재 이름 기반 숫자 상태 보존은 파일 핸들·코덱 문맥·키·GPU 자원의 자동 이전을 뜻하지 않는다. + +## 6. 구현 시 필수 확인 + +아직 통과한 검사가 아니라 다음 구현의 합격 조건이다. + +1. 버퍼 2개와 요청 1개로 여러 번 제출·완료·반납해 슬롯 수와 예산이 보존된다. +2. 포화된 제출은 상태·유효 길이·소유권을 변경하지 않는다. +3. 취소 요청만 받은 출력 버퍼는 다른 읽기·쓰기·반납이 거절된다. 입력 버퍼의 공유 읽기는 + 가능하나 쓰기·반납은 거절된다. 관련 문맥·모듈 교체도 거절되고 최종 완료 회수 전에는 해제하지 않는다. +4. 늦은 완료·중복 완료·해제된 세대의 핸들이 새 버퍼를 회수하거나 수정하지 않는다. +5. 실패 출력은 명시한 유효 접두부만 공개한다. 인증 실패 단위에서는 유효 길이가 0이다. +6. 입력 소비 완료 후 남는 출력·종료 처리가 정확히 진행된다. 느린 소비자에서 사용량이 예산을 넘지 않는다. +7. 길이·누적 크기 넘침, 세대 소진, 할당 실패를 명시적으로 처리한다. +8. 모듈 내부 공간과 대기 작업까지 포함한 피크를 계산하고 외부 할당과 전체 RSS를 따로 보고한다. -- cgit v1.2.3