optimize.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. ;;; WebAssembly assembler
  2. ;;; Copyright (C) 2023, 2024 Igalia, S.L.
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; Optimizer for WebAssembly.
  18. ;;;
  19. ;;; We rely on most of the main optimizations to be done by Tree-IL
  20. ;;; (e.g. partial evaluation) and CPS (e.g. CSE, DCE) phases. However
  21. ;;; there are some specificities about the WebAssembly target that are
  22. ;;; better dealt with on the WebAssembly level.
  23. ;;;
  24. ;;; Notably, when emitting CPS, it is most natural to emit loads and
  25. ;;; stores from named locations, for example virtual machine registers.
  26. ;;; But WebAssembly really wants to have more implicit data flow via its
  27. ;;; stack machine. Of course, these are just two ways of encoding the
  28. ;;; same thing, but to produce small WebAssembly files with a minimal
  29. ;;; set of locals (and a minimal amount of local.get and local.set), we
  30. ;;; have this explicit low-level pass.
  31. ;;;
  32. ;;; The basic idea of this pass is to parse WebAssembly functions (and
  33. ;;; other expressions) to a sequence of *packets* that take their
  34. ;;; arguments and define their results directly from and to named
  35. ;;; locals. This eliminates stack effects, facilitating reordering of
  36. ;;; packets. Each packet is left with some set of read, write, and
  37. ;;; control effects; we can use these effects to determine when it is
  38. ;;; permissible to swap two adjacent packets. The optimization comes in
  39. ;;; a pass that attempts to coalesce packets with a greedy bottom-up
  40. ;;; algorithm, reordering as necessary and possible. Coalescing two
  41. ;;; packets eliminates defs from a predecessor and uses from a
  42. ;;; successor, reducing the total number of locals, and allowing for
  43. ;;; data to flow on the stack instead of through locals. Finally, we
  44. ;;; lower packets back to a sequence of wasm instructions,
  45. ;;; re-introducing local.get / local.set terms and computing the set of
  46. ;;; needed locals for the function.
  47. ;;;
  48. ;;; Code:
  49. (define-module (wasm optimize)
  50. #:use-module (ice-9 match)
  51. #:use-module ((srfi srfi-1) #:select (append-map fold fold-right))
  52. #:use-module (srfi srfi-9)
  53. #:use-module (wasm effects)
  54. #:use-module (wasm stack)
  55. #:use-module (wasm symbolify)
  56. #:use-module (wasm types)
  57. #:export (optimize-wasm))
  58. (define-record-type <packet>
  59. (make-packet code uses defs effect)
  60. packet?
  61. (code packet-code) ; list of inst
  62. (uses packet-uses) ; list of local id, <func-sig> order
  63. (defs packet-defs) ; list of local id, <func-sig> order
  64. (effect packet-effect)) ; <effect>
  65. (define (optimize-locals wasm)
  66. (define (symbol-list->hashq syms)
  67. (define table (make-hash-table))
  68. (for-each (lambda (name)
  69. (unless (symbol? name) (error "unnamed local"))
  70. (hashq-set! table name #t))
  71. syms)
  72. table)
  73. (define* (make-gensym names #:optional (stem "tmp"))
  74. (define counter 0)
  75. (define (gensym)
  76. (let ((sym (string->symbol (format #f "~a~a" stem counter))))
  77. (set! counter (1+ counter))
  78. (if (hashq-ref names sym)
  79. (gensym)
  80. sym)))
  81. gensym)
  82. (define (parse-packets body ctx local-types gensym)
  83. (define (introduce-local! val-type)
  84. (let ((id (gensym)))
  85. (hashq-set! local-types id val-type)
  86. id))
  87. (define (block-entry-packet param-types)
  88. (make-packet '() '() (map introduce-local! param-types) nofx))
  89. (define (block-exit-packet ctx stack)
  90. (let* ((effect (fallthrough-stack-effect ctx))
  91. (ctx (apply-stack-effect ctx effect)))
  92. (cond
  93. ((unreachable-ctx? ctx)
  94. (make-packet '() '() '() nofx))
  95. ((invalid-ctx? ctx)
  96. (error "validation error: " (invalid-ctx-reason ctx)))
  97. (else
  98. (make-packet '() (reverse stack) '() nofx)))))
  99. (define (visit-body body ctx stack)
  100. (match body
  101. (() (list (block-exit-packet ctx stack)))
  102. ((('local.tee id) . body)
  103. (visit-body `((local.set ,id) (local.get ,id) . body) ctx stack))
  104. ((inst . body)
  105. (call-with-values (lambda () (visit-inst inst ctx stack))
  106. (lambda (packet ctx stack)
  107. (cons packet (visit-body body ctx stack)))))))
  108. (define (%visit-block kind label param-types result-types body ctx)
  109. (let* ((entry (block-entry-packet param-types))
  110. (ctx (push-block ctx label kind param-types result-types))
  111. (stack (match (packet-defs entry)
  112. (((id . type) ...) (reverse id)))))
  113. (cons entry (visit-body body ctx stack))))
  114. (define (visit-block kind label type body ctx)
  115. (match type
  116. (($ <type-use> _ ($ <func-sig> (($ <param> _ params) ...) results))
  117. (%visit-block kind label params results body ctx))))
  118. (define (visit-catch tag-id try-label try-type body ctx)
  119. (match (lookup-tag ctx tag-id)
  120. (($ <tag-type>
  121. _ ($ <type-use> _ ($ <func-sig> (($ <param> _ tag-params)) ())))
  122. (match try-type
  123. (($ <type-use> _ ($ <func-sig> try-params try-results))
  124. (%visit-block 'catch try-label tag-params try-results body
  125. ctx))))))
  126. (define (visit-catch-all try-label try-type body ctx)
  127. (match try-type
  128. (($ <type-use> _ ($ <func-sig> try-params try-results))
  129. (%visit-block 'catch-all try-label '() try-results body ctx))))
  130. (define (visit-inst inst ctx stack)
  131. (define stack-effect (compute-stack-effect ctx inst))
  132. (define effect (compute-effect inst))
  133. (define params (stack-effect-params stack-effect))
  134. (define results (or (stack-effect-results stack-effect) '()))
  135. (match (apply-stack-effect ctx stack-effect)
  136. (($ <invalid-ctx> reason)
  137. (error "validation error: " reason))
  138. ((and ctx ($ <unreachable-ctx>))
  139. (values (make-packet '() '() '() nofx) ctx stack))
  140. (ctx*
  141. (define uses (reverse (list-head stack (length params))))
  142. (define defs (map introduce-local! results))
  143. (define stack* (fold cons (list-tail stack (length params)) defs))
  144. (define packet
  145. (match inst
  146. (('local.get id)
  147. (unless (null? uses) (error "unexpected" inst))
  148. (make-packet '() `(,id) defs effect))
  149. (('local.set id)
  150. (unless (null? defs) (error "unexpected" inst))
  151. (make-packet '() uses `(,id) effect))
  152. (_
  153. (define code
  154. (list
  155. (match inst
  156. (('block label type body)
  157. `(block ,label ,type
  158. ,(visit-block 'block label type body ctx)))
  159. (('loop label type body)
  160. `(loop ,label ,type
  161. ,(visit-block 'loop label type body ctx)))
  162. (('if label type consequent alternate)
  163. `(if ,label ,type
  164. ,(visit-block 'if label type consequent ctx)
  165. ,(visit-block 'if label type alternate ctx)))
  166. (('try label type body catches catch-all)
  167. `(try ,label ,type
  168. ,(visit-block 'try label type body ctx)
  169. ,(map
  170. (match-lambda
  171. ((tag-id . body)
  172. (visit-catch tag-id label type body ctx)))
  173. catches)
  174. ,(and catch-all
  175. (visit-catch-all label type body ctx))))
  176. (('try_delegate label type body handler)
  177. `(try_delegate ,label ,type
  178. ,(visit-block 'try label type body ctx)
  179. ,handler))
  180. (_ inst))))
  181. (make-packet code uses defs effect))))
  182. (values packet ctx* stack*))))
  183. (visit-body body ctx '()))
  184. (define (schedule-packets packets)
  185. ;; Not yet implemented.
  186. ;;
  187. ;; Sketch: For each packet from last to first, reorder uses, then
  188. ;; attempt coalesce. To reorder uses, visit each use in the packet
  189. ;; in stack order. Is var used just once? If so, find packet that
  190. ;; def of that var. Try to reorder it forwards. If it reaches the
  191. ;; packet, merge packets: union the effects, cancel defs/uses at
  192. ;; boundary, append uses of first packet, append code.
  193. packets)
  194. (define (lower-packets packets local-types)
  195. (define used-locals (make-hash-table))
  196. (define (record-local! id)
  197. (hashq-set! used-locals id #t)
  198. id)
  199. (define (lower-inst inst)
  200. (match inst
  201. (('block label type body)
  202. `(block ,label ,type ,(lower-body body)))
  203. (('loop label type body)
  204. `(loop ,label ,type ,(lower-body body)))
  205. (('if label type consequent alternate)
  206. `(if ,label ,type
  207. ,(lower-body consequent)
  208. ,(lower-body alternate)))
  209. (('try label type body catches catch-all)
  210. `(try ,label ,type
  211. ,(lower-body body)
  212. ,(map lower-body catches)
  213. ,(and=> catch-all lower-body)))
  214. (('try_delegate label type body handler)
  215. `(try_delegate ,label ,type
  216. ,(lower-body body)
  217. ,handler))
  218. (inst inst)))
  219. (define (lower-body packets)
  220. (define (local.get id)
  221. `(local.get ,(record-local! id)))
  222. (define (local.set id)
  223. `(local.get ,(record-local! id)))
  224. (fold-right
  225. (lambda (packet out)
  226. (match packet
  227. ((($ <packet> code uses defs fx) . packets)
  228. (fold local.get
  229. (fold-right (lambda (inst out)
  230. (cons (lower-inst inst) out))
  231. code
  232. (fold local.set
  233. (lower-body packets)
  234. defs))
  235. uses))))
  236. '() packets))
  237. (define (build-locals)
  238. (define locals-by-type (make-hash-table))
  239. (define (add-local id type)
  240. (hash-set! locals-by-type type
  241. (cons id (hash-ref locals-by-type type '()))))
  242. (hash-for-each (lambda (id val)
  243. (match (hashq-ref local-types id)
  244. (#f
  245. ;; A local.ref / local.set to a param.
  246. #f)
  247. (type
  248. (add-local id type))))
  249. used-locals)
  250. (define (symbol<? a b)
  251. (string<? (symbol->string a) (symbol->string b)))
  252. (define (type<? t1 t2)
  253. (cond
  254. ((symbol? t1)
  255. (if (symbol? t2)
  256. (symbol<? t1 t2)
  257. #t))
  258. ((symbol? t2) #f)
  259. (else
  260. (match t1
  261. (($ <ref-type> nullable?1 ht1)
  262. (match t2
  263. (($ <ref-type> nullable?2 ht2)
  264. (if (eq? ht1 ht2)
  265. nullable?2
  266. (symbol<? ht1 ht2)))))))))
  267. (append-map
  268. cdr
  269. (sort
  270. (hash-map->list
  271. (lambda (type ids)
  272. (cons type
  273. (map (lambda (id)
  274. (make-local id type))
  275. (sort ids symbol<?))))
  276. locals-by-type)
  277. (lambda (a b)
  278. (match a ((t1 . _) (match b ((t2 . _) (type<? t1 t2)))))))))
  279. (let* ((code (lower-body packets))
  280. (locals (build-locals)))
  281. (values locals code)))
  282. (define (optimize-func port func)
  283. (match func
  284. (($ <func> id type (($ <local> lid ltype) ...) body)
  285. (define gensym
  286. (make-gensym (symbol-list->hashq lid)))
  287. (define param-ids
  288. (map param-id (func-sig-params (type-use-sig type))))
  289. (define local-types (make-hash-table))
  290. (for-each (lambda (id type)
  291. (hashq-set! local-types id type))
  292. lid ltype)
  293. (call-with-values (lambda ()
  294. (lower-packets
  295. (schedule-packets
  296. (parse-packets body
  297. (initial-ctx wasm func)
  298. local-types gensym))
  299. local-types))
  300. (lambda (locals body)
  301. (make-func id type locals body))))))
  302. (match wasm
  303. (($ <wasm> id types imports funcs tables memories globals exports start
  304. elems datas tags strings custom)
  305. (let ((funcs (map optimize-func funcs)))
  306. (make-wasm id types imports funcs tables memories globals exports start
  307. elems datas tags strings custom)))))
  308. (define (optimize-wasm wasm)
  309. (optimize-locals (symbolify-wasm wasm)))