~james/remy

feat(frontend): parameterise checked compiler resource limits

commit
75a52175806c238d0b18ff0878be652f95c5ac77 browse tree
parent
33c6eac26df0f52eff4cf919f587a3b60628ab12
author
james <j@cpl0.zip>
signature
Verified ~james signed with a key registered by ~james
date
2026-10-03 11:00 UTC

4 files changed, +62 −20

cli/driver.ML modified

@@ -26,15 +26,18 @@ fun output (ok, message) =
2626 (TextIO.output (if ok then TextIO.stdOut else TextIO.stdErr, text message);
2727 if ok then () else OS.Process.exit OS.Process.failure);
2828
29+fun emit destination ((ok, bytes), report) =
30+ if ok then (write destination bytes; output (true,report)) else output (false,report);
31+
2932 fun main () =
3033 (case CommandLine.arguments () of
3134 ["vectors", source, destination] =>
3235 (case Remy.vectors (read source) of (true, bytes) => write destination bytes | result => output result)
3336 | ["eval", source, x, y, t] => output (Remy.reference (read source) (chars x) (chars y) (chars t))
34- | [source, destination] =>
35- let val ((ok, bytes), report) = Remy.artifact (read source)
36- in if ok then (write destination bytes; output (true,report)) else output (false,report) end
37- | _ => raise Fail "usage: remy source binary | remy vectors source data | remy eval source x y t")
37+ | ["limits", depth, nodes, stack, source, destination] =>
38+ emit destination (Remy.configured (chars depth) (chars nodes) (chars stack) (read source))
39+ | [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")
3841 handle exn =>
3942 (TextIO.output (TextIO.stdErr, "remy: " ^ General.exnMessage exn ^ "\n");
4043 OS.Process.exit OS.Process.failure);

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 artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
9+export_code configured artifact vectors reference ascii ordinal in SML module_name Remy file_prefix remy
1010
1111 end

src/Frontend.thy modified

@@ -104,24 +104,45 @@ next
104104 drule close_expression; auto dest: Suc.IH)
105105 qed
106106
107-definition prepare :: "string \<Rightarrow> expression option" where
108- "prepare s = (if \<not> list_all (\<lambda>c. nat_of_char c < 128) s then None else
109- Option.bind (parse 128 [] (lex s [] [])) (\<lambda>(e,rest).
110- if rest = [] \<and> nodes e \<le> 4096 then
107+record limits =
108+ depth_budget :: nat
109+ node_budget :: nat
110+ stack_budget :: nat
111+
112+definition defaults :: limits where
113+ "defaults = \<lparr>depth_budget = 128, node_budget = 4096, stack_budget = 512\<rparr>"
114+
115+definition valid_limits :: "limits \<Rightarrow> bool" where
116+ "valid_limits lim = (depth_budget lim \<le> 128 \<and> node_budget lim \<le> 4096 \<and> stack_budget lim \<le> 512)"
117+
118+definition prepare_with :: "limits \<Rightarrow> string \<Rightarrow> expression option" where
119+ "prepare_with lim s = (if \<not> valid_limits lim \<or> \<not> list_all (\<lambda>c. nat_of_char c < 128) s then None else
120+ Option.bind (parse (depth_budget lim) [] (lex s [] [])) (\<lambda>(e,rest).
121+ if rest = [] \<and> nodes e \<le> node_budget lim then
111122 let code = compile (optimise e) [] in
112- if check code 0 = Some 1 \<and> demand (optimise e) \<le> 512 \<and> list_all encodable (native code)
123+ if check code 0 = Some 1 \<and> demand (optimise e) \<le> stack_budget lim \<and> list_all encodable (native code)
113124 then Some e else None else None))"
114125
126+definition prepare :: "string \<Rightarrow> expression option" where
127+ "prepare = prepare_with defaults"
128+
129+lemma prepare_with_scoped: "prepare_with lim s = Some e \<Longrightarrow> scoped 0 e"
130+ by (auto simp: prepare_with_def Let_def dest: parse_scoped split: if_splits bind_splits prod.splits)
131+
132+lemma prepare_with_bounded:
133+ "prepare_with lim s = Some e \<Longrightarrow> bounded (stack_budget lim) (compile (optimise e) []) 0"
134+ by (auto simp: prepare_with_def Let_def closed_demand optimise_scoped dest: parse_scoped split: if_splits bind_splits prod.splits)
135+
115136 lemma prepare_scoped: "prepare s = Some e \<Longrightarrow> scoped 0 e"
116- by (auto simp: prepare_def Let_def dest: parse_scoped split: if_splits bind_splits prod.splits)
137+ unfolding prepare_def using prepare_with_scoped by blast
117138
118139 lemma prepare_bounded:
119140 "prepare s = Some e \<Longrightarrow> bounded 512 (compile (optimise e) []) 0"
120- by (auto simp: prepare_def Let_def closed_demand optimise_scoped dest: parse_scoped split: if_splits bind_splits prod.splits)
141+ using prepare_with_bounded[where lim=defaults] by (simp add: prepare_def defaults_def)
121142
122143 lemma prepare_encodable:
123144 "prepare s = Some e \<Longrightarrow> list_all encodable (native (compile (optimise e) []))"
124- by (auto simp: prepare_def Let_def split: if_splits bind_splits prod.splits)
145+ by (auto simp: prepare_def prepare_with_def Let_def split: if_splits bind_splits prod.splits)
125146
126147 theorem prepared_correct:
127148 "prepare source = Some e \<Longrightarrow>
@@ -195,22 +216,33 @@ termination by (relation "measure (\<lambda>(fuel,names,tokens,pos). fuel)") aut
195216
196217 declare diagnose.simps [simp del]
197218
198-definition source_error :: "string \<Rightarrow> string" where
199- "source_error s = (if \<not> list_all (\<lambda>c. nat_of_char c < 128) s then ''source must contain ASCII'' @ newline else
200- case diagnose 128 [] (lex s [] []) 0 of
219+definition source_error_with :: "limits \<Rightarrow> string \<Rightarrow> string" where
220+ "source_error_with lim s = (if \<not> valid_limits lim then ''limits exceed compiler bounds'' @ newline else if \<not> list_all (\<lambda>c. nat_of_char c < 128) s then ''source must contain ASCII'' @ newline else
221+ case diagnose (depth_budget lim) [] (lex s [] []) 0 of
201222 Failed (Unexpected pos) \<Rightarrow> ''syntax error at token '' @ decimal pos @ newline
202223 | Failed (Exhausted pos) \<Rightarrow> ''parse depth limit exceeded at token '' @ decimal pos @ newline
203224 | Parsed (e,rest,pos) \<Rightarrow> if rest \<noteq> [] then ''syntax error at token '' @ decimal pos @ newline
204- else if nodes e > 4096 then ''program exceeds 4096 nodes'' @ newline
205- else if demand (optimise e) > 512 then ''evaluation stack exceeds 512 words'' @ newline
225+ else if nodes e > node_budget lim then ''program exceeds '' @ decimal (node_budget lim) @ '' nodes'' @ newline
226+ else if demand (optimise e) > stack_budget lim then ''evaluation stack exceeds '' @ decimal (stack_budget lim) @ '' words'' @ newline
206227 else ''native validation failed'' @ newline)"
207228
208-definition artifact :: "string \<Rightarrow> (bool \<times> string) \<times> string" where
209- "artifact s = (case prepare s of None \<Rightarrow> ((False,[]), source_error s)
229+definition artifact_with :: "limits \<Rightarrow> string \<Rightarrow> (bool \<times> string) \<times> string" where
230+ "artifact_with lim s = (case prepare_with lim s of None \<Rightarrow> ((False,[]), source_error_with lim s)
210231 | Some e \<Rightarrow> let opt = optimise e; bytes = executable e in
211232 ((True, map char_of_nat bytes), decimal (nodes e) @ '' nodes -> '' @ decimal (nodes opt) @
212233 '' nodes, '' @ decimal (length bytes) @ '' bytes, '' @ decimal (demand opt) @ '' stack words'' @ newline))"
213234
235+definition source_error :: "string \<Rightarrow> string" where
236+ "source_error = source_error_with defaults"
237+
238+definition artifact :: "string \<Rightarrow> (bool \<times> string) \<times> string" where
239+ "artifact = artifact_with defaults"
240+
241+definition configured :: "string \<Rightarrow> string \<Rightarrow> string \<Rightarrow> string \<Rightarrow> (bool \<times> string) \<times> string" where
242+ "configured depth count stacktext source = (case (number depth, number count, number stacktext) of
243+ (Some d,Some n,Some k) \<Rightarrow> artifact_with \<lparr>depth_budget=d, node_budget=n, stack_budget=k\<rparr> source
244+ | _ \<Rightarrow> ((False,[]),''invalid resource limits'' @ newline))"
245+
214246 definition boundary :: "nat list" where
215247 "boundary = [0,1,63,64,65,127,255,256,2 ^ 31,2 ^ 32,2 ^ 63,2 ^ 64 - 1]"
216248

tests/cli.py modified

@@ -39,6 +39,13 @@ def main():
3939 large = f'(+ {large} {large})'
4040 source.write_text(large)
4141 assert '4096 nodes' in invoke(source,binary,ok=False)
42+ source.write_text('(+ x y)')
43+ invoke('limits',4,3,2,source,binary)
44+ for depth, nodes, stack, message in [(4,2,2,'2 nodes'),(4,3,1,'1 words'),
45+ (0,3,2,'parse depth'),(129,3,2,'compiler bounds'),('bad',3,2,'invalid resource')]:
46+ binary.write_bytes(b'preserve')
47+ assert message in invoke('limits',depth,nodes,stack,source,binary,ok=False)
48+ assert binary.read_bytes() == b'preserve'
4249 source.write_text('é')
4350 assert 'ASCII' in invoke(source,binary,ok=False)
4451 for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']: