하나의 apply 경계가 서로 다른 두 프로시저 표현을 연결한다.
평가기와 컴파일 코드가 하나의 환경 모형과 인자 규약과 프로시저 디스패치 경계를 공유하면 평가기는 컴파일 프로시저를 호출하고 컴파일 코드는 해석 프로시저를 호출할 수 있습니다.
해석 프로시저와 컴파일 프로시저가 같은 객체인 척하지 않으면서 서로 호출하려면 어떤 런타임 합의가 필요할까요?
- 해석 프로시저와 컴파일 프로시저를 서로 다른 태그 표현으로 유지하기
- 하나의 어휘 환경과 인자 목록 순서 규약 공유하기
- 원시·해석·컴파일 프로시저를 하나의 apply 경계로 보내기
- 평가기에서 컴파일 코드 호출하기
- 컴파일 코드에서 해석 프로시저 호출하기
- 두 방향의 실제 표현 디스패치 순서 기록하기
- 환경과 인자와 디스패치 규약으로 공유 apply 경계 설명하기
공유 전역 환경은 포장된 원시 프로시저와 double이라는 해석 프로시저와 add-three라는 컴파일 프로시저를 저장합니다. evaluate가 만드는 해석 클로저의 본문은 식 데이터로 남습니다. compile-expression이 만드는 컴파일 클로저의 본문은 스택 기계 명령 목록입니다. 두 표현은 눈에 보이게 다르지만 모두 매개변수와 어휘 환경을 가지고 같은 순서의 인자 목록을 받습니다.
apply-any가 인터페이스입니다. 평가기의 적용은 연산자와 피연산자를 평가한 뒤 이 경계에 도달합니다. 컴파일된 call 명령은 VM 스택에서 프로시저와 인자를 꺼낸 뒤 같은 경계에 도달합니다. 평가기에서 컴파일 코드로 가는 실행은 (compiled primitive)을 기록합니다. add-three가 컴파일 코드로 들어간 뒤 덧셈이 원시 프로시저를 호출하기 때문입니다. 컴파일 코드에서 평가기로 가는 실행은 (interpreted primitive)을 기록합니다. 컴파일 코드가 double을 호출한 뒤 해석 본문이 원시 곱셈을 사용하기 때문입니다. 수업 모형은 두 방향의 정확한 교차 호출 계약을 기록합니다.
(begin
(define (tagged-list? expression tag)
(and (pair? expression) (eq? (car expression) tag)))
(define (make-frame variables values)
(cons 'frame (map cons variables values)))
(define (frame-bindings frame) (cdr frame))
(define (first-frame environment) (car environment))
(define (enclosing-environment environment) (cdr environment))
(define (find-binding variable bindings)
(cond ((null? bindings) #f)
((eq? variable (caar bindings)) (car bindings))
(else (find-binding variable (cdr bindings)))))
(define (lookup-variable-value variable environment)
(if (null? environment)
(error "unbound shared variable" variable)
(let ((binding
(find-binding
variable
(frame-bindings (first-frame environment)))))
(if binding
(cdr binding)
(lookup-variable-value
variable
(enclosing-environment environment))))))
(define (define-variable! variable value environment)
(let ((frame (first-frame environment)))
(let ((binding (find-binding variable (frame-bindings frame))))
(if binding
(set-cdr! binding value)
(set-cdr! frame
(cons (cons variable value)
(frame-bindings frame)))))))
(define (extend-environment variables values base-environment)
(if (= (length variables) (length values))
(cons (make-frame variables values) base-environment)
(error "shared argument count mismatch" variables values)))
(define (make-primitive implementation)
(list 'primitive implementation))
(define (primitive-procedure? procedure)
(tagged-list? procedure 'primitive))
(define (primitive-implementation procedure) (cadr procedure))
(define (make-interpreted-procedure parameters body environment)
(list 'interpreted parameters body environment))
(define (interpreted-procedure? procedure)
(tagged-list? procedure 'interpreted))
(define (interpreted-parameters procedure) (cadr procedure))
(define (interpreted-body procedure) (caddr procedure))
(define (interpreted-environment procedure) (cadddr procedure))
(define (make-compiled-procedure parameters code environment)
(list 'compiled parameters code environment))
(define (compiled-procedure? procedure)
(tagged-list? procedure 'compiled))
(define (compiled-parameters procedure) (cadr procedure))
(define (compiled-code procedure) (caddr procedure))
(define (compiled-environment procedure) (cadddr procedure))
(define (self-evaluating? expression)
(or (number? expression) (string? expression) (boolean? expression)))
(define (quoted? expression) (tagged-list? expression 'quote))
(define (if? expression) (tagged-list? expression 'if))
(define (lambda? expression) (tagged-list? expression 'lambda))
(define (begin? expression) (tagged-list? expression 'begin))
(define (application? expression) (pair? expression))
(define (eval-sequence expressions environment)
(cond ((null? expressions)
(error "empty interpreted sequence"))
((null? (cdr expressions))
(evaluate (car expressions) environment))
(else
(evaluate (car expressions) environment)
(eval-sequence (cdr expressions) environment))))
(define (list-of-values expressions environment)
(if (null? expressions)
'()
(cons (evaluate (car expressions) environment)
(list-of-values (cdr expressions) environment))))
(define (evaluate expression environment)
(cond ((self-evaluating? expression) expression)
((symbol? expression)
(lookup-variable-value expression environment))
((quoted? expression) (cadr expression))
((if? expression)
(if (evaluate (cadr expression) environment)
(evaluate (caddr expression) environment)
(evaluate (cadddr expression) environment)))
((lambda? expression)
(make-interpreted-procedure
(cadr expression)
(cddr expression)
environment))
((begin? expression)
(eval-sequence (cdr expression) environment))
((application? expression)
(apply-any
(evaluate (car expression) environment)
(list-of-values (cdr expression) environment)))
(else (error "unknown interpreted expression" expression))))
(define (compile-expression expression)
(cond ((self-evaluating? expression)
(list (list 'constant expression)))
((symbol? expression)
(list (list 'lookup expression)))
((quoted? expression)
(list (list 'constant (cadr expression))))
((if? expression)
(append
(compile-expression (cadr expression))
(list
(list 'branch
(compile-expression (caddr expression))
(compile-expression (cadddr expression))))))
((lambda? expression)
(list
(list 'closure
(cadr expression)
(compile-sequence (cddr expression)))))
((begin? expression)
(compile-sequence (cdr expression)))
((application? expression)
(append
(compile-expression (car expression))
(compile-operands (cdr expression))
(list (list 'call (length (cdr expression))))))
(else (error "unknown compiled expression" expression))))
(define (compile-operands expressions)
(if (null? expressions)
'()
(append (compile-expression (car expressions))
(compile-operands (cdr expressions)))))
(define (compile-sequence expressions)
(cond ((null? expressions)
(error "empty compiled sequence"))
((null? (cdr expressions))
(compile-expression (car expressions)))
(else
(append (compile-expression (car expressions))
(list (list 'pop))
(compile-sequence (cdr expressions))))))
(define (make-stack) (cons 'stack '()))
(define (push-stack! stack value)
(set-cdr! stack (cons value (cdr stack))))
(define (pop-stack! stack)
(if (null? (cdr stack))
(error "empty shared stack")
(let ((value (cadr stack)))
(set-cdr! stack (cddr stack))
value)))
(define (top-stack stack)
(if (null? (cdr stack))
(error "missing shared result")
(cadr stack)))
(define (stack-empty? stack) (null? (cdr stack)))
(define (pop-arguments! count stack)
(if (= count 0)
'()
(let ((argument (pop-stack! stack)))
(append (pop-arguments! (- count 1) stack)
(list argument)))))
(define (execute-code code environment stack)
(if (null? code)
(top-stack stack)
(let ((instruction (car code))
(remaining (cdr code)))
(cond
((tagged-list? instruction 'constant)
(push-stack! stack (cadr instruction))
(execute-code remaining environment stack))
((tagged-list? instruction 'lookup)
(push-stack!
stack
(lookup-variable-value (cadr instruction) environment))
(execute-code remaining environment stack))
((tagged-list? instruction 'closure)
(push-stack!
stack
(make-compiled-procedure
(cadr instruction)
(caddr instruction)
environment))
(execute-code remaining environment stack))
((tagged-list? instruction 'pop)
(pop-stack! stack)
(execute-code remaining environment stack))
((tagged-list? instruction 'branch)
(let ((predicate (pop-stack! stack)))
(execute-code
(if predicate (cadr instruction) (caddr instruction))
environment
stack)
(execute-code remaining environment stack)))
((tagged-list? instruction 'call)
(let ((arguments
(pop-arguments! (cadr instruction) stack)))
(let ((procedure (pop-stack! stack)))
(push-stack! stack (apply-any procedure arguments))
(execute-code remaining environment stack))))
(else (error "unknown shared instruction" instruction))))))
(define dispatch-log '())
(define (procedure-kind procedure)
(cond ((primitive-procedure? procedure) 'primitive)
((interpreted-procedure? procedure) 'interpreted)
((compiled-procedure? procedure) 'compiled)
(else (error "unknown shared procedure" procedure))))
(define (record-dispatch! procedure)
(set! dispatch-log
(cons (procedure-kind procedure) dispatch-log)))
(define (apply-any procedure arguments)
(record-dispatch! procedure)
(cond
((primitive-procedure? procedure)
(apply (primitive-implementation procedure) arguments))
((interpreted-procedure? procedure)
(eval-sequence
(interpreted-body procedure)
(extend-environment
(interpreted-parameters procedure)
arguments
(interpreted-environment procedure))))
((compiled-procedure? procedure)
(let ((stack (make-stack)))
(let ((value
(execute-code
(compiled-code procedure)
(extend-environment
(compiled-parameters procedure)
arguments
(compiled-environment procedure))
stack)))
(pop-stack! stack)
value)))
(else (error "cannot apply shared procedure" procedure))))
(define primitive-bindings
(list (cons '+ +) (cons '- -) (cons '* *)
(cons '= =) (cons '< <) (cons 'list list)))
(define global-environment
(list
(make-frame
(map car primitive-bindings)
(map (lambda (binding)
(make-primitive (cdr binding)))
primitive-bindings))))
(define double
(make-interpreted-procedure
'(x)
'((* x 2))
global-environment))
(define-variable! 'double double global-environment)
(define closure-stack (make-stack))
(define add-three
(execute-code
(compile-expression '(lambda (x) (+ x 3)))
global-environment
closure-stack))
(pop-stack! closure-stack)
(define-variable! 'add-three add-three global-environment)
(set! dispatch-log '())
(define evaluator-value
(evaluate '(add-three 5) global-environment))
(define evaluator-dispatches (reverse dispatch-log))
(set! dispatch-log '())
(define compiled-call-stack (make-stack))
(define compiled-value
(execute-code
(compile-expression '(double 7))
global-environment
compiled-call-stack))
(pop-stack! compiled-call-stack)
(define compiled-dispatches (reverse dispatch-log))
(list
(list 'evaluator-to-compiled
evaluator-value
evaluator-dispatches)
(list 'compiled-to-evaluator
compiled-value
compiled-dispatches
(stack-empty? compiled-call-stack))
(list 'representations
(compiled-procedure? add-three)
(interpreted-procedure? double))))- 출력
- —
- 값
- —
- 진단
- —
프로그램은 ((evaluator-to-compiled 8 (compiled primitive)) (compiled-to-evaluator 14 (interpreted primitive) #t) (representations #t #t))을 반환합니다. 두 디스패치 목록은 선택한 실행에서 실제로 들어간 프로시저 표현을 기록합니다.
먼저 평가기가 add-three를 조회해 apply-any의 compiled 분기로 들어가고 컴파일 클로저의 VM 조회와 원시 덧셈 호출로 이어지는 경로를 따라가세요. 다음에는 double을 호출하는 컴파일 call 명령이 interpreted 분기로 들어가 환경을 확장하고 식 본문을 평가한 뒤 원시 곱셈으로 가는 경로를 따라가세요. 두 경로가 같은 전역 환경과 인자 순서 규약을 공유하면서도 interpreted와 compiled 태그를 구분하는지 확인하세요.
프로그램을 수정하고 결과를 비교해 보세요.
add-three를 호출하는 해석 프로시저와 double을 호출하는 컴파일 프로시저를 하나씩 추가하세요. 두 wrapper를 호출하기 전에 중첩 디스패치 로그를 예상하세요.
힌트 보기
각 wrapper는 자기 표현을 시간 순서 경로의 앞에 하나 더 붙입니다. 바깥 호출부터 시작해 본문이 들어가는 표현을 따라가세요.