repl.scm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. ;;; REPL commands
  2. ;;; Copyright (C) 2023 David Thompson <dave@spritely.institute>
  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. ;;; Handy REPL commands for development.
  18. ;;;
  19. ;;; Code:
  20. (define-module (hoot repl)
  21. #:use-module (hoot compile)
  22. #:use-module (hoot reflect)
  23. #:use-module (ice-9 control)
  24. #:use-module (ice-9 match)
  25. #:use-module (srfi srfi-1)
  26. #:use-module (srfi srfi-9)
  27. #:use-module (system repl command)
  28. #:use-module (system repl common)
  29. #:use-module (system repl debug)
  30. #:use-module (system repl repl)
  31. #:use-module (wasm dump)
  32. #:use-module (wasm types)
  33. #:use-module (wasm vm))
  34. ;;;
  35. ;;; Wasm VM tools
  36. ;;;
  37. (define (for-each/index proc lst)
  38. (let loop ((lst lst) (i 0))
  39. (match lst
  40. (() *unspecified*)
  41. ((x . rest)
  42. (proc i x)
  43. (loop rest (+ i 1))))))
  44. (define (print-list proc title items)
  45. (format #t "~a:\n" title)
  46. (for-each/index (lambda (i item)
  47. (format #t " ~a:\t" i)
  48. (proc item))
  49. items))
  50. (define (print-stack stack)
  51. (match (wasm-stack-items stack)
  52. (() (display "Empty stack.\n"))
  53. (items
  54. (print-list (lambda (x) (format #t "~s\n" x))
  55. "Value stack"
  56. items))))
  57. (define (print-locals locals)
  58. (if (zero? (vector-length locals))
  59. (display "No locals.\n")
  60. (print-list (lambda (x) (format #t "~s\n" x))
  61. "Locals"
  62. (vector->list locals))))
  63. (define (print-runtime-error e)
  64. (print-exception (current-output-port) #f
  65. (exception-kind e)
  66. (exception-args e))
  67. (newline)
  68. (print-stack (wasm-runtime-error-stack e))
  69. (newline)
  70. (print-locals (wasm-runtime-error-locals e))
  71. (newline)
  72. (print-location (validated-wasm-ref
  73. (wasm-instance-module
  74. (wasm-runtime-error-instance e)))
  75. (wasm-runtime-error-position e)))
  76. (define-syntax-rule (with-wasm-error-handling body ...)
  77. (with-exception-handler (lambda (e) (print-runtime-error e))
  78. (lambda () body ...)
  79. #:unwind? #t
  80. #:unwind-for-type &wasm-runtime-error))
  81. (define (block-type-repr type)
  82. (match type
  83. ((? func-sig?)
  84. (match (type-repr type)
  85. (('func params+results ...)
  86. params+results)))
  87. ((? ref-type?)
  88. `((param ,(val-type-repr type))))
  89. (_ `((param ,type)))))
  90. (define (print-location wasm path)
  91. (define invalid-path '(-1))
  92. (define (path-remainder path i)
  93. (match path
  94. ((idx . rest)
  95. (if (and (= idx i) (not (null? rest))) rest invalid-path))))
  96. (define (here? path i)
  97. (match path
  98. ((idx) (= i idx))
  99. (_ #f)))
  100. (define (indent level)
  101. (unless (= level 0)
  102. (display " ")
  103. (indent (- level 1))))
  104. (define (print-block-type type)
  105. (for-each (lambda (x)
  106. (format #t " ~s" x))
  107. (block-type-repr type)))
  108. (define (print-instr level instr path)
  109. (match instr
  110. (((and op (or 'block 'loop)) _ (or ($ <type-use> _ sig) sig) body)
  111. (format #t "(~a" op)
  112. (print-block-type sig)
  113. (newline)
  114. (print-instrs (+ level 1) body path)
  115. (display ")"))
  116. (('if _ (or ($ <type-use> _ sig) sig) consequent alternate)
  117. (display "(if")
  118. (print-block-type sig)
  119. (unless (null? consequent)
  120. (newline)
  121. (indent (+ level 1))
  122. (display "(then\n")
  123. (print-instrs (+ level 2) consequent
  124. (path-remainder path 0))
  125. (display ")"))
  126. (unless (null? alternate)
  127. (newline)
  128. (indent (+ level 1))
  129. (display "(else\n")
  130. (print-instrs (+ level 2) alternate
  131. (path-remainder path 1))
  132. (display ")"))
  133. (display ")"))
  134. (_
  135. (write instr))))
  136. (define (print-instrs level instrs path)
  137. (indent level)
  138. (let loop ((instrs instrs)
  139. (i 0))
  140. (match instrs
  141. (() #t)
  142. ((instr . rest)
  143. (if (here? path i)
  144. (begin
  145. (display "<<< ")
  146. (print-instr level instr (path-remainder path i))
  147. (display " >>>"))
  148. (print-instr level instr (path-remainder path i)))
  149. (unless (null? rest)
  150. (newline)
  151. (indent level)
  152. (loop rest (+ i 1)))))))
  153. (define (count-imports kind)
  154. (fold (lambda (i sum)
  155. (match i
  156. (($ <import> _ _ k)
  157. (if (eq? kind k) (+ sum 1) sum))))
  158. 0 (wasm-imports wasm)))
  159. (match path
  160. (('func idx . path*)
  161. (match (list-ref (wasm-funcs wasm) (- idx (count-imports 'func)))
  162. (($ <func> id ($ <type-use> _ sig) locals body)
  163. (format #t "(func ~a" idx)
  164. (print-block-type sig)
  165. (newline)
  166. (print-instrs 1 body path*)
  167. (display ")"))))
  168. (('global idx . path*)
  169. (match (list-ref (wasm-globals wasm) (- idx (count-imports 'global)))
  170. (($ <global> id ($ <global-type> mutable? type) init)
  171. (let ((t (val-type-repr type)))
  172. (format #t "(global ~a " idx)
  173. (write (if mutable? `(mut ,t) t))
  174. (newline)
  175. (print-instrs 1 init path*)
  176. (display ")")))))
  177. (('data idx . path*)
  178. (match (list-ref (wasm-datas wasm) idx)
  179. (($ <data> id mode mem offset init)
  180. (format #t "(data ~a ~a ~a ~a\n" idx mode mem offset)
  181. (print-instrs 1 init path*)
  182. (display ")"))))
  183. (('elem idx j . path*)
  184. (match (list-ref (wasm-elems wasm) idx)
  185. (($ <elem> id mode table type offset inits)
  186. (let ((t (val-type-repr type)))
  187. (format #t "(elem ~a ~a ~a ~a" idx mode table t)
  188. (when offset
  189. (newline)
  190. (print-instrs 1 offset (if (= j 0) path* invalid-path)))
  191. (let loop ((inits inits) (i 1))
  192. (match inits
  193. (() #t)
  194. ((init . rest)
  195. (newline)
  196. (print-instrs 1 init (if (= j 1) path* invalid-path))
  197. (loop rest (+ i 1)))))
  198. (display ")"))))))
  199. (newline))
  200. (define (wasm-trace path instr instance stack blocks locals)
  201. (let ((instr (match instr ; abbreviate blocks
  202. (((and (or 'block 'loop) op) _ type . _)
  203. `(,op ,(block-type-repr type) ...))
  204. (('if _ type . _)
  205. `(if ,(block-type-repr type) ...))
  206. (_ instr))))
  207. (define (abbrev x)
  208. (match x
  209. ((? wasm-null?) 'null)
  210. ((? wasm-struct?) 'struct)
  211. ((? wasm-array?) 'array)
  212. ((? wasm-func?) 'func)
  213. (_ x)))
  214. (format #t "⌄ instr: ~a\n" instr)
  215. (format #t " loc: ~a @ ~a\n" instance (reverse path))
  216. (format #t " stack: ~s\n" (map abbrev (wasm-stack-items stack)))
  217. (format #t " locals: ~a\n" (map abbrev (vector->list locals)))))
  218. (define (->wasm x)
  219. (match x
  220. ((? wasm? wasm) wasm)
  221. ((? validated-wasm? mod) (validated-wasm-ref mod))
  222. ((? wasm-instance? instance)
  223. (validated-wasm-ref (wasm-instance-module instance)))
  224. ((? hoot-module? mod)
  225. (validated-wasm-ref
  226. (wasm-instance-module
  227. (hoot-module-instance mod))))))
  228. (define-record-type <wasm-debug>
  229. (make-wasm-debug position instruction instance stack blocks locals)
  230. wasm-debug?
  231. (position wasm-debug-position)
  232. (instruction wasm-debug-instruction)
  233. (instance wasm-debug-instance)
  234. (stack wasm-debug-stack)
  235. (blocks wasm-debug-blocks)
  236. (locals wasm-debug-locals)
  237. (continue? wasm-debug-continue? set-wasm-debug-continue!))
  238. (define current-wasm-debug (make-parameter #f))
  239. (define-syntax-rule (when-debugging body ...)
  240. (if (current-wasm-debug)
  241. (begin body ...)
  242. (error "not in a WASM debugger")))
  243. ;; This code is based on error-string in (system repl
  244. ;; exception-handling) and adapted to work with Guile's new exception
  245. ;; objects.
  246. (define (error-message exn stack)
  247. (let ((key (exception-kind exn))
  248. (args (exception-args exn)))
  249. (call-with-output-string
  250. (lambda (port)
  251. (let ((frame (and (< 0 (vector-length stack)) (vector-ref stack 0))))
  252. (print-exception port frame key args))))))
  253. (define (enter-wasm-debugger exn)
  254. (let* ((tag (and (pair? (fluid-ref %stacks))
  255. (cdr (fluid-ref %stacks))))
  256. (stack (stack->vector (make-stack #t 3 tag 0 1)))
  257. (msg (error-message exn stack))
  258. (wasm-debug (make-wasm-debug (wasm-runtime-error-position exn)
  259. (wasm-runtime-error-instruction exn)
  260. (wasm-runtime-error-instance exn)
  261. (wasm-runtime-error-stack exn)
  262. (wasm-runtime-error-blocks exn)
  263. (wasm-runtime-error-locals exn))))
  264. (parameterize ((current-wasm-debug wasm-debug))
  265. (format #t "~a\n" msg)
  266. (format #t "Entering WASM debug prompt. ")
  267. (format #t "Type `,help wasm' for info or `,q' to continue.\n")
  268. (start-repl #:debug (make-debug stack 0 msg))
  269. (wasm-debug-continue? wasm-debug))))
  270. (define (wasm-step position instruction instance stack blocks locals)
  271. (let ((wasm-debug (make-wasm-debug (reverse position) instruction instance stack
  272. blocks locals)))
  273. (parameterize ((current-wasm-debug wasm-debug))
  274. (format #t "Instruction: ~a\n" instruction)
  275. (format #t "Location: ~a\n" (reverse position))
  276. (start-repl))))
  277. (define (reset-instruction-listener)
  278. (current-instruction-listener
  279. (lambda (position instr instance stack blocks locals) #t)))
  280. (define (continue)
  281. (set-wasm-debug-continue! (current-wasm-debug) #t)
  282. (throw 'quit))
  283. (define-meta-command ((wasm-dump wasm) repl #:optional exp)
  284. "wasm-dump [WASM]
  285. Display information about WASM, or the current WASM instance when debugging."
  286. (dump-wasm (->wasm
  287. (cond
  288. (exp (repl-eval repl exp))
  289. ((current-wasm-debug) => wasm-debug-instance)
  290. (else (error "no WASM object specified"))))
  291. #:dump-func-defs? #f))
  292. (define-meta-command ((wasm-trace wasm) repl exp)
  293. "wasm-trace EXP
  294. Evaluate EXP with verbose WASM tracing enabled."
  295. (with-wasm-error-handling
  296. (parameterize ((current-instruction-listener wasm-trace))
  297. (call-with-values (lambda () (repl-eval repl exp))
  298. (lambda vals
  299. (for-each (lambda (v) (repl-print repl v)) vals))))))
  300. (define-meta-command ((wasm-freq wasm) repl exp)
  301. "wasm-freq EXP
  302. Evaluate EXP and count how many times each WASM instruction is evaluated."
  303. (let ((count 0)
  304. (histogram (make-hash-table)))
  305. (define (wasm-stats path instr instance stack blocks locals)
  306. (set! count (+ count 1))
  307. (match instr
  308. ((op . _)
  309. (hashq-set! histogram op (+ (hashq-ref histogram op 0) 1)))))
  310. (with-wasm-error-handling
  311. (parameterize ((current-instruction-listener wasm-stats))
  312. (call-with-values (lambda () (repl-eval repl exp))
  313. (lambda vals
  314. (display "op\tcount\n")
  315. (display "--\t-----\n")
  316. (for-each (match-lambda
  317. ((op . k)
  318. (format #t "~a\t~a\n" op k)))
  319. (sort (hash-fold alist-cons '() histogram)
  320. (lambda (a b) (< (cdr a) (cdr b)))))
  321. (format #t "\n~a instructions total\n\n" count)
  322. (for-each (lambda (v) (repl-print repl v)) vals)))))))
  323. (define-meta-command ((wasm-catch wasm) repl exp)
  324. "wasm-catch EXP
  325. Catch and debug WASM runtime errors that are raised by evaluating EXP."
  326. (let ((thunk (repl-prepare-eval-thunk repl exp)))
  327. (call/ec
  328. (lambda (return)
  329. (with-exception-handler (lambda (exn)
  330. (if (wasm-runtime-error? exn)
  331. (unless (enter-wasm-debugger exn)
  332. (reset-instruction-listener)
  333. (return))
  334. (raise-exception exn)))
  335. (lambda ()
  336. (call-with-values (lambda () (%start-stack #t thunk))
  337. (lambda vals
  338. (reset-instruction-listener)
  339. (for-each (lambda (v) (repl-print repl v)) vals)))))))))
  340. (define-meta-command ((wasm-stack wasm) repl)
  341. "wasm-stack
  342. Print the state of the WASM stack in the current context."
  343. (when-debugging
  344. (print-stack (wasm-debug-stack (current-wasm-debug)))))
  345. (define-meta-command ((wasm-locals wasm) repl)
  346. "wasm-locals
  347. Print the state of the WASM locals in the current context."
  348. (when-debugging
  349. (print-locals (wasm-debug-locals (current-wasm-debug)))))
  350. (define-meta-command ((wasm-pos wasm) repl)
  351. "wasm-pos
  352. Highlight the instruction where WASM execution has paused."
  353. (when-debugging
  354. (let ((debug (current-wasm-debug)))
  355. (print-location (->wasm (wasm-debug-instance debug)) (wasm-debug-position debug)))))
  356. (define-meta-command ((wasm-eval wasm) repl instr)
  357. "wasm-eval INSTR
  358. Evaluate the WASM instruction INSTR in the current debug context."
  359. (when-debugging
  360. (let ((execute (@@ (wasm vm) execute)))
  361. (match (current-wasm-debug)
  362. (($ <wasm-debug> position _ instance stack blocks locals)
  363. (execute (repl-eval repl instr) position instance stack blocks locals))))))
  364. (define-meta-command ((wasm-continue wasm) repl)
  365. "wasm-continue
  366. Set WASM execution to continue without interruption until the next error."
  367. (when-debugging
  368. (reset-instruction-listener)
  369. (when (current-wasm-debug)
  370. (continue))))
  371. (define-meta-command ((wasm-step wasm) repl)
  372. "wasm-step
  373. Set WASM execution to pause before each instruction."
  374. (when-debugging
  375. (current-instruction-listener wasm-step)
  376. (when (current-wasm-debug)
  377. (continue))))
  378. ;;;
  379. ;;; Scheme tools
  380. ;;;
  381. (define-meta-command ((hoot-compile hoot) repl exp . opts)
  382. "hoot-compile EXP OPTS ...
  383. Compile EXP and return a Wasm module."
  384. (let ((exp (repl-parse repl exp))
  385. (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
  386. (repl-print repl (apply compile exp opts))))
  387. (define-meta-command ((hoot-compile-file hoot) repl file . opts)
  388. "hoot-compile-file FILE OPTS ...
  389. Compile FILE and return a Wasm module."
  390. (let ((file (repl-eval repl file))
  391. (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
  392. (repl-print repl (apply compile-file file opts))))
  393. (define (run-wasm-and-print-results repl wasm)
  394. (call-with-values (lambda ()
  395. (with-wasm-error-handling
  396. (hoot-load (hoot-instantiate wasm))))
  397. (lambda vals
  398. (for-each (lambda (val)
  399. (repl-print repl val))
  400. vals))))
  401. (define-meta-command ((hoot-run hoot) repl exp . opts)
  402. "hoot-run EXP OPTS ...
  403. Compile and run EXP in the Wasm interpreter."
  404. (let ((exp (repl-parse repl exp))
  405. (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
  406. (run-wasm-and-print-results repl (apply compile exp opts))))
  407. (define-meta-command ((hoot-run-file hoot) repl file . opts)
  408. "hoot-run FILE OPTS ...
  409. Compile and run FILE in the Wasm interpreter."
  410. (let ((file (repl-eval repl file))
  411. (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
  412. (run-wasm-and-print-results repl (apply compile-file file opts))))