빌드 전체가 한 모듈이라 파일 이름도 하나였다. std.list 안에서 터진 경계 검사가
프로그램의 파일 이름을 대고 있었으니, 줄 번호는 맞는데 파일이 틀려서 엉뚱한 줄을
가리켰다 -- 이름을 안 대는 것보다 나쁘다.
모듈이 파일 표를 들고 트랩은 그 인덱스를 든다. 생성기는 파일마다 FE_FILE_n 을
한 번씩 찍는다.
before: index out of bounds at main.fe:2
after: index out of bounds at pick.fe:6
그리고 Parser.on 이 구조체 리터럴 안에서 다시 try 를 쓴다. 앞서 그것이 깨졌던
것은 try 때문이 아니라 Parser 가 1 바이트로 자리잡았기 때문이었다.
224/224, 31/31.
셀프호스팅에 손대기 전의 강제 함수다. 아픈 자리를 전부 건드린다: R4 아래의
토큰 구조체, 태그드 유니온, 진단 출력, 유닛 경계.
토큰은 자기가 나온 글자를 담지 않는다. R4 가 대여를 집합 저장소에서 막으므로,
어디서 시작해 얼마나 긴지를 적고 소스는 옆에서 같이 다닌다. 위치도 &mut usize
로 옆에서 다닌다 -- 슬라이스와 함께 구조체에 들어갈 수 없기 때문이다. 이것이
R11 이 말하는 모양이고, 쓸 수 있다.
first keyword unit @1 / number 42 @3 / text "hi" @3 / arrow -> @5
keyword 6 name 7 number 1 text 1 punct 15 / total 30
길에서 고친 것:
- binding.Type.Variant 가 안 풀렸다. 유닛 경계 이름 조회가 심볼만 보고 타입을
보지 않았다.
- 문자열 const 전역이 빈 슬라이스로 나갔다. 포인터는 링커만 아는 수라서 바이트에
구멍을 두고 링커가 채우게 한다.
- exec.py 가 OUTPUT 마커를 여러 개 적어도 마지막 하나만 검사했다. 고치자마자
readfile 의 낡은 기대가 드러났다.
run.py 217/217, exec.py 27/27.
전역은 정적 저장소다. 초기값이 컴파일타임 상수면 이미지에 박고 아니면 0이다.
문자열 리터럴은 바이트를 이미지에 두고 포인터와 길이를 값으로 만든다. 같은
글자는 같은 저장소를 쓴다 -- 읽기 전용이라 공유가 공짜다.
defers 프로그램이 defer 순서를 실행으로 고정한다. 등록 역순이고, 이른 return
과 끝까지 간 경로 양쪽 다 돈다.
인덱스는 부호 없는 비교와 트랩으로 펴진다. --no-checks 는 메시지가 아니라
비교와 분기 자체를 없앤다 -- 그게 그 플래그의 전부다.
버그 셋:
- 값으로 넘긴 구조체 파라미터는 주소로 도착하는데 lowering 이 그걸 몰라서
포인터를 구조체로 읽었다. 변수마다 by_address 를 기록한다.
- 덩어리 반환의 숨은 결과 인자를 지역이 아니라 임시값으로 다뤘다.
- store 에 폭이 없어서 1바이트 bool 을 4바이트로 썼다. 옆 지역변수가
뭉개졌고 logic 프로그램이 틀린 답을 냈다.
tests/exec.py 가 새 스위트다. run.py 는 컴파일러가 프로그램에 대해 뭐라고
하는지 보고, 이쪽은 프로그램이 실제로 무엇을 하는지 본다. 보고만 되고
방출되지 않는 경계검사는 저기서는 통과하고 여기서는 실패한다.
run.py 194/194, exec.py 6/6.
fec --emit-asm -> wasm -> wlink -> .exe. 툴체인은 고정된 Open Watcom 그대로다.
레지스터 할당기가 없다. 임시값마다 스택 슬롯을 주고, 명령마다 피연산자를
고정 레지스터로 읽어 계산하고 다시 저장한다. 느린 코드지만 명백히 옳은
코드이고, 옳은 것이 먼저다. 나중에 할당기를 끼워도 나머지는 모른다 --
임시값이 어디 사는지만 바뀐다.
런타임 fec/rt/start.asm 은 진입 스텁과 fe_trap 이다. trap 은 이유와 파일과
줄을 stderr 에 쓰고 3으로 끝낸다.
처음으로 Ferro 프로그램이 실행됐다:
1..10 합 -> 55
(7*6-2)/4 -> 10
루프+호출+분기 -> 1
tests/build.py 가 컴파일하고 링크하고 돌린다.
함수, 파라미터, 지역, 리터럴, 이름, 산술과 비교, 대입, if, while,
break/continue, 호출, 참조, 필드 접근, 포인터 역참조까지.
Slot 이 표현식의 결과다 -- 임시값에 든 값이거나 메모리 안의 자리다. 덩어리는
언제나 자리다. 임시값은 레지스터이고 덩어리는 거기 안 들어가기 때문이다.
and/or 는 연산이 아니라 제어 흐름으로 내린다. 오른쪽을 평가하지 않아야 하는
경우가 있어서다.
블록에 terminated 플래그를 뒀다. 없으면 분기 안의 return 이 join 으로 가는
점프에 덮인다.
--dump-ir 로 볼 수 있다. try/catch/defer/drop/for/배열/옵셔널/에러유니온과
제네릭 인스턴스는 아직이다.
명령 12개와 종결자 4개. 함수 단위 기본 블록이고 임시값은 블록을 넘지 않아
phi 노드가 없다 -- 블록을 넘겨야 하는 값은 지역을 경유한다. 코드가 조금 더
생기지만 레지스터 할당기를 블록 단위로 유지해 준다.
Ferro 타입은 여기서 사라진다. 구조체·슬라이스·옵셔널·에러 유니온이 전부
mem<N> 이고 필드는 lowering 이 계산한 바이트 오프셋이다. 모노모피제이션이
프론트엔드에서 끝나므로 IR 에 제네릭이라는 개념도 없다.
덩어리는 언제나 주소로 오간다. 크기 임계값이 없어서 ISA 마다 다른 구조체 전달
규칙을 통째로 피해간다. trap 은 이유와 줄 번호만 남기고 파일 이름 문자열은
유닛당 하나를 공유한다.
IR.md 가 설명이고 ir.h/ir.c 가 그 형태다. 아직 아무도 만들지 않는다.