diff options
author | David Thompson <dthompson2@worcester.edu> | 2023-01-21 10:32:27 -0500 |
---|---|---|
committer | David Thompson <dthompson2@worcester.edu> | 2023-06-08 08:14:41 -0400 |
commit | 3cd4639e3056eb3ed1d6bb34fa73d23bd4c66441 (patch) | |
tree | 15ba863e90ab0b37d36f6b34f9f08a0cf5219137 | |
parent | 7222f641750ef55b5c44341347e56044c3cf5e85 (diff) |
Constant propagation and partial evaluation.
-rw-r--r-- | chickadee/graphics/seagull.scm | 99 |
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 ;;; |