Skip to content

Introduce FunctionsAndProofs IR and rename OrderedLaurel to CoreWithLaurelTypes#925

Open
keyboardDrummer-bot wants to merge 5 commits intomainfrom
issue-924-functions-and-proofs-ir
Open

Introduce FunctionsAndProofs IR and rename OrderedLaurel to CoreWithLaurelTypes#925
keyboardDrummer-bot wants to merge 5 commits intomainfrom
issue-924-functions-and-proofs-ir

Conversation

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator

Summary

Implements steps (c) and (d) from #924: introduces the FunctionsAndProofs IR and renames OrderedLaurel to CoreWithLaurelTypes.

Changes

New file: FunctionsAndProofs.lean

  • Defines FunctionsAndProofsProgram with functions, proofs, datatypes, and constants fields (all using Laurel.Procedure)
  • Adds a temporary laurelToFunctionsAndProofs translation that partitions Laurel procedures by isFunctional — functional procedures become functions, non-functional become proofs

CoreGroupingAndOrdering.lean

  • Renames OrderedLaurelCoreWithLaurelTypes
  • Changes OrderedDecl:
    • procsfuncs (for function SCCs, with isRecursive flag)
    • Adds procedure (singular, for individual proofs — no mutual recursion)
  • orderFunctionsAndProofs replaces orderProgram: takes a FunctionsAndProofsProgram, runs both functions and proofs through the SCC/topological ordering (so invokeOn axioms are correctly placed), then classifies each SCC into funcs or procedure decls

LaurelToCoreTranslator.lean

  • translateLaurelToCore now takes CoreWithLaurelTypes instead of OrderedLaurel
  • Handles funcs (translated to Core functions) and procedure (translated to Core procedures) separately

LaurelCompilationPipeline.lean

  • Pipeline now goes: Laurel → laurelToFunctionsAndProofsorderFunctionsAndProofstranslateLaurelToCore → Core

Pipeline (new)

Laurel passes → FunctionsAndProofsProgram → CoreWithLaurelTypes → Core

Testing

  • All existing tests pass (only pre-existing failures in DDM.Integration.Java.TestGen and Python.AnalyzeLaurelTest)
  • No new test failures introduced

Closes steps (c) and (d) of #924.

…aurelTypes

- Add FunctionsAndProofsProgram structure with functions, proofs, datatypes, constants
- Add temporary laurelToFunctionsAndProofs translation (functional → functions, non-functional → proofs)
- Rename OrderedLaurel to CoreWithLaurelTypes
- Change OrderedDecl: replace 'procs' with 'funcs' (for function SCCs) and 'procedure' (for individual proofs)
- Rewire pipeline: Laurel → FunctionsAndProofs → CoreWithLaurelTypes → Core
- Both functions and proofs participate in topological ordering via the call graph

Addresses steps (c) and (d) of #924.
Comment thread Strata/Languages/Laurel/CoreGroupingAndOrdering.lean Outdated
Comment thread Strata/Languages/Laurel/LaurelCompilationPipeline.lean Outdated
@keyboardDrummer keyboardDrummer marked this pull request as ready for review April 15, 2026 12:49
@keyboardDrummer keyboardDrummer requested a review from a team April 15, 2026 12:50
@fabiomadge
Copy link
Copy Markdown
Contributor

Can you motivate these changes?

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

This PR implements steps (c) and (d) from #924. The motivation comes from a planned restructuring of the Laurel-to-Core pipeline to support a new "contract pass" and "proof pass."

Currently the pipeline is:

Laurel passes → OrderedLaurel → Core

The goal (from #924) is to evolve it into:

Laurel passes (ending with contract pass) → FunctionsAndProofs (proof pass) → CoreWithLaurelTypes (ordering) → Core

The key idea is to explicitly separate Laurel procedures into functions (pure computation) and proofs (verification-only), which today are conflated under a single procs variant in OrderedDecl. This separation is needed because the upcoming contract pass will generate precondition/postcondition procedures, and the proof pass will split each Laurel procedure into a function (with assertions/assumptions stripped) and a proof (retaining the full body). Having a dedicated IR makes these transformations cleaner and gives each phase a well-typed input/output.

Concretely, this PR:

  1. Introduces FunctionsAndProofsProgram as a new IR that explicitly separates functions from proofs (both still represented as Laurel.Procedure for now).
  2. Renames OrderedLaurelCoreWithLaurelTypes and splits OrderedDecl.procs into OrderedDecl.funcs (for function SCCs) and OrderedDecl.procedure (for individual proofs), since proofs don't participate in mutual recursion.
  3. Updates the pipeline and translator accordingly.

This is scaffolding for the contract and proof passes that will follow. The current laurelToFunctionsAndProofs is a temporary shim (partitioning by isFunctional) that will be replaced once the contract and proof passes land.

public inductive OrderedDecl where
/-- A group of functions (single non-recursive, or mutually recursive). -/
| procs (procs : List Procedure) (isRecursive : Bool)
| funcs (funcs : List Procedure) (isRecursive : Bool)
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.

What would it mean to have >1 functions in the list with isRecursive=false? Or is this disallowed somewhere?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good question. Having >1 functions with isRecursive=false shouldn't happen in practice — computeSccDecls sets isRecursive=true whenever the SCC has more than one member (see procs.length > 1 on line 161). The only way to get isRecursive=false is a single-element SCC with no self-loop.

That said, the type doesn't enforce this invariant — it's maintained by the caller. @keyboardDrummer, would you like me to tighten this, e.g. by splitting into separate func/mutualFuncs constructors, or is the current representation fine for now?

`FunctionsAndProofsProgram`.
-/
public structure OrderedLaurel where
public structure CoreWithLaurelTypes where
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.

Curious why the name was changed. I think OrderedLaurel is more descriptive than CoreWithLaurelTypes, though I think I may know what you mean by the latter (non-blocking).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The rename was specified in #924 by @keyboardDrummer. The idea is that at this point in the pipeline, declarations have been grouped and topologically ordered (Core's structure), but the types are still Laurel types — the final dumb translation to Core hasn't happened yet. So CoreWithLaurelTypes describes what the IR is: Core's shape, Laurel's types.

I agree OrderedLaurel is also descriptive — happy to defer to whatever you and @keyboardDrummer prefer.

Comment thread Strata/Languages/Laurel/CoreGroupingAndOrdering.lean
@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Thanks for the review @joscoh! I replied to each of your three comments inline. Summary:

  1. isRecursive=false with >1 functions (line 176): This can't happen — computeSccDecls sets isRecursive=true whenever the SCC has >1 member. The type doesn't enforce this though; asked @keyboardDrummer whether we should tighten it.

  2. Rename to CoreWithLaurelTypes (line 189): This was specified in Introduce Contract and Proof phase to Laurel #924. The name reflects that the IR has Core's structure (grouped/ordered decls) but still uses Laurel types. Happy to defer to whatever you both prefer.

  3. groupDatatypesByScc vs groupDatatypesByScc' (line 223): The public groupDatatypesByScc was dead code left over from when orderProgram took a Program. I've removed it in commit 07963a9.

Comment on lines +204 to +209
let orderedDecls := (computeSccDecls program).flatMap fun (procs, isRecursive) =>
-- Split the SCC into functions and proofs
let (funcs, proofs) := procs.partition (fun p => funcNames.contains p.name.text)
let funcDecl := if funcs.isEmpty then [] else [OrderedDecl.funcs funcs isRecursive]
let proofDecls := proofs.map OrderedDecl.procedure
funcDecl ++ proofDecls
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.

This SCC splitting has a subtle correctness concern. If an SCC contains both functions and proofs (e.g., a function that calls a proof, or vice versa), two things go wrong:

  1. isRecursive leaks across the split. The flag was computed for the whole SCC. After partitioning, the functions subset might not be recursive among themselves, but they'll be marked isRecursive=true because the SCC was recursive due to cross-category edges. This would cause translateProcedureToFunction to emit a recFuncBlock for functions that aren't actually mutually recursive.

  2. Ordering within the SCC is lost. Proofs are emitted after functions (funcDecl ++ proofDecls). If a function depends on a proof in the same SCC, the proof won't be available yet.

I suspect mixed SCCs can't happen today (a functional procedure probably can't call a non-functional one), but the code doesn't enforce or document this assumption. At minimum, consider adding an assertion or comment:

-- Mixed SCCs (functions + proofs) should not occur: functional procedures
-- do not call non-functional ones. If this changes, the splitting logic
-- here needs revisiting.
if !funcs.isEmpty && !proofs.isEmpty then
  -- log a warning or panic

(program.staticProcedures.filter (fun p => !p.body.isExternal))
|>.partition (fun p => p.invokeOn.isSome)
allProcs.partition (fun p => p.invokeOn.isSome)
let nonExternal : List Procedure := withInvokeOn ++ withoutInvokeOn
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.

Nit: nonExternal is a misleading name now that computeSccDecls takes a FunctionsAndProofsProgram — external procedures are already filtered out by laurelToFunctionsAndProofs. This is just the invokeOn-partitioned list of all procedures. Consider renaming to orderedProcs or allProcsSorted, and updating the comment on line 122 ("Build a call-graph over all non-external procedures") accordingly.

Comment on lines +29 to +34
structure FunctionsAndProofsProgram where
functions : List Procedure
proofs : List Procedure
datatypes : List DatatypeDefinition
constants : List Constant

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.

The structure looks good. One question: should FunctionsAndProofsProgram carry a well-formedness invariant that functions and proofs are disjoint (no procedure name appears in both)? Today this is guaranteed by List.partition, but if the structure is ever constructed differently (e.g., by the upcoming proof pass), the invariant could be violated silently.

Not blocking — just worth considering as the IR evolves.

Comment on lines +40 to +46
def laurelToFunctionsAndProofs (program : Program) : FunctionsAndProofsProgram :=
let nonExternal := program.staticProcedures.filter (fun p => !p.body.isExternal)
let (functions, proofs) := nonExternal.partition (·.isFunctional)
let datatypes := program.types.filterMap fun td => match td with
| .Datatype dt => some dt
| _ => none
{ functions, proofs, datatypes, constants := program.constants }
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.

The doc comment says "Temporary translation" — good. For traceability, consider adding a reference to the issue: "Will be replaced by the contract and proof passes (#924)."

Also: program.types may contain .Composite entries that are silently dropped here. This is correct (they're handled via the program parameter in translateLaurelToCore), but a brief comment would help future readers understand why only .Datatype is kept.

Comment on lines 172 to +176
public inductive OrderedDecl where
/-- A group of functions (single non-recursive, or mutually recursive). -/
| procs (procs : List Procedure) (isRecursive : Bool)
| funcs (funcs : List Procedure) (isRecursive : Bool)
/-- A single (non-functional) procedure. -/
| procedure (procedure : Procedure)
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.

Re joscoh's unresolved thread (line 174): the invariant that funcs has >1 element only when isRecursive=true is maintained by computeSccDecls but not enforced by the type. Since this is scaffolding for #924, I'd suggest at minimum adding a doc comment on the funcs constructor:

Suggested change
public inductive OrderedDecl where
/-- A group of functions (single non-recursive, or mutually recursive). -/
| procs (procs : List Procedure) (isRecursive : Bool)
| funcs (funcs : List Procedure) (isRecursive : Bool)
/-- A single (non-functional) procedure. -/
| procedure (procedure : Procedure)
/-- A group of functions (single non-recursive, or mutually recursive).
Invariant: `funcs.length > 1 → isRecursive = true`. -/
| funcs (funcs : List Procedure) (isRecursive : Bool)

Comment on lines +124 to +125
let functionsAndProofs := laurelToFunctionsAndProofs program
let ordered := orderFunctionsAndProofs functionsAndProofs
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.

No direct unit tests for laurelToFunctionsAndProofs or orderFunctionsAndProofs. The coverage comes from end-to-end tests through the full pipeline, which is reasonable for now since this is scaffolding.

However, as the contract and proof passes land, consider adding targeted tests that verify:

  1. laurelToFunctionsAndProofs correctly partitions functional vs. non-functional procedures
  2. orderFunctionsAndProofs preserves the topological ordering invariant (dependencies before dependents)
  3. No procedures are lost in the Laurel → FunctionsAndProofs → CoreWithLaurelTypes → Core pipeline (a simple count check would suffice)

@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review @tautschnig! I've pushed a commit (ed8ba72) addressing the straightforward items. Here's a summary of each point:

Addressed in ed8ba72

  1. nonExternal naming (line 120) — Renamed to orderedProcs and procsArr, and updated the comment to say "Build a call-graph over all procedures" (external procedures are already filtered out by laurelToFunctionsAndProofs).

  2. Doc comment on funcs constructor (line 176) — Added the suggested invariant doc comment: Invariant: funcs.length > 1 → isRecursive = true.

  3. laurelToFunctionsAndProofs doc comment (line 46) — Added the issue reference ("Will be replaced by the contract and proof passes (Introduce Contract and Proof phase to Laurel #924)") and a comment explaining why .Composite entries are dropped (they're handled via the original Program in translateLaurelToCore).

Discussion points

  1. Mixed SCC correctness concern (line 209) — This is a good catch. I agree that mixed SCCs (functions + proofs in the same SCC) would cause both isRecursive leaking and ordering issues. I believe you're right that this can't happen today — isFunctional partitions cleanly and functional procedures don't call non-functional ones. I'll add a comment documenting this assumption. @keyboardDrummer, would you also like a runtime assertion (e.g., logging a warning if both funcs and proofs are non-empty in the same SCC), or is a comment sufficient for now?

  2. Well-formedness invariant on FunctionsAndProofsProgram (line 34) — Good point for future-proofing. Today disjointness is guaranteed by List.partition, but as the IR evolves (e.g., when the proof pass lands), this could be violated. I'll leave this as-is for now since it's scaffolding, but it's worth revisiting when Introduce Contract and Proof phase to Laurel #924 progresses. @keyboardDrummer, any preference?

  3. Testing suggestions (line 125) — Agreed that targeted tests for the partition, ordering invariant, and no-procedure-loss would be valuable as the contract/proof passes land. For now the end-to-end coverage is sufficient for this scaffolding. I'll track this as a follow-up.

@tautschnig
Copy link
Copy Markdown
Contributor

@keyboardDrummer-bot Please resolve git conflicts.

Keep both the FunctionsAndProofs pipeline from this branch and the
overflowChecks field added on main.
@keyboardDrummer-bot
Copy link
Copy Markdown
Collaborator Author

Resolved the merge conflict in LaurelCompilationPipeline.lean. The conflict was between this PR's FunctionsAndProofs pipeline changes and the overflowChecks field added on main (from #592). The resolution keeps both: the laurelToFunctionsAndProofs/orderFunctionsAndProofs pipeline from this branch, and the overflowChecks initialization from main.

Built Strata.Languages.Laurel.LaurelCompilationPipeline successfully before pushing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants