Add seed RISC-V decoder

ober

da2b1f1dc79156f072e15cf5122200b8180de3b5

diff --git a/Makefile b/Makefile
index 5b045b0..a0539dc 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@ test-binary: binary
 	./$(BINARY) headers ./$(BINARY)
 	./$(BINARY) raw --arch x86-64 --syntax intel --hex '55 48 89 e5 c3'
 	./$(BINARY) raw --arch aarch64 --hex '20 00 80 d2 c0 03 5f d6'
+	./$(BINARY) raw --arch riscv64 --hex '13 01 01 ff 67 80 00 00'
 	$(SCHEME) --libdirs $(LIBDIRS) --script tests/write-raw-bytes.ss ./.jasm-raw.bin
 	./$(BINARY) raw --arch x86-64 --syntax intel --max-bytes 2 --file ./.jasm-raw.bin
 	$(SCHEME) --libdirs $(LIBDIRS) --script tests/write-sample-elf.ss ./.jasm-sample.o
diff --git a/docs/plan.md b/docs/plan.md
index c41ea56..7528545 100644
--- a/docs/plan.md
+++ b/docs/plan.md
@@ -38,6 +38,8 @@ reference material and parity targets.
   - `aarch64`: common move, add/sub, load/store, return instructions.
   - `mips`, `mipsel`, `mips64`, `mips64el`: seed fixed-width raw-byte decoding
     for common integer/load/store/branch/jump instructions.
+  - `riscv32`, `riscv64`: seed RV32/RV64 base integer decoding for common
+    arithmetic, load/store, branch, jump, and system instructions.
 - Tests cover the initial decoder slice.
 - `(jasm object elf)` can parse ELF32/ELF64 headers and section tables from a
   bytevector or file, map supported machines to decoder architectures, discover
@@ -387,8 +389,9 @@ RISC-V is a good third architecture because its encoding is regular.
 
 Required:
 
-- RV32 and RV64.
-- Base integer ISA.
+- RV32 and RV64. Initial raw decoder support is implemented.
+- Base integer ISA. Initial arithmetic/load/store/branch/jump/system coverage
+  is implemented.
 - compressed `C` extension.
 - `M`, `A`, `F`, `D`.
 - vector extension later.
@@ -696,7 +699,7 @@ Status: started.
 
 - RV32/RV64 base integer.
 - compressed extension.
-- ELF integration.
+- ELF integration. Initial `EM_RISCV` mapping is implemented.
 
 ### Milestone 8: MIPS/MIPS64 Baseline
 
diff --git a/jasm b/jasm
index c66bc20..bca0118 100755
Binary files a/jasm and b/jasm differ
diff --git a/lib/jasm/disasm.ss b/lib/jasm/disasm.ss
index 9b91d84..c1f2715 100644
--- a/lib/jasm/disasm.ss
+++ b/lib/jasm/disasm.ss
@@ -20,7 +20,7 @@
           (jasm instruction)
           (jasm format))
 
-  (define supported-architectures '(x86-64 aarch64 mips mipsel mips64 mips64el))
+  (define supported-architectures '(x86-64 aarch64 mips mipsel mips64 mips64el riscv32 riscv64))
 
   (define architecture-aliases
     '((x86-64 . x86-64)
@@ -36,7 +36,15 @@
       (mips32el . mipsel)
       (mips64 . mips64)
       (mips64eb . mips64)
-      (mips64el . mips64el)))
+      (mips64el . mips64el)
+      (riscv . riscv64)
+      (risc-v . riscv64)
+      (riscv32 . riscv32)
+      (risc-v32 . riscv32)
+      (rv32 . riscv32)
+      (riscv64 . riscv64)
+      (risc-v64 . riscv64)
+      (rv64 . riscv64)))
 
   (define (normalize-architecture arch)
     (let* ([sym (cond
@@ -152,6 +160,11 @@
   (define (signed16 x)
     (if (>= x #x8000) (- x #x10000) x))
 
+  (define (sign-extend x width)
+    (let ([sign (expt 2 (- width 1))]
+          [limit (expt 2 width)])
+      (if (>= x sign) (- x limit) x)))
+
   (define (bits n hi lo)
     (modulo (quotient n (expt 2 lo)) (expt 2 (+ 1 (- hi lo)))))
 
@@ -560,11 +573,225 @@
                      (mips-unknown arch bv offset address remaining)))]
               [else (mips-unknown arch bv offset address remaining)])))))
 
+  (define riscv-reg-abi
+    '#("zero" "ra" "sp" "gp" "tp" "t0" "t1" "t2"
+       "s0" "s1" "a0" "a1" "a2" "a3" "a4" "a5"
+       "a6" "a7" "s2" "s3" "s4" "s5" "s6" "s7"
+       "s8" "s9" "s10" "s11" "t3" "t4" "t5" "t6"))
+
+  (define (riscv64? arch)
+    (eq? (normalize-architecture arch) 'riscv64))
+
+  (define (riscv-reg n options)
+    (if (eq? (option-ref options 'registers 'abi) 'numeric)
+        (string-append "x" (number->string n))
+        (vector-ref riscv-reg-abi n)))
+
+  (define (riscv-unknown arch bv offset address remaining)
+    (if (< remaining 4)
+        (make-instruction arch address 1
+          (string-append ".byte\t0x" (byte->hex2 (bv-u8 bv offset)))
+          (bv-slice bv offset 1))
+        (let ([word (bv-u32-le bv offset)])
+          (make-instruction arch address 4
+            (string-append ".word\t0x" (number->hex word))
+            (bv-slice bv offset 4)))))
+
+  (define (riscv-r mnemonic rd rs1 rs2 options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rd options) ", "
+                   (riscv-reg rs1 options) ", "
+                   (riscv-reg rs2 options)))
+
+  (define (riscv-i mnemonic rd rs1 imm options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rd options) ", "
+                   (riscv-reg rs1 options) ", "
+                   (number->string imm)))
+
+  (define (riscv-load mnemonic rd rs1 imm options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rd options) ", "
+                   (number->string imm) "("
+                   (riscv-reg rs1 options) ")"))
+
+  (define (riscv-store mnemonic rs2 rs1 imm options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rs2 options) ", "
+                   (number->string imm) "("
+                   (riscv-reg rs1 options) ")"))
+
+  (define (riscv-branch mnemonic rs1 rs2 target options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rs1 options) ", "
+                   (riscv-reg rs2 options) ", "
+                   (hex-number target)))
+
+  (define (riscv-u mnemonic rd imm options)
+    (string-append mnemonic "\t"
+                   (riscv-reg rd options) ", "
+                   (hex-number imm)))
+
+  (define (riscv-jal rd target options)
+    (string-append "jal\t" (riscv-reg rd options) ", " (hex-number target)))
+
+  (define (riscv-imm-i word)
+    (sign-extend (bits word 31 20) 12))
+
+  (define (riscv-imm-s word)
+    (sign-extend (+ (* (bits word 31 25) #x20)
+                    (bits word 11 7))
+                 12))
+
+  (define (riscv-imm-b word)
+    (sign-extend (+ (* (bits word 31 31) #x1000)
+                    (* (bits word 7 7) #x800)
+                    (* (bits word 30 25) #x20)
+                    (* (bits word 11 8) #x2))
+                 13))
+
+  (define (riscv-imm-u word)
+    (bitwise-and word #xfffff000))
+
+  (define (riscv-imm-j word)
+    (sign-extend (+ (* (bits word 31 31) #x100000)
+                    (* (bits word 19 12) #x1000)
+                    (* (bits word 20 20) #x800)
+                    (* (bits word 30 21) #x2))
+                 21))
+
+  (define (decode-riscv arch bv offset address options)
+    (let* ([arch (normalize-architecture arch)]
+           [len (bytevector-length bv)]
+           [remaining (- len offset)])
+      (if (< remaining 4)
+          (riscv-unknown arch bv offset address remaining)
+          (let* ([word (bv-u32-le bv offset)]
+                 [opcode (bits word 6 0)]
+                 [rd (bits word 11 7)]
+                 [funct3 (bits word 14 12)]
+                 [rs1 (bits word 19 15)]
+                 [rs2 (bits word 24 20)]
+                 [funct7 (bits word 31 25)])
+            (cond
+              [(= word #x00000073)
+               (make-instruction arch address 4 "ecall" (bv-slice bv offset 4))]
+              [(= word #x00100073)
+               (make-instruction arch address 4 "ebreak" (bv-slice bv offset 4))]
+              [(= word #x00000013)
+               (make-instruction arch address 4 "nop" (bv-slice bv offset 4))]
+              [(= word #x00008067)
+               (make-instruction arch address 4 "ret" (bv-slice bv offset 4))]
+              [(= opcode #x33)
+               (let ([mnemonic
+                      (cond
+                        [(and (= funct3 0) (= funct7 #x00)) "add"]
+                        [(and (= funct3 0) (= funct7 #x20)) "sub"]
+                        [(and (= funct3 1) (= funct7 #x00)) "sll"]
+                        [(and (= funct3 2) (= funct7 #x00)) "slt"]
+                        [(and (= funct3 3) (= funct7 #x00)) "sltu"]
+                        [(and (= funct3 4) (= funct7 #x00)) "xor"]
+                        [(and (= funct3 5) (= funct7 #x00)) "srl"]
+                        [(and (= funct3 5) (= funct7 #x20)) "sra"]
+                        [(and (= funct3 6) (= funct7 #x00)) "or"]
+                        [(and (= funct3 7) (= funct7 #x00)) "and"]
+                        [else #f])])
+                 (if mnemonic
+                     (make-instruction arch address 4
+                       (riscv-r mnemonic rd rs1 rs2 options)
+                       (bv-slice bv offset 4))
+                     (riscv-unknown arch bv offset address remaining)))]
+              [(= opcode #x13)
+               (let* ([imm (riscv-imm-i word)]
+                      [shamt (bits word (if (riscv64? arch) 25 24) 20)]
+                      [mnemonic
+                       (case funct3
+                         [(0) "addi"]
+                         [(2) "slti"]
+                         [(3) "sltiu"]
+                         [(4) "xori"]
+                         [(6) "ori"]
+                         [(7) "andi"]
+                         [(1) (and (= funct7 #x00) "slli")]
+                         [(5) (cond [(= funct7 #x00) "srli"]
+                                    [(= funct7 #x20) "srai"]
+                                    [else #f])]
+                         [else #f])])
+                 (if mnemonic
+                     (make-instruction arch address 4
+                       (if (or (= funct3 1) (= funct3 5))
+                           (riscv-i mnemonic rd rs1 shamt options)
+                           (riscv-i mnemonic rd rs1 imm options))
+                       (bv-slice bv offset 4))
+                     (riscv-unknown arch bv offset address remaining)))]
+              [(= opcode #x03)
+               (let ([mnemonic
+                      (case funct3
+                        [(0) "lb"]
+                        [(1) "lh"]
+                        [(2) "lw"]
+                        [(3) (and (riscv64? arch) "ld")]
+                        [(4) "lbu"]
+                        [(5) "lhu"]
+                        [(6) (and (riscv64? arch) "lwu")]
+                        [else #f])])
+                 (if mnemonic
+                     (make-instruction arch address 4
+                       (riscv-load mnemonic rd rs1 (riscv-imm-i word) options)
+                       (bv-slice bv offset 4))
+                     (riscv-unknown arch bv offset address remaining)))]
+              [(= opcode #x23)
+               (let ([mnemonic
+                      (case funct3
+                        [(0) "sb"]
+                        [(1) "sh"]
+                        [(2) "sw"]
+                        [(3) (and (riscv64? arch) "sd")]
+                        [else #f])])
+                 (if mnemonic
+                     (make-instruction arch address 4
+                       (riscv-store mnemonic rs2 rs1 (riscv-imm-s word) options)
+                       (bv-slice bv offset 4))
+                     (riscv-unknown arch bv offset address remaining)))]
+              [(= opcode #x63)
+               (let ([mnemonic
+                      (case funct3
+                        [(0) "beq"]
+                        [(1) "bne"]
+                        [(4) "blt"]
+                        [(5) "bge"]
+                        [(6) "bltu"]
+                        [(7) "bgeu"]
+                        [else #f])])
+                 (if mnemonic
+                     (make-instruction arch address 4
+                       (riscv-branch mnemonic rs1 rs2 (+ address (riscv-imm-b word)) options)
+                       (bv-slice bv offset 4))
+                     (riscv-unknown arch bv offset address remaining)))]
+              [(= opcode #x37)
+               (make-instruction arch address 4
+                 (riscv-u "lui" rd (riscv-imm-u word) options)
+                 (bv-slice bv offset 4))]
+              [(= opcode #x17)
+               (make-instruction arch address 4
+                 (riscv-u "auipc" rd (riscv-imm-u word) options)
+                 (bv-slice bv offset 4))]
+              [(= opcode #x6f)
+               (make-instruction arch address 4
+                 (riscv-jal rd (+ address (riscv-imm-j word)) options)
+                 (bv-slice bv offset 4))]
+              [(= opcode #x67)
+               (make-instruction arch address 4
+                 (riscv-load "jalr" rd rs1 (riscv-imm-i word) options)
+                 (bv-slice bv offset 4))]
+              [else (riscv-unknown arch bv offset address remaining)])))))
+
   (define (decode-instruction arch bv offset address options)
     (case (normalize-architecture arch)
       [(x86-64) (decode-x86-64 bv offset address options)]
       [(aarch64) (decode-aarch64 bv offset address options)]
       [(mips mipsel mips64 mips64el) (decode-mips arch bv offset address options)]
+      [(riscv32 riscv64) (decode-riscv arch bv offset address options)]
       [else (error 'decode-instruction "decoder not implemented for architecture" arch)]))
 
   (define (disassemble-bytevector arch bv . maybe-options)
diff --git a/lib/jasm/object/elf.ss b/lib/jasm/object/elf.ss
index bd7abb6..643da48 100644
--- a/lib/jasm/object/elf.ss
+++ b/lib/jasm/object/elf.ss
@@ -218,6 +218,10 @@
          (case (elf-object-class obj)
            [(elf64) (if (eq? (elf-object-endian obj) 'little) 'mips64el 'mips64)]
            [else (if (eq? (elf-object-endian obj) 'little) 'mipsel 'mips)])]
+        [(243)
+         (case (elf-object-class obj)
+           [(elf64) 'riscv64]
+           [else 'riscv32])]
         [else (error 'elf-object-architecture "unsupported ELF machine" machine)])))
 
   (define (read-c-string bv offset limit)
diff --git a/main.ss b/main.ss
index 1c75345..af7174d 100644
--- a/main.ss
+++ b/main.ss
@@ -8,7 +8,7 @@
 
 (define (usage)
   (display "Usage:\n")
-  (display "  jasm raw --arch <x86-64|aarch64|mips|mipsel|mips64|mips64el> [--syntax att|intel] [--address n] [--max-bytes n] [--stop-address n] (--hex <bytes>|--file path)\n")
+  (display "  jasm raw --arch <x86-64|aarch64|mips|mipsel|mips64|mips64el|riscv32|riscv64> [--syntax att|intel] [--address n] [--max-bytes n] [--stop-address n] (--hex <bytes>|--file path)\n")
   (display "  jasm object [--arch <arch>] [--syntax att|intel] [--section name] <file>\n")
   (display "  jasm headers <file>\n")
   (display "  jasm sections <file>\n")
diff --git a/tests/test-jasm.ss b/tests/test-jasm.ss
index d3e6fbf..1275906 100644
--- a/tests/test-jasm.ss
+++ b/tests/test-jasm.ss
@@ -225,6 +225,10 @@
   (normalize-architecture 'mips32el)
   'mipsel)
 
+(test "normalize architecture rv64"
+  (normalize-architecture 'rv64)
+  'riscv64)
+
 (test "architecture endianness"
   (list (architecture-endianness 'mips)
         (architecture-endianness 'mips64el))
@@ -293,6 +297,13 @@
       result))
   '(elf64 ".text"))
 
+(test "elf riscv64 architecture"
+  (let ([bv (sample-elf64le)])
+    (bytevector-u8-set! bv 18 #xf3)
+    (bytevector-u8-set! bv 19 0)
+    (elf-object-architecture (read-elf-bytevector bv)))
+  'riscv64)
+
 (test "mach-o section discovery"
   (let* ([obj (read-macho-bytevector (sample-macho64le))]
          [text (macho-section-by-name obj "__TEXT,__text")])
@@ -431,6 +442,26 @@
   (one-text 'mips "27 bd ff e0" '((registers . numeric)))
   "addiu\t$29, $29, -32")
 
+(test "riscv addi"
+  (one-text 'riscv64 "13 01 01 ff")
+  "addi\tsp, sp, -16")
+
+(test "riscv sd"
+  (one-text 'riscv64 "23 34 11 00")
+  "sd\tra, 8(sp)")
+
+(test "riscv ld"
+  (one-text 'riscv64 "83 30 81 00")
+  "ld\tra, 8(sp)")
+
+(test "riscv ret"
+  (one-text 'riscv64 "67 80 00 00")
+  "ret")
+
+(test "riscv numeric registers"
+  (one-text 'riscv64 "13 01 01 ff" '((registers . numeric)))
+  "addi\tx2, x2, -16")
+
 (test "format string"
   (disassemble->string 'x86-64
     (hex-string->bytevector "55 c3")