Skip to documentation
SLOP

tiny.smt.bitvec

Reference tiny.smt bitvec

Defined in tiny.smt.

Decides formulas over Booleans, fixed-width bit-vectors, arrays and uninterpreted functions by turning them into clauses for a SAT solver, and reads a satisfying assignment back as a value for each constant.

API (19)

Actions

Public operations.

Types and contracts

Public types and contracts.

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

Source

Called byCallstest sourcelib.smt.src.bitvectest: bit-vector encoder compares fin...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects unsi...+36 morebitvec.EncoderencodeBoolprivate sourcelib.smt.src.bitvec.EncoderencodeFunctionCongruencebitvec.EncoderassertTerm
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.smt.src.bitvectest: bit-vector encoder solves with ...bitvec.EncoderencodeBoolprivate sourcelib.smt.src.bitvec.EncoderencodeFunctionCongruencebitvec.EncoderassumeTerm
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.smt.src.bitvectest: bit-vector encoder compares fin...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects unsi...+38 moreprivate sourcelib.smt.src.bitvec.EncoderfreeArraybitvec.Encoderdeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate sourcelib.smt.src.bitvec.Encoderencodetest sourcelib.smt.src.bitvectest: bit-vector encoder finds wrappe...test sourcelib.smt.src.bitvectest: bit-vector encoder rejects inva...test sourcelib.smt.src.bitvectest: bit-vector encoder solves subtr...private sourcelib.smt.src.bitvec.Encoderencodebitvec.EncoderencodeBits
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsbitvec.EncoderassertTermbitvec.EncoderassumeTermprivate sourcelib.smt.src.bitvec.Encoderencodeprivate sourcelib.smt.src.bitvec.EncoderencodeBoolNaryprivate sourcelib.smt.src.bitvec.Encoderencodebitvec.EncoderencodeBool
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsbitvec.Encodersolvetest sourcelib.smt.src.bitvectest: bit-vector encoder extracts nam...test sourcelib.smt.src.bitvectest: bit-vector encoder permits dist...test sourcelib.smt.src.bitvectest: bit-vector encoder solves arith...test sourcelib.smt.src.bitvectest: bit-vector encoder solves bitwi...+9 moreprivate sourcelib.smt.src.bitvec.Encoderencodebitvec.EncoderencodeSymbols
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.smt.src.bitvectest: bit-vector encoder compares fin...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects sign...test sourcelib.smt.src.bitvectest: bit-vector encoder detects unsi...+38 morebitvec.Encoderinit
Static calls · unresolved targets: 0 · external targets: 5.
Called byCallstest sourcelib.smt.src.bitvectest: bit-vector encoder extracts nam...test sourcelib.smt.src.bitvectest: bit-vector encoder permits dist...test sourcelib.smt.src.bitvectest: bit-vector encoder solves arith...test sourcelib.smt.src.bitvectest: bit-vector encoder solves bitwi...test sourcelib.smt.src.bitvectest: bit-vector encoder solves conca...+8 moreprivate sourcelib.smt.src.bitvec.EncoderappendFunctionApplicationModelprivate sourcelib.smt.src.bitvec.EncodermodelValueForModelappendModeldeinitModelinitbitvec.Encodermodel
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.smt.src.bitvectest: bit-vector encoder solves with ...private sourcelib.smt.src.bitvec.EncoderencodeFunctionCongruencebitvec.EncoderencodeSymbolsbitvec.Encodersolve
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersModelValuedeinitbitvec.FunctionModelEntrydeinit
Static calls · unresolved targets: 0 · external targets: 2.

Source: lib/smt/src/bitvec.zig

zig
//! Decides formulas over Booleans, fixed-width bit-vectors, arrays and uninterpreted functions by//! turning them into clauses for a SAT solver, and reads a satisfying assignment back as a value//! for each constant.//!//! A caller holds a formula and needs to know whether some values of its constants make it true,//! and which values do. A caller also tests a condition without asserting it for good, and after a//! contradiction learns a set of its tested conditions that the formula refutes together.//!//! A SAT solver accepts only clauses over Boolean variables, so every bit-vector operation has to//! become Boolean relations between single bits. An array with an index of w bits has 2 to the//! power w cells, so its size doubles with each index bit. An uninterpreted function has no//! definition: the only thing known about it is that equal arguments give equal results.//!//! [SMT-LIB](https://smt-lib.org/) defines the fixed-size bit-vector operators on every input,//! division by zero and shifts past the width included. The encoder follows those definitions://! unsigned division by zero gives all ones, the remainder by zero is the dividend, a left shift or//! a logical right shift by at least the width gives zero, and an arithmetic right shift by at//! least the width copies the sign bit into every bit.//!//! The encoder (`Encoder`) gives each bit-vector one Boolean variable per bit, least significant//! bit first, and each operator the clauses of and, or and exclusive-or gates over those bits.//! Addition carries from each bit to the next, multiplication adds one shifted partial product per//! bit, and division finds its quotient one bit at a time from the most significant bit down. One//! variable fixed true by a unit clause stands for true, and its negation stands for false, so//! every bit of a Boolean constant or a bit-vector constant is one of those two literals.//!//! An array becomes one cell of element bits for each index value, so the encoder refuses an index//! wider than eight bits with `ArrayIndexTooWide` and an array has at most 256 cells. Two arrays//! are equal when every pair of cells is equal.//!//! For each pair of applications of one function, one clause makes their results equal whenever//! their arguments are equal. The encoder writes these clauses once, the first time a caller//! asserts, assumes or solves, over every application in the `Context`, asserted or not.//!//! The encoder encodes each term once and remembers the result, in a table sized to the `Context`//! at `Encoder.init`, so a term built after `init` fails with `TermOutOfRange`. Integer terms and//! `distinct` fail with `UnsupportedTerm`. `solve` encodes every named constant of the `Context`,//! so a single integer constant fails every solve.//!//! After a satisfiable answer, `Encoder.model` reads the solver's assignment into a `Model`: one//! value per named constant and one entry per encoded function application. A model value holds at//! most 128 bits, and a wider named constant, function argument, function result or array cell//! fails with `ModelValueTooWide`.//!//! A caller builds an `Encoder` over a `Context` and a `sat.Solver`, asserts terms with//! `assertTerm`, assumes terms with `assumeTerm`, and calls `solve`. On a satisfiable answer it//! reads `model`, and on an unsatisfiable one it asks the solver for the unsat core and the proof.const std = @import("std");const sat = @import("sat/root.zig");const term = @import("term.zig");/// The errors the encoder and `Encoder.model` return besides the solver's and the allocator's, for/// code that reports why a formula could not be encoded or read back. The encoder's functions/// return `Error`, which is `anyerror`, so the compiler does not check a switch on these tags for/// completeness.pub const EncodeError = error{    /// A term used as a condition, such as an assertion, an assumption or an operand of `and`, has    /// another sort.    ExpectedBool,    /// A term used as a bit-vector operand has another sort.    ExpectedBitVec,    /// The operands of an operator have different widths or different kinds, or an application's    /// argument count differs from its declaration.    SortMismatch,    /// The term is an integer term or `distinct`, which the encoder does not support, or it    /// compares two values that were never encoded.    UnsupportedTerm,    /// `Encoder.model` found a constant with no value: the solver has not assigned it, or it was    /// never encoded. The error follows a model read before a satisfiable answer, or of a constant    /// that `encodeSymbols` has not reached.    ModelUnavailable,    /// `Encoder.model` found a value wider than 128 bits.    ModelValueTooWide,    /// The term's index is past the terms that existed when the `Encoder` was built.    TermOutOfRange,    /// An extract range lies outside its operand, or an operator that needs at least one bit got a    /// bit-vector of width 0.    InvalidBitVectorRange,    /// A term used as an array has another sort.    ExpectedArray,    /// An array's index is wider than eight bits.    ArrayIndexTooWide,    /// An application names a function past the declarations of the `Context`.    FunctionOutOfRange,};/// The error set of the encoder's functions: any error, returned by every public function of the/// encoder, so a caller can absorb it into its own error set. The set covers `EncodeError`, the/// solver's errors, `error.OutOfMemory` and the errors of `Context.sortOf`. Because it is/// `anyerror`, the compiler cannot list the errors a call returns.pub const Error = anyerror;const max_native_array_index_width = 8;const ArrayValue = struct {    index_width: u32,    element_width: u32,    cells: [][]sat.Literal,};const Value = union(enum) {    none,    bool: sat.Literal,    bits: []sat.Literal,    array: ArrayValue,};/// The value of a constant, a function argument or a function result in a model: a Boolean, a/// bit-vector or an array, for code that reads one from a `Model` to print it or compare it with an/// expected value. A value that holds an array owns its cells, so its owner calls `deinit`.pub const ModelValue = union(enum) {    /// A Boolean value.    bool: bool,    /// A bit-vector value: its bits as an unsigned number, least significant bit as bit 0, and its    /// width.    bitvec: struct {        value: u128,        width: u32,    },    /// An array value: its index width, its element width and one number per cell, the cell for    /// index i at position i. The cells are a slice the value owns.    array: struct {        index_width: u32,        element_width: u32,        cells: []u128,    },    /// Frees an array value's cells with `allocator`, which has to be the allocator that made them,    /// for a caller that holds a value outside a `Model`. The function frees nothing for a Boolean    /// or a bit-vector.    pub fn deinit(self: *ModelValue, allocator: std.mem.Allocator) void {        switch (self.*) {            .array => |array| allocator.free(array.cells),            else => {},        }        self.* = undefined;    }    /// Writes the value in SMT-LIB syntax: `true` or `false`, `(_ bvV W)`, or an array as    /// `(array (_ BitVec i) (_ BitVec e) 0->(_ bvV e) ...)` with one index and value per cell, for    /// `Model.write` to call for every value. The function returns only the writer's errors.    pub fn write(self: ModelValue, writer: *std.Io.Writer) std.Io.Writer.Error!void {        switch (self) {            .bool => |value| try writer.writeAll(if (value) "true" else "false"),            .bitvec => |value| try writer.print("(_ bv{d} {d})", .{ value.value, value.width }),            .array => |value| {                try writer.print("(array (_ BitVec {d}) (_ BitVec {d})", .{ value.index_width, value.element_width });                for (value.cells, 0..) |cell, index| {                    try writer.print(" {d}->(_ bv{d} {d})", .{ index, cell, value.element_width });                }                try writer.writeAll(")");            },        }    }};/// One named constant of a model and its value, for code that walks a `Model`.pub const ModelEntry = struct {    /// The constant's name, a copy the `Model` owns and frees.    name: []u8,    /// The constant's value, which the `Model` owns and frees.    value: ModelValue,};/// One application of an uninterpreted function in a model: its argument values and its result/// value, for code that checks a function's model.pub const FunctionModelEntry = struct {    /// The argument values in declaration order, a slice the entry owns.    arguments: []ModelValue,    /// The value of the application's result, which the entry owns.    result: ModelValue,    /// Frees every argument value, the argument slice and the result value with `allocator`, for    /// `FunctionModel.deinit` to call for each entry.    pub fn deinit(self: *FunctionModelEntry, allocator: std.mem.Allocator) void {        for (self.arguments) |*argument| {            argument.deinit(allocator);        }        allocator.free(self.arguments);        self.result.deinit(allocator);        self.* = undefined;    }};/// The model of one uninterpreted function: its name and one entry per application the encoder/// encoded, returned by `Model.getFunction` so code can check each application of one function. The/// model lists the applications in the formula, and it gives no value for other arguments.pub const FunctionModel = struct {    /// The function's name, a copy the model owns.    name: []u8,    /// The entries in the order of their applications in the `Context`. Two applications with equal    /// arguments give two entries with equal results.    entries: std.ArrayList(FunctionModelEntry) = .empty,    /// Frees the name, every entry and the entry list with `allocator`, for `Model.deinit` to call    /// for each function.    pub fn deinit(self: *FunctionModel, allocator: std.mem.Allocator) void {        allocator.free(self.name);        for (self.entries.items) |*entry| {            entry.deinit(allocator);        }        self.entries.deinit(allocator);        self.* = undefined;    }};/// The values a satisfying assignment gives: one per named constant, and one entry per encoded/// application of each uninterpreted function. A caller reads a constant's value from it with `get`/// or prints it with `write` after `Encoder.model` returns one for a satisfiable answer. The model/// owns every name and value in it, and its owner frees them all with `deinit`.pub const Model = struct {    allocator: std.mem.Allocator,    /// The named constants and their values, in the order the constants were built. Two constants    /// built with one name give two entries, and `get` returns the first.    entries: std.ArrayList(ModelEntry) = .empty,    /// The model of each uninterpreted function with an encoded application, one per function name.    functions: std.ArrayList(FunctionModel) = .empty,    /// Returns an empty model that allocates with `allocator`. A caller makes one to build an    /// expected model by hand, and `Encoder.model` makes one. The call allocates nothing.    pub fn init(allocator: std.mem.Allocator) Model {        return .{ .allocator = allocator };    }    /// Frees every name, every value and every function model. The owner calls it once when done    /// with the values. A value returned by `get` and a pointer returned by `getFunction` are    /// invalid afterward.    pub fn deinit(self: *Model) void {        for (self.entries.items) |*entry| {            self.allocator.free(entry.name);            entry.value.deinit(self.allocator);        }        self.entries.deinit(self.allocator);        for (self.functions.items) |*function| {            function.deinit(self.allocator);        }        self.functions.deinit(self.allocator);        self.* = undefined;    }    /// Adds the constant `name` with value `value`. `Encoder.model` calls it once per named    /// constant. The call copies `name` and takes ownership of `value`, and on failure it frees    /// `value`.    pub fn append(self: *Model, name: []const u8, value: ModelValue) !void {        var owned_value = value;        errdefer owned_value.deinit(self.allocator);        const owned_name = try self.allocator.dupe(u8, name);        errdefer self.allocator.free(owned_name);        try self.entries.append(self.allocator, .{ .name = owned_name, .value = owned_value });    }    /// Returns the value of the first constant named `name`, or `null` when the model has none. A    /// caller reads with it a constant's value after a satisfiable answer, as every encoder test    /// does. An array value's cells stay owned by the model, so the caller does not free them. The    /// lookup checks each entry in turn.    pub fn get(self: *const Model, name: []const u8) ?ModelValue {        for (self.entries.items) |entry| {            if (std.mem.eql(u8, entry.name, name)) return entry.value;        }        return null;    }    /// Adds an entry with argument values `arguments` and result value `result` to the model of the    /// function `name`, and creates that function's model on first use. `Encoder.model` calls it    /// once per encoded application. The call takes ownership of `arguments` and `result`, and on    /// failure it frees them.    pub fn appendFunctionApplication(self: *Model, name: []const u8, arguments: []ModelValue, result: ModelValue) !void {        var owned_result = result;        errdefer {            for (arguments) |*argument| {                argument.deinit(self.allocator);            }            self.allocator.free(arguments);            owned_result.deinit(self.allocator);        }        const function = try self.functionModel(name);        try function.entries.append(self.allocator, .{ .arguments = arguments, .result = owned_result });    }    /// Returns the model of the function `name`, or `null` when no application of it was encoded. A    /// caller checks the values an uninterpreted function took with it. The model keeps ownership,    /// and the pointer is valid until the model changes or is freed.    pub fn getFunction(self: *const Model, name: []const u8) ?*const FunctionModel {        for (self.functions.items) |*function| {            if (std.mem.eql(u8, function.name, name)) return function;        }        return null;    }    /// Writes one `name: value` line per constant, then per function a `name:` line followed by one    /// indented `(arguments) -> result` line per entry, with each value in the syntax of    /// `ModelValue.write`. A caller prints a model for a person to read with it. The call returns    /// only the writer's errors.    pub fn write(self: *const Model, writer: *std.Io.Writer) std.Io.Writer.Error!void {        for (self.entries.items) |entry| {            try writer.print("{s}: ", .{entry.name});            try entry.value.write(writer);            try writer.writeAll("\n");        }        for (self.functions.items) |function| {            try writer.print("{s}:\n", .{function.name});            for (function.entries.items) |entry| {                try writer.writeAll("  (");                for (entry.arguments, 0..) |argument, index| {                    if (index > 0) try writer.writeAll(", ");                    try argument.write(writer);                }                try writer.writeAll(") -> ");                try entry.result.write(writer);                try writer.writeAll("\n");            }        }    }    fn functionModel(self: *Model, name: []const u8) !*FunctionModel {        for (self.functions.items) |*item| {            if (std.mem.eql(u8, item.name, name)) return item;        }        const owned_name = try self.allocator.dupe(u8, name);        errdefer self.allocator.free(owned_name);        try self.functions.append(self.allocator, .{ .name = owned_name });        return &self.functions.items[self.functions.items.len - 1];    }};const BitwiseKind = enum {    and_,    or_,    xor,};const ShiftKind = enum {    left,    right,};const RotateKind = enum {    left,    right,};const DivRemResult = struct {    quotient: []sat.Literal,    remainder: []sat.Literal,};const ExtendKind = enum {    zero,    sign,};/// Turns the terms of one `Context` into clauses of one `sat.Solver`, and reads the solver's/// assignment back as a `Model`. A caller builds one per solve, over the `Context` that holds its/// terms and the solver that will decide them. The encoder borrows the `Context` and the solver, so/// both have to outlive it. The encoder encodes each term once and remembers the result, and the/// clauses it adds stay in the solver.pub const Encoder = struct {    allocator: std.mem.Allocator,    ctx: *const term.Context,    solver: *sat.Solver,    cache: []Value,    true_literal: sat.Literal,    false_literal: sat.Literal,    function_congruence_encoded: bool = false,    /// Returns an encoder over `ctx` and `solver` that allocates with `allocator`. A caller builds    /// the encoder once the formula's terms exist and before asserting any of them. The call adds    /// one variable to the solver and a unit clause that fixes it true, and the encoder uses it as    /// the constant true. The encoder sizes its table of encoded terms to the terms of `ctx` at    /// this call, so a term built later fails with `TermOutOfRange`.    pub fn init(allocator: std.mem.Allocator, ctx: *const term.Context, solver: *sat.Solver) Error!Encoder {        const true_variable = try solver.addVariable();        const true_literal = sat.Literal.positive(true_variable);        try solver.addClause(&.{true_literal});        const cache = try allocator.alloc(Value, ctx.nodes.items.len);        @memset(cache, .none);        return .{            .allocator = allocator,            .ctx = ctx,            .solver = solver,            .cache = cache,            .true_literal = true_literal,            .false_literal = true_literal.negated(),        };    }    /// Frees the bits and array cells of every encoded term and the table that held them. The owner    /// calls it once when done with the encoder. The clauses already added to the solver stay    /// there.    pub fn deinit(self: *Encoder) void {        for (self.cache) |value| {            switch (value) {                .bits => |bits| self.allocator.free(bits),                .array => |array| self.freeArray(array),                else => {},            }        }        self.allocator.free(self.cache);        self.* = undefined;    }    /// Encodes the Boolean term `assertion` and adds a unit clause that makes it true in every    /// later solve. A caller states with it each part of the formula that has to hold, as a caller    /// does for every assertion of a `Script`. The first call of `assertTerm`, `assumeTerm` or    /// `solve` also adds the equal-arguments clauses for every function application of the    /// `Context`. The call returns `ExpectedBool` for a term of another sort.    pub fn assertTerm(self: *Encoder, assertion: term.Term) Error!void {        const literal = try self.encodeBool(assertion);        try self.solver.addClause(&.{literal});        try self.encodeFunctionCongruence();    }    /// Encodes the Boolean term `assumption`, adds its literal to the solver's active assumptions    /// and returns that literal. A caller calls it to test a condition without asserting it for    /// good, and matches the returned literal against the solver's unsat core, a set of tested    /// conditions that the formula refutes together. The assumption holds in every solve until the    /// caller pops its assumption frame or clears the assumptions. The clauses that encode the term    /// stay in the solver after the assumption is removed. The first call of `assertTerm`,    /// `assumeTerm` or `solve` also adds the equal-arguments clauses for every function    /// application. The call returns `ExpectedBool` for a term of another sort.    pub fn assumeTerm(self: *Encoder, assumption: term.Term) Error!sat.Literal {        const literal = try self.encodeBool(assumption);        try self.solver.assume(literal);        try self.encodeFunctionCongruence();        return literal;    }    /// Encodes every named constant of the `Context`, adds the equal-arguments clauses if no    /// earlier call did, and solves under the active assumptions. A caller decides the formula with    /// it after asserting and assuming its terms. The call returns the solver's answer:    /// satisfiable, unsatisfiable or unknown. Because the encoder encodes every named constant, a    /// single integer constant in the `Context` makes the call fail with `UnsupportedTerm`, even if    /// no assertion uses it.    pub fn solve(self: *Encoder) Error!sat.Status {        try self.encodeSymbols();        try self.encodeFunctionCongruence();        return try self.solver.solveWithActiveAssumptions();    }    /// Encodes the Boolean term `id` and returns the literal that is true exactly when the term is.    /// A caller that builds its own clauses around a term, or checks one term's value, gets the    /// term's literal with it. The call returns `ExpectedBool` for a term of another sort.    pub fn encodeBool(self: *Encoder, id: term.Term) Error!sat.Literal {        switch (try self.encode(id)) {            .bool => |literal| return literal,            else => return EncodeError.ExpectedBool,        }    }    /// Encodes the bit-vector term `id` and returns one literal per bit, least significant bit    /// first. A caller that builds its own clauses over a bit-vector's bits gets them with it. The    /// encoder owns the returned slice, which stays valid until `deinit`. The call returns    /// `ExpectedBitVec` for a term of another sort.    pub fn encodeBits(self: *Encoder, id: term.Term) Error![]const sat.Literal {        switch (try self.encode(id)) {            .bits => |bits| return bits,            else => return EncodeError.ExpectedBitVec,        }    }    fn encodeArray(self: *Encoder, id: term.Term) Error!ArrayValue {        switch (try self.encode(id)) {            .array => |array| return array,            else => return EncodeError.ExpectedArray,        }    }    /// Encodes every named constant of the `Context`, so that `model` finds a value for each one. A    /// caller that calls `sat.Solver.solve` directly, as the encoder's tests do, calls it first so    /// that every constant has a value to read. `solve` calls it.    pub fn encodeSymbols(self: *Encoder) Error!void {        for (self.ctx.nodes.items, 0..) |node, index| {            switch (node) {                .symbol => _ = try self.encode(@intCast(index)),                else => {},            }        }    }    /// Returns a `Model` that allocates with `allocator`, with the value of every named constant    /// and one entry per encoded function application. A caller reads the satisfying values with it    /// after a satisfiable answer. The caller owns the model and frees it with `Model.deinit`. The    /// call returns `ModelUnavailable` when a constant has no value, as before a satisfiable answer    /// or before `encodeSymbols`, `ModelValueTooWide` for a value above 128 bits, and    /// `UnsupportedTerm` for an integer constant.    pub fn model(self: *const Encoder, allocator: std.mem.Allocator) Error!Model {        var result = Model.init(allocator);        errdefer result.deinit();        for (self.ctx.nodes.items, 0..) |node, index| {            switch (node) {                .symbol => |symbol| try result.append(symbol.name, try self.modelValueFor(allocator, @intCast(index), symbol.sort)),                .apply => |application| {                    if (self.cache[index] != .none) try self.appendFunctionApplicationModel(allocator, &result, @intCast(index), application);                },                else => {},            }        }        return result;    }    fn encode(self: *Encoder, id: term.Term) Error!Value {        if (id >= self.cache.len) return EncodeError.TermOutOfRange;        if (self.cache[id] != .none) return self.cache[id];        const node = self.ctx.nodes.items[id];        const value = switch (node) {            .symbol => |symbol| try self.encodeSymbol(symbol.sort),            .apply => |item| try self.encodeApply(item),            .bool => |value| Value{ .bool = if (value) self.true_literal else self.false_literal },            .int => return EncodeError.UnsupportedTerm,            .bitvec => |value| Value{ .bits = try self.constantBits(value.value, value.width) },            .not => |operand| Value{ .bool = (try self.encodeBool(operand)).negated() },            .and_ => |operands| Value{ .bool = try self.encodeBoolNary(operands, true) },            .or_ => |operands| Value{ .bool = try self.encodeBoolNary(operands, false) },            .implies => |pair| Value{ .bool = try self.orGate((try self.encodeBool(pair.lhs)).negated(), try self.encodeBool(pair.rhs)) },            .eq => |pair| try self.encodeEq(pair.lhs, pair.rhs),            .add => return EncodeError.UnsupportedTerm,            .mul => return EncodeError.UnsupportedTerm,            .le, .lt, .ge, .gt => return EncodeError.UnsupportedTerm,            .distinct => return EncodeError.UnsupportedTerm,            .bvadd => |pair| Value{ .bits = try self.encodeAdd(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsub => |pair| Value{ .bits = try self.encodeSub(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvmul => |pair| Value{ .bits = try self.encodeMul(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvule => |pair| Value{ .bool = try self.encodeUle(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvult => |pair| Value{ .bool = try self.encodeUlt(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsle => |pair| Value{ .bool = try self.encodeSle(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvslt => |pair| Value{ .bool = try self.encodeSlt(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvuaddo => |pair| Value{ .bool = try self.encodeUaddOverflow(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsaddo => |pair| Value{ .bool = try self.encodeSaddOverflow(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvssubo => |pair| Value{ .bool = try self.encodeSsubOverflow(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvumulo => |pair| Value{ .bool = try self.encodeUmulOverflow(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsmulo => |pair| Value{ .bool = try self.encodeSmulOverflow(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvnot => |operand| Value{ .bits = try self.encodeBitwiseNot(try self.encodeBits(operand)) },            .bvand => |pair| Value{ .bits = try self.encodeBitwiseBinary(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs), .and_) },            .bvor => |pair| Value{ .bits = try self.encodeBitwiseBinary(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs), .or_) },            .bvxor => |pair| Value{ .bits = try self.encodeBitwiseBinary(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs), .xor) },            .bvshl => |pair| Value{ .bits = try self.encodeShift(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs), .left) },            .bvlshr => |pair| Value{ .bits = try self.encodeShift(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs), .right) },            .bvashr => |pair| Value{ .bits = try self.encodeArithmeticShiftRight(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvrotl => |item| Value{ .bits = try self.encodeRotate(try self.encodeBits(item.operand), item.amount, .left) },            .bvrotr => |item| Value{ .bits = try self.encodeRotate(try self.encodeBits(item.operand), item.amount, .right) },            .bvudiv => |pair| blk: {                const result = try self.encodeUnsignedDivRem(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs));                self.allocator.free(result.remainder);                break :blk Value{ .bits = result.quotient };            },            .bvurem => |pair| blk: {                const result = try self.encodeUnsignedDivRem(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs));                self.allocator.free(result.quotient);                break :blk Value{ .bits = result.remainder };            },            .bvsdiv => |pair| Value{ .bits = try self.encodeSignedDiv(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsrem => |pair| Value{ .bits = try self.encodeSignedRem(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvsmod => |pair| Value{ .bits = try self.encodeSignedMod(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .array_select => |item| Value{ .bits = try self.encodeArraySelect(try self.encodeArray(item.array), try self.encodeBits(item.index)) },            .array_store => |item| Value{ .array = try self.encodeArrayStore(try self.encodeArray(item.array), try self.encodeBits(item.index), try self.encodeBits(item.value)) },            .bvconcat => |pair| Value{ .bits = try self.encodeConcat(try self.encodeBits(pair.lhs), try self.encodeBits(pair.rhs)) },            .bvextract => |item| Value{ .bits = try self.encodeExtract(try self.encodeBits(item.operand), item.high, item.low) },            .bvzeroext => |item| Value{ .bits = try self.encodeExtend(try self.encodeBits(item.operand), item.extra, .zero) },            .bvsignext => |item| Value{ .bits = try self.encodeExtend(try self.encodeBits(item.operand), item.extra, .sign) },        };        self.cache[id] = value;        return value;    }    fn encodeSymbol(self: *Encoder, sort: term.Sort) Error!Value {        return switch (sort) {            .bool => .{ .bool = try self.freshLiteral() },            .bitvec => |width| .{ .bits = try self.freshBits(width) },            .array => |array| .{ .array = try self.freshArray(array.index_width, array.element_width) },            .int => EncodeError.UnsupportedTerm,        };    }    fn encodeApply(self: *Encoder, item: term.ApplyExpr) Error!Value {        if (item.function >= self.ctx.functions.items.len) return EncodeError.FunctionOutOfRange;        const decl = self.ctx.functions.items[item.function];        if (decl.params.len != item.args.len) return EncodeError.SortMismatch;        for (item.args) |argument| {            _ = try self.encode(argument);        }        return try self.encodeSymbol(decl.result);    }    fn modelValueFor(self: *const Encoder, allocator: std.mem.Allocator, id: term.Term, sort: term.Sort) Error!ModelValue {        if (id >= self.cache.len) return EncodeError.TermOutOfRange;        return switch (sort) {            .bool => switch (self.cache[id]) {                .bool => |literal| switch (self.solver.literalValue(literal)) {                    .true => .{ .bool = true },                    .false => .{ .bool = false },                    .unset => EncodeError.ModelUnavailable,                },                else => EncodeError.ModelUnavailable,            },            .bitvec => |width| switch (self.cache[id]) {                .bits => |bits| .{ .bitvec = .{ .value = try modelValue(self.solver, bits), .width = width } },                else => EncodeError.ModelUnavailable,            },            .array => switch (self.cache[id]) {                .array => |array| try self.modelArrayValue(allocator, array),                else => EncodeError.ModelUnavailable,            },            .int => EncodeError.UnsupportedTerm,        };    }    fn appendFunctionApplicationModel(        self: *const Encoder,        allocator: std.mem.Allocator,        model_result: *Model,        id: term.Term,        application: term.ApplyExpr,    ) Error!void {        if (application.function >= self.ctx.functions.items.len) return EncodeError.FunctionOutOfRange;        const decl = self.ctx.functions.items[application.function];        const arguments = try self.modelArguments(allocator, application.args);        var arguments_handled = false;        errdefer {            if (!arguments_handled) {                for (arguments) |*argument| {                    argument.deinit(allocator);                }                allocator.free(arguments);            }        }        const value = try self.modelValueFor(allocator, id, decl.result);        var value_handled = false;        errdefer {            if (!value_handled) {                var owned_value = value;                owned_value.deinit(allocator);            }        }        arguments_handled = true;        value_handled = true;        try model_result.appendFunctionApplication(decl.name, arguments, value);    }    fn modelArguments(self: *const Encoder, allocator: std.mem.Allocator, arguments: []const term.Term) Error![]ModelValue {        const values = try allocator.alloc(ModelValue, arguments.len);        var initialized: usize = 0;        errdefer {            for (values[0..initialized]) |*value| {                value.deinit(allocator);            }            allocator.free(values);        }        for (arguments, 0..) |argument, index| {            values[index] = try self.modelValueFor(allocator, argument, try self.ctx.sortOf(argument));            initialized += 1;        }        return values;    }    fn encodeEq(self: *Encoder, lhs: term.Term, rhs: term.Term) Error!Value {        const lhs_value = try self.encode(lhs);        const rhs_value = try self.encode(rhs);        return .{ .bool = try self.encodeValueEqual(lhs_value, rhs_value) };    }    fn encodeValueEqual(self: *Encoder, lhs_value: Value, rhs_value: Value) Error!sat.Literal {        return switch (lhs_value) {            .bool => |lhs_literal| switch (rhs_value) {                .bool => |rhs_literal| try self.xnorGate(lhs_literal, rhs_literal),                else => EncodeError.SortMismatch,            },            .bits => |lhs_bits| switch (rhs_value) {                .bits => |rhs_bits| try self.bitsEqual(lhs_bits, rhs_bits),                else => EncodeError.SortMismatch,            },            .array => |lhs_array| switch (rhs_value) {                .array => |rhs_array| try self.arraysEqual(lhs_array, rhs_array),                else => EncodeError.SortMismatch,            },            .none => EncodeError.UnsupportedTerm,        };    }    fn encodeFunctionCongruence(self: *Encoder) Error!void {        if (self.function_congruence_encoded) return;        for (self.ctx.nodes.items, 0..) |node, index| {            switch (node) {                .apply => |application| {                    const id: term.Term = @intCast(index);                    _ = try self.encode(id);                    var previous_index: usize = 0;                    while (previous_index < index) : (previous_index += 1) {                        switch (self.ctx.nodes.items[previous_index]) {                            .apply => |previous| {                                if (previous.function != application.function) continue;                                try self.encodeApplicationCongruence(previous, @intCast(previous_index), application, id);                            },                            else => {},                        }                    }                },                else => {},            }        }        self.function_congruence_encoded = true;    }    fn encodeApplicationCongruence(        self: *Encoder,        lhs_application: term.ApplyExpr,        lhs: term.Term,        rhs_application: term.ApplyExpr,        rhs: term.Term,    ) Error!void {        if (lhs_application.args.len != rhs_application.args.len) return EncodeError.SortMismatch;        const clause = try self.allocator.alloc(sat.Literal, lhs_application.args.len + 1);        defer self.allocator.free(clause);        for (lhs_application.args, rhs_application.args, 0..) |lhs_arg, rhs_arg, index| {            clause[index] = switch (try self.encodeEq(lhs_arg, rhs_arg)) {                .bool => |literal| literal.negated(),                else => return EncodeError.ExpectedBool,            };        }        clause[lhs_application.args.len] = try self.encodeValueEqual(try self.encode(lhs), try self.encode(rhs));        try self.solver.addClause(clause);    }    fn encodeBoolNary(self: *Encoder, operands: []const term.Term, comptime is_and: bool) Error!sat.Literal {        if (operands.len == 0) return if (is_and) self.true_literal else self.false_literal;        var result = try self.encodeBool(operands[0]);        for (operands[1..]) |operand| {            const rhs = try self.encodeBool(operand);            result = if (is_and) try self.andGate(result, rhs) else try self.orGate(result, rhs);        }        return result;    }    fn constantBits(self: *Encoder, value: u128, width: u32) Error![]sat.Literal {        const bits = try self.allocator.alloc(sat.Literal, width);        for (bits, 0..) |*bit, index| {            bit.* = if (((value >> @intCast(index)) & 1) == 1) self.true_literal else self.false_literal;        }        return bits;    }    fn freshBits(self: *Encoder, width: u32) Error![]sat.Literal {        const bits = try self.allocator.alloc(sat.Literal, width);        errdefer self.allocator.free(bits);        for (bits) |*bit| {            bit.* = try self.freshLiteral();        }        return bits;    }    fn freshArray(self: *Encoder, index_width: u32, element_width: u32) Error!ArrayValue {        const cell_count = try arrayCellCount(index_width);        const cells = try self.allocator.alloc([]sat.Literal, cell_count);        var initialized: usize = 0;        errdefer {            for (cells[0..initialized]) |cell| self.allocator.free(cell);            self.allocator.free(cells);        }        for (cells) |*cell| {            cell.* = try self.freshBits(element_width);            initialized += 1;        }        return .{ .index_width = index_width, .element_width = element_width, .cells = cells };    }    fn freeArray(self: *Encoder, array: ArrayValue) void {        for (array.cells) |cell| self.allocator.free(cell);        self.allocator.free(array.cells);    }    fn modelArrayValue(self: *const Encoder, allocator: std.mem.Allocator, array: ArrayValue) Error!ModelValue {        const cells = try allocator.alloc(u128, array.cells.len);        errdefer allocator.free(cells);        for (cells, array.cells) |*target, bits| {            target.* = try modelValue(self.solver, bits);        }        return .{ .array = .{ .index_width = array.index_width, .element_width = array.element_width, .cells = cells } };    }    fn freshLiteral(self: *Encoder) Error!sat.Literal {        return sat.Literal.positive(try self.solver.addVariable());    }    fn encodeAdd(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error![]sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        const out = try self.allocator.alloc(sat.Literal, lhs.len);        errdefer self.allocator.free(out);        var carry = self.false_literal;        for (lhs, rhs, 0..) |a, b, index| {            const sum_ab = try self.xorGate(a, b);            out[index] = try self.xorGate(sum_ab, carry);            const carry_ab = try self.andGate(a, b);            const carry_ac = try self.andGate(a, carry);            const carry_bc = try self.andGate(b, carry);            carry = try self.orGate(try self.orGate(carry_ab, carry_ac), carry_bc);        }        return out;    }    fn encodeSub(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error![]sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        const out = try self.allocator.alloc(sat.Literal, lhs.len);        errdefer self.allocator.free(out);        var carry = self.true_literal;        for (lhs, rhs, 0..) |a, b, index| {            const not_b = b.negated();            const sum_ab = try self.xorGate(a, not_b);            out[index] = try self.xorGate(sum_ab, carry);            const carry_ab = try self.andGate(a, not_b);            const carry_ac = try self.andGate(a, carry);            const carry_bc = try self.andGate(not_b, carry);            carry = try self.orGate(try self.orGate(carry_ab, carry_ac), carry_bc);        }        return out;    }    fn encodeMul(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error![]sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        var result = try self.constantBits(0, @intCast(lhs.len));        errdefer self.allocator.free(result);        for (rhs, 0..) |rhs_bit, shift| {            const partial = try self.allocator.alloc(sat.Literal, lhs.len);            defer self.allocator.free(partial);            for (partial, 0..) |*bit, index| {                bit.* = if (index < shift) self.false_literal else try self.andGate(lhs[index - shift], rhs_bit);            }            const next = try self.encodeAdd(result, partial);            self.allocator.free(result);            result = next;        }        return result;    }    fn encodeUlt(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        var equal_prefix = self.true_literal;        var less = self.false_literal;        var index = lhs.len;        while (index > 0) {            index -= 1;            const bit_less = try self.andGate(lhs[index].negated(), rhs[index]);            less = try self.orGate(less, try self.andGate(equal_prefix, bit_less));            equal_prefix = try self.andGate(equal_prefix, try self.xnorGate(lhs[index], rhs[index]));        }        return less;    }    fn encodeUle(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        return try self.orGate(try self.encodeUlt(lhs, rhs), try self.bitsEqual(lhs, rhs));    }    fn encodeSlt(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        if (lhs.len == 0) return EncodeError.InvalidBitVectorRange;        const lhs_sign = lhs[lhs.len - 1];        const rhs_sign = rhs[rhs.len - 1];        const signs_differ = try self.xorGate(lhs_sign, rhs_sign);        const lhs_negative_rhs_positive = try self.andGate(lhs_sign, rhs_sign.negated());        const same_sign_less = try self.andGate(signs_differ.negated(), try self.encodeUlt(lhs, rhs));        return try self.orGate(lhs_negative_rhs_positive, same_sign_less);    }    fn encodeSle(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        return try self.orGate(try self.encodeSlt(lhs, rhs), try self.bitsEqual(lhs, rhs));    }    fn bitsEqual(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        var result = self.true_literal;        for (lhs, rhs) |a, b| {            result = try self.andGate(result, try self.xnorGate(a, b));        }        return result;    }    fn arraysEqual(self: *Encoder, lhs: ArrayValue, rhs: ArrayValue) Error!sat.Literal {        if (lhs.index_width != rhs.index_width or lhs.element_width != rhs.element_width or lhs.cells.len != rhs.cells.len) return EncodeError.SortMismatch;        var result = self.true_literal;        for (lhs.cells, rhs.cells) |lhs_cell, rhs_cell| {            result = try self.andGate(result, try self.bitsEqual(lhs_cell, rhs_cell));        }        return result;    }    fn encodeUaddOverflow(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        var carry = self.false_literal;        for (lhs, rhs) |a, b| {            const carry_ab = try self.andGate(a, b);            const carry_ac = try self.andGate(a, carry);            const carry_bc = try self.andGate(b, carry);            carry = try self.orGate(try self.orGate(carry_ab, carry_ac), carry_bc);        }        return carry;    }    fn encodeSaddOverflow(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        if (lhs.len == 0) return EncodeError.InvalidBitVectorRange;        const sum = try self.encodeAdd(lhs, rhs);        defer self.allocator.free(sum);        const lhs_sign = lhs[lhs.len - 1];        const rhs_sign = rhs[rhs.len - 1];        const sum_sign = sum[sum.len - 1];        const same_input_sign = (try self.xorGate(lhs_sign, rhs_sign)).negated();        const result_sign_changed = try self.xorGate(lhs_sign, sum_sign);        return try self.andGate(same_input_sign, result_sign_changed);    }    fn encodeSsubOverflow(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        if (lhs.len == 0) return EncodeError.InvalidBitVectorRange;        const negated_rhs = try self.encodeNeg(rhs);        defer self.allocator.free(negated_rhs);        const difference = try self.encodeAdd(lhs, negated_rhs);        defer self.allocator.free(difference);        const lhs_sign = lhs[lhs.len - 1];        const rhs_sign = rhs[rhs.len - 1];        const difference_sign = difference[difference.len - 1];        const input_signs_differ = try self.xorGate(lhs_sign, rhs_sign);        const result_sign_changed = try self.xorGate(lhs_sign, difference_sign);        return try self.andGate(input_signs_differ, result_sign_changed);    }    fn encodeUmulOverflow(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        const double_width = lhs.len * 2;        const lhs_extended = try self.allocator.alloc(sat.Literal, double_width);        defer self.allocator.free(lhs_extended);        const rhs_extended = try self.allocator.alloc(sat.Literal, double_width);        defer self.allocator.free(rhs_extended);        for (lhs, 0..) |bit, index| {            lhs_extended[index] = bit;        }        for (rhs, 0..) |bit, index| {            rhs_extended[index] = bit;        }        for (lhs.len..double_width) |index| {            lhs_extended[index] = self.false_literal;            rhs_extended[index] = self.false_literal;        }        const product = try self.encodeMul(lhs_extended, rhs_extended);        defer self.allocator.free(product);        return try self.orLiterals(product[lhs.len..]);    }    fn encodeSmulOverflow(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error!sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        if (lhs.len == 0) return EncodeError.InvalidBitVectorRange;        const double_width = lhs.len * 2;        const lhs_extended = try self.allocator.alloc(sat.Literal, double_width);        defer self.allocator.free(lhs_extended);        const rhs_extended = try self.allocator.alloc(sat.Literal, double_width);        defer self.allocator.free(rhs_extended);        @memcpy(lhs_extended[0..lhs.len], lhs);        @memcpy(rhs_extended[0..rhs.len], rhs);        for (lhs_extended[lhs.len..]) |*bit| {            bit.* = lhs[lhs.len - 1];        }        for (rhs_extended[rhs.len..]) |*bit| {            bit.* = rhs[rhs.len - 1];        }        const product = try self.encodeMul(lhs_extended, rhs_extended);        defer self.allocator.free(product);        const result_sign = product[lhs.len - 1];        var representable = self.true_literal;        for (product[lhs.len..]) |upper_bit| {            representable = try self.andGate(representable, try self.xnorGate(upper_bit, result_sign));        }        return representable.negated();    }    fn encodeNeg(self: *Encoder, bits: []const sat.Literal) Error![]sat.Literal {        if (bits.len == 0) return EncodeError.InvalidBitVectorRange;        const inverted = try self.encodeBitwiseNot(bits);        errdefer self.allocator.free(inverted);        const one = try self.constantBits(1, @intCast(bits.len));        defer self.allocator.free(one);        const result = try self.encodeAdd(inverted, one);        self.allocator.free(inverted);        return result;    }    fn encodeBitwiseNot(self: *Encoder, bits: []const sat.Literal) Error![]sat.Literal {        const out = try self.allocator.alloc(sat.Literal, bits.len);        for (out, bits) |*target, bit| {            target.* = bit.negated();        }        return out;    }    fn encodeBitwiseBinary(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal, kind: BitwiseKind) Error![]sat.Literal {        if (lhs.len != rhs.len) return EncodeError.SortMismatch;        const out = try self.allocator.alloc(sat.Literal, lhs.len);        errdefer self.allocator.free(out);        for (out, lhs, rhs) |*target, a, b| {            target.* = switch (kind) {                .and_ => try self.andGate(a, b),                .or_ => try self.orGate(a, b),                .xor => try self.xorGate(a, b),            };        }        return out;    }    fn encodeShift(self: *Encoder, value: []const sat.Literal, amount: []const sat.Literal, kind: ShiftKind) Error![]sat.Literal {        if (value.len != amount.len) return EncodeError.SortMismatch;        const selectors = try self.allocator.alloc(sat.Literal, value.len);        defer self.allocator.free(selectors);        for (selectors, 0..) |*selector, shift| {            selector.* = try self.bitsEqualConstant(amount, shift);        }        const out = try self.allocator.alloc(sat.Literal, value.len);        errdefer self.allocator.free(out);        for (out, 0..) |*target, output_index| {            var result = self.false_literal;            for (selectors, 0..) |selector, shift| {                const source = switch (kind) {                    .left => if (output_index >= shift) value[output_index - shift] else self.false_literal,                    .right => if (output_index + shift < value.len) value[output_index + shift] else self.false_literal,                };                if (source.raw == self.false_literal.raw) continue;                result = try self.orGate(result, try self.andGate(selector, source));            }            target.* = result;        }        return out;    }    fn encodeArithmeticShiftRight(self: *Encoder, value: []const sat.Literal, amount: []const sat.Literal) Error![]sat.Literal {        if (value.len != amount.len) return EncodeError.SortMismatch;        if (value.len == 0) return EncodeError.InvalidBitVectorRange;        const logical = try self.encodeShift(value, amount, .right);        errdefer self.allocator.free(logical);        const inverted = try self.encodeBitwiseNot(value);        defer self.allocator.free(inverted);        const shifted_inverted = try self.encodeShift(inverted, amount, .right);        defer self.allocator.free(shifted_inverted);        const negative = try self.encodeBitwiseNot(shifted_inverted);        defer self.allocator.free(negative);        const sign = value[value.len - 1];        const out = try self.allocator.alloc(sat.Literal, value.len);        errdefer self.allocator.free(out);        for (out, logical, negative) |*target, positive_bit, negative_bit| {            target.* = try self.muxGate(sign, negative_bit, positive_bit);        }        self.allocator.free(logical);        return out;    }    fn encodeRotate(self: *Encoder, bits: []const sat.Literal, amount: u32, kind: RotateKind) Error![]sat.Literal {        if (bits.len == 0) return EncodeError.InvalidBitVectorRange;        const shift = @as(usize, @intCast(amount)) % bits.len;        const out = try self.allocator.alloc(sat.Literal, bits.len);        for (out, 0..) |*target, index| {            const source_index = switch (kind) {                .left => if (index >= shift) index - shift else bits.len - (shift - index),                .right => if (index >= bits.len - shift) index - (bits.len - shift) else index + shift,            };            target.* = bits[source_index];        }        return out;    }    fn encodeUnsignedDivRem(self: *Encoder, dividend: []const sat.Literal, divisor: []const sat.Literal) Error!DivRemResult {        if (dividend.len != divisor.len) return EncodeError.SortMismatch;        if (dividend.len == 0) return EncodeError.InvalidBitVectorRange;        const width = dividend.len;        const extended_width = std.math.add(usize, width, 1) catch return EncodeError.InvalidBitVectorRange;        var remainder = try self.allocator.alloc(sat.Literal, extended_width);        errdefer self.allocator.free(remainder);        @memset(remainder, self.false_literal);        const divisor_extended = try self.allocator.alloc(sat.Literal, extended_width);        defer self.allocator.free(divisor_extended);        @memcpy(divisor_extended[0..width], divisor);        divisor_extended[width] = self.false_literal;        const quotient = try self.allocator.alloc(sat.Literal, width);        errdefer self.allocator.free(quotient);        var index = width;        while (index > 0) {            index -= 1;            const shifted = try self.allocator.alloc(sat.Literal, extended_width);            errdefer self.allocator.free(shifted);            shifted[0] = dividend[index];            @memcpy(shifted[1..], remainder[0..width]);            const can_subtract = try self.encodeUle(divisor_extended, shifted);            const difference = try self.encodeSub(shifted, divisor_extended);            errdefer self.allocator.free(difference);            const next_remainder = try self.allocator.alloc(sat.Literal, extended_width);            errdefer self.allocator.free(next_remainder);            for (next_remainder, shifted, difference) |*target, shifted_bit, difference_bit| {                target.* = try self.muxGate(can_subtract, difference_bit, shifted_bit);            }            quotient[index] = can_subtract;            self.allocator.free(remainder);            self.allocator.free(shifted);            self.allocator.free(difference);            remainder = next_remainder;        }        const result_remainder = try self.allocator.alloc(sat.Literal, width);        errdefer self.allocator.free(result_remainder);        @memcpy(result_remainder, remainder[0..width]);        self.allocator.free(remainder);        return .{ .quotient = quotient, .remainder = result_remainder };    }    fn encodeSignedDiv(self: *Encoder, dividend: []const sat.Literal, divisor: []const sat.Literal) Error![]sat.Literal {        if (dividend.len != divisor.len) return EncodeError.SortMismatch;        if (dividend.len == 0) return EncodeError.InvalidBitVectorRange;        const abs_dividend = try self.encodeAbs(dividend);        defer self.allocator.free(abs_dividend);        const abs_divisor = try self.encodeAbs(divisor);        defer self.allocator.free(abs_divisor);        const result = try self.encodeUnsignedDivRem(abs_dividend, abs_divisor);        defer self.allocator.free(result.quotient);        defer self.allocator.free(result.remainder);        const negated_quotient = try self.encodeNeg(result.quotient);        defer self.allocator.free(negated_quotient);        const negative = try self.xorGate(dividend[dividend.len - 1], divisor[divisor.len - 1]);        return try self.muxBits(negative, negated_quotient, result.quotient);    }    fn encodeSignedRem(self: *Encoder, dividend: []const sat.Literal, divisor: []const sat.Literal) Error![]sat.Literal {        if (dividend.len != divisor.len) return EncodeError.SortMismatch;        if (dividend.len == 0) return EncodeError.InvalidBitVectorRange;        const abs_dividend = try self.encodeAbs(dividend);        defer self.allocator.free(abs_dividend);        const abs_divisor = try self.encodeAbs(divisor);        defer self.allocator.free(abs_divisor);        const result = try self.encodeUnsignedDivRem(abs_dividend, abs_divisor);        defer self.allocator.free(result.quotient);        defer self.allocator.free(result.remainder);        const negated_remainder = try self.encodeNeg(result.remainder);        defer self.allocator.free(negated_remainder);        return try self.muxBits(dividend[dividend.len - 1], negated_remainder, result.remainder);    }    fn encodeSignedMod(self: *Encoder, dividend: []const sat.Literal, divisor: []const sat.Literal) Error![]sat.Literal {        if (dividend.len != divisor.len) return EncodeError.SortMismatch;        if (dividend.len == 0) return EncodeError.InvalidBitVectorRange;        const abs_dividend = try self.encodeAbs(dividend);        defer self.allocator.free(abs_dividend);        const abs_divisor = try self.encodeAbs(divisor);        defer self.allocator.free(abs_divisor);        const result = try self.encodeUnsignedDivRem(abs_dividend, abs_divisor);        defer self.allocator.free(result.quotient);        defer self.allocator.free(result.remainder);        const zero = try self.constantBits(0, @intCast(dividend.len));        defer self.allocator.free(zero);        const remainder_is_zero = try self.bitsEqual(result.remainder, zero);        const negated_remainder = try self.encodeNeg(result.remainder);        defer self.allocator.free(negated_remainder);        const negative_plus_divisor = try self.encodeAdd(negated_remainder, divisor);        defer self.allocator.free(negative_plus_divisor);        const positive_plus_divisor = try self.encodeAdd(result.remainder, divisor);        defer self.allocator.free(positive_plus_divisor);        const lhs_positive_branch = try self.muxBits(divisor[divisor.len - 1], positive_plus_divisor, result.remainder);        defer self.allocator.free(lhs_positive_branch);        const lhs_negative_branch = try self.muxBits(divisor[divisor.len - 1], negated_remainder, negative_plus_divisor);        defer self.allocator.free(lhs_negative_branch);        const adjusted = try self.muxBits(dividend[dividend.len - 1], lhs_negative_branch, lhs_positive_branch);        defer self.allocator.free(adjusted);        return try self.muxBits(remainder_is_zero, result.remainder, adjusted);    }    fn encodeAbs(self: *Encoder, bits: []const sat.Literal) Error![]sat.Literal {        if (bits.len == 0) return EncodeError.InvalidBitVectorRange;        const negated = try self.encodeNeg(bits);        defer self.allocator.free(negated);        return try self.muxBits(bits[bits.len - 1], negated, bits);    }    fn bitsEqualConstant(self: *Encoder, bits: []const sat.Literal, value: usize) Error!sat.Literal {        var result = self.true_literal;        for (bits, 0..) |bit, index| {            const bit_is_set = index < @bitSizeOf(usize) and ((value >> @intCast(index)) & 1) == 1;            const selected = if (bit_is_set) bit else bit.negated();            result = try self.andGate(result, selected);        }        return result;    }    fn encodeArraySelect(self: *Encoder, array: ArrayValue, index: []const sat.Literal) Error![]sat.Literal {        const index_width: usize = @intCast(array.index_width);        const element_width: usize = @intCast(array.element_width);        if (index.len != index_width) return EncodeError.SortMismatch;        const cell_count = try arrayCellCount(array.index_width);        if (array.cells.len != cell_count) return EncodeError.SortMismatch;        const out = try self.allocator.alloc(sat.Literal, element_width);        errdefer self.allocator.free(out);        @memset(out, self.false_literal);        for (array.cells, 0..) |cell, cell_index| {            if (cell.len != element_width) return EncodeError.SortMismatch;            const selector = try self.bitsEqualConstant(index, cell_index);            for (out, cell) |*target, bit| {                if (bit.raw == self.false_literal.raw) continue;                target.* = try self.orGate(target.*, try self.andGate(selector, bit));            }        }        return out;    }    fn encodeArrayStore(self: *Encoder, array: ArrayValue, index: []const sat.Literal, value: []const sat.Literal) Error!ArrayValue {        const index_width: usize = @intCast(array.index_width);        const element_width: usize = @intCast(array.element_width);        if (index.len != index_width or value.len != element_width) return EncodeError.SortMismatch;        const cell_count = try arrayCellCount(array.index_width);        if (array.cells.len != cell_count) return EncodeError.SortMismatch;        const cells = try self.allocator.alloc([]sat.Literal, cell_count);        var initialized: usize = 0;        errdefer {            for (cells[0..initialized]) |cell| self.allocator.free(cell);            self.allocator.free(cells);        }        for (cells, array.cells, 0..) |*target, cell, cell_index| {            if (cell.len != element_width) return EncodeError.SortMismatch;            const selector = try self.bitsEqualConstant(index, cell_index);            target.* = try self.muxBits(selector, value, cell);            initialized += 1;        }        return .{ .index_width = array.index_width, .element_width = array.element_width, .cells = cells };    }    fn encodeConcat(self: *Encoder, lhs: []const sat.Literal, rhs: []const sat.Literal) Error![]sat.Literal {        const out = try self.allocator.alloc(sat.Literal, lhs.len + rhs.len);        @memcpy(out[0..rhs.len], rhs);        @memcpy(out[rhs.len..], lhs);        return out;    }    fn encodeExtract(self: *Encoder, bits: []const sat.Literal, high: u32, low: u32) Error![]sat.Literal {        if (low > high or high >= bits.len) return EncodeError.InvalidBitVectorRange;        const width = high - low + 1;        const out = try self.allocator.alloc(sat.Literal, width);        const base: usize = @intCast(low);        for (out, 0..) |*target, index| {            target.* = bits[base + index];        }        return out;    }    fn encodeExtend(self: *Encoder, bits: []const sat.Literal, extra: u32, kind: ExtendKind) Error![]sat.Literal {        if (bits.len == 0) return EncodeError.InvalidBitVectorRange;        const extra_len: usize = @intCast(extra);        const width = std.math.add(usize, bits.len, extra_len) catch return EncodeError.InvalidBitVectorRange;        const out = try self.allocator.alloc(sat.Literal, width);        @memcpy(out[0..bits.len], bits);        const extension = switch (kind) {            .zero => self.false_literal,            .sign => bits[bits.len - 1],        };        for (out[bits.len..]) |*target| {            target.* = extension;        }        return out;    }    fn orLiterals(self: *Encoder, literals: []const sat.Literal) Error!sat.Literal {        if (literals.len == 0) return self.false_literal;        var result = literals[0];        for (literals[1..]) |literal| {            result = try self.orGate(result, literal);        }        return result;    }    fn andGate(self: *Encoder, a: sat.Literal, b: sat.Literal) Error!sat.Literal {        const out = try self.freshLiteral();        try self.solver.addClause(&.{ a.negated(), b.negated(), out });        try self.solver.addClause(&.{ a, out.negated() });        try self.solver.addClause(&.{ b, out.negated() });        return out;    }    fn orGate(self: *Encoder, a: sat.Literal, b: sat.Literal) Error!sat.Literal {        const out = try self.freshLiteral();        try self.solver.addClause(&.{ a, b, out.negated() });        try self.solver.addClause(&.{ a.negated(), out });        try self.solver.addClause(&.{ b.negated(), out });        return out;    }    fn xorGate(self: *Encoder, a: sat.Literal, b: sat.Literal) Error!sat.Literal {        const out = try self.freshLiteral();        try self.solver.addClause(&.{ a, b, out.negated() });        try self.solver.addClause(&.{ a.negated(), b.negated(), out.negated() });        try self.solver.addClause(&.{ a, b.negated(), out });        try self.solver.addClause(&.{ a.negated(), b, out });        return out;    }    fn xnorGate(self: *Encoder, a: sat.Literal, b: sat.Literal) Error!sat.Literal {        return (try self.xorGate(a, b)).negated();    }    fn muxGate(self: *Encoder, selector: sat.Literal, when_true: sat.Literal, when_false: sat.Literal) Error!sat.Literal {        return try self.orGate(try self.andGate(selector, when_true), try self.andGate(selector.negated(), when_false));    }    fn muxBits(self: *Encoder, selector: sat.Literal, when_true: []const sat.Literal, when_false: []const sat.Literal) Error![]sat.Literal {        if (when_true.len != when_false.len) return EncodeError.SortMismatch;        const out = try self.allocator.alloc(sat.Literal, when_true.len);        errdefer self.allocator.free(out);        for (out, when_true, when_false) |*target, true_bit, false_bit| {            target.* = try self.muxGate(selector, true_bit, false_bit);        }        return out;    }};fn modelValue(solver: *const sat.Solver, bits: []const sat.Literal) Error!u128 {    if (bits.len > 128) return EncodeError.ModelValueTooWide;    var value: u128 = 0;    for (bits, 0..) |bit, index| {        const bit_value = solver.literalValue(bit);        if (bit_value == .true) value |= @as(u128, 1) << @intCast(index);    }    return value;}fn arrayCellCount(index_width: u32) Error!usize {    if (index_width > max_native_array_index_width) return EncodeError.ArrayIndexTooWide;    return @as(usize, 1) << @intCast(index_width);}test "bit-vector encoder proves unsigned self-less-than impossible" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const assertion = try ctx.bvult(x, x);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder proves signed self-less-than impossible" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const assertion = try ctx.bvslt(x, x);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder solves with Boolean term assumptions" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const p = try ctx.symbol("p", .bool);    const q = try ctx.symbol("q", .bool);    const r = try ctx.symbol("r", .bool);    const operands = [_]term.Term{ p, q };    const disjunction = try ctx.or_(&operands);    const not_p = try ctx.not(p);    const not_q = try ctx.not(q);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(disjunction);    const not_p_literal = try encoder.assumeTerm(not_p);    _ = try encoder.assumeTerm(r);    const not_q_literal = try encoder.assumeTerm(not_q);    try std.testing.expectEqual(sat.Status.unsat, try encoder.solve());    try std.testing.expectEqualSlices(sat.Literal, &.{ not_p_literal, not_q_literal }, solver.lastUnsatCore());    var artifact = (try solver.lastProofArtifact(std.testing.allocator)).?;    defer artifact.deinit();    try std.testing.expectEqualSlices(sat.Literal, &.{ not_p_literal, not_q_literal }, artifact.assumptions.items);    try std.testing.expect(try artifact.valid());}test "bit-vector encoder solves signed comparison model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const negative = try ctx.symbol("negative", .{ .bitvec = 4 });    const positive = try ctx.symbol("positive", .{ .bitvec = 4 });    const minus_one = try ctx.bitvecValue(0xf, 4);    const one = try ctx.bitvecValue(0x1, 4);    const min_value = try ctx.bitvecValue(0x8, 4);    const negative_is_minus_one = try ctx.eq(negative, minus_one);    const positive_is_one = try ctx.eq(positive, one);    const negative_less_positive = try ctx.bvslt(negative, positive);    const negative_less_equal_negative = try ctx.bvsle(negative, negative);    const min_less_negative = try ctx.bvslt(min_value, negative);    const positive_not_less_negative = try ctx.not(try ctx.bvslt(positive, negative));    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(negative_is_minus_one);    try encoder.assertTerm(positive_is_one);    try encoder.assertTerm(negative_less_positive);    try encoder.assertTerm(negative_less_equal_negative);    try encoder.assertTerm(min_less_negative);    try encoder.assertTerm(positive_not_less_negative);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0xf, .width = 4 } }, model_result.get("negative").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0x1, .width = 4 } }, model_result.get("positive").?);}test "bit-vector encoder finds wrapped increment model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const one = try ctx.bitvecValue(1, 4);    const zero = try ctx.bitvecValue(0, 4);    const assertion = try ctx.eq(try ctx.bvadd(x, one), zero);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    const bits = try encoder.encodeBits(x);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    try std.testing.expectEqual(@as(u128, 15), try modelValue(&solver, bits));}test "bit-vector encoder solves subtraction model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const three = try ctx.bitvecValue(3, 4);    const twelve = try ctx.bitvecValue(12, 4);    const assertion = try ctx.eq(try ctx.bvsub(x, three), twelve);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    const bits = try encoder.encodeBits(x);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    try std.testing.expectEqual(@as(u128, 15), try modelValue(&solver, bits));}test "bit-vector encoder extracts named model values" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const flag = try ctx.symbol("flag", .bool);    const one = try ctx.bitvecValue(1, 4);    const zero = try ctx.bitvecValue(0, 4);    const assertion = try ctx.eq(try ctx.bvadd(x, one), zero);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try encoder.assertTerm(flag);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(@as(usize, 2), model_result.entries.items.len);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 15, .width = 4 } }, model_result.get("x").?);    try std.testing.expectEqual(ModelValue{ .bool = true }, model_result.get("flag").?);    var buffer: [128]u8 = undefined;    var stream = std.Io.Writer.fixed(&buffer);    try model_result.write(&stream);    try std.testing.expect(std.mem.indexOf(u8, stream.buffered(), "x: (_ bv15 4)") != null);    try std.testing.expect(std.mem.indexOf(u8, stream.buffered(), "flag: true") != null);}test "bit-vector encoder solves finite array store select model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const memory = try ctx.symbol("memory", .{ .array = .{ .index_width = 2, .element_width = 4 } });    const index = try ctx.symbol("index", .{ .bitvec = 2 });    const other_index = try ctx.symbol("other_index", .{ .bitvec = 2 });    const value = try ctx.symbol("value", .{ .bitvec = 4 });    const other_value = try ctx.symbol("other_value", .{ .bitvec = 4 });    const index_one = try ctx.bitvecValue(1, 2);    const index_two = try ctx.bitvecValue(2, 2);    const ten = try ctx.bitvecValue(0xa, 4);    const five = try ctx.bitvecValue(0x5, 4);    const written = try ctx.arrayStore(memory, index, value);    const other_written = try ctx.arrayStore(written, other_index, other_value);    const index_is_one = try ctx.eq(index, index_one);    const other_index_is_two = try ctx.eq(other_index, index_two);    const value_is_ten = try ctx.eq(value, ten);    const other_value_is_five = try ctx.eq(other_value, five);    const stored_read = try ctx.eq(try ctx.arraySelect(written, index), value);    const preserved_read = try ctx.eq(try ctx.arraySelect(written, other_index), try ctx.arraySelect(memory, other_index));    const nested_stored_read = try ctx.eq(try ctx.arraySelect(other_written, index), value);    const nested_other_read = try ctx.eq(try ctx.arraySelect(other_written, other_index), other_value);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(index_is_one);    try encoder.assertTerm(other_index_is_two);    try encoder.assertTerm(value_is_ten);    try encoder.assertTerm(other_value_is_five);    try encoder.assertTerm(stored_read);    try encoder.assertTerm(preserved_read);    try encoder.assertTerm(nested_stored_read);    try encoder.assertTerm(nested_other_read);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 1, .width = 2 } }, model_result.get("index").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 2, .width = 2 } }, model_result.get("other_index").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0xa, .width = 4 } }, model_result.get("value").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0x5, .width = 4 } }, model_result.get("other_value").?);    switch (model_result.get("memory").?) {        .array => |array| {            try std.testing.expectEqual(@as(u32, 2), array.index_width);            try std.testing.expectEqual(@as(u32, 4), array.element_width);            try std.testing.expectEqual(@as(usize, 4), array.cells.len);        },        else => return error.TestExpectedArrayModel,    }    var buffer: [512]u8 = undefined;    var stream = std.Io.Writer.fixed(&buffer);    try model_result.write(&stream);    try std.testing.expect(std.mem.indexOf(u8, stream.buffered(), "memory: (array (_ BitVec 2) (_ BitVec 4)") != null);}test "bit-vector encoder rejects overwritten array read contradiction" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const memory = try ctx.symbol("memory", .{ .array = .{ .index_width = 2, .element_width = 4 } });    const index = try ctx.bitvecValue(1, 2);    const ten = try ctx.bitvecValue(0xa, 4);    const three = try ctx.bitvecValue(0x3, 4);    const written = try ctx.arrayStore(memory, index, ten);    const overwritten = try ctx.arrayStore(written, index, three);    const contradiction = try ctx.eq(try ctx.arraySelect(overwritten, index), ten);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(contradiction);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder compares finite arrays extensionally" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const lhs = try ctx.symbol("lhs", .{ .array = .{ .index_width = 2, .element_width = 4 } });    const rhs = try ctx.symbol("rhs", .{ .array = .{ .index_width = 2, .element_width = 4 } });    const index = try ctx.bitvecValue(2, 2);    const arrays_equal = try ctx.eq(lhs, rhs);    const reads_differ = try ctx.not(try ctx.eq(try ctx.arraySelect(lhs, index), try ctx.arraySelect(rhs, index)));    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(arrays_equal);    try encoder.assertTerm(reads_differ);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder enforces uninterpreted function congruence" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = term.Sort{ .bitvec = 4 };    const x = try ctx.symbol("x", bv4);    const y = try ctx.symbol("y", bv4);    const f = try ctx.function("f", &.{bv4}, bv4);    const fx = try ctx.apply(f, &.{x});    const fy = try ctx.apply(f, &.{y});    const arguments_equal = try ctx.eq(x, y);    const results_differ = try ctx.not(try ctx.eq(fx, fy));    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(arguments_equal);    try encoder.assertTerm(results_differ);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder permits distinct uninterpreted function results" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = term.Sort{ .bitvec = 4 };    const x = try ctx.symbol("x", bv4);    const y = try ctx.symbol("y", bv4);    const f = try ctx.function("f", &.{bv4}, bv4);    const fx = try ctx.apply(f, &.{x});    const fy = try ctx.apply(f, &.{y});    const one = try ctx.bitvecValue(1, 4);    const two = try ctx.bitvecValue(2, 4);    const three = try ctx.bitvecValue(3, 4);    const four = try ctx.bitvecValue(4, 4);    const x_is_one = try ctx.eq(x, one);    const y_is_two = try ctx.eq(y, two);    const fx_is_three = try ctx.eq(fx, three);    const fy_is_four = try ctx.eq(fy, four);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(x_is_one);    try encoder.assertTerm(y_is_two);    try encoder.assertTerm(fx_is_three);    try encoder.assertTerm(fy_is_four);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 1, .width = 4 } }, model_result.get("x").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 2, .width = 4 } }, model_result.get("y").?);    const f_model = model_result.getFunction("f").?;    try std.testing.expectEqual(@as(usize, 2), f_model.entries.items.len);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 1, .width = 4 } }, f_model.entries.items[0].arguments[0]);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 3, .width = 4 } }, f_model.entries.items[0].result);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 2, .width = 4 } }, f_model.entries.items[1].arguments[0]);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 4, .width = 4 } }, f_model.entries.items[1].result);}test "bit-vector model skips unused uninterpreted function applications" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = term.Sort{ .bitvec = 4 };    const x = try ctx.symbol("x", bv4);    const f = try ctx.function("f", &.{bv4}, bv4);    _ = try ctx.apply(f, &.{x});    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expect(model_result.getFunction("f") == null);}test "bit-vector encoder enforces bool-returning function congruence" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const bv4 = term.Sort{ .bitvec = 4 };    const x = try ctx.symbol("x", bv4);    const y = try ctx.symbol("y", bv4);    const p = try ctx.function("p", &.{bv4}, .bool);    const px = try ctx.apply(p, &.{x});    const py = try ctx.apply(p, &.{y});    const arguments_equal = try ctx.eq(x, y);    const py_false = try ctx.not(py);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(arguments_equal);    try encoder.assertTerm(px);    try encoder.assertTerm(py_false);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder rejects terms appended after initialization" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    const zero = try ctx.bitvecValue(0, 4);    try std.testing.expectError(EncodeError.TermOutOfRange, encoder.assertTerm(try ctx.eq(x, zero)));}test "bit-vector encoder multiplies exactly modulo width" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const three = try ctx.bitvecValue(3, 4);    const fifteen = try ctx.bitvecValue(15, 4);    const assertion = try ctx.eq(try ctx.bvmul(x, three), fifteen);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder solves unsigned division and remainder model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const dividend = try ctx.symbol("dividend", .{ .bitvec = 4 });    const divisor = try ctx.symbol("divisor", .{ .bitvec = 4 });    const thirteen = try ctx.bitvecValue(13, 4);    const three = try ctx.bitvecValue(3, 4);    const four = try ctx.bitvecValue(4, 4);    const one = try ctx.bitvecValue(1, 4);    const dividend_is_thirteen = try ctx.eq(dividend, thirteen);    const divisor_is_three = try ctx.eq(divisor, three);    const quotient_is_four = try ctx.eq(try ctx.bvudiv(dividend, divisor), four);    const remainder_is_one = try ctx.eq(try ctx.bvurem(dividend, divisor), one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(dividend_is_thirteen);    try encoder.assertTerm(divisor_is_three);    try encoder.assertTerm(quotient_is_four);    try encoder.assertTerm(remainder_is_one);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 13, .width = 4 } }, model_result.get("dividend").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 3, .width = 4 } }, model_result.get("divisor").?);}test "bit-vector encoder follows unsigned division by zero semantics" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const dividend = try ctx.bitvecValue(10, 4);    const zero = try ctx.bitvecValue(0, 4);    const all_ones = try ctx.bitvecValue(15, 4);    const quotient_is_all_ones = try ctx.eq(try ctx.bvudiv(dividend, zero), all_ones);    const remainder_is_dividend = try ctx.eq(try ctx.bvurem(dividend, zero), dividend);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(quotient_is_all_ones);    try encoder.assertTerm(remainder_is_dividend);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder rejects wrapped unsigned division quotient" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const one = try ctx.bitvecValue(1, 4);    const three = try ctx.bitvecValue(3, 4);    const eleven = try ctx.bitvecValue(11, 4);    const impossible = try ctx.eq(try ctx.bvudiv(one, three), eleven);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(impossible);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder solves signed division remainder and modulo" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const negative = try ctx.symbol("negative", .{ .bitvec = 4 });    const positive = try ctx.symbol("positive", .{ .bitvec = 4 });    const positive_divisor = try ctx.symbol("positive_divisor", .{ .bitvec = 4 });    const negative_divisor = try ctx.symbol("negative_divisor", .{ .bitvec = 4 });    const minus_seven = try ctx.bitvecValue(0b1001, 4);    const plus_seven = try ctx.bitvecValue(0b0111, 4);    const plus_three = try ctx.bitvecValue(0b0011, 4);    const minus_three = try ctx.bitvecValue(0b1101, 4);    const minus_two = try ctx.bitvecValue(0b1110, 4);    const minus_one = try ctx.bitvecValue(0b1111, 4);    const plus_one = try ctx.bitvecValue(0b0001, 4);    const plus_two = try ctx.bitvecValue(0b0010, 4);    const negative_is_minus_seven = try ctx.eq(negative, minus_seven);    const positive_is_plus_seven = try ctx.eq(positive, plus_seven);    const positive_divisor_is_plus_three = try ctx.eq(positive_divisor, plus_three);    const negative_divisor_is_minus_three = try ctx.eq(negative_divisor, minus_three);    const negative_quotient = try ctx.eq(try ctx.bvsdiv(negative, positive_divisor), minus_two);    const negative_remainder = try ctx.eq(try ctx.bvsrem(negative, positive_divisor), minus_one);    const negative_modulo = try ctx.eq(try ctx.bvsmod(negative, positive_divisor), plus_two);    const positive_quotient = try ctx.eq(try ctx.bvsdiv(positive, negative_divisor), minus_two);    const positive_remainder = try ctx.eq(try ctx.bvsrem(positive, negative_divisor), plus_one);    const positive_modulo = try ctx.eq(try ctx.bvsmod(positive, negative_divisor), minus_two);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(negative_is_minus_seven);    try encoder.assertTerm(positive_is_plus_seven);    try encoder.assertTerm(positive_divisor_is_plus_three);    try encoder.assertTerm(negative_divisor_is_minus_three);    try encoder.assertTerm(negative_quotient);    try encoder.assertTerm(negative_remainder);    try encoder.assertTerm(negative_modulo);    try encoder.assertTerm(positive_quotient);    try encoder.assertTerm(positive_remainder);    try encoder.assertTerm(positive_modulo);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0b1001, .width = 4 } }, model_result.get("negative").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0b0111, .width = 4 } }, model_result.get("positive").?);}test "bit-vector encoder follows signed division by zero semantics" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const negative = try ctx.bitvecValue(0b1001, 4);    const positive = try ctx.bitvecValue(0b0111, 4);    const zero = try ctx.bitvecValue(0, 4);    const one = try ctx.bitvecValue(1, 4);    const all_ones = try ctx.bitvecValue(0b1111, 4);    const negative_division = try ctx.eq(try ctx.bvsdiv(negative, zero), one);    const positive_division = try ctx.eq(try ctx.bvsdiv(positive, zero), all_ones);    const negative_remainder = try ctx.eq(try ctx.bvsrem(negative, zero), negative);    const positive_remainder = try ctx.eq(try ctx.bvsrem(positive, zero), positive);    const negative_modulo = try ctx.eq(try ctx.bvsmod(negative, zero), negative);    const positive_modulo = try ctx.eq(try ctx.bvsmod(positive, zero), positive);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(negative_division);    try encoder.assertTerm(positive_division);    try encoder.assertTerm(negative_remainder);    try encoder.assertTerm(positive_remainder);    try encoder.assertTerm(negative_modulo);    try encoder.assertTerm(positive_modulo);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder detects unsigned addition overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const one = try ctx.bitvecValue(1, 4);    const fifteen = try ctx.bitvecValue(15, 4);    const is_fifteen = try ctx.eq(x, fifteen);    const overflow = try ctx.bvuaddo(x, one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_fifteen);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder proves bounded unsigned addition does not overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const one = try ctx.bitvecValue(1, 4);    const fifteen = try ctx.bitvecValue(15, 4);    const assertion = try ctx.and_(&.{ try ctx.bvult(x, fifteen), try ctx.bvuaddo(x, one) });    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder detects signed addition overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const seven = try ctx.bitvecValue(7, 4);    const one = try ctx.bitvecValue(1, 4);    const is_seven = try ctx.eq(x, seven);    const overflow = try ctx.bvsaddo(x, one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_seven);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder proves signed addition inside range does not overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const minus_one = try ctx.bitvecValue(15, 4);    const one = try ctx.bitvecValue(1, 4);    const is_minus_one = try ctx.eq(x, minus_one);    const overflow = try ctx.bvsaddo(x, one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_minus_one);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder detects signed subtraction overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const min = try ctx.bitvecValue(8, 4);    const one = try ctx.bitvecValue(1, 4);    const is_min = try ctx.eq(x, min);    const overflow = try ctx.bvssubo(x, one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_min);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder proves signed subtraction inside range does not overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const minus_one = try ctx.bitvecValue(15, 4);    const one = try ctx.bitvecValue(1, 4);    const is_minus_one = try ctx.eq(x, minus_one);    const overflow = try ctx.bvssubo(x, one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_minus_one);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder detects unsigned multiplication overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const six = try ctx.bitvecValue(6, 4);    const three = try ctx.bitvecValue(3, 4);    const is_six = try ctx.eq(x, six);    const overflow = try ctx.bvumulo(x, three);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_six);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder proves bounded unsigned multiplication does not overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const five = try ctx.bitvecValue(5, 4);    const three = try ctx.bitvecValue(3, 4);    const assertion = try ctx.and_(&.{ try ctx.eq(x, five), try ctx.bvumulo(x, three) });    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder detects signed multiplication overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const minus_four = try ctx.bitvecValue(12, 4);    const three = try ctx.bitvecValue(3, 4);    const is_minus_four = try ctx.eq(x, minus_four);    const overflow = try ctx.bvsmulo(x, three);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_minus_four);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder proves signed multiplication inside range does not overflow" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const minus_two = try ctx.bitvecValue(14, 4);    const three = try ctx.bitvecValue(3, 4);    const is_minus_two = try ctx.eq(x, minus_two);    const overflow = try ctx.bvsmulo(x, three);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(is_minus_two);    try encoder.assertTerm(overflow);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder solves bitwise mask model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const ten = try ctx.bitvecValue(0b1010, 4);    const three = try ctx.bitvecValue(0b0011, 4);    const one = try ctx.bitvecValue(0b0001, 4);    const zero = try ctx.bitvecValue(0b0000, 4);    const eight = try ctx.bitvecValue(0b1000, 4);    const eleven = try ctx.bitvecValue(0b1011, 4);    const mask_assertion = try ctx.eq(try ctx.bvand(x, ten), eight);    const low_bit_assertion = try ctx.eq(try ctx.bvand(x, one), zero);    const or_assertion = try ctx.eq(try ctx.bvor(x, three), eleven);    const all_ones = try ctx.bitvecValue(0b1111, 4);    const not_assertion = try ctx.eq(try ctx.bvxor(x, all_ones), try ctx.bvnot(x));    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(mask_assertion);    try encoder.assertTerm(low_bit_assertion);    try encoder.assertTerm(or_assertion);    try encoder.assertTerm(not_assertion);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 8, .width = 4 } }, model_result.get("x").?);}test "bit-vector encoder proves xor self contradiction unsat" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const one = try ctx.bitvecValue(1, 4);    const assertion = try ctx.eq(try ctx.bvxor(x, x), one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(assertion);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder solves symbolic logical shift model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const amount = try ctx.symbol("amount", .{ .bitvec = 4 });    const two = try ctx.bitvecValue(2, 4);    const three = try ctx.bitvecValue(3, 4);    const twelve = try ctx.bitvecValue(12, 4);    const shifted = try ctx.bvshl(x, amount);    const roundtrip = try ctx.bvlshr(shifted, amount);    const amount_is_two = try ctx.eq(amount, two);    const shifted_is_twelve = try ctx.eq(shifted, twelve);    const roundtrip_is_x = try ctx.eq(roundtrip, x);    const x_is_three = try ctx.eq(x, three);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(amount_is_two);    try encoder.assertTerm(shifted_is_twelve);    try encoder.assertTerm(roundtrip_is_x);    try encoder.assertTerm(x_is_three);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 3, .width = 4 } }, model_result.get("x").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 2, .width = 4 } }, model_result.get("amount").?);}test "bit-vector encoder treats large logical shift amounts as zero" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const one = try ctx.bitvecValue(1, 4);    const four = try ctx.bitvecValue(4, 4);    const zero = try ctx.bitvecValue(0, 4);    const shl_is_zero = try ctx.eq(try ctx.bvshl(one, four), zero);    const lshr_is_zero = try ctx.eq(try ctx.bvlshr(one, four), zero);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(shl_is_zero);    try encoder.assertTerm(lshr_is_zero);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder solves arithmetic shift model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const negative = try ctx.symbol("negative", .{ .bitvec = 4 });    const positive = try ctx.symbol("positive", .{ .bitvec = 4 });    const amount = try ctx.symbol("amount", .{ .bitvec = 4 });    const minus_four = try ctx.bitvecValue(0b1100, 4);    const plus_six = try ctx.bitvecValue(0b0110, 4);    const one = try ctx.bitvecValue(1, 4);    const minus_two = try ctx.bitvecValue(0b1110, 4);    const plus_three = try ctx.bitvecValue(0b0011, 4);    const logical_negative = try ctx.bitvecValue(0b0110, 4);    const negative_is_minus_four = try ctx.eq(negative, minus_four);    const positive_is_plus_six = try ctx.eq(positive, plus_six);    const amount_is_one = try ctx.eq(amount, one);    const arithmetic_negative = try ctx.eq(try ctx.bvashr(negative, amount), minus_two);    const logical_negative_match = try ctx.eq(try ctx.bvlshr(negative, amount), logical_negative);    const arithmetic_positive = try ctx.eq(try ctx.bvashr(positive, amount), plus_three);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(negative_is_minus_four);    try encoder.assertTerm(positive_is_plus_six);    try encoder.assertTerm(amount_is_one);    try encoder.assertTerm(arithmetic_negative);    try encoder.assertTerm(logical_negative_match);    try encoder.assertTerm(arithmetic_positive);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0b1100, .width = 4 } }, model_result.get("negative").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0b0110, .width = 4 } }, model_result.get("positive").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 1, .width = 4 } }, model_result.get("amount").?);}test "bit-vector encoder treats large arithmetic shift amounts as sign fill" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const negative = try ctx.bitvecValue(0b1001, 4);    const positive = try ctx.bitvecValue(0b0111, 4);    const four = try ctx.bitvecValue(4, 4);    const all_ones = try ctx.bitvecValue(0b1111, 4);    const zero = try ctx.bitvecValue(0, 4);    const negative_is_all_ones = try ctx.eq(try ctx.bvashr(negative, four), all_ones);    const positive_is_zero = try ctx.eq(try ctx.bvashr(positive, four), zero);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(negative_is_all_ones);    try encoder.assertTerm(positive_is_zero);    try std.testing.expectEqual(sat.Status.sat, try solver.solve());}test "bit-vector encoder solves fixed rotate model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const nine = try ctx.bitvecValue(0b1001, 4);    const left_one = try ctx.bitvecValue(0b0011, 4);    const right_one = try ctx.bitvecValue(0b1100, 4);    const right_two = try ctx.bitvecValue(0b0110, 4);    const x_is_nine = try ctx.eq(x, nine);    const rotate_left_one = try ctx.eq(try ctx.bvrotl(x, 1), left_one);    const rotate_left_five = try ctx.eq(try ctx.bvrotl(x, 5), left_one);    const rotate_right_one = try ctx.eq(try ctx.bvrotr(x, 1), right_one);    const rotate_right_two = try ctx.eq(try ctx.bvrotr(x, 2), right_two);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(x_is_nine);    try encoder.assertTerm(rotate_left_one);    try encoder.assertTerm(rotate_left_five);    try encoder.assertTerm(rotate_right_one);    try encoder.assertTerm(rotate_right_two);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0b1001, .width = 4 } }, model_result.get("x").?);}test "bit-vector encoder rejects impossible fixed rotate result" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const nine = try ctx.bitvecValue(0b1001, 4);    const impossible = try ctx.eq(try ctx.bvrotl(nine, 1), nine);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(impossible);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder rejects impossible large shift result" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const one = try ctx.bitvecValue(1, 4);    const four = try ctx.bitvecValue(4, 4);    const impossible = try ctx.eq(try ctx.bvshl(one, four), one);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(impossible);    try std.testing.expectEqual(sat.Status.unsat, try solver.solve());}test "bit-vector encoder solves concat and extract model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const high = try ctx.symbol("high", .{ .bitvec = 4 });    const low = try ctx.symbol("low", .{ .bitvec = 4 });    const word = try ctx.bvconcat(high, low);    const high_value = try ctx.bitvecValue(0xa, 4);    const low_value = try ctx.bitvecValue(0x5, 4);    const packed_value = try ctx.bitvecValue(0xa5, 8);    const high_assertion = try ctx.eq(try ctx.bvextract(word, 7, 4), high_value);    const low_assertion = try ctx.eq(try ctx.bvextract(word, 3, 0), low_value);    const packed_assertion = try ctx.eq(word, packed_value);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(high_assertion);    try encoder.assertTerm(low_assertion);    try encoder.assertTerm(packed_assertion);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0xa, .width = 4 } }, model_result.get("high").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0x5, .width = 4 } }, model_result.get("low").?);}test "bit-vector encoder solves zero and sign extension model" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const positive = try ctx.symbol("positive", .{ .bitvec = 4 });    const negative = try ctx.symbol("negative", .{ .bitvec = 4 });    const positive_value = try ctx.bitvecValue(0x5, 4);    const negative_value = try ctx.bitvecValue(0xa, 4);    const zero_extended = try ctx.bvzeroext(positive, 4);    const positive_signed = try ctx.bvsignext(positive, 4);    const negative_signed = try ctx.bvsignext(negative, 4);    const zero_extended_value = try ctx.bitvecValue(0x05, 8);    const positive_signed_value = try ctx.bitvecValue(0x05, 8);    const negative_signed_value = try ctx.bitvecValue(0xfa, 8);    const positive_is_value = try ctx.eq(positive, positive_value);    const negative_is_value = try ctx.eq(negative, negative_value);    const zero_extended_is_value = try ctx.eq(zero_extended, zero_extended_value);    const positive_signed_is_value = try ctx.eq(positive_signed, positive_signed_value);    const negative_signed_is_value = try ctx.eq(negative_signed, negative_signed_value);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try encoder.assertTerm(positive_is_value);    try encoder.assertTerm(negative_is_value);    try encoder.assertTerm(zero_extended_is_value);    try encoder.assertTerm(positive_signed_is_value);    try encoder.assertTerm(negative_signed_is_value);    try encoder.encodeSymbols();    try std.testing.expectEqual(sat.Status.sat, try solver.solve());    var model_result = try encoder.model(std.testing.allocator);    defer model_result.deinit();    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0x5, .width = 4 } }, model_result.get("positive").?);    try std.testing.expectEqual(ModelValue{ .bitvec = .{ .value = 0xa, .width = 4 } }, model_result.get("negative").?);}test "bit-vector encoder rejects invalid extract range" {    var ctx = term.Context.init(std.testing.allocator);    defer ctx.deinit();    const x = try ctx.symbol("x", .{ .bitvec = 4 });    const invalid = try ctx.bvextract(x, 4, 0);    var solver = sat.Solver.init(std.testing.allocator);    defer solver.deinit();    var encoder = try Encoder.init(std.testing.allocator, &ctx, &solver);    defer encoder.deinit();    try std.testing.expectError(EncodeError.InvalidBitVectorRange, encoder.encodeBits(invalid));}

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

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

Complete caller list for bitvec.Encoder.assertTerm

41 direct callers.

Complete caller list for bitvec.Encoder.deinit

43 direct callers.

Complete caller list for bitvec.Encoder.encodeSymbols

14 direct callers.

Complete caller list for bitvec.Encoder.init

43 direct callers.

Complete caller list for bitvec.Encoder.model

13 direct callers.

Audit

Definitions18
Public names18
Members24
Version26.7.0
Revisiondaab053ee433