compile.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. ;;; WebAssembly compiler
  2. ;;; Copyright (C) 2023, 2024, 2025, 2025 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. #:use-module (language tree-il)
  37. #:export (%default-program-imports
  38. scheme->sealed-tree-il
  39. read-and-compile
  40. compile-file
  41. compile
  42. library-load-path-extension))
  43. (define-syntax-rule (with-hoot-target . body)
  44. (with-target "wasm32-unknown-hoot"
  45. (lambda ()
  46. (parameterize ((target-runtime 'hoot))
  47. . body))))
  48. (define (%include-from-path filename)
  49. (let ((filename
  50. (or (search-path (append (hoot-system-load-path) (hoot-load-path))
  51. filename
  52. (hoot-load-extensions))
  53. (error 'include "file not found in path" filename))))
  54. (call-with-include-port
  55. (datum->syntax #f (canonicalize-path filename))
  56. (lambda (p)
  57. (let lp ()
  58. (match (read-syntax p)
  59. ((? eof-object?) #'())
  60. (x (cons x (lp)))))))))
  61. (define (include-relative-to-file base)
  62. (lambda (filename)
  63. (let ((filename (if (absolute-file-name? filename)
  64. filename
  65. (in-vicinity (dirname (canonicalize-path base))
  66. filename))))
  67. (unless (file-exists? filename)
  68. (error "file not found" filename))
  69. (call-with-include-port
  70. (datum->syntax #f filename)
  71. (lambda (p)
  72. (let lp ()
  73. (match (read-syntax p)
  74. ((? eof-object?) #'())
  75. (x (cons x (lp))))))))))
  76. (define (include-relative-to-port port)
  77. (cond
  78. ((port-filename port) => include-relative-to-file)
  79. (else (lambda (filename) (error "port has no file name" port)))))
  80. (define* (hoot-features #:key (import-abi? #f))
  81. (let ((features '(r7rs exact-closed ieee-float full-unicode ratios
  82. wasm hoot hoot-1.0)))
  83. (cons (if import-abi? 'hoot-aux 'hoot-main) features)))
  84. (define %default-program-imports '((scheme base)))
  85. (define (features-module-loader import-abi?)
  86. (lambda* (name #:key (features '()))
  87. (and (equal? name '(hoot features))
  88. (let ((trusted? #t))
  89. (parse-library
  90. `((library (hoot features)
  91. (export features)
  92. (import (hoot syntax))
  93. (define (features) ',features)))
  94. trusted?)))))
  95. (define* (%library-load-path-extension load-path #:key (trusted? #f))
  96. (define (relativize-filename filename)
  97. (or (or-map (lambda (dir)
  98. (let ((dir (false-if-exception (canonicalize-path dir))))
  99. (and dir
  100. (not (string-null? dir))
  101. (string-prefix? dir filename)
  102. (cond
  103. ((file-name-separator?
  104. (string-ref dir (1- (string-length dir))))
  105. (substring filename (string-length dir)))
  106. ((file-name-separator?
  107. (string-ref filename (string-length dir)))
  108. (substring filename (1+ (string-length dir))))
  109. (else #f)))))
  110. load-path)
  111. filename))
  112. (define (read-forms-from-file filename)
  113. (call-with-include-port
  114. (datum->syntax #f (canonicalize-path filename))
  115. (lambda (p)
  116. (set-port-filename! p (relativize-filename (port-filename p)))
  117. (let lp ()
  118. (match (read-syntax p)
  119. ((? eof-object?) #'())
  120. (x (cons x (lp))))))))
  121. (define (name-component->string x)
  122. (cond
  123. ((symbol? x)
  124. (let ((str (symbol->string x)))
  125. (when (or (equal? str "")
  126. (equal? str ".")
  127. (equal? str "..")
  128. (string-any file-name-separator? str)
  129. (absolute-file-name? str))
  130. (error "invalid name component" x))
  131. str))
  132. ((and (exact-integer? x) (not (negative? x)))
  133. (number->string x))
  134. (else
  135. (error "invalid name component" x))))
  136. (define (library-name->file-name name)
  137. (string-join (map name-component->string name) file-name-separator-string))
  138. (define (locate-library name)
  139. (search-path load-path (library-name->file-name name) %load-extensions))
  140. (lambda (load-library)
  141. (lambda* (name #:key (features '()))
  142. (define (load-library-from-file filename)
  143. (parse-library (read-forms-from-file filename) trusted?
  144. #:include-file (include-relative-to-file filename)
  145. #:features features))
  146. (cond
  147. ((load-library name #:features features))
  148. ((locate-library name) => load-library-from-file)
  149. (else #f)))))
  150. (define (builtin-module-loader import-abi?)
  151. ((%library-load-path-extension %stdlib-path #:trusted? #t)
  152. (features-module-loader import-abi?)))
  153. (define* (library-load-path-extension load-path #:key (features '()))
  154. (%library-load-path-extension load-path))
  155. (define (rewrite-expander-primitives exp make-module-ref)
  156. (match exp
  157. (($ <primitive-ref> src name)
  158. (case name
  159. ((list->vector)
  160. (make-module-ref src '(hoot vectors) name #f))
  161. ((list cons)
  162. (make-module-ref src '(hoot pairs) name #f))
  163. ((map)
  164. (make-module-ref src '(hoot lists) name #f))
  165. ((syntax-violation)
  166. (make-module-ref src '(hoot core-syntax-helpers) name #f))
  167. (($sc-dispatch)
  168. (make-module-ref src '(hoot expander) name #f))
  169. (else
  170. exp)))))
  171. (define (rewrite-expander-host-reference exp make-module-ref)
  172. (match exp
  173. (($ <module-ref> src '(guile) name #f)
  174. (make-primitive-ref src name))
  175. (exp
  176. (error "unexpected host module reference!" exp))))
  177. (define* (scheme->sealed-tree-il expr #:key
  178. (imports %default-program-imports)
  179. (import-abi? #f)
  180. (include-file %include-from-path)
  181. (features
  182. (hoot-features #:import-abi? import-abi?))
  183. (extend-load-library (lambda (f) f))
  184. (load-library
  185. (extend-load-library
  186. (builtin-module-loader import-abi?))))
  187. (define group
  188. (match expr
  189. ((? library-group?) expr)
  190. (_ (parse-library-group `(library-group (import . ,imports) ,expr)
  191. #:include-file include-file))))
  192. (define linked
  193. (link-library-group group
  194. #:load-library load-library
  195. #:features features
  196. #:allow-dangling-import?
  197. (lambda (name)
  198. (equal? name '(hoot primitives)))))
  199. (expand-library-group linked
  200. #:primitives '(hoot primitives)
  201. #:rewrite-primitive rewrite-expander-primitives
  202. #:rewrite-host-reference rewrite-expander-host-reference
  203. #:call-with-target (lambda (f)
  204. (with-hoot-target (f)))))
  205. (define* (compile expr #:key
  206. (imports %default-program-imports)
  207. (import-abi? #f)
  208. (export-abi? #t)
  209. (include-file %include-from-path)
  210. (features (hoot-features #:import-abi? import-abi?))
  211. (extend-load-library
  212. (library-load-path-extension (hoot-load-path)))
  213. (load-library
  214. (extend-load-library (builtin-module-loader import-abi?)))
  215. (optimization-level (default-optimization-level))
  216. (warning-level (default-warning-level))
  217. (dump-tree-il? #f)
  218. (dump-cps? #f)
  219. (dump-wasm? #f)
  220. (emit-names? #f)
  221. (opts '()))
  222. (define tree-il
  223. (scheme->sealed-tree-il expr #:imports imports
  224. #:import-abi? import-abi?
  225. #:features features
  226. #:include-file include-file
  227. #:load-library load-library))
  228. (with-hoot-target
  229. (define cps
  230. (%compile tree-il #:env #f #:from 'tree-il #:to 'cps
  231. #:optimization-level optimization-level
  232. #:warning-level warning-level
  233. #:opts (if dump-tree-il?
  234. (cons* #:dump-optimized-tree-il? #t opts)
  235. opts)))
  236. (high-level-cps->wasm cps
  237. #:import-abi? import-abi?
  238. #:export-abi? export-abi?
  239. #:optimization-level optimization-level
  240. #:warning-level warning-level
  241. #:dump-cps? dump-cps?
  242. #:dump-wasm? dump-wasm?
  243. #:emit-names? emit-names?
  244. #:opts opts)))
  245. (define* (read-and-compile port #:key
  246. (import-abi? #f)
  247. (export-abi? #t)
  248. (optimization-level (default-optimization-level))
  249. (warning-level (default-warning-level))
  250. (include-file (include-relative-to-port port))
  251. (features
  252. (hoot-features #:import-abi? import-abi?))
  253. (extend-load-library (lambda (f) f))
  254. (load-library
  255. (extend-load-library (builtin-module-loader import-abi?)))
  256. (dump-tree-il? #f)
  257. (dump-cps? #f)
  258. (dump-wasm? #f)
  259. (emit-names? #f)
  260. (opts '()))
  261. (define (name-matches? stx sym)
  262. (eq? (syntax->datum stx) sym))
  263. (define-syntax-rule (symbolic-match? name)
  264. (name-matches? #'name 'name))
  265. (define forms
  266. (let lp ()
  267. (let ((expr (read-syntax port)))
  268. (if (eof-object? expr)
  269. '()
  270. (cons expr (lp))))))
  271. (define group
  272. (syntax-case forms ()
  273. (((library-group . _))
  274. (symbolic-match? library-group)
  275. (parse-library-group (car forms) #:include-file include-file
  276. #:features features))
  277. (((import . imports) . body)
  278. (symbolic-match? import)
  279. (parse-library-group #'(library-group (import . imports) . body)))
  280. (((use-modules . imports) . body)
  281. (symbolic-match? use-modules)
  282. (parse-library-group #'(library-group (use-modules . imports) . body)))
  283. (_
  284. (parse-library-group
  285. `(library-group (import . ,%default-program-imports) . ,forms)))))
  286. (compile group
  287. #:import-abi? import-abi?
  288. #:export-abi? export-abi?
  289. #:optimization-level optimization-level
  290. #:warning-level warning-level
  291. #:load-library load-library
  292. #:features features
  293. #:dump-tree-il? dump-tree-il?
  294. #:dump-cps? dump-cps?
  295. #:dump-wasm? dump-wasm?
  296. #:emit-names? emit-names?
  297. #:opts opts))
  298. (define* (compile-file input-file #:key
  299. (output-file #f)
  300. (import-abi? #f)
  301. (export-abi? #t)
  302. (optimization-level (default-optimization-level))
  303. (warning-level (default-warning-level))
  304. (include-file (include-relative-to-file input-file))
  305. (features (hoot-features #:import-abi? import-abi?))
  306. (extend-load-library (lambda (f) f))
  307. (load-library
  308. (extend-load-library (builtin-module-loader import-abi?)))
  309. (dump-tree-il? #f)
  310. (dump-cps? #f)
  311. (dump-wasm? #f)
  312. (emit-names? #f)
  313. (opts '()))
  314. (call-with-input-file input-file
  315. (lambda (in)
  316. (set-port-encoding! in (or (file-encoding in) "UTF-8"))
  317. (let ((wasm (read-and-compile in
  318. #:import-abi? import-abi?
  319. #:export-abi? export-abi?
  320. #:optimization-level optimization-level
  321. #:warning-level warning-level
  322. #:include-file include-file
  323. #:features features
  324. #:load-library load-library
  325. #:dump-tree-il? dump-tree-il?
  326. #:dump-cps? dump-cps?
  327. #:dump-wasm? dump-wasm?
  328. #:emit-names? emit-names?
  329. #:opts opts)))
  330. (when output-file
  331. (let ((bytes (assemble-wasm wasm)))
  332. (call-with-output-file output-file
  333. (lambda (out)
  334. (put-bytevector out bytes)))))
  335. wasm))))
  336. (install-inline-wasm!)