Skip to content

Commit b69bd7d

Browse files
Enable concurrent speculative checking with thread-safe irept
Make irept reference counting thread-safe with std::atomic<unsigned>. Use fetch_sub(1)==1 for the decrement+check pattern. Performance impact: ~2% (within noise). Add per-thread merge_irept isolation: swap in a fresh merge_irept for symex during the concurrent phase so new steps don't share the hash table with existing steps that the speculative thread reads. Add thread-local stream_message_handlert for the speculative thread, with captured output printed after join. Add set_message_handler() and swap_merge_irep() public methods to symex_target_equationt. The speculative thread now runs concurrently with symex: it reads existing steps (safe with atomic ref_count and isolated merge pools) while symex appends new steps with its own merge_irep. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent ebdde61 commit b69bd7d

13 files changed

Lines changed: 231 additions & 135 deletions

doc/man/cbmc.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@ threads. Symbolic execution pauses every N steps (set via
436436
\fB\-\-incremental\-check\-interval\fR) and hands off to the solver.
437437
Requires \fB\-\-incremental\-check\-interval\fR.
438438
.TP
439+
\fB\-\-speculative\-check\fR
440+
speculatively check assertions on partial equations using
441+
cone\-of\-influence slicing. Use with
442+
\fB\-\-incremental\-check\-interval\fR.
443+
.TP
439444
\fB\-\-show\-vcc\fR
440445
show the verification conditions
441446
.TP

doc/man/jbmc.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ threads. Symbolic execution pauses every N steps (set via
350350
\fB\-\-incremental\-check\-interval\fR) and hands off to the solver.
351351
Requires \fB\-\-incremental\-check\-interval\fR.
352352
.TP
353+
\fB\-\-speculative\-check\fR
354+
speculatively check assertions on partial equations using
355+
cone\-of\-influence slicing. Use with
356+
\fB\-\-incremental\-check\-interval\fR.
357+
.TP
353358
\fB\-\-show\-vcc\fR
354359
show the verification conditions
355360
.TP

src/cbmc/cbmc_parse_options.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,9 @@ void cbmc_parse_optionst::get_command_line_options(optionst &options)
449449
if(cmdline.isset("concurrent-incremental"))
450450
options.set_option("concurrent-incremental", true);
451451

452+
if(cmdline.isset("speculative-check"))
453+
options.set_option("speculative-check", true);
454+
452455
if(cmdline.isset("graphml-witness"))
453456
{
454457
options.set_option("graphml-witness", cmdline.get_value("graphml-witness"));

src/goto-checker/bmc_util.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ std::chrono::duration<double> prepare_property_decider_incremental(
201201
"(incremental-loop):" \
202202
"(incremental-check-interval):" \
203203
"(concurrent-incremental)" \
204+
"(speculative-check)" \
204205
"(unwind-min):" \
205206
"(unwind-max):" \
206207
"(ignore-properties-before-unwind-min)" \
@@ -231,6 +232,10 @@ std::chrono::duration<double> prepare_property_decider_incremental(
231232
" {y--concurrent-incremental} \t " \
232233
"run symbolic execution and SAT solving concurrently in separate " \
233234
"threads (use with {y--incremental-check-interval})\n" \
235+
" {y--speculative-check} \t " \
236+
"speculatively check assertions on partial equations using " \
237+
"cone-of-influence slicing (use with " \
238+
"{y--incremental-check-interval})\n" \
234239
" {y--unwind-min} {unr} \t " \
235240
"start incremental-loop after {unr} unwindings but before solving that " \
236241
"iteration. If for example it is 1, then the loop will be unwound once, " \

src/goto-checker/concurrent_incremental_symex_checker.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,13 @@ concurrent_incremental_symex_checkert::operator()(propertiest &properties)
343343

344344
full_equation_generated = sync.symex_done;
345345

346-
revert_slice(equation);
347-
346+
// New steps from symex will be converted on the next iteration
347+
// by prepare_property_decider_incremental. We do NOT revert the
348+
// slice or reset current_equation_converted, because re-converting
349+
// the entire equation into the same solver creates duplicate
350+
// Tseitin clauses that corrupt the formula.
348351
update_properties_status_from_symex_target_equation(
349352
properties, result.updated_properties, equation);
350-
351-
current_equation_converted = false;
352353
}
353354

354355
return result;

src/goto-checker/concurrent_incremental_symex_checker.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ Author: CBMC Contributors
1414
#ifndef CPROVER_GOTO_CHECKER_CONCURRENT_INCREMENTAL_SYMEX_CHECKER_H
1515
#define CPROVER_GOTO_CHECKER_CONCURRENT_INCREMENTAL_SYMEX_CHECKER_H
1616

17-
1817
#include <goto-programs/unwindset.h>
1918

2019
#include <goto-symex/path_storage.h>

src/goto-checker/periodic_incremental_symex_checker.cpp

Lines changed: 99 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ Author: CBMC Contributors
1313

1414
#include "periodic_incremental_symex_checker.h"
1515

16+
#include <util/std_expr.h>
1617
#include <util/ui_message.h>
1718

1819
#include <goto-symex/slice.h>
20+
#include <solvers/prop/prop_conv_solver.h>
1921

2022
#include "bmc_util.h"
23+
#include "counterexample_beautification.h"
2124
#include "solver_factory.h"
25+
2226
#include <sstream>
23-
#include <solvers/prop/prop_conv_solver.h>
24-
#include "counterexample_beautification.h"
2527

2628
// --- symex_bmc_periodic_stept implementation ---
2729

@@ -119,6 +121,7 @@ periodic_incremental_symex_checkert::periodic_incremental_symex_checkert(
119121
unwindset,
120122
static_cast<unsigned>(
121123
options.get_signed_int_option("incremental-check-interval"))),
124+
speculative_checking_enabled(options.get_bool_option("speculative-check")),
122125
property_decider(options, ui_message_handler, equation, ns)
123126
{
124127
unwindset.parse_unwind(options.get_option("unwind"));
@@ -147,7 +150,6 @@ periodic_incremental_symex_checkert::operator()(propertiest &properties)
147150
full_equation_generated = !symex.from_entry_point_of(
148151
goto_symext::get_goto_function(goto_model), symex_symbol_table);
149152

150-
// This might add new properties such as unwinding assertions.
151153
update_properties_status_from_symex_target_equation(
152154
properties, result.updated_properties, equation);
153155

@@ -181,8 +183,7 @@ periodic_incremental_symex_checkert::operator()(propertiest &properties)
181183
++solver_calls;
182184
log.status()
183185
<< "Periodic check #" << solver_calls << ": running "
184-
<< property_decider.get_decision_procedure()
185-
.decision_procedure_text()
186+
<< property_decider.get_decision_procedure().decision_procedure_text()
186187
<< messaget::eom;
187188

188189
decision_proceduret::resultt dec_result = property_decider.solve();
@@ -193,8 +194,8 @@ periodic_incremental_symex_checkert::operator()(propertiest &properties)
193194
const auto solver_stop = std::chrono::steady_clock::now();
194195
solver_runtime +=
195196
std::chrono::duration<double>(solver_stop - solver_start);
196-
log.status() << "Runtime decision procedure: "
197-
<< solver_runtime.count() << "s" << messaget::eom;
197+
log.status() << "Runtime decision procedure: " << solver_runtime.count()
198+
<< "s" << messaget::eom;
198199

199200
result.progress =
200201
dec_result == decision_proceduret::resultt::D_SATISFIABLE
@@ -206,74 +207,80 @@ periodic_incremental_symex_checkert::operator()(propertiest &properties)
206207

207208
property_decider.pop_incremental_assumptions();
208209
}
209-
else
210+
else if(speculative_checking_enabled)
210211
{
211-
// Partial equation: launch speculative check in a thread.
212-
// Save equation state before the speculative check modifies it.
213-
saved_step_state.clear();
214-
saved_step_state.reserve(equation.SSA_steps.size());
215-
for(const auto &step : equation.SSA_steps)
216-
saved_step_state.push_back(
217-
{step.converted, step.cond_handle, step.guard_handle});
212+
// Speculative cone-of-influence check on partial equation.
213+
// Only check the most recently discovered assertion to
214+
// minimize overhead.
215+
const std::size_t current_assertions = equation.count_assertions();
216+
if(current_assertions > last_speculative_assertion_count)
217+
{
218+
last_speculative_assertion_count = current_assertions;
218219

219-
++solver_calls;
220-
log.status() << "Speculative check #" << solver_calls
221-
<< " on partial equation (" << equation.SSA_steps.size()
222-
<< " steps)" << messaget::eom;
223-
224-
speculative_sat = false;
225-
// Snapshot the current equation size. The speculative thread
226-
// will only process steps up to this point. Symex may append
227-
// new steps concurrently, but the thread won't see them.
228-
const std::size_t snapshot_size = equation.SSA_steps.size();
229-
speculative_thread = std::make_unique<std::thread>(
230-
[this, snapshot_size]()
231-
{
232-
std::ostringstream spec_log_stream;
233-
stream_message_handlert spec_mh(spec_log_stream);
234-
spec_mh.set_verbosity(
235-
ui_message_handler.get_verbosity());
236-
237-
solver_factoryt solvers(
238-
options, ns, spec_mh, false);
239-
auto spec_solver = solvers.get_solver();
240-
auto &spec_dp = spec_solver->decision_procedure();
241-
242-
auto *prop_conv =
243-
dynamic_cast<prop_conv_solvert *>(&spec_dp);
244-
if(prop_conv)
245-
prop_conv->set_all_frozen();
246-
247-
// Only convert the snapshot (existing steps).
248-
// New steps appended by symex are not touched.
249-
std::size_t count = 0;
250-
for(auto &step : equation.SSA_steps)
220+
++solver_calls;
221+
log.status() << "Speculative check #" << solver_calls
222+
<< " on partial equation (" << equation.SSA_steps.size()
223+
<< " steps)" << messaget::eom;
224+
225+
speculative_sat = false;
226+
const std::size_t snapshot_size = equation.SSA_steps.size();
227+
speculative_thread = std::make_unique<std::thread>(
228+
[this, snapshot_size]()
251229
{
252-
if(count >= snapshot_size)
253-
break;
254-
if(!step.ignore)
230+
std::ostringstream spec_log_stream;
231+
stream_message_handlert spec_mh(spec_log_stream);
232+
spec_mh.set_verbosity(ui_message_handler.get_verbosity());
233+
234+
// Find the last assertion in the snapshot.
235+
symex_target_equationt::SSA_stepst::const_iterator last_assert;
236+
bool found = false;
237+
std::size_t count = 0;
238+
for(auto it = equation.SSA_steps.begin();
239+
it != equation.SSA_steps.end() && count < snapshot_size;
240+
++it, ++count)
241+
{
242+
if(it->is_assert() && !it->ignore)
243+
{
244+
last_assert = it;
245+
found = true;
246+
}
247+
}
248+
249+
if(!found)
250+
{
251+
speculative_log_output = spec_log_stream.str();
252+
return;
253+
}
254+
255+
auto cone = cone_of_influence(
256+
equation.SSA_steps, last_assert, snapshot_size);
257+
258+
solver_factoryt solvers(options, ns, spec_mh, false);
259+
auto spec_solver = solvers.get_solver();
260+
auto &dp = spec_solver->decision_procedure();
261+
262+
for(const auto *step : cone)
255263
{
256-
step.guard_handle = spec_dp.handle(step.guard);
257-
if(step.is_assume())
258-
step.cond_handle = spec_dp.handle(step.cond_expr);
259-
else if(step.is_assignment() || step.is_constraint())
260-
spec_dp.set_to_true(step.cond_expr);
261-
else if(step.is_assert())
264+
if(step->is_assignment() || step->is_constraint())
265+
dp.set_to_true(step->cond_expr);
266+
else if(step->is_assume())
267+
{
268+
exprt g = dp.handle(step->guard);
269+
dp.set_to_true(implies_exprt(g, step->cond_expr));
270+
}
271+
else if(step->is_assert())
262272
{
263-
step.cond_handle = spec_dp.handle(step.cond_expr);
264-
spec_dp.set_to_false(step.cond_expr);
273+
exprt g = dp.handle(step->guard);
274+
dp.set_to_true(implies_exprt(g, not_exprt(step->cond_expr)));
265275
}
266276
}
267-
++count;
268-
}
269277

270-
auto r = spec_solver->decision_procedure()();
271-
speculative_sat =
272-
(r == decision_proceduret::resultt::D_SATISFIABLE);
278+
if(dp() == decision_proceduret::resultt::D_SATISFIABLE)
279+
speculative_sat = true;
273280

274-
speculative_log_output = spec_log_stream.str();
275-
});
276-
// Symex continues immediately — speculative thread runs concurrently.
281+
speculative_log_output = spec_log_stream.str();
282+
});
283+
}
277284
}
278285
}
279286

@@ -288,56 +295,44 @@ periodic_incremental_symex_checkert::operator()(propertiest &properties)
288295
break;
289296
}
290297

291-
// We continue symbolic execution
298+
// Resume symbolic execution
292299
if(!full_equation_generated)
293300
{
294-
// Give symex a fresh merge_irep so new steps don't share
295-
// the hash table with existing steps. This allows the
296-
// speculative thread to safely read existing steps' ireps
297-
// while symex appends new ones.
298-
merge_irept saved_merge_irep =
299-
equation.swap_merge_irep(merge_irept{});
300-
301-
// Resume symex concurrently with the speculative thread.
302-
full_equation_generated =
303-
!symex.resume(goto_symext::get_goto_function(goto_model));
304-
305-
// Join speculative thread after symex pauses.
306-
if(speculative_thread)
301+
if(speculative_checking_enabled)
307302
{
308-
speculative_thread->join();
309-
speculative_thread.reset();
303+
// Swap merge_irep so speculative thread can safely read
304+
// existing steps while symex appends new ones.
305+
merge_irept saved = equation.swap_merge_irep(merge_irept{});
310306

311-
auto sit = saved_step_state.begin();
312-
for(auto &step : equation.SSA_steps)
313-
{
314-
if(sit == saved_step_state.end())
315-
break;
316-
step.converted = sit->converted;
317-
step.cond_handle = sit->cond_handle;
318-
step.guard_handle = sit->guard_handle;
319-
++sit;
320-
}
307+
full_equation_generated =
308+
!symex.resume(goto_symext::get_goto_function(goto_model));
321309

322-
if(speculative_sat)
310+
if(speculative_thread)
323311
{
324-
log.status() << "Speculative check found potential failure"
325-
<< messaget::eom;
326-
}
312+
speculative_thread->join();
313+
speculative_thread.reset();
327314

328-
equation.set_message_handler(ui_message_handler);
329-
if(!speculative_log_output.empty())
330-
{
331-
log.debug() << speculative_log_output << messaget::eom;
315+
if(speculative_sat)
316+
{
317+
log.status() << "Speculative check found potential failure"
318+
<< messaget::eom;
319+
}
320+
321+
equation.set_message_handler(ui_message_handler);
322+
if(!speculative_log_output.empty())
323+
log.debug() << speculative_log_output << messaget::eom;
332324
}
333-
}
334325

335-
// Restore the original merge_irep (merges both pools).
336-
equation.swap_merge_irep(std::move(saved_merge_irep));
326+
equation.swap_merge_irep(std::move(saved));
327+
}
328+
else
329+
{
330+
full_equation_generated =
331+
!symex.resume(goto_symext::get_goto_function(goto_model));
332+
}
337333

338334
revert_slice(equation);
339335

340-
// This might add new properties such as unwinding assertions.
341336
update_properties_status_from_symex_target_equation(
342337
properties, result.updated_properties, equation);
343338

0 commit comments

Comments
 (0)