diff --git a/qi-lib/flow/core/compiler.rkt b/qi-lib/flow/core/compiler.rkt index b37ce538..102ea12e 100644 --- a/qi-lib/flow/core/compiler.rkt +++ b/qi-lib/flow/core/compiler.rkt @@ -17,17 +17,8 @@ racket/function racket/undefined (prefix-in fancy: fancy-app) - racket/list) - -;; "Composes" higher-order functions inline by directly applying them -;; to the result of each subsequent application, with the last argument -;; being passed to the penultimate application as a (single) argument. -;; This is specialized to our implementation of stream fusion in the -;; arguments it expects and how it uses them. -(define-syntax inline-compose1 - (syntax-rules () - [(_ f) f] - [(_ [op f] rest ...) (op f (inline-compose1 rest ...))])) + racket/list + "deforest.rkt") (begin-for-syntax @@ -48,144 +39,6 @@ (define (compile-flow stx) (process-bindings (optimize-flow stx))) - ;; Used for producing the stream from particular - ;; expressions. Implicit producer is list->cstream-next and it is - ;; not created by using this class but rather explicitly used when - ;; no syntax class producer is matched. - (define-syntax-class fusable-stream-producer - #:attributes (next prepare) - #:datum-literals (#%host-expression #%partial-application esc) - (pattern (~and (esc (#%host-expression (~literal range))) - stx) - #:attr next #'range->cstream-next - #:attr prepare #'range->cstream-prepare) - (pattern (~and (#%partial-application - ((#%host-expression (~literal range)) - (#%host-expression arg) ...)) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:with vindaloo (if (and chirality (eq? chirality 'right)) - #'curry - #'curryr) - #:attr next #'range->cstream-next - #:attr prepare #'(vindaloo range->cstream-prepare arg ...))) - - ;; Matches any stream transformer that can be in the head position - ;; of the fused sequence even when there is no explicit - ;; producer. Procedures accepting variable number of arguments like - ;; `map` cannot be in this class. - (define-syntax-class fusable-stream-transformer0 - #:attributes (f next) - #:datum-literals (#%host-expression #%partial-application) - (pattern (~and (#%partial-application - ((#%host-expression (~literal filter)) - (#%host-expression f))) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:when (and chirality (eq? chirality 'right)) - #:attr next #'filter-cstream-next)) - - ;; All implemented stream transformers - within the stream, only - ;; single value is being passed and therefore procedures like `map` - ;; can (and should) be matched. - (define-syntax-class fusable-stream-transformer - #:attributes (f next) - #:datum-literals (#%host-expression #%partial-application) - (pattern (~and (#%partial-application - ((#%host-expression (~literal map)) - (#%host-expression f))) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:when (and chirality (eq? chirality 'right)) - #:attr next #'map-cstream-next) - (pattern (~and (#%partial-application - ((#%host-expression (~literal filter)) - (#%host-expression f))) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:when (and chirality (eq? chirality 'right)) - #:attr next #'filter-cstream-next)) - - ;; Terminates the fused sequence (consumes the stream) and produces - ;; an actual result value. The implicit consumer is cstream->list is - ;; not part of this class as it is added explicitly when generating - ;; the fused operation. - (define-syntax-class fusable-stream-consumer - #:attributes (op init end) - #:datum-literals (#%host-expression #%partial-application) - (pattern (~and (#%partial-application - ((#%host-expression (~literal foldr)) - (#%host-expression op) - (#%host-expression init))) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:when (and chirality (eq? chirality 'right)) - #:attr end #'(foldr-cstream op init)) - (pattern (~and (#%partial-application - ((#%host-expression (~literal foldl)) - (#%host-expression op) - (#%host-expression init))) - stx) - #:do [(define chirality (syntax-property #'stx 'chirality))] - #:when (and chirality (eq? chirality 'right)) - #:attr end #'(foldl-cstream op init))) - - (define-syntax-class non-fusable - (pattern (~not (~or _:fusable-stream-transformer - _:fusable-stream-producer - _:fusable-stream-consumer)))) - - ;; Generates a syntax for the fused operation for given - ;; sequence. The syntax list must already conform to the rule that - ;; if the first operation is a fusable-stream-transformer, it must - ;; be a fusable-stream-transformer0 as well! - (define (generate-fused-operation ops) - (syntax-parse (reverse ops) - [(g:fusable-stream-consumer - op:fusable-stream-transformer ... - p:fusable-stream-producer) - ;; Contract probably not needed (prepare should produce - ;; meaningful error messages) - #`(esc (λ args - ((#,@#'g.end - (inline-compose1 [op.next op.f] ... - p.next)) - (apply p.prepare args))))] - [(g:fusable-stream-consumer - p:fusable-stream-producer) - ;; dtto - #`(esc (λ args - ((#,@#'g.end p.next) - (apply p.prepare args))))] - ;; The list must contain fusable-stream-transformer0 as the last element! - [(g:fusable-stream-consumer - op:fusable-stream-transformer ...) - ;; TODO: Add contract - #`(esc (λ (lst) - ((#,@#'g.end - (inline-compose1 [op.next op.f] ... - list->cstream-next)) - lst)))] - [(op:fusable-stream-transformer ... - p:fusable-stream-producer) - ;; dtto - #`(esc (λ args - ((cstream->list - (inline-compose1 [op.next op.f] ... - p.next)) - (apply p.prepare args))))] - ;; dtto - [(op:fusable-stream-transformer ...) - #'(esc (λ (lst) - ;; have a contract here for the input - ;; validate it's a list, and error message - ;; can include the op syntax object - ((cstream->list - (inline-compose1 [op.next op.f] ... - list->cstream-next)) - lst)))] - )) - (define-qi-expansion-step (normalize-rewrite stx) ;; TODO: the "active" components of the expansions should be ;; optimized, i.e. they should be wrapped with a recursive @@ -241,49 +94,6 @@ ;; return syntax unchanged if there are no known optimizations [_ stx])) - ;; 0. "Qi-normal form" - ;; 1. deforestation pass - ;; 2. other passes ... - ;; e.g.: - ;; changing internal representation to lists from values - may affect passes - ;; passes as distinct stages is safe and interesting, a conservative start - ;; one challenge: traversing the syntax tree - (define (deforest-rewrite stx) - (syntax-parse stx - [((~datum thread) _0:non-fusable ... - p:fusable-stream-producer - f:fusable-stream-transformer ...+ - g:fusable-stream-consumer - _1 ...) - #:with fused (generate-fused-operation (syntax->list #'(p f ... g))) - #'(thread _0 ... fused _1 ...)] - [((~datum thread) _0:non-fusable ... - p:fusable-stream-producer - g:fusable-stream-consumer - _1 ...) - #:with fused (generate-fused-operation (syntax->list #'(p g))) - #'(thread _0 ... fused _1 ...)] - [((~datum thread) _0:non-fusable ... - f1:fusable-stream-transformer0 - f:fusable-stream-transformer ...+ - g:fusable-stream-consumer - _1 ...) - #:with fused (generate-fused-operation (syntax->list #'(f1 f ... g))) - #'(thread _0 ... fused _1 ...)] - [((~datum thread) _0:non-fusable ... - p:fusable-stream-producer - f:fusable-stream-transformer ...+ - _1 ...) - #:with fused (generate-fused-operation (syntax->list #'(p f ...))) - #'(thread _0 ... fused _1 ...)] - [((~datum thread) _0:non-fusable ... - f1:fusable-stream-transformer0 - f:fusable-stream-transformer ...+ - _1 ...) - #:with fused (generate-fused-operation (syntax->list #'(f1 f ...))) - #'(thread _0 ... fused _1 ...)] - [_ this-syntax])) - ;; Applies f repeatedly to the init-val terminating the loop if the ;; result of f is #f or the new syntax object is eq? to the previous ;; (possibly initial) one. diff --git a/qi-lib/flow/core/deforest.rkt b/qi-lib/flow/core/deforest.rkt new file mode 100644 index 00000000..92a880b0 --- /dev/null +++ b/qi-lib/flow/core/deforest.rkt @@ -0,0 +1,261 @@ +#lang racket/base + +(provide (for-syntax deforest-rewrite)) + +(require (for-syntax racket/base + syntax/parse) + racket/performance-hint + racket/match + racket/function + racket/list) + +;; These bindings are used for ~literal matching to introduce implicit +;; producer/consumer when none is explicitly given in the flow. +(define-syntax cstream->list #'-cstream->list) +(define-syntax list->cstream #'-list->cstream) + +;; "Composes" higher-order functions inline by directly applying them +;; to the result of each subsequent application, with the last argument +;; being passed to the penultimate application as a (single) argument. +;; This is specialized to our implementation of stream fusion in the +;; arguments it expects and how it uses them. +(define-syntax inline-compose1 + (syntax-rules () + [(_ f) f] + [(_ [op f] rest ...) (op f (inline-compose1 rest ...))])) + +(begin-for-syntax + + ;; Used for producing the stream from particular + ;; expressions. Implicit producer is list->cstream-next and it is + ;; not created by using this class but rather explicitly used when + ;; no syntax class producer is matched. + (define-syntax-class fusable-stream-producer + #:attributes (next prepare) + #:datum-literals (#%host-expression #%partial-application esc) + (pattern (esc (#%host-expression (~literal range))) + #:attr next #'range->cstream-next + #:attr prepare #'range->cstream-prepare) + (pattern (~and (#%partial-application + ((#%host-expression (~literal range)) + (#%host-expression arg) ...)) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:with vindaloo (if (and chirality (eq? chirality 'right)) + #'curry + #'curryr) + #:attr next #'range->cstream-next + #:attr prepare #'(vindaloo range->cstream-prepare arg ...)) + (pattern (~literal list->cstream) + #:attr next #'list->cstream-next + #:attr prepare #'identity)) + + ;; Matches any stream transformer that can be in the head position + ;; of the fused sequence even when there is no explicit + ;; producer. Procedures accepting variable number of arguments like + ;; `map` cannot be in this class. + (define-syntax-class fusable-stream-transformer0 + #:attributes (f next) + #:datum-literals (#%host-expression #%partial-application) + (pattern (~and (#%partial-application + ((#%host-expression (~literal filter)) + (#%host-expression f))) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:when (and chirality (eq? chirality 'right)) + #:attr next #'filter-cstream-next)) + + ;; All implemented stream transformers - within the stream, only + ;; single value is being passed and therefore procedures like `map` + ;; can (and should) be matched. + (define-syntax-class fusable-stream-transformer + #:attributes (f next) + #:datum-literals (#%host-expression #%partial-application) + (pattern (~and (#%partial-application + ((#%host-expression (~literal map)) + (#%host-expression f))) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:when (and chirality (eq? chirality 'right)) + #:attr next #'map-cstream-next) + (pattern (~and (#%partial-application + ((#%host-expression (~literal filter)) + (#%host-expression f))) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:when (and chirality (eq? chirality 'right)) + #:attr next #'filter-cstream-next)) + + ;; Terminates the fused sequence (consumes the stream) and produces + ;; an actual result value. The implicit consumer is cstream->list is + ;; not part of this class as it is added explicitly when generating + ;; the fused operation. + (define-syntax-class fusable-stream-consumer + #:attributes (end) + #:datum-literals (#%host-expression #%partial-application) + (pattern (~and (#%partial-application + ((#%host-expression (~literal foldr)) + (#%host-expression op) + (#%host-expression init))) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:when (and chirality (eq? chirality 'right)) + #:attr end #'(foldr-cstream-next op init)) + (pattern (~and (#%partial-application + ((#%host-expression (~literal foldl)) + (#%host-expression op) + (#%host-expression init))) + stx) + #:do [(define chirality (syntax-property #'stx 'chirality))] + #:when (and chirality (eq? chirality 'right)) + #:attr end #'(foldl-cstream-next op init)) + (pattern (~literal cstream->list) + #:attr end #'(cstream-next->list)) + (pattern (esc (#%host-expression (~literal car))) + #:attr end #'(car-cstream-next))) + + (define-syntax-class non-fusable + (pattern (~not (~or _:fusable-stream-transformer + _:fusable-stream-producer + _:fusable-stream-consumer)))) + + ;; Generates a syntax for the fused operation for given + ;; sequence. The syntax list must already conform to the rule that + ;; if the first operation is a fusable-stream-transformer, it must + ;; be a fusable-stream-transformer0 as well! + (define (generate-fused-operation ops) + (syntax-parse (reverse ops) + [(c:fusable-stream-consumer + t:fusable-stream-transformer ... + p:fusable-stream-producer) + ;; Contract probably not needed (prepare should produce + ;; meaningful error messages) + #`(esc (λ args + ((#,@#'c.end + (inline-compose1 [t.next t.f] ... + p.next)) + (apply p.prepare args))))])) + + ;; 0. "Qi-normal form" + ;; 1. deforestation pass + ;; 2. other passes ... + ;; e.g.: + ;; changing internal representation to lists from values - may affect passes + ;; passes as distinct stages is safe and interesting, a conservative start + ;; one challenge: traversing the syntax tree + (define (deforest-rewrite stx) + (syntax-parse stx + [((~datum thread) _0:non-fusable ... + p:fusable-stream-producer + ;; There can be zero transformers here: + t:fusable-stream-transformer ... + c:fusable-stream-consumer + _1 ...) + #:with fused (generate-fused-operation + (syntax->list #'(p t ... c))) + #'(thread _0 ... fused _1 ...)] + [((~datum thread) _0:non-fusable ... + t1:fusable-stream-transformer0 + t:fusable-stream-transformer ... + c:fusable-stream-consumer + _1 ...) + #:with fused (generate-fused-operation + (syntax->list #'(list->cstream t1 t ... c))) + #'(thread _0 ... fused _1 ...)] + [((~datum thread) _0:non-fusable ... + p:fusable-stream-producer + ;; Must be 1 or more transformers here: + t:fusable-stream-transformer ...+ + _1 ...) + #:with fused (generate-fused-operation + (syntax->list #'(p t ... cstream->list))) + #'(thread _0 ... fused _1 ...)] + [((~datum thread) _0:non-fusable ... + f1:fusable-stream-transformer0 + f:fusable-stream-transformer ...+ + _1 ...) + #:with fused (generate-fused-operation + (syntax->list #'(list->cstream f1 f ... cstream->list))) + #'(thread _0 ... fused _1 ...)] + [_ this-syntax])) + + ) + +(begin-encourage-inline + + ;; Producers + + (define-inline (list->cstream-next done skip yield) + (λ (state) + (cond [(null? state) (done)] + [else (yield (car state) (cdr state))]))) + + (define-inline (range->cstream-next done skip yield) + (λ (state) + (match-define (list l h s) state) + (cond [(< l h) + (yield l (cons (+ l s) (cdr state)))] + [else (done)]))) + + (define range->cstream-prepare + (case-lambda + [(h) (list 0 h 1)] + [(l h) (list l h 1)] + [(l h s) (list l h s)])) + + ;; Transformers + + (define-inline (map-cstream-next f next) + (λ (done skip yield) + (next done + skip + (λ (value state) + (yield (f value) state))))) + + (define-inline (filter-cstream-next f next) + (λ (done skip yield) + (next done + skip + (λ (value state) + (if (f value) + (yield value state) + (skip state)))))) + + ;; Consumers + + (define-inline (cstream-next->list next) + (λ (state) + (let loop ([state state]) + ((next (λ () null) + (λ (state) (loop state)) + (λ (value state) + (cons value (loop state)))) + state)))) + + (define-inline (foldr-cstream-next op init next) + (λ (state) + (let loop ([state state]) + ((next (λ () init) + (λ (state) (loop state)) + (λ (value state) + (op value (loop state)))) + state)))) + + (define-inline (foldl-cstream-next op init next) + (λ (state) + (let loop ([acc init] [state state]) + ((next (λ () acc) + (λ (state) (loop acc state)) + (λ (value state) + (loop (op value acc) state))) + state)))) + + (define-inline (car-cstream-next next) + (λ (state) + (let loop ([state state]) + ((next (λ () (error 'car "Empty list!")) + (λ (state) (loop state)) + (λ (value state) + value)))))) + + ) diff --git a/qi-lib/flow/core/impl.rkt b/qi-lib/flow/core/impl.rkt index c0306a83..8cfc523a 100644 --- a/qi-lib/flow/core/impl.rkt +++ b/qi-lib/flow/core/impl.rkt @@ -19,15 +19,7 @@ values->list feedback-times feedback-while - kw-helper - cstream->list - list->cstream-next - range->cstream-next - range->cstream-prepare - map-cstream-next - filter-cstream-next - foldr-cstream - foldl-cstream) + kw-helper) (require racket/match (only-in racket/function @@ -246,65 +238,3 @@ (loop (values->list (apply f args))) (apply then-f args))))) - -;; Stream fusion -(define-inline (cstream->list next) - (λ (state) - (let loop ([state state]) - ((next (λ () null) - (λ (state) (loop state)) - (λ (value state) - (cons value (loop state)))) - state)))) - -(define-inline (foldr-cstream op init next) - (λ (state) - (let loop ([state state]) - ((next (λ () init) - (λ (state) (loop state)) - (λ (value state) - (op value (loop state)))) - state)))) - -(define-inline (foldl-cstream op init next) - (λ (state) - (let loop ([acc init] [state state]) - ((next (λ () acc) - (λ (state) (loop acc state)) - (λ (value state) - (loop (op value acc) state))) - state)))) - -(define-inline (list->cstream-next done skip yield) - (λ (state) - (cond [(null? state) (done)] - [else (yield (car state) (cdr state))]))) - -(define-inline (range->cstream-next done skip yield) - (λ (state) - (match-define (list l h s) state) - (cond [(< l h) - (yield l (cons (+ l s) (cdr state)))] - [else (done)]))) - -(define range->cstream-prepare - (case-lambda - [(h) (list 0 h 1)] - [(l h) (list l h 1)] - [(l h s) (list l h s)])) - -(define-inline (map-cstream-next f next) - (λ (done skip yield) - (next done - skip - (λ (value state) - (yield (f value) state))))) - -(define-inline (filter-cstream-next f next) - (λ (done skip yield) - (next done - skip - (λ (value state) - (if (f value) - (yield value state) - (skip state))))))