tiny.smt.term
Defined in tiny.smt.
The formulas the package solves are trees of typed expressions over Booleans, integers, bit-vectors and arrays, with named constants and uninterpreted functions, and each expression has a sort.
API (17)
Types and contracts
Public types and contracts.
ApplyExpr: The payload of a function application: the declared function and its argument terms.ArraySelectExpr: The payload of an array read: the array termarrayand the index termindex.ArraySort: The shape of an array sort: the width of its bit-vector index and the width of its bit-vector element.ArrayStoreExpr: The payload of an array write: the array termarray, the index termindexand the element termvaluestored there.BinaryOperands: The payload of a two-operand term: the left operand termlhsand the right operand termrhs.BitVecExpr: The payload of a bit-vector constant: its value and its width.BitVecExtendExpr: The payload of a widening of the bit-vector termoperand.BitVecExtractExpr: The payload of a bit range taken from the bit-vector termoperand, between two bit positions.BitVecRotateExpr: The payload of a rotation of the bit-vector termoperandby a fixed number of bits.Context: One table that owns every term and function declaration it holds.Expr: One term: a tag naming its operator and a payload holding its operands.Function: A function declaration, named by its index in the declaration table of theContextthat holds it.FunctionDecl: One declared function: its name, the sorts of its arguments and the sort of its result.Script: A logic name and the list of terms asserted over oneContext.Sort: The sort of a term: Boolean, integer, a bit-vector of a given width, or an array from bit-vectors to bit-vectors.SymbolExpr: The payload of a named constant: its name and its sort.Term: A term, named by its index in the table of theContextthat built it.
Source
Source: lib/smt/src/root.zig:100
zig
pub const term = @import("term.zig");Source: lib/smt/src/term.zig
zig
//! The formulas the package solves are trees of typed expressions over Booleans, integers,//! bit-vectors and arrays, with named constants and uninterpreted functions, and each expression//! has a sort.//!//! A caller builds a formula one operator at a time and needs each piece to be a small value it can//! reuse as an operand of larger pieces. The encoder and the SMT-LIB writer walk every expression//! and read its operator, its operands and its sort.//!//! Formulas share subexpressions, as `x` appears in both `x + 1` and `x < x + 1`, so the pieces//! refer to one another and need one owner. Each operator accepts operands of particular sorts, and//! a formula with a mismatched operand has no meaning.//!//! [SMT-LIB](https://smt-lib.org/) defines the sorts and operators of its theories: Booleans with//! equality, integers, fixed-size bit-vectors, arrays and uninterpreted functions. The term kinds//! here carry SMT-LIB's operator names, and the SMT-LIB writer and parser map each kind to its//! standard operator.//!//! Every term lives in one table that owns every term and function declaration it holds, a//! *context* (`Context`), and each term is its 32-bit index in that table (`Term`). Building a term//! appends one entry and returns its index, so an index stays valid until the table is freed, and//! building one expression twice gives two indexes.//!//! The builders copy the names and operand lists they receive, so a caller may free its own slices//! after the call. The builders check no sorts, except that `function` refuses a second declaration//! of one name with other sorts and `apply` checks its arguments against the declaration. `sortOf`//! computes a term's sort on demand and reports a mismatch. A builder stores operand indexes as//! given, so an index that names no earlier term leaves a term on which `sortOf` fails or recurses//! without end.//!//! Arrays map a bit-vector index to a bit-vector element, and their sort records the two widths//! (`ArraySort`). Integer terms can be built, sorted and written as SMT-LIB, and the bit-vector//! encoder refuses them. The SMT-LIB parser returns, and the writer reads, a logic name with the//! list of terms asserted over one table, a *script* (`Script`). Freeing the table frees every//! term, name and operand list at once, and nothing frees a single term.const std = @import("std");/// The shape of an array sort: the width of its bit-vector index and the width of its bit-vector/// element. Code that declares an array constant builds one to give the array's shape, as the/// encoder's array tests do. The bit-vector encoder accepts an index of at most eight bits.pub const ArraySort = struct { /// The width of the index in bits. The index of every read or write of the array has this /// width. index_width: u32, /// The width of each element in bits. A read of the array is a bit-vector of this width. element_width: u32,};/// The sort of a term: Boolean, integer, a bit-vector of a given width, or an array from/// bit-vectors to bit-vectors. Code declares every constant and function with a sort, and `sortOf`/// returns one for every term.pub const Sort = union(enum) { /// The Boolean sort. SMT-LIB writes it `Bool`. bool, /// The sort of mathematical integers. SMT-LIB writes it `Int`. The bit-vector encoder refuses /// terms of this sort with `UnsupportedTerm`. int, /// A bit-vector sort, with its width in bits as the payload. SMT-LIB writes it `(_ BitVec n)`. /// Any width is accepted, 0 and widths above 128 included. A bit-vector constant wider than 128 /// bits makes the encoder panic. A named constant wider than 128 bits encodes, and /// `Encoder.model` returns `ModelValueTooWide` for it. bitvec: u32, /// An array sort, with its index and element widths as the payload. SMT-LIB writes it /// `(Array (_ BitVec i) (_ BitVec e))`. array: ArraySort, /// Writes the sort in SMT-LIB syntax: `Bool`, `Int`, `(_ BitVec n)` or /// `(Array (_ BitVec i) (_ BitVec e))`. The SMT-LIB writer calls it to print the sort of every /// declaration. The function returns only the writer's errors. pub fn write(self: Sort, writer: *std.Io.Writer) std.Io.Writer.Error!void { switch (self) { .bool => try writer.writeAll("Bool"), .int => try writer.writeAll("Int"), .bitvec => |width| try writer.print("(_ BitVec {d})", .{width}), .array => |array| try writer.print( "(Array (_ BitVec {d}) (_ BitVec {d}))", .{ array.index_width, array.element_width }, ), } } /// Returns true when the two sorts have the same kind and the same widths. `sortOf` and /// `function` call it to compare the sort an operator needs with the sort it got. pub fn eql(self: Sort, other: Sort) bool { return std.meta.eql(self, other); }};/// A term, named by its index in the table of the `Context` that built it. Every builder returns/// one and every operand is one, so callers pass terms around as plain integers. The index means/// nothing in another `Context`. The index stays valid until its `Context` is freed.pub const Term = u32;/// A function declaration, named by its index in the declaration table of the `Context` that holds/// it. `function` returns one, and `apply` takes one to build an application.pub const Function = u32;/// The payload of a named constant: its name and its sort. Code that walks terms reads it from a/// `.symbol` term to learn a constant's name and sort.pub const SymbolExpr = struct { /// The constant's name, a copy the `Context` owns and frees. Two constants may share a name, /// because every call to `symbol` builds a new term. name: []const u8, /// The constant's sort. sort: Sort,};/// The payload of a function application: the declared function and its argument terms. Code that/// walks terms reads it from an `.apply` term to learn which function is applied to which/// arguments.pub const ApplyExpr = struct { /// The applied function's index in the declaration table. function: Function, /// The argument terms in declaration order, a copy the `Context` owns and frees. args: []const Term,};/// The payload of a bit-vector constant: its value and its width. Code that walks terms reads it/// from a `.bitvec` term to learn a constant's bits.pub const BitVecExpr = struct { /// The constant's bits as an unsigned number: bit i of the number is bit i of the vector, /// counted from the least significant. Nothing checks the value against the width, and the /// encoder reads only the low `width` bits. value: u128, /// The constant's width in bits. A width above 128 makes the encoder panic. width: u32,};/// The payload of a two-operand term: the left operand term `lhs` and the right operand term `rhs`./// Code that walks terms reads it from every two-operand term, such as `.eq`, `.bvadd` or `.bvult`.pub const BinaryOperands = struct { lhs: Term, rhs: Term,};/// The payload of an array read: the array term `array` and the index term `index`. Code that walks/// terms reads it from an `.array_select` term.pub const ArraySelectExpr = struct { array: Term, index: Term,};/// The payload of an array write: the array term `array`, the index term `index` and the element/// term `value` stored there. Code that walks terms reads it from an `.array_store` term.pub const ArrayStoreExpr = struct { array: Term, index: Term, value: Term,};/// The payload of a bit range taken from the bit-vector term `operand`, between two bit positions./// Code that walks terms reads it from a `.bvextract` term.pub const BitVecExtractExpr = struct { operand: Term, /// The position of the highest bit taken, counted from 0 at the least significant bit. The /// position has to be below the operand's width, or `sortOf` and the encoder report /// `InvalidBitVectorRange`. high: u32, /// The position of the lowest bit taken, counted from 0 at the least significant bit. The /// position has to be at most `high`, or `sortOf` and the encoder report /// `InvalidBitVectorRange`. low: u32,};/// The payload of a widening of the bit-vector term `operand`. Code that walks terms reads it from/// a `.bvzeroext` or `.bvsignext` term.pub const BitVecExtendExpr = struct { operand: Term, /// The number of bits added above the operand's most significant bit. extra: u32,};/// The payload of a rotation of the bit-vector term `operand` by a fixed number of bits. Code that/// walks terms reads it from a `.bvrotl` or `.bvrotr` term.pub const BitVecRotateExpr = struct { operand: Term, /// The number of positions to rotate. The encoder takes it modulo the operand's width, so 5 on /// a 4-bit operand rotates by 1. amount: u32,};/// One declared function: its name, the sorts of its arguments and the sort of its result. Code/// that walks the declarations reads one per function, as the SMT-LIB writer does for each/// `declare-fun`.pub const FunctionDecl = struct { /// The function's name, a copy the `Context` owns and frees. The name is unique among the /// declarations of one `Context`. name: []const u8, /// The sorts of the arguments in order, a copy the `Context` owns and frees. The slice length /// is the function's arity: 0 declares a constant function. params: []const Sort, /// The sort of every application's result. result: Sort,};/// One term: a tag naming its operator and a payload holding its operands. Code that walks a/// formula switches on it, as the encoder and the SMT-LIB writer do for every term. The `Context`/// stores one per term, and a term's index is its position in that table. A tag named after an/// SMT-LIB operator reads and writes as that operator, and each tag whose name differs gives its/// SMT-LIB spelling. The bit-vector encoder refuses the integer tags (`int`, `add`, `mul`, `le`,/// `lt`, `ge`, `gt`) and `distinct` with `UnsupportedTerm`.pub const Expr = union(enum) { /// A named constant, with its name and sort as the payload. The encoder gives it fresh /// variables, one per bit. symbol: SymbolExpr, /// A function application, with the function and its arguments as the payload. Its sort is the /// function's result sort. apply: ApplyExpr, /// A Boolean constant. SMT-LIB writes it `true` or `false`. bool: bool, /// An integer constant, a signed 128-bit value. The bit-vector encoder refuses it. int: i128, /// A bit-vector constant, with its value and width as the payload. SMT-LIB writes it /// `(_ bvV W)`. bitvec: BitVecExpr, /// The negation of one Boolean operand. not: Term, /// The conjunction of any number of Boolean operands. With no operands it is true. SMT-LIB /// writes it `and`. and_: []const Term, /// The disjunction of any number of Boolean operands. With no operands it is false. SMT-LIB /// writes it `or`. or_: []const Term, /// `lhs` implies `rhs`, both Boolean. SMT-LIB writes it `=>`. implies: BinaryOperands, /// `lhs` equals `rhs`, a Boolean term over two operands of one sort. Two arrays are equal when /// every cell is equal. SMT-LIB writes it `=`. eq: BinaryOperands, /// True when all operands differ from one another, over operands of one sort. The bit-vector /// encoder refuses it. distinct: []const Term, /// The integer sum of any number of integer operands. SMT-LIB writes it `+`. The bit-vector /// encoder refuses it. add: []const Term, /// The integer product of any number of integer operands. SMT-LIB writes it `*`. The bit-vector /// encoder refuses it. mul: []const Term, /// `lhs` is at most `rhs`, as integers. SMT-LIB writes it `<=`. The bit-vector encoder refuses /// it. le: BinaryOperands, /// `lhs` is less than `rhs`, as integers. SMT-LIB writes it `<`. The bit-vector encoder refuses /// it. lt: BinaryOperands, /// `lhs` is at least `rhs`, as integers. SMT-LIB writes it `>=`. The bit-vector encoder refuses /// it. ge: BinaryOperands, /// `lhs` is greater than `rhs`, as integers. SMT-LIB writes it `>`. The bit-vector encoder /// refuses it. gt: BinaryOperands, /// `lhs` is at most `rhs`, as unsigned bit-vectors of one width. bvule: BinaryOperands, /// `lhs` is less than `rhs`, as unsigned bit-vectors of one width. bvult: BinaryOperands, /// `lhs` is at most `rhs`, as two's-complement bit-vectors of one width. bvsle: BinaryOperands, /// `lhs` is less than `rhs`, as two's-complement bit-vectors of one width. bvslt: BinaryOperands, /// True when the unsigned sum of `lhs` and `rhs` does not fit their width. bvuaddo: BinaryOperands, /// True when the two's-complement sum of `lhs` and `rhs` does not fit their width: both have /// one sign and the sum has the other. bvsaddo: BinaryOperands, /// True when the two's-complement difference `lhs` minus `rhs` does not fit their width. bvssubo: BinaryOperands, /// True when the unsigned product of `lhs` and `rhs` does not fit their width. bvumulo: BinaryOperands, /// True when the two's-complement product of `lhs` and `rhs` does not fit their width. bvsmulo: BinaryOperands, /// The bitwise complement of one bit-vector operand, of the same width. bvnot: Term, /// The bitwise and of two bit-vectors of one width. bvand: BinaryOperands, /// The bitwise or of two bit-vectors of one width. bvor: BinaryOperands, /// The bitwise exclusive or of two bit-vectors of one width. bvxor: BinaryOperands, /// `lhs` shifted toward its most significant bit by the value of `rhs`, with zeros shifted in, /// over two bit-vectors of one width. A shift amount at or above the width gives zero. bvshl: BinaryOperands, /// `lhs` shifted toward its least significant bit by the value of `rhs`, with zeros shifted in. /// A shift amount at or above the width gives zero. bvlshr: BinaryOperands, /// `lhs` shifted toward its least significant bit by the value of `rhs`, with copies of its /// sign bit shifted in. A shift amount at or above the width fills every bit with the sign bit. bvashr: BinaryOperands, /// The unsigned quotient of `lhs` by `rhs`, rounded down. Division by zero gives all ones. bvudiv: BinaryOperands, /// The unsigned remainder of `lhs` by `rhs`. The remainder by zero is `lhs`. bvurem: BinaryOperands, /// The two's-complement quotient of `lhs` by `rhs`, rounded toward zero. Division by zero gives /// 1 for a negative `lhs` and all ones otherwise. bvsdiv: BinaryOperands, /// The two's-complement remainder of `lhs` by `rhs`, with the sign of `lhs`. The remainder by /// zero is `lhs`. bvsrem: BinaryOperands, /// The two's-complement modulo of `lhs` by `rhs`, with the sign of `rhs`. The modulo by zero is /// `lhs`. bvsmod: BinaryOperands, /// The element of an array at an index, a bit-vector of the element width. The index has the /// array's index width. SMT-LIB writes it `select`. array_select: ArraySelectExpr, /// An array equal to its operand everywhere except at one index, which holds the new element. /// The term's sort is the operand's array sort. SMT-LIB writes it `store`. array_store: ArrayStoreExpr, /// The bits of `lhs` above the bits of `rhs`, one bit-vector as wide as the two together. /// SMT-LIB writes it `concat`. bvconcat: BinaryOperands, /// The bits from position `high` down to position `low` of one bit-vector, both included, so /// the result has `high - low + 1` bits. SMT-LIB writes it `((_ extract high low) x)`. bvextract: BitVecExtractExpr, /// A bit-vector widened by `extra` zero bits above its most significant bit. SMT-LIB writes it /// `((_ zero_extend extra) x)`. bvzeroext: BitVecExtendExpr, /// A bit-vector widened by `extra` copies of its sign bit. SMT-LIB writes it /// `((_ sign_extend extra) x)`. bvsignext: BitVecExtendExpr, /// A bit-vector rotated toward its most significant bit by a fixed number of positions, with /// the top bits wrapping to the bottom. SMT-LIB writes it `((_ rotate_left amount) x)`. bvrotl: BitVecRotateExpr, /// A bit-vector rotated toward its least significant bit by a fixed number of positions, with /// the bottom bits wrapping to the top. SMT-LIB writes it `((_ rotate_right amount) x)`. bvrotr: BitVecRotateExpr, /// The sum of two bit-vectors of one width, modulo 2 to the power of the width. bvadd: BinaryOperands, /// The difference `lhs` minus `rhs` of two bit-vectors of one width, modulo 2 to the power of /// the width. bvsub: BinaryOperands, /// The product of two bit-vectors of one width, modulo 2 to the power of the width. bvmul: BinaryOperands,};/// One table that owns every term and function declaration it holds. Every formula starts here: a/// caller makes one, builds its terms with the builders, and frees them all at once with `deinit`./// Each builder appends one term and returns its index. The builders check no sorts, except/// `function` and `apply`, and `sortOf` checks a term's sort when a caller asks. Every builder can/// fail with `error.OutOfMemory`, and a failed builder leaves the table as it was. A `Script`, a/// parser and an encoder borrow the `Context` and have to be freed before it.pub const Context = struct { /// The allocator for every term, name, operand list and declaration. A `Script` over this table /// and the SMT-LIB parser allocate with it too. allocator: std.mem.Allocator, /// Every term in the order it was built: entry i is the term whose index is i. The encoder and /// the SMT-LIB writer read it to walk every named constant. nodes: std.ArrayList(Expr) = .empty, /// Every function declaration in the order it was declared: entry i is the function whose index /// is i. functions: std.ArrayList(FunctionDecl) = .empty, /// Returns an empty table that allocates with `allocator`, so a caller makes one before /// building any term. The call allocates nothing. pub fn init(allocator: std.mem.Allocator) Context { return .{ .allocator = allocator }; } /// Frees every term's name and operand list, every declaration and both tables, so the owner /// calls it once after every `Script`, parser and encoder built over the table is done with its /// terms. Every `Term` and `Function` index from this table is invalid afterward. pub fn deinit(self: *Context) void { for (self.nodes.items) |node| { switch (node) { .symbol => |sym| self.allocator.free(sym.name), .apply => |item| self.allocator.free(item.args), .and_, .or_, .distinct, .add, .mul => |items| self.allocator.free(items), else => {}, } } for (self.functions.items) |decl| { self.allocator.free(decl.name); self.allocator.free(decl.params); } self.functions.deinit(self.allocator); self.nodes.deinit(self.allocator); self.* = undefined; } /// Appends a named constant of the given sort and returns its term, so code declares each free /// variable of a formula with it, as the SMT-LIB parser does for every `declare-const`. The /// builder copies `name`, so the caller may free its own copy. The builder builds a new term on /// every call, even for a name used before. pub fn symbol(self: *Context, name: []const u8, sort: Sort) !Term { const owned_name = try self.allocator.dupe(u8, name); errdefer self.allocator.free(owned_name); return try self.append(.{ .symbol = .{ .name = owned_name, .sort = sort } }); } /// Declares a function by name, argument sorts and result sort, and returns its index, so code /// declares each uninterpreted function with it, as the SMT-LIB parser does for every /// `declare-fun`. A second call with the same name and the same sorts returns the first index. /// A second call with the same name and other sorts returns `error.DuplicateFunction`. The /// builder copies `name` and `params`. pub fn function( self: *Context, name: []const u8, params: []const Sort, result: Sort, ) !Function { for (self.functions.items, 0..) |decl, index| { if (!std.mem.eql(u8, decl.name, name)) continue; if (!sortListsEqual(decl.params, params) or !decl.result.eql(result)) { return error.DuplicateFunction; } return @intCast(index); } const owned_name = try self.allocator.dupe(u8, name); errdefer self.allocator.free(owned_name); const owned_params = try self.allocator.dupe(Sort, params); errdefer self.allocator.free(owned_params); const id: Function = @intCast(self.functions.items.len); try self.functions.append(self.allocator, .{ .name = owned_name, .params = owned_params, .result = result, }); return id; } /// Appends the application of the function `function_id` to `args` and returns its term, so /// code applies a declared function to arguments with it. The call returns /// `error.UnknownFunction` for an index past the declarations, `error.FunctionArityMismatch` /// for the wrong number of arguments, and `error.FunctionArgumentSortMismatch` for an argument /// of the wrong sort. The builder computes each argument's sort with `sortOf`, so the call also /// returns the errors of `sortOf` for an argument that has no sort. The builder copies `args`. pub fn apply(self: *Context, function_id: Function, args: []const Term) !Term { if (function_id >= self.functions.items.len) return error.UnknownFunction; const decl = self.functions.items[function_id]; if (decl.params.len != args.len) return error.FunctionArityMismatch; for (args, decl.params) |arg, expected| { if (!(try self.sortOf(arg)).eql(expected)) return error.FunctionArgumentSortMismatch; } const owned = try self.allocator.dupe(Term, args); errdefer self.allocator.free(owned); return try self.append(.{ .apply = .{ .function = function_id, .args = owned } }); } /// Returns the sort of term `id`, checking every operand below it against the sorts its /// operator accepts, so code checks a formula's sorts before encoding or writing, and the /// encoder reads each function argument's sort with it. The call returns `error.TermOutOfRange` /// for an index past the table, `error.SortMismatch` for an operand of the wrong sort, /// `error.InvalidBitVectorRange` for a bad extract range, and the errors of `apply` for a bad /// application. The function allocates nothing, and the call walks the whole term below `id` on /// every call. The recursion has no depth bound, and a term that refers to itself makes the /// call recurse without end. pub fn sortOf(self: *const Context, id: Term) anyerror!Sort { if (id >= self.nodes.items.len) return error.TermOutOfRange; return switch (self.nodes.items[id]) { .symbol => |sym| sym.sort, .apply => |item| try self.sortOfApply(item), .bool => .bool, .int => .int, .bitvec => |value| .{ .bitvec = value.width }, .not => |operand| try self.expectBoolResult(operand), .and_, .or_ => |operands| try self.expectBoolList(operands), .implies => |pair| try self.expectBoolPair(pair.lhs, pair.rhs), .eq => |pair| try self.expectSameSortPair(pair.lhs, pair.rhs), .distinct => |operands| try self.expectSameSortList(operands), .add, .mul => |operands| try self.expectIntList(operands), .le => |pair| try self.expectIntPair(pair.lhs, pair.rhs), .lt => |pair| try self.expectIntPair(pair.lhs, pair.rhs), .ge => |pair| try self.expectIntPair(pair.lhs, pair.rhs), .gt => |pair| try self.expectIntPair(pair.lhs, pair.rhs), .bvule => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvult => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvsle => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvslt => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvuaddo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvsaddo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvssubo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvumulo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvsmulo => |pair| try self.expectBitVecPair(pair.lhs, pair.rhs), .bvnot => |operand| try self.expectBitVecResult(operand), .bvand => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvor => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvxor => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvshl => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvlshr => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvashr => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvudiv => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvurem => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvsdiv => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvsrem => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvsmod => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvadd => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvsub => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .bvmul => |pair| try self.expectBitVecPairResult(pair.lhs, pair.rhs), .array_select => |item| try self.sortOfArraySelect(item.array, item.index), .array_store => |item| try self.sortOfArrayStore(item.array, item.index, item.value), .bvconcat => |pair| try self.sortOfConcat(pair.lhs, pair.rhs), .bvextract => |item| try self.sortOfExtract(item.operand, item.high, item.low), .bvzeroext => |item| try self.sortOfExtend(item.operand, item.extra), .bvsignext => |item| try self.sortOfExtend(item.operand, item.extra), .bvrotl => |item| try self.expectBitVecResult(item.operand), .bvrotr => |item| try self.expectBitVecResult(item.operand), }; } /// Appends the constant `value` and returns its term, so code builds the Boolean constants with /// it. pub fn boolValue(self: *Context, value: bool) !Term { return try self.append(.{ .bool = value }); } /// Appends the integer constant `value` and returns its term, so code builds integer constants /// with it, as the SMT-LIB parser does for every numeral. pub fn intValue(self: *Context, value: i128) !Term { return try self.append(.{ .int = value }); } /// Appends the constant with bits `value` and width `width` and returns its term, so code /// builds bit-vector constants with it, as the SMT-LIB parser does for every `(_ bvV W)`. The /// builder checks neither the value against the width nor the width. A width above 128 makes /// the encoder panic. pub fn bitvecValue(self: *Context, value: u128, width: u32) !Term { return try self.append(.{ .bitvec = .{ .value = value, .width = width } }); } /// Appends the negation of `operand` and returns its term, so code negates a Boolean term with /// it. pub fn not(self: *Context, operand: Term) !Term { return try self.append(.{ .not = operand }); } /// Appends the conjunction of `operands` and returns its term, so code calls it to join Boolean /// terms that must all hold. The builder copies `operands`. pub fn and_(self: *Context, operands: []const Term) !Term { return try self.appendList(.and_, operands); } /// Appends the disjunction of `operands` and returns its term, so code calls it to join Boolean /// terms of which one must hold. The builder copies `operands`. pub fn or_(self: *Context, operands: []const Term) !Term { return try self.appendList(.or_, operands); } /// Appends `lhs` implies `rhs` and returns its term, so code states that one Boolean term /// implies another with it. pub fn implies(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .implies = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` equals `rhs` and returns its term, so code calls it to state that two terms /// are equal. pub fn eq(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .eq = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that `operands` all differ and returns its term, so code states that terms /// all differ with it. The builder copies `operands`. The bit-vector encoder refuses the term. pub fn distinct(self: *Context, operands: []const Term) !Term { return try self.appendList(.distinct, operands); } /// Appends the integer sum of `operands` and returns its term, so code adds integer terms with /// it. The builder copies `operands`. The bit-vector encoder refuses the term. pub fn add(self: *Context, operands: []const Term) !Term { return try self.appendList(.add, operands); } /// Appends the integer product of `operands` and returns its term, so code multiplies integer /// terms with it. The builder copies `operands`. The bit-vector encoder refuses the term. pub fn mul(self: *Context, operands: []const Term) !Term { return try self.appendList(.mul, operands); } /// Appends the two-operand term of kind `tag` over `lhs` and `rhs` and returns its term, so the /// SMT-LIB parser calls it with the tag of each two-operand operator it reads, and one call /// covers every such operator. The parameter `tag` names a kind whose payload is /// `BinaryOperands`, or `.array_select`, which the builder builds with `arraySelect`. Any other /// tag is a compile error. pub fn binary(self: *Context, comptime tag: std.meta.Tag(Expr), lhs: Term, rhs: Term) !Term { return switch (tag) { .implies, .eq, .le, .lt, .ge, .gt, .bvule, .bvult, .bvsle, .bvslt, .bvuaddo, .bvsaddo, .bvssubo, .bvumulo, .bvsmulo, .bvand, .bvor, .bvxor, .bvshl, .bvlshr, .bvashr, .bvudiv, .bvurem, .bvsdiv, .bvsrem, .bvsmod, .bvconcat, .bvadd, .bvsub, .bvmul, => try self.appendPair(tag, lhs, rhs), .array_select => try self.arraySelect(lhs, rhs), else => unreachable, }; } /// Appends `lhs` at most `rhs`, as integers, and returns its term, so code compares integer /// terms with it. The bit-vector encoder refuses the term. pub fn le(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .le = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` less than `rhs`, as integers, and returns its term, so code compares integer /// terms with it. The bit-vector encoder refuses the term. pub fn lt(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .lt = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` at least `rhs`, as integers, and returns its term, so code compares integer /// terms with it. The bit-vector encoder refuses the term. pub fn ge(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .ge = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` greater than `rhs`, as integers, and returns its term, so code compares /// integer terms with it. The bit-vector encoder refuses the term. pub fn gt(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .gt = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` at most `rhs`, as unsigned bit-vectors, and returns its term, so code compares /// bit-vectors as unsigned numbers with it. pub fn bvule(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvule = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` less than `rhs`, as unsigned bit-vectors, and returns its term, so code /// compares bit-vectors as unsigned numbers with it, as the SMT-LIB round-trip test does. pub fn bvult(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvult = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` at most `rhs`, as two's-complement bit-vectors, and returns its term, so code /// compares bit-vectors as two's-complement numbers with it. pub fn bvsle(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsle = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` less than `rhs`, as two's-complement bit-vectors, and returns its term, so /// code compares bit-vectors as two's-complement numbers with it. pub fn bvslt(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvslt = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that the unsigned sum of `lhs` and `rhs` overflows their width and returns /// its term. Code asks with it whether an unsigned addition can overflow. pub fn bvuaddo(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvuaddo = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that the two's-complement sum of `lhs` and `rhs` overflows their width and /// returns its term. Code asks with it whether a two's-complement addition can overflow. pub fn bvsaddo(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsaddo = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that the two's-complement difference `lhs` minus `rhs` overflows their /// width and returns its term. Code asks with it whether a two's-complement subtraction can /// overflow. pub fn bvssubo(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvssubo = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that the unsigned product of `lhs` and `rhs` overflows their width and /// returns its term. Code asks with it whether an unsigned multiplication can overflow. pub fn bvumulo(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvumulo = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the claim that the two's-complement product of `lhs` and `rhs` overflows their width /// and returns its term. Code asks with it whether a two's-complement multiplication can /// overflow. pub fn bvsmulo(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsmulo = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the bitwise complement of `operand` and returns its term. Code complements every bit /// of a bit-vector with it. pub fn bvnot(self: *Context, operand: Term) !Term { return try self.append(.{ .bvnot = operand }); } /// Appends the bitwise and of `lhs` and `rhs` and returns its term. Code masks bit-vectors with /// it. pub fn bvand(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvand = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the bitwise or of `lhs` and `rhs` and returns its term. Code sets bits of a /// bit-vector with it. pub fn bvor(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvor = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the bitwise exclusive or of `lhs` and `rhs` and returns its term. Code flips bits of /// a bit-vector with it. pub fn bvxor(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvxor = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` shifted toward its most significant bit by the value of `rhs` and returns its /// term. Code shifts a bit-vector left with it. pub fn bvshl(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvshl = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` shifted toward its least significant bit by the value of `rhs`, with zeros /// shifted in, and returns its term. Code shifts with it a bit-vector right with zero fill. pub fn bvlshr(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvlshr = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` shifted toward its least significant bit by the value of `rhs`, with copies of /// its sign bit shifted in, and returns its term. Code shifts a two's-complement bit-vector /// right with it. pub fn bvashr(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvashr = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the unsigned quotient of `lhs` by `rhs` and returns its term. Code divides /// bit-vectors as unsigned numbers with it. pub fn bvudiv(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvudiv = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the unsigned remainder of `lhs` by `rhs` and returns its term. Code takes the /// unsigned remainder of bit-vectors with it. pub fn bvurem(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvurem = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the two's-complement quotient of `lhs` by `rhs`, rounded toward zero, and returns /// its term. Code divides bit-vectors as two's-complement numbers with it. pub fn bvsdiv(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsdiv = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the remainder of `lhs` by `rhs` with the sign of `lhs` and returns its term. Code /// takes the two's-complement remainder of bit-vectors with it. pub fn bvsrem(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsrem = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the modulo of `lhs` by `rhs` with the sign of `rhs` and returns its term. Code takes /// the two's-complement modulo of bit-vectors with it. pub fn bvsmod(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsmod = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the element of `array` at `index` and returns its term. Code reads an array at an /// index with it. pub fn arraySelect(self: *Context, array: Term, index: Term) !Term { return try self.append(.{ .array_select = .{ .array = array, .index = index } }); } /// Appends the array equal to `array` except that `index` holds `value`, and returns its term. /// Code writes an element into an array with it. pub fn arrayStore(self: *Context, array: Term, index: Term, value: Term) !Term { return try self.append(.{ .array_store = .{ .array = array, .index = index, .value = value, } }); } /// Appends the bits of `lhs` above the bits of `rhs` and returns its term. Code joins two /// bit-vectors into a wider one with it. pub fn bvconcat(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvconcat = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the bits from position `high` down to position `low` of `operand`, both included, /// and returns its term. Code takes a range of bits from a bit-vector with it. It checks no /// range: `sortOf` and the encoder report `InvalidBitVectorRange` when `low` is above `high` or /// `high` is at or above the width. pub fn bvextract(self: *Context, operand: Term, high: u32, low: u32) !Term { return try self.append(.{ .bvextract = .{ .operand = operand, .high = high, .low = low } }); } /// Appends `operand` widened by `extra` zero bits above its most significant bit and returns /// its term. Code widens an unsigned bit-vector with it. pub fn bvzeroext(self: *Context, operand: Term, extra: u32) !Term { return try self.append(.{ .bvzeroext = .{ .operand = operand, .extra = extra } }); } /// Appends `operand` widened by `extra` copies of its sign bit and returns its term. Code /// widens a two's-complement bit-vector with it. pub fn bvsignext(self: *Context, operand: Term, extra: u32) !Term { return try self.append(.{ .bvsignext = .{ .operand = operand, .extra = extra } }); } /// Appends `operand` rotated toward its most significant bit by `amount` positions and returns /// its term. Code rotates a bit-vector left by a fixed amount with it. pub fn bvrotl(self: *Context, operand: Term, amount: u32) !Term { return try self.append(.{ .bvrotl = .{ .operand = operand, .amount = amount } }); } /// Appends `operand` rotated toward its least significant bit by `amount` positions and returns /// its term. Code rotates a bit-vector right by a fixed amount with it. pub fn bvrotr(self: *Context, operand: Term, amount: u32) !Term { return try self.append(.{ .bvrotr = .{ .operand = operand, .amount = amount } }); } /// Appends the sum of `lhs` and `rhs`, modulo 2 to the power of their width, and returns its /// term. Code adds bit-vectors with it. pub fn bvadd(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvadd = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends `lhs` minus `rhs`, modulo 2 to the power of their width, and returns its term. Code /// subtracts bit-vectors with it. pub fn bvsub(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvsub = .{ .lhs = lhs, .rhs = rhs } }); } /// Appends the product of `lhs` and `rhs`, modulo 2 to the power of their width, and returns /// its term. Code multiplies bit-vectors with it. pub fn bvmul(self: *Context, lhs: Term, rhs: Term) !Term { return try self.append(.{ .bvmul = .{ .lhs = lhs, .rhs = rhs } }); } fn sortOfApply(self: *const Context, item: ApplyExpr) !Sort { if (item.function >= self.functions.items.len) return error.UnknownFunction; const decl = self.functions.items[item.function]; if (decl.params.len != item.args.len) return error.FunctionArityMismatch; for (item.args, decl.params) |arg, expected| { if (!(try self.sortOf(arg)).eql(expected)) return error.FunctionArgumentSortMismatch; } return decl.result; } fn expectBoolResult(self: *const Context, operand: Term) !Sort { if (!(try self.sortOf(operand)).eql(.bool)) return error.SortMismatch; return .bool; } fn expectBoolPair(self: *const Context, lhs: Term, rhs: Term) !Sort { if (!(try self.sortOf(lhs)).eql(.bool) or !(try self.sortOf(rhs)).eql(.bool)) { return error.SortMismatch; } return .bool; } fn expectBoolList(self: *const Context, operands: []const Term) !Sort { for (operands) |operand| { if (!(try self.sortOf(operand)).eql(.bool)) return error.SortMismatch; } return .bool; } fn expectIntPair(self: *const Context, lhs: Term, rhs: Term) !Sort { if (!(try self.sortOf(lhs)).eql(.int) or !(try self.sortOf(rhs)).eql(.int)) { return error.SortMismatch; } return .bool; } fn expectIntList(self: *const Context, operands: []const Term) !Sort { for (operands) |operand| { if (!(try self.sortOf(operand)).eql(.int)) return error.SortMismatch; } return .int; } fn expectSameSortPair(self: *const Context, lhs: Term, rhs: Term) !Sort { if (!(try self.sortOf(lhs)).eql(try self.sortOf(rhs))) return error.SortMismatch; return .bool; } fn expectSameSortList(self: *const Context, operands: []const Term) !Sort { if (operands.len == 0) return .bool; const expected = try self.sortOf(operands[0]); for (operands[1..]) |operand| { if (!(try self.sortOf(operand)).eql(expected)) return error.SortMismatch; } return .bool; } fn expectBitVecPair(self: *const Context, lhs: Term, rhs: Term) !Sort { _ = try self.expectBitVecPairResult(lhs, rhs); return .bool; } fn expectBitVecResult(self: *const Context, operand: Term) !Sort { return switch (try self.sortOf(operand)) { .bitvec => |width| .{ .bitvec = width }, else => error.SortMismatch, }; } fn expectBitVecPairResult(self: *const Context, lhs: Term, rhs: Term) !Sort { return switch (try self.sortOf(lhs)) { .bitvec => |lhs_width| switch (try self.sortOf(rhs)) { .bitvec => |rhs_width| { if (lhs_width != rhs_width) return error.SortMismatch; return .{ .bitvec = lhs_width }; }, else => error.SortMismatch, }, else => error.SortMismatch, }; } fn sortOfArraySelect(self: *const Context, array: Term, index: Term) !Sort { const array_sort = try self.sortOf(array); const index_sort = try self.sortOf(index); return switch (array_sort) { .array => |shape| switch (index_sort) { .bitvec => |width| { if (width != shape.index_width) return error.SortMismatch; return .{ .bitvec = shape.element_width }; }, else => error.SortMismatch, }, else => error.SortMismatch, }; } fn sortOfArrayStore(self: *const Context, array: Term, index: Term, value: Term) !Sort { const array_sort = try self.sortOf(array); const index_sort = try self.sortOf(index); const value_sort = try self.sortOf(value); return switch (array_sort) { .array => |shape| switch (index_sort) { .bitvec => |index_width| switch (value_sort) { .bitvec => |element_width| { if (index_width != shape.index_width or element_width != shape.element_width) { return error.SortMismatch; } return array_sort; }, else => error.SortMismatch, }, else => error.SortMismatch, }, else => error.SortMismatch, }; } fn sortOfConcat(self: *const Context, lhs: Term, rhs: Term) !Sort { return switch (try self.sortOf(lhs)) { .bitvec => |lhs_width| switch (try self.sortOf(rhs)) { .bitvec => |rhs_width| .{ .bitvec = try std.math.add(u32, lhs_width, rhs_width) }, else => error.SortMismatch, }, else => error.SortMismatch, }; } fn sortOfExtract(self: *const Context, operand: Term, high: u32, low: u32) !Sort { return switch (try self.sortOf(operand)) { .bitvec => |width| { if (low > high or high >= width) return error.InvalidBitVectorRange; return .{ .bitvec = high - low + 1 }; }, else => error.SortMismatch, }; } fn sortOfExtend(self: *const Context, operand: Term, extra: u32) !Sort { return switch (try self.sortOf(operand)) { .bitvec => |width| .{ .bitvec = try std.math.add(u32, width, extra) }, else => error.SortMismatch, }; } fn append(self: *Context, expr: Expr) !Term { const id: Term = @intCast(self.nodes.items.len); try self.nodes.append(self.allocator, expr); return id; } fn appendPair(self: *Context, comptime tag: std.meta.Tag(Expr), lhs: Term, rhs: Term) !Term { return try self.append(@unionInit(Expr, @tagName(tag), .{ .lhs = lhs, .rhs = rhs })); } fn appendList(self: *Context, comptime tag: std.meta.Tag(Expr), operands: []const Term) !Term { const owned = try self.allocator.dupe(Term, operands); errdefer self.allocator.free(owned); return switch (tag) { .and_ => try self.append(.{ .and_ = owned }), .or_ => try self.append(.{ .or_ = owned }), .distinct => try self.append(.{ .distinct = owned }), .add => try self.append(.{ .add = owned }), .mul => try self.append(.{ .mul = owned }), else => unreachable, }; }};fn sortListsEqual(lhs: []const Sort, rhs: []const Sort) bool { if (lhs.len != rhs.len) return false; for (lhs, rhs) |left, right| { if (!left.eql(right)) return false; } return true;}/// A logic name and the list of terms asserted over one `Context`. `smtlib.parseScript` returns/// one, `smtlib.writeScript` prints one, and code that solves a script asserts each of its terms/// with the encoder. It borrows the `Context` and allocates its list with the `Context`'s/// allocator.pub const Script = struct { /// The `Context` whose terms the script asserts. ctx: *Context, /// The logic name, such as `QF_BV`, that `set-logic` gives. The script borrows it: a script /// from `smtlib.parseScript` points into the parsed text, which has to outlive the script. logic: []const u8, /// The asserted terms in the order they were asserted. assertions: std.ArrayList(Term) = .empty, /// Returns a script over `ctx` with logic `logic` and no assertions. Code makes an empty script /// before asserting terms, as the SMT-LIB writer's tests do. It copies neither and allocates /// nothing. pub fn init(ctx: *Context, logic: []const u8) Script { return .{ .ctx = ctx, .logic = logic }; } /// Frees the list of assertions and leaves the terms to the `Context`. The owner calls it /// before freeing the `Context`. pub fn deinit(self: *Script) void { self.assertions.deinit(self.ctx.allocator); self.* = undefined; } /// Appends `assertion` to the list. Code adds one assertion to a script with it. It checks /// nothing, including whether the term is Boolean. pub fn assertTerm(self: *Script, assertion: Term) !void { try self.assertions.append(self.ctx.allocator, assertion); }};test "term context stores structured arithmetic expression" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .int); const one = try ctx.intValue(1); const sum = try ctx.add(&.{ x, one }); const zero = try ctx.intValue(0); _ = try ctx.gt(sum, zero); try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}test "term context stores function applications" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const bv4 = Sort{ .bitvec = 4 }; const x = try ctx.symbol("x", bv4); const y = try ctx.symbol("y", bv4); const flag = try ctx.symbol("flag", .bool); const f = try ctx.function("f", &.{bv4}, bv4); const fx = try ctx.apply(f, &.{x}); _ = try ctx.apply(f, &.{y}); try std.testing.expectEqual(@as(usize, 1), ctx.functions.items.len); try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len); try std.testing.expect((try ctx.sortOf(fx)).eql(bv4)); try std.testing.expectEqual(f, try ctx.function("f", &.{bv4}, bv4)); try std.testing.expectError(error.DuplicateFunction, ctx.function("f", &.{bv4}, .bool)); try std.testing.expectError(error.FunctionArgumentSortMismatch, ctx.apply(f, &.{flag}));}test "term context infers composite sorts" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const bv4 = Sort{ .bitvec = 4 }; const high = try ctx.symbol("high", bv4); const low = try ctx.symbol("low", bv4); const word = try ctx.bvconcat(high, low); const upper = try ctx.bvextract(word, 7, 4); const lower = try ctx.bvextract(word, 3, 0); const memory = try ctx.symbol("memory", .{ .array = .{ .index_width = 2, .element_width = 4, } }); const index = try ctx.symbol("index", .{ .bitvec = 2 }); const read = try ctx.arraySelect(memory, index); try std.testing.expect((try ctx.sortOf(word)).eql(.{ .bitvec = 8 })); try std.testing.expect((try ctx.sortOf(upper)).eql(bv4)); try std.testing.expect((try ctx.sortOf(lower)).eql(bv4)); try std.testing.expect((try ctx.sortOf(try ctx.eq(upper, lower))).eql(.bool)); try std.testing.expect((try ctx.sortOf(read)).eql(bv4));}test "term context stores bit-vector overflow predicates" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .{ .bitvec = 8 }); const y = try ctx.symbol("y", .{ .bitvec = 8 }); _ = try ctx.bvuaddo(x, y); _ = try ctx.bvsaddo(x, y); _ = try ctx.bvssubo(x, y); _ = try ctx.bvumulo(x, y); _ = try ctx.bvsmulo(x, y); try std.testing.expectEqual(@as(usize, 7), ctx.nodes.items.len);}test "term context stores signed bit-vector comparisons" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .{ .bitvec = 8 }); const y = try ctx.symbol("y", .{ .bitvec = 8 }); const difference = try ctx.bvsub(x, y); _ = try ctx.bvslt(difference, y); _ = try ctx.bvsle(x, y); try std.testing.expect((try ctx.sortOf(difference)).eql(.{ .bitvec = 8 })); try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}test "term context stores bit-vector bitwise operators" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .{ .bitvec = 8 }); const y = try ctx.symbol("y", .{ .bitvec = 8 }); _ = try ctx.bvnot(x); _ = try ctx.bvand(x, y); _ = try ctx.bvor(x, y); _ = try ctx.bvxor(x, y); try std.testing.expectEqual(@as(usize, 6), ctx.nodes.items.len);}test "term context stores bit-vector shift operators" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .{ .bitvec = 8 }); const amount = try ctx.symbol("amount", .{ .bitvec = 8 }); _ = try ctx.bvshl(x, amount); _ = try ctx.bvlshr(x, amount); _ = try ctx.bvashr(x, amount); _ = try ctx.bvudiv(x, amount); _ = try ctx.bvurem(x, amount); _ = try ctx.bvsdiv(x, amount); _ = try ctx.bvsrem(x, amount); _ = try ctx.bvsmod(x, amount); try std.testing.expectEqual(@as(usize, 10), ctx.nodes.items.len);}test "term context stores bit-vector width-changing operators" { var ctx = 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); _ = try ctx.bvextract(word, 7, 4); _ = try ctx.bvzeroext(high, 4); _ = try ctx.bvsignext(low, 4); try std.testing.expectEqual(@as(usize, 6), ctx.nodes.items.len);}test "term context stores bit-vector rotate operators" { var ctx = Context.init(std.testing.allocator); defer ctx.deinit(); const x = try ctx.symbol("x", .{ .bitvec = 8 }); _ = try ctx.bvrotl(x, 3); _ = try ctx.bvrotr(x, 5); try std.testing.expectEqual(@as(usize, 3), ctx.nodes.items.len);}test "term context stores finite bit-vector array operators" { var ctx = 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 value = try ctx.symbol("value", .{ .bitvec = 4 }); const written = try ctx.arrayStore(memory, index, value); _ = try ctx.arraySelect(written, index); try std.testing.expectEqual(@as(usize, 5), ctx.nodes.items.len);}Audit
| Definitions | 11 |
|---|---|
| Public names | 11 |
| Members | 22 |
| Version | 26.7.0 |
| Revision | daab053ee433 |