Skip to content
This repository has been archived by the owner on Jul 7, 2024. It is now read-only.

Commit

Permalink
Pattern match against some common recursion patterns
Browse files Browse the repository at this point in the history
Currently we match against:

 - (cond [X RECUR] [true Y]) => While
 - (cond [X Y] [true RECUR]) => While not
 - Anything else => Forever + breaks

This thankfully is sufficient to catch the most "interesting" loops, such
as for and while. It is worth noting that this only works when we've
run the optimiser as that strips useless assignments. I'm not too worried
about that though.
  • Loading branch information
SquidDev committed Jun 7, 2017
1 parent 3d9ba73 commit 14e2df1
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 9 deletions.
63 changes: 61 additions & 2 deletions urn/analysis/tag/categories.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
(with (recur { :var var :def def })
(visit-nodes lookup state def 3 true nil recur)
(unless (.> recur :tail) (error! "Expected tail recursive function from letrec"))
(.<! lookup def (cat "lambda" :recur recur)))
(.<! lookup def (cat "lambda" :recur (visit-recur lookup recur))))
(visit-node lookup state def true)))
(cat "set!")]
[(= func (.> builtins :quote))
Expand All @@ -162,7 +162,7 @@
(with (recur { :var (.> node :defVar) :def def})
(visit-nodes lookup state def 3 true nil recur)
(.<! lookup def (if (.> recur :tail)
(cat "lambda" :recur recur)
(cat "lambda" :recur (visit-recur lookup recur))
(cat "lambda"))))
(visit-node lookup state def true)))
(cat "define")]
Expand Down Expand Up @@ -404,6 +404,65 @@
(.<! lookup node (cat "quote-list")))
(.<! lookup node (cat "quote-const"))))

(defun visit-recur (lookup recur)
"Attempts to categorise a recursive context."
(with (lam (.> recur :def))
(cond
[(and
;; Check the lambda only has one expression
(= (n lam) 3)
(with (child (nth lam 3))
(and
;; And that expression is a condition of the form (cond [EXPR X] [true Y])
(list? child) (builtin? (car child) :cond) (= (n child) 3)
(builtin? (car (nth child 3)) :true)
(! (.> lookup (car (nth child 2)) :stmt)))))

(let* [(fst-case (nth (nth lam 3) 2))
(snd-case (nth (nth lam 3) 3))

(fst (and (>= (n fst-case) 2) (just-recur? lookup (last fst-case) recur)))
(snd (and (>= (n snd-case) 2) (just-recur? lookup (last snd-case) recur)))]
(cond
[(and fst snd) (.<! recur :category "forever")]
[fst (.<! recur :category "while")]
[snd (.<! recur :category "while-not")]
[true (.<! recur :category "forever")]))]

[true
(.<! recur :category "forever")]))

recur)

(defun just-recur? (lookup node recur)
"Determine whether NODE is just calls to the recursive context RECUR."
(if (list? node)
(let [(cat (.> lookup node))
(head (car node))]
(cond
;; If we're a tail call then we're obviously not an exit.
[(= (.> cat :category) "call-tail")
(when (/= (.> cat :recur) recur) (error! "Incorrect recur"))
true]

;; If we're a lambda then check the last node is an exit node.
[(and (list? head) (builtin? (car head) :lambda))
(and (>= (n head) 3) (just-recur? lookup (last node) recur))]

;; If we're a condition, then check if any node is an exit node.
[(builtin? head :cond)
(with (found true)
(for i 2 (n node) 1
(when found
;; Something is an exit if the case is empty or contains an exit.
(with (case (nth node i))
(set! found (and (>= (n case) 2) (just-recur? lookup (last case) recur))) head)))
found)]

;; If we're
[true false]))
false))

(defpass categorise-nodes (compiler nodes state)
"Categorise a group of NODES, annotating their appropriate node type."
:cat '("categorise")
Expand Down
36 changes: 29 additions & 7 deletions urn/backend/lua/emit.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@

(w/end-block! out "end")))))

(when (.> cat :recur) (w/begin-block! out "while true do"))
(compile-block node out state 3 "return ")
(when (.> cat :recur) (w/end-block! out "end"))
(if (.> cat :recur)
(compile-recur (.> cat :recur) out state "return ")
(compile-block node out state 3 "return "))
(w/unindent! out)
(for-each arg args (pop-escape-var! (.> arg :var) state))
(w/append! out "end)")))]
Expand Down Expand Up @@ -447,11 +447,9 @@

;; Wrap non-returning loops in an implicit lambda - this is really ugly
;; but it means we don't have to handle "break"s.
(w/begin-block! out "while true do")
(if (= ret "return ")
(compile-block head out state 3 "return ")
(compile-block head out state 3 ret (.> cat :recur)))
(w/end-block! out "end")
(compile-recur (.> cat :recur) out state "return ")
(compile-recur (.> cat :recur) out state ret (.> cat :recur)))

(for-each arg args (pop-escape-var! (.> arg :var) state)))]

Expand Down Expand Up @@ -649,6 +647,30 @@
(w/append! out ")")
true)))

(defun compile-recur (recur out state ret break)
"Compile a recursive lambda."
(case (.> recur :category)
["while"
(with (node (nth (.> recur :def) 3))
(w/append! out "while ")
(compile-expression (car (nth node 2)) out state)
(w/begin-block! out " do")
(compile-block (nth node 2) out state 2 ret break)
(w/end-block! out "end")
(compile-block (nth node 3) out state 2 ret))]
["while-not"
(with (node (nth (.> recur :def) 3))
(w/append! out "while not (")
(compile-expression (car (nth node 2)) out state)
(w/begin-block! out ") do")
(compile-block (nth node 3) out state 2 ret break)
(w/end-block! out "end")
(compile-block (nth node 2) out state 2 ret))]
["forever"
(w/begin-block! out "while true do")
(compile-block (.> recur :def) out state 3 ret break)
(w/end-block! out "end")]))

(defun compile-block (nodes out state start ret break)
"Compile a block of expressions."
:hidden
Expand Down

0 comments on commit 14e2df1

Please sign in to comment.