컴파일러는 이름을 끊임없이 찾는데 리스트 선형 탐색은 그 모양이 아니다. 키는
맵이 소유하는 한 버퍼에 복사되고 슬롯은 그 안의 어디인지만 적는다 -- R11 이
말하는 아레나와 핸들 모양이고, 그래서 맵을 놓는 것이 엔트리마다 하나가 아니라
두 번의 해제다.
개방 주소법에 선형 탐사. 표는 2의 거듭제곱이라 나눗셈이 아니라 마스크이고,
탐사가 길어지는 것이 표가 차는 것보다 먼저라 3/4 에서 자란다.
길에서 고친 것 넷:
- 인스턴스를 만들 때 선언 유닛으로 전환하지 않아서, 필드 타입 Slot(V) 를
호출자 유닛에서 찾고 있었다.
- mem.alloc_slice 가 원소 타입으로 단순한 이름만 받았다. Slot(V) 같은
인스턴스도 받는다.
- 쓸 수 있는지를 바인딩이 아니라 소유된 것이 정한다. let p: ^[]mut T 는 p 를
고정하고 그것이 소유한 것은 쓸 수 있게 둔다. 슬라이스 인덱스도 마찬가지다.
- 메서드 인자에 자유 함수와 같은 호출 한정 약화가 없었다.
알게 된 것: 대여는 루트 단위라 self 의 한 필드에 쓰는 동안 다른 필드를 읽을 수
없다. 지역으로 빼거나 메서드를 나누면 되지만, 필드 단위 대여가 있으면 훨씬
편할 자리다.
count 5 / fn 2 let 3 missing 0 / grown 64 / after 40 / balanced
218/218, 28/28.
SPEC R11 은 재귀·그래프 모양 데이터를 아레나가 값을 소유하고 정수 핸들이
가리키는 것으로 답한다. 그 답은 아레나가 실제로 있어야 쓸 수 있다.
핸들은 오프셋이라 아레나가 사는 동안 유효하고, 두 핸들을 비교하는 것은 두 수를
비교하는 것이다. 아레나는 몰래 자라지 않는다 -- 움직인 핸들은 더 이상 아무것도
가리키지 않기 때문이다.
길에서 고친 것 셋:
- Self 가 제네릭 인스턴스에서만 타입으로 묶여 있어서, 평범한 구조체의 Self{..}
가 안 풀렸다. 이제 모든 메서드에서 묶는다.
- binding.Type.method() 가 식 자리에서 해석되지 않았다.
- 다른 유닛의 비제네릭 구조체 메서드가 lowering 되지 않고 extern 으로만 나갔다.
파일을 나눌 때 그 가지가 빠졌다.
handles 0 4 8 / value 65 / full / reset 0 / balanced
exec.py 26/26.
런타임에 open/read/close 와 명령줄을 넣었다. std.io 가 그 위에 파일 열기,
읽기, 쓰기, 그리고 명령줄을 조각으로 나누는 것을 얹는다. 인용부호 처리는
런타임이 알 일이 아니라 라이브러리가 할 일이다.
길에서 고친 것들:
- *T 가 타입 시스템에 실체가 없어서 덩어리로 취급됐다. 이제 진짜 종류다 --
주소일 뿐이고 추적할 대여도 실행할 drop 도 없는 Copy 타입. 그 결과 &u8 이
*u8 에 자동으로 맞지 않게 됐는데, 그게 맞다: R9 는 그 변환을 unsafe 안의
@ptr_cast 로만 허용한다.
- raw 포인터에 정수를 더하면 더 뒤의 주소다. 소유자나 대여에는 허용하지
않는다 -- 자기 자리가 있는 것에서 걸어나가는 것이 *T 의 용도다.
- @volatile_load / @volatile_store / @ptr_cast 를 내린다.
- undefined 가 선언된 타입을 따른다. 없으면 손으로 타이핑할 수 있는 것보다
큰 버퍼를 선언할 방법이 아예 없었다.
R8 이 정확히 동작하는 것도 확인했다: 참조성 파라미터가 둘인 함수는 슬라이스를
반환할 수 없다. 어디서 파생됐는지 시그니처가 말하지 않기 때문이다.
exec.py 24/24.
std.str 은 eq/starts_with/find/trim/parse_int 을 바이트 슬라이스 위에서 한다.
std.list 는 자라는 제네릭 시퀀스다 -- 버퍼를 소유하므로 리스트를 놓으면
원소도 놓인다. 성장은 두 배씩이라 push 당 복사량이 상수로 눌린다.
찾은 버그 넷:
- 메서드가 자기 타입의 유닛이 아니라 호출한 유닛에 속한 것으로 계산됐다.
다른 유닛의 제네릭 타입을 쓰면 필드가 전부 private 으로 보였다.
- 참조로 도달한 메서드를 찾지 못했다. self.grow() 가 안 됐다.
- 이미 참조인 수신자의 주소를 한 번 더 떠서 넘겼다. 포인터의 포인터를 받은
메서드가 그것을 구조체로 읽었다.
- 유닛으로 한정된 제네릭 타입(list.List(i32))이 타입 자리에서도 식 자리에서도
해석되지 않았다.
drop 을 가진 타입은 인스턴스마다 그 메서드가 존재해야 한다 -- 이름으로 부르는
사람이 없어도 스코프 정리가 부른다. 그리고 자기 drop 안에서는 필드를 꺼낼 수
있다. 객체가 사라지는 중이라 뒤에 남아 읽힐 것이 없다.
run.py 207/207, exec.py 19/19.
defer 목록을 '스코프가 아직 갚아야 할 것' 목록으로 일반화했다. defer 블록과
소유 값 해제가 같은 목록에 쓰인 순서대로 들어가고, 모든 이탈 경로가 역순으로
갚는다.
해제에는 값 옆에 플래그를 둔다. 값이 저장될 때 세우고 넘겨줄 때 지운다. 값이
아직 여기 있는 경로에서만 해제되는데, 그건 코드의 모양만 봐서는 알 수 없는
것이다. 검사기가 소유권을 넘기는 사용을 이미 표시해두므로 그것을 읽는다.
!void 함수의 빈 return 은 성공이다. 줄 값도 없고 오류도 없다는 뜻인데
프론트엔드가 타입 불일치로 거부하고 있었다.
런타임이 할당/해제 횟수를 센다. owndrop 프로그램이 그 둘이 일치함을
실행으로 증명한다 -- 이른 반환, 이미 넘긴 값, 스코프 끝 전부.
io.Writer 는 핸들 하나짜리 enum 이다. 참조도 컨텍스트 포인터도 담지 않으므로
Copy 이고 자유롭게 오간다 (SPEC 5 R8). fmt 는 sink 를 소유하지 않는다 --
호출자가 버퍼를 주고 앞에서 몇 바이트가 쓰였는지 돌려받는다.
lowering 에 추가: enum 변이 상수, match, 정수 캐스트, 문자열 이스케이프.
프론트엔드 정밀도 하나: 항상 빠져나가는 분기의 상태를 병합하지 않는다. 그
분기가 소비한 값이 그 분기를 지나지 않은 경로에서도 소비된 것처럼 보였다.
fmt_i32 가 이것 때문에 못 쓰였다.
extern "c" 이름은 유닛 접두사를 붙이지 않는다. 링커가 이미 아는 이름이라는
것이 그 선언의 요점이다.
run.py 199/199, exec.py 11/11.
std 는 예약된 이름이고 프로그램이 아니라 컴파일러와 함께 있으므로 자기 루트를
갖는다 (--std=). 본문 없는 선언은 링커가 찾을 것 -- 런타임이나 C 라이브러리 --
이므로 IR 에 extern 으로 나간다.
런타임에 write/alloc/free/exit 를 넣었다. 이것이 표준 라이브러리가 스스로
말할 수 없는 전부이고 나머지는 Ferro 로 쓴다.
@trap @unreachable @size_of @align_of @line 을 내린다.
링크 이름에서 점과 괄호를 걸렀다. 유닛 경로에는 점이 있고 제네릭 인스턴스에는
괄호가 있는데 어셈블러가 받지 않는다.