compile.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. ;;; WebAssembly compiler
  2. ;;; Copyright (C) 2023, 2024 Igalia, S.L.
  3. ;;; Copyright (C) 2023 Robin Templeton <robin@spritely.institute>
  4. ;;; Copyright (C) 2023 David Thompson <dave@spritely.institute>
  5. ;;;
  6. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  7. ;;; you may not use this file except in compliance with the License.
  8. ;;; You may obtain a copy of the License at
  9. ;;;
  10. ;;; http://www.apache.org/licenses/LICENSE-2.0
  11. ;;;
  12. ;;; Unless required by applicable law or agreed to in writing, software
  13. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  14. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. ;;; See the License for the specific language governing permissions and
  16. ;;; limitations under the License.
  17. ;;; Commentary:
  18. ;;;
  19. ;;; Scheme to WebAssembly compiler.
  20. ;;;
  21. ;;; Code:
  22. (define-module (hoot compile)
  23. #:use-module (ice-9 binary-ports)
  24. #:use-module (ice-9 match)
  25. #:use-module ((system base compile)
  26. #:select ((compile . %compile)
  27. default-warning-level
  28. default-optimization-level))
  29. #:use-module (system base target)
  30. #:use-module (hoot library-group)
  31. #:use-module (hoot inline-wasm)
  32. #:use-module (hoot backend)
  33. #:use-module (hoot frontend)
  34. #:use-module (hoot config)
  35. #:use-module (wasm assemble)
  36. #:export (%default-program-imports
  37. scheme->sealed-tree-il
  38. read-and-compile
  39. compile-file
  40. compile
  41. library-load-path-extension))
  42. (define-syntax-rule (with-hoot-target . body)
  43. (with-target "wasm32-unknown-hoot"
  44. (lambda ()
  45. (parameterize ((target-runtime 'hoot))
  46. . body))))
  47. (define (%include-from-path filename)
  48. (let ((filename
  49. (or (search-path (append (hoot-system-load-path) (hoot-load-path))
  50. filename
  51. (hoot-load-extensions))
  52. (error 'include "file not found in path" filename))))
  53. (call-with-include-port
  54. (datum->syntax #f (canonicalize-path filename))
  55. (lambda (p)
  56. (let lp ()
  57. (match (read-syntax p)
  58. ((? eof-object?) #'())
  59. (x (cons x (lp)))))))))
  60. (define (include-relative-to-file base)
  61. (lambda (filename)
  62. (let ((filename (if (absolute-file-name? filename)
  63. filename
  64. (in-vicinity (dirname (canonicalize-path base))
  65. filename))))
  66. (unless (file-exists? filename)
  67. (error "file not found" filename))
  68. (call-with-include-port
  69. (datum->syntax #f filename)
  70. (lambda (p)
  71. (let lp ()
  72. (match (read-syntax p)
  73. ((? eof-object?) #'())
  74. (x (cons x (lp))))))))))
  75. (define (include-relative-to-port port)
  76. (cond
  77. ((port-filename port) => include-relative-to-file)
  78. (else (lambda (filename) (error "port has no file name" port)))))
  79. (define* (hoot-features #:key (import-abi? #f))
  80. (let ((features '(r7rs exact-closed ieee-float full-unicode ratios
  81. wasm hoot hoot-1.0)))
  82. (cons (if import-abi? 'hoot-aux 'hoot-main) features)))
  83. (define %default-program-imports
  84. '((scheme base)
  85. (scheme case-lambda)
  86. (scheme char)
  87. (scheme complex)
  88. (scheme cxr)
  89. (scheme eval)
  90. (scheme file)
  91. (scheme inexact)
  92. (scheme lazy)
  93. (scheme load)
  94. (scheme read)
  95. (scheme repl)
  96. (scheme process-context)
  97. (scheme time)
  98. (scheme write)
  99. (ice-9 match)
  100. (only (hoot syntax) lambda* case-lambda* define* define-syntax-rule)
  101. (only (hoot primitives) %inline-wasm %wasm-export)
  102. (only (hoot numbers) 1+ 1-)
  103. (only (hoot pairs) cons*)
  104. (only (hoot debug) pk)))
  105. (define (features-module-loader import-abi?)
  106. (lambda (name)
  107. (and (equal? name '(hoot features))
  108. (let ((trusted? #t))
  109. (parse-library
  110. `((library (hoot features)
  111. (export features)
  112. (import (hoot syntax))
  113. (define (features)
  114. ',(hoot-features #:import-abi? import-abi?))))
  115. trusted?)))))
  116. (define* (%library-load-path-extension load-path #:key (trusted? #f))
  117. (define (read-forms-from-file filename)
  118. (call-with-include-port
  119. (datum->syntax #f (canonicalize-path filename))
  120. (lambda (p)
  121. (let lp ()
  122. (match (read-syntax p)
  123. ((? eof-object?) #'())
  124. (x (cons x (lp))))))))
  125. (define (load-library-from-file filename)
  126. (parse-library (read-forms-from-file filename) trusted?))
  127. (define (name-component->string x)
  128. (cond
  129. ((symbol? x)
  130. (let ((str (symbol->string x)))
  131. (when (or (equal? str "")
  132. (equal? str ".")
  133. (equal? str "..")
  134. (string-any file-name-separator? str)
  135. (absolute-file-name? str))
  136. (error "invalid name component" x))
  137. str))
  138. ((and (exact-integer? x) (not (negative? x)))
  139. (number->string x))
  140. (else
  141. (error "invalid name component" x))))
  142. (define (library-name->file-name name)
  143. (string-join (map name-component->string name) file-name-separator-string))
  144. (define (locate-library name)
  145. (search-path load-path (library-name->file-name name) %load-extensions))
  146. (lambda (load-library)
  147. (lambda (name)
  148. (cond
  149. ((load-library name))
  150. ((locate-library name) => load-library-from-file)
  151. (else #f)))))
  152. (define (builtin-module-loader import-abi?)
  153. ((%library-load-path-extension %stdlib-path #:trusted? #t)
  154. (features-module-loader import-abi?)))
  155. (define (library-load-path-extension load-path)
  156. (%library-load-path-extension load-path))
  157. (define* (scheme->sealed-tree-il expr #:key
  158. (imports %default-program-imports)
  159. (import-abi? #f)
  160. (include-file %include-from-path)
  161. (extend-load-library (lambda (f) f))
  162. (load-library
  163. (extend-load-library
  164. (builtin-module-loader import-abi?))))
  165. (define group
  166. (match expr
  167. ((? library-group?) expr)
  168. (_ (parse-library-group `(library-group (import . ,imports) ,expr)
  169. #:include-file include-file))))
  170. (define linked
  171. (link-library-group group
  172. #:load-library load-library
  173. #:allow-dangling-import?
  174. (lambda (name)
  175. (equal? name '(hoot primitives)))))
  176. (expand-library-group linked
  177. #:primitives '(hoot primitives)
  178. #:call-with-target (lambda (f)
  179. (with-hoot-target (f)))))
  180. (define* (compile expr #:key
  181. (imports %default-program-imports)
  182. (import-abi? #f)
  183. (export-abi? #t)
  184. (include-file %include-from-path)
  185. (extend-load-library
  186. (library-load-path-extension (hoot-load-path)))
  187. (load-library
  188. (extend-load-library (builtin-module-loader import-abi?)))
  189. (optimization-level (default-optimization-level))
  190. (warning-level (default-warning-level))
  191. (dump-tree-il? #f)
  192. (dump-cps? #f)
  193. (dump-wasm? #f)
  194. (emit-names? #f)
  195. (opts '()))
  196. (define tree-il
  197. (scheme->sealed-tree-il expr #:imports imports
  198. #:import-abi? import-abi?
  199. #:include-file include-file
  200. #:load-library load-library))
  201. (with-hoot-target
  202. (define cps
  203. (%compile tree-il #:env #f #:from 'tree-il #:to 'cps
  204. #:optimization-level optimization-level
  205. #:warning-level warning-level
  206. #:opts (if dump-tree-il?
  207. (cons* #:dump-optimized-tree-il? #t opts)
  208. opts)))
  209. (high-level-cps->wasm cps
  210. #:import-abi? import-abi?
  211. #:export-abi? export-abi?
  212. #:optimization-level optimization-level
  213. #:warning-level warning-level
  214. #:dump-cps? dump-cps?
  215. #:dump-wasm? dump-wasm?
  216. #:emit-names? emit-names?
  217. #:opts opts)))
  218. (define* (read-and-compile port #:key
  219. (import-abi? #f)
  220. (export-abi? #t)
  221. (optimization-level (default-optimization-level))
  222. (warning-level (default-warning-level))
  223. (include-file (include-relative-to-port port))
  224. (extend-load-library (lambda (f) f))
  225. (load-library
  226. (extend-load-library (builtin-module-loader import-abi?)))
  227. (dump-tree-il? #f)
  228. (dump-cps? #f)
  229. (dump-wasm? #f)
  230. (emit-names? #f)
  231. (opts '()))
  232. (define (name-matches? stx sym)
  233. (eq? (syntax->datum stx) sym))
  234. (define-syntax-rule (symbolic-match? name)
  235. (name-matches? #'name 'name))
  236. (define forms
  237. (let lp ()
  238. (let ((expr (read-syntax port)))
  239. (if (eof-object? expr)
  240. '()
  241. (cons expr (lp))))))
  242. (define group
  243. (syntax-case forms ()
  244. (((library-group . _))
  245. (symbolic-match? library-group)
  246. (parse-library-group (car forms) #:include-file include-file))
  247. (((import . imports) . body)
  248. (symbolic-match? import)
  249. (parse-library-group #'(library-group (import . imports) . body)))
  250. (((use-modules . imports) . body)
  251. (symbolic-match? use-modules)
  252. (parse-library-group #'(library-group (use-modules . imports) . body)))
  253. (_
  254. (parse-library-group
  255. `(library-group (import . ,%default-program-imports) . ,forms)))))
  256. (compile group
  257. #:import-abi? import-abi?
  258. #:export-abi? export-abi?
  259. #:optimization-level optimization-level
  260. #:warning-level warning-level
  261. #:load-library load-library
  262. #:dump-tree-il? dump-tree-il?
  263. #:dump-cps? dump-cps?
  264. #:dump-wasm? dump-wasm?
  265. #:emit-names? emit-names?
  266. #:opts opts))
  267. (define* (compile-file input-file #:key
  268. (output-file #f)
  269. (import-abi? #f)
  270. (export-abi? #t)
  271. (optimization-level (default-optimization-level))
  272. (warning-level (default-warning-level))
  273. (include-file (include-relative-to-file input-file))
  274. (extend-load-library (lambda (f) f))
  275. (load-library
  276. (extend-load-library (builtin-module-loader import-abi?)))
  277. (dump-tree-il? #f)
  278. (dump-cps? #f)
  279. (dump-wasm? #f)
  280. (emit-names? #f)
  281. (opts '()))
  282. (call-with-input-file input-file
  283. (lambda (in)
  284. (set-port-encoding! in (or (file-encoding in) "UTF-8"))
  285. (let ((wasm (read-and-compile in
  286. #:import-abi? import-abi?
  287. #:export-abi? export-abi?
  288. #:optimization-level optimization-level
  289. #:warning-level warning-level
  290. #:include-file include-file
  291. #:load-library load-library
  292. #:dump-tree-il? dump-tree-il?
  293. #:dump-cps? dump-cps?
  294. #:dump-wasm? dump-wasm?
  295. #:emit-names? emit-names?
  296. #:opts opts)))
  297. (when output-file
  298. (let ((bytes (assemble-wasm wasm)))
  299. (call-with-output-file output-file
  300. (lambda (out)
  301. (put-bytevector out bytes)))))
  302. wasm))))
  303. (install-inline-wasm!)