summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2023-01-21 10:32:27 -0500
committerDavid Thompson <dthompson2@worcester.edu>2023-06-08 08:14:41 -0400
commit3cd4639e3056eb3ed1d6bb34fa73d23bd4c66441 (patch)
tree15ba863e90ab0b37d36f6b34f9f08a0cf5219137
parent7222f641750ef55b5c44341347e56044c3cf5e85 (diff)
Constant propagation and partial evaluation.
-rw-r--r--chickadee/graphics/seagull.scm99
1 files changed, 99 insertions, 0 deletions
diff --git a/chickadee/graphics/seagull.scm b/chickadee/graphics/seagull.scm
index f7dd555..ebb79a0 100644
--- a/chickadee/graphics/seagull.scm
+++ b/chickadee/graphics/seagull.scm
@@ -109,6 +109,9 @@
(make-exception-with-message "seagull: unbound variable")
(make-exception-with-irritants (list name env))))))
+(define (lookup* name env)
+ (assq-ref env name))
+
(define (lookup-all names env)
(map (lambda (name) (lookup name env)) names))
@@ -314,6 +317,102 @@
;;;
+;;; Constant propagation and partial evaluation
+;;;
+
+;; Replace references to constants (variables that store an immediate
+;; value: integer, float, boolean) with the constants themselves.
+;; Then look for opportunities to evaluate primitive expressions that
+;; have constant arguments. This will make the type inferencer's job
+;; a bit easier.
+
+(define (propagate:if predicate consequent alternate env)
+ `(if ,(propagate-constants predicate env)
+ ,(propagate-constants consequent env)
+ ,(propagate-constants alternate env)))
+
+(define (propagate:lambda params body env)
+ `(lambda ,params ,(propagate-constants body env)))
+
+(define (propagate:let names exps body env)
+ (define exps*
+ (map (lambda (exp)
+ (propagate-constants exp env))
+ exps))
+ ;; Extend environment with known constants.
+ (define env*
+ (fold (lambda (name exp env*)
+ (if (immediate? exp)
+ (extend-env name exp env*)
+ env*))
+ env names exps*))
+ ;; Drop all bindings for constant expressions.
+ (define bindings
+ (filter-map (lambda (name exp)
+ (if (immediate? exp)
+ #f
+ (list name exp)))
+ names exps*))
+ ;; If there are no bindings left, remove the 'let' entirely.
+ (if (null? bindings)
+ (propagate-constants body env*)
+ `(let ,bindings
+ ,(propagate-constants body env*))))
+
+(define (propagate:primcall operator args env)
+ `(primcall ,operator
+ ,@(map (lambda (arg)
+ (propagate-constants arg env))
+ args)))
+
+(define (propagate:call operator args env)
+ `(call ,(propagate-constants operator env)
+ ,@(map (lambda (arg)
+ (propagate-constants arg env))
+ args)))
+
+;; The division of two integers can result in a rational, non-integer,
+;; such as 1/2. This isn't how integer division works in GLSL, so we
+;; need to round the result to an integer.
+(define (glsl-divide x y)
+ (let ((result (/ x y)))
+ (if (or (float? result) (integer? result))
+ result
+ (round result))))
+
+(define (propagate:arithmetic op x y env)
+ (define x* (propagate-constants x env))
+ (define y* (propagate-constants y env))
+ (if (or (and (exact-integer? x*) (exact-integer? y*))
+ (and (float? x*) (float? y*)))
+ (let ((op* (case op
+ ((+) +)
+ ((-) -)
+ ((*) *)
+ ((/) glsl-divide))))
+ (op* x* y*))
+ `(primcall ,op ,x* ,y*)))
+
+(define (propagate-constants exp env)
+ (match exp
+ ((? immediate?) exp)
+ (('var name _)
+ (or (lookup* name env) exp))
+ (('if predicate consequent alternate)
+ (propagate:if predicate consequent alternate env))
+ (('lambda (params ...) body)
+ (propagate:lambda params body env))
+ (('let ((names exps) ...) body)
+ (propagate:let names exps body env))
+ (('primcall (and (or '+ '- '* '/) op) x y)
+ (propagate:arithmetic op x y env))
+ (('primcall operator args)
+ (propagate:primcall operator args env))
+ (('call operator args ...)
+ (propagate:call operator args env))))
+
+
+;;;
;;; Function hoisting
;;;