123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- ;;; REPL commands
- ;;; Copyright (C) 2023 David Thompson <dave@spritely.institute>
- ;;;
- ;;; Licensed under the Apache License, Version 2.0 (the "License");
- ;;; you may not use this file except in compliance with the License.
- ;;; You may obtain a copy of the License at
- ;;;
- ;;; http://www.apache.org/licenses/LICENSE-2.0
- ;;;
- ;;; Unless required by applicable law or agreed to in writing, software
- ;;; distributed under the License is distributed on an "AS IS" BASIS,
- ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ;;; See the License for the specific language governing permissions and
- ;;; limitations under the License.
- ;;; Commentary:
- ;;;
- ;;; Handy REPL commands for development.
- ;;;
- ;;; Code:
- (define-module (hoot repl)
- #:use-module (hoot compile)
- #:use-module (hoot reflect)
- #:use-module (ice-9 control)
- #:use-module (ice-9 match)
- #:use-module (srfi srfi-1)
- #:use-module (srfi srfi-9)
- #:use-module (system repl command)
- #:use-module (system repl common)
- #:use-module (system repl debug)
- #:use-module (system repl repl)
- #:use-module (wasm dump)
- #:use-module (wasm types)
- #:use-module (wasm vm))
- ;;;
- ;;; Wasm VM tools
- ;;;
- (define (for-each/index proc lst)
- (let loop ((lst lst) (i 0))
- (match lst
- (() *unspecified*)
- ((x . rest)
- (proc i x)
- (loop rest (+ i 1))))))
- (define (print-list proc title items)
- (format #t "~a:\n" title)
- (for-each/index (lambda (i item)
- (format #t " ~a:\t" i)
- (proc item))
- items))
- (define (print-stack stack)
- (match (wasm-stack-items stack)
- (() (display "Empty stack.\n"))
- (items
- (print-list (lambda (x) (format #t "~s\n" x))
- "Value stack"
- items))))
- (define (print-locals locals)
- (if (zero? (vector-length locals))
- (display "No locals.\n")
- (print-list (lambda (x) (format #t "~s\n" x))
- "Locals"
- (vector->list locals))))
- (define (print-runtime-error e)
- (print-exception (current-output-port) #f
- (exception-kind e)
- (exception-args e))
- (newline)
- (print-stack (wasm-runtime-error-stack e))
- (newline)
- (print-locals (wasm-runtime-error-locals e))
- (newline)
- (print-location (validated-wasm-ref
- (wasm-instance-module
- (wasm-runtime-error-instance e)))
- (wasm-runtime-error-position e)))
- (define-syntax-rule (with-wasm-error-handling body ...)
- (with-exception-handler (lambda (e) (print-runtime-error e))
- (lambda () body ...)
- #:unwind? #t
- #:unwind-for-type &wasm-runtime-error))
- (define (block-type-repr type)
- (match type
- ((? func-sig?)
- (match (type-repr type)
- (('func params+results ...)
- params+results)))
- ((? ref-type?)
- `((param ,(val-type-repr type))))
- (_ `((param ,type)))))
- (define (print-location wasm path)
- (define invalid-path '(-1))
- (define (path-remainder path i)
- (match path
- ((idx . rest)
- (if (and (= idx i) (not (null? rest))) rest invalid-path))))
- (define (here? path i)
- (match path
- ((idx) (= i idx))
- (_ #f)))
- (define (indent level)
- (unless (= level 0)
- (display " ")
- (indent (- level 1))))
- (define (print-block-type type)
- (for-each (lambda (x)
- (format #t " ~s" x))
- (block-type-repr type)))
- (define (print-instr level instr path)
- (match instr
- (((and op (or 'block 'loop)) _ (or ($ <type-use> _ sig) sig) body)
- (format #t "(~a" op)
- (print-block-type sig)
- (newline)
- (print-instrs (+ level 1) body path)
- (display ")"))
- (('if _ (or ($ <type-use> _ sig) sig) consequent alternate)
- (display "(if")
- (print-block-type sig)
- (unless (null? consequent)
- (newline)
- (indent (+ level 1))
- (display "(then\n")
- (print-instrs (+ level 2) consequent
- (path-remainder path 0))
- (display ")"))
- (unless (null? alternate)
- (newline)
- (indent (+ level 1))
- (display "(else\n")
- (print-instrs (+ level 2) alternate
- (path-remainder path 1))
- (display ")"))
- (display ")"))
- (_
- (write instr))))
- (define (print-instrs level instrs path)
- (indent level)
- (let loop ((instrs instrs)
- (i 0))
- (match instrs
- (() #t)
- ((instr . rest)
- (if (here? path i)
- (begin
- (display "<<< ")
- (print-instr level instr (path-remainder path i))
- (display " >>>"))
- (print-instr level instr (path-remainder path i)))
- (unless (null? rest)
- (newline)
- (indent level)
- (loop rest (+ i 1)))))))
- (define (count-imports kind)
- (fold (lambda (i sum)
- (match i
- (($ <import> _ _ k)
- (if (eq? kind k) (+ sum 1) sum))))
- 0 (wasm-imports wasm)))
- (match path
- (('func idx . path*)
- (match (list-ref (wasm-funcs wasm) (- idx (count-imports 'func)))
- (($ <func> id ($ <type-use> _ sig) locals body)
- (format #t "(func ~a" idx)
- (print-block-type sig)
- (newline)
- (print-instrs 1 body path*)
- (display ")"))))
- (('global idx . path*)
- (match (list-ref (wasm-globals wasm) (- idx (count-imports 'global)))
- (($ <global> id ($ <global-type> mutable? type) init)
- (let ((t (val-type-repr type)))
- (format #t "(global ~a " idx)
- (write (if mutable? `(mut ,t) t))
- (newline)
- (print-instrs 1 init path*)
- (display ")")))))
- (('data idx . path*)
- (match (list-ref (wasm-datas wasm) idx)
- (($ <data> id mode mem offset init)
- (format #t "(data ~a ~a ~a ~a\n" idx mode mem offset)
- (print-instrs 1 init path*)
- (display ")"))))
- (('elem idx j . path*)
- (match (list-ref (wasm-elems wasm) idx)
- (($ <elem> id mode table type offset inits)
- (let ((t (val-type-repr type)))
- (format #t "(elem ~a ~a ~a ~a" idx mode table t)
- (when offset
- (newline)
- (print-instrs 1 offset (if (= j 0) path* invalid-path)))
- (let loop ((inits inits) (i 1))
- (match inits
- (() #t)
- ((init . rest)
- (newline)
- (print-instrs 1 init (if (= j 1) path* invalid-path))
- (loop rest (+ i 1)))))
- (display ")"))))))
- (newline))
- (define (wasm-trace path instr instance stack blocks locals)
- (let ((instr (match instr ; abbreviate blocks
- (((and (or 'block 'loop) op) _ type . _)
- `(,op ,(block-type-repr type) ...))
- (('if _ type . _)
- `(if ,(block-type-repr type) ...))
- (_ instr))))
- (define (abbrev x)
- (match x
- ((? wasm-null?) 'null)
- ((? wasm-struct?) 'struct)
- ((? wasm-array?) 'array)
- ((? wasm-func?) 'func)
- (_ x)))
- (format #t "⌄ instr: ~a\n" instr)
- (format #t " loc: ~a @ ~a\n" instance (reverse path))
- (format #t " stack: ~s\n" (map abbrev (wasm-stack-items stack)))
- (format #t " locals: ~a\n" (map abbrev (vector->list locals)))))
- (define (->wasm x)
- (match x
- ((? wasm? wasm) wasm)
- ((? validated-wasm? mod) (validated-wasm-ref mod))
- ((? wasm-instance? instance)
- (validated-wasm-ref (wasm-instance-module instance)))
- ((? hoot-module? mod)
- (validated-wasm-ref
- (wasm-instance-module
- (hoot-module-instance mod))))))
- (define-record-type <wasm-debug>
- (make-wasm-debug position instruction instance stack blocks locals)
- wasm-debug?
- (position wasm-debug-position)
- (instruction wasm-debug-instruction)
- (instance wasm-debug-instance)
- (stack wasm-debug-stack)
- (blocks wasm-debug-blocks)
- (locals wasm-debug-locals)
- (continue? wasm-debug-continue? set-wasm-debug-continue!))
- (define current-wasm-debug (make-parameter #f))
- (define-syntax-rule (when-debugging body ...)
- (if (current-wasm-debug)
- (begin body ...)
- (error "not in a WASM debugger")))
- ;; This code is based on error-string in (system repl
- ;; exception-handling) and adapted to work with Guile's new exception
- ;; objects.
- (define (error-message exn stack)
- (let ((key (exception-kind exn))
- (args (exception-args exn)))
- (call-with-output-string
- (lambda (port)
- (let ((frame (and (< 0 (vector-length stack)) (vector-ref stack 0))))
- (print-exception port frame key args))))))
- (define (enter-wasm-debugger exn)
- (let* ((tag (and (pair? (fluid-ref %stacks))
- (cdr (fluid-ref %stacks))))
- (stack (stack->vector (make-stack #t 3 tag 0 1)))
- (msg (error-message exn stack))
- (wasm-debug (make-wasm-debug (wasm-runtime-error-position exn)
- (wasm-runtime-error-instruction exn)
- (wasm-runtime-error-instance exn)
- (wasm-runtime-error-stack exn)
- (wasm-runtime-error-blocks exn)
- (wasm-runtime-error-locals exn))))
- (parameterize ((current-wasm-debug wasm-debug))
- (format #t "~a\n" msg)
- (format #t "Entering WASM debug prompt. ")
- (format #t "Type `,help wasm' for info or `,q' to continue.\n")
- (start-repl #:debug (make-debug stack 0 msg))
- (wasm-debug-continue? wasm-debug))))
- (define (wasm-step position instruction instance stack blocks locals)
- (let ((wasm-debug (make-wasm-debug (reverse position) instruction instance stack
- blocks locals)))
- (parameterize ((current-wasm-debug wasm-debug))
- (format #t "Instruction: ~a\n" instruction)
- (format #t "Location: ~a\n" (reverse position))
- (start-repl))))
- (define (reset-instruction-listener)
- (current-instruction-listener
- (lambda (position instr instance stack blocks locals) #t)))
- (define (continue)
- (set-wasm-debug-continue! (current-wasm-debug) #t)
- (throw 'quit))
- (define-meta-command ((wasm-dump wasm) repl #:optional exp)
- "wasm-dump [WASM]
- Display information about WASM, or the current WASM instance when debugging."
- (dump-wasm (->wasm
- (cond
- (exp (repl-eval repl exp))
- ((current-wasm-debug) => wasm-debug-instance)
- (else (error "no WASM object specified"))))
- #:dump-func-defs? #f))
- (define-meta-command ((wasm-trace wasm) repl exp)
- "wasm-trace EXP
- Evaluate EXP with verbose WASM tracing enabled."
- (with-wasm-error-handling
- (parameterize ((current-instruction-listener wasm-trace))
- (call-with-values (lambda () (repl-eval repl exp))
- (lambda vals
- (for-each (lambda (v) (repl-print repl v)) vals))))))
- (define-meta-command ((wasm-freq wasm) repl exp)
- "wasm-freq EXP
- Evaluate EXP and count how many times each WASM instruction is evaluated."
- (let ((count 0)
- (histogram (make-hash-table)))
- (define (wasm-stats path instr instance stack blocks locals)
- (set! count (+ count 1))
- (match instr
- ((op . _)
- (hashq-set! histogram op (+ (hashq-ref histogram op 0) 1)))))
- (with-wasm-error-handling
- (parameterize ((current-instruction-listener wasm-stats))
- (call-with-values (lambda () (repl-eval repl exp))
- (lambda vals
- (display "op\tcount\n")
- (display "--\t-----\n")
- (for-each (match-lambda
- ((op . k)
- (format #t "~a\t~a\n" op k)))
- (sort (hash-fold alist-cons '() histogram)
- (lambda (a b) (< (cdr a) (cdr b)))))
- (format #t "\n~a instructions total\n\n" count)
- (for-each (lambda (v) (repl-print repl v)) vals)))))))
- (define-meta-command ((wasm-catch wasm) repl exp)
- "wasm-catch EXP
- Catch and debug WASM runtime errors that are raised by evaluating EXP."
- (let ((thunk (repl-prepare-eval-thunk repl exp)))
- (call/ec
- (lambda (return)
- (with-exception-handler (lambda (exn)
- (if (wasm-runtime-error? exn)
- (unless (enter-wasm-debugger exn)
- (reset-instruction-listener)
- (return))
- (raise-exception exn)))
- (lambda ()
- (call-with-values (lambda () (%start-stack #t thunk))
- (lambda vals
- (reset-instruction-listener)
- (for-each (lambda (v) (repl-print repl v)) vals)))))))))
- (define-meta-command ((wasm-stack wasm) repl)
- "wasm-stack
- Print the state of the WASM stack in the current context."
- (when-debugging
- (print-stack (wasm-debug-stack (current-wasm-debug)))))
- (define-meta-command ((wasm-locals wasm) repl)
- "wasm-locals
- Print the state of the WASM locals in the current context."
- (when-debugging
- (print-locals (wasm-debug-locals (current-wasm-debug)))))
- (define-meta-command ((wasm-pos wasm) repl)
- "wasm-pos
- Highlight the instruction where WASM execution has paused."
- (when-debugging
- (let ((debug (current-wasm-debug)))
- (print-location (->wasm (wasm-debug-instance debug)) (wasm-debug-position debug)))))
- (define-meta-command ((wasm-eval wasm) repl instr)
- "wasm-eval INSTR
- Evaluate the WASM instruction INSTR in the current debug context."
- (when-debugging
- (let ((execute (@@ (wasm vm) execute)))
- (match (current-wasm-debug)
- (($ <wasm-debug> position _ instance stack blocks locals)
- (execute (repl-eval repl instr) position instance stack blocks locals))))))
- (define-meta-command ((wasm-continue wasm) repl)
- "wasm-continue
- Set WASM execution to continue without interruption until the next error."
- (when-debugging
- (reset-instruction-listener)
- (when (current-wasm-debug)
- (continue))))
- (define-meta-command ((wasm-step wasm) repl)
- "wasm-step
- Set WASM execution to pause before each instruction."
- (when-debugging
- (current-instruction-listener wasm-step)
- (when (current-wasm-debug)
- (continue))))
- ;;;
- ;;; Scheme tools
- ;;;
- (define-meta-command ((hoot-compile hoot) repl exp . opts)
- "hoot-compile EXP OPTS ...
- Compile EXP and return a Wasm module."
- (let ((exp (repl-parse repl exp))
- (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
- (repl-print repl (apply compile exp opts))))
- (define-meta-command ((hoot-compile-file hoot) repl file . opts)
- "hoot-compile-file FILE OPTS ...
- Compile FILE and return a Wasm module."
- (let ((file (repl-eval repl file))
- (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
- (repl-print repl (apply compile-file file opts))))
- (define (run-wasm-and-print-results repl wasm)
- (call-with-values (lambda ()
- (with-wasm-error-handling
- (hoot-load (hoot-instantiate wasm))))
- (lambda vals
- (for-each (lambda (val)
- (repl-print repl val))
- vals))))
- (define-meta-command ((hoot-run hoot) repl exp . opts)
- "hoot-run EXP OPTS ...
- Compile and run EXP in the Wasm interpreter."
- (let ((exp (repl-parse repl exp))
- (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
- (run-wasm-and-print-results repl (apply compile exp opts))))
- (define-meta-command ((hoot-run-file hoot) repl file . opts)
- "hoot-run FILE OPTS ...
- Compile and run FILE in the Wasm interpreter."
- (let ((file (repl-eval repl file))
- (opts (map (lambda (opt) (repl-eval repl opt)) opts)))
- (run-wasm-and-print-results repl (apply compile-file file opts))))
|