Skip to documentation
SLOP

tiny.smt.sat.scratch

Reference tiny.smt sat scratch

Defined in sat.

Scratch memory for one conflict analysis at a time: room for the literals of the clause being learned and one mark per variable.

API (1)

Types and contracts

Public types and contracts.

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

Source

Source: lib/smt/src/sat/root.zig:48

zig
pub const scratch = @import("scratch.zig");

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

zig
//! Scratch memory for one conflict analysis at a time: room for the literals of the clause being//! learned and one mark per variable. The solver analyzes each conflict it meets above decision//! level zero, and a caller that bounds memory wants that analysis to run in memory set aside//! before the search starts. The clause learned from one conflict holds at most one literal per//! variable, and the analysis marks each variable at most once, so the memory one analysis needs//! grows with the variable count alone. The solver already allocates the list of assigned literals//! in the order they were assigned (the trail), which holds at most one literal per variable.//!//! The scratch (`ConflictScratch`) lives in the trail's allocation, past its first variable-count//! entries. At the start of each solve, the solver grows that allocation to the variable count plus//! the entries the scratch needs (`Capacity.trail_capacity`) and hands the tail to the scratch. The//! scratch takes storage of exactly the byte length it needs, refuses any other length with//! `error.StorageLengthMismatch`, calls no allocator, and returns the same storage from `deinit`.//! That storage holds one literal per variable, then one bit per variable rounded up to whole//! bytes.//!//! The analysis works through a handle to the scratch memory for one conflict analysis (a *loan*),//! and the scratch hands out one loan at a time. A loan records the number of loans taken so far//! and its own address, so a released loan or a copy of a loan is refused with//! `error.ConflictScratchStaleLoan`. Every refusal comes before any change, so the scratch and the//! caller's storage stay as they were. Taking a loan clears the marks, at most one byte per 8//! variables, and a loan holds at most one literal and one resolution step per variable. A//! compile-time record of this memory (`ConflictScratch.claim`) states the size equation and the//! refusal behavior, and the shape check at the end of the file validates it.const std = @import("std");const alloc_phase = @import("alloc_phase");const types = @import("types.zig");const assert = std.debug.assert;const Literal = types.Literal;/// 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;    }};fn typedSlice(    comptime T: type,    bytes: ConflictScratch.Storage,    offset: usize,    count: usize,) []T {    const byte_count = count * @sizeOf(T);    const region: []align(@alignOf(T)) u8 = @alignCast(bytes[offset..][0..byte_count]);    return std.mem.bytesAsSlice(T, region);}test "conflict scratch capacity matches an independent typed-byte model" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_capacity"),            null,            null,            null,            null,            null,            null,        );    }    const capacity = try ConflictScratch.Capacity.derive(        ConflictScratch.Limits.inspect(96),    );    try std.testing.expectEqual(@as(usize, 96), capacity.variables);    try std.testing.expectEqual(@as(usize, 96 * @sizeOf(Literal)), capacity.literal_bytes);    try std.testing.expectEqual(capacity.literal_bytes, capacity.seen_offset);    try std.testing.expectEqual(@as(usize, 12), capacity.seen_bytes);    try std.testing.expectEqual(        @as(usize, 96 * @sizeOf(Literal) + 12),        capacity.storage_bytes,    );    try std.testing.expectEqual(@as(usize, 99), capacity.backing_literals);    try std.testing.expectEqual(@as(usize, 195), capacity.trail_capacity);    try std.testing.expectError(        error.CapacityOverflow,        ConflictScratch.Capacity.derive(ConflictScratch.Limits.inspect(std.math.maxInt(usize))),    );}test "conflict scratch returns exact caller storage and rejects inexact transfers" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_storage_identity"),            null,            null,            null,            null,            null,            null,        );    }    var bytes: [8 * @sizeOf(Literal) + 1]u8 align(ConflictScratch.storage_alignment) =        @splat(0xa5);    const before = bytes;    try std.testing.expectError(        error.StorageLengthMismatch,        ConflictScratch.init(bytes[0 .. bytes.len - 1], .{ .variables = 8 }),    );    try std.testing.expectEqualSlices(u8, &before, &bytes);    var oversized: [8 * @sizeOf(Literal) + 2]u8 align(ConflictScratch.storage_alignment) =        @splat(0x5a);    const oversized_before = oversized;    const oversized_transfer: ConflictScratch.Storage = oversized[0..];    const oversized_pointer = @intFromPtr(oversized_transfer.ptr);    const oversized_length = oversized_transfer.len;    try std.testing.expectError(        error.StorageLengthMismatch,        ConflictScratch.init(oversized_transfer, .{ .variables = 8 }),    );    try std.testing.expectEqual(oversized_pointer, @intFromPtr(oversized_transfer.ptr));    try std.testing.expectEqual(oversized_length, oversized_transfer.len);    try std.testing.expectEqualSlices(u8, &oversized_before, &oversized);    const transferred: ConflictScratch.Storage = bytes[0..];    var owner = try ConflictScratch.init(transferred, .{ .variables = 8 });    owner.activate();    const returned = owner.deinit();    try std.testing.expectEqual(@intFromPtr(transferred.ptr), @intFromPtr(returned.ptr));    try std.testing.expectEqual(transferred.len, returned.len);}test "conflict scratch rejects before mutation and exactly returns its affine loan" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_acquisition"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_overload"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_reuse"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_work_bound"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(ConflictScratch, "smt_conflict_scratch_foreign_risk"),            null,            null,            null,            null,            null,            null,        );    }    var bytes: [3 * @sizeOf(Literal) + 1]u8 align(ConflictScratch.storage_alignment) = undefined;    var owner = try ConflictScratch.init(bytes[0..], .{ .variables = 3 });    defer _ = owner.deinit();    owner.activate();    var loan: ConflictScratch.Loan = undefined;    try owner.acquire(3, &loan);    const copied_loan = loan;    const literal_pointer = owner.literals.ptr;    const seen_pointer = owner.seen.ptr;    try std.testing.expectError(        error.ConflictScratchStaleLoan,        copied_loan.append(Literal.positive(0)),    );    try std.testing.expectEqual(@as(usize, 0), owner.loan_literal_count);    try std.testing.expectError(        error.ConflictScratchStaleLoan,        owner.release(&copied_loan),    );    try std.testing.expect(owner.loaned);    try std.testing.expect(try loan.markSeen(0));    try std.testing.expect(!try loan.markSeen(0));    try loan.append(Literal.positive(0));    try loan.append(Literal.negative(1));    try loan.append(Literal.positive(2));    try loan.recordResolution();    try loan.recordResolution();    try loan.recordResolution();    try std.testing.expectError(        error.ConflictScratchCapacityExceeded,        loan.append(Literal.negative(2)),    );    try std.testing.expectError(error.ConflictScratchCapacityExceeded, loan.recordResolution());    var overlapping: ConflictScratch.Loan = undefined;    try std.testing.expectError(error.ConflictScratchInUse, owner.acquire(3, &overlapping));    try std.testing.expectEqual(@as(usize, 3), (try loan.literals()).len);    try owner.release(&loan);    try std.testing.expectError(        error.ConflictScratchStaleLoan,        copied_loan.append(Literal.positive(0)),    );    owner.seen[0] = 0x80;    var oversized: ConflictScratch.Loan = undefined;    try std.testing.expectError(        error.ConflictScratchCapacityExceeded,        owner.acquire(4, &oversized),    );    try std.testing.expect(owner.seen[0] != 0);    try std.testing.expect(!owner.loaned);    var reused: ConflictScratch.Loan = undefined;    try owner.acquire(3, &reused);    try std.testing.expectEqual(literal_pointer, owner.literals.ptr);    try std.testing.expectEqual(seen_pointer, owner.seen.ptr);    try std.testing.expect(!try reused.isSeen(0));    try std.testing.expectError(        error.ConflictScratchStaleLoan,        copied_loan.append(Literal.positive(0)),    );    try owner.release(&reused);    owner.loan_epoch = std.math.maxInt(u64);    owner.seen[0] = 0x80;    var exhausted: ConflictScratch.Loan = undefined;    try std.testing.expectError(        error.ConflictScratchLoanEpochExhausted,        owner.acquire(3, &exhausted),    );    try std.testing.expect(owner.seen[0] != 0);    try std.testing.expect(!owner.loaned);}comptime {    alloc_phase.capacity.requireProvisionedRejectingOwnerShape(ConflictScratch);}

Audit

Definitions1
Public names1
Members0
Version26.7.0
Revisiondaab053ee433