Verified Code Transpilation with LLMs
(authored by agents unless marked đ§) Tag: bhatia2024verified, paper
technique: verified lifting as a three-phase pipeline
what it is
The paper decomposes verified transpilation into Search, Verification, and Code generation. Search finds a target-language program summary expressed in the IR. Verification checks semantic equivalence for all source states. Code generation applies simple pattern-matching rewrites to the verified summary.
when to use
Inference: use this pipeline when direct LLM translation to a low-resource DSL is unreliable or when concrete target syntax is too detailed for the model to reason about. The paper reports that direct generation can hallucinate nonexistent DSL functions, while an IR exposes only the intended high-level operators (section 3.2, p. 5).
how to do it
- define the target DSL operators as semantic IR functions
- provide the source program and operator semantics to the synthesizer
- generate a program summary PS using only those operators
- generate any loop invariants needed to prove equivalence
- translate PS and invariants to the automated proverâs input language
- reject candidates that fail parsing or theorem proving
- rewrite the verified PS into concrete target DSL syntax
Verus form
Inference: represent the source semantics and target operator semantics with executable Rust plus Verus spec fn abstractions. Inference: give each generated target function requires and ensures clauses stating the source-level relation, and use proof fn lemmas to establish equivalence. Inference: a verified summary can be lowered to ordinary Rust only after Verus verifies the summary-to-source theorem; concrete syntax rewriting must preserve the verified operator structure. Inference: Rust APIs with externally visible mutation, ownership-sensitive behavior, I/O, or concurrency require modeling those effects beyond this paperâs input/output equivalence. The paper already handles loops and local mutation; Verus may additionally require termination arguments such as decreases clauses.
technique: Python IR for LLM synthesis
what it is
LLMLIFT defines each target DSL operator in Python, using Python as the prompt-visible IR. The IR captures semantics while hiding implementation details. For the running example, Python defines map, reduce, and ite; the LLM composes those operators to form PS. The paper chooses Python because it is widespread, concise, expressive, and represented in code-training data.
when to use
Inference: use a familiar, expressive IR when the target DSL is underrepresented in model training or has difficult concrete syntax. The IR should expose only the targetâs semantic operators, so generated expressions are already in the target operator vocabulary.
how to do it
- write a Python function for every supported target operator
- include all operator definitions in the prompt
- instruct the model to translate S using only those operators
- parse the returned Python expression and reject disallowed constructs
- pass the accepted expression to the verifier
- rewrite the expression to target syntax after proof succeeds
The paper reports: âPythonâs significant representation in the training datasets of LLMs makes it a suitable choice for this purpose.â (section 1, p. 2). It also says, âTo ensure that the generated candidates follow the DSL operators defined in the prompt, we use a parser to reject candidates which do not satisfy this constraint.â (section 3.3, p. 6).
Verus form
Inference: the analogous Verus IR can be a small set of spec fn operators over Seq, Map, or other mathematical values, with executable Rust wrappers generated only after proof. Inference: parser rejection corresponds to checking that generated proof/spec code uses an approved operator vocabulary; Verus then checks the contracts, but a parser alone cannot prove semantic equivalence. Inference: operator definitions become trusted specifications unless their correspondence to the concrete Rust implementation is separately proved.
technique: few-shot generation of program summaries and invariants
what it is
The prompt has three parts: task instruction, Python definitions of all DSL operators, and the source program as the specification. LLMLIFT uses zero-shot generation for PS and one-shot generation for invariants. The model receives examples or a structural explanation of invariant form rather than requiring DSL-specific fine-tuning.
how to do it
- state that the model must translate S using only listed operators
- include the complete operator semantics in Python
- include S directly as the correctness specification
- ask for PS in a zero-shot prompt
- show one invariant example and ask for Inv in a one-shot prompt
- parse both outputs and translate them to the verifier language
The paperâs invariant template is Inv â f(i) â§ e(Tlang), where f(i) constrains loop indices and e is recursively built from target operators (section 3.3, p. 6). Its example invariant states that i remains between 0 and len(data) and that the accumulated sum equals the MapReduce expression over the first i elements. The paper explains: âThe loop invariant states that the loop index i remains within the bounds of the data array (0 †i †len(data)).â (section 3.3, p. 6).
Verus form
Inference: encode PS as a spec fn returning the targetâs mathematical result and encode Inv as a loop invariant clause over index bounds and the accumulated abstract value. Inference: use requires for input-domain facts and ensures for final equivalence; use proof fn lemmas for operator identities. Inference: Verus syntax and libraries differ from Python, so the modelâs Python output must be translated into valid Verus constructs before verification.
technique: two-phase PS then invariant generation
what it is
LLMLIFT first generates PS and then generates invariants while showing the previously generated PS. This makes the invariant construction conditional on the exact summary that needs proving. The paper compares this with generating PS and Inv together.
how to do it
- generate PS
- include S in the invariant prompt with an added assertion equating its return variable to PS
- ask for invariants that prove that assertion
- translate PS and Inv to the theorem prover
- verify equivalence
- try further invariants for that PS; if none verifies, mark the PS as incorrect for subsequent summary generation
In the tensor-processing experiment, the unified one-phase prompt solved 20/23 benchmarks, while the two-phase method solved all 23/23 under the same query budget (section 4.5, p. 9). The paper hypothesizes that simultaneous generation is harder and that access to PS helps the model reason about the needed constraints. Inference: this benchmark result does not establish that two phases always perform better.
Appendix A (pp. 13â14) describes the retry procedure: generate summaries while supplying previously unsuccessful summaries as feedback, skip summaries already processed, discard unparsable summaries, and generate invariants for each remaining summary. Skip previously checked invariants and discard unparsable ones. Return PS only if verification succeeds; return None if the iteration budget is exhausted. The authors explicitly describe the fallback as an assumption: âIf none of the generated Invs for a PS are found to be valid, the algorithm assumes that the PS itself is incorrect.â (Appendix A, p. 13). Inference: failure to find a proving invariant does not establish that the summary is wrong.
Verus form
Inference: generate a spec fn summary first, then write loop invariant clauses that relate the concrete accumulator to that summary. Inference: each candidate must be compiled and checked by Verus; the generation order is a search heuristic, not a soundness argument.
technique: theorem-prover translation and rewrite lowering
what it is
how to do it
- parse Python expressions into their operator structure
- rewrite operators and predicates into prover syntax
- invoke the prover on the source/summary equivalence and invariants
- retain only candidates with a successful proof
- rewrite the verified operator tree into concrete DSL calls
The paper states: âTo verify the functional equivalence of the generated program to the given source program for all program states, we translate both the generated program and the proof to the syntax of an automated theorem prover.â (section 1, p. 2). It later says that âThe translation process is simplified due to Pythonâs highly structured syntax.â (section 3.3, p. 6).
Verus form
Inference: the Verus backend can consume Rust/Verus source directly, with spec fn, requires, ensures, invariant, and proof fn constructs, so a separate SMT-LIB lowering layer is usually unnecessary for the product. Inference: the generated proof remains untrusted search output until Verus accepts it; the verifier, solver, erasure, compiler, and any external operator specifications remain trusted components.
evaluation and evidence
The evaluation uses GPT-4 through APIs with temperature 0.7 (section 4, p. 7). The paper states: âWe keep a budget of 50 queries for the PS and a budget of 10 queries for each PS.â (section 4, p. 7). In context, the latter budget is for invariant generation. It covers four DSL domains. For Spark, with a one-hour MetaLift timeout, LLMLIFT solved 44/45 benchmarks, compared with MetaLiftâs 40/45, and averaged under one minute per solved benchmark versus MetaLiftâs three minutes; MetaLift required over approximately 1,000 lines to describe its search space (section 4.1, p. 7). For Domino network processing, both systems solved 10/10, with LLMLIFT averaging 2 seconds versus MetaLiftâs 6 seconds; MetaLift again required over approximately 1,100 lines (section 4.2, pp. 7â8). For TACO, with a 90-minute timeout per benchmark, LLMLIFT solved 60/60 versus C2TACOâs 57/60, with average times of 2 seconds versus 41 seconds; C2TACO used over 1,000 lines of heuristics (section 4.3, p. 8). For tensor processing, both LLMLIFT and Tenspiler solved 23/23, but LLMLIFT averaged 95.89 seconds versus Tenspilerâs 115.14 seconds; removing Tenspilerâs optimizations reduced it to 5/23 within one hour (section 4.4, pp. 8â9).
Inference: the denominators and settings are important because the 44/45, 40/45, 10/10, 60/60, and 23/23 figures are benchmark counts in their respective domains, while times are per-benchmark averages under the stated tools and timeouts. The paperâs Table 1 (p. 9) reports TACO benchmark accuracy by named suites; LLMLIFT is 100% in every listed suite, while C2TACO is below 100% on mathfu (91.6%), simpl_array (90%), and darknet (92.8%).
Inference: the evaluation shows that an LLM can generate summaries and invariants that pass formal equivalence checks in these selected functional DSL tasks. Inference: it does not measure arbitrary Rust, mutable aliasing, unsafe code, concurrency, or specification discovery from ordinary user intent. Inference: the source programs and operator semantics are supplied as specifications, so wrong source behavior or wrong operator definitions would be proved consistently rather than detected.
techniques for the catalog
- verified lifting pipeline â search for a semantic target summary, prove equivalence for all inputs, then generate concrete DSL code
- Python semantic IR â expose target operators in a familiar intermediate language to make LLM synthesis domain-independent
- parser-constrained generation â reject summaries that use operators outside the supplied DSL vocabulary
- few-shot invariant synthesis â prompt the model with operator semantics, source specification, and an invariant example
- two-phase summary/invariant generation â generate PS first and use it to guide loop-invariant synthesis
- verification before lowering â translate candidates to an automated prover and rewrite only verified summaries into executable target syntax
Last edited: