theory Frontend imports X86 begin fun finish :: "string \ string list \ string list" where "finish [] out = out" | "finish word out = rev word # out" fun scan :: "string \ string \ string list \ bool \ string list" where "scan [] word out comment = rev (finish word out)" | "scan (c # cs) word out comment = (if comment then scan cs [] out (c \ CHR 0x0A \ c \ CHR 0x0D) else if c = CHR '';'' then scan cs [] (finish word out) True else if c = CHR '' '' \ c = CHR 0x09 \ c = CHR 0x0A \ c = CHR 0x0D then scan cs [] (finish word out) False else if c = CHR ''('' \ c = CHR '')'' then scan cs [] ([c] # finish word out) False else scan cs (c # word) out False)" definition lex :: "string \ string \ string list \ string list" where "lex s word out = scan s word out False" fun digit :: "char \ nat" where "digit c = (let n = nat_of_char c in if 48 \ n \ n < 58 then n - 48 else if 65 \ n \ n < 71 then n - 55 else if 97 \ n \ n < 103 then n - 87 else 16)" definition parse_digits :: "nat \ string \ nat option" where "parse_digits base s = (if s = [] \ \ list_all (\c. digit c < base) s then None else let n = foldl (\n c. min (2 ^ 64) (base * n + digit c)) 0 s in if n < 2 ^ 64 then Some n else None)" definition number :: "string \ nat option" where "number s = (case s of a # b # rest \ if a = CHR ''0'' \ (b = CHR ''x'' \ b = CHR ''X'') then parse_digits 16 rest else parse_digits 10 s | _ \ parse_digits 10 s)" lemma parse_digits_bound: "parse_digits base s = Some n \ n < 2 ^ 64" by (auto simp only: parse_digits_def Let_def option.inject option.distinct split: if_splits) theorem number_bound: "number s = Some n \ n < 2 ^ 64" using parse_digits_bound by (auto simp: number_def split: list.splits if_splits) definition valid_name :: "string \ bool" where "valid_name s = (case s of [] \ False | c # cs \ 97 \ nat_of_char c \ nat_of_char c \ 122 \ list_all (\d. (97 \ nat_of_char d \ nat_of_char d \ 122) \ (48 \ nat_of_char d \ nat_of_char d < 58)) cs \ s \ ''let'' \ s \ ''if'')" fun lookup :: "string \ string list \ nat option" where "lookup name [] = None" | "lookup name (x # xs) = (if name = x then Some 0 else map_option Suc (lookup name xs))" lemma lookup_bound: "lookup name names = Some i \ i < length names" by (induction names arbitrary: i) (auto split: if_splits option.splits) fun operator :: "string \ operator option" where "operator s = (if s = ''+'' then Some Add else if s = ''-'' then Some Sub else if s = ''*'' then Some Mul else if s = ''and'' then Some And else if s = ''or'' then Some Or else if s = ''xor'' then Some Xor else if s = ''shl'' then Some Shl else if s = ''shr'' then Some Shr else if s = ''<'' then Some Less else None)" definition atom :: "string list \ string \ expression option" where "atom names s = (case lookup s names of Some i \ Some (Variable i) | None \ if s = ''x'' then Some (Input X) else if s = ''y'' then Some (Input Y) else if s = ''t'' then Some (Input T) else map_option (Literal \ word_of_nat) (number s))" lemma atom_scoped: "atom names s = Some e \ scoped (length names) e" by (auto simp: atom_def split: option.splits if_splits dest: lookup_bound) fun close :: "expression \ string list \ (expression \ string list) option" where "close e (s # rest) = (if s = '')'' then Some (e,rest) else None)" | "close e [] = None" lemma close_expression: "close e tokens = Some (a,rest) \ a = e" by (cases tokens) (auto split: if_splits) function parse :: "nat \ string list \ string list \ (expression \ string list) option" where "parse fuel names tokens = (if fuel = 0 then None else case tokens of [] \ None | s # rest \ if s \ ''('' then map_option (\e. (e,rest)) (atom names s) else case rest of [] \ None | op # args \ if op = ''let'' then (case args of [] \ None | name # body \ if \ valid_name name then None else Option.bind (parse (fuel - 1) names body) (\(a,tail). Option.bind (parse (fuel - 1) (name # names) tail) (\(b,tail). close (Let a b) tail))) else if op = ''if'' then Option.bind (parse (fuel - 1) names args) (\(c,tail). Option.bind (parse (fuel - 1) names tail) (\(a,tail). Option.bind (parse (fuel - 1) names tail) (\(b,tail). close (Cond c a b) tail))) else Option.bind (operator op) (\f. Option.bind (parse (fuel - 1) names args) (\(a,tail). Option.bind (parse (fuel - 1) names tail) (\(b,tail). close (Binary f a b) tail))))" by pat_completeness auto termination by (relation "measure (\(fuel,names,tokens). fuel)") auto declare parse.simps [simp del] theorem parse_scoped: "parse fuel names tokens = Some (e,rest) \ scoped (length names) e" proof (induction fuel arbitrary: names tokens e rest) case 0 then show ?case by (subst (asm) parse.simps) auto next case (Suc fuel) from Suc.prems show ?case by (subst (asm) parse.simps) (auto simp del: operator.simps dest: atom_scoped split: list.splits if_splits bind_splits option.splits prod.splits; drule close_expression; auto dest: Suc.IH) qed record limits = depth_budget :: nat node_budget :: nat stack_budget :: nat definition defaults :: limits where "defaults = \depth_budget = 128, node_budget = 4096, stack_budget = 512\" definition valid_limits :: "limits \ bool" where "valid_limits lim = (depth_budget lim \ 128 \ node_budget lim \ 4096 \ stack_budget lim \ 512)" definition prepare_with :: "limits \ string \ expression option" where "prepare_with lim s = (if \ valid_limits lim \ \ list_all (\c. nat_of_char c < 128) s then None else Option.bind (parse (depth_budget lim) [] (lex s [] [])) (\(e,rest). if rest = [] \ nodes e \ node_budget lim then let code = compile (optimise e) [] in if check code 0 = Some 1 \ demand (optimise e) \ stack_budget lim \ list_all encodable (native code) then Some e else None else None))" definition prepare :: "string \ expression option" where "prepare = prepare_with defaults" lemma prepare_with_scoped: "prepare_with lim s = Some e \ scoped 0 e" by (auto simp: prepare_with_def Let_def dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_with_bounded: "prepare_with lim s = Some e \ bounded (stack_budget lim) (compile (optimise e) []) 0" by (auto simp: prepare_with_def Let_def closed_demand optimise_scoped dest: parse_scoped split: if_splits bind_splits prod.splits) lemma prepare_scoped: "prepare s = Some e \ scoped 0 e" unfolding prepare_def using prepare_with_scoped by blast lemma prepare_bounded: "prepare s = Some e \ bounded 512 (compile (optimise e) []) 0" using prepare_with_bounded[where lim=defaults] by (simp add: prepare_def defaults_def) lemma prepare_encodable: "prepare s = Some e \ list_all encodable (native (compile (optimise e) []))" by (auto simp: prepare_def prepare_with_def Let_def split: if_splits bind_splits prod.splits) lemma prepare_program: "prepare s = Some e \ list_all encodable (program e)" unfolding program_def using prepare_encodable by (auto intro: transfers_property) theorem prepared_correct: "prepare source = Some e \ execute values (compile (optimise e) []) [] = Some [evaluate e [] values]" using compile_closed prepare_scoped by blast theorem prepared_native: "prepare source = Some e \ map_option observe (run (native (compile (optimise e) [])) s) = Some (inputs s, evaluate e [] (inputs s) # stack s)" using native_expression prepare_scoped by blast theorem prepared_bytes: "prepare source = Some e \ recover (length (program e)) (executable e) = Some (program e, [195])" unfolding executable_def using recover_encode prepare_program by blast theorem prepared_stacks: "prepare source = Some e \ \frames. snapshots values (compile (optimise e) []) [] = Some frames \ list_all (\s. length s \ 512) frames" using bounded_execution[where s="[]" and limit=512] prepare_bounded by auto function decimal :: "nat \ string" where "decimal n = (if n < 10 then [char_of_nat (48+n)] else decimal (n div 10) @ [char_of_nat (48+n mod 10)])" by pat_completeness auto termination by (relation "measure id") (auto simp: div_less_dividend) definition newline :: string where "newline = [CHR 0x0A]" fun regname :: "register \ string" where "regname Rax = ''rax''" | "regname Rcx = ''rcx''" | "regname Rbx = ''rbx''" | "regname Rdx = ''rdx''" | "regname R8 = ''r8''" | "regname R9 = ''r9''" fun opname :: "operator \ string" where "opname Add = ''add''" | "opname Sub = ''sub''" | "opname Mul = ''mul''" | "opname And = ''and''" | "opname Or = ''or''" | "opname Xor = ''xor''" | "opname Shl = ''shl''" | "opname Shr = ''shr''" | "opname Less = ''less''" fun mnemonic :: "opcode \ string" where "mnemonic (Immediate v) = ''immediate rax '' @ decimal (unat v)" | "mnemonic (Push r) = ''push '' @ regname r" | "mnemonic (Pop r) = ''pop '' @ regname r" | "mnemonic (Move dst src) = ''mov '' @ regname dst @ '' '' @ regname src" | "mnemonic (Peek i) = ''peek rax stack['' @ decimal i @ '']''" | "mnemonic (Arithmetic op) = ''arithmetic '' @ opname op" | "mnemonic Conditional = ''conditional rax rcx rbx''" fun listing :: "nat \ (nat list \ opcode) list \ string" where "listing offset [] = decimal offset @ '' ret'' @ newline" | "listing offset ((bytes,op) # code) = decimal offset @ '' '' @ mnemonic op @ '' ['' @ concat (map (\b. decimal b @ '' '') bytes) @ '']'' @ newline @ listing (offset + length bytes) code" fun linear_listing :: "nat \ opcode list \ string" where "linear_listing offset [] = []" | "linear_listing offset (op # code) = decimal offset @ '' '' @ mnemonic op @ newline @ linear_listing (offset + length (encode op)) code" fun flowlisting :: "nat \ flow \ string" where "flowlisting offset (Basic code) = linear_listing offset code" | "flowlisting offset (Sequence a b) = flowlisting offset a @ flowlisting (offset + length (flowbytes a)) b" | "flowlisting offset (Choice a b) = decimal offset @ '' test rax rax'' @ newline @ decimal (offset + 3) @ '' jz '' @ decimal (offset + 14 + length (flowbytes a)) @ newline @ flowlisting (offset + 9) a @ decimal (offset + 9 + length (flowbytes a)) @ '' jmp '' @ decimal (offset + length (flowbytes (Choice a b))) @ newline @ flowlisting (offset + 14 + length (flowbytes a)) b" definition disassemble :: "string \ bool \ string" where "disassemble bytes = (case blocks (map nat_of_char bytes) of None \ (case inspect (map nat_of_char bytes) of None \ (False,''invalid native block stream'' @ newline) | Some code \ (True,flowlisting 0 code @ decimal (length (flowbytes code)) @ '' ret'' @ newline)) | Some code \ (True,listing 0 code))" definition validate :: "string \ bool \ string" where "validate bytes = (case blocks (map nat_of_char bytes) of None \ (case inspect (map nat_of_char bytes) of None \ (False,''invalid native block stream'' @ newline) | Some code \ (True,''native branch, stack and register effects verified'' @ newline)) | Some code \ if audit (map snd code) 0 = Some 0 then (True,''native stack and register effects verified'' @ newline) else (False,''invalid native stack or register effects'' @ newline))" datatype problem = Unexpected nat | Exhausted nat datatype 'a outcome = Failed problem | Parsed 'a fun parsed_bind :: "'a outcome \ ('a \ 'b outcome) \ 'b outcome" where "parsed_bind (Failed p) f = Failed p" | "parsed_bind (Parsed x) f = f x" fun close_at :: "expression \ string list \ nat \ (expression \ string list \ nat) outcome" where "close_at e [] pos = Failed (Unexpected pos)" | "close_at e (s # rest) pos = (if s = '')'' then Parsed (e,rest,Suc pos) else Failed (Unexpected pos))" lemma close_at_cursor: "close_at e tokens pos = Parsed (a,rest,nextpos) \ a = e \ nextpos = Suc pos" by (cases tokens) (auto split: if_splits) function diagnose :: "nat \ string list \ string list \ nat \ (expression \ string list \ nat) outcome" where "diagnose fuel names tokens pos = (if fuel = 0 then Failed (Exhausted pos) else case tokens of [] \ Failed (Unexpected pos) | s # rest \ if s \ ''('' then (case atom names s of None \ Failed (Unexpected pos) | Some e \ Parsed (e,rest,Suc pos)) else case rest of [] \ Failed (Unexpected (Suc pos)) | op # args \ if op = ''let'' then (case args of [] \ Failed (Unexpected (pos + 2)) | name # body \ if \ valid_name name then Failed (Unexpected (pos + 2)) else parsed_bind (diagnose (fuel - 1) names body (pos + 3)) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) (name # names) tail nextpos) (\(b,tail,lastpos). close_at (Let a b) tail lastpos))) else if op = ''if'' then parsed_bind (diagnose (fuel - 1) names args (pos + 2)) (\(c,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(b,tail,lastpos). close_at (Cond c a b) tail lastpos))) else case operator op of None \ Failed (Unexpected (Suc pos)) | Some f \ parsed_bind (diagnose (fuel - 1) names args (pos + 2)) (\(a,tail,nextpos). parsed_bind (diagnose (fuel - 1) names tail nextpos) (\(b,tail,lastpos). close_at (Binary f a b) tail lastpos)))" by pat_completeness auto termination by (relation "measure (\(fuel,names,tokens,pos). fuel)") auto declare diagnose.simps [simp del] definition source_error_with :: "limits \ string \ string" where "source_error_with lim s = (if \ valid_limits lim then ''limits exceed compiler bounds'' @ newline else if \ list_all (\c. nat_of_char c < 128) s then ''source must contain ASCII'' @ newline else case diagnose (depth_budget lim) [] (lex s [] []) 0 of Failed (Unexpected pos) \ ''syntax error at token '' @ decimal pos @ newline | Failed (Exhausted pos) \ ''parse depth limit exceeded at token '' @ decimal pos @ newline | Parsed (e,rest,pos) \ if rest \ [] then ''syntax error at token '' @ decimal pos @ newline else if nodes e > node_budget lim then ''program exceeds '' @ decimal (node_budget lim) @ '' nodes'' @ newline else if demand (optimise e) > stack_budget lim then ''evaluation stack exceeds '' @ decimal (stack_budget lim) @ '' words'' @ newline else ''native validation failed'' @ newline)" definition artifact_with :: "limits \ string \ (bool \ string) \ string" where "artifact_with lim s = (case prepare_with lim s of None \ ((False,[]), source_error_with lim s) | Some e \ let opt = optimise e; code = branching e; bytes = flowbytes code @ [195] in if flowaudit code 0 = Some 0 \ flowvalid code then ((True, map char_of_nat bytes), decimal (nodes e) @ '' nodes -> '' @ decimal (nodes opt) @ '' nodes, '' @ decimal (length bytes) @ '' bytes, '' @ decimal (demand opt) @ '' stack words'' @ newline) else ((False,[]),''allocated code exceeds native limits'' @ newline))" definition source_error :: "string \ string" where "source_error = source_error_with defaults" definition artifact :: "string \ (bool \ string) \ string" where "artifact = artifact_with defaults" theorem artifact_execution: assumes "artifact source = ((True,bytes),report)" shows "\e d. prepare source = Some e \ bytes = map char_of_nat (flowbytes (branching e) @ [195]) \ executes64 (flowbytes (branching e)) c d \ registers (state64 d) Rax = evaluate e [] (inputs (state64 c)) \ stack (state64 d) = stack (state64 c) \ inputs (state64 d) = inputs (state64 c)" proof - obtain e where e: "prepare source = Some e" "flowvalid (branching e)" "bytes = map char_of_nat (flowbytes (branching e) @ [195])" using assms by (auto simp: artifact_def artifact_with_def prepare_def Let_def split: option.splits if_splits) show ?thesis using compiled_execution[OF prepare_scoped[OF e(1)] e(2), of c] e by blast qed definition baseline :: "string \ (bool \ string) \ string" where "baseline s = (case prepare s of None \ ((False,[]),source_error s) | Some e \ ((True,map char_of_nat (executable e)),''stack backend'' @ newline))" definition configured :: "string \ string \ string \ string \ (bool \ string) \ string" where "configured depth count stacktext source = (case (number depth, number count, number stacktext) of (Some d,Some n,Some k) \ artifact_with \depth_budget=d, node_budget=n, stack_budget=k\ source | _ \ ((False,[]),''invalid resource limits'' @ newline))" definition boundary :: "nat list" where "boundary = [0,1,63,64,65,127,255,256,2 ^ 31,2 ^ 32,2 ^ 63,2 ^ 64 - 1]" definition sample :: "nat \ input \ pixel" where "sample i = (\v. word_of_nat (if i < 12 then boundary ! ((i + (case v of X \ 0 | Y \ 3 | T \ 7)) mod 12) else case v of X \ (17*i) mod 257 | Y \ (29*i) mod 131 | T \ 13*i))" definition row :: "expression \ nat \ nat list" where "row e i = concat (map (\v. octets 8 (unat v)) [sample i X, sample i Y, sample i T, evaluate e [] (sample i)])" definition vectors :: "string \ bool \ string" where "vectors s = (case prepare s of None \ (False, source_error s) | Some e \ (True, map char_of_nat (concat (map (row e) [0..<512]))))" definition reference :: "string \ string \ string \ string \ bool \ string" where "reference source x y t = (case prepare source of None \ (False,source_error source) | Some e \ case (number x, number y, number t) of (Some a,Some b,Some c) \ (True, decimal (unat (evaluate e [] (\v. word_of_nat (case v of X \ a | Y \ b | T \ c)))) @ newline) | _ \ (False,''invalid input'' @ newline))" definition initial64 :: "nat \ nat \ nat \ processor" where "initial64 x y t = \state64 = \registers = (\r. word_of_nat (if r = R8 then x else if r = R9 then y else if r = Rdx then t else 0)), stack = [0]\, pointer64 = 65536, zero64 = None, carry64 = None\" definition simulate :: "string \ string \ string \ string \ bool \ string" where "simulate code x y t = (if \ fst (validate code) then (False,''invalid native image'' @ newline) else case (number x,number y,number t) of (Some a,Some b,Some c) \ (case simulate64 (Suc (length code)) (map nat_of_char code) (initial64 a b c) of None \ (False,''unsupported or incomplete x86 execution'' @ newline) | Some q \ if stack (state64 q) = [] then (True,decimal (unat (registers (state64 q) Rax)) @ newline) else (False,''return frame was not consumed'' @ newline)) | _ \ (False,''invalid input'' @ newline))" end