compile.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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 #:key (features '()))
  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) ',features)))
  114. trusted?)))))
  115. (define* (%library-load-path-extension load-path #:key (trusted? #f))
  116. (define (relativize-filename filename)
  117. (or (or-map (lambda (dir)
  118. (let ((dir (false-if-exception (canonicalize-path dir))))
  119. (and dir
  120. (not (string-null? dir))
  121. (string-prefix? dir filename)
  122. (cond
  123. ((file-name-separator?
  124. (string-ref dir (1- (string-length dir))))
  125. (substring filename (string-length dir)))
  126. ((file-name-separator?
  127. (string-ref filename (string-length dir)))
  128. (substring filename (1+ (string-length dir))))
  129. (else #f)))))
  130. load-path)
  131. filename))
  132. (define (read-forms-from-file filename)
  133. (call-with-include-port
  134. (datum->syntax #f (canonicalize-path filename))
  135. (lambda (p)
  136. (set-port-filename! p (relativize-filename (port-filename p)))
  137. (let lp ()
  138. (match (read-syntax p)
  139. ((? eof-object?) #'())
  140. (x (cons x (lp))))))))
  141. (define (name-component->string x)
  142. (cond
  143. ((symbol? x)
  144. (let ((str (symbol->string x)))
  145. (when (or (equal? str "")
  146. (equal? str ".")
  147. (equal? str "..")
  148. (string-any file-name-separator? str)
  149. (absolute-file-name? str))
  150. (error "invalid name component" x))
  151. str))
  152. ((and (exact-integer? x) (not (negative? x)))
  153. (number->string x))
  154. (else
  155. (error "invalid name component" x))))
  156. (define (library-name->file-name name)
  157. (string-join (map name-component->string name) file-name-separator-string))
  158. (define (locate-library name)
  159. (search-path load-path (library-name->file-name name) %load-extensions))
  160. (lambda (load-library)
  161. (lambda* (name #:key (features '()))
  162. (define (load-library-from-file filename)
  163. (parse-library (read-forms-from-file filename) trusted?
  164. #:include-file (include-relative-to-file filename)
  165. #:features features))
  166. (cond
  167. ((load-library name #:features features))
  168. ((locate-library name) => load-library-from-file)
  169. (else #f)))))
  170. (define (builtin-module-loader import-abi?)
  171. ((%library-load-path-extension %stdlib-path #:trusted? #t)
  172. (features-module-loader import-abi?)))
  173. (define* (library-load-path-extension load-path #:key (features '()))
  174. (%library-load-path-extension load-path))
  175. (define* (scheme->sealed-tree-il expr #:key
  176. (imports %default-program-imports)
  177. (import-abi? #f)
  178. (include-file %include-from-path)
  179. (features
  180. (hoot-features #:import-abi? import-abi?))
  181. (extend-load-library (lambda (f) f))
  182. (load-library
  183. (extend-load-library
  184. (builtin-module-loader import-abi?))))
  185. (define group
  186. (match expr
  187. ((? library-group?) expr)
  188. (_ (parse-library-group `(library-group (import . ,imports) ,expr)
  189. #:include-file include-file))))
  190. (define linked
  191. (link-library-group group
  192. #:load-library load-library
  193. #:features features
  194. #:allow-dangling-import?
  195. (lambda (name)
  196. (equal? name '(hoot primitives)))))
  197. (expand-library-group linked
  198. #:primitives '(hoot primitives)
  199. #:call-with-target (lambda (f)
  200. (with-hoot-target (f)))))
  201. (define* (compile expr #:key
  202. (imports %default-program-imports)
  203. (import-abi? #f)
  204. (export-abi? #t)
  205. (include-file %include-from-path)
  206. (features (hoot-features #:import-abi? import-abi?))
  207. (extend-load-library
  208. (library-load-path-extension (hoot-load-path)))
  209. (load-library
  210. (extend-load-library (builtin-module-loader import-abi?)))
  211. (optimization-level (default-optimization-level))
  212. (warning-level (default-warning-level))
  213. (dump-tree-il? #f)
  214. (dump-cps? #f)
  215. (dump-wasm? #f)
  216. (emit-names? #f)
  217. (opts '()))
  218. (define tree-il
  219. (scheme->sealed-tree-il expr #:imports imports
  220. #:import-abi? import-abi?
  221. #:features features
  222. #:include-file include-file
  223. #:load-library load-library))
  224. (with-hoot-target
  225. (define cps
  226. (%compile tree-il #:env #f #:from 'tree-il #:to 'cps
  227. #:optimization-level optimization-level
  228. #:warning-level warning-level
  229. #:opts (if dump-tree-il?
  230. (cons* #:dump-optimized-tree-il? #t opts)
  231. opts)))
  232. (high-level-cps->wasm cps
  233. #:import-abi? import-abi?
  234. #:export-abi? export-abi?
  235. #:optimization-level optimization-level
  236. #:warning-level warning-level
  237. #:dump-cps? dump-cps?
  238. #:dump-wasm? dump-wasm?
  239. #:emit-names? emit-names?
  240. #:opts opts)))
  241. (define* (read-and-compile port #:key
  242. (import-abi? #f)
  243. (export-abi? #t)
  244. (optimization-level (default-optimization-level))
  245. (warning-level (default-warning-level))
  246. (include-file (include-relative-to-port port))
  247. (features
  248. (hoot-features #:import-abi? import-abi?))
  249. (extend-load-library (lambda (f) f))
  250. (load-library
  251. (extend-load-library (builtin-module-loader import-abi?)))
  252. (dump-tree-il? #f)
  253. (dump-cps? #f)
  254. (dump-wasm? #f)
  255. (emit-names? #f)
  256. (opts '()))
  257. (define (name-matches? stx sym)
  258. (eq? (syntax->datum stx) sym))
  259. (define-syntax-rule (symbolic-match? name)
  260. (name-matches? #'name 'name))
  261. (define forms
  262. (let lp ()
  263. (let ((expr (read-syntax port)))
  264. (if (eof-object? expr)
  265. '()
  266. (cons expr (lp))))))
  267. (define group
  268. (syntax-case forms ()
  269. (((library-group . _))
  270. (symbolic-match? library-group)
  271. (parse-library-group (car forms) #:include-file include-file
  272. #:features features))
  273. (((import . imports) . body)
  274. (symbolic-match? import)
  275. (parse-library-group #'(library-group (import . imports) . body)))
  276. (((use-modules . imports) . body)
  277. (symbolic-match? use-modules)
  278. (parse-library-group #'(library-group (use-modules . imports) . body)))
  279. (_
  280. (parse-library-group
  281. `(library-group (import . ,%default-program-imports) . ,forms)))))
  282. (compile group
  283. #:import-abi? import-abi?
  284. #:export-abi? export-abi?
  285. #:optimization-level optimization-level
  286. #:warning-level warning-level
  287. #:load-library load-library
  288. #:features features
  289. #:dump-tree-il? dump-tree-il?
  290. #:dump-cps? dump-cps?
  291. #:dump-wasm? dump-wasm?
  292. #:emit-names? emit-names?
  293. #:opts opts))
  294. (define* (compile-file input-file #:key
  295. (output-file #f)
  296. (import-abi? #f)
  297. (export-abi? #t)
  298. (optimization-level (default-optimization-level))
  299. (warning-level (default-warning-level))
  300. (include-file (include-relative-to-file input-file))
  301. (features (hoot-features #:import-abi? import-abi?))
  302. (extend-load-library (lambda (f) f))
  303. (load-library
  304. (extend-load-library (builtin-module-loader import-abi?)))
  305. (dump-tree-il? #f)
  306. (dump-cps? #f)
  307. (dump-wasm? #f)
  308. (emit-names? #f)
  309. (opts '()))
  310. (call-with-input-file input-file
  311. (lambda (in)
  312. (set-port-encoding! in (or (file-encoding in) "UTF-8"))
  313. (let ((wasm (read-and-compile in
  314. #:import-abi? import-abi?
  315. #:export-abi? export-abi?
  316. #:optimization-level optimization-level
  317. #:warning-level warning-level
  318. #:include-file include-file
  319. #:features features
  320. #:load-library load-library
  321. #:dump-tree-il? dump-tree-il?
  322. #:dump-cps? dump-cps?
  323. #:dump-wasm? dump-wasm?
  324. #:emit-names? emit-names?
  325. #:opts opts)))
  326. (when output-file
  327. (let ((bytes (assemble-wasm wasm)))
  328. (call-with-output-file output-file
  329. (lambda (out)
  330. (put-bytevector out bytes)))))
  331. wasm))))
  332. (install-inline-wasm!)