lower-globals.scm 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. ;;; Pass to lower globals with non-constant initexprs
  2. ;;; Copyright (C) 2023 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. ;;; Some global constants aren't. A symbol literal, for example, needs
  18. ;;; to be interned in a symtab, and that's not a constant instruction
  19. ;;; from WebAssembly's point of view. Interning a symbol needs to be
  20. ;;; done in a "start" function; only then will the WebAssembly global
  21. ;;; have its final value and be available for use. For that reason
  22. ;;; other globals that are constant by themselves might not be, if they
  23. ;;; directly or indirectly reference a non-constant value.
  24. ;;;
  25. ;;; Our approach is to just let the compiler and stdlib use non-constant
  26. ;;; initialization expressions for globals. We have the same
  27. ;;; precondition as in standard WebAssembly that the globals are sorted:
  28. ;;; initexprs can only reference globals with a lower index. Then we
  29. ;;; run a post-pass to transform non-constant initexpr into
  30. ;;; placeholders, and synthesize a start function containing the needed
  31. ;;; initializations.
  32. ;;;
  33. ;;; Code:
  34. (define-module (wasm lower-globals)
  35. #:use-module (ice-9 match)
  36. #:use-module ((srfi srfi-1) #:select (append-map fold))
  37. #:use-module (wasm types)
  38. #:export (lower-globals))
  39. (define (lower-globals wasm)
  40. (match wasm
  41. (($ <wasm> id types imports funcs tables memories globals exports start
  42. elems datas tags strings custom)
  43. (define imported-func-count
  44. (fold (lambda (import count)
  45. (match import
  46. (($ <import> mod name 'func) (1+ count))
  47. (_ count)))
  48. 0 imports))
  49. (define imported-global-count
  50. (fold (lambda (import count)
  51. (match import
  52. (($ <import> mod name 'func) (1+ count))
  53. (_ count)))
  54. 0 imports))
  55. (define all-globals-constant? #t)
  56. (define deferred-initializations (make-hash-table))
  57. (define (constant-global? id-or-idx)
  58. (not (hashq-ref deferred-initializations id-or-idx)))
  59. (define (constant-expr? expr)
  60. (define (constant-inst? inst)
  61. (match inst
  62. (((or 'i32.const 'f32.const 'i64.const 'f64.const) _) #t)
  63. (('ref.null _) #t)
  64. (('ref.func _) #t)
  65. (('global.get g) (constant-global? g))
  66. (('struct.new _) #t)
  67. (('struct.new_default _) #t)
  68. (('array.new _) #t)
  69. (('array.new_default _) #t)
  70. (('array.new_fixed _ _) #t)
  71. (('extern.externalize) #t)
  72. (('extern.internalize) #t)
  73. (('ref.i31) #t)
  74. (('string.const _) #t)
  75. (_ #f)))
  76. (and-map constant-inst? expr))
  77. (define (mutable-nullable-type type)
  78. (match type
  79. (($ <global-type> mutable? type)
  80. (make-global-type
  81. #t
  82. (match type
  83. (($ <ref-type> nullable? ht) (make-ref-type #t ht))
  84. (_ type))))))
  85. (define (default-initializer type)
  86. (match type
  87. (($ <global-type> mutable? type)
  88. (match type
  89. (($ <ref-type> _ ht) `((ref.null ,ht)))
  90. ('i32 '((i32.const 0)))
  91. ('i64 '((i64.const 0)))
  92. ('f32 '((f32.const 0)))
  93. ('f64 '((f64.const 0)))))))
  94. (define lowered-globals
  95. (map (lambda (global idx)
  96. (match global
  97. (($ <global> id type (? constant-expr?))
  98. global)
  99. (($ <global> id type init)
  100. (set! all-globals-constant? #f)
  101. (hashq-set! deferred-initializations idx global)
  102. (when id
  103. (hashq-set! deferred-initializations id global))
  104. (make-global id (mutable-nullable-type type)
  105. (default-initializer type)))))
  106. globals
  107. (iota (length globals) imported-global-count)))
  108. (define deferred-initexpr
  109. (append-map (lambda (idx)
  110. (match (hashq-ref deferred-initializations idx)
  111. (($ <global> id type init)
  112. (append init `((global.set ,(or id idx)))))
  113. (#f '())))
  114. (iota (length globals) imported-global-count)))
  115. (define existing-start-func
  116. (and start
  117. (let lp ((funcs funcs) (idx imported-func-count))
  118. (match funcs
  119. (() (error "start func not found; is it imported?"))
  120. (((and func ($ <func> id type locals body)) . funcs)
  121. (if (or (eq? idx start) (eq? id start))
  122. func
  123. (lp funcs (1+ idx))))))))
  124. (define existing-start-func-type
  125. (match existing-start-func
  126. (($ <func> id type locals body) type)
  127. (#f (or-map (match-lambda
  128. (($ <type> id ($ <func-sig> () ()))
  129. id)
  130. (_ #f))
  131. types))))
  132. (define new-start-func-type
  133. (or existing-start-func-type
  134. (make-type '$__start (make-func-sig '() '()))))
  135. (define new-types
  136. (if existing-start-func-type
  137. types
  138. (append types (list new-start-func-type))))
  139. (define new-start-func-id (or start '$__start))
  140. (define new-start-func
  141. (match existing-start-func
  142. (($ <func> id type locals body)
  143. (make-func id type locals (append deferred-initexpr body)))
  144. (#f
  145. (make-func new-start-func-id
  146. (make-type-use (or existing-start-func-type '$__start)
  147. (make-func-sig '() '()))
  148. '() deferred-initexpr))))
  149. (define new-funcs
  150. (let lp ((funcs funcs))
  151. (match funcs
  152. ((func . funcs)
  153. (if (eq? existing-start-func func)
  154. (cons new-start-func funcs)
  155. (cons func (lp funcs))))
  156. (() (list new-start-func)))))
  157. (define (rewrite-global-get-in-expr expr)
  158. (define (global-needs-cast? g)
  159. (match (hashq-ref deferred-initializations g)
  160. (($ <global> id
  161. ($ <global-type> mutable? ($ <ref-type> nullable? ht))
  162. initexpr)
  163. (not nullable?))
  164. (_ #f)))
  165. (match expr
  166. (() '())
  167. ((('global.get (? global-needs-cast? g)) . expr)
  168. `((global.get ,g) (ref.as_non_null)
  169. . ,(rewrite-global-get-in-expr expr)))
  170. ((inst . expr)
  171. (cons (match inst
  172. (('block label type insts)
  173. `(block ,label ,type ,(rewrite-global-get-in-expr insts)))
  174. (('loop label type insts)
  175. `(loop ,label ,type ,(rewrite-global-get-in-expr insts)))
  176. (('if label type consequent alternate)
  177. `(if ,label ,type ,(rewrite-global-get-in-expr consequent)
  178. ,(rewrite-global-get-in-expr alternate)))
  179. (('try label type body catches catch-all)
  180. `(try ,label ,type
  181. ,(rewrite-global-get-in-expr body)
  182. ,(map rewrite-global-get-in-expr catches)
  183. ,(and=> catch-all rewrite-global-get-in-expr)))
  184. (('try_delegate label type body handler)
  185. `(try_delegate ,label ,type
  186. ,(rewrite-global-get-in-expr body)
  187. ,handler))
  188. (_ inst))
  189. (rewrite-global-get-in-expr expr)))))
  190. (define (rewrite-global-get-in-func func)
  191. (match func
  192. (($ <func> id type locals body)
  193. (make-func id type locals (rewrite-global-get-in-expr body)))))
  194. (define (rewrite-global-get-in-global global)
  195. (match global
  196. (($ <global> id type init)
  197. (make-global id type (rewrite-global-get-in-expr init)))))
  198. (define (rewrite-global-get-in-elem elem)
  199. (match elem
  200. (($ <elem> id mode table type offset inits)
  201. (make-elem id mode table type
  202. (and=> offset rewrite-global-get-in-expr)
  203. (map rewrite-global-get-in-expr inits)))))
  204. (if all-globals-constant?
  205. wasm
  206. (let ((funcs (map rewrite-global-get-in-func new-funcs))
  207. (globals (map rewrite-global-get-in-global lowered-globals))
  208. (elems (map rewrite-global-get-in-elem elems)))
  209. (make-wasm id
  210. new-types
  211. imports
  212. funcs
  213. tables
  214. memories
  215. globals
  216. exports
  217. new-start-func-id
  218. elems
  219. datas
  220. tags
  221. strings
  222. custom))))))