~james/remy

feat(cli): disassemble emitted native instruction blocks

commit
5ccdadce3e9d6b55c645dcb7057b9d5c747f501a browse tree
parent
44cdd77b156b101cebd114088595dd4f1a0203fb
author
james <j@cpl0.zip>
signature
Verified ~james signed with a key registered by ~james
date
2026-10-18 11:00 UTC

5 files changed, +51 −2

cli/driver.ML modified

@@ -36,8 +36,9 @@ fun main () =
3636 | ["eval", source, x, y, t] => output (Remy.reference (read source) (chars x) (chars y) (chars t))
3737 | ["limits", depth, nodes, stack, source, destination] =>
3838 emit destination (Remy.configured (chars depth) (chars nodes) (chars stack) (read source))
39+ | ["disassemble", binary] => output (Remy.disassemble (read binary))
3940 | [source, destination] => emit destination (Remy.artifact (read source))
40- | _ => raise Fail "usage: remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t")
41+ | _ => raise Fail "usage: remy disassemble binary | remy limits depth nodes stack source binary | remy source binary | remy vectors source data | remy eval source x y t")
4142 handle exn =>
4243 (TextIO.output (TextIO.stdErr, "remy: " ^ General.exnMessage exn ^ "\n");
4344 OS.Process.exit OS.Process.failure);

src/Encoding.thy modified

@@ -233,6 +233,16 @@ theorem recover_encode:
233233 recover (length ops) (concat (map encode ops) @ tail) = Some (ops,tail)"
234234 by (induction ops) (simp_all add: decode_encode)
235235
236+function blocks :: "nat list \<Rightarrow> (nat list \<times> opcode) list option" where
237+ "blocks bs = (if bs = [195] then Some [] else
238+ Option.bind (decode bs) (\<lambda>(op,rest).
239+ map_option (\<lambda>code. (take (length bs - length rest) bs,op) # code) (blocks rest)))"
240+ by pat_completeness auto
241+termination
242+ by (relation "measure length") (auto dest: decode_progress)
243+
244+declare blocks.simps [simp del]
245+
236246 definition executable :: "expression \<Rightarrow> nat list" where
237247 "executable e = concat (map encode (program e)) @ [195]"
238248

src/Export.thy modified

@@ -6,6 +6,6 @@ definition ascii :: "integer \<Rightarrow> char" where "ascii = char_of"
66
77 definition ordinal :: "char \<Rightarrow> integer" where "ordinal = of_char"
88
9-export_code configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
9+export_code disassemble configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
1010
1111 end

src/Frontend.thy modified

@@ -179,6 +179,35 @@ termination by (relation "measure id") (auto simp: div_less_dividend)
179179
180180 definition newline :: string where "newline = [CHR 0x0A]"
181181
182+fun regname :: "register \<Rightarrow> string" where
183+ "regname Rax = ''rax''" | "regname Rcx = ''rcx''" | "regname Rbx = ''rbx''"
184+| "regname Rdx = ''rdx''" | "regname R8 = ''r8''" | "regname R9 = ''r9''"
185+
186+fun opname :: "operator \<Rightarrow> string" where
187+ "opname Add = ''add''" | "opname Sub = ''sub''" | "opname Mul = ''mul''"
188+| "opname And = ''and''" | "opname Or = ''or''" | "opname Xor = ''xor''"
189+| "opname Shl = ''shl''" | "opname Shr = ''shr''" | "opname Less = ''less''"
190+
191+fun mnemonic :: "opcode \<Rightarrow> string" where
192+ "mnemonic (Immediate v) = ''immediate rax '' @ decimal (unat v)"
193+| "mnemonic (Push r) = ''push '' @ regname r"
194+| "mnemonic (Pop r) = ''pop '' @ regname r"
195+| "mnemonic (Move dst src) = ''mov '' @ regname dst @ '' '' @ regname src"
196+| "mnemonic (Peek i) = ''peek rax stack['' @ decimal i @ '']''"
197+| "mnemonic (Arithmetic op) = ''arithmetic '' @ opname op"
198+| "mnemonic Conditional = ''conditional rax rcx rbx''"
199+
200+fun listing :: "nat \<Rightarrow> (nat list \<times> opcode) list \<Rightarrow> string" where
201+ "listing offset [] = decimal offset @ '' ret'' @ newline"
202+| "listing offset ((bytes,op) # code) = decimal offset @ '' '' @ mnemonic op @
203+ '' ['' @ concat (map (\<lambda>b. decimal b @ '' '') bytes) @ '']'' @ newline @
204+ listing (offset + length bytes) code"
205+
206+definition disassemble :: "string \<Rightarrow> bool \<times> string" where
207+ "disassemble bytes = (case blocks (map nat_of_char bytes) of
208+ None \<Rightarrow> (False,''invalid native block stream'' @ newline)
209+ | Some code \<Rightarrow> (True,listing 0 code))"
210+
182211 datatype problem = Unexpected nat | Exhausted nat
183212
184213 datatype 'a outcome = Failed problem | Parsed 'a

tests/cli.py modified

@@ -46,6 +46,15 @@ def main():
4646 binary.write_bytes(b'preserve')
4747 assert message in invoke('limits',depth,nodes,stack,source,binary,ok=False)
4848 assert binary.read_bytes() == b'preserve'
49+ source.write_text('(+ x y)')
50+ invoke(source,binary)
51+ listing = invoke('disassemble',binary)
52+ assert 'mov rax r9' in listing and 'arithmetic add' in listing and 'ret' in listing
53+ for malformed in [b'',bytes([195,0]),bytes([72,199,192,0]),bytes([80])]:
54+ binary.write_bytes(malformed)
55+ assert 'invalid native' in invoke('disassemble',binary,ok=False)
56+ binary.write_bytes(bytes([72,184,42,0,0,0,0,0,0,0,195]))
57+ assert '10 ret' in invoke('disassemble',binary)
4958 source.write_text('é')
5059 assert 'ASCII' in invoke(source,binary,ok=False)
5160 for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']: