extend source and target calculi with observable tick effects + add invariant checks for relation preservation and target arity/shape

This commit is contained in:
2026-04-27 12:51:47 +00:00
parent 7f024737cf
commit 31596491f0
18 changed files with 395 additions and 44 deletions
+4 -1
View File
@@ -8,6 +8,10 @@ the target language makes representation explicit and forces each transition to
the target calculus is intentionally lower level than the source and trades abstraction for control over layout and calling. it has decent tuple and sum representations and uses explicit box and unbox operations to mediate between them. equality on integers and booleans is primitive rather than encoded as well as recursive binding being built in rather than derived the target calculus is intentionally lower level than the source and trades abstraction for control over layout and calling. it has decent tuple and sum representations and uses explicit box and unbox operations to mediate between them. equality on integers and booleans is primitive rather than encoded as well as recursive binding being built in rather than derived
the audit path now records pass-boundary observations for source, specialisation, inlining, effect rewriting, and representation lowering. each corpus case is checked against relation preservation, target arity/shape validation, termination class preservation, and effect trace preservation. unsafe profiles intentionally refute these invariants so optimiser bugs show up as small source/target deltas rather than vague failures.
the source calculus also has an explicit `tick` effect. this is enough to expose unsound dce-style rewrites: eliminating a dead binding is only valid when the eliminated term is effect free.
## example(s) ## example(s)
a representation exposure witness starts from a polymorphic constant false term a representation exposure witness starts from a polymorphic constant false term
@@ -33,4 +37,3 @@ TArrow (TPair (TInt, TBool), TPair (TInt, TBool))
``` ```
the target worker receives `RInt` and `RBool` while the wrapper preserves the boxed source interface the target worker receives `RInt` and `RBool` while the wrapper preserves the boxed source interface
+15 -4
View File
@@ -7,7 +7,7 @@ type profile = {
} }
let safe_flags = let safe_flags =
{ Pipeline.default_flags with unsafe_repr_eq = false; unsafe_strict_unroll = false } { Pipeline.default_flags with unsafe_repr_eq = false; unsafe_strict_unroll = false; unsafe_effect_drop = false }
let profiles = let profiles =
[ [
@@ -17,17 +17,28 @@ let profiles =
{ name = "unsafe-inline"; flags = { safe_flags with unsafe_repr_eq = true }; relation = Relation.Boxed_unboxed }; { name = "unsafe-inline"; flags = { safe_flags with unsafe_repr_eq = true }; relation = Relation.Boxed_unboxed };
{ name = "unsafe-unbox"; flags = Pipeline.default_flags; relation = Relation.Boxed_unboxed }; { name = "unsafe-unbox"; flags = Pipeline.default_flags; relation = Relation.Boxed_unboxed };
{ name = "unsafe-strictness"; flags = { safe_flags with inline = false; unsafe_strict_unroll = true }; relation = Relation.Boxed_unboxed }; { name = "unsafe-strictness"; flags = { safe_flags with inline = false; unsafe_strict_unroll = true }; relation = Relation.Boxed_unboxed };
{ name = "unsafe-effects"; flags = { safe_flags with unsafe_effect_drop = true }; relation = Relation.Boxed_unboxed };
] ]
let run_case (case : Corpus.case) = let run_case (case : Corpus.case) =
let audit = Audit.audit_case case in let audit = Audit.audit_case case in
let failed_invariants =
audit.Audit.invariants
|> List.filter (fun result -> not result.Audit.passed)
|> List.map (fun result -> Audit.invariant_kind_to_string result.Audit.kind)
in
Printf.printf Printf.printf
"case %s\nclassification %s\nverdict %s\nsource %s\ntarget %s\n\n" "case %s\nclassification %s\nverdict %s\nsource %s\ntarget %s\nfailed invariants %s\n\n"
case.Corpus.name case.Corpus.name
(Audit.failure_mode_to_string audit.Audit.failure_mode) (Audit.failure_mode_to_string audit.Audit.failure_mode)
(Reporting.verdict_to_string audit.Audit.comparison.Relation.verdict) (Reporting.verdict_to_string audit.Audit.comparison.Relation.verdict)
(Reporting.string_of_source_outcome audit.Audit.source_trace.Source.outcome) (Reporting.string_of_source_trace audit.Audit.source_trace)
(Reporting.string_of_target_outcome audit.Audit.target_trace.Target.outcome) (Reporting.string_of_target_trace audit.Audit.target_trace)
(if failed_invariants = [] then "none" else String.concat ", " failed_invariants);
if audit.Audit.failure_mode <> Audit.Preserved then begin
Printf.printf "%s\n\n" (Reporting.emit_pipeline_visualisation audit.Audit.compiled);
Printf.printf "%s\n\n" (Audit.emit_pass_boundaries audit)
end
let run_profile profile = let run_profile profile =
let result = Gen.run_campaign profile.flags profile.relation ~count:160 ~max_depth:4 () in let result = Gen.run_campaign profile.flags profile.relation ~count:160 ~max_depth:4 () in
+134 -1
View File
@@ -2,9 +2,30 @@ type failure_mode =
| Preserved | Preserved
| Representation_exposure | Representation_exposure
| Strictness_shift | Strictness_shift
| Effect_shift
| Type_error of string | Type_error of string
| Other_failure of string | Other_failure of string
type invariant_kind =
| Relation_preservation
| Target_arity
| Termination_class
| Effect_trace
type invariant_result = {
kind : invariant_kind;
passed : bool;
detail : string;
}
type pass_boundary = {
pass : string;
source_outcome : Source.outcome option;
target_outcome : Target.outcome option;
effects : int;
verdict : string;
}
type obligation_status = type obligation_status =
| Discharged | Discharged
| Assumed | Assumed
@@ -27,15 +48,24 @@ type case_audit = {
typecheck : (unit, string) result; typecheck : (unit, string) result;
failure_mode : failure_mode; failure_mode : failure_mode;
obligations : obligation_result list; obligations : obligation_result list;
invariants : invariant_result list;
pass_boundaries : pass_boundary list;
} }
let failure_mode_to_string = function let failure_mode_to_string = function
| Preserved -> "preserved" | Preserved -> "preserved"
| Representation_exposure -> "representation_exposure" | Representation_exposure -> "representation_exposure"
| Strictness_shift -> "strictness_shift" | Strictness_shift -> "strictness_shift"
| Effect_shift -> "effect_shift"
| Type_error msg -> "type_error " ^ msg | Type_error msg -> "type_error " ^ msg
| Other_failure msg -> "other_failure " ^ msg | Other_failure msg -> "other_failure " ^ msg
let invariant_kind_to_string = function
| Relation_preservation -> "relation_preservation"
| Target_arity -> "target_arity"
| Termination_class -> "termination_class"
| Effect_trace -> "effect_trace"
let obligation_status_to_string = function let obligation_status_to_string = function
| Discharged -> "discharged" | Discharged -> "discharged"
| Assumed -> "assumed" | Assumed -> "assumed"
@@ -50,6 +80,8 @@ let classify validation compiled comparison =
| Ok () -> | Ok () ->
begin match comparison.Relation.verdict with begin match comparison.Relation.verdict with
| Relation.Related -> Preserved | Relation.Related -> Preserved
| Relation.Unrelated msg when String.equal msg "effect trace changed" -> Effect_shift
| Relation.Unrelated msg when String.equal msg "effect trace changed before divergence" -> Effect_shift
| Relation.Unrelated msg when has_obligation Pipeline.Exposed_representation compiled.Pipeline.obligations -> | Relation.Unrelated msg when has_obligation Pipeline.Exposed_representation compiled.Pipeline.obligations ->
Representation_exposure Representation_exposure
| Relation.Unrelated msg when String.equal msg "target diverged where source terminated" -> | Relation.Unrelated msg when String.equal msg "target diverged where source terminated" ->
@@ -69,6 +101,8 @@ let status_for_obligation failure_mode (obligation : Pipeline.obligation) =
{ obligation; status = Refuted; note = "counterexample exposes a representation specific equality primitive" } { obligation; status = Refuted; note = "counterexample exposes a representation specific equality primitive" }
| Strictness_shift, Pipeline.Strictness_risk -> | Strictness_shift, Pipeline.Strictness_risk ->
{ obligation; status = Refuted; note = "counterexample changes termination by forcing a recursive payload" } { obligation; status = Refuted; note = "counterexample changes termination by forcing a recursive payload" }
| Effect_shift, Pipeline.Effect_observation ->
{ obligation; status = Refuted; note = "counterexample changes the observable effect trace" }
| Type_error msg, _ -> | Type_error msg, _ ->
{ obligation; status = Refuted; note = "program failed validation: " ^ msg } { obligation; status = Refuted; note = "program failed validation: " ^ msg }
| Other_failure msg, _ -> | Other_failure msg, _ ->
@@ -81,6 +115,65 @@ let status_for_obligation failure_mode (obligation : Pipeline.obligation) =
{ obligation; status = Assumed; note = "unsafe inlining may expose representation observers" } { obligation; status = Assumed; note = "unsafe inlining may expose representation observers" }
| _, Pipeline.Strictness_risk -> | _, Pipeline.Strictness_risk ->
{ obligation; status = Assumed; note = "unsafe lowering may shift strictness" } { obligation; status = Assumed; note = "unsafe lowering may shift strictness" }
| _, Pipeline.Effect_observation ->
{ obligation; status = Assumed; note = "effectful source terms require effect preservation" }
let same_termination_class source target =
match source, target with
| Source.Value _, Target.Value _ -> true
| Source.Diverged _, Target.Diverged _ -> true
| Source.Stuck _, Target.Stuck _ -> true
| _ -> false
let invariant_result kind passed detail = { kind; passed; detail }
let check_invariants comparison source_trace target_trace target_program =
let target_validation = Target.validate target_program in
[
invariant_result
Relation_preservation
(comparison.Relation.verdict = Relation.Related)
(Reporting.verdict_to_string comparison.Relation.verdict);
invariant_result
Target_arity
(match target_validation with Ok () -> true | Error _ -> false)
(match target_validation with Ok () -> "target IR validates" | Error errors -> String.concat "; " errors);
invariant_result
Termination_class
(same_termination_class source_trace.Source.outcome target_trace.Target.outcome)
(Reporting.string_of_source_outcome source_trace.Source.outcome ^ " / " ^
Reporting.string_of_target_outcome target_trace.Target.outcome);
invariant_result
Effect_trace
(source_trace.Source.effects = target_trace.Target.effects)
("source effects " ^ string_of_int source_trace.Source.effects ^
", target effects " ^ string_of_int target_trace.Target.effects);
]
let pass_boundaries compiled target_trace =
let source_boundary pass term =
let trace = Source.evaluate term in
{
pass;
source_outcome = Some trace.Source.outcome;
target_outcome = None;
effects = trace.Source.effects;
verdict = Reporting.string_of_source_trace trace;
}
in
[
source_boundary "source" compiled.Pipeline.source_program;
source_boundary "specialise" compiled.Pipeline.specialised;
source_boundary "inline" compiled.Pipeline.inlined;
source_boundary "effect" compiled.Pipeline.effect_rewritten;
{
pass = "repr_lower";
source_outcome = None;
target_outcome = Some target_trace.Target.outcome;
effects = target_trace.Target.effects;
verdict = Reporting.string_of_target_trace target_trace;
};
]
let audit_case (case : Corpus.case) = let audit_case (case : Corpus.case) =
let compiled = Pipeline.compile case.Corpus.flags case.Corpus.ty case.Corpus.source in let compiled = Pipeline.compile case.Corpus.flags case.Corpus.ty case.Corpus.source in
@@ -95,6 +188,8 @@ let audit_case (case : Corpus.case) =
{ {
Relation.source_outcome = source_trace.Source.outcome; Relation.source_outcome = source_trace.Source.outcome;
target_outcome = Target.Stuck ("invalid target IR: " ^ String.concat "; " errors); target_outcome = Target.Stuck ("invalid target IR: " ^ String.concat "; " errors);
source_effects = source_trace.Source.effects;
target_effects = 0;
verdict = Relation.Unrelated "invalid target IR"; verdict = Relation.Unrelated "invalid target IR";
} }
in in
@@ -105,6 +200,7 @@ let audit_case (case : Corpus.case) =
{ {
Target.steps = [compiled.Pipeline.target_program]; Target.steps = [compiled.Pipeline.target_program];
outcome = Target.Stuck ("invalid target IR: " ^ String.concat "; " errors); outcome = Target.Stuck ("invalid target IR: " ^ String.concat "; " errors);
effects = 0;
} }
in in
let typecheck = Typecheck.check case.Corpus.ty case.Corpus.source in let typecheck = Typecheck.check case.Corpus.ty case.Corpus.source in
@@ -116,7 +212,9 @@ let audit_case (case : Corpus.case) =
in in
let failure_mode = classify validation compiled comparison in let failure_mode = classify validation compiled comparison in
let obligations = List.map (status_for_obligation failure_mode) compiled.Pipeline.obligations in let obligations = List.map (status_for_obligation failure_mode) compiled.Pipeline.obligations in
{ case; compiled; comparison; source_trace; specialised_trace; inlined_trace; target_trace; typecheck; failure_mode; obligations } let invariants = check_invariants comparison source_trace target_trace compiled.Pipeline.target_program in
let pass_boundaries = pass_boundaries compiled target_trace in
{ case; compiled; comparison; source_trace; specialised_trace; inlined_trace; target_trace; typecheck; failure_mode; obligations; invariants; pass_boundaries }
let source_outcome trace = Reporting.string_of_source_outcome trace.Source.outcome let source_outcome trace = Reporting.string_of_source_outcome trace.Source.outcome
let target_outcome trace = Reporting.string_of_target_outcome trace.Target.outcome let target_outcome trace = Reporting.string_of_target_outcome trace.Target.outcome
@@ -126,6 +224,33 @@ let emit_obligation_result result =
"| `" ^ o.Pipeline.pass ^ "` | `" ^ Reporting.obligation_kind_to_string o.Pipeline.kind ^ "| `" ^ o.Pipeline.pass ^ "` | `" ^ Reporting.obligation_kind_to_string o.Pipeline.kind ^
"` | `" ^ obligation_status_to_string result.status ^ "` | " ^ result.note ^ " |" "` | `" ^ obligation_status_to_string result.status ^ "` | " ^ result.note ^ " |"
let emit_invariant_result result =
"| `" ^ invariant_kind_to_string result.kind ^ "` | `" ^
(if result.passed then "ok" else "failed") ^ "` | " ^ result.detail ^ " |"
let emit_boundary boundary =
"| `" ^ boundary.pass ^ "` | `" ^ boundary.verdict ^ "` |"
let emit_invariants audit =
String.concat "\n"
([
"### invariants";
"";
"| invariant | status | detail |";
"| --- | --- | --- |";
]
@ List.map emit_invariant_result audit.invariants)
let emit_pass_boundaries audit =
String.concat "\n"
([
"### pass boundaries";
"";
"| pass | observation |";
"| --- | --- |";
]
@ List.map emit_boundary audit.pass_boundaries)
let emit_case_audit audit = let emit_case_audit audit =
let typecheck = let typecheck =
match audit.typecheck with match audit.typecheck with
@@ -144,6 +269,14 @@ let emit_case_audit audit =
"| specialised | `" ^ source_outcome audit.specialised_trace ^ "` |"; "| specialised | `" ^ source_outcome audit.specialised_trace ^ "` |";
"| inlined | `" ^ source_outcome audit.inlined_trace ^ "` |"; "| inlined | `" ^ source_outcome audit.inlined_trace ^ "` |";
"| target | `" ^ target_outcome audit.target_trace ^ "` |"; "| target | `" ^ target_outcome audit.target_trace ^ "` |";
"| source effects | `" ^ string_of_int audit.source_trace.Source.effects ^ "` |";
"| target effects | `" ^ string_of_int audit.target_trace.Target.effects ^ "` |";
"";
Reporting.emit_pipeline_visualisation audit.compiled;
"";
emit_pass_boundaries audit;
"";
emit_invariants audit;
""; "";
"### obligation ledger"; "### obligation ledger";
""; "";
+26 -1
View File
@@ -2,9 +2,30 @@ type failure_mode =
| Preserved | Preserved
| Representation_exposure | Representation_exposure
| Strictness_shift | Strictness_shift
| Effect_shift
| Type_error of string | Type_error of string
| Other_failure of string | Other_failure of string
type invariant_kind =
| Relation_preservation
| Target_arity
| Termination_class
| Effect_trace
type invariant_result = {
kind : invariant_kind;
passed : bool;
detail : string;
}
type pass_boundary = {
pass : string;
source_outcome : Source.outcome option;
target_outcome : Target.outcome option;
effects : int;
verdict : string;
}
type obligation_status = type obligation_status =
| Discharged | Discharged
| Assumed | Assumed
@@ -27,11 +48,15 @@ type case_audit = {
typecheck : (unit, string) result; typecheck : (unit, string) result;
failure_mode : failure_mode; failure_mode : failure_mode;
obligations : obligation_result list; obligations : obligation_result list;
invariants : invariant_result list;
pass_boundaries : pass_boundary list;
} }
val audit_case : Corpus.case -> case_audit val audit_case : Corpus.case -> case_audit
val failure_mode_to_string : failure_mode -> string val failure_mode_to_string : failure_mode -> string
val obligation_status_to_string : obligation_status -> string val obligation_status_to_string : obligation_status -> string
val invariant_kind_to_string : invariant_kind -> string
val emit_pass_boundaries : case_audit -> string
val emit_invariants : case_audit -> string
val emit_case_audit : case_audit -> string val emit_case_audit : case_audit -> string
val emit_matrix : case_audit list -> string val emit_matrix : case_audit list -> string
+17 -2
View File
@@ -13,10 +13,13 @@ type case = {
let default = Pipeline.default_flags let default = Pipeline.default_flags
let safer = let safer =
{ default with unsafe_repr_eq = false; unsafe_strict_unroll = false } { default with unsafe_repr_eq = false; unsafe_strict_unroll = false; unsafe_effect_drop = false }
let strictness_flags = let strictness_flags =
{ default with inline = false; unsafe_repr_eq = false; unsafe_strict_unroll = true } { default with inline = false; unsafe_repr_eq = false; unsafe_strict_unroll = true; unsafe_effect_drop = false }
let effect_flags =
{ safer with unsafe_effect_drop = true }
let poly_const_false = let poly_const_false =
Source.TyLam ("a", Source.Lam ("x", TVar "a", TArrow (TVar "a", TBool), Source.Lam ("y", TVar "a", TBool, Source.Bool false))) Source.TyLam ("a", Source.Lam ("x", TVar "a", TArrow (TVar "a", TBool), Source.Lam ("y", TVar "a", TBool, Source.Bool false)))
@@ -42,6 +45,9 @@ let pair_sum_roundtrip =
("p", Source.LetPair ("n", "b", Source.Var "p", Source.If (Source.Var "b", Source.Var "n", Source.Int 0))), ("p", Source.LetPair ("n", "b", Source.Var "p", Source.If (Source.Var "b", Source.Var "n", Source.Int 0))),
("k", Source.Var "k") ) ("k", Source.Var "k") )
let effectful_dead_let =
Source.Let ("_", Source.Tick ("alloc", Source.Int 0), Source.Int 1)
let boxed_pair_projection = let boxed_pair_projection =
Source.App Source.App
( Source.Lam ( Source.Lam
@@ -121,4 +127,13 @@ let all =
flags = strictness_flags; flags = strictness_flags;
relation = Relation.Boxed_unboxed; relation = Relation.Boxed_unboxed;
}; };
{
name = "effect-elision-breaks-observation";
summary = "drop an effectful dead binding during source simplification";
claim = "DCE is only valid when the eliminated term is effect free";
ty = TInt;
source = effectful_dead_let;
flags = effect_flags;
relation = Relation.Boxed_unboxed;
};
] ]
+4
View File
@@ -85,6 +85,10 @@ let sample_terms ~count ~max_depth () =
Source.Fix ("loop", TInt, Source.Var "loop") ), Source.Fix ("loop", TInt, Source.Var "loop") ),
Source.Int 0 ); Source.Int 0 );
}; };
{
ty = TInt;
term = Source.Let ("_", Source.Tick ("alloc", Source.Int 0), Source.Int 1);
};
] ]
in in
let rec fill acc i attempts = let rec fill acc i attempts =
+49 -5
View File
@@ -6,12 +6,14 @@ type optimisation_flags = {
repr_lower : bool; repr_lower : bool;
unsafe_repr_eq : bool; unsafe_repr_eq : bool;
unsafe_strict_unroll : bool; unsafe_strict_unroll : bool;
unsafe_effect_drop : bool;
} }
type obligation_kind = type obligation_kind =
| Preserve_relation | Preserve_relation
| Exposed_representation | Exposed_representation
| Strictness_risk | Strictness_risk
| Effect_observation
| Worker_wrapper_proof | Worker_wrapper_proof
type obligation = { type obligation = {
@@ -26,6 +28,7 @@ type compiled = {
source_program : Source.term; source_program : Source.term;
specialised : Source.term; specialised : Source.term;
inlined : Source.term; inlined : Source.term;
effect_rewritten : Source.term;
target_program : Target.term; target_program : Target.term;
obligations : obligation list; obligations : obligation list;
} }
@@ -37,6 +40,7 @@ let default_flags =
repr_lower = true; repr_lower = true;
unsafe_repr_eq = true; unsafe_repr_eq = true;
unsafe_strict_unroll = true; unsafe_strict_unroll = true;
unsafe_effect_drop = true;
} }
let obligation ?(kind = Preserve_relation) pass subject detail = let obligation ?(kind = Preserve_relation) pass subject detail =
@@ -52,10 +56,22 @@ let rec inline_cost = function
| Source.Fix (_, _, body) -> 2 + inline_cost body | Source.Fix (_, _, body) -> 2 + inline_cost body
| Source.Eq (_, a, b) -> 1 + inline_cost a + inline_cost b | Source.Eq (_, a, b) -> 1 + inline_cost a + inline_cost b
| Source.If (c, t, e) -> 1 + inline_cost c + inline_cost t + inline_cost e | Source.If (c, t, e) -> 1 + inline_cost c + inline_cost t + inline_cost e
| Source.Tick (_, t) -> 1 + inline_cost t
| Source.Let (_, a, b) -> inline_cost a + inline_cost b | Source.Let (_, a, b) -> inline_cost a + inline_cost b
| Source.LetPair (_, _, a, b) -> 1 + inline_cost a + inline_cost b | Source.LetPair (_, _, a, b) -> 1 + inline_cost a + inline_cost b
| Source.Case (s, (_, l), (_, r)) -> 1 + inline_cost s + inline_cost l + inline_cost r | Source.Case (s, (_, l), (_, r)) -> 1 + inline_cost s + inline_cost l + inline_cost r
let rec contains_tick = function
| Source.Tick _ -> true
| Source.Pair (a, b) | Source.App (a, b) | Source.Eq (_, a, b) -> contains_tick a || contains_tick b
| Source.Inl (_, t) | Source.Inr (_, t) | Source.Roll (_, t) | Source.Unroll t -> contains_tick t
| Source.Lam (_, _, _, body) | Source.TyLam (_, body) | Source.Fix (_, _, body) -> contains_tick body
| Source.TyApp (t, _) -> contains_tick t
| Source.If (c, t, e) -> contains_tick c || contains_tick t || contains_tick e
| Source.Let (_, a, b) | Source.LetPair (_, _, a, b) -> contains_tick a || contains_tick b
| Source.Case (s, (_, l), (_, r)) -> contains_tick s || contains_tick l || contains_tick r
| Source.Var _ | Source.Int _ | Source.Bool _ -> false
let rec inline_term note unsafe_repr_eq term = let rec inline_term note unsafe_repr_eq term =
match term with match term with
| Source.App (Source.Lam (x, _, _, body), arg) when inline_cost body <= 8 -> | Source.App (Source.Lam (x, _, _, body), arg) when inline_cost body <= 8 ->
@@ -95,10 +111,11 @@ let rec inline_term note unsafe_repr_eq term =
| Source.Eq (g, a, b) -> Source.Eq (g, inline_term note unsafe_repr_eq a, inline_term note unsafe_repr_eq b) | Source.Eq (g, a, b) -> Source.Eq (g, inline_term note unsafe_repr_eq a, inline_term note unsafe_repr_eq b)
| Source.If (c, t, e) -> | Source.If (c, t, e) ->
Source.If (inline_term note unsafe_repr_eq c, inline_term note unsafe_repr_eq t, inline_term note unsafe_repr_eq e) Source.If (inline_term note unsafe_repr_eq c, inline_term note unsafe_repr_eq t, inline_term note unsafe_repr_eq e)
| Source.Tick (label, t) -> Source.Tick (label, inline_term note unsafe_repr_eq t)
| Source.Let (x, a, b) -> | Source.Let (x, a, b) ->
let a' = inline_term note unsafe_repr_eq a in let a' = inline_term note unsafe_repr_eq a in
let b' = inline_term note unsafe_repr_eq b in let b' = inline_term note unsafe_repr_eq b in
if inline_cost a' <= 4 then begin if inline_cost a' <= 4 && not (contains_tick a') then begin
note Preserve_relation "let inlining" ("inlined binding " ^ x); note Preserve_relation "let inlining" ("inlined binding " ^ x);
Source.substitute x a' b' Source.substitute x a' b'
end else Source.Let (x, a', b') end else Source.Let (x, a', b')
@@ -107,6 +124,28 @@ let rec inline_term note unsafe_repr_eq term =
Source.Case (inline_term note unsafe_repr_eq s, (x, inline_term note unsafe_repr_eq l), (y, inline_term note unsafe_repr_eq r)) Source.Case (inline_term note unsafe_repr_eq s, (x, inline_term note unsafe_repr_eq l), (y, inline_term note unsafe_repr_eq r))
| other -> other | other -> other
let rec drop_effects note = function
| Source.Tick (label, t) ->
note Effect_observation "source effect" ("dropped observable tick " ^ label);
drop_effects note t
| Source.Pair (a, b) -> Source.Pair (drop_effects note a, drop_effects note b)
| Source.Inl (ty, t) -> Source.Inl (ty, drop_effects note t)
| Source.Inr (ty, t) -> Source.Inr (ty, drop_effects note t)
| Source.Lam (x, a, b, body) -> Source.Lam (x, a, b, drop_effects note body)
| Source.App (f, a) -> Source.App (drop_effects note f, drop_effects note a)
| Source.TyLam (x, body) -> Source.TyLam (x, drop_effects note body)
| Source.TyApp (t, ty) -> Source.TyApp (drop_effects note t, ty)
| Source.Roll (ty, t) -> Source.Roll (ty, drop_effects note t)
| Source.Unroll t -> Source.Unroll (drop_effects note t)
| Source.Fix (f, ty, body) -> Source.Fix (f, ty, drop_effects note body)
| Source.Eq (g, a, b) -> Source.Eq (g, drop_effects note a, drop_effects note b)
| Source.If (c, t, e) -> Source.If (drop_effects note c, drop_effects note t, drop_effects note e)
| Source.Let (x, a, b) -> Source.Let (x, drop_effects note a, drop_effects note b)
| Source.LetPair (x, y, a, b) -> Source.LetPair (x, y, drop_effects note a, drop_effects note b)
| Source.Case (s, (x, l), (y, r)) ->
Source.Case (drop_effects note s, (x, drop_effects note l), (y, drop_effects note r))
| other -> other
let rec repr_of_typ = function let rec repr_of_typ = function
| TInt -> Target.RInt | TInt -> Target.RInt
| TBool -> Target.RBool | TBool -> Target.RBool
@@ -142,6 +181,7 @@ let rec lower strict_unroll = function
| Source.Eq (GBool, a, b) -> Target.EqBool (lower strict_unroll a, lower strict_unroll b) | Source.Eq (GBool, a, b) -> Target.EqBool (lower strict_unroll a, lower strict_unroll b)
| Source.If (c, t, e) -> | Source.If (c, t, e) ->
Target.If (lower strict_unroll c, lower strict_unroll t, lower strict_unroll e) Target.If (lower strict_unroll c, lower strict_unroll t, lower strict_unroll e)
| Source.Tick (label, t) -> Target.Tick (label, lower strict_unroll t)
| Source.Let (x, a, b) -> Target.Let (x, lower strict_unroll a, lower strict_unroll b) | Source.Let (x, a, b) -> Target.Let (x, lower strict_unroll a, lower strict_unroll b)
| Source.LetPair (x, y, a, b) -> | Source.LetPair (x, y, a, b) ->
Target.Let Target.Let
@@ -188,14 +228,18 @@ let compile flags source_type source_program =
if flags.inline then inline_term (note "inline") flags.unsafe_repr_eq specialised if flags.inline then inline_term (note "inline") flags.unsafe_repr_eq specialised
else specialised else specialised
in in
let effect_rewritten =
if flags.unsafe_effect_drop then drop_effects (note "effect") inlined
else inlined
in
let target_program = let target_program =
if flags.repr_lower then begin if flags.repr_lower then begin
note "repr_lower" Worker_wrapper_proof "worker-wrapper" "wrapper must refine the boxed to unboxed relation on arguments and results"; note "repr_lower" Worker_wrapper_proof "worker-wrapper" "wrapper must refine the boxed to unboxed relation on arguments and results";
if flags.unsafe_strict_unroll then if flags.unsafe_strict_unroll then
note "repr_lower" Strictness_risk "roll/unroll" "strict unroll lowering may force recursive payloads earlier than the source"; note "repr_lower" Strictness_risk "roll/unroll" "strict unroll lowering may force recursive payloads earlier than the source";
match source_type with match source_type with
| TArrow (TPair (TInt, TBool), TPair (TInt, TBool)) -> synthesize_worker_wrapper source_type inlined | TArrow (TPair (TInt, TBool), TPair (TInt, TBool)) -> synthesize_worker_wrapper source_type effect_rewritten
| _ -> lower flags.unsafe_strict_unroll inlined | _ -> lower flags.unsafe_strict_unroll effect_rewritten
end else lower false inlined end else lower false effect_rewritten
in in
{ source_type; source_program; specialised; inlined; target_program; obligations = List.rev !obligations } { source_type; source_program; specialised; inlined; effect_rewritten; target_program; obligations = List.rev !obligations }
+3 -1
View File
@@ -6,12 +6,14 @@ type optimisation_flags = {
repr_lower : bool; repr_lower : bool;
unsafe_repr_eq : bool; unsafe_repr_eq : bool;
unsafe_strict_unroll : bool; unsafe_strict_unroll : bool;
unsafe_effect_drop : bool;
} }
type obligation_kind = type obligation_kind =
| Preserve_relation | Preserve_relation
| Exposed_representation | Exposed_representation
| Strictness_risk | Strictness_risk
| Effect_observation
| Worker_wrapper_proof | Worker_wrapper_proof
type obligation = { type obligation = {
@@ -26,10 +28,10 @@ type compiled = {
source_program : Source.term; source_program : Source.term;
specialised : Source.term; specialised : Source.term;
inlined : Source.term; inlined : Source.term;
effect_rewritten : Source.term;
target_program : Target.term; target_program : Target.term;
obligations : obligation list; obligations : obligation list;
} }
val default_flags : optimisation_flags val default_flags : optimisation_flags
val compile : optimisation_flags -> typ -> Source.term -> compiled val compile : optimisation_flags -> typ -> Source.term -> compiled
+19 -6
View File
@@ -12,6 +12,8 @@ type verdict =
type comparison = { type comparison = {
source_outcome : Source.outcome; source_outcome : Source.outcome;
target_outcome : Target.outcome; target_outcome : Target.outcome;
source_effects : int;
target_effects : int;
verdict : verdict; verdict : verdict;
} }
@@ -53,18 +55,29 @@ let rec cross_value_relation rel ty sv tv =
| _ -> false | _ -> false
let compare_programs ?(fuel = 256) rel ty src tgt = let compare_programs ?(fuel = 256) rel ty src tgt =
let source_outcome = Source.observe ~fuel src in let source_trace = Source.evaluate ~fuel src in
let target_outcome = Target.observe ~fuel tgt in let target_trace = Target.evaluate ~fuel tgt in
let source_outcome = source_trace.Source.outcome in
let target_outcome = target_trace.Target.outcome in
let verdict = let verdict =
match source_outcome, target_outcome with match source_outcome, target_outcome with
| Source.Value sv, Target.Value tv -> | Source.Value sv, Target.Value tv ->
if cross_value_relation rel ty sv tv then Related if source_trace.Source.effects <> target_trace.Target.effects then
Unrelated "effect trace changed"
else if cross_value_relation rel ty sv tv then Related
else Unrelated "value relation failed" else Unrelated "value relation failed"
| Source.Diverged _, Target.Diverged _ -> Related | Source.Diverged _, Target.Diverged _ ->
if source_trace.Source.effects = target_trace.Target.effects then Related
else Unrelated "effect trace changed before divergence"
| Source.Diverged _, Target.Value _ -> Unrelated "target terminated where source diverged" | Source.Diverged _, Target.Value _ -> Unrelated "target terminated where source diverged"
| Source.Value _, Target.Diverged _ -> Unrelated "target diverged where source terminated" | Source.Value _, Target.Diverged _ -> Unrelated "target diverged where source terminated"
| Source.Stuck msg, _ -> Unrelated ("source stuck: " ^ msg) | Source.Stuck msg, _ -> Unrelated ("source stuck: " ^ msg)
| _, Target.Stuck msg -> Unrelated ("target stuck: " ^ msg) | _, Target.Stuck msg -> Unrelated ("target stuck: " ^ msg)
in in
{ source_outcome; target_outcome; verdict } {
source_outcome;
target_outcome;
source_effects = source_trace.Source.effects;
target_effects = target_trace.Target.effects;
verdict;
}
+2 -1
View File
@@ -12,10 +12,11 @@ type verdict =
type comparison = { type comparison = {
source_outcome : Source.outcome; source_outcome : Source.outcome;
target_outcome : Target.outcome; target_outcome : Target.outcome;
source_effects : int;
target_effects : int;
verdict : verdict; verdict : verdict;
} }
val source_value_relation : relation -> typ -> Source.value -> Source.value -> bool val source_value_relation : relation -> typ -> Source.value -> Source.value -> bool
val cross_value_relation : relation -> typ -> Source.value -> Target.value -> bool val cross_value_relation : relation -> typ -> Source.value -> Target.value -> bool
val compare_programs : ?fuel:int -> relation -> typ -> Source.term -> Target.term -> comparison val compare_programs : ?fuel:int -> relation -> typ -> Source.term -> Target.term -> comparison
+20 -2
View File
@@ -2,6 +2,7 @@ let obligation_kind_to_string = function
| Pipeline.Preserve_relation -> "preserve_relation" | Pipeline.Preserve_relation -> "preserve_relation"
| Pipeline.Exposed_representation -> "exposed_representation" | Pipeline.Exposed_representation -> "exposed_representation"
| Pipeline.Strictness_risk -> "strictness_risk" | Pipeline.Strictness_risk -> "strictness_risk"
| Pipeline.Effect_observation -> "effect_observation"
| Pipeline.Worker_wrapper_proof -> "worker_wrapper_proof" | Pipeline.Worker_wrapper_proof -> "worker_wrapper_proof"
let string_of_source_outcome = function let string_of_source_outcome = function
@@ -14,6 +15,12 @@ let string_of_target_outcome = function
| Target.Stuck msg -> "stuck " ^ msg | Target.Stuck msg -> "stuck " ^ msg
| Target.Diverged n -> "diverged after " ^ string_of_int n ^ " steps" | Target.Diverged n -> "diverged after " ^ string_of_int n ^ " steps"
let string_of_source_trace trace =
string_of_source_outcome trace.Source.outcome ^ ", effects " ^ string_of_int trace.Source.effects
let string_of_target_trace trace =
string_of_target_outcome trace.Target.outcome ^ ", effects " ^ string_of_int trace.Target.effects
let string_of_relation = function let string_of_relation = function
| Relation.Baseline -> "baseline" | Relation.Baseline -> "baseline"
| Relation.Boxed_unboxed -> "boxed_unboxed" | Relation.Boxed_unboxed -> "boxed_unboxed"
@@ -41,6 +48,17 @@ let emit_obligations (obligations : Pipeline.obligation list) =
" / " ^ o.Pipeline.subject ^ ": " ^ o.Pipeline.detail) " / " ^ o.Pipeline.subject ^ ": " ^ o.Pipeline.detail)
|> String.concat "\n" |> String.concat "\n"
let emit_pipeline_visualisation (compiled : Pipeline.compiled) =
String.concat "\n"
[
"pipeline";
"source: " ^ Source.string_of_term compiled.Pipeline.source_program;
"specialise: " ^ Source.string_of_term compiled.Pipeline.specialised;
"inline: " ^ Source.string_of_term compiled.Pipeline.inlined;
"effect: " ^ Source.string_of_term compiled.Pipeline.effect_rewritten;
"repr_lower: " ^ Target.string_of_term compiled.Pipeline.target_program;
]
let emit_case_header (case : Corpus.case) (comparison : Relation.comparison) (compiled : Pipeline.compiled) = let emit_case_header (case : Corpus.case) (comparison : Relation.comparison) (compiled : Pipeline.compiled) =
String.concat "\n" String.concat "\n"
[ [
@@ -75,7 +93,7 @@ let emit_counterexample title (src_trace : Source.trace) (tgt_trace : Target.tra
else "\n... " ^ string_of_int tgt_hidden ^ " more target steps omitted" else "\n... " ^ string_of_int tgt_hidden ^ " more target steps omitted"
in in
"source trace:\n" ^ emit_steps Source.string_of_term src_steps ^ "source trace:\n" ^ emit_steps Source.string_of_term src_steps ^
src_suffix ^ "\n\nsource outcome: " ^ string_of_source_outcome src_trace.Source.outcome ^ src_suffix ^ "\n\nsource outcome: " ^ string_of_source_trace src_trace ^
"\n\ntarget trace:\n" ^ emit_steps Target.string_of_term tgt_steps ^ "\n\ntarget trace:\n" ^ emit_steps Target.string_of_term tgt_steps ^
tgt_suffix ^ "\n\ntarget outcome: " ^ string_of_target_outcome tgt_trace.Target.outcome ^ tgt_suffix ^ "\n\ntarget outcome: " ^ string_of_target_trace tgt_trace ^
"\n\nobligations:\n" ^ emit_obligations obligations ^ "\n" "\n\nobligations:\n" ^ emit_obligations obligations ^ "\n"
+3
View File
@@ -1,9 +1,12 @@
val obligation_kind_to_string : Pipeline.obligation_kind -> string val obligation_kind_to_string : Pipeline.obligation_kind -> string
val string_of_source_outcome : Source.outcome -> string val string_of_source_outcome : Source.outcome -> string
val string_of_target_outcome : Target.outcome -> string val string_of_target_outcome : Target.outcome -> string
val string_of_source_trace : Source.trace -> string
val string_of_target_trace : Target.trace -> string
val string_of_relation : Relation.relation -> string val string_of_relation : Relation.relation -> string
val verdict_to_string : Relation.verdict -> string val verdict_to_string : Relation.verdict -> string
val take_steps : int -> 'a list -> 'a list * int val take_steps : int -> 'a list -> 'a list * int
val emit_obligations : Pipeline.obligation list -> string val emit_obligations : Pipeline.obligation list -> string
val emit_pipeline_visualisation : Pipeline.compiled -> string
val emit_case_header : Corpus.case -> Relation.comparison -> Pipeline.compiled -> string val emit_case_header : Corpus.case -> Relation.comparison -> Pipeline.compiled -> string
val emit_counterexample : string -> Source.trace -> Target.trace -> Pipeline.obligation list -> string val emit_counterexample : string -> Source.trace -> Target.trace -> Pipeline.obligation list -> string
+35 -9
View File
@@ -18,6 +18,7 @@ type term =
| Fix of var * typ * term | Fix of var * typ * term
| Eq of ground * term * term | Eq of ground * term * term
| If of term * term * term | If of term * term * term
| Tick of string * term
| Let of var * term * term | Let of var * term * term
| LetPair of var * var * term * term | LetPair of var * var * term * term
| Case of term * (var * term) * (var * term) | Case of term * (var * term) * (var * term)
@@ -59,6 +60,7 @@ type outcome =
type trace = { type trace = {
steps : term list; steps : term list;
outcome : outcome; outcome : outcome;
effects : int;
} }
let typ_of (Expr (ty, _)) = ty let typ_of (Expr (ty, _)) = ty
@@ -83,6 +85,7 @@ let rec string_of_term = function
"(eq_" ^ string_of_ground g ^ " " ^ string_of_term a ^ " " ^ string_of_term b ^ ")" "(eq_" ^ string_of_ground g ^ " " ^ string_of_term a ^ " " ^ string_of_term b ^ ")"
| If (c, t, e) -> | If (c, t, e) ->
"(if " ^ string_of_term c ^ " then " ^ string_of_term t ^ " else " ^ string_of_term e ^ ")" "(if " ^ string_of_term c ^ " then " ^ string_of_term t ^ " else " ^ string_of_term e ^ ")"
| Tick (label, t) -> "(tick[" ^ label ^ "] " ^ string_of_term t ^ ")"
| Let (x, a, b) -> | Let (x, a, b) ->
"(let " ^ x ^ " = " ^ string_of_term a ^ " in " ^ string_of_term b ^ ")" "(let " ^ x ^ " = " ^ string_of_term a ^ " in " ^ string_of_term b ^ ")"
| LetPair (x, y, a, b) -> | LetPair (x, y, a, b) ->
@@ -127,6 +130,7 @@ let rec substitute x repl term =
if String.equal x f then term else Fix (f, ty, go body) if String.equal x f then term else Fix (f, ty, go body)
| Eq (g, a, b) -> Eq (g, go a, go b) | Eq (g, a, b) -> Eq (g, go a, go b)
| If (c, t, e) -> If (go c, go t, go e) | If (c, t, e) -> If (go c, go t, go e)
| Tick (label, t) -> Tick (label, go t)
| Let (y, a, b) -> | Let (y, a, b) ->
Let (y, go a, if String.equal x y then b else go b) Let (y, go a, if String.equal x y then b else go b)
| LetPair (y, z, a, b) -> | LetPair (y, z, a, b) ->
@@ -151,6 +155,7 @@ let rec substitute_type x repl term =
| Fix (f, ty, body) -> Fix (f, sub_ty ty, go body) | Fix (f, ty, body) -> Fix (f, sub_ty ty, go body)
| Eq (g, a, b) -> Eq (g, go a, go b) | Eq (g, a, b) -> Eq (g, go a, go b)
| If (c, t, e) -> If (go c, go t, go e) | If (c, t, e) -> If (go c, go t, go e)
| Tick (label, t) -> Tick (label, go t)
| Let (y, a, b) -> Let (y, go a, go b) | Let (y, a, b) -> Let (y, go a, go b)
| LetPair (y, z, a, b) -> LetPair (y, z, go a, go b) | LetPair (y, z, a, b) -> LetPair (y, z, go a, go b)
| Case (scrut, (y, l), (z, r)) -> Case (go scrut, (y, go l), (z, go r)) | Case (scrut, (y, l), (z, r)) -> Case (go scrut, (y, go l), (z, go r))
@@ -211,6 +216,8 @@ let rec step term =
| If (Bool false, _, e) -> Ok e | If (Bool false, _, e) -> Ok e
| If (c, t, e) when not (is_value c) -> Result.map (fun c' -> If (c', t, e)) (stepv c) | If (c, t, e) when not (is_value c) -> Result.map (fun c' -> If (c', t, e)) (stepv c)
| If _ -> stuck "if scrutinee is not a bool" | If _ -> stuck "if scrutinee is not a bool"
| Tick (_, t) when is_value t -> Ok t
| Tick (label, t) -> Result.map (fun t' -> Tick (label, t')) (stepv t)
| Let (x, a, b) when is_value a -> Ok (substitute x a b) | Let (x, a, b) when is_value a -> Ok (substitute x a b)
| Let (x, a, b) -> Result.map (fun a' -> Let (x, a', b)) (stepv a) | Let (x, a, b) -> Result.map (fun a' -> Let (x, a', b)) (stepv a)
| LetPair (x, y, Pair (a, b), body) when is_value a && is_value b -> | LetPair (x, y, Pair (a, b), body) when is_value a && is_value b ->
@@ -234,24 +241,43 @@ let rec step term =
| Int _ | Bool _ | Lam _ | TyLam _ | Roll _ -> stuck "value" | Int _ | Bool _ | Lam _ | TyLam _ | Roll _ -> stuck "value"
| Inl _ | Inr _ -> stuck "sum injection expects value subterm" | Inl _ | Inr _ -> stuck "sum injection expects value subterm"
let rec active_tick = function
| Tick (_, payload) when is_value payload -> 1
| Tick (_, payload) -> active_tick payload
| App (f, a) when not (is_value f) -> active_tick f
| App (f, a) when not (is_value a) -> active_tick a
| Pair (a, _) when not (is_value a) -> active_tick a
| Pair (_, b) when not (is_value b) -> active_tick b
| Inl (_, t) | Inr (_, t) when not (is_value t) -> active_tick t
| Eq (_, a, _) when not (is_value a) -> active_tick a
| Eq (_, _, b) when not (is_value b) -> active_tick b
| If (c, _, _) when not (is_value c) -> active_tick c
| Let (_, a, _) when not (is_value a) -> active_tick a
| LetPair (_, _, scrut, _) when not (is_value scrut) -> active_tick scrut
| Case (scrut, _, _) when not (is_value scrut) -> active_tick scrut
| TyApp (t, _) when not (is_value t) -> active_tick t
| Unroll t when not (is_value t) -> active_tick t
| _ -> 0
let evaluate ?(fuel = 256) term = let evaluate ?(fuel = 256) term =
let rec go n acc t = let rec go effects n acc t =
if n = 0 then { steps = List.rev (t :: acc); outcome = Diverged fuel } if n = 0 then { steps = List.rev (t :: acc); outcome = Diverged fuel; effects }
else if is_value t then else if is_value t then
match value_of_term t with match value_of_term t with
| Some v -> { steps = List.rev (t :: acc); outcome = Value v } | Some v -> { steps = List.rev (t :: acc); outcome = Value v; effects }
| None -> { steps = List.rev (t :: acc); outcome = Stuck "internal value decoding failure" } | None -> { steps = List.rev (t :: acc); outcome = Stuck "internal value decoding failure"; effects }
else else
let ticked = active_tick t in
match step t with match step t with
| Ok t' -> go (n - 1) (t :: acc) t' | Ok t' -> go (effects + ticked) (n - 1) (t :: acc) t'
| Error "value" -> | Error "value" ->
begin begin
match value_of_term t with match value_of_term t with
| Some v -> { steps = List.rev (t :: acc); outcome = Value v } | Some v -> { steps = List.rev (t :: acc); outcome = Value v; effects }
| None -> { steps = List.rev (t :: acc); outcome = Stuck "internal value decoding failure" } | None -> { steps = List.rev (t :: acc); outcome = Stuck "internal value decoding failure"; effects }
end end
| Error msg -> { steps = List.rev (t :: acc); outcome = Stuck msg } | Error msg -> { steps = List.rev (t :: acc); outcome = Stuck msg; effects }
in in
go fuel [] term go 0 fuel [] term
let observe ?fuel term = (evaluate ?fuel term).outcome let observe ?fuel term = (evaluate ?fuel term).outcome
+2
View File
@@ -18,6 +18,7 @@ type term =
| Fix of var * typ * term | Fix of var * typ * term
| Eq of ground * term * term | Eq of ground * term * term
| If of term * term * term | If of term * term * term
| Tick of string * term
| Let of var * term * term | Let of var * term * term
| LetPair of var * var * term * term | LetPair of var * var * term * term
| Case of term * (var * term) * (var * term) | Case of term * (var * term) * (var * term)
@@ -59,6 +60,7 @@ type outcome =
type trace = { type trace = {
steps : term list; steps : term list;
outcome : outcome; outcome : outcome;
effects : int;
} }
val typ_of : 'a expr -> typ val typ_of : 'a expr -> typ
+46 -9
View File
@@ -30,6 +30,7 @@ type term =
| EqInt of term * term | EqInt of term * term
| EqBool of term * term | EqBool of term * term
| If of term * term * term | If of term * term * term
| Tick of string * term
| Box of typ * term | Box of typ * term
| Unbox of term | Unbox of term
| Roll of typ * term | Roll of typ * term
@@ -66,6 +67,7 @@ type outcome =
type trace = { type trace = {
steps : term list; steps : term list;
outcome : outcome; outcome : outcome;
effects : int;
} }
let rec string_of_repr = function let rec string_of_repr = function
@@ -99,6 +101,7 @@ let rec string_of_term = function
| EqBool (a, b) -> "(eq_bool# " ^ string_of_term a ^ " " ^ string_of_term b ^ ")" | EqBool (a, b) -> "(eq_bool# " ^ string_of_term a ^ " " ^ string_of_term b ^ ")"
| If (c, t, e) -> | If (c, t, e) ->
"(if# " ^ string_of_term c ^ " then " ^ string_of_term t ^ " else " ^ string_of_term e ^ ")" "(if# " ^ string_of_term c ^ " then " ^ string_of_term t ^ " else " ^ string_of_term e ^ ")"
| Tick (label, t) -> "(tick#[" ^ label ^ "] " ^ string_of_term t ^ ")"
| Box (_, t) -> "(box " ^ string_of_term t ^ ")" | Box (_, t) -> "(box " ^ string_of_term t ^ ")"
| Unbox t -> "(unbox " ^ string_of_term t ^ ")" | Unbox t -> "(unbox " ^ string_of_term t ^ ")"
| Roll (_, t) -> "(roll# " ^ string_of_term t ^ ")" | Roll (_, t) -> "(roll# " ^ string_of_term t ^ ")"
@@ -270,6 +273,7 @@ let validate term =
let tr, errors = infer ctx env t errors in let tr, errors = infer ctx env t errors in
let er, errors = infer ctx env e errors in let er, errors = infer ctx env e errors in
(tr, expect_repr ctx tr er (expect_repr ctx RBool cr errors)) (tr, expect_repr ctx tr er (expect_repr ctx RBool cr errors))
| Tick (_, t) -> infer ctx env t errors
| Box (ty, t) -> | Box (ty, t) ->
let _r, errors = infer ctx env t errors in let _r, errors = infer ctx env t errors in
(RBox ty, errors) (RBox ty, errors)
@@ -356,6 +360,7 @@ let rec substitute x repl term =
| EqInt (a, b) -> EqInt (go a, go b) | EqInt (a, b) -> EqInt (go a, go b)
| EqBool (a, b) -> EqBool (go a, go b) | EqBool (a, b) -> EqBool (go a, go b)
| If (c, t, e) -> If (go c, go t, go e) | If (c, t, e) -> If (go c, go t, go e)
| Tick (label, t) -> Tick (label, go t)
| Box (ty, t) -> Box (ty, go t) | Box (ty, t) -> Box (ty, go t)
| Unbox t -> Unbox (go t) | Unbox t -> Unbox (go t)
| Roll (ty, t) -> Roll (ty, go t) | Roll (ty, t) -> Roll (ty, go t)
@@ -421,6 +426,8 @@ let rec step term =
| If (Bool false, _, e) -> Ok e | If (Bool false, _, e) -> Ok e
| If (c, t, e) when not (is_value c) -> Result.map (fun c' -> If (c', t, e)) (step c) | If (c, t, e) when not (is_value c) -> Result.map (fun c' -> If (c', t, e)) (step c)
| If _ -> Error "if# on non-bool value" | If _ -> Error "if# on non-bool value"
| Tick (_, t) when is_value t -> Ok t
| Tick (label, t) -> Result.map (fun t' -> Tick (label, t')) (step t)
| Box (ty, t) when not (is_value t) -> Result.map (fun t' -> Box (ty, t')) (step t) | Box (ty, t) when not (is_value t) -> Result.map (fun t' -> Box (ty, t')) (step t)
| Unbox (Box (_, v)) when is_value v -> Ok v | Unbox (Box (_, v)) when is_value v -> Ok v
| Unbox t when not (is_value t) -> Result.map (fun t' -> Unbox t') (step t) | Unbox t when not (is_value t) -> Result.map (fun t' -> Unbox t') (step t)
@@ -443,24 +450,54 @@ let rec step term =
| Int _ | Bool _ | Lam _ | Box _ | Roll _ -> Error "value" | Int _ | Bool _ | Lam _ | Box _ | Roll _ -> Error "value"
| Inl _ | Inr _ -> Error "sum injection expects value subterm" | Inl _ | Inr _ -> Error "sum injection expects value subterm"
let rec first_active_tick = function
| Tick (_, payload) when is_value payload -> 1
| Tick (_, payload) -> first_active_tick payload
| App (f, _) when not (is_value f) -> first_active_tick f
| App (_, args) ->
args
|> List.find_opt (fun arg -> not (is_value arg))
|> Option.map first_active_tick
|> Option.value ~default:0
| Tuple xs ->
xs
|> List.find_opt (fun arg -> not (is_value arg))
|> Option.map first_active_tick
|> Option.value ~default:0
| Proj (_, t) when not (is_value t) -> first_active_tick t
| Inl (_, _, t) | Inr (_, _, t) when not (is_value t) -> first_active_tick t
| Case (s, _, _) when not (is_value s) -> first_active_tick s
| Let (_, a, _) when not (is_value a) -> first_active_tick a
| EqInt (a, _) when not (is_value a) -> first_active_tick a
| EqInt (_, b) when not (is_value b) -> first_active_tick b
| EqBool (a, _) when not (is_value a) -> first_active_tick a
| EqBool (_, b) when not (is_value b) -> first_active_tick b
| If (c, _, _) when not (is_value c) -> first_active_tick c
| Box (_, t) when not (is_value t) -> first_active_tick t
| Unbox t when not (is_value t) -> first_active_tick t
| Unroll t when not (is_value t) -> first_active_tick t
| Halt t when not (is_value t) -> first_active_tick t
| _ -> 0
let evaluate ?(fuel = 256) term = let evaluate ?(fuel = 256) term =
let rec go n acc t = let rec go effects n acc t =
if n = 0 then { steps = List.rev (t :: acc); outcome = Diverged fuel } if n = 0 then { steps = List.rev (t :: acc); outcome = Diverged fuel; effects }
else if is_value t then else if is_value t then
match value_of_term t with match value_of_term t with
| Some v -> { steps = List.rev (t :: acc); outcome = Value v } | Some v -> { steps = List.rev (t :: acc); outcome = Value v; effects }
| None -> { steps = List.rev (t :: acc); outcome = Stuck "internal target value decoding failure" } | None -> { steps = List.rev (t :: acc); outcome = Stuck "internal target value decoding failure"; effects }
else else
let ticked = first_active_tick t in
match step t with match step t with
| Ok t' -> go (n - 1) (t :: acc) t' | Ok t' -> go (effects + ticked) (n - 1) (t :: acc) t'
| Error "value" -> | Error "value" ->
begin match value_of_term t with begin match value_of_term t with
| Some v -> { steps = List.rev (t :: acc); outcome = Value v } | Some v -> { steps = List.rev (t :: acc); outcome = Value v; effects }
| None -> { steps = List.rev (t :: acc); outcome = Stuck "internal target value decoding failure" } | None -> { steps = List.rev (t :: acc); outcome = Stuck "internal target value decoding failure"; effects }
end end
| Error msg -> { steps = List.rev (t :: acc); outcome = Stuck msg } | Error msg -> { steps = List.rev (t :: acc); outcome = Stuck msg; effects }
in in
go fuel [] term go 0 fuel [] term
let observe ?fuel term = (evaluate ?fuel term).outcome let observe ?fuel term = (evaluate ?fuel term).outcome
+2
View File
@@ -30,6 +30,7 @@ type term =
| EqInt of term * term | EqInt of term * term
| EqBool of term * term | EqBool of term * term
| If of term * term * term | If of term * term * term
| Tick of string * term
| Box of typ * term | Box of typ * term
| Unbox of term | Unbox of term
| Roll of typ * term | Roll of typ * term
@@ -66,6 +67,7 @@ type outcome =
type trace = { type trace = {
steps : term list; steps : term list;
outcome : outcome; outcome : outcome;
effects : int;
} }
val string_of_repr : repr -> string val string_of_repr : repr -> string
+1 -1
View File
@@ -95,6 +95,7 @@ let rec type_of_env env term =
let* te = type_of_env env e in let* te = type_of_env env e in
let* () = expect tt te in let* () = expect tt te in
Ok tt Ok tt
| Source.Tick (_, t) -> type_of_env env t
| Source.Let (x, a, b) -> | Source.Let (x, a, b) ->
let* ta = type_of_env env a in let* ta = type_of_env env a in
type_of_env ((x, ta) :: env) b type_of_env ((x, ta) :: env) b
@@ -125,4 +126,3 @@ let is_well_typed expected term =
match check expected term with match check expected term with
| Ok () -> true | Ok () -> true
| Error _ -> false | Error _ -> false
+13 -1
View File
@@ -57,6 +57,18 @@ let () =
assert_true assert_true
"expected strictness shift witness" "expected strictness shift witness"
(strict.failure_mode = Audit.Strictness_shift); (strict.failure_mode = Audit.Strictness_shift);
let effect_audit = Audit.audit_case (find_case "effect-elision-breaks-observation") in
assert_true
"expected effect shift witness"
(effect_audit.failure_mode = Audit.Effect_shift);
assert_true
"expected unsafe effect rewrite to emit effect observation obligation"
(has_obligation Pipeline.Effect_observation effect_audit.compiled.obligations);
assert_true
"expected effect trace invariant failure"
(List.exists
(fun result -> result.Audit.kind = Audit.Effect_trace && not result.Audit.passed)
effect_audit.Audit.invariants);
let generated = Gen.sample_terms ~count:80 ~max_depth:4 () in let generated = Gen.sample_terms ~count:80 ~max_depth:4 () in
List.iter List.iter
(fun specimen -> (fun specimen ->
@@ -84,7 +96,7 @@ let () =
"expected unsupported worker-wrapper source shape to be rejected" "expected unsupported worker-wrapper source shape to be rejected"
(fun () -> (fun () ->
Pipeline.compile Pipeline.compile
{ Pipeline.default_flags with unsafe_repr_eq = false; unsafe_strict_unroll = false } { Pipeline.default_flags with unsafe_repr_eq = false; unsafe_strict_unroll = false; unsafe_effect_drop = false }
(Types.TArrow (Types.TPair (Types.TInt, Types.TBool), Types.TPair (Types.TInt, Types.TBool))) (Types.TArrow (Types.TPair (Types.TInt, Types.TBool), Types.TPair (Types.TInt, Types.TBool)))
(Source.Lam (Source.Lam
( "p", ( "p",