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.
ConflictScratch: 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.
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
| Definitions | 1 |
|---|---|
| Public names | 1 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |