Compiler/virtual machine interpreter: Difference between revisions

Content added Content deleted
Line 7,985: Line 7,985:
data_size = load_code()
data_size = load_code()
run_vm(data_size)</lang>
run_vm(data_size)</lang>

=={{header|Racket}}==
{{trans|Common Lisp}}

This example is for ''Typed'' Racket and is practically a word for word translation of the Common Lisp. This close similarity was done on purpose, to ease comparison of the two languages.



<lang Racket>#lang typed/racket
;;;
;;; The Rosetta Code Virtual Machine, in Typed Racket.
;;;
;;; Migrated from the Common Lisp.
;;;

;;; Yes, I could compute how much memory is needed, or I could assume
;;; that the instructions are in address order. However, for *this*
;;; implementation I am going to use a large fixed-size memory and use
;;; the address fields of instructions to place the instructions.
(: executable-memory-size Positive-Fixnum)
(define executable-memory-size 65536)

;;; Similarly, I am going to have fixed size data and stack memory.
(: data-memory-size Positive-Fixnum)
(define data-memory-size 2048)
(: stack-memory-size Positive-Fixnum)
(define stack-memory-size 2048)

;;; And so I am going to have specialized types for the different
;;; kinds of memory the platform contains. Also for its "word" and
;;; register types.
(define-type Word Nonnegative-Fixnum)
(define-type Register (Boxof Word))
(define-type Executable-Memory (Mutable-Vectorof Byte))
(define-type Data-Memory (Mutable-Vectorof Word))
(define-type Stack-Memory (Mutable-Vectorof Word))

(define re-blank-line #px"^\\s*$")
(define re-parse-instr-1 #px"^\\s*(\\d+)\\s*(.*\\S)")
(define re-parse-instr-2 #px"(?i:^(\\S+)\\s*(.*))")
(define re-parse-instr-3 #px"^[[(]?([0-9-]+)")
(define re-header
#px"(?i:^\\s*Datasize\\s*:\\s*(\\d+)\\s*Strings\\s*:\\s*(\\d+))")
(define re-leading-spaces #px"^\\s*")

(define opcode-names
'("halt"
"add"
"sub"
"mul"
"div"
"mod"
"lt"
"gt"
"le"
"ge"
"eq"
"ne"
"and"
"or"
"neg"
"not"
"prtc"
"prti"
"prts"
"fetch"
"store"
"push"
"jmp"
"jz"))

(: blank-line? (String -> Boolean))
(define (blank-line? s)
(not (not (regexp-match re-blank-line s))))

(: opcode-from-name (String -> Byte))
(define (opcode-from-name s)
(let ((i (index-of opcode-names s)))
(assert i)
(cast i Byte)))

(: create-executable-memory (-> Executable-Memory))
(define (create-executable-memory)
(make-vector executable-memory-size (opcode-from-name "halt")))

(: create-data-memory (-> Data-Memory))
(define (create-data-memory)
(make-vector data-memory-size 0))

(: create-stack-memory (-> Stack-Memory))
(define (create-stack-memory)
(make-vector stack-memory-size 0))

(: create-register (-> Register))
(define (create-register)
(box 0))

(struct machine
((sp : Register) ; Stack pointer.
(ip : Register) ; Instruction pointer (that is, program counter).
(code : Executable-Memory)
(data : Data-Memory)
(stack : Stack-Memory)
(strings : (Immutable-Vectorof String))
(output : Output-Port))
#:type-name Machine
#:constructor-name %make-machine)

(: make-machine ((Immutable-Vectorof String) Output-Port -> Machine))
(define (make-machine strings outf)
(%make-machine (create-register)
(create-register)
(create-executable-memory)
(create-data-memory)
(create-stack-memory)
strings
outf))

(define-type Instruction-Data (List Word Byte (U False Word)))

(: insert-instruction (Executable-Memory Instruction-Data -> Void))
(define (insert-instruction memory instr)
(void
(match instr
((list address opcode arg)
(let ((instr-size (if arg 5 1)))
(unless (<= (+ address instr-size) executable-memory-size)
(raise-user-error
"the VM's executable memory size is exceeded"))
(vector-set! memory address opcode)
(when arg
;; Big-endian order.
(vector-set! memory (+ address 1)
(bitwise-and (arithmetic-shift arg -24) #xFF))
(vector-set! memory (+ address 2)
(bitwise-and (arithmetic-shift arg -16) #xFF))
(vector-set! memory (+ address 3)
(bitwise-and (arithmetic-shift arg -8) #xFF))
(vector-set! memory (+ address 4)
(bitwise-and arg #xFF))))))))

(: load-executable-memory (Executable-Memory
(Listof Instruction-Data) ->
Void))
(define (load-executable-memory memory instr-lst)
(let loop ((p instr-lst))
(if (null? p)
(void)
(let ((instr (car p)))
(insert-instruction memory (car p))
(loop (cdr p))))))

(: number->word (Number -> Word))
(define (number->word n)
(cast (bitwise-and (cast n Integer) #xFFFFFFFF) Word))

(: string->word (String -> Word))
(define (string->word s)
(let ((n (string->number s)))
(assert (number? n))
(number->word n)))

(: parse-instruction (String -> (U False Instruction-Data)))
(define (parse-instruction s)
(and (not (blank-line? s))
(let* ((strings (cast (regexp-match re-parse-instr-1 s)
(Listof String)))
(address (cast (string->number (second strings))
Word))
(split (cast (regexp-match re-parse-instr-2
(third strings))
(Listof String)))
(opcode-name (string-downcase (second split)))
(opcode (opcode-from-name opcode-name))
(arguments (third split))
(has-arg? (match opcode-name
((or "fetch" "store" "push" "jmp" "jz") #t)
(_ #f))))
(if has-arg?
(let* ((argstr-lst
(cast (regexp-match re-parse-instr-3 arguments)
(Listof String)))
(argstr (second argstr-lst))
(arg (string->word argstr)))
`(,address ,opcode ,arg))
`(,address ,opcode #f)))))

(: read-instructions (Input-Port -> (Listof Instruction-Data)))
(define (read-instructions inpf)
(let loop ((line (read-line inpf))
(lst (cast '() (Listof Instruction-Data))))
(if (eof-object? line)
(reverse lst)
(let ((instr (parse-instruction line)))
(loop (read-line inpf)
(if instr
(cons instr lst)
lst))))))

(: read-datasize-and-strings-count (Input-Port -> (Values Word Word)))
(define (read-datasize-and-strings-count inpf)
(let ((line (read-line inpf)))
(unless (string? line)
(raise-user-error "empty input"))
;; This is a permissive implementation.
(let* ((strings (cast (regexp-match re-header line)
(Listof String)))
(datasize (string->word (second strings)))
(strings-count (string->word (third strings))))
(values datasize strings-count))))

(: parse-string-literal (String -> String))
(define (parse-string-literal s)
;; This is a permissive implementation, but only in that it skips
;; any leading space. It does not check carefully for outright
;; mistakes.
(let* ((s (regexp-replace re-leading-spaces s ""))
(quote-mark (string-ref s 0)))
(let loop ((i 1)
(lst (cast '() (Listof Char))))
(if (char=? (string-ref s i) quote-mark)
(list->string (reverse lst))
(let ((c (string-ref s i)))
(if (char=? c #\\)
(let ((c0 (match (string-ref s (+ i 1))
(#\n #\newline)
(c1 c1))))
(loop (+ i 2) (cons c0 lst)))
(loop (+ i 1) (cons c lst))))))))

(: read-string-literals (Input-Port Word -> (Listof String)))
(define (read-string-literals inpf strings-count)
(for/list ((i (in-range strings-count)))
(let ((line (read-line inpf)))
(begin (assert (string? line))
(parse-string-literal line)))))

(: open-inpf (String -> Input-Port))
(define (open-inpf inpf-filename)
(if (string=? inpf-filename "-")
(current-input-port)
(open-input-file inpf-filename)))

(: open-outf (String -> Output-Port))
(define (open-outf outf-filename)
(if (string=? outf-filename "-")
(current-output-port)
(open-output-file outf-filename #:exists 'truncate)))

(: word-signbit? (Word -> Boolean))
(define (word-signbit? x)
;; True if and only if the sign bit is set.
(not (zero? (bitwise-and x #x80000000))))

(: word-add (Word Word -> Word))
(define (word-add x y)
;; Addition with overflow freely allowed.
(cast (bitwise-and (+ x y) #xFFFFFFFF) Word))

(: word-neg (Word -> Word))
(define (word-neg x)
;; The two's complement.
(word-add (cast (bitwise-xor x #xFFFFFFFF) Word) 1))

(: word-sub (Word Word -> Word))
(define (word-sub x y)
;; Subtraction with overflow freely allowed.
(word-add x (word-neg y)))

(: word-mul (Word Word -> Word))
(define (word-mul x y)
;; Signed multiplication.
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(let ((abs-x (if x<0 (word-neg x) x))
(abs-y (if y<0 (word-neg y) y)))
(let* ((abs-xy (cast (bitwise-and (* abs-x abs-y) #xFFFFFFFF)
Word)))
(if x<0
(if y<0 abs-xy (word-neg abs-xy))
(if y<0 (word-neg abs-xy) abs-xy))))))

(: word-div (Word Word -> Word))
(define (word-div x y)
;; The quotient after signed integer division with truncation
;; towards zero.
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(let ((abs-x (if x<0 (word-neg x) x))
(abs-y (if y<0 (word-neg y) y)))
(let* ((abs-x/y (cast (bitwise-and (quotient abs-x abs-y)
#xFFFFFFFF)
Word)))
(if x<0
(if y<0 abs-x/y (word-neg abs-x/y))
(if y<0 (word-neg abs-x/y) abs-x/y))))))

(: word-mod (Word Word -> Word))
(define (word-mod x y)
;; The remainder after signed integer division with truncation
;; towards zero.
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(let ((abs-x (if x<0 (word-neg x) x))
(abs-y (if y<0 (word-neg y) y)))
(let* ((abs-x/y (cast (bitwise-and (remainder abs-x abs-y)
#xFFFFFFFF)
Word)))
(if x<0
(if y<0 abs-x/y (word-neg abs-x/y))
(if y<0 (word-neg abs-x/y) abs-x/y))))))

(: b2i (Boolean -> (U Zero One)))
(define (b2i b)
(if b 1 0))

(: word-lt (Word Word -> Word))
(define (word-lt x y)
;; Signed comparison: is x less than y?
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(b2i (if x<0
(if y<0 (< x y) #t)
(if y<0 #f (< x y))))))

(: word-le (Word Word -> Word))
(define (word-le x y)
;; Signed comparison: is x less than or equal to y?
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(b2i (if x<0
(if y<0 (<= x y) #t)
(if y<0 #f (<= x y))))))

(: word-gt (Word Word -> Word))
(define (word-gt x y)
;; Signed comparison: is x greater than y?
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(b2i (if x<0
(if y<0 (> x y) #f)
(if y<0 #t (> x y))))))

(: word-ge (Word Word -> Word))
(define (word-ge x y)
;; Signed comparison: is x greater than or equal to y?
(let ((x<0 (word-signbit? x))
(y<0 (word-signbit? y)))
(b2i (if x<0
(if y<0 (>= x y) #f)
(if y<0 #t (>= x y))))))

(: word-eq (Word Word -> Word))
(define (word-eq x y)
;; Is x equal to y?
(b2i (= x y)))

(: word-ne (Word Word -> Word))
(define (word-ne x y)
;; Is x not equal to y?
(b2i (not (= x y))))

(: word-cmp (Word -> Word))
(define (word-cmp x)
;; The logical complement.
(b2i (zero? x)))

(: word-and (Word Word -> Word))
(define (word-and x y)
;; The logical conjunction.
(b2i (and (not (zero? x)) (not (zero? y)))))

(: word-or (Word Word -> Word))
(define (word-or x y)
;; The logical disjunction.
(b2i (or (not (zero? x)) (not (zero? y)))))

(: unop (Stack-Memory Register (Word -> Word) -> Void))
(define (unop stack sp operation)
;; Perform a unary operation on the stack.
(let ((i (unbox sp)))
(unless (<= 1 i)
(raise-user-error "stack underflow"))
(let ((x (vector-ref stack (- i 1))))
;; Note how, in contrast to Common Lisp, "operation" is not in a
;; namespace separate from that of "ordinary" values, such as
;; numbers and strings. (Which way is "better" is a matter of
;; taste, and probably depends mostly on what "functional"
;; language one learnt first. Mine was Caml Light, so I prefer
;; the Scheme way. :) )
(vector-set! stack (- i 1) (operation x)))))

(: binop (Stack-Memory Register (Word Word -> Word) -> Void))
(define (binop stack sp operation)
;; Perform a binary operation on the stack.
(let ((i (unbox sp)))
(unless (<= 2 i)
(raise-user-error "stack underflow"))
(let ((x (vector-ref stack (- i 2)))
(y (vector-ref stack (- i 1))))
(vector-set! stack (- i 2) (operation x y)))
(set-box! sp (cast (- i 1) Word))))

(: jri (Executable-Memory Register -> Void))
(define (jri code ip)
;; Jump relative immediate.
(let ((j (unbox ip)))
(unless (<= (+ j 4) executable-memory-size)
(raise-user-error "address past end of executable memory"))
;; Big-endian order.
(let* ((offset (vector-ref code (+ j 3)))
(offset (bitwise-ior
(arithmetic-shift (vector-ref code (+ j 2)) 8)
offset))
(offset (bitwise-ior
(arithmetic-shift (vector-ref code (+ j 1)) 16)
offset))
(offset (bitwise-ior
(arithmetic-shift (vector-ref code j) 24)
offset)))
(set-box! ip (word-add j (cast offset Word))))))

(: jriz (Stack-Memory Register Executable-Memory Register -> Void))
(define (jriz stack sp code ip)
;; Jump relative immediate, if zero.
(let ((i (unbox sp)))
(unless (<= 1 i)
(raise-user-error "stack underflow"))
(let ((x (vector-ref stack (- i 1))))
(set-box! sp (- i 1))
(if (zero? x)
(jri code ip)
(let ((j (unbox ip)))
(set-box! ip (cast (+ j 4) Word)))))))

(: get-immediate-value (Executable-Memory Register -> Word))
(define (get-immediate-value code ip)
(let ((j (unbox ip)))
(unless (<= (+ j 4) executable-memory-size)
(raise-user-error "address past end of executable memory"))
;; Big-endian order.
(let* ((x (vector-ref code (+ j 3)))
(x (bitwise-ior
(arithmetic-shift (vector-ref code (+ j 2)) 8)
x))
(x (bitwise-ior
(arithmetic-shift (vector-ref code (+ j 1)) 16)
x))
(x (bitwise-ior
(arithmetic-shift (vector-ref code j) 24)
x)))
(set-box! ip (cast (+ j 4) Word))
(cast x Word))))

(: pushi (Stack-Memory Register Executable-Memory Register -> Void))
(define (pushi stack sp code ip)
;; Push-immediate a value from executable memory onto the stack.
(let ((i (unbox sp)))
(unless (< i stack-memory-size)
(raise-user-error "stack overflow"))
(vector-set! stack i (get-immediate-value code ip))
(set-box! sp (cast (+ i 1) Word))))

(: fetch (Stack-Memory
Register Executable-Memory Register
Data-Memory -> Void))
(define (fetch stack sp code ip data)
;; Fetch data to the stack, using the storage location given in
;; executable memory.
(let ((i (unbox sp)))
(unless (< i stack-memory-size)
(raise-user-error "stack overflow"))
(let* ((k (get-immediate-value code ip))
(x (vector-ref data k)))
(vector-set! stack i x)
(set-box! sp (cast (+ i 1) Word)))))

(: pop-one (Stack-Memory Register -> Word))
(define (pop-one stack sp)
(let ((i (unbox sp)))
(unless (<= 1 i)
(raise-user-error "stack underflow"))
(let* ((x (vector-ref stack (- i 1))))
(set-box! sp (- i 1))
x)))

(: store (Stack-Memory
Register Executable-Memory Register
Data-Memory -> Void))
(define (store stack sp code ip data)
;; Store data from the stack, using the storage location given in
;; executable memory.
(let ((i (unbox sp)))
(unless (<= 1 i)
(raise-user-error "stack underflow"))
(let ((k (get-immediate-value code ip))
(x (pop-one stack sp)))
(vector-set! data k x))))

(: prti (Stack-Memory Register Output-Port -> Void))
(define (prti stack sp outf)
;; Print the top value of the stack, as a signed decimal value.
(let* ((n (pop-one stack sp))
(n<0 (word-signbit? n)))
(if n<0
(begin (display "-" outf)
(display (word-neg n) outf))
(display n outf))))

(: prtc (Stack-Memory Register Output-Port -> Void))
(define (prtc stack sp outf)
;; Print the top value of the stack, as a character.
(let ((c (pop-one stack sp)))
(display (integer->char c) outf)))

(: prts (Stack-Memory
Register (Immutable-Vectorof String) Output-Port -> Void))
(define (prts stack sp strings outf)
;; Print the string specified by the top of the stack.
(let* ((k (pop-one stack sp))
(s (vector-ref strings k)))
(display s outf)))

;;
;; I have written macros in the standard R6RS fashion, with a lambda
;; and syntax-case, so the examples may be widely illustrative. Racket
;; supports this style, despite (purposely) not adhering to any Scheme
;; standard.
;;
;; Some Schemes that do not provide syntax-case (CHICKEN, for
;; instance) provide alternatives that may be quite different.
;;
;; R5RS and R7RS require only syntax-rules, which cannot do what we
;; are doing here. (What we are doing is similar to using ## in a
;; modern C macro, except that the pieces are not merely raw text, and
;; they must be properly typed at every stage.)
;;
(define-syntax define-machine-binop
(lambda (stx)
(syntax-case stx ()
((_ op)
(let* ((op^ (syntax->datum #'op))
(machine-op (string-append "machine-" op^))
(machine-op (string->symbol machine-op))
(machine-op (datum->syntax stx machine-op))
(word-op (string-append "word-" op^))
(word-op (string->symbol word-op))
(word-op (datum->syntax stx word-op)))
#`(begin
(: #,machine-op (Machine -> Void))
(define (#,machine-op mach)
(binop (machine-stack mach)
(machine-sp mach)
#,word-op))))))))

(define-syntax define-machine-unop
(lambda (stx)
(syntax-case stx ()
((_ op)
(let* ((op^ (syntax->datum #'op))
(machine-op (string-append "machine-" op^))
(machine-op (string->symbol machine-op))
(machine-op (datum->syntax stx machine-op))
(word-op (string-append "word-" op^))
(word-op (string->symbol word-op))
(word-op (datum->syntax stx word-op)))
#`(begin
(: #,machine-op (Machine -> Void))
(define (#,machine-op mach)
(unop (machine-stack mach)
(machine-sp mach)
#,word-op))))))))

(define-machine-binop "add")
(define-machine-binop "sub")
(define-machine-binop "mul")
(define-machine-binop "div")
(define-machine-binop "mod")
(define-machine-binop "lt")
(define-machine-binop "gt")
(define-machine-binop "le")
(define-machine-binop "ge")
(define-machine-binop "eq")
(define-machine-binop "ne")
(define-machine-binop "and")
(define-machine-binop "or")

(define-machine-unop "neg")

(: machine-not (Machine -> Void))
(define (machine-not mach)
(unop (machine-stack mach)
(machine-sp mach)
word-cmp))

(: machine-prtc (Machine -> Void))
(define (machine-prtc mach)
(prtc (machine-stack mach)
(machine-sp mach)
(machine-output mach)))

(: machine-prti (Machine -> Void))
(define (machine-prti mach)
(prti (machine-stack mach)
(machine-sp mach)
(machine-output mach)))

(: machine-prts (Machine -> Void))
(define (machine-prts mach)
(prts (machine-stack mach)
(machine-sp mach)
(machine-strings mach)
(machine-output mach)))

(: machine-fetch (Machine -> Void))
(define (machine-fetch mach)
(fetch (machine-stack mach)
(machine-sp mach)
(machine-code mach)
(machine-ip mach)
(machine-data mach)))

(: machine-store (Machine -> Void))
(define (machine-store mach)
(store (machine-stack mach)
(machine-sp mach)
(machine-code mach)
(machine-ip mach)
(machine-data mach)))

(: machine-push (Machine -> Void))
(define (machine-push mach)
(pushi (machine-stack mach)
(machine-sp mach)
(machine-code mach)
(machine-ip mach)))

(: machine-jmp (Machine -> Void))
(define (machine-jmp mach)
(jri (machine-code mach)
(machine-ip mach)))

(: machine-jz (Machine -> Void))
(define (machine-jz mach)
(jriz (machine-stack mach)
(machine-sp mach)
(machine-code mach)
(machine-ip mach)))

(: get-opcode (Machine -> Byte))
(define (get-opcode mach)
(let ((code (machine-code mach))
(ip (machine-ip mach)))
(let ((j (unbox ip)))
(unless (< j executable-memory-size)
(raise-user-error "address past end of executable memory"))
(let ((opcode (vector-ref code j)))
(set-box! ip (cast (+ j 1) Word))
opcode))))

(: run-instruction (Machine Byte -> Void))
(define (run-instruction mach opcode)
(let ((op-mod-4 (bitwise-and opcode #x3))
(op-div-4 (arithmetic-shift opcode -2)))
(match op-div-4
(0 (match op-mod-4
(1 (machine-add mach))
(2 (machine-sub mach))
(3 (machine-mul mach))))
(1 (match op-mod-4
(0 (machine-div mach))
(1 (machine-mod mach))
(2 (machine-lt mach))
(3 (machine-gt mach))))
(2 (match op-mod-4
(0 (machine-le mach))
(1 (machine-ge mach))
(2 (machine-eq mach))
(3 (machine-ne mach))))
(3 (match op-mod-4
(0 (machine-and mach))
(1 (machine-or mach))
(2 (machine-neg mach))
(3 (machine-not mach))))
(4 (match op-mod-4
(0 (machine-prtc mach))
(1 (machine-prti mach))
(2 (machine-prts mach))
(3 (machine-fetch mach))))
(5 (match op-mod-4
(0 (machine-store mach))
(1 (machine-push mach))
(2 (machine-jmp mach))
(3 (machine-jz mach)))))))

(: run-vm (Machine -> Void))
(define (run-vm mach)
(let ((opcode-for-halt (cast (opcode-from-name "halt") Byte))
(opcode-for-add (cast (opcode-from-name "add") Byte))
(opcode-for-jz (cast (opcode-from-name "jz") Byte)))
(let loop ((opcode (get-opcode mach)))
(unless (= opcode opcode-for-halt)
(begin
(when (or (< opcode opcode-for-add)
(< opcode-for-jz opcode))
(raise-user-error "unsupported opcode"))
(run-instruction mach opcode)
(loop (get-opcode mach)))))))

(define (usage-error)
(display "Usage: vm [INPUTFILE [OUTPUTFILE]]" (current-error-port))
(newline (current-error-port))
(display "If either INPUTFILE or OUTPUTFILE is \"-\", the respective"
(current-error-port))
(display " standard I/O is used." (current-error-port))
(newline (current-error-port))
(exit 1))

(: get-filenames (-> (Values String String)))
(define (get-filenames)
(match (current-command-line-arguments)
((vector) (values "-" "-"))
((vector inpf-filename)
(values (cast inpf-filename String) "-"))
((vector inpf-filename outf-filename)
(values (cast inpf-filename String)
(cast outf-filename String)))
(_ (usage-error)
(values "" ""))))

(let-values (((inpf-filename outf-filename) (get-filenames)))
(let* ((inpf (open-inpf inpf-filename))
(outf (open-outf outf-filename)))
(let-values (((datasize strings-count)
(read-datasize-and-strings-count inpf)))
(let* ((strings
(vector->immutable-vector
(list->vector
(read-string-literals inpf strings-count))))
(instructions (read-instructions inpf))

(mach (make-machine strings outf)))

(unless (<= datasize data-memory-size)
(raise-user-error
"the VM's data memory size is exceeded"))
(load-executable-memory (machine-code mach) instructions)
(run-vm mach)

(unless (string=? inpf-filename "-")
(close-input-port inpf))
(unless (string=? outf-filename "-")
(close-output-port outf))

(exit 0)))))</lang>




=={{header|Raku}}==
=={{header|Raku}}==