Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion Strata/Languages/Core/SMTEncoder.lean
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,32 @@ def lMonoTyToTermType (useArrayTheory : Bool := false) (ty : LMonoTy) : TermType
.constr name (args.map $ lMonoTyToTermType useArrayTheory)
| .ftvar tv => .constr tv []

/-- The total twin of a checked sequence operation. -/
private def seqTotalTwin? : String → Option String
| "Sequence.select" => some "Sequence.select!"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this use factory function names instead of brittle strings?

| "Sequence.update" => some "Sequence.update!"
| "Sequence.take" => some "Sequence.take!"
| "Sequence.drop" => some "Sequence.drop!"
| _ => none

/--
The solver symbol for the function named `name`.

A checked sequence operation and its total twin denote the same function: they
differ only in the bounds precondition, which is discharged as a separate
obligation before encoding. Both are therefore encoded as one symbol, that of
the total twin, so that a fact stated with either form applies to the other.
-/
private def seqSymbol (name : String) : String :=

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(a) This runs on every function the encoder sees and takes the full name apart each time. toSMTOp already has the short name (baseName) a few lines up. Checking that first would skip the work for every op that isn't one of these four. Also, the tys.isEmpty case can't happen, so it could go.

(b) This only works because each ! op has the same axioms as its checked twin. Now that they share one solver symbol, only the axioms of whichever form appears first get loaded. If someone later adds an axiom to just one of them, it would be lost or applied to both. Could you add a short note on the four ! definitions in Factory.lean saying their axioms must stay the same as the checked versions?

match Core.NameMangling.demangleFuncName name with
| some (base, tys) =>
match seqTotalTwin? base with
| some twin =>
let stem := Core.NameMangling.monoPrefix ++ Core.NameMangling.monoDelim ++ twin
if tys.isEmpty then stem else stem ++ Core.NameMangling.monoDelim ++ tys
| none => name
| none => (seqTotalTwin? name).getD name

/-- Convert a datatype's constructors to typed SMT constructors. -/
private def datatypeConstructorsToSMT (d : LDatatype CoreLParams.IDMeta) (useArrayTheory : Bool := false): List SMTConstructor :=
d.constrs.map fun c =>
Expand Down Expand Up @@ -635,7 +661,7 @@ def toSMTOp (factory : @Lambda.Factory CoreLParams) (fn : CoreIdent) (fnty : LMo
let bvs := formalStrs.zip smt_intys
let outty := tys.getLast (by exact @LMonoTy.destructArrow_non_empty fnty)
let (smt_outty, ctx) ← LMonoTy.toSMTType outty ctx
let uf : UF := { id := (toString $ format fn), args := smt_intys, out := smt_outty }
let uf : UF := { id := seqSymbol (toString $ format fn), args := smt_intys, out := smt_outty }
let arrowParams := func.inputs.toList.filter (fun (_, ty) => ty.containsArrow)
if !arrowParams.isEmpty then
let names := arrowParams.map (fun (n, _) => toString (format n))
Expand Down
56 changes: 56 additions & 0 deletions StrataTest/Languages/Core/Examples/Seq.lean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add one out-of-range case, e.g. assert Sequence.select(g, 5) == Sequence.select!(g, 5) with length(g) == 2?

Original file line number Diff line number Diff line change
Expand Up @@ -748,5 +748,61 @@ Result: ✅ pass
#guard_msgs in
#eval Strata.Core.verify seqSelectBangBuildPgm

-- A checked sequence operation and its total twin denote the same function, so
-- in bounds the two agree. They are encoded as one solver symbol.

private def seqCheckedTotalPgm :=
#strata
program Core;

const g : Sequence int;

procedure P()
{
assume [g_len]: Sequence.length(g) == 2;
assert [select_pair]: Sequence.select(g, 0) == Sequence.select!(g, 0);
assert [update_pair]: Sequence.update(g, 0, 7) == Sequence.update!(g, 0, 7);
assert [take_pair]: Sequence.take(g, 1) == Sequence.take!(g, 1);
assert [drop_pair]: Sequence.drop(g, 1) == Sequence.drop!(g, 1);
};
#end

/--
info:
Obligation: assert_select_pair_calls_Sequence.select_0
Property: out-of-bounds access check
Result: ✅ pass

Obligation: select_pair
Property: assert
Result: ✅ pass

Obligation: assert_update_pair_calls_Sequence.update_0
Property: out-of-bounds access check
Result: ✅ pass

Obligation: update_pair
Property: assert
Result: ✅ pass

Obligation: assert_take_pair_calls_Sequence.take_0
Property: out-of-bounds access check
Result: ✅ pass

Obligation: take_pair
Property: assert
Result: ✅ pass

Obligation: assert_drop_pair_calls_Sequence.drop_0
Property: out-of-bounds access check
Result: ✅ pass

Obligation: drop_pair
Property: assert
Result: ✅ pass
-/
#guard_msgs in
#eval Strata.Core.verify seqCheckedTotalPgm (options := .quiet)

end
----------------------------------------------------------------------
Loading