Skip to documentation
SLOP

tiny.smt.ConflictScratch

Reference tiny.smt ConflictScratch

Defined in sat.scratch.

Scratch memory for one conflict analysis at a time, in storage its caller hands over: a buffer of up to one literal per variable and one mark bit per variable.

API (35)

Actions

Public operations.

Types and contracts

Public types and contracts.

Fields and members

Public fields and members.

No direct callersNo direct callssat.scratchConflictScratch
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/smt/src/sat/scratch.zig:39

zig
/// Scratch memory for one conflict analysis at a time, in storage its caller hands over: a buffer/// of up to one literal per variable and one mark bit per variable. The solver builds one over the/// tail of its trail allocation at the start of each solve and takes a loan from it for each/// conflict it analyzes. `init` takes the storage, `activate` moves the scratch from/// `.initialization` to `.steady`, and `deinit` moves it to `.teardown` and returns the storage./// The scratch calls no allocator. One loan is out at a time, and every loan function refuses a/// released or copied loan.pub const ConflictScratch = struct {    /// The errors that `acquire`, `release` and the loan functions return. The solver's conflict    /// analysis passes them up with `try`, so they belong to the errors a solve can return. Each    /// error is returned before any change to the scratch.    pub const Exhaustion = error{        /// `acquire` was called while a loan is out.        ConflictScratchInUse,        /// `acquire` asked for more variables than the scratch covers, or `Loan.append` or        /// `Loan.recordResolution` went past the loan's variable count.        ConflictScratchCapacityExceeded,        /// `acquire` found the loan counter at the largest `u64`.        ConflictScratchLoanEpochExhausted,        /// A loan function or `release` got a loan that is released, a copy at another address,        /// from a scratch outside `.steady`, or from another scratch.        ConflictScratchStaleLoan,    };    /// A compile-time record of the scratch's memory: what the storage covers, what stays outside    /// it, the equation that sizes it from `Limits`, the refusal behavior, the work bound, and the    /// tests that witness each obligation. The shape check at the end of the file validates the    /// record and the scratch's shape at compile time. The equation gives the variable count times    /// the size of a literal, plus the variable count divided by 8 and rounded up, in bytes. The    /// record states that short storage, oversize use, an exhausted loan counter, copied loans and    /// overlapping loans are refused before the caller's storage or the scratch changes. The record    /// states the work bound: taking a loan clears at most one byte per 8 variables, and an    /// analysis takes at most one resolution step per variable. The learned clauses the solver    /// keeps, the trail's own entries, the clauses, the watch lists, the search state and the proof    /// steps stay outside the storage.    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "smt.conflict_scratch",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "conflict_analysis_learned_literal_scratch",                        .lifetime = .transferred,                        .detail = "one learned literal per admitted solver variable in the exact trail-tail storage transferred by the caller",                    },                    .{                        .id = "conflict_analysis_seen_variable_scratch",                        .lifetime = .transferred,                        .detail = "one dense seen bit per admitted solver variable in the exact trail-tail storage transferred by the caller",                    },                },                .excluded = &.{                    "retained learned-clause literals copied by addLearnedClause",                    "the primary trail prefix, rounded tail padding, original clauses, clause headers, watch lists, and search state",                    "proof trace steps and reverse-unit-propagation assignment state",                    "old plus new trail backing overlap while variable growth establishes a larger admitted solve workspace",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(Limits, "variables", "variables"),                },                .type_selectors = &.{                    alloc_phase.capacity.bindType(Literal, "literal"),                },                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .constant = 8 },                    .{ .ceiling_division = .{ .left = 0, .right = 2 } },                    .{ .add = .{ .left = 1, .right = 3 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 4,                }},            },            .overload = .{                .kind = .reject_before_mutation,                .detail = "short storage, oversize use, epoch exhaustion, copied tokens, and overlapping address-bound affine loans fail before caller storage or ownership state changes",            },            .risks = .{                .transitive = .{                    .status = .open,                    .detail = "learnFromConflict uses one address-bound epoch loan within stable token-storage and owner lifetimes and bounded SAT properties check status, proof, core, and learned-clause semantics; retained learned-clause copies remain a separate owner",                },                .foreign = .{                    .status = .excluded,                    .detail = "the exact transferred trail tail is process-local memory and conflict analysis crosses no operating-system or foreign-runtime edge",                },            },            .work = .{                .equation = "initialization <= 1, acquire clearing <= ceil(variables / 8), and resolution steps <= variables",            },            .obligations = &.{                .{ .key = "smt_conflict_scratch_capacity", .role = .capacity_model },                .{ .key = "smt_conflict_scratch_acquisition", .role = .custom },                .{ .key = "smt_conflict_scratch_storage_identity", .role = .foreign_risk },                .{ .key = "smt_conflict_scratch_overload", .role = .overload },                .{ .key = "smt_conflict_scratch_reuse", .role = .overload },                .{ .key = "smt_conflict_scratch_work_bound", .role = .work_bound },                .{ .key = "smt_conflict_scratch_solver_admission", .role = .overload },                .{ .key = "smt_conflict_scratch_solver_semantics", .role = .transitive_risk },                .{ .key = "smt_conflict_scratch_foreign_risk", .role = .foreign_risk },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },        },    };    /// The alignment the caller's storage needs, equal to the alignment of a literal. A caller    /// aligns a buffer of its own to this value before it hands the buffer to `init`.    pub const storage_alignment: usize = @alignOf(Literal);    /// The byte slice a scratch takes from its caller, aligned for literals. A caller passes one to    /// `init` and gets the same one back from `deinit`.    pub const Storage = []align(storage_alignment) u8;    /// The errors of `init`: `error.CapacityOverflow` from `Capacity.derive`, and    /// `error.StorageLengthMismatch` when the storage length differs from `Capacity.storage_bytes`.    /// The solver passes them up from the start of a solve.    pub const InitError = Capacity.DeriveError || error{StorageLengthMismatch};    /// Work bounds that the shape check reads: any number of setup steps, and zero cleanup steps    /// per call. The shape check at the end of the file requires it beside the record in `claim`.    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = std.math.maxInt(usize),        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    phase: alloc_phase.capacity.Phase,    /// The sizes `Capacity.derive` gave for the limits passed to `init`. `admits` and `acquire`    /// compare with its variable count, and the solver reads it to find the storage again at each    /// analysis.    capacity: Capacity,    /// The storage passed to `init`, which `deinit` returns. The literal buffer starts at its first    /// byte, and the mark bits start at `Capacity.seen_offset`. The solver checks at each analysis    /// that it still matches the tail of the trail allocation.    storage: Storage,    literals: []Literal,    seen: []u8,    loaned: bool = false,    loan_epoch: u64 = 0,    loan_identity: usize = 0,    loan_variables: usize = 0,    loan_literal_count: usize = 0,    loan_resolution_steps: usize = 0,    /// The input that sizes a scratch: its variable count. The solver builds one from the variable    /// count a solve needs, counting every variable its assumptions name.    pub const Limits = struct {        /// The number of variables the scratch covers.        variables: usize,        /// Returns the limits for `variables`. The solver builds its scratch limits through it.        pub fn inspect(variables: usize) Limits {            return .{ .variables = variables };        }    };    /// The sizes of one scratch and of the trail allocation that holds it, in bytes and in    /// literals. The solver derives it at the start of each solve to size its trail allocation and    /// to find the scratch storage inside it.    pub const Capacity = struct {        /// The variable count from the limits, which is the most literals and marks a loan uses.        variables: usize,        /// Bytes of the literal buffer: the variable count times the size of a literal.        literal_bytes: usize,        /// The byte offset of the mark bits in the storage, equal to `literal_bytes`.        seen_offset: usize,        /// Bytes of mark bits: the variable count divided by 8, rounded up.        seen_bytes: usize,        /// Bytes of storage: `literal_bytes` plus `seen_bytes`. `init` refuses storage of any other        /// length.        storage_bytes: usize,        /// The storage size in literals, rounded up, which is the number of trail entries the        /// solver sets aside for the scratch.        backing_literals: usize,        /// The trail allocation the solver needs, in literals: the variable count plus        /// `backing_literals`.        trail_capacity: usize,        /// The error of `derive`: `error.CapacityOverflow`. `init` and the solver pass it up before        /// anything is allocated.        pub const DeriveError = error{CapacityOverflow};        /// Returns the sizes for `limits`. The solver sizes its trail allocation from it before a        /// solve, and `init` checks the storage length against it. The call returns        /// `error.CapacityOverflow` when the literal bytes, the storage bytes or the trail        /// allocation overflows `usize`.        pub fn derive(limits: Limits) DeriveError!Capacity {            const literal_bytes = std.math.mul(                usize,                limits.variables,                @sizeOf(Literal),            ) catch return error.CapacityOverflow;            const seen_bytes = limits.variables / 8 +                @intFromBool(limits.variables % 8 != 0);            const storage_bytes = std.math.add(                usize,                literal_bytes,                seen_bytes,            ) catch return error.CapacityOverflow;            const backing_literals = storage_bytes / @sizeOf(Literal) +                @intFromBool(storage_bytes % @sizeOf(Literal) != 0);            const trail_capacity = std.math.add(                usize,                limits.variables,                backing_literals,            ) catch return error.CapacityOverflow;            assert(literal_bytes % @alignOf(Literal) == 0);            return .{                .variables = limits.variables,                .literal_bytes = literal_bytes,                .seen_offset = literal_bytes,                .seen_bytes = seen_bytes,                .storage_bytes = storage_bytes,                .backing_literals = backing_literals,                .trail_capacity = trail_capacity,            };        }    };    /// A handle to the scratch memory for one conflict analysis, which reaches the scratch's    /// literal buffer and marks and holds the scratch's address and the loan's number. The solver's    /// conflict analysis takes one per conflict with `acquire`, marks the variables it meets,    /// collects the learned clause's literals, and hands the loan back with `release`. Every loan    /// function checks the loan first and returns `error.ConflictScratchStaleLoan` when the loan    /// was released, when a later `acquire` replaced it, when it is a copy at another address, or    /// when the scratch is outside `.steady`. The loan stays valid only at the address `acquire`    /// filled, so the caller keeps it in place until `release`.    pub const Loan = struct {        const BitPosition = struct {            byte: usize,            mask: u8,        };        owner: *ConflictScratch,        epoch: u64,        /// Marks `variable_index` as met and returns true, or returns false when it was already        /// marked. The analysis calls it on each literal of a clause it resolves on, and a false        /// result skips a variable it has already counted. The call asserts that `variable_index`        /// is below the loan's variable count. The call returns `error.ConflictScratchStaleLoan`        /// for a stale loan.        pub fn markSeen(self: *const Loan, variable_index: u32) Exhaustion!bool {            const owner = try self.ownerFor();            assert(variable_index < owner.loan_variables);            const bit = bitPosition(variable_index);            if (owner.seen[bit.byte] & bit.mask != 0) return false;            owner.seen[bit.byte] |= bit.mask;            return true;        }        /// Removes the mark from `variable_index`. The analysis unmarks the variable it resolves on        /// at each step. The call asserts that `variable_index` is below the loan's variable count        /// and is marked. The call returns `error.ConflictScratchStaleLoan` for a stale loan.        pub fn clearSeen(self: *const Loan, variable_index: u32) Exhaustion!void {            const owner = try self.ownerFor();            assert(variable_index < owner.loan_variables);            const bit = bitPosition(variable_index);            assert(owner.seen[bit.byte] & bit.mask != 0);            owner.seen[bit.byte] &= ~bit.mask;        }        /// Returns true when `variable_index` is marked. The analysis walks the trail backward and        /// stops at the first marked variable. The call asserts that `variable_index` is below the        /// loan's variable count. The call returns `error.ConflictScratchStaleLoan` for a stale        /// loan.        pub fn isSeen(self: *const Loan, variable_index: u32) Exhaustion!bool {            const owner = try self.ownerFor();            assert(variable_index < owner.loan_variables);            const bit = bitPosition(variable_index);            return owner.seen[bit.byte] & bit.mask != 0;        }        /// Adds `literal` after the loan's earlier literals. The analysis adds each literal of the        /// learned clause through it. The call returns `error.ConflictScratchCapacityExceeded` when        /// the loan already holds its variable count of literals, and        /// `error.ConflictScratchStaleLoan` for a stale loan, before any change.        pub fn append(self: *const Loan, literal: Literal) Exhaustion!void {            const owner = try self.ownerFor();            if (owner.loan_literal_count == owner.loan_variables) {                return error.ConflictScratchCapacityExceeded;            }            owner.literals[owner.loan_literal_count] = literal;            owner.loan_literal_count += 1;        }        /// Counts one resolution step. The analysis calls it at the start of each resolution step.        /// The call returns `error.ConflictScratchCapacityExceeded` when the loan has already        /// counted its variable count of steps, and `error.ConflictScratchStaleLoan` for a stale        /// loan, before counting.        pub fn recordResolution(self: *const Loan) Exhaustion!void {            const owner = try self.ownerFor();            if (owner.loan_resolution_steps == owner.loan_variables) {                return error.ConflictScratchCapacityExceeded;            }            owner.loan_resolution_steps += 1;        }        /// Returns the literals added since `acquire`, in order. The analysis reads the learned        /// clause through it when the resolution ends. The slice points into the scratch storage,        /// so literals added under a later loan overwrite it. The call returns        /// `error.ConflictScratchStaleLoan` for a stale loan.        pub fn literals(self: *const Loan) Exhaustion![]const Literal {            const owner = try self.ownerFor();            assert(owner.loan_literal_count <= owner.loan_variables);            return owner.literals[0..owner.loan_literal_count];        }        fn ownerFor(self: *const Loan) Exhaustion!*ConflictScratch {            const owner = self.owner;            if (owner.phase != .steady or                !owner.loaned or                owner.loan_epoch != self.epoch or                owner.loan_identity != @intFromPtr(self))            {                return error.ConflictScratchStaleLoan;            }            return owner;        }        fn bitPosition(variable_index: u32) BitPosition {            return .{                .byte = variable_index / 8,                .mask = @as(u8, 1) << @intCast(variable_index % 8),            };        }    };    /// Returns a scratch in `.initialization` over `storage`, sized for `limits`. The solver builds    /// one over the tail of its trail allocation at the start of each solve that has at least one    /// variable. The call returns `error.CapacityOverflow` from `Capacity.derive`, and    /// `error.StorageLengthMismatch` when `storage` is shorter or longer than    /// `Capacity.storage_bytes`. On either error, `storage` stays unchanged and stays with the    /// caller. The scratch holds `storage` until `deinit` returns it.    pub fn init(storage: Storage, limits: Limits) InitError!ConflictScratch {        const capacity = try Capacity.derive(limits);        if (storage.len != capacity.storage_bytes) {            return error.StorageLengthMismatch;        }        const owned = storage;        const literals = typedSlice(Literal, owned, 0, capacity.variables);        const seen = owned[capacity.seen_offset..][0..capacity.seen_bytes];        assert(literals.len * @sizeOf(Literal) == capacity.literal_bytes);        assert(seen.len == capacity.seen_bytes);        return .{            .phase = .initialization,            .capacity = capacity,            .storage = owned,            .literals = literals,            .seen = seen,        };    }    /// Moves the scratch from `.initialization` to `.steady`, after which `acquire` can hand out    /// loans. The solver calls it once, right after `init`. The call asserts that the scratch is as    /// `init` left it.    pub fn activate(self: *ConflictScratch) void {        assert(self.phase == .initialization);        assert(self.storage.len == self.capacity.storage_bytes);        assert(!self.loaned);        assert(self.loan_identity == 0);        assert(self.loan_variables == 0);        assert(self.loan_literal_count == 0);        assert(self.loan_resolution_steps == 0);        self.phase = .steady;    }    /// Returns true when the scratch covers at least `required.variables` variables. A caller that    /// holds a scratch checks with it whether the scratch covers a variable count before reusing    /// the scratch for that count. The solver builds a new scratch at each solve, so the package    /// itself makes no call to it. The call asserts that the scratch is `.steady`.    pub fn admits(self: *const ConflictScratch, required: Capacity) bool {        assert(self.phase == .steady);        return self.capacity.variables >= required.variables;    }    /// Clears the marks and fills `loan` as the one loan out, covering `variables` variables. The    /// analysis takes a loan with it at the start of each conflict it analyzes. The call returns    /// `error.ConflictScratchInUse` while another loan is out,    /// `error.ConflictScratchCapacityExceeded` when `variables` passes the scratch's variable    /// count, and `error.ConflictScratchLoanEpochExhausted` when the loan counter is at the largest    /// `u64`. Each error leaves the scratch and its marks as they were. The call clears every mark    /// byte, at most one byte per 8 variables of the scratch. The call records the address of    /// `loan`, so the caller keeps `loan` in place until `release`. The call asserts that the    /// scratch is `.steady`.    pub fn acquire(        self: *ConflictScratch,        variables: usize,        loan: *Loan,    ) Exhaustion!void {        assert(self.phase == .steady);        if (self.loaned) return error.ConflictScratchInUse;        if (variables > self.capacity.variables) {            return error.ConflictScratchCapacityExceeded;        }        const epoch = std.math.add(u64, self.loan_epoch, 1) catch {            return error.ConflictScratchLoanEpochExhausted;        };        @memset(self.seen, 0);        loan.* = .{            .owner = self,            .epoch = epoch,        };        self.loaned = true;        self.loan_epoch = epoch;        self.loan_identity = @intFromPtr(loan);        self.loan_variables = variables;        self.loan_literal_count = 0;        self.loan_resolution_steps = 0;    }    /// Ends the current loan, so `acquire` can hand out the next one. The analysis releases its    /// loan when it returns, whether it learned a clause or failed. The call returns    /// `error.ConflictScratchStaleLoan` when `loan` is stale or comes from another scratch, and    /// then the current loan stays out. Every loan function refuses `loan` after the call. The call    /// asserts that the scratch is `.steady`.    pub fn release(self: *ConflictScratch, loan: *const Loan) Exhaustion!void {        assert(self.phase == .steady);        const owner = try loan.ownerFor();        if (owner != self) return error.ConflictScratchStaleLoan;        assert(self.loan_literal_count <= self.loan_variables);        assert(self.loan_resolution_steps <= self.loan_variables);        self.loaned = false;        self.loan_identity = 0;        self.loan_variables = 0;        self.loan_literal_count = 0;        self.loan_resolution_steps = 0;    }    /// Moves the scratch to `.teardown`, returns the storage passed to `init`, and leaves the    /// scratch undefined. The solver calls it when a solve ends and checks that the storage came    /// back with the same address and length. The call asserts that no loan is out. The call works    /// from `.initialization` or `.steady`.    pub fn deinit(self: *ConflictScratch) Storage {        assert(self.phase != .teardown);        assert(!self.loaned);        assert(self.loan_identity == 0);        assert(self.loan_variables == 0);        assert(self.loan_literal_count == 0);        assert(self.loan_resolution_steps == 0);        assert(self.storage.len == self.capacity.storage_bytes);        self.phase = .teardown;        const storage = self.storage;        self.* = undefined;        return storage;    }};

