1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-05-28 16:00:22 +02:00

Convert primitive-eval to "compile" its expressions to linked closures

* libguile/memoize.c (memoize): Fix meta on subsequent case-lambda
  clauses.

* module/ice-9/eval.scm (primitive-eval): Rewrite to compile expressions
  to thunks, to avoid runtime dispatch cost.
This commit is contained in:
Andy Wingo 2014-12-10 14:34:44 +01:00
parent dc33a94502
commit 95de4f52a8
2 changed files with 518 additions and 473 deletions

View file

@ -574,7 +574,7 @@ memoize (SCM exp, SCM env)
SCM_BOOL_F);
return MAKMEMO_LAMBDA (memoize (body, new_env), arity,
SCM_BOOL_F /* meta, filled in later */);
SCM_EOL /* meta, filled in later */);
}
case SCM_EXPANDED_LET:

View file

@ -27,22 +27,18 @@
;;; psyntax), then memoized into internal forms. The evaluator itself
;;; only operates on the internal forms ("memoized expressions").
;;;
;;; Environments are represented as linked lists of the form (VAL ... .
;;; MOD). If MOD is #f, it means the environment was captured before
;;; modules were booted. If MOD is the literal value '(), we are
;;; evaluating at the top level, and so should track changes to the
;;; current module.
;;;
;;; Evaluate this in Emacs to make code indentation work right:
;;;
;;; (put 'memoized-expression-case 'scheme-indent-function 1)
;;; Environments are represented as a chain of vectors, linked through
;;; their first elements. The terminal element of an environment is the
;;; module that was current when the outer lexical environment was
;;; entered.
;;;
;;; Code:
(eval-when (compile)
(define (primitive-eval exp)
"Evaluate @var{exp} in the current module."
(define-syntax env-toplevel
(syntax-rules ()
((_ env)
@ -79,366 +75,337 @@
(vector-set! e (1+ width) val)
(lp (vector-ref e 0) (1- d)))))))
;; For evaluating the initializers in a "let" expression. We have to
;; evaluate the initializers before creating the environment rib, to
;; prevent continuation-related shenanigans; see
;; http://wingolog.org/archives/2013/11/02/scheme-quiz-time for a
;; deeper discussion.
;;
;; This macro will inline evaluation of the first N initializers.
;; That number N is indicated by the number of template arguments
;; passed to the macro. It's a bit nasty but it's flexible and
;; optimizes well.
(define-syntax let-env-evaluator
(syntax-rules ()
((eval-and-make-env eval env (template ...))
(let ()
(define-syntax eval-and-make-env
(syntax-rules ()
((eval-and-make-env inits width (template ...) k)
(let lp ((n (length '(template ...))) (vals '()))
(if (eqv? n width)
(let ((env (make-env n #f env)))
(let lp ((n (1- n)) (vals vals))
(if (null? vals)
(k env)
(begin
(env-set! env 0 n (car vals))
(lp (1- n) (cdr vals))))))
(lp (1+ n)
(cons (eval (vector-ref inits n) env) vals)))))
((eval-and-make-env inits width (var (... ...)) k)
(let ((n (length '(var (... ...)))))
(if (eqv? n width)
(k (make-env n #f env))
(let* ((x (eval (vector-ref inits n) env))
(k (lambda (env)
(env-set! env 0 n x)
(k env))))
(eval-and-make-env inits width (x var (... ...)) k)))))))
(lambda (inits)
(let ((width (vector-length inits))
(k (lambda (env) env)))
(eval-and-make-env inits width () k)))))))
;; This is a modified version of Oleg Kiselyov's "pmatch".
(define-syntax-rule (match e cs ...)
(let ((v e)) (expand-clauses v cs ...)))
;; Fast case for procedures with fixed arities.
(define-syntax make-fixed-closure
(define-syntax expand-clauses
(syntax-rules ()
((_ v) ((error "unreachable")))
((_ v (pat e0 e ...) cs ...)
(let ((fk (lambda () (expand-clauses v cs ...))))
(expand-pattern v pat (let () e0 e ...) (fk))))))
(define-syntax expand-pattern
(syntax-rules (_ quote unquote)
((_ v _ kt kf) kt)
((_ v () kt kf) (if (null? v) kt kf))
((_ v (quote lit) kt kf)
(if (equal? v (quote lit)) kt kf))
((_ v (unquote exp) kt kf)
(if (equal? v exp) kt kf))
((_ v (x . y) kt kf)
(if (pair? v)
(let ((vx (car v)) (vy (cdr v)))
(expand-pattern vx x (expand-pattern vy y kt kf) kf))
kf))
((_ v #f kt kf) (if (eqv? v #f) kt kf))
((_ v var kt kf) (let ((var v)) kt))))
(define-syntax typecode
(lambda (x)
(define *max-static-argument-count* 8)
(define (make-formals n)
(map (lambda (i)
(datum->syntax
x
(string->symbol
(string (integer->char (+ (char->integer #\a) i))))))
(iota n)))
(syntax-case x ()
((_ eval nreq body env) (not (identifier? #'env))
#'(let ((e env))
(make-fixed-closure eval nreq body e)))
((_ eval nreq body env)
#`(case nreq
#,@(map (lambda (nreq)
(let ((formals (make-formals nreq)))
#`((#,nreq)
(lambda (#,@formals)
(eval body
(make-env* env #,@formals))))))
(iota *max-static-argument-count*))
(else
#,(let ((formals (make-formals *max-static-argument-count*)))
#`(lambda (#,@formals . more)
(let ((env (make-env nreq #f env)))
#,@(map (lambda (formal n)
#`(env-set! env 0 #,n #,formal))
formals (iota (length formals)))
(let lp ((i #,*max-static-argument-count*)
(args more))
(cond
((= i nreq)
(eval body
((_ type)
(or (memoized-typecode (syntax->datum #'type))
(error "not a typecode" (syntax->datum #'type)))))))
(define (compile-lexical-ref depth width)
(lambda (env)
(env-ref env depth width)))
(define (compile-call f nargs args)
(let ((f (compile f)))
(match args
(() (lambda (env) ((f env))))
((a)
(let ((a (compile a)))
(lambda (env) ((f env) (a env)))))
((a b)
(let ((a (compile a))
(b (compile b)))
(lambda (env) ((f env) (a env) (b env)))))
((a b c)
(let ((a (compile a))
(b (compile b))
(c (compile c)))
(lambda (env) ((f env) (a env) (b env) (c env)))))
((a b c . args)
(let ((a (compile a))
(b (compile b))
(c (compile c))
(args (let lp ((args args))
(if (null? args)
env
'()
(cons (compile (car args)) (lp (cdr args)))))))
(lambda (env)
(apply (f env) (a env) (b env) (c env)
(let lp ((args args))
(if (null? args)
'()
(cons ((car args) env) (lp (cdr args))))))))))))
(define (compile-box-ref box)
(match box
((,(typecode resolve) . var-or-loc)
(lambda (env)
(cond
((variable? var-or-loc) (variable-ref var-or-loc))
(else
(set! var-or-loc
(%resolve-variable var-or-loc (env-toplevel env)))
(variable-ref var-or-loc)))))
((,(typecode lexical-ref) depth . width)
(lambda (env)
(variable-ref (env-ref env depth width))))
(_
(let ((box (compile box)))
(lambda (env)
(variable-ref (box env)))))))
(define (compile-resolve var-or-loc)
(lambda (env)
(cond
((variable? var-or-loc) var-or-loc)
(else
(set! var-or-loc (%resolve-variable var-or-loc (env-toplevel env)))
var-or-loc))))
(define (compile-if test consequent alternate)
(let ((test (compile test))
(consequent (compile consequent))
(alternate (compile alternate)))
(lambda (env)
(if (test env) (consequent env) (alternate env)))))
(define (compile-quote x)
(lambda (env) x))
(define (compile-let inits body)
(let ((body (compile body))
(width (vector-length inits)))
(case width
((0) (lambda (env)
(body (make-env* env))))
((1)
(let ((a (compile (vector-ref inits 0))))
(lambda (env)
(body (make-env* env (a env))))))
((2)
(let ((a (compile (vector-ref inits 0)))
(b (compile (vector-ref inits 1))))
(lambda (env)
(body (make-env* env (a env) (b env))))))
((3)
(let ((a (compile (vector-ref inits 0)))
(b (compile (vector-ref inits 1)))
(c (compile (vector-ref inits 2))))
(lambda (env)
(body (make-env* env (a env) (b env) (c env))))))
((4)
(let ((a (compile (vector-ref inits 0)))
(b (compile (vector-ref inits 1)))
(c (compile (vector-ref inits 2)))
(d (compile (vector-ref inits 3))))
(lambda (env)
(body (make-env* env (a env) (b env) (c env) (d env))))))
(else
(let lp ((n width)
(k (lambda (env)
(make-env width #f env))))
(if (zero? n)
(lambda (env)
(body (k env)))
(lp (1- n)
(let ((init (compile (vector-ref inits (1- n)))))
(lambda (env)
(let* ((x (init env))
(new-env (k env)))
(env-set! new-env 0 (1- n) x)
new-env))))))))))
(define (compile-fixed-lambda body nreq)
(case nreq
((0) (lambda (env)
(lambda ()
(body (make-env* env)))))
((1) (lambda (env)
(lambda (a)
(body (make-env* env a)))))
((2) (lambda (env)
(lambda (a b)
(body (make-env* env a b)))))
((3) (lambda (env)
(lambda (a b c)
(body (make-env* env a b c)))))
((4) (lambda (env)
(lambda (a b c d)
(body (make-env* env a b c d)))))
((5) (lambda (env)
(lambda (a b c d e)
(body (make-env* env a b c d e)))))
((6) (lambda (env)
(lambda (a b c d e f)
(body (make-env* env a b c d e f)))))
((7) (lambda (env)
(lambda (a b c d e f g)
(body (make-env* env a b c d e f g)))))
(else
(lambda (env)
(lambda (a b c d e f g . more)
(let ((env (make-env nreq #f env)))
(env-set! env 0 0 a)
(env-set! env 0 1 b)
(env-set! env 0 2 c)
(env-set! env 0 3 d)
(env-set! env 0 4 e)
(env-set! env 0 5 f)
(env-set! env 0 6 g)
(let lp ((n 7) (args more))
(cond
((= n nreq)
(unless (null? args)
(scm-error 'wrong-number-of-args
"eval" "Wrong number of arguments"
'() #f))))
'() #f))
(body env))
((null? args)
(scm-error 'wrong-number-of-args
"eval" "Wrong number of arguments"
'() #f))
(else
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args))))))))))))))
(env-set! env 0 n (car args))
(lp (1+ n) (cdr args)))))))))))
;; Fast case for procedures with fixed arities and a rest argument.
(define-syntax make-rest-closure
(lambda (x)
(define *max-static-argument-count* 3)
(define (make-formals n)
(map (lambda (i)
(datum->syntax
x
(string->symbol
(string (integer->char (+ (char->integer #\a) i))))))
(iota n)))
(syntax-case x ()
((_ eval nreq body env) (not (identifier? #'env))
#'(let ((e env))
(make-rest-closure eval nreq body e)))
((_ eval nreq body env)
#`(case nreq
#,@(map (lambda (nreq)
(let ((formals (make-formals nreq)))
#`((#,nreq)
(lambda (#,@formals . rest)
(eval body
(make-env* env #,@formals rest))))))
(iota *max-static-argument-count*))
(define (compile-rest-lambda body nreq rest?)
(case nreq
((0) (lambda (env)
(lambda rest
(body (make-env* env rest)))))
((1) (lambda (env)
(lambda (a . rest)
(body (make-env* env a rest)))))
((2) (lambda (env)
(lambda (a b . rest)
(body (make-env* env a b rest)))))
((3) (lambda (env)
(lambda (a b c . rest)
(body (make-env* env a b c rest)))))
(else
#,(let ((formals (make-formals *max-static-argument-count*)))
#`(lambda (#,@formals . more)
(lambda (env)
(lambda (a b c . more)
(let ((env (make-env (1+ nreq) #f env)))
#,@(map (lambda (formal n)
#`(env-set! env 0 #,n #,formal))
formals (iota (length formals)))
(let lp ((i #,*max-static-argument-count*)
(args more))
(env-set! env 0 0 a)
(env-set! env 0 1 b)
(env-set! env 0 2 c)
(let lp ((n 3) (args more))
(cond
((= i nreq)
(env-set! env 0 nreq args)
(eval body env))
((= n nreq)
(env-set! env 0 n args)
(body env))
((null? args)
(scm-error 'wrong-number-of-args
"eval" "Wrong number of arguments"
'() #f))
(else
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args))))))))))))))
(env-set! env 0 n (car args))
(lp (1+ n) (cdr args)))))))))))
(define-syntax call
(lambda (x)
(define *max-static-call-count* 4)
(syntax-case x ()
((_ eval proc nargs args env) (identifier? #'env)
#`(case nargs
#,@(map (lambda (nargs)
#`((#,nargs)
(proc
#,@(map
(lambda (n)
(let lp ((n n) (args #'args))
(if (zero? n)
#`(eval (car #,args) env)
(lp (1- n) #`(cdr #,args)))))
(iota nargs)))))
(iota *max-static-call-count*))
(else
(apply proc
#,@(map
(lambda (n)
(let lp ((n n) (args #'args))
(if (zero? n)
#`(eval (car #,args) env)
(lp (1- n) #`(cdr #,args)))))
(iota *max-static-call-count*))
(let lp ((exps #,(let lp ((n *max-static-call-count*)
(args #'args))
(if (zero? n)
args
(lp (1- n) #`(cdr #,args)))))
(args '()))
(if (null? exps)
(reverse args)
(lp (cdr exps)
(cons (eval (car exps) env) args)))))))))))
;; This macro could be more straightforward if the compiler had better
;; copy propagation. As it is we do some copy propagation by hand.
(define-syntax mx-bind
(lambda (x)
(syntax-case x ()
((_ data () body)
#'body)
((_ data (a . b) body) (and (identifier? #'a) (identifier? #'b))
#'(let ((a (car data))
(b (cdr data)))
body))
((_ data (a . b) body) (identifier? #'a)
#'(let ((a (car data))
(xb (cdr data)))
(mx-bind xb b body)))
((_ data (a . b) body)
#'(let ((xa (car data))
(xb (cdr data)))
(mx-bind xa a (mx-bind xb b body))))
((_ data v body) (identifier? #'v)
#'(let ((v data))
body)))))
;; The resulting nested if statements will be an O(n) dispatch. Once
;; we compile `case' effectively, this situation will improve.
(define-syntax mx-match
(lambda (x)
(syntax-case x (quote else)
((_ mx data tag)
#'(error "what" mx))
((_ mx data tag (else body))
#'body)
((_ mx data tag (('type pat) body) c* ...)
#`(if (eqv? tag #,(or (memoized-typecode (syntax->datum #'type))
(error "not a typecode" #'type)))
(mx-bind data pat body)
(mx-match mx data tag c* ...))))))
(define-syntax memoized-expression-case
(lambda (x)
(syntax-case x ()
((_ mx c ...)
#'(let ((tag (car mx))
(data (cdr mx)))
(mx-match mx data tag c ...)))))))
;;;
;;; On 18 Feb 2010, I did a profile of how often the various memoized expression
;;; types occur when getting to a prompt on a fresh build. Here are the numbers
;;; I got:
;;;
;;; lexical-ref: 32933054
;;; call: 20281547
;;; toplevel-ref: 13228724
;;; if: 9156156
;;; quote: 6610137
;;; let: 2619707
;;; lambda: 1010921
;;; begin: 948945
;;; lexical-set: 509862
;;; call-with-values: 139668
;;; apply: 49402
;;; module-ref: 14468
;;; define: 1259
;;; toplevel-set: 328
;;; call/cc: 0
;;; module-set: 0
;;;
;;; So until we compile `case' into a computed goto, we'll order the clauses in
;;; `eval' in this order, to put the most frequent cases first.
;;;
(define primitive-eval
(let ()
;; We pre-generate procedures with fixed arities, up to some number
;; of arguments, and some rest arities; see make-fixed-closure and
;; make-rest-closure above.
;; Procedures with rest, optional, or keyword arguments, potentially with
;; multiple arities, as with case-lambda.
(define (make-general-closure env body nreq rest? nopt kw ninits unbound
alt)
(define alt-proc
(and alt ; (body meta nreq ...)
(let* ((body (car alt))
(spec (cddr alt))
(nreq (car spec))
(rest (if (null? (cdr spec)) #f (cadr spec)))
(tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
(nopt (if tail (car tail) 0))
(kw (and tail (cadr tail)))
(ninits (if tail (caddr tail) 0))
(unbound (and tail (cadddr tail)))
(alt (and tail (car (cddddr tail)))))
(make-general-closure env body nreq rest nopt kw ninits unbound
alt))))
(define (set-procedure-arity! proc)
(let lp ((alt alt) (nreq nreq) (nopt nopt) (rest? rest?))
(if (not alt)
(begin
(set-procedure-property! proc 'arglist
(list nreq
nopt
(if kw (cdr kw) '())
(and kw (car kw))
(and rest? '_)))
(set-procedure-minimum-arity! proc nreq nopt rest?))
(let* ((spec (cddr alt))
(nreq* (car spec))
(rest?* (if (null? (cdr spec)) #f (cadr spec)))
(tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
(nopt* (if tail (car tail) 0))
(alt* (and tail (car (cddddr tail)))))
(if (or (< nreq* nreq)
(and (= nreq* nreq)
(if rest?
(and rest?* (> nopt* nopt))
(or rest?* (> nopt* nopt)))))
(lp alt* nreq* nopt* rest?*)
(lp alt* nreq nopt rest?)))))
proc)
(set-procedure-arity!
(lambda %args
(define (npositional args)
(let lp ((n 0) (args args))
(if (or (null? args)
(and (>= n nreq) (keyword? (car args))))
n
(lp (1+ n) (cdr args)))))
(let ((nargs (length %args)))
(define (compile-opt-lambda body nreq rest? nopt ninits unbound make-alt)
(lambda (env)
(define alt (and make-alt (make-alt env)))
(lambda args
(let ((nargs (length args)))
(cond
((or (< nargs nreq)
(and (not kw) (not rest?) (> nargs (+ nreq nopt)))
(and alt kw (not rest?) (> (npositional %args) (+ nreq nopt))))
((or (< nargs nreq) (and (not rest?) (> nargs (+ nreq nopt))))
(if alt
(apply alt-proc %args)
(apply alt args)
((scm-error 'wrong-number-of-args
"eval" "Wrong number of arguments"
'() #f))))
(else
(let* ((nvals (+ nreq (if rest? 1 0) ninits))
(env (make-env nvals unbound env)))
(let lp ((i 0) (args %args))
(define (bind-req args)
(let lp ((i 0) (args args))
(cond
((< i nreq)
;; Bind required arguments.
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args)))
((not kw)
;; Optional args (possibly), but no keyword args.
(let lp ((i i) (args args))
(else
(bind-opt args)))))
(define (bind-opt args)
(let lp ((i nreq) (args args))
(cond
((and (< i (+ nreq nopt)) (< i nargs))
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args)))
(else
(bind-rest args)))))
(define (bind-rest args)
(when rest?
(env-set! env 0 (+ nreq nopt) args))
(eval body env)))))
(else
;; Optional args. As before, but stop at the first
;; keyword.
(let lp ((i i) (args args))
(body env))
(bind-req args))))))))
(define (compile-kw-lambda body nreq rest? nopt kw ninits unbound make-alt)
(define allow-other-keys? (car kw))
(define keywords (cdr kw))
(lambda (env)
(define alt (and make-alt (make-alt env)))
(lambda args
(define (npositional args)
(let lp ((n 0) (args args))
(if (or (null? args)
(and (>= n nreq) (keyword? (car args))))
n
(lp (1+ n) (cdr args)))))
(let ((nargs (length args)))
(cond
((and (< i (+ nreq nopt))
(< i nargs)
((or (< nargs nreq)
(and alt (not rest?) (> (npositional args) (+ nreq nopt))))
(if alt
(apply alt args)
((scm-error 'wrong-number-of-args
"eval" "Wrong number of arguments"
'() #f))))
(else
(let* ((nvals (+ nreq (if rest? 1 0) ninits))
(env (make-env nvals unbound env)))
(define (bind-req args)
(let lp ((i 0) (args args))
(cond
((< i nreq)
;; Bind required arguments.
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args)))
(else
(bind-opt args)))))
(define (bind-opt args)
(let lp ((i nreq) (args args))
(cond
((and (< i (+ nreq nopt)) (< i nargs)
(not (keyword? (car args))))
(env-set! env 0 i (car args))
(lp (1+ i) (cdr args)))
(else
(bind-rest args)))))
(define (bind-rest args)
(when rest?
(env-set! env 0 (+ nreq nopt) args))
(let ((aok (car kw))
(kw (cdr kw)))
;; Now scan args for keywords.
(bind-kw args))
(define (bind-kw args)
(let lp ((args args))
(cond
((and (pair? args) (pair? (cdr args))
(keyword? (car args)))
(let ((kw-pair (assq (car args) kw))
(let ((kw-pair (assq (car args) keywords))
(v (cadr args)))
(if kw-pair
;; Found a known keyword; set its value.
(env-set! env 0 (cdr kw-pair) v)
;; Unknown keyword.
(if (not aok)
(if (not allow-other-keys?)
((scm-error
'keyword-argument-error
"eval" "Unrecognized keyword"
@ -452,115 +419,193 @@
"eval" "Invalid keyword"
'() (list (car args))))))
(else
;; Finally, eval the body.
(eval body env))))))))))))))))))
(body env)))))
(bind-req args))))))))
;; The "engine". EXP is a memoized expression.
(define (eval exp env)
(memoized-expression-case exp
(('lexical-ref (depth . width))
(env-ref env depth width))
(define (compute-arity alt nreq rest? nopt kw)
(let lp ((alt alt) (nreq nreq) (nopt nopt) (rest? rest?))
(if (not alt)
(let ((arglist (list nreq
nopt
(if kw (cdr kw) '())
(and kw (car kw))
(and rest? '_))))
(values arglist nreq nopt rest?))
(let* ((spec (cddr alt))
(nreq* (car spec))
(rest?* (if (null? (cdr spec)) #f (cadr spec)))
(tail (and (pair? (cdr spec)) (pair? (cddr spec)) (cddr spec)))
(nopt* (if tail (car tail) 0))
(alt* (and tail (car (cddddr tail)))))
(if (or (< nreq* nreq)
(and (= nreq* nreq)
(if rest?
(and rest?* (> nopt* nopt))
(or rest?* (> nopt* nopt)))))
(lp alt* nreq* nopt* rest?*)
(lp alt* nreq nopt rest?))))))
(('call (f nargs . args))
(let ((proc (eval f env)))
(call eval proc nargs args env)))
(define (compile-general-lambda body nreq rest? nopt kw ninits unbound alt)
(call-with-values
(lambda ()
(compute-arity alt nreq rest? nopt kw))
(lambda (arglist min-nreq min-nopt min-rest?)
(define make-alt
(match alt
(#f #f)
((body meta nreq . tail)
(compile-lambda body meta nreq tail))))
(define make-closure
(if kw
(compile-kw-lambda body nreq rest? nopt kw ninits unbound make-alt)
(compile-opt-lambda body nreq rest? nopt ninits unbound make-alt)))
(lambda (env)
(let ((proc (make-closure env)))
(set-procedure-property! proc 'arglist arglist)
(set-procedure-minimum-arity! proc min-nreq min-nopt min-rest?)
proc)))))
(('box-ref box)
(memoized-expression-case box
;; Accelerate common cases.
(('resolve var-or-loc)
(if (variable? var-or-loc)
(variable-ref var-or-loc)
(variable-ref (eval box env))))
(('lexical-ref (depth . width))
(variable-ref (env-ref env depth width)))
(else
(variable-ref (eval box env)))))
(define (compile-lambda body meta nreq tail)
(define (set-procedure-meta meta proc)
(match meta
(() proc)
(((prop . val) . meta)
(set-procedure-meta meta
(lambda (env)
(let ((proc (proc env)))
(set-procedure-property! proc prop val)
proc))))))
(let ((body (compile body)))
(set-procedure-meta
meta
(match tail
(() (compile-fixed-lambda body nreq))
((rest? . tail)
(match tail
(() (compile-rest-lambda body nreq rest?))
((nopt kw ninits unbound alt)
(compile-general-lambda body nreq rest? nopt kw
ninits unbound alt))))))))
(('resolve var-or-loc)
(if (variable? var-or-loc)
var-or-loc
(let ((var (%resolve-variable var-or-loc (env-toplevel env))))
(set-cdr! exp var)
var)))
(('if (test consequent . alternate))
(if (eval test env)
(eval consequent env)
(eval alternate env)))
(('quote x)
x)
(('let (inits . body))
(eval body ((let-env-evaluator eval env (_ _ _ _)) inits)))
(('lambda (body meta nreq . tail))
(let ((proc
(if (null? tail)
(make-fixed-closure eval nreq body env)
(mx-bind
tail (rest? . tail)
(if (null? tail)
(make-rest-closure eval nreq body env)
(mx-bind
tail (nopt kw ninits unbound alt)
(make-general-closure env body nreq rest?
nopt kw ninits unbound
alt)))))))
(let lp ((meta meta))
(unless (null? meta)
(set-procedure-property! proc (caar meta) (cdar meta))
(lp (cdr meta))))
proc))
(('capture-env (locs . body))
(define (compile-capture-env locs body)
(let ((body (compile body)))
(lambda (env)
(let* ((len (vector-length locs))
(new-env (make-env len #f (env-toplevel env))))
(let lp ((n 0))
(when (< n len)
(mx-bind
(vector-ref locs n) (depth . width)
(env-set! new-env 0 n (env-ref env depth width)))
(match (vector-ref locs n)
((depth . width)
(env-set! new-env 0 n (env-ref env depth width))))
(lp (1+ n))))
(eval body new-env)))
(body new-env)))))
(('seq (head . tail))
(begin
(eval head env)
(eval tail env)))
(define (compile-seq head tail)
(let ((head (compile head))
(tail (compile tail)))
(lambda (env)
(head env)
(tail env))))
(('box-set! (box . val))
(variable-set! (eval box env) (eval val env)))
(define (compile-box-set! box val)
(let ((box (compile box))
(val (compile val)))
(lambda (env)
(let ((val (val env)))
(variable-set! (box env) val)))))
(('lexical-set! ((depth . width) . x))
(env-set! env depth width (eval x env)))
(define (compile-lexical-set! depth width x)
(let ((x (compile x)))
(lambda (env)
(env-set! env depth width (x env)))))
(('call-with-values (producer . consumer))
(call-with-values (eval producer env)
(eval consumer env)))
(define (compile-call-with-values producer consumer)
(let ((producer (compile producer))
(consumer (compile consumer)))
(lambda (env)
(call-with-values (producer env)
(consumer env)))))
(('apply (f args))
(apply (eval f env) (eval args env)))
(define (compile-apply f args)
(let ((f (compile f))
(args (compile args)))
(lambda (env)
(apply (f env) (args env)))))
(('capture-module x)
(eval x (current-module)))
(define (compile-capture-module x)
(let ((x (compile x)))
(lambda (env)
(x (current-module)))))
(('call-with-prompt (tag thunk . handler))
(call-with-prompt
(eval tag env)
(eval thunk env)
(eval handler env)))
(define (compile-call-with-prompt tag thunk handler)
(let ((tag (compile tag))
(thunk (compile thunk))
(handler (compile handler)))
(lambda (env)
(call-with-prompt (tag env) (thunk env) (handler env)))))
(('call/cc proc)
(call/cc (eval proc env)))))
(define (compile-call/cc proc)
(let ((proc (compile proc)))
(lambda (env)
(call/cc (proc env)))))
;; primitive-eval
(lambda (exp)
"Evaluate @var{exp} in the current module."
(eval
(define (compile exp)
(match exp
((,(typecode lexical-ref) depth . width)
(compile-lexical-ref depth width))
((,(typecode call) f nargs . args)
(compile-call f nargs args))
((,(typecode box-ref) . box)
(compile-box-ref box))
((,(typecode resolve) . var-or-loc)
(compile-resolve var-or-loc))
((,(typecode if) test consequent . alternate)
(compile-if test consequent alternate))
((,(typecode quote) . x)
(compile-quote x))
((,(typecode let) inits . body)
(compile-let inits body))
((,(typecode lambda) body meta nreq . tail)
(compile-lambda body meta nreq tail))
((,(typecode capture-env) locs . body)
(compile-capture-env locs body))
((,(typecode seq) head . tail)
(compile-seq head tail))
((,(typecode box-set!) box . val)
(compile-box-set! box val))
((,(typecode lexical-set!) (depth . width) . x)
(compile-lexical-set! depth width x))
((,(typecode call-with-values) producer . consumer)
(compile-call-with-values producer consumer))
((,(typecode apply) f args)
(compile-apply f args))
((,(typecode capture-module) . x)
(compile-capture-module x))
((,(typecode call-with-prompt) tag thunk . handler)
(compile-call-with-prompt tag thunk handler))
((,(typecode call/cc) . proc)
(compile-call/cc proc))))
(let ((proc (compile
(memoize-expression
(if (macroexpanded? exp)
exp
((module-transformer (current-module)) exp)))
#f))))
((module-transformer (current-module)) exp)))))
(env #f))
(proc env)))