From a5a523c2689696bca6305cf2549ce577d769ec24 Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:03:45 +0000 Subject: [PATCH 1/6] scaffolding for inlining This uses a transformer binding to capture the definition to be inlined, and then does the inlining in a new compiler pass prior to normalization. (WIP from the meeting on Friday May 23 2025) --- qi-lib/flow/core/compiler.rkt | 5 ++- qi-lib/flow/core/compiler/0005-inline.rkt | 48 +++++++++++++++++++++++ qi-lib/on.rkt | 16 ++++++-- 3 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 qi-lib/flow/core/compiler/0005-inline.rkt diff --git a/qi-lib/flow/core/compiler.rkt b/qi-lib/flow/core/compiler.rkt index c8101307b..207e2f0f2 100644 --- a/qi-lib/flow/core/compiler.rkt +++ b/qi-lib/flow/core/compiler.rkt @@ -4,10 +4,11 @@ (require (for-syntax racket/base syntax/parse) - "compiler/1000-qi0.rkt" - "compiler/2000-bindings.rkt" + "compiler/0005-inline.rkt" "compiler/0010-normalize.rkt" "compiler/0100-deforest.rkt" + "compiler/1000-qi0.rkt" + "compiler/2000-bindings.rkt" "passes.rkt") (begin-for-syntax diff --git a/qi-lib/flow/core/compiler/0005-inline.rkt b/qi-lib/flow/core/compiler/0005-inline.rkt new file mode 100644 index 000000000..be9d255b8 --- /dev/null +++ b/qi-lib/flow/core/compiler/0005-inline.rkt @@ -0,0 +1,48 @@ +#lang racket/base + +(provide (for-syntax inline-pass + flowdef)) + +(require (for-syntax racket/base + syntax/parse + "../strategy.rkt" + "../private/form-property.rkt") + "../passes.rkt") + +(begin-for-syntax + (struct flowdef (name def) + #:transparent + #:property prop:set!-transformer + (λ (flowdef-instance stx) + ;; stx is either (flow-name arg ...) or simply flow-name + (syntax-parse stx + [_:id (flowdef-name flowdef-instance)] + ;; use . args instead of ... to be agnostic to the possibility + ;; of any #%app transformers being at play + [(id:id . args) (datum->syntax stx + (cons #'(#%expression id) #'args) stx)] + [((~literal set!) _1 _2) + (raise-syntax-error #f "set! not allowed!")])))) + +(begin-for-syntax + (define (inline-rewrite stx) + (syntax-parse stx + #:datum-literals (#%host-expression + esc) + [(esc (#%host-expression id)) + #:declare id (static flowdef? "flow name") + ;; def is now bound to the flowdef struct instance + (define def (attribute id.value)) + (flowdef-def def)] + [_ stx])) + + (define-and-register-pass 5 (inline-pass stx) + (attach-form-property + (find-and-map/qi + ;; "knapsack" problem + inline-rewrite ; don't use fixed-point finding + ;; check if identifier + ;; check if bound to flowdef struct + ;; then do inlining + ;; can probably use syntax-local-apply-transformer, should do everything that we need. + stx)))) diff --git a/qi-lib/on.rkt b/qi-lib/on.rkt index fd53065cf..b5b5f729f 100644 --- a/qi-lib/on.rkt +++ b/qi-lib/on.rkt @@ -9,7 +9,10 @@ (require syntax/parse/define (for-syntax racket/base syntax/parse/lib/function-header - "flow/aux-syntax.rkt") + "flow/aux-syntax.rkt" + racket/syntax) + (only-in "flow/core/compiler/0005-inline.rkt" + flowdef) "flow.rkt" (only-in "private/util.rkt" define-alias @@ -37,12 +40,17 @@ (define-alias π flow-lambda) (define-alias flow-λ flow-lambda) +;; TODO: disallow set! of these bindings to anything else (define-syntax-parser define-flow [(_ ((~or* head:id head:function-header) . args:formals) clause:clause) #'(define head (flow-lambda args - clause))] + clause))] [(_ name:id clause:clause) - #'(define name - (flow clause))]) + #:with new-name (format-id #'hi "flow:~a" #'name) + #'(begin + (define-syntax name + (flowdef #'new-name #'clause)) + (define new-name + (flow clause)))]) From 1d13c173997dbbceb4b51f5e2b5030de25021520 Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:07:38 +0000 Subject: [PATCH 2/6] get inlining to work by pre-expanding the captured syntax (WIP from today's meeting, May 30 2025) --- qi-lib/on.rkt | 6 ++++-- qi-test/tests/definitions.rkt | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/qi-lib/on.rkt b/qi-lib/on.rkt index b5b5f729f..85bf2a91c 100644 --- a/qi-lib/on.rkt +++ b/qi-lib/on.rkt @@ -16,7 +16,8 @@ "flow.rkt" (only-in "private/util.rkt" define-alias - params-parser)) + params-parser) + (submod "flow/extended/expander.rkt" invoke)) (define-syntax-parser on [(_ args:subject) @@ -49,8 +50,9 @@ clause))] [(_ name:id clause:clause) #:with new-name (format-id #'hi "flow:~a" #'name) + #:with expanded-flow (expand-flow #'clause) #'(begin (define-syntax name - (flowdef #'new-name #'clause)) + (flowdef #'new-name #'expanded-flow)) (define new-name (flow clause)))]) diff --git a/qi-test/tests/definitions.rkt b/qi-test/tests/definitions.rkt index 2c8ab2c30..54a2bb5ec 100644 --- a/qi-test/tests/definitions.rkt +++ b/qi-test/tests/definitions.rkt @@ -3,6 +3,7 @@ (provide tests) (require qi + qi/list rackunit rackunit/text-ui (only-in math sqr)) @@ -28,7 +29,19 @@ (~>> (memq n))) (list ((t 3) 1 2 3) ((t 0) 1 2 3))) - '((3) #f))) + '((3) #f)) + ;; A test that exercises inlining. This doesn't actually verify + ;; that inlining was performed. We'll need more tests for that + ;; (e.g., testing the compiler rewrites, not just the output) + (check-equal? (let () + (define-flow my-filter + (filter odd?)) + (define-flow my-map + (map sqr)) + (define-flow my-filter-map + (~> my-filter my-map)) + (my-filter-map (list 1 2 3 4 5))) + '(1 9 25))) (test-suite "define-switch" From 2f59629ce2d0c6ed81890add1dcef64da7a9c681 Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:07:38 +0000 Subject: [PATCH 3/6] Avoid recursion in inlining In case of flows that contain references to themselves, for instance, inlining would naively not terminate. This modifies the inlining pass to signal to the `find-and-map` traversal *not* to traverse further into the produced syntax. (WIP from today's meeting) --- qi-lib/flow/core/compiler/0005-inline.rkt | 4 +- qi-lib/flow/core/strategy.rkt | 18 ++++----- qi-test/tests/compiler/rules/inlining.rkt | 48 +++++++++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 qi-test/tests/compiler/rules/inlining.rkt diff --git a/qi-lib/flow/core/compiler/0005-inline.rkt b/qi-lib/flow/core/compiler/0005-inline.rkt index be9d255b8..b11796792 100644 --- a/qi-lib/flow/core/compiler/0005-inline.rkt +++ b/qi-lib/flow/core/compiler/0005-inline.rkt @@ -33,7 +33,9 @@ #:declare id (static flowdef? "flow name") ;; def is now bound to the flowdef struct instance (define def (attribute id.value)) - (flowdef-def def)] + (syntax-property (flowdef-def def) + 'qi-do-not-recurse + #t)] [_ stx])) (define-and-register-pass 5 (inline-pass stx) diff --git a/qi-lib/flow/core/strategy.rkt b/qi-lib/flow/core/strategy.rkt index 2b6fbda35..829552b25 100644 --- a/qi-lib/flow/core/strategy.rkt +++ b/qi-lib/flow/core/strategy.rkt @@ -21,15 +21,15 @@ ;; f : syntax? -> (or/c syntax? #f) (match stx [(? syntax?) (let ([stx^ (f stx)]) - (if stx^ - ;; we keep traversing the produced syntax - ;; to transform nested syntax as needed - (datum->syntax stx^ - (find-and-map f (syntax-e stx^)) - stx^ - stx^) - ;; false was returned, so we stop - stx))] + (cond [(not stx^) stx] ; false was returned, so we stop + [(syntax-property stx^ 'qi-do-not-recurse) + (syntax-property-remove stx^ 'qi-do-not-recurse)] + ;; we keep traversing the produced syntax + ;; to transform nested syntax as needed + [else (datum->syntax stx^ + (find-and-map f (syntax-e stx^)) + stx^ + stx^)]))] [(cons a d) (cons (find-and-map f a) (find-and-map f d))] [_ stx])) diff --git a/qi-test/tests/compiler/rules/inlining.rkt b/qi-test/tests/compiler/rules/inlining.rkt new file mode 100644 index 000000000..243f89a05 --- /dev/null +++ b/qi-test/tests/compiler/rules/inlining.rkt @@ -0,0 +1,48 @@ +#lang racket/base + +(provide tests) + +(require (for-syntax racket/base) + rackunit + rackunit/text-ui + racket/function + ;; necessary to recognize and expand core forms correctly + qi/flow/extended/expander + ;; necessary to correctly expand the right-threading form + qi/flow/extended/forms + (submod qi/flow/extended/expander invoke) + qi/flow/core/compiler + (for-template qi/flow/core/compiler) + qi/on) + +;; NOTE: we may need to tag test syntax with `tag-form-syntax` +;; in some cases. See the comment on that function definition. +;; It's not necessary if we are directly using the expander +;; output, as that already includes the property, but we might +;; need to reattach it if we tranform that syntax in some way. + +(define (runs-within-time? f timeout) + (define handle (thread f)) + (define result (sync/timeout timeout handle)) + (kill-thread handle) ; no-op if already dead + (not (not result))) + +(define tests + + (test-suite + "inlining" + + (test-suite + "does not inline and enter infinite loop" + (test-true "does not enter infinite loop" + (runs-within-time? + (thunk + (expand + #'(let () + (define-flow f (if odd? (~> add1 f) _)) + (f 4)))) + 1.0))))) + +(module+ main + (void + (run-tests tests))) From 5e3e6f29c58c1426a39ecf665d2cc0e31eec7891 Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:07:38 +0000 Subject: [PATCH 4/6] inline *all* non-recursive definitions (WIP from today's meeting) --- qi-lib/flow/core/compiler/0005-inline.rkt | 16 +++++++-- qi-test/tests/compiler/rules.rkt | 2 ++ qi-test/tests/compiler/rules/inlining.rkt | 40 ++++++++++++++++------- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/qi-lib/flow/core/compiler/0005-inline.rkt b/qi-lib/flow/core/compiler/0005-inline.rkt index b11796792..e2215a6f3 100644 --- a/qi-lib/flow/core/compiler/0005-inline.rkt +++ b/qi-lib/flow/core/compiler/0005-inline.rkt @@ -5,6 +5,7 @@ (require (for-syntax racket/base syntax/parse + syntax/id-table "../strategy.rkt" "../private/form-property.rkt") "../passes.rkt") @@ -25,15 +26,24 @@ (raise-syntax-error #f "set! not allowed!")])))) (begin-for-syntax - (define (inline-rewrite stx) + (define ((inline-rewrite already-inlined-table) stx) (syntax-parse stx #:datum-literals (#%host-expression esc) [(esc (#%host-expression id)) #:declare id (static flowdef? "flow name") + #:fail-when (free-id-table-ref already-inlined-table + #'id + #false) #false ;; def is now bound to the flowdef struct instance (define def (attribute id.value)) - (syntax-property (flowdef-def def) + (define already-inlined-table* + (free-id-table-set already-inlined-table + #'id + #true)) + (syntax-property (find-and-map/qi + (inline-rewrite already-inlined-table*) + (flowdef-def def)) 'qi-do-not-recurse #t)] [_ stx])) @@ -42,7 +52,7 @@ (attach-form-property (find-and-map/qi ;; "knapsack" problem - inline-rewrite ; don't use fixed-point finding + (inline-rewrite (make-immutable-free-id-table)) ; don't use fixed-point finding ;; check if identifier ;; check if bound to flowdef struct ;; then do inlining diff --git a/qi-test/tests/compiler/rules.rkt b/qi-test/tests/compiler/rules.rkt index 873b459f5..2286ce5cc 100644 --- a/qi-test/tests/compiler/rules.rkt +++ b/qi-test/tests/compiler/rules.rkt @@ -5,6 +5,7 @@ (require rackunit rackunit/text-ui (prefix-in normalize: "rules/normalize.rkt") + (prefix-in inline: "rules/inlining.rkt") (prefix-in deforest: "rules/deforest.rkt") (prefix-in full-cycle: "rules/full-cycle.rkt")) @@ -15,6 +16,7 @@ "Compiler rule tests" normalize:tests + inline:tests deforest:tests full-cycle:tests)) diff --git a/qi-test/tests/compiler/rules/inlining.rkt b/qi-test/tests/compiler/rules/inlining.rkt index 243f89a05..bc3223231 100644 --- a/qi-test/tests/compiler/rules/inlining.rkt +++ b/qi-test/tests/compiler/rules/inlining.rkt @@ -13,7 +13,8 @@ (submod qi/flow/extended/expander invoke) qi/flow/core/compiler (for-template qi/flow/core/compiler) - qi/on) + qi/on + qi/flow) ;; NOTE: we may need to tag test syntax with `tag-form-syntax` ;; in some cases. See the comment on that function definition. @@ -32,16 +33,33 @@ (test-suite "inlining" - (test-suite - "does not inline and enter infinite loop" - (test-true "does not enter infinite loop" - (runs-within-time? - (thunk - (expand - #'(let () - (define-flow f (if odd? (~> add1 f) _)) - (f 4)))) - 1.0))))) + (test-true "does not enter infinite loop" + (runs-within-time? + (thunk + (expand + #'(let () + (define-flow f (if odd? (~> add1 f) _)) + (f 4)))) + 1.0)) + (test-equal? "does inline occurrences in sequence" + (caddr + (syntax->datum + (expand + #'(let () + (define-flow f (if odd? (~> add1 f f) _)) + (f 4))))) + (caddr + (syntax->datum + (expand + #'(let () + (define flow:f + (flow + (if odd? + (~> add1 + (if odd? (~> add1 flow:f flow:f) _) + (if odd? (~> add1 flow:f flow:f) _)) + _))) + (flow:f 4)))))))) (module+ main (void From 5f4d124b90520f046ddedd0f8542c8ea1a996c5d Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:07:38 +0000 Subject: [PATCH 5/6] delay define-flow expansion --- qi-lib/flow/core/compiler/0005-inline.rkt | 5 +++-- qi-lib/on.rkt | 6 +++--- qi-test/tests/compiler/rules/inlining.rkt | 11 ++++++++++- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/qi-lib/flow/core/compiler/0005-inline.rkt b/qi-lib/flow/core/compiler/0005-inline.rkt index e2215a6f3..ef299aa90 100644 --- a/qi-lib/flow/core/compiler/0005-inline.rkt +++ b/qi-lib/flow/core/compiler/0005-inline.rkt @@ -4,6 +4,7 @@ flowdef)) (require (for-syntax racket/base + racket/promise syntax/parse syntax/id-table "../strategy.rkt" @@ -11,7 +12,7 @@ "../passes.rkt") (begin-for-syntax - (struct flowdef (name def) + (struct flowdef (name expanded) #:transparent #:property prop:set!-transformer (λ (flowdef-instance stx) @@ -43,7 +44,7 @@ #true)) (syntax-property (find-and-map/qi (inline-rewrite already-inlined-table*) - (flowdef-def def)) + (force (flowdef-expanded def))) 'qi-do-not-recurse #t)] [_ stx])) diff --git a/qi-lib/on.rkt b/qi-lib/on.rkt index 85bf2a91c..ba68327b9 100644 --- a/qi-lib/on.rkt +++ b/qi-lib/on.rkt @@ -10,7 +10,8 @@ (for-syntax racket/base syntax/parse/lib/function-header "flow/aux-syntax.rkt" - racket/syntax) + racket/syntax + racket/promise) (only-in "flow/core/compiler/0005-inline.rkt" flowdef) "flow.rkt" @@ -50,9 +51,8 @@ clause))] [(_ name:id clause:clause) #:with new-name (format-id #'hi "flow:~a" #'name) - #:with expanded-flow (expand-flow #'clause) #'(begin (define-syntax name - (flowdef #'new-name #'expanded-flow)) + (flowdef #'new-name (delay (expand-flow #'clause)))) (define new-name (flow clause)))]) diff --git a/qi-test/tests/compiler/rules/inlining.rkt b/qi-test/tests/compiler/rules/inlining.rkt index bc3223231..4a6ece1b5 100644 --- a/qi-test/tests/compiler/rules/inlining.rkt +++ b/qi-test/tests/compiler/rules/inlining.rkt @@ -6,6 +6,7 @@ rackunit rackunit/text-ui racket/function + racket/math ;; necessary to recognize and expand core forms correctly qi/flow/extended/expander ;; necessary to correctly expand the right-threading form @@ -14,6 +15,7 @@ qi/flow/core/compiler (for-template qi/flow/core/compiler) qi/on + qi/macro qi/flow) ;; NOTE: we may need to tag test syntax with `tag-form-syntax` @@ -59,7 +61,14 @@ (if odd? (~> add1 flow:f flow:f) _) (if odd? (~> add1 flow:f flow:f) _)) _))) - (flow:f 4)))))))) + (flow:f 4)))))) + (test-equal? "support macros defined after the flow" + (let () + (define-flow f (~> (pare sqr +) ▽)) + (define-qi-syntax-rule (pare car-flo cdr-flo) + (group 1 car-flo cdr-flo)) + (on (3 6 9) f)) + '(9 15)))) (module+ main (void From 2c08412334841d674080ece031cac9bab3a76d90 Mon Sep 17 00:00:00 2001 From: eutro Date: Fri, 31 Oct 2025 20:07:38 +0000 Subject: [PATCH 6/6] try to fix 'disappeared-use -es --- qi-lib/flow/core/compiler/0005-inline.rkt | 13 +- qi-lib/flow/core/compiler/1000-qi0.rkt | 242 +++++++++++----------- qi-test/tests/compiler/rules/inlining.rkt | 66 +++++- 3 files changed, 190 insertions(+), 131 deletions(-) diff --git a/qi-lib/flow/core/compiler/0005-inline.rkt b/qi-lib/flow/core/compiler/0005-inline.rkt index ef299aa90..8ebbbe9bc 100644 --- a/qi-lib/flow/core/compiler/0005-inline.rkt +++ b/qi-lib/flow/core/compiler/0005-inline.rkt @@ -5,6 +5,7 @@ (require (for-syntax racket/base racket/promise + racket/syntax syntax/parse syntax/id-table "../strategy.rkt" @@ -29,6 +30,7 @@ (begin-for-syntax (define ((inline-rewrite already-inlined-table) stx) (syntax-parse stx + #:track-literals #:datum-literals (#%host-expression esc) [(esc (#%host-expression id)) @@ -38,15 +40,16 @@ #false) #false ;; def is now bound to the flowdef struct instance (define def (attribute id.value)) + (define inlinable-code (force (flowdef-expanded def))) (define already-inlined-table* (free-id-table-set already-inlined-table #'id #true)) - (syntax-property (find-and-map/qi - (inline-rewrite already-inlined-table*) - (force (flowdef-expanded def))) - 'qi-do-not-recurse - #t)] + (define inlined-def + (find-and-map/qi + (inline-rewrite already-inlined-table*) + inlinable-code)) + (syntax-property inlined-def 'qi-do-not-recurse #t)] [_ stx])) (define-and-register-pass 5 (inline-pass stx) diff --git a/qi-lib/flow/core/compiler/1000-qi0.rkt b/qi-lib/flow/core/compiler/1000-qi0.rkt index 15ae6925f..0e559c869 100644 --- a/qi-lib/flow/core/compiler/1000-qi0.rkt +++ b/qi-lib/flow/core/compiler/1000-qi0.rkt @@ -22,125 +22,129 @@ ;; this is a macro so it receives the entire expression ;; (qi0->racket ...). We use cadr here to parse the ;; contained expression. - (syntax-parse (cadr (syntax->list stx)) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;;;; Core language forms ;;;; - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - ;; A note regarding symbolic aliases like ~>, ⏚ and △: - ;; - ;; These aren't technically part of the core language - ;; as they aren't directly part of the syntax - ;; spec in the expander (which includes only, e.g., - ;; thread and tee and so on). Instead, they are simply - ;; aliased at the module level there when provided. - ;; Yet, during code generation in the present module, - ;; it's more convenient to express expansions - ;; using these symbolic aliases, and that's the - ;; reason we retain these in the patterns below. As - ;; these patterns are matched as _datum literals_, - ;; it doesn't matter that they aren't actually the - ;; literal core forms declared in the expander. - - [((~datum gen) ex:expr ...) - #'(λ _ (values ex ...))] - ;; pass-through (identity flow) - [(~datum _) #'values] - ;; routing - [(~or* (~datum ⏚) (~datum ground)) ; NOTE: technically not core - #'(qi0->racket (select))] - [((~or* (~datum ~>) (~datum thread)) onex:clause ...) - #`(compose . #,(reverse - (syntax->list - #'((qi0->racket onex) ...))))] - [e:relay-form (relay-parser #'e)] - [e:tee-form (tee-parser #'e)] - ;; map and filter - [e:amp-form (amp-parser #'e)] ; NOTE: technically not core - [e:pass-form (pass-parser #'e)] ; NOTE: technically not core - ;; prisms - [e:sep-form (sep-parser #'e)] - [(~or* (~datum ▽) (~datum collect)) - #'list] - ;; predicates - [(~or* (~datum NOT) (~datum !)) - #'not] - [(~datum XOR) - #'parity-xor] - [((~datum and) onex:clause ...) - #'(conjoin (qi0->racket onex) ...)] - [((~datum or) onex:clause ...) - #'(disjoin (qi0->racket onex) ...)] - [((~datum not) onex:clause) ; NOTE: technically not core - #'(negate (qi0->racket onex))] - [((~datum all) onex:clause) - #`(give (curry andmap (qi0->racket onex)))] - [((~datum any) onex:clause) - #'(give (curry ormap (qi0->racket onex)))] - - ;; selection - [e:select-form (select-parser #'e)] - [e:block-form (block-parser #'e)] - [e:group-form (group-parser #'e)] - ;; conditionals - [e:if-form (if-parser #'e)] - [e:sieve-form (sieve-parser #'e)] - [e:partition-form (partition-parser #'e)] - ;; exceptions - [e:try-form (try-parser #'e)] - ;; folds - [e:fold-left-form (fold-left-parser #'e)] - [e:fold-right-form (fold-right-parser #'e)] - ;; high-level routing - [e:fanout-form (fanout-parser #'e)] - ;; looping - [e:feedback-form (feedback-parser #'e)] - [e:loop-form (loop-parser #'e)] - [((~datum loop2) pred:clause mapex:clause combex:clause) - #'(letrec ([loop2 (qi0->racket (if pred - (~> (== (-< (esc cdr) - (~> (esc car) mapex)) _) - (group 1 _ combex) - (esc loop2)) - (select 2)))]) - loop2)] - ;; towards universality - [(~datum appleye) - #'call] - [e:clos-form (clos-parser #'e)] - [e:deforestable-form (deforestable-parser #'e)] - ;; escape hatch for racket expressions or anything - ;; to be "passed through" - [((~datum esc) ex:expr) - #'ex] - - ;;; Miscellaneous - - ;; Partial application with syntactically pre-supplied arguments - ;; in a blanket template - [((~datum #%blanket-template) e) - (blanket-template-form-parser this-syntax)] - - ;; Fine-grained template-based application - ;; This handles templates that indicate a specific number of template - ;; variables (i.e. expected arguments). The semantics of template-based - ;; application here is fulfilled by the fancy-app module. In order to use - ;; it, we simply use the #%app macro provided by fancy-app instead of the - ;; implicit one used for function application in racket/base. - ;; "prarg" = "pre-supplied argument" - [((~datum #%fine-template) (prarg-pre ... (~datum _) prarg-post ...)) - #'(fancy:#%app prarg-pre ... _ prarg-post ...)] - - ;; If in the course of optimization we ever end up with a fully - ;; simplified host expression, the compiler would a priori reject it as - ;; not being a core Qi expression. So we add this extra rule here - ;; to simply pass this expression through. - ;; TODO: should `#%host-expression` be formally declared as being part - ;; of the core language by including it in the syntax-spec grammar - ;; in extended/expander.rkt? - [((~datum #%host-expression) hex) - this-syntax])) + (define argument-stx (cadr (syntax->list stx))) + (syntax-track-origin + (syntax-parse argument-stx + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;;;; Core language forms ;;;; + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; A note regarding symbolic aliases like ~>, ⏚ and △: + ;; + ;; These aren't technically part of the core language + ;; as they aren't directly part of the syntax + ;; spec in the expander (which includes only, e.g., + ;; thread and tee and so on). Instead, they are simply + ;; aliased at the module level there when provided. + ;; Yet, during code generation in the present module, + ;; it's more convenient to express expansions + ;; using these symbolic aliases, and that's the + ;; reason we retain these in the patterns below. As + ;; these patterns are matched as _datum literals_, + ;; it doesn't matter that they aren't actually the + ;; literal core forms declared in the expander. + + [((~datum gen) ex:expr ...) + #'(λ _ (values ex ...))] + ;; pass-through (identity flow) + [(~datum _) #'values] + ;; routing + [(~or* (~datum ⏚) (~datum ground)) ; NOTE: technically not core + #'(qi0->racket (select))] + [((~or* (~datum ~>) (~datum thread)) onex:clause ...) + #`(compose . #,(reverse + (syntax->list + #'((qi0->racket onex) ...))))] + [e:relay-form (relay-parser #'e)] + [e:tee-form (tee-parser #'e)] + ;; map and filter + [e:amp-form (amp-parser #'e)] ; NOTE: technically not core + [e:pass-form (pass-parser #'e)] ; NOTE: technically not core + ;; prisms + [e:sep-form (sep-parser #'e)] + [(~or* (~datum ▽) (~datum collect)) + #'list] + ;; predicates + [(~or* (~datum NOT) (~datum !)) + #'not] + [(~datum XOR) + #'parity-xor] + [((~datum and) onex:clause ...) + #'(conjoin (qi0->racket onex) ...)] + [((~datum or) onex:clause ...) + #'(disjoin (qi0->racket onex) ...)] + [((~datum not) onex:clause) ; NOTE: technically not core + #'(negate (qi0->racket onex))] + [((~datum all) onex:clause) + #`(give (curry andmap (qi0->racket onex)))] + [((~datum any) onex:clause) + #'(give (curry ormap (qi0->racket onex)))] + + ;; selection + [e:select-form (select-parser #'e)] + [e:block-form (block-parser #'e)] + [e:group-form (group-parser #'e)] + ;; conditionals + [e:if-form (if-parser #'e)] + [e:sieve-form (sieve-parser #'e)] + [e:partition-form (partition-parser #'e)] + ;; exceptions + [e:try-form (try-parser #'e)] + ;; folds + [e:fold-left-form (fold-left-parser #'e)] + [e:fold-right-form (fold-right-parser #'e)] + ;; high-level routing + [e:fanout-form (fanout-parser #'e)] + ;; looping + [e:feedback-form (feedback-parser #'e)] + [e:loop-form (loop-parser #'e)] + [((~datum loop2) pred:clause mapex:clause combex:clause) + #'(letrec ([loop2 (qi0->racket (if pred + (~> (== (-< (esc cdr) + (~> (esc car) mapex)) _) + (group 1 _ combex) + (esc loop2)) + (select 2)))]) + loop2)] + ;; towards universality + [(~datum appleye) + #'call] + [e:clos-form (clos-parser #'e)] + [e:deforestable-form (deforestable-parser #'e)] + ;; escape hatch for racket expressions or anything + ;; to be "passed through" + [((~datum esc) ex:expr) + #'ex] + + ;;; Miscellaneous + + ;; Partial application with syntactically pre-supplied arguments + ;; in a blanket template + [((~datum #%blanket-template) e) + (blanket-template-form-parser this-syntax)] + + ;; Fine-grained template-based application + ;; This handles templates that indicate a specific number of template + ;; variables (i.e. expected arguments). The semantics of template-based + ;; application here is fulfilled by the fancy-app module. In order to use + ;; it, we simply use the #%app macro provided by fancy-app instead of the + ;; implicit one used for function application in racket/base. + ;; "prarg" = "pre-supplied argument" + [((~datum #%fine-template) (prarg-pre ... (~datum _) prarg-post ...)) + #'(fancy:#%app prarg-pre ... _ prarg-post ...)] + + ;; If in the course of optimization we ever end up with a fully + ;; simplified host expression, the compiler would a priori reject it as + ;; not being a core Qi expression. So we add this extra rule here + ;; to simply pass this expression through. + ;; TODO: should `#%host-expression` be formally declared as being part + ;; of the core language by including it in the syntax-spec grammar + ;; in extended/expander.rkt? + [((~datum #%host-expression) hex) + this-syntax]) + argument-stx + #'qi0->racket)) #| diff --git a/qi-test/tests/compiler/rules/inlining.rkt b/qi-test/tests/compiler/rules/inlining.rkt index 4a6ece1b5..ecd6f9868 100644 --- a/qi-test/tests/compiler/rules/inlining.rkt +++ b/qi-test/tests/compiler/rules/inlining.rkt @@ -6,6 +6,7 @@ rackunit rackunit/text-ui racket/function + racket/list racket/math ;; necessary to recognize and expand core forms correctly qi/flow/extended/expander @@ -30,6 +31,34 @@ (kill-thread handle) ; no-op if already dead (not (not result))) +(define (all-disappeared-uses stx) + (define uses '()) + (let loop ([stx stx]) + (define stx-e + (cond + [(syntax? stx) + (define stx-uses (syntax-property stx 'disappeared-use)) + (when stx-uses (set! uses (cons stx-uses uses))) + (syntax-e stx)] + [else stx])) + (cond + [(list? stx-e) + (for-each loop stx-e)] + [(pair? stx-e) + (loop (car stx-e)) + (loop (cdr stx-e))] + [(vector? stx-e) + (for ([substx (in-vector stx-e)]) + (loop substx))] + [else (void)])) + (flatten uses)) + +(define (datum-identifier=? left right) + (eq? (syntax-e left) (syntax-e right))) + +(define (datum-member? elt elts) + (member elt elts datum-identifier=?)) + (define tests (test-suite @@ -56,19 +85,42 @@ #'(let () (define flow:f (flow - (if odd? - (~> add1 - (if odd? (~> add1 flow:f flow:f) _) - (if odd? (~> add1 flow:f flow:f) _)) - _))) + (if odd? + (~> add1 + (if odd? (~> add1 flow:f flow:f) _) + (if odd? (~> add1 flow:f flow:f) _)) + _))) (flow:f 4)))))) - (test-equal? "support macros defined after the flow" + ;; this ensures that expansion of flow definitions is deferred at + ;; least until the rest of the definition context is partially + ;; expanded + (test-equal? "supports macros defined after the flow" (let () (define-flow f (~> (pare sqr +) ▽)) (define-qi-syntax-rule (pare car-flo cdr-flo) (group 1 car-flo cdr-flo)) (on (3 6 9) f)) - '(9 15)))) + '(9 15)) + ;; this ensures that even if the flow is inlined, DrRacket still tracks + ;; the usage + (test-check "adds 'disappeared-use property" + datum-member? + #'my-flow + (all-disappeared-uses + (expand #'(let () + (define-flow my-flow +) + (flow my-flow))))) + (test-equal? "inlining does not mess up bindings 1" + (let () + (define-flow f (~> (as x) (gen 0))) + (on (1) (~> (as x) (gen 2) f (gen x)))) + 1) + (test-equal? "inlining does not mess up bindings 2" + (let () + (define x 2) + (define-flow f (gen x)) + (on (1) (~> (as x) f))) + 2))) (module+ main (void