Source: lib/smt/src/root.zig:104

zig
pub const ConflictScratch = sat.ConflictScratch;
Called byCallsNo direct callsConflictScratchinittest sourcelib.smt.src.sat.scratchtest: conflict scratch capacity match...private sourcelib.smt.src.sat.solver.SolverensureConflictScratchStoragetest sourcelib.smt.src.sat.testtest: conflict scratch growth rejects...test sourcelib.smt.src.sat.testtest: sat solver conflict scratch pre...ConflictScratch.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.sat.scratchtest: conflict scratch capacity match...private sourcelib.smt.src.sat.solver.SolverensureConflictScratchStorageConflictScratch.Limitsinspect
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...private sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.Loanappend
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanbitPositionprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.LoanclearSeen
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...private sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanbitPositionprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.LoanisSeen
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...private sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.Loanliterals
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...private sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanbitPositionprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.LoanmarkSeen
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...private sourcelib.smt.src.sat.solver.SolverlearnFromConflictprivate sourcelib.smt.src.sat.scratch.ConflictScratch.LoanownerForConflictScratch.LoanrecordResolution
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...ConflictScratchacquire
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...test sourcelib.smt.src.sat.scratchtest: conflict scratch returns exact ...ConflictScratchactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...test sourcelib.smt.src.sat.scratchtest: conflict scratch returns exact ...ConflictScratchdeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...test sourcelib.smt.src.sat.scratchtest: conflict scratch returns exact ...private sourcelib.smt.src.sat.solver.SolversolveWithAssumptionsModeConflictScratch.Capacityderiveprivate sourcelib.smt.src.sat.scratchtypedSliceConflictScratchinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.sat.scratchtest: conflict scratch rejects before...ConflictScratchrelease
Static calls · unresolved targets: 0 · external targets: 1.

Also reachable as

sat.ConflictScratch.

Audit

Definitions25
Public names75
Members26
Version26.7.0
Revisiondaab053ee433