Skip to content

Commit

Permalink
interpreter/*: complete implementation of runtime functions for #1
Browse files Browse the repository at this point in the history
Rewrote the function-wrappers in `state.rkt`. Instead of using an expensive loop
for each invocation of each function at runtime, there are now just 8 wrapper
functions defined explicitly. Additionally, the final wrapper function now
implements the lookup of arguments on the stack beyond the first six.

In the process of testing, the whole byte-string thing became clearly a poor
choice. Instead of wasting any more time on it, I've revised all the byte-string
stuff to just use integers instead. As a compromise (the byte-string choice was
made to prohibit abusing the memory model), the `memory-set!` function prevents
writing integers to memory that are larger than `word-size-bits`.

Cleaned up some other comments and such, too.
  • Loading branch information
pdarragh committed Jul 6, 2022
1 parent d52146d commit 9c16d3d
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 44 deletions.
3 changes: 2 additions & 1 deletion interpreter/interp.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
[(Call (? (curry hash-has-key? runtime) external-function))
;; call the external function
(let* ([func (hash-ref runtime external-function)]
[result (func registers memory 0)] ; FIXME: stack pointer
[sp (hash-ref registers 'rsp)]
[result (func registers memory sp)]
[new-registers (hash-set registers 'rax result)])
(make-state #:with-registers new-registers))]
[(Call dst)
Expand Down
59 changes: 34 additions & 25 deletions interpreter/memory.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@
;; min-address:
;; The lowest address available.
;;
;; last-instruction-address:
;; The address of the last instruction pre-loaded into memory.
;;
;; memhash:
;; A mutable hashmap from addresses to immutable vectors of Cells.
;;
Expand All @@ -53,11 +56,12 @@
;;
;; error-on-overwrite:
;; Whether to prevent attempts by [memory-set!] to overwrite addresses
;; that currently hold non-byte-string values.
;; that were specially initialized.
;;
;; TODO: Remove transparency?
(struct Memory (max-address
min-address
last-instruction-address
memhash
handling
max-depth
Expand All @@ -75,24 +79,25 @@
;; Sets up the memory to be used during emulation. All arguments except the list
;; of instructions are optional. The instruction list must be non-empty.
;;
;; Returns two values: the next free word-aligned address, and a [Memory] struct
;; that should be passed to all the memory-related functions.
;; Returns three values: the first word-aligned address, the word-aligned
;; address of the last instruction, and a [Memory] struct that should be passed
;; to all the memory-related functions.
;;
;; instructions:
;; A list of instructions to include in the initial (highest) memory
;; addresses. Anything given in this list will be written without first
;; checking if the value is a [bytes?].
;; checking if the value is an [integer?].
;;
;; max-address:
;; The highest address available. This is the "first" address to be used,
;; rounded down to the nearest word boundary.
;;
;; default: #xffffffffffffffff
;; default: [#xffffffffffffffff]
;;
;; min-address:
;; The lowest address available. Memory cannot be written beyond this.
;;
;; default: #xffff800000000000
;; default: [#xffff800000000000]
;;
;; handling-strategy:
;; Memory can be set up to only allow a specific number of writes to a
Expand All @@ -119,9 +124,7 @@
;; Whether to throw errors when attempting to overwrite memory that was
;; allocated during initialization.
;;
;; default: #t
;;
;; TODO: Make this actually work via address, rather than type.
;; default: [#t]
(define (initialize-memory instructions
[max-address #xffffffffffffffff]
[min-address #xffff800000000000]
Expand All @@ -145,15 +148,17 @@
last-address
(Memory max-address
min-address
last-address
(make-hash address-instruction-pairs)
handling-strategy
max-depth
error-on-initialized-overwrite))))

;; Retrieves the value stored at the indicated address. If no value is present
;; in the hash table, an empty set of bytes are returned representing what would
;; be expected if our memory was not being emulated.
(define (memory-ref memory address [failure-result make-empty-bytes])
;; in the hash table, the result of [(failure-result)] is returned, representing
;; what would be expected if our memory was not being emulated. By default, the
;; [failure-result] function produces [0].
(define (memory-ref memory address [failure-result (λ () 0)])
(let ([result (hash-ref (Memory-memhash memory)
address
#f)])
Expand All @@ -177,14 +182,17 @@
(>= (memory-depth memory address)
(Memory-max-depth memory)))))

;; Determines whether the indicated address holds a special (non-byte-string)
;; value. Since instructions are written into memory during memory
;; initialization, this is a way to check if the address is holding an
;; instruction.
;; Determines whether the indicated address holds a special initialized value.
;; Since instructions are written into memory during memory initialization, this
;; is a way to check if the address is holding an instruction.
;;
;; TODO: Rename.
(define (specialized-initial-value? memory address)
(not (bytes? (memory-ref memory address))))
(>= address (Memory-last-instruction-address memory)))

;; Determines whether the indicated address actually holds a value.
;;
;; TODO: Rename for clarity.
(define (initialized? memory address)
(or (memory-ref memory address #f)
#t))
Expand All @@ -194,9 +202,11 @@
(define (memory-set! memory address tick value)
(cond
[(not (Memory? memory))
(raise-user-error 'memory-set "expected initialized memory; got ~v" memory)]
[(not (bytes? value))
(raise-user-error 'memory-set! "values to be stored in memory must be bytes")]
(raise-user-error 'memory-set! "expected initialized memory; got ~v" memory)]
[(not (integer? value))
(raise-user-error 'memory-set! "values to be stored in memory must be integers")]
[(not (= 0 (arithmetic-shift value (- (word-size-bits)))))
(raise-user-error 'memory-set! "values to be stored in memory must be no larger than ~a bits" (word-size-bits))]
[(> address (Memory-max-address memory))
(raise-user-error 'memory-set! "expected address less than ~a; got ~a" (Memory-max-address memory) address)]
[(< address (Memory-min-address memory))
Expand Down Expand Up @@ -240,9 +250,8 @@
;; [memory] is used.
;;
;; NOTE: The [proc] procedure should be a two-argument function that takes in an
;; address and a corresponding memory value. Most values will be byte strings,
;; but be careful if your address range includes any specially initialized
;; memory.
;; address and a corresponding memory value. Most values will be integers, but
;; be careful if your address range includes any specially initialized memory.
(define (memory-map proc memory [hi-address #f] [lo-address #f])
(unless hi-address
(set! hi-address (Memory-max-address memory)))
Expand All @@ -261,8 +270,8 @@
;;
;; NOTE: The [pred] predicate should be a two-argument function that takes in an
;; address and a corresponding memory value and returns a Boolean. Most values
;; will be byte strings, but be careful if your address range includes any
;; specially initialized memory.
;; will be integers, but be careful if your address range includes any specially
;; initialized memory.
(define (memory-filter pred memory [hi-address #f] [lo-address #f])
(unless hi-address
(set! hi-address (Memory-max-address memory)))
Expand Down
43 changes: 25 additions & 18 deletions interpreter/state.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -60,24 +60,31 @@
;; order for passing the first six arguments to the function, and additional
;; arguments are passed in reverse order on the stack.
(define (convert-external-function func)
(let* ([register-order '(rdi rsi rdx rcx r8 r9)]
[regc (length register-order)]
[argc (procedure-arity func)])
(cond
[(integer? argc)
(λ (registers memory sp)
(let ([args (for/list ([argn (range argc)])
(if (< argn regc)
;; Take the argument from the appropriate register.
(hash-ref registers (list-ref register-order argn))
;; Take the argument from the stack.
;; TODO: implement this!
(error 'initialize-state "external functions needing more than 6 arguments are not yet implemented")))])
(apply func args)))]
[(list? argc)
(raise-user-error
'initialize-state
"a86 currently does not support runtime functions with optional arguments; please wrap your function")])))
(match (procedure-arity func)
[0 (λ (rs m sp) (func))]
[1 (λ (rs m sp) (func (hash-ref rs 'rdi)))]
[2 (λ (rs m sp) (func (hash-ref rs 'rdi) (hash-ref rs 'rsi)))]
[3 (λ (rs m sp) (func (hash-ref rs 'rdi) (hash-ref rs 'rsi) (hash-ref rs 'rdx)))]
[4 (λ (rs m sp) (func (hash-ref rs 'rdi) (hash-ref rs 'rsi) (hash-ref rs 'rdx) (hash-ref rs 'rcx)))]
[5 (λ (rs m sp) (func (hash-ref rs 'rdi) (hash-ref rs 'rsi) (hash-ref rs 'rdx) (hash-ref rs 'rcx) (hash-ref rs 'r8)))]
[6 (λ (rs m sp) (func (hash-ref rs 'rdi) (hash-ref rs 'rsi) (hash-ref rs 'rdx) (hash-ref rs 'rcx) (hash-ref rs 'r8) (hash-ref rs 'r9)))]
[(? integer? argc)
(λ (rs m sp)
(let ([reg-args (list (hash-ref rs 'rdi) (hash-ref rs 'rsi)
(hash-ref rs 'rdx) (hash-ref rs 'rcx)
(hash-ref rs 'r8) (hash-ref rs 'r9))]
[mem-args (for/fold ([result (list)]
[sp sp]
#:result (reverse result))
([_ (range (- argc 6))])
;; Read value from memory and increment stack pointer.
(values (cons (memory-ref m sp) result)
(previous-word-aligned-address sp)))])
(apply func (append reg-args mem-args))))]
[(? list?)
(raise-user-error
'initialize-state
"a86 currently does not support runtime functions with optional arguments; please wrap your function")]))

;; Given a [Program], initializes the machine state.
(define (initialize-state program [runtime (hash)])
Expand Down

0 comments on commit 9c16d3d

Please sign in to comment.