inline-wasm.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. ;;; WebAssembly inline assembly
  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. ;;; Compiler support for inline assembly.
  18. ;;;
  19. ;;; Code:
  20. (define-module (hoot inline-wasm)
  21. #:use-module (ice-9 match)
  22. #:use-module ((language tree-il primitives)
  23. #:select (add-interesting-primitive!))
  24. #:use-module ((language tree-il effects)
  25. #:select (add-primcall-effect-analyzer!))
  26. #:use-module ((language tree-il compile-cps)
  27. #:select (define-custom-primcall-converter))
  28. #:use-module (language tree-il)
  29. #:use-module (language cps)
  30. #:use-module (language cps intmap)
  31. #:use-module (language cps utils)
  32. #:use-module (language cps with-cps)
  33. #:use-module (wasm types)
  34. #:use-module (wasm wat)
  35. #:use-module ((hoot primitives) #:select (%inline-wasm %wasm-import))
  36. #:export (install-inline-wasm!))
  37. (define (inline-wasm-effect-free? args)
  38. (define (effect-free-expr? expr)
  39. (define (effect-free-inst? inst)
  40. (match inst
  41. (((or 'block 'loop) label type body)
  42. (effect-free-expr? body))
  43. (('if label type consequent alternate)
  44. (and (effect-free-expr? consequent)
  45. (effect-free-expr? alternate)))
  46. (('try label type body catches catch-all)
  47. (and (effect-free-expr? body)
  48. (and-map effect-free-expr? catches)
  49. (or (not catch-all) (effect-free-expr? catch-all))))
  50. (('try_delegate label type body handler)
  51. (effect-free-expr? body))
  52. ((op . args)
  53. (case op
  54. ((nop
  55. br br_if br_table return drop select
  56. local.get local.set local.tee global.get
  57. table.get
  58. i32.load i64.load f32.load f64.load
  59. i32.load8_s i32.load8_u
  60. i32.load16_s i32.load16_u
  61. i64.load8_s i64.load8_u
  62. i64.load16_s i64.load16_u
  63. i64.load32_s i64.load32_u
  64. memory.size
  65. i32.const i64.const f32.const f64.const
  66. i32.eqz i32.eq i32.ne
  67. i32.lt_s i32.lt_u i32.gt_s i32.gt_u
  68. i32.le_s i32.le_u i32.ge_s i32.ge_u
  69. i64.eqz i64.eq i64.ne
  70. i64.lt_s i64.lt_u i64.gt_s i64.gt_u
  71. i64.le_s i64.le_u i64.ge_s i64.ge_u
  72. f32.eq f32.ne f32.lt f32.gt f32.le f32.ge
  73. f64.eq f64.ne f64.lt f64.gt f64.le f64.ge
  74. i32.clz i32.ctz i32.popcnt
  75. i32.add i32.sub i32.mul
  76. i32.div_s i32.div_u i32.rem_s i32.rem_u
  77. i32.and i32.or i32.xor
  78. i32.shl i32.shr_s i32.shr_u i32.rotl i32.rotr
  79. i64.clz i64.ctz i64.popcnt
  80. i64.add i64.sub i64.mul
  81. i64.div_s i64.div_u i64.rem_s i64.rem_u
  82. i64.and i64.or i64.xor
  83. i64.shl i64.shr_s i64.shr_u i64.rotl i64.rotr
  84. f32.abs f32.neg f32.ceil f32.floor f32.trunc f32.nearest f32.sqrt
  85. f32.add f32.sub f32.mul f32.div f32.min f32.max f32.copysign
  86. f64.abs f64.neg f64.ceil f64.floor f64.trunc f64.nearest f64.sqrt
  87. f64.add f64.sub f64.mul f64.div f64.min f64.max f64.copysign
  88. i32.wrap_i64 i32.trunc_f32_s i32.trunc_f32_u
  89. i32.trunc_f64_s i32.trunc_f64_u
  90. i64.extend_i32_s i64.extend_i32_u i64.trunc_f32_s i64.trunc_f32_u
  91. i64.trunc_f64_s i64.trunc_f64_u
  92. f32.convert_i32_s f32.convert_i32_u
  93. f32.convert_i64_s f32.convert_i64_u
  94. f32.demote_f64 f64.convert_i32_s f64.convert_i32_u
  95. f64.convert_i64_s f64.convert_i64_u
  96. f64.promote_f32
  97. i32.reinterpret_f32 i64.reinterpret_f64 f32.reinterpret_i32
  98. f64.reinterpret_i64
  99. i32.extend8_s i32.extend16_s
  100. i64.extend8_s i64.extend16_s i64.extend32_s
  101. ref.null ref.is_null ref.func ref.eq ref.as_non_null
  102. struct.new struct.new_default struct.get struct.get_s struct.get_u
  103. array.new array.new_default array.new_fixed array.new_data
  104. array.new_elem array.get array.get_s array.get_u
  105. array.len
  106. ref.test ref.cast br_on_cast br_on_cast_fail
  107. extern.internalize extern.externalize
  108. ref.i31 i31.get_s i31.get_u
  109. string.new_utf8 string.new_wtf16
  110. string.const string.measure_utf8 string.measure_wtf8
  111. string.measure_wtf16 string.concat string.eq
  112. string.is_usv_sequence
  113. string.new_lossy_utf8 string.new_wtf8
  114. string.as_wtf8 stringview_wtf8.advance stringview_wtf8.slice
  115. string.as_wtf16 stringview_wtf16.length
  116. stringview_wtf16.get_codeunit stringview_wtf16.slice
  117. ;; Assume that stateful iter stringviews are ephemeral.
  118. string.as_iter stringview_iter.next stringview_iter.advance
  119. stringview_iter.rewind stringview_iter.slice
  120. string.compare string.from_code_point
  121. string.new_utf8_array string.new_wtf16_array
  122. string.new_lossy_utf8_array string.new_wtf8_array
  123. i8x16.splat i16x8.splat i32x4.splat i64x2.splat
  124. f32x4.splat f64x2.splat
  125. ;; A number of SIMD opcodes missing here.
  126. i32.trunc_sat_f32_s i32.trunc_sat_f32_u
  127. i32.trunc_sat_f64_s i32.trunc_sat_f64_u
  128. i64.trunc_sat_f32_s i64.trunc_sat_f32_u
  129. i64.trunc_sat_f64_s i64.trunc_sat_f64_u
  130. table.size)
  131. #t)
  132. (else #f)))))
  133. (and-map effect-free-inst? expr))
  134. (match args
  135. ((($ <const> _ wat) . args)
  136. (match (false-if-exception (wat->wasm (list wat)))
  137. (($ <wasm> mod-id () ()
  138. (($ <func> id ($ <type-use> #f
  139. ($ <func-sig> params results))
  140. locals body))
  141. () () () () #f () () () () ())
  142. (and (= (length params) (length args))
  143. (effect-free-expr? body)))
  144. (_ #f)))))
  145. (define (wasm-import-effect-free? args)
  146. (match args
  147. (() #t)
  148. (_ #f)))
  149. (define install-inline-wasm!
  150. (let ((m (current-module)))
  151. (lambda ()
  152. ;; The useless reference is to prevent a warning that (language
  153. ;; tree-il primitives) is unused; we just import the module so that we
  154. ;; can add %inline-asm as a primitive, because for reasons I don't
  155. ;; understand, you can't call add-interesting-primitive! from within
  156. ;; the compilation unit that defines the primitive.
  157. %inline-wasm
  158. (save-module-excursion
  159. (lambda ()
  160. (set-current-module m)
  161. (add-interesting-primitive! '%inline-wasm)
  162. (add-interesting-primitive! '%wasm-import)
  163. (add-primcall-effect-analyzer! '%inline-wasm inline-wasm-effect-free?)
  164. (add-primcall-effect-analyzer! '%wasm-import wasm-import-effect-free?))))))
  165. (define (n-valued-continuation cps src nvals k)
  166. (define (enumerate f n)
  167. (let lp ((i 0))
  168. (if (< i n)
  169. (cons (f i) (lp (1+ i)))
  170. '())))
  171. (match (intmap-ref cps k)
  172. (($ $ktail)
  173. (let ((names (enumerate (lambda (n) 'result) nvals))
  174. (temps (enumerate (lambda (n) (fresh-var)) nvals)))
  175. (with-cps cps
  176. (letk k* ($kargs names temps
  177. ($continue k src ($values temps))))
  178. k*)))
  179. (($ $kreceive ($ $arity req () rest () #f) kargs)
  180. (cond
  181. ((and (not rest) (= nvals (length req)))
  182. (with-cps cps
  183. kargs))
  184. ((and rest (= nvals (length req)))
  185. (let ((names (enumerate (lambda (n) 'result) nvals))
  186. (temps (enumerate (lambda (n) (fresh-var)) nvals)))
  187. (with-cps cps
  188. (letv rest)
  189. (letk knil ($kargs ('rest) (rest)
  190. ($continue kargs src
  191. ($values ,(append temps (list rest))))))
  192. (letk k ($kargs names temps
  193. ($continue knil src ($const '()))))
  194. k)))
  195. ((and rest (zero? (length req)))
  196. ;; Very annoyingly, this can happen as a result of the
  197. ;; compilation of e.g. (letrec ((x A)) B), where X is not used in
  198. ;; B. This gets compiled to (<seq> A B), and when the CPS
  199. ;; converter doesn't know that A is zero-valued, it just makes a
  200. ;; (lambda ignored B) continuation. This happens to us when
  201. ;; prelude bindings that are inline-wasm forms are unused in a
  202. ;; user program. So, we cons it up!
  203. (let ((names (enumerate (lambda (n) 'result) nvals))
  204. (temps (enumerate (lambda (n) (fresh-var)) nvals)))
  205. (define (cons-values cps temps k)
  206. (match temps
  207. (()
  208. (with-cps cps
  209. (build-term
  210. ($continue k src ($const '())))))
  211. ((temp . temps)
  212. (with-cps cps
  213. (letv rest)
  214. (letk kcons ($kargs ('rest) (rest)
  215. ($continue k src
  216. ($primcall 'cons #f (temp rest)))))
  217. ($ (cons-values temps kcons))))))
  218. (with-cps cps
  219. (let$ term (cons-values temps kargs))
  220. (letk k ($kargs names temps ,term))
  221. k)))
  222. (else
  223. (error "unexpected continuation for n-valued result" nvals))))))
  224. (define-syntax-rule (assert-match x pat message)
  225. (match x
  226. (pat #t)
  227. (_ (error message x))))
  228. (define-custom-primcall-converter (%inline-wasm cps src args convert-args k)
  229. (define (unpack-arg cps arg type have-arg)
  230. (match type
  231. (($ <ref-type> _ _)
  232. (have-arg cps arg))
  233. ((or 'i32 'i64)
  234. (with-cps cps
  235. (letv val)
  236. (let$ cont (have-arg val))
  237. (letk kval ($kargs ('val) (val) ,cont))
  238. (build-term
  239. ($continue kval src ($primcall 'scm->u64/truncate #f (arg))))))
  240. ((or 'f32 'f64)
  241. (with-cps cps
  242. (letv val)
  243. (let$ cont (have-arg val))
  244. (letk kval ($kargs ('val) (val) ,cont))
  245. (build-term
  246. ($continue kval src ($primcall 'scm->f64 #f (arg))))))
  247. (_
  248. (error "invalid param type for inline wasm" type))))
  249. (define (unpack-args cps args types have-args)
  250. (match args
  251. (() (have-args cps '()))
  252. ((arg . args)
  253. (match types
  254. ((type . types)
  255. (unpack-arg cps arg type
  256. (lambda (cps arg)
  257. (unpack-args cps args types
  258. (lambda (cps args)
  259. (have-args cps (cons arg args)))))))))))
  260. (define (pack-result cps result type have-result)
  261. (match type
  262. (($ <ref-type> #f 'eq)
  263. (have-result cps result))
  264. ('i64
  265. (with-cps cps
  266. (letv val)
  267. (let$ cont (have-result val))
  268. (letk kval ($kargs ('val) (val) ,cont))
  269. (build-term
  270. ($continue kval src ($primcall 's64->scm #f (result))))))
  271. ('f64
  272. (with-cps cps
  273. (letv val)
  274. (let$ cont (have-result val))
  275. (letk kval ($kargs ('val) (val) ,cont))
  276. (build-term
  277. ($continue kval src ($primcall 'f64->scm #f (result))))))
  278. (_
  279. (error "invalid result type for inline wasm" type))))
  280. (define (pack-results cps results types have-results)
  281. (match results
  282. (() (have-results cps '()))
  283. ((result . results)
  284. (match types
  285. ((type . types)
  286. (pack-result
  287. cps result type
  288. (lambda (cps result)
  289. (pack-results cps results types
  290. (lambda (cps results)
  291. (have-results cps (cons result results)))))))))))
  292. (match args
  293. ((($ <const> _ code) . args)
  294. (assert-match code ('func . _)
  295. "inline-wasm: expected a single (func ...)")
  296. (match (wat->wasm (list code))
  297. ;; We expect a single func and no other definitions (types,
  298. ;; tables, etc).
  299. (($ <wasm> mod-id () ()
  300. ((and func ($ <func> id ($ <type-use> #f
  301. ($ <func-sig> params results))
  302. locals body)))
  303. () () () () #f () () () () ())
  304. (unless (= (length params) (length args))
  305. (error "inline asm with incorrect number of args" code))
  306. (convert-args cps args
  307. (lambda (cps args)
  308. (unpack-args
  309. cps args (map param-type params)
  310. (lambda (cps args)
  311. (define result-names (map (lambda (_) #f) results))
  312. (define result-vars (map (lambda (_) (fresh-var)) results))
  313. (with-cps cps
  314. (let$ k* (n-valued-continuation src (length results) k))
  315. (let$ pack (pack-results
  316. result-vars results
  317. (lambda (cps vars)
  318. (with-cps cps
  319. (build-term
  320. ($continue k* src ($values vars)))))))
  321. (letk k** ($kargs result-names result-vars ,pack))
  322. (build-term
  323. ($continue k** src
  324. ($primcall 'inline-wasm func args)))))))))))))
  325. (define-custom-primcall-converter (%wasm-import cps src args convert-args k)
  326. (match args
  327. ((($ <const> _ code) . args)
  328. (assert-match code ('func . _) "wasm-import: expected a single (func ...)")
  329. (assert-match args () "wasm-import: expected 0 args")
  330. (match (wat->wasm (list code))
  331. ;; We expect only a single import.
  332. (($ <wasm> mod-id () ((and import ($ <import> mod name kind id type)))
  333. () () () () () #f () () () () ())
  334. (with-cps cps
  335. (let$ k* (n-valued-continuation src 0 k))
  336. (build-term
  337. ($continue k* src ($primcall 'wasm-import import ())))))))))