상태를 드러내고 제어를 조립하고 그래프와 호출 경계를 보존하세요.
정본 프로그램 스물세 개로 레지스터 상태와 pc 제어, 명시적 스택, 레이블과 실행 프로시저, 연산·저장 추상화, 성능 계수기, 벡터 메모리와 복사 수집, 컴파일 target과 linkage, 표현식·조합식 순서, 재귀 compiled 복귀, 명시적 제어 평가기, 표현을 넘는 프로시저 호출을 다시 연결합니다.
유한한 실행 하나의 모든 레지스터 값과 다음 명령과 스택 항목과 힙 포인터와 컴파일 명령과 복귀 주소와 프로시저 표현을 설명할 수 있나요?
- 바뀌는 레지스터 값을 하나의 상태로 나타내기
- pc로 다음 명령 고르기
- 미뤄 둔 곱셈을 명시적인 스택에 저장하기
- 식 데이터를 별도 실행 전에 명령열로 컴파일하기
- 레지스터 계약에 필요한 save와 restore만 넣기
- 프레임 깊이와 바인딩 오프셋으로 값 가져오기
- 루트에서 힙 참조를 추적하고 도달 불가능한 할당 분류하기
- 컨트롤러 레이블을 숫자 명령 위치로 바꾸기
- 해석된 assign, test, branch, goto, halt 실행하기
- 가져온 명령 수, push 수, 최대 스택 깊이 세기
- 명시적인 continuation으로 공유·중첩 서브루틴에서 복귀하기
- 명시적인 평가기 레지스터와 레이블과 스택 규약으로 eval과 apply 실행하기
- 하나의 환경과 apply 경계로 해석·컴파일 프로시저를 서로 호출하기
- 상수·레지스터·레이블·연산·스택·제어 이동을 한 기계 언어로 조립하기
- 컨트롤러를 유지하며 연산 패키지나 레지스터 저장 바꾸기
- 지원하는 모든 명령의 읽기·쓰기·스택·pc 계약 설명하기
- 명령마다 실행 클로저를 만들고 프로시저 수열 재사용하기
- 순서쌍을 나란한 car·cdr 벡터의 주소로 표현하기
- 공유·순환 참조를 forwarding하며 도달 가능한 셀 복사하기
- 소스 형식으로 컴파일러를 디스패치하며 target과 linkage 지키기
- 정의·대입·분기·클로저·수열·적용을 별도 IR로 컴파일하기
- 한 call 경계 전에 연산자와 피연산자를 보이는 소스 순서로 컴파일하기
- 완전한 재귀 compiled procedure 목록 읽고 실행하기
레지스터 기계 수업은 상태와 제어를 구체화합니다. 레지스터가 현재 값을 담고 pc가 다음 명령을 고르며 레이블은 수치 위치가 되고 save와 restore가 다른 경로를 거치는 동안 살아 있어야 할 값을 보존합니다. 생성된 실행 프로시저는 명령 태그 디스패치를 실행 루프에서 조립 단계로 옮깁니다.
기계 설계 추상화는 컨트롤러 텍스트와 이름 붙은 연산 패키지 및 레지스터 저장을 분리합니다. 명령 요약은 각 태그의 데이터·스택·제어 효과를 밝히고 계측은 벽시계나 하드웨어 비용이 아니라 한 유한 컨트롤러의 일을 셉니다.
벡터 메모리는 표현된 호스트 pair를 나란한 car·cdr 벡터의 명시적인 주소로 바꿉니다. 복사 수집기는 root에서 시작하고 필드를 따라가기 전에 forwarding 항목을 설치해 살아 있는 셀만 to-space로 옮기며 공유 꼬리와 순환을 보존합니다.
컴파일러 수업은 소스 분류와 실행을 분리합니다. 전용 컴파일러가 target과 linkage를 받아 보수적인 명령열 계약을 합성하며, 생성된 IR 실행기는 소스가 if인지 lambda인지 define인지 application인지 다시 묻지 않습니다.
조합식 컴파일은 apply 경계를 건너기 전에 연산자 먼저, 피연산자 왼쪽부터 순서를 보존합니다. 재귀 factorial 목록은 entry와 저장된 continuation·인자, base case, after-call 코드, 간접 복귀, 명령 수와 최대 스택 깊이를 컨트롤러 하나에서 드러냅니다.
명시적 제어 평가기는 이 기계 메커니즘을 eval-dispatch와 apply-dispatch로 결합하고, 미완료 적용 작업을 보존하며, 수열의 마지막 식 전에 호출자 continuation을 복원하고, 조건식과 대입과 정의와 원시·복합 호출을 이름 붙은 경로로 처리합니다.
컴파일 코드·평가기 인터페이스는 서로 다른 tagged procedure 표현을 유지하면서 어휘 환경과 정렬된 인자와 apply 디스패치를 공유합니다. 모든 관찰은 선택한 교육용 기계와 유한 입력의 정확한 상태 변화를 기록합니다.
- 출력
- —
- 값
- —
- 진단
- —
각 프로그램은 대응하는 제5장 수업의 첫 예상 관찰을 반환합니다. 새 대표 관찰에는 33개 명령 뒤 2에서 끝나는 유클리드 기계, (ptr 2)가 루트인 세 셀 벡터 목록, 할당 다섯 셀을 살아 있는 세 셀로 줄이는 복사 수집, (18 large 15)를 반환하는 compiled 핵심 형식, 66개 명령과 최대 스택 깊이 10으로 120을 반환하는 열다섯 명령 factorial 컨트롤러가 포함됩니다.
레지스터 쓰기, pc 변화, flag 결정, 스택 성장·축소, 레이블 해석, 생성 실행 클로저, 연산 패키지 조회, 벡터 주소, forwarding 쓰기, 컴파일러 디스패치, target·linkage 명령, IR 실행, 연산자·피연산자 순서, 재귀 복귀, 평가기 디스패치, 표현을 넘는 apply 경로를 찾으세요. 고정 한도의 실행 흐름은 선택한 유한 실행만 설명합니다.
기계와 메모리와 컴파일러와 평가기를 생각하는 질문 스물세 개
계산을 다시 이어 가려면 어떤 정보가 상태에 있어야 할까요?
정답 run은 산술 규칙 자체를 담고 있지 않습니다. b가 0인지 묻고 그렇지 않으면 step을 반복하므로 전이 규칙과 컨트롤러가 분리된 채 유지됩니다.
전이 프로시저 하나가 여러 기계 명령을 어떻게 나타낼까요?
정답 0번 명령은 b를 검사해 나머지 사이클로 들어가거나 pc 5의 halt로 이동합니다. 다른 명령들은 temp를 통해 값을 옮긴 뒤 검사로 제어를 되돌립니다.
기계가 재귀 문제 안으로 내려가는 동안 무엇을 저장해야 할까요?
정답 return 동안 각 전이는 저장된 곱할 값 하나를 pop하고 value를 갱신합니다. 빈 스택은 미뤄 둔 곱셈이 더는 남아 있지 않음을 뜻하므로 value가 최종 답이 됩니다.
식 트리는 어떻게 선형 명령열이 될까요?
정답 execute는 상수를 push합니다. 산술 명령은 오른쪽 값과 왼쪽 값을 pop하고 둘을 결합하여 결과를 push합니다. 남은 코드가 없으면 스택 맨 위의 값이 프로그램 값입니다.
두 명령열 사이에서 컴파일러는 언제 레지스터를 보존해야 할까요?
정답 감싸진 첫 번째 명령열은 이제 레지스터를 저장하기 위해 그 레지스터를 필요로 하며, restore 이후에는 해당 레지스터가 수정되었다고 더 이상 드러내지 않습니다. 충돌의 어느 한쪽 조건이라도 없으면 합성은 스택 명령을 전혀 내보내지 않습니다.
컴파일할 때 환경에 관해 어떤 지식을 런타임 조회 밖으로 옮길 수 있을까요?
정답 find-address는 변수 이름을 담고 있는 컴파일러 환경 프레임을 대상으로 이름 검색을 수행합니다. x에 대해 (1 0)을 만들고 나면 런타임은 그 주소를 써서 일치하는 값 프레임에서 42를 가져올 수 있습니다. 이 예제는 컴파일러와 기계가 프레임 배치 하나에 합의하게 만듭니다.
참조가 그래프를 이룰 때 저장소 관리자는 어떤 할당을 보존해야 할까요?
정답 두 번째 힙은 a에서 b와 c를 거쳐 다시 a로 돌아오는 순환을 포함합니다. contains?는 재방문을 멈추고 mark-roots는 x에서 두 번째 순회를 시작합니다. 그런 다음 unreachable은 모든 할당을 훑어 표시 집합 밖의 dead만 분류합니다. 이 수업은 루트 집합을 기준으로 객체의 도달 가능성을 추적하고 분류하는 과정을 모델링합니다.
기계가 숫자 위치를 필요로 하기 전까지 컨트롤러가 읽기 쉬운 레이블을 쓰려면 어떻게 해야 할까요?
정답 assemble은 레이블 기호를 건너뛰고 resolve에 요청해 branch와 goto 대상만 바꿉니다. 일반 명령은 바뀌지 않고 유지됩니다. 반환된 수열은 숫자 대상을 갖는 조립된 컨트롤러 데이터이며, 다음 컨트롤러 실행 단계의 입력으로 전달됩니다.
숫자 프로그램 카운터 하나가 대입과 검사와 제어 이동을 어떻게 조정할까요?
정답 첫 번째 프로그램은 n이 2인 상태에서 전체 컨트롤러를 실행하여 product를 2로 남깁니다. 두 번째 프로그램은 n이 1인 상태에서 시작하여 halt가 아닌 모든 명령 뒤에 pc, n, product, flag를 기록합니다. 두 실행 모두 40단계 보호 장치를 둡니다. 명령 실행기는 해결된 컨트롤러 벡터 위에서 assign, test, branch, goto, halt를 처리합니다.
마지막 레지스터 값만으로는 알 수 없는 어떤 일을 컨트롤러 수준 계수기가 보여 줄까요?
정답 n이 1일 때 분기는 기본 대입으로 바로 건너뛰어 스택 사용 없이 다섯 개의 명령을 가져온 뒤 halt에 도달합니다. n이 3일 때 컨트롤러는 두 재귀 수준에서 continue와 n을 저장하므로, 네 번의 push를 수행하고 깊이 4에 도달하며 27개의 명령을 가져온 뒤 값 6과 빈 스택으로 정지합니다. 이 수치들은 정확히 이 컨트롤러와 입력값과 계측 규약을 설명하며, 실행기가 가져온 명령 수와 스택 사용량을 명시적인 측정 데이터로 보고합니다.
레지스터 기계는 명령을 복사하지 않고 공유하거나 중첩한 서브루틴에서 어떻게 돌아올까요?
정답 두 번째 컨트롤러는 double-then-add-one 서브루틴을 부르고 그 서브루틴이 다시 double을 부릅니다. 안쪽 호출도 continue를 써야 하므로 바깥 서브루틴은 원래 호출자의 값을 먼저 저장합니다. double이 돌아오면 restore로 원래 주소를 복원하고 add1을 끝낸 뒤 main으로 복귀합니다. 이 유한 실행기는 수업에 나온 명시적 연결과 스택 한 칸을 모델링합니다.
평가기가 호스트 언어 재귀로 표현되지 않고 모든 continuation을 직접 보존해야 할 때 무엇이 달라질까요?
정답 수열 평가는 ev-sequence-last-exp가 마지막 식을 eval-dispatch로 보내기 전에 호출자의 continuation을 복원하므로 꼬리 재귀입니다. 따라서 두 sum-iter 실행은 재귀 호출 수가 달라도 측정한 평가기 최대 스택 깊이가 같습니다. 별도의 컨트롤러 경로는 if와 set!과 define 주변의 식과 환경과 continuation을 저장하고 복원합니다. 마지막 guest 프로그램은 재귀와 어휘 클로저 상태와 변경을 함께 실행하고 빈 스택으로 멈춥니다. 이 관찰은 유한 시뮬레이터와 선택한 컨트롤러의 정확한 상태 변화를 기록합니다.
해석 프로시저와 컴파일 프로시저가 같은 객체인 척하지 않으면서 서로 호출하려면 어떤 런타임 합의가 필요할까요?
정답 apply-any가 인터페이스입니다. 평가기의 적용은 연산자와 피연산자를 평가한 뒤 이 경계에 도달합니다. 컴파일된 call 명령은 VM 스택에서 프로시저와 인자를 꺼낸 뒤 같은 경계에 도달합니다. 평가기에서 컴파일 코드로 가는 실행은 (compiled primitive)을 기록합니다. add-three가 컴파일 코드로 들어간 뒤 덧셈이 원시 프로시저를 호출하기 때문입니다. 컴파일 코드에서 평가기로 가는 실행은 (interpreted primitive)을 기록합니다. 컴파일 코드가 double을 호출한 뒤 해석 본문이 원시 곱셈을 사용하기 때문입니다. 수업 모형은 두 방향의 정확한 교차 호출 계약을 기록합니다.
서로 다른 기계의 컨트롤러를 한 시뮬레이터가 실행하게 하는 공통 규약은 무엇일까요?
정답 evaluate-source는 const와 reg와 label과 op에 하나씩 의미를 줍니다. execute-one!은 조립된 명령 하나를 가져와 태그에 맞는 규칙을 적용합니다. 유클리드 컨트롤러는 대입과 검사와 분기와 레이블 이동과 perform을 사용하고, 서브루틴 컨트롤러는 continuation을 저장·복원한 뒤 레지스터에 든 주소로 돌아갑니다. 수업용 레지스터 기계 시뮬레이터는 지정된 명령어 집합을 명시적인 기계 상태 위에서 실행합니다.
기계 설계를 전체 재작성하지 않고 부분적으로 바꾸게 하는 경계는 무엇일까요?
정답 두 번째 프로그램은 같은 카운터 컨트롤러에 레지스터 bank 두 개를 줍니다. 한 bank는 가변 연관 레코드를 쓰고 다른 bank는 이름-인덱스 표 뒤의 벡터를 씁니다. 컨트롤러는 read와 write 메시지만 보냅니다. 같은 유한 관찰은 이 클라이언트 계약이 저장 방식 변경을 견딘다는 사실을 보여 줍니다.
완전한 컨트롤러를 추론하기 전에 명령 하나에 대해 무엇을 알아야 할까요?
정답 예제 컨트롤러는 이 참고표를 실제 실행과 맞춥니다. 상수와 연산 결과를 대입하고 val을 저장·복원하며 레이블 위치를 continue에 넣고 간접 이동한 뒤 test와 branch와 perform과 halt를 거칩니다. 이 요약은 유한 시뮬레이터 서브셋의 정확한 참고표입니다.
기계 루프가 실행 프로시저를 가져와 호출하기만 하도록 조립기가 미리 할 수 있는 일은 무엇일까요?
정답 첫 프로그램은 참 분기가 건너뛴 대입도 실행 프로시저를 가진다는 점을 보여 줍니다. 조립은 한 실행 경로가 아니라 컨트롤러 전체를 처리하기 때문입니다. factorial 프로그램은 클로저 여섯 개를 한 번 만들고 가변 레지스터와 계수기는 따로 가진 두 기계 인스턴스에 재사용합니다.
순서쌍이 호스트 pair가 아니라 정수 주소로 표현되어도 car와 cdr와 변경과 공유를 어떻게 유지할까요?
정답 공유 예제는 꼬리 하나를 할당한 뒤 서로 다른 바깥 셀 두 개의 cdr에 같은 포인터를 저장합니다. 그 포인터의 car 슬롯 하나를 바꾸면 두 목록의 관찰이 함께 바뀝니다. 고정 벡터 용량과 계속 증가하는 free 포인터는 수집기가 살아 있는 셀을 다시 배치하기 전까지 명시적인 한계로 남습니다.
공유 객체를 복제하거나 순환을 끝없이 도는 일 없이 살아 있는 저장 공간을 어떻게 압축할까요?
정답 첫 힙에는 셀 다섯 개가 있지만 root 두 개에서 닿는 셀은 세 개뿐입니다. 복사된 left와 right는 복사된 shared 꼬리 하나를 가리키고 쓰레기 셀 두 개의 forwarding 항목은 #f로 남습니다. 두 번째 힙의 자기 순환은 cdr 복사가 옛 포인터를 다시 만났을 때 이미 기록된 새 포인터를 즉시 재사용합니다.
어떤 컴파일러 결정은 소스 문법에 달리고 어떤 결정은 요청한 target 레지스터와 linkage에 달릴까요?
정답 명령열은 statements와 함께 보수적인 needs·modifies 집합을 가집니다. append-sequences는 순서를 유지하며 이 계약을 합칩니다. 조건식 예제는 재귀적으로 컴파일한 술어와 두 갈래 주위에 레이블과 linkage를 놓습니다. lambda 컴파일러는 본문을 재귀 컴파일해 그 명령 데이터를 표현된 compiled procedure에 저장합니다.
컴파일 뒤 실행 루프에서 사라지는 소스 언어 결정은 무엇일까요?
정답 compile-expression이 소스 형식을 분류한 뒤 run-code는 컴파일 명령 태그를 디스패치합니다. compiled procedure는 생성 때 포획한 어휘 환경을 새 호출 프레임으로 확장하고 빈 피연산자 스택에서 본문 코드를 실행합니다. 수업용 VM은 나열된 명령 집합으로 전역 정의와 포획한 지역 대입과 인용과 분기와 클로저와 원시 적용을 실행합니다.
컴파일된 적용 코드가 보존해야 할 순서와 표현 계약은 무엇일까요?
정답 두 번째 프로그램에서는 평가기가 compiled add-three를 호출하고 compiled 코드가 interpreted double을 호출합니다. 두 경로 모두 정렬된 인자 목록을 apply-any에 넘기며 dispatch-log는 실제 primitive·interpreted·compiled 태그를 남깁니다. 이 실행은 수업에서 사용하는 호출 규약을 기록합니다.
재귀 소스 프로시저의 암묵적인 호출 스택을 어떤 기계 상태가 대신할까요?
정답 계측 실행은 n = 5와 done을 가리키는 continue로 시작합니다. n = 0을 포함해 entry 여섯 번이 관찰되고, 미완료 호출 다섯 개가 값 두 개씩 보존하므로 최대 스택 깊이는 10입니다. halt 전에 열 값이 모두 복원됩니다. 가져온 66개 명령과 정확한 컨트롤러 목록은 이 유한 컴파일 프로시저를 측정합니다.