compile.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2013-2014, 2016-2020, 2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2015 Taylan Ulrich Bayırlı/Kammer <taylanbayirli@gmail.com>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix build compile)
  20. #:use-module (srfi srfi-1)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 format)
  23. #:use-module (ice-9 threads)
  24. #:use-module (system base target)
  25. #:use-module (system base compile)
  26. #:use-module (system base message)
  27. #:use-module (guix modules)
  28. #:use-module (guix build utils)
  29. #:use-module (language tree-il optimize)
  30. #:use-module (language cps optimize)
  31. #:export (compile-files))
  32. ;;; Commentary:
  33. ;;;
  34. ;;; Support code to compile Guile code as efficiently as possible (with 2.2).
  35. ;;;
  36. ;;; Code:
  37. (define (clear-keyword-arguments keywords args)
  38. "Set to #f the value associated with each of the KEYWORDS in ARGS."
  39. (let loop ((args args)
  40. (result '()))
  41. (match args
  42. (()
  43. (reverse result))
  44. (((? keyword? kw) arg . rest)
  45. (loop rest
  46. (if (memq kw keywords)
  47. (cons* #f kw result)
  48. (cons* arg kw result))))
  49. ((head . tail)
  50. (loop tail (cons head result))))))
  51. (define optimizations-for-level
  52. (or (and=> (false-if-exception
  53. (resolve-interface '(system base optimize)))
  54. (lambda (iface)
  55. (module-ref iface 'optimizations-for-level))) ;Guile 3.0
  56. (let () ;Guile 2.2
  57. (define %default-optimizations
  58. ;; Default optimization options (equivalent to -O2 on Guile 2.2).
  59. (append (tree-il-default-optimization-options)
  60. (cps-default-optimization-options)))
  61. (define %lightweight-optimizations
  62. ;; Lightweight optimizations (like -O0, but with partial evaluation).
  63. (let loop ((opts %default-optimizations)
  64. (result '()))
  65. (match opts
  66. (() (reverse result))
  67. ((#:partial-eval? _ rest ...)
  68. (loop rest `(#t #:partial-eval? ,@result)))
  69. ((kw _ rest ...)
  70. (loop rest `(#f ,kw ,@result))))))
  71. (lambda (level)
  72. ;; In the upcoming Guile 3.0.8, .go files include code of their
  73. ;; inlinable exports and free variables are resolved at compile time
  74. ;; (both are enabled at -O1) to permit cross-module inlining
  75. ;; (enabled at -O2). Unfortunately, this currently leads to
  76. ;; non-reproducible and more expensive builds, so we turn it off
  77. ;; here:
  78. ;; <https://wingolog.org/archives/2021/05/13/cross-module-inlining-in-guile>.
  79. (clear-keyword-arguments '(#:inlinable-exports? #:resolve-free-vars?
  80. #:cross-module-inlining?)
  81. (if (<= level 1)
  82. %lightweight-optimizations
  83. %default-optimizations))))))
  84. (define (supported-warning-type? type)
  85. "Return true if TYPE, a symbol, denotes a supported warning type."
  86. (find (lambda (warning-type)
  87. (eq? type (warning-type-name warning-type)))
  88. %warning-types))
  89. (define %warnings
  90. ;; FIXME: 'format' is missing because it reports "non-literal format
  91. ;; strings" due to the fact that we use 'G_' instead of '_'. We'll need
  92. ;; help from Guile to solve this.
  93. (let ((optional (lambda (type)
  94. (if (supported-warning-type? type)
  95. (list type)
  96. '()))))
  97. `(unbound-variable arity-mismatch
  98. macro-use-before-definition ;new in 2.2
  99. ,@(optional 'shadowed-toplevel)))) ;new in 2.2.5
  100. (define (optimization-options file)
  101. "Return the default set of optimizations options for FILE."
  102. (define (strip-option option lst)
  103. (let loop ((lst lst)
  104. (result '()))
  105. (match lst
  106. (()
  107. (reverse result))
  108. ((kw value rest ...)
  109. (if (eq? kw option)
  110. (append (reverse result) rest)
  111. (loop rest (cons* value kw result)))))))
  112. (define (override-option option value lst)
  113. `(,option ,value ,@(strip-option option lst)))
  114. (cond ((or (string-contains file "gnu/packages/")
  115. (string-contains file "gnu/tests/"))
  116. ;; Use '-O1' to have partial evaluation and primitive inlining so we
  117. ;; can honor the "macro writer's bill of rights".
  118. (optimizations-for-level 1))
  119. ((string-contains file "gnu/services/")
  120. ;; '-O2 -Ono-letrectify' compiles about ~20% faster than '-O2' for
  121. ;; large files like gnu/services/mail.scm.
  122. (override-option #:letrectify? #f
  123. (optimizations-for-level 2)))
  124. (else
  125. (optimizations-for-level 3))))
  126. (define (scm->go file)
  127. "Strip the \".scm\" suffix from FILE, and append \".go\"."
  128. (string-append (string-drop-right file 4) ".go"))
  129. (define (relative-file directory file)
  130. "Return FILE relative to DIRECTORY, if possible."
  131. (if (string-prefix? (string-append directory "/") file)
  132. (string-drop file (+ 1 (string-length directory)))
  133. file))
  134. (define* (load-files directory files
  135. #:key
  136. (report-load (const #f))
  137. (debug-port (%make-void-port "w")))
  138. "Load FILES, a list of relative file names, from DIRECTORY."
  139. (define total
  140. (length files))
  141. (let loop ((files files)
  142. (completed 0))
  143. (match files
  144. (()
  145. (unless (zero? total)
  146. (report-load #f total completed))
  147. *unspecified*)
  148. ((file files ...)
  149. (let ((file (relative-file directory file)))
  150. (report-load file total completed)
  151. (format debug-port "~%loading '~a'...~%" file)
  152. (resolve-interface (file-name->module-name file))
  153. (loop files (+ 1 completed)))))))
  154. (define-syntax-rule (with-augmented-search-path path item body ...)
  155. "Within the dynamic extent of BODY, augment PATH by adding ITEM to the
  156. front."
  157. (let ((initial-value path))
  158. (dynamic-wind
  159. (lambda ()
  160. (set! path (cons item path)))
  161. (lambda ()
  162. body ...)
  163. (lambda ()
  164. (set! path initial-value)))))
  165. (define (call/exit-on-exception file thunk)
  166. "Evaluate THUNK and exit right away if an exception is thrown. Report FILE
  167. as the file that was being compiled when the exception was thrown."
  168. (catch #t
  169. thunk
  170. (const #f)
  171. (lambda (key . args)
  172. (false-if-exception
  173. ;; Duplicate stderr to avoid thread-safety issues.
  174. (let* ((port (duplicate-port (current-error-port) "w0"))
  175. (stack (make-stack #t))
  176. (depth (stack-length stack))
  177. (frame (and (> depth 1) (stack-ref stack 1))))
  178. (newline port)
  179. (format port "error: failed to compile '~a':~%~%" file)
  180. (false-if-exception (display-backtrace stack port))
  181. (print-exception port frame key args)))
  182. ;; Don't go any further.
  183. (primitive-exit 1))))
  184. (define-syntax-rule (exit-on-exception file exp ...)
  185. "Evaluate EXP and exit if an exception is thrown. Report FILE as the faulty
  186. file when an exception is thrown."
  187. (call/exit-on-exception file (lambda () exp ...)))
  188. (define* (compile-files source-directory build-directory files
  189. #:key
  190. (host %host-type)
  191. (workers (current-processor-count))
  192. (optimization-options optimization-options)
  193. (warning-options `(#:warnings ,%warnings))
  194. (report-load (const #f))
  195. (report-compilation (const #f))
  196. (debug-port (%make-void-port "w")))
  197. "Compile FILES, a list of source files taken from SOURCE-DIRECTORY, to
  198. BUILD-DIRECTORY, using up to WORKERS parallel workers. The resulting object
  199. files are for HOST, a GNU triplet such as \"x86_64-linux-gnu\"."
  200. (define progress-lock (make-mutex))
  201. (define total (length files))
  202. (define progress 0)
  203. (define (build file)
  204. (with-mutex progress-lock
  205. (report-compilation file total progress)
  206. (set! progress (+ 1 progress)))
  207. ;; Exit as soon as something goes wrong.
  208. (exit-on-exception
  209. file
  210. (let ((relative (relative-file source-directory file)))
  211. (compile-file file
  212. #:output-file (string-append build-directory "/"
  213. (scm->go relative))
  214. #:opts (append warning-options
  215. (optimization-options relative))))))
  216. (with-augmented-search-path %load-path source-directory
  217. (with-augmented-search-path %load-compiled-path build-directory
  218. (with-fluids ((*current-warning-prefix* ""))
  219. ;; Make sure the compiler's modules are loaded before 'with-target'
  220. ;; (since 'with-target' influences the .go loader), and before
  221. ;; starting to compile files in parallel.
  222. (compile #f)
  223. (with-target host
  224. (lambda ()
  225. ;; FIXME: To work around <https://bugs.gnu.org/15602>, we first
  226. ;; load all of FILES.
  227. (load-files source-directory files
  228. #:report-load report-load
  229. #:debug-port debug-port)
  230. ;; XXX: Don't use too many workers to work around the insane
  231. ;; memory requirements of the compiler in Guile 2.2.2:
  232. ;; <https://lists.gnu.org/archive/html/guile-devel/2017-05/msg00033.html>.
  233. (n-par-for-each (min workers 8) build files)
  234. (unless (zero? total)
  235. (report-compilation #f total total))))))))
  236. (eval-when (eval load)
  237. (when (and (string=? "2" (major-version))
  238. (or (string=? "0" (minor-version))
  239. (and (string=? (minor-version) "2")
  240. (< (string->number (micro-version)) 4))))
  241. ;; Work around <https://bugs.gnu.org/31878> on Guile < 2.2.4.
  242. ;; Serialize 'try-module-autoload' calls.
  243. (set! (@ (guile) try-module-autoload)
  244. (let ((mutex (make-mutex 'recursive))
  245. (real (@ (guile) try-module-autoload)))
  246. (lambda* (module #:optional version)
  247. (with-mutex mutex
  248. (real module version)))))))
  249. ;;; Local Variables:
  250. ;;; eval: (put 'with-augmented-search-path 'scheme-indent-function 2)
  251. ;;; eval: (put 'with-target 'scheme-indent-function 1)
  252. ;;; End: