z3-src 501.0.1

Source distribution of the Z3 SMT solver, for use as a build dependency
Documentation
/*++
Copyright (c) 2026 Microsoft Corporation

Module Name:

    psmt.cpp

Abstract:

    Tests for the parallel SMT tactic (psmt).
    Regression test for GitHub issue #9985 (deadlock on psmt).

--*/
#ifndef SINGLE_THREAD

#include "ast/reg_decl_plugins.h"
#include "ast/array_decl_plugin.h"
#include "ast/arith_decl_plugin.h"
#include "smt/smt_solver.h"
#include "smt/tactic/smt_tactic_core.h"
#include "tactic/goal.h"
#include "tactic/tactic.h"
#include <iostream>

// Regression test for GitHub issue #9985:
//
// psmt used to deadlock when every CDCL worker returned l_undef with a
// reason other than "max-conflicts-reached" (e.g., theory incompleteness).
// batch_manager::set_unknown() changed the terminal state but did not
// notify backbone workers or the core-minimizer worker that were blocked on
// their condition variables.  Those workers blocked forever.
//
// Fix: set_unknown() now calls m_bb_cv.notify_all() and
// m_core_min_cv.notify_all() so all waiting helper threads observe the
// state change and exit cleanly.
//
// We exercise three cases:
//   1. SAT  – trivially satisfiable boolean formula.
//   2. UNSAT – trivially unsatisfiable boolean formula.
//   3. UNKNOWN (no deadlock) – formula whose root cube is theory-incomplete.
//      The formula uses (as-array f) terms for a function f : Int -> Bool.
//      The array theory marks itself incomplete for as-array, so every
//      CDCL worker returns l_undef with "(incomplete (theory array))",
//      triggering the set_unknown path that used to deadlock.
static void tst_psmt_worker() {
    ast_manager m;
    reg_decl_plugins(m);
    params_ref p;

    // ------------------------------------------------------------------
    // 1. SAT test: assert (or x (not x)) – always true
    // ------------------------------------------------------------------
    {
        tactic_ref t = mk_parallel_smt_tactic(m, p);
        goal_ref   g = alloc(goal, m, false, true, false);
        expr_ref   x(m.mk_const(symbol("x"), m.mk_bool_sort()), m);
        g->assert_expr(m.mk_or(x, m.mk_not(x)));

        model_ref           md;
        labels_vec          labels;
        proof_ref           pr(m);
        expr_dependency_ref core(m);
        std::string         reason_unknown;
        lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
        ENSURE(r == l_true);
        (void)r;
        std::cout << "psmt SAT: " << r << "\n";
    }

    // ------------------------------------------------------------------
    // 2. UNSAT test: assert (and x (not x))
    // ------------------------------------------------------------------
    {
        tactic_ref t = mk_parallel_smt_tactic(m, p);
        goal_ref   g = alloc(goal, m, false, true, false);
        expr_ref   x(m.mk_const(symbol("x"), m.mk_bool_sort()), m);
        g->assert_expr(m.mk_and(x, m.mk_not(x)));

        model_ref           md;
        labels_vec          labels;
        proof_ref           pr(m);
        expr_dependency_ref core(m);
        std::string         reason_unknown;
        lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
        ENSURE(r == l_false);
        (void)r;
        std::cout << "psmt UNSAT: " << r << "\n";
    }

    // ------------------------------------------------------------------
    // 3. UNKNOWN (deadlock regression) test.
    //
    // Reproduce: (declare-fun f (Int) Bool)
    //            (declare-fun g (Int) Bool)
    //            (assert (distinct f g))
    //            (check-sat-using psmt)
    //
    // In SMT-LIB2, the function symbols f and g are lifted to array terms
    // via (as-array f) and (as-array g).  The array theory is explicitly
    // incomplete for as-array, so check_sat returns l_undef with reason
    // "(incomplete (theory array))".  Previously set_unknown() forgot to
    // notify backbone and core-minimizer workers' condition variables,
    // causing a deadlock; now it does.
    // ------------------------------------------------------------------
    {
        tactic_ref t = mk_parallel_smt_tactic(m, p);
        goal_ref   g = alloc(goal, m, false, true, false);

        // Build func_decls: f : Int -> Bool, g : Int -> Bool
        arith_util arith(m);
        sort* int_s     = arith.mk_int();
        sort* domain[1] = { int_s };
        func_decl* f_decl = m.mk_func_decl(symbol("f_fn"), 1, domain, m.mk_bool_sort());
        func_decl* g_decl = m.mk_func_decl(symbol("g_fn"), 1, domain, m.mk_bool_sort());

        // (as-array f) and (as-array g): array representations of f and g
        array_util autil(m);
        expr_ref f_arr(autil.mk_as_array(f_decl), m);
        expr_ref g_arr(autil.mk_as_array(g_decl), m);

        // (distinct (as-array f) (as-array g))
        expr* dist_args[2] = { f_arr, g_arr };
        expr_ref distinct_fg(m.mk_distinct(2, dist_args), m);
        g->assert_expr(distinct_fg);

        model_ref           md;
        labels_vec          labels;
        proof_ref           pr(m);
        expr_dependency_ref core(m);
        std::string         reason_unknown;
        lbool r = check_sat(*t, g, md, labels, pr, core, reason_unknown);
        // The result must be l_undef (theory-incomplete).
        // If the fix is absent, this call deadlocks instead of returning.
        ENSURE(r == l_undef);
        (void)r;
        std::cout << "psmt UNKNOWN (no deadlock): " << r << "\n";
    }

    std::cout << "psmt tests passed\n";
}

void tst_psmt() {
    tst_psmt_worker();
}

#else

void tst_psmt() {
    // Single-threaded build: parallel tactic degrades to sequential.
    // No deadlock is possible; nothing to test.
}

#endif