tiny.smt.choir.dialect.SmtDialect
Defined in choir.dialect.
The SMT dialect: its name, its specification, one struct per operation, and the functions that build and read its types.
API (117)
Actions
Public operations.
ApplyOp.create: Creates ansmt.applyoperation that applies the functionfunction_nametooperands, with one result of typeresult_typeand the source locationloc, and returns the struct holding it.ApplyOp.getName: Returns the name of the applied function from thenameattribute.ApplyOp.getResult: Returns the operation's one result, the value of the application.ArraySelectOp.create: Creates ansmt.array.selectoperation that readsarrayatindexat the source locationloc, and returns the struct holding it.ArraySelectOp.getResult: Returns the operation's one result, the element read, so a caller passes it to the operations that use it.ArrayStoreOp.create: Creates ansmt.array.storeoperation that writesvalueintoarrayatindexat the source locationloc, and returns the struct holding it.ArrayStoreOp.getResult: Returns the operation's one result, the array after the write, so a caller passes it to the operations that read or write it next.AssertOp.create: Creates ansmt.assertoperation onassertionat the source locationloc, leaves its name and group label unset, and returns the struct holding it.AssertOp.createGrouped: Creates ansmt.assertoperation onassertionwith the nameassertion_nameand the group labelgroup.AssertOp.createNamed: Creates ansmt.assertoperation onassertionwith the nameassertion_name, and leaves its group label unset.AssertOp.getAssertion: Returns the operation's one operand, the asserted formula, so code that reads the assertions back turns each asserted formula into a solver term.AssertOp.getGroup: Returns the assertion's group label from itsgroupattribute, so code that reports answers sorts them by this label.AssertOp.getName: Returns the assertion's name from itsnameattribute, so code that reports an answer names the assertion it concerns.BitVecConstOp.create: Creates ansmt.bv.constoperation holdingvalue, with one result of the bit-vector type of widthwidthand the source locationloc, and returns the struct holding it.BitVecConstOp.getResult: Returns the operation's one result, the constant's value.BitVecConstOp.getValue: Returns the constant, parsed from the decimal text of itsvalueattribute.BoolConstOp.create: Creates ansmt.bool.constoperation holdingvalue, with one result of the Boolean type and the source locationloc, and returns the struct holding it.BoolConstOp.getResult: Returns the operation's one result, the constant's value.BoolConstOp.getValue: Returns the constant from itsvalueattribute.BvConcatOp.create: Creates ansmt.bvconcatoperation that joinslhsaboverhsat the source locationloc, and returns the struct holding it, for a checker building a wider value from two parts withlhsas the high part.BvConcatOp.getResult: Returns the operation's one result, the joined value, so a caller can pass it to the operations that use it.BvExtractOp.create: Creates ansmt.bvextractoperation that keeps the bits ofoperandfrom positionhighdown to positionlowat the source locationloc, and returns the struct holding it, so a checker can narrow a value to fewer bits by keeping its low bits, such as positions7down to0.BvExtractOp.getHigh: Returns the highest bit position the extraction keeps, from thehighattribute, so code that reads a formula back can build the solver's extraction term.BvExtractOp.getLow: Returns the lowest bit position the extraction keeps, from thelowattribute, so code that reads a formula back can build the solver's extraction term.BvExtractOp.getResult: Returns the operation's one result, the extracted bits, so a caller can pass them to the operations that use them.BvRotlOp.create: Creates ansmt.bvrotloperation that rotatesoperandbyamountpositions at the source locationloc, and returns the struct holding it, so a checker can rotate a value left by the number of positions it knows when it builds the formula.BvRotlOp.getAmount: Returns the rotation amount from theamountattribute, so code that reads a formula back can build the solver's rotation term.BvRotlOp.getResult: Returns the operation's one result, the rotated value, so a caller can pass it to the operations that use it.BvRotrOp.create: Creates ansmt.bvrotroperation that rotatesoperandbyamountpositions at the source locationloc, and returns the struct holding it, so a checker can rotate a value right by the number of positions it knows when it builds the formula.BvRotrOp.getAmount: Returns the rotation amount from theamountattribute, so code that reads a formula back can build the solver's rotation term.BvRotrOp.getResult: Returns the operation's one result, the rotated value, so a caller can pass it to the operations that use it.BvSignExtOp.create: Creates ansmt.bvsignextoperation that addsextracopies of the sign bit ofoperandabove it at the source locationloc, and returns the struct holding it.BvSignExtOp.getExtra: Returns the number of added bits from theextraattribute, so code that reads a formula back can build the solver's extension term.BvSignExtOp.getResult: Returns the operation's one result, the widened value, so a caller can pass the value to the operations that use it.BvZeroExtOp.create: Creates ansmt.bvzeroextoperation that addsextrazero bits aboveoperandat the source locationloc, and returns the struct holding it, so a checker can widen an unsigned value by the number of bits the target width needs.BvZeroExtOp.getExtra: Returns the number of added bits from theextraattribute, so code that reads a formula back can build the solver's extension term.BvZeroExtOp.getResult: Returns the operation's one result, the widened value, so a caller can pass it to the operations that use it.VarOp.create: Creates ansmt.varoperation namedsymbol, with one result of typeresult_typeand the source locationloc, and returns the struct holding it.VarOp.getName: Returns the variable's name from itsnameattribute.VarOp.getResult: Returns the operation's one result, the variable's value.arrayShape: Returns the index width and the element width of the array typetyp, as a copy of itsArrayTypePayload, so code that reads a formula back can pick an array sort from a value's type.bitVecWidth: Returns the width of the bit-vector typetyp, so code that reads a formula back can pick a solver sort and a constant's width from a value's type.getArrayType: Returns the array type fromctxwhose index is a bit-vector ofindex_widthbits and whose element is a bit-vector ofelement_widthbits: the typesmt.arraywith the textindex_width:element_width.getBitVecType: Returns the bit-vector type ofwidthbits fromctx: the typesmt.bvwithwidthas decimal text.getBoolType: Returns the dialect's Boolean type,smt.bool, fromctx.
Types and contracts
Public types and contracts.
AndOp: The conjunction of two Boolean formulas, as the operationsmt.and.ApplyOp: The application of a function to the operation's operands: the operationsmt.apply, with one result and the function's name in itsnameattribute.ArraySelectOp: The element of an array at an index, as the operationsmt.array.select, with the array and the index as operands and one result of the element type.ArrayStoreOp: The array equal to a given array except that one index holds a new value, as the operationsmt.array.store, with the array, the index and the value as operands and one result of the array's type.ArrayTypePayload: The index width and the element width of an array type, parsed from the textindex:elementthe type carries.AssertOp: A Boolean formula that must hold: the operationsmt.assert, with one Boolean operand, no result, and an optional name and group label in itsnameandgroupattributes.BitVecConstOp: A bit-vector constant: the operationsmt.bv.const, with one result of a bit-vector type and the value in itsvalueattribute as decimal text.BitVecTypePayload: The width of a bit-vector type, parsed from the decimal text the type carries.BoolConstOp: A Boolean constant: the operationsmt.bool.const, with one Boolean result and the value in itsvalueattribute.BvAddOp: The sum of two bit-vectors of one width, modulo 2 to the power of the width, as the operationsmt.bvadd.BvAndOp: The bitwise and of two bit-vectors of one width, as the operationsmt.bvand.BvAshrOp: The first bit-vector shifted toward its least significant bit by the value of the second, with copies of its sign bit shifted in, as the operationsmt.bvashr.BvConcatOp: The bits of the first bit-vector above the bits of the second, as the operationsmt.bvconcat, with one result whose width is the sum of the two widths, for a checker joining two bit-vectors into a wider one, for example two halves of a word.BvExtractOp: The bits of a bit-vector from a high position down to a low position, both included, as the operationsmt.bvextract, with the positions in itshighandlowattributes, for a checker taking a range of bits from a bit-vector, for example to narrow a value to a smaller width.BvLshrOp: The first bit-vector shifted toward its least significant bit by the value of the second, with zeros shifted in, as the operationsmt.bvlshr.BvMulOp: The product of two bit-vectors of one width, modulo 2 to the power of the width, as the operationsmt.bvmul.BvNotOp: The bitwise complement of a bit-vector, as the operationsmt.bvnot.BvOrOp: The bitwise or of two bit-vectors of one width, as the operationsmt.bvor.BvRotlOp: The rotation of a bit-vector toward its most significant bit by a fixed number of positions, as the operationsmt.bvrotl, with the number of positions in itsamountattribute, for a checker that rotates a bit-vector left.BvRotrOp: The rotation of a bit-vector toward its least significant bit by a fixed number of positions, as the operationsmt.bvrotr, with the number of positions in itsamountattribute, for a checker that rotates a bit-vector right.BvSaddoOp: Whether the two's-complement sum of two bit-vectors overflows their width, as the operationsmt.bvsaddo, for a checker that asks whether a program's signed addition can overflow.BvSdivOp: The two's-complement quotient of the first bit-vector by the second, rounded toward zero, as the operationsmt.bvsdiv, for a checker lowering a program's signed division into this operation.BvShlOp: The first bit-vector shifted toward its most significant bit by the value of the second, as the operationsmt.bvshl.BvSignExtOp: A bit-vector widened by copies of its sign bit, as the operationsmt.bvsignext, with the number of added bits in itsextraattribute.BvSleOp: Whether the first bit-vector is at most the second as two's-complement numbers, as the operationsmt.bvsle, for a checker that states a signed bound that includes its limit, for example that an offset is at least zero.BvSltOp: Whether the first bit-vector is less than the second as two's-complement numbers, as the operationsmt.bvslt, for a checker that states a signed bound, for example that a value is below a limit as a two's-complement number.BvSmodOp: The modulo of the first bit-vector by the second with the sign of the second, as the operationsmt.bvsmod, for a checker taking a signed modulo whose result follows the divisor's sign.BvSmuloOp: Whether the two's-complement product of two bit-vectors overflows their width, as the operationsmt.bvsmulo, for a checker that asks whether a program's signed multiplication can overflow.BvSremOp: The remainder of the first bit-vector by the second with the sign of the first, as the operationsmt.bvsrem, for a checker taking the signed remainder of a program's division.BvSsuboOp: Whether the two's-complement difference of two bit-vectors, the first minus the second, overflows their width, as the operationsmt.bvssubo, for a checker that asks whether a program's signed subtraction can overflow.BvSubOp: The difference of two bit-vectors of one width, the first minus the second, modulo 2 to the power of the width, as the operationsmt.bvsub.BvUaddoOp: Whether the unsigned sum of two bit-vectors overflows their width, as the operationsmt.bvuaddo, for a checker that asks whether a program's unsigned addition can overflow.BvUdivOp: The unsigned quotient of the first bit-vector by the second, as the operationsmt.bvudiv, for a checker lowering a program's unsigned division into this operation.BvUleOp: Whether the first bit-vector is at most the second as unsigned numbers, as the operationsmt.bvule, for a checker that states an unsigned bound that includes its limit, for example that an end offset is at most a length.BvUltOp: Whether the first bit-vector is less than the second as unsigned numbers, as the operationsmt.bvult, for a checker that states an unsigned bound, for example that an index is below a length.BvUmuloOp: Whether the unsigned product of two bit-vectors overflows their width, as the operationsmt.bvumulo, for a checker that asks whether a program's unsigned multiplication can overflow.BvUremOp: The unsigned remainder of the first bit-vector by the second, as the operationsmt.bvurem, for a checker taking the unsigned remainder of a program's division.BvXorOp: The bitwise exclusive or of two bit-vectors of one width, as the operationsmt.bvxor.BvZeroExtOp: A bit-vector widened by zero bits above its most significant bit, as the operationsmt.bvzeroext, with the number of added bits in itsextraattribute, for a checker widening an unsigned value to a larger width, for example before comparing it with a wider value.EqOp: The equality of two values of one type, as the operationsmt.eq.ImpliesOp: The implication from the first Boolean formula to the second, as the operationsmt.implies.NotOp: The negation of a Boolean formula, as the operationsmt.not.OrOp: The disjunction of two Boolean formulas, as the operationsmt.or.VarOp: A named variable of a given type: the operationsmt.var, with one result and the variable's name in itsnameattribute.
Values and defaults
Public values and defaults.
ApplyOp.operation_name: The full name of the operation,smt.apply.ApplyOp.operation_spec: The specification ofsmt.apply: its full name, its one attribute keyname, and its side effects declared as unknown.ArraySelectOp.operation_name: The full name of the operation,smt.array.select.ArraySelectOp.operation_spec: The specification ofsmt.array.select: its full name and its side effects declared as unknown.ArrayStoreOp.operation_name: The full name of the operation,smt.array.store.ArrayStoreOp.operation_spec: The specification ofsmt.array.store: its full name and its side effects declared as unknown.AssertOp.operation_name: The full name of the operation,smt.assert.AssertOp.operation_spec: The specification ofsmt.assert: its full name, its attribute keysnameandgroup, its side effects declared as unknown, and the rule that its operand is Boolean.BitVecConstOp.operation_name: The full name of the operation,smt.bv.const.BitVecConstOp.operation_spec: The specification ofsmt.bv.const: its full name, its one attribute keyvalue, and its side effects declared as unknown.BoolConstOp.operation_name: The full name of the operation,smt.bool.const.BoolConstOp.operation_spec: The specification ofsmt.bool.const: its full name, its one attribute keyvalue, and its side effects declared as unknown.BvConcatOp.operation_name: The full name of the operation,smt.bvconcat, so code that walks operations can compare each operation's name with this constant to find the concatenations.BvConcatOp.operation_spec: The specification ofsmt.bvconcat: its full name and its side effects declared as unknown.BvExtractOp.operation_name: The full name of the operation,smt.bvextract, so code that walks operations can compare each operation's name with this constant to find the extractions.BvExtractOp.operation_spec: The specification ofsmt.bvextract: its full name, its attribute keyshighandlow, and its side effects declared as unknown.BvRotlOp.operation_name: The full name of the operation,smt.bvrotl, so code that walks operations can compare each operation's name with this constant to find the left rotations.BvRotlOp.operation_spec: The specification ofsmt.bvrotl: its full name, its one attribute keyamount, its side effects declared as unknown, and the rule that the operand and the result share one type.BvRotrOp.operation_name: The full name of the operation,smt.bvrotr, so code that walks operations can compare each operation's name with this constant to find the right rotations.BvRotrOp.operation_spec: The specification ofsmt.bvrotr: its full name, its one attribute keyamount, its side effects declared as unknown, and the rule that the operand and the result share one type.BvSignExtOp.operation_name: The full name of the operation,smt.bvsignext.BvSignExtOp.operation_spec: The specification ofsmt.bvsignext: its full name, its one attribute keyextra, and its side effects declared as unknown.BvZeroExtOp.operation_name: The full name of the operation,smt.bvzeroext, so code that walks operations can compare each operation's name with this constant to find the zero extensions.BvZeroExtOp.operation_spec: The specification ofsmt.bvzeroext: its full name, its one attribute keyextra, and its side effects declared as unknown.VarOp.operation_name: The full name of the operation,smt.var.VarOp.operation_spec: The specification ofsmt.var: its full name, its one attribute keyname, and its side effects declared as unknown.name: The dialect's name,smt.spec: The dialect's specification: its name, its 42 operations, its three type names, and the function that reads the widths of the bit-vector and array types.
Source
Source: lib/smt/src/choir/dialect.zig:65
zig
/// The SMT dialect: its name, its specification, one struct per operation, and the functions that/// build and read its types. A caller names every operation, type getter and width reader of the/// dialect through this struct, and names the dialect by its `name` when it asks a context to load/// it. Its 42 operation structs are the declarations that end in `Op`. The namespace re-exports it/// as `SmtDialect`.pub const SmtDialect = struct { /// The dialect's name, `smt`. A caller asks a context for the dialect by this name after /// registering it. Every operation name and type name of the dialect starts with `smt.`. /// `registry` records the dialect's loader under this name. pub const name = "smt"; /// The dialect's specification: its name, its 42 operations, its three type names, and the /// function that reads the widths of the bit-vector and array types. `loadDialect` and the type /// getters load the dialect into a context from this value, and a test loads it directly. The /// list of operations comes from every declaration of `SmtDialect` that carries an operation /// specification. Loading it a second time into one context returns at once and leaves the /// context as it was. pub const spec = ir.dialects.DialectSpec{ .name = name, .operations = ir.dialects.operations(@This()), .types = &.{ ir.dialects.typeName(type_names.boolean), ir.dialects.typeName(type_names.bv), ir.dialects.typeName(type_names.array), }, .type_interface_fallbacks = &.{ .{ .id = interfaces.TypeParamInterface.id, .fallback = typeParamFallback }, }, }; const same_operands_and_result_type_trait = ir.dialects.trait(ir.traits.SameOperandsAndResultType); const same_type_operands_trait = ir.dialects.trait(ir.traits.SameTypeOperands); const operand0_is_bool_constraint = ir.dialects.typeConstraint.exact(0, type_names.boolean); const operand1_is_bool_constraint = ir.dialects.typeConstraint.exact(1, type_names.boolean); const result0_is_bool_constraint = ir.dialects.typeConstraint.exact(0, type_names.boolean); const op_specs = ir.dialects.opSpec.dialect(@This()); const op_templates = ir.dialects.operationTemplate.dialect(@This()); /// The width of a bit-vector type, parsed from the decimal text the type carries. `bitVecWidth` /// reads a bit-vector type's width through this struct, and a caller reads the width through /// `bitVecWidth`. The context parses it the first time a caller asks for the type's width and /// keeps it for the type. The struct holds one field, `width`, the number of bits, at least 1. /// A type whose text is empty, fails to parse as a decimal number or is 0 gets no payload, so /// `bitVecWidth` returns `null` for it. pub const BitVecTypePayload = struct { width: u32, }; /// The index width and the element width of an array type, parsed from the text `index:element` /// the type carries. `arrayShape` returns an array type's two widths in this struct, and a /// caller reads the widths from it to pick the sort of an array. The context parses it the /// first time a caller asks for the type's widths and keeps it for the type. An array type maps /// bit-vectors of the index width to bit-vectors of the element width. A type whose text /// differs from two decimal numbers joined by one colon, or holds a 0, gets no payload, so /// `arrayShape` returns `null` for it. pub const ArrayTypePayload = struct { /// The width in bits of the bit-vectors that index the array, at least 1. /// `ArraySelectOp.create` and `ArrayStoreOp.create` require an index of this width. A /// caller reads it to build the array's sort. index_width: u32, /// The width in bits of the bit-vectors the array holds, at least 1. `ArraySelectOp.create` /// gives its result this width, and `ArrayStoreOp.create` requires a stored value of this /// width. A caller reads it to build the array's sort. element_width: u32, }; const type_param_vtable = interfaces.TypeParamInterface.VTable{ .parse = parseTypeParams, }; /// A named variable of a given type: the operation `smt.var`, with one result and the /// variable's name in its `name` attribute. A checker declares each free variable of its /// formula, such as a parameter or an input of the program it checks, as one of these /// operations. The struct holds the operation in `op`, creates it with `create`, and reads it /// with `getResult` and `getName`. pub const VarOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the variable through the getters. op: *ir.Operation, /// The specification of `smt.var`: its full name, its one attribute key `name`, and its /// side effects declared as unknown. The dialect's specification gathers this value, so /// loading the dialect registers the operation with the context. `SmtDialect.spec` lists it /// among the dialect's operations. pub const operation_spec = op_specs.define(.{ .mnemonic = "var", .attrs = &.{attr_names.name}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.var`. Code that walks operations compares each /// operation's name with this constant to find the variables. `create` creates operations /// under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.var` operation named `symbol`, with one result of type `result_type` and /// the source location `loc`, and returns the struct holding it. A checker calls it once /// for each parameter or input of the program it checks, with the type of that parameter or /// input. The context keeps its own copy of `symbol`. Neither the call nor /// `ir.verifyOperation` checks `result_type`, and a variable may have any type of the /// context, the dialect's three types included. The new operation belongs to no block until /// the caller adds `op` to one. The call returns the errors of creating the operation and /// its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, symbol: []const u8, result_type: ir.Type) !VarOp { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addTypes(&.{result_type}); const op = try builder.create(state); try op.setAttr(attr_names.name, try ctx.getStringAttr(symbol)); return .{ .op = op }; } /// Returns the operation's one result, the variable's value. A caller passes the variable's /// value to the operations that use the variable. pub fn getResult(self: *const VarOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the variable's name from its `name` attribute. Code that reads a formula back /// turns each variable into a named constant of the solver under this name. The slice /// points into the context's copy of the name. The call returns `null` when the operation /// lacks a string attribute under `name`. pub fn getName(self: VarOp) ?[]const u8 { const attr = self.op.getAttrAs(ir.Attribute.StringAttr, attr_names.name) orelse return null; return attr.getValue(); } }; /// A Boolean constant: the operation `smt.bool.const`, with one Boolean result and the value in /// its `value` attribute. A checker states a fixed truth value, for example an obligation that /// holds for every input, as one of these operations. The struct holds the operation in `op`, /// creates it with `create`, and reads it with `getResult` and `getValue`. pub const BoolConstOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to inspect the /// operation or add it to a block. A caller that finds an operation named `operation_name` /// builds the struct around it and reads the constant through `getValue`. op: *ir.Operation, /// The specification of `smt.bool.const`: its full name, its one attribute key `value`, and /// its side effects declared as unknown. The dialect's specification gathers this value, so /// loading the dialect registers the operation with the context. `SmtDialect.spec` lists it /// among the dialect's operations. pub const operation_spec = op_specs.define(.{ .mnemonic = "bool.const", .attrs = &.{attr_names.value}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bool.const`. Code that walks operations compares /// each operation's name with this constant to find the Boolean constants. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bool.const` operation holding `value`, with one result of the Boolean /// type and the source location `loc`, and returns the struct holding it. A checker creates /// the constant `true` or `false` for an obligation whose answer it knows while it builds /// the formula. The call builds the Boolean type first, which loads the dialect into `ctx` /// when unloaded. The new operation belongs to no block until the caller adds `op` to one. /// The call returns the errors of `getBoolType` and of creating the operation and its /// attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, value: bool) !BoolConstOp { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addTypes(&.{try getBoolType(ctx)}); const op = try builder.create(state); try op.setAttr(attr_names.value, try ctx.getBoolAttr(value)); return .{ .op = op }; } /// Returns the operation's one result, the constant's value. A caller passes the constant's /// value to the operations that use it. pub fn getResult(self: *const BoolConstOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the constant from its `value` attribute. Code that reads a formula back turns /// each Boolean constant into the solver's `true` or `false`. The call returns `null` when /// the operation lacks a Boolean attribute under `value`. pub fn getValue(self: BoolConstOp) ?bool { const attr = self.op.getAttrAs(ir.Attribute.BoolAttr, attr_names.value) orelse return null; return attr.getValue(); } }; /// A bit-vector constant: the operation `smt.bv.const`, with one result of a bit-vector type /// and the value in its `value` attribute as decimal text. A checker states a fixed bit-vector, /// such as a literal of the program it checks or a limit to compare against, as one of these /// operations. The struct holds the operation in `op`, creates it with `create`, and reads it /// with `getResult` and `getValue`. The width lives in the result's type, and the attribute /// holds only the value. pub const BitVecConstOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the constant through `getValue`. op: *ir.Operation, /// The specification of `smt.bv.const`: its full name, its one attribute key `value`, and /// its side effects declared as unknown. The dialect's specification gathers this value, so /// loading the dialect registers the operation with the context. `SmtDialect.spec` lists it /// among the dialect's operations. pub const operation_spec = op_specs.define(.{ .mnemonic = "bv.const", .attrs = &.{attr_names.value}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bv.const`. Code that walks operations compares each /// operation's name with this constant to find the bit-vector constants. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bv.const` operation holding `value`, with one result of the bit-vector /// type of width `width` and the source location `loc`, and returns the struct holding it. /// A checker creates one constant for each literal of the program it checks, and for limits /// such as the width of a shifted value. The call writes `value` as decimal text into the /// `value` attribute. The call builds the bit-vector type first, which loads the dialect /// into `ctx` when unloaded. The call checks neither that `value` fits in `width` bits nor /// that `width` is above 0. The new operation belongs to no block until the caller adds /// `op` to one. The call returns the errors of building the type and of creating the /// operation and its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, width: u32, value: u128) !BitVecConstOp { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addTypes(&.{try getBitVecType(ctx, width)}); const op = try builder.create(state); var buf: [40]u8 = undefined; try op.setAttr(attr_names.value, try ctx.getStringAttr(try std.fmt.bufPrint(&buf, "{d}", .{value}))); return .{ .op = op }; } /// Returns the operation's one result, the constant's value. A caller passes the constant's /// value to the operations that use it. pub fn getResult(self: *const BitVecConstOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the constant, parsed from the decimal text of its `value` attribute. Code that /// reads a formula back turns each bit-vector constant into a solver constant of the /// result's width. The call returns `null` when the operation lacks a string attribute /// under `value` or its text fails to parse as a decimal number that fits in a `u128`. pub fn getValue(self: BitVecConstOp) ?u128 { const attr = self.op.getAttrAs(ir.Attribute.StringAttr, attr_names.value) orelse return null; return std.fmt.parseInt(u128, attr.getValue(), 10) catch null; } }; /// The application of a function to the operation's operands: the operation `smt.apply`, with /// one result and the function's name in its `name` attribute. A checker states facts about a /// function it leaves undefined by applying the function by name to its arguments. The dialect /// declares no function: the operand types are the argument types, and the result's type is the /// function's result type. The struct holds the operation in `op`, creates it with `create`, /// and reads it with `getResult` and `getName`. pub const ApplyOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the function's name through `getName`. op: *ir.Operation, /// The specification of `smt.apply`: its full name, its one attribute key `name`, and its /// side effects declared as unknown. The dialect's specification gathers this value, so /// loading the dialect registers the operation with the context. `SmtDialect.spec` lists it /// among the dialect's operations. pub const operation_spec = op_specs.define(.{ .mnemonic = "apply", .attrs = &.{attr_names.name}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.apply`. Code that walks operations compares each /// operation's name with this constant to find the function applications. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.apply` operation that applies the function `function_name` to /// `operands`, with one result of type `result_type` and the source location `loc`, and /// returns the struct holding it. A checker applies a function it leaves undefined to the /// values it passes as arguments. The operation copies `operands`, and the context keeps /// its own copy of `function_name`. The call checks no types, and `operands` may be empty. /// The new operation belongs to no block until the caller adds `op` to one. The call /// returns the errors of creating the operation and its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, function_name: []const u8, operands: []const *ir.Value, result_type: ir.Type) !ApplyOp { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(operands); state.addTypes(&.{result_type}); const op = try builder.create(state); try op.setAttr(attr_names.name, try ctx.getStringAttr(function_name)); return .{ .op = op }; } /// Returns the operation's one result, the value of the application. A caller passes the /// application's value to the operations that use it. pub fn getResult(self: *const ApplyOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the name of the applied function from the `name` attribute. Code that reads a /// formula back declares an uninterpreted function under this name. The slice points into /// the context's copy of the name. The call returns `null` when the operation lacks a /// string attribute under `name`. pub fn getName(self: ApplyOp) ?[]const u8 { const attr = self.op.getAttrAs(ir.Attribute.StringAttr, attr_names.name) orelse return null; return attr.getValue(); } }; /// A Boolean formula that must hold: the operation `smt.assert`, with one Boolean operand, no /// result, and an optional name and group label in its `name` and `group` attributes. A checker /// states each property it must check as one assertion, names it after the check, and gives it /// a group label so that a report can sort the answers. The struct holds the operation in `op`, /// creates it with `create`, `createNamed` or `createGrouped`, and reads it with /// `getAssertion`, `getName` and `getGroup`. A caller keeps a list of the assertion operations, /// and code that reads the formula back walks that list. pub const AssertOp = struct { /// The operation the struct holds. `create` sets it, and a caller appends it to its list of /// assertions. A caller that reads its list back builds the struct around each operation /// named `operation_name`. op: *ir.Operation, /// The specification of `smt.assert`: its full name, its attribute keys `name` and `group`, /// its side effects declared as unknown, and the rule that its operand is Boolean. /// `SmtDialect.spec` lists it among the dialect's operations, so loading the dialect /// registers the operation with the context, and `ir.verifyOperation` checks the operand /// rule. pub const operation_spec = op_specs.define(.{ .mnemonic = "assert", .attrs = &.{ attr_names.name, attr_names.group }, .interfaces = &.{unknown_effects}, .operand_types = &.{operand0_is_bool_constraint}, }); /// The full name of the operation, `smt.assert`. Code that reads a list of assertions back /// checks each operation's name against this constant before it builds the struct. `create` /// creates operations under this name. A caller refuses an operation of another name in its /// list of assertions. pub const operation_name = operation_spec.name; /// Creates an `smt.assert` operation on `assertion` at the source location `loc`, leaves /// its name and group label unset, and returns the struct holding it. A caller asserts a /// background fact that its reports leave unnamed, such as an equation that fixes a /// variable to a witness value. The call checks no type, and `ir.verifyOperation` checks /// that `assertion` is Boolean. The new operation belongs to no block until the caller adds /// `op` to one. The call returns the errors of creating the operation in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, assertion: *ir.Value) !AssertOp { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{assertion}); const op = try builder.create(state); return .{ .op = op }; } /// Creates an `smt.assert` operation on `assertion` with the name `assertion_name`, and /// leaves its group label unset. A caller asserts a formula under a name, so a report can /// say which assertion an answer concerns. The call calls `create` and then sets the `name` /// attribute. The context keeps its own copy of `assertion_name`. The call returns the /// errors of `create` and of setting the attribute in `ctx`. pub fn createNamed(ctx: *ir.Context, loc: ir.Location, assertion_name: []const u8, assertion: *ir.Value) !AssertOp { const op = try create(ctx, loc, assertion); try op.op.setAttr(attr_names.name, try ctx.getStringAttr(assertion_name)); return op; } /// Creates an `smt.assert` operation on `assertion` with the name `assertion_name` and the /// group label `group`. A checker asserts each property under a name and a group label such /// as `bounds` or `overflow`, so a report can sort the answers by kind. The call calls /// `createNamed` and then sets the `group` attribute. The context keeps its own copies of /// `assertion_name` and `group`. The call returns the errors of `createNamed` and of /// setting the attribute in `ctx`. pub fn createGrouped(ctx: *ir.Context, loc: ir.Location, assertion_name: []const u8, group: []const u8, assertion: *ir.Value) !AssertOp { const op = try createNamed(ctx, loc, assertion_name, assertion); try op.op.setAttr(attr_names.group, try ctx.getStringAttr(group)); return op; } /// Returns the operation's one operand, the asserted formula, so code that reads the /// assertions back turns each asserted formula into a solver term. On an operation with no /// operand, the call panics in Debug and ReleaseSafe builds, and its behavior is undefined /// in ReleaseFast and ReleaseSmall builds. pub fn getAssertion(self: AssertOp) *ir.Value { return self.op.getOperand(0).?; } /// Returns the assertion's name from its `name` attribute, so code that reports an answer /// names the assertion it concerns. The call returns `null` whenever the operation lacks a /// string attribute under `name`, such as for an assertion made by `create`. pub fn getName(self: AssertOp) ?[]const u8 { const attr = self.op.getAttrAs(ir.Attribute.StringAttr, attr_names.name) orelse return null; return attr.getValue(); } /// Returns the assertion's group label from its `group` attribute, so code that reports /// answers sorts them by this label. The call returns `null` whenever the operation lacks a /// string attribute under `group`, such as for an assertion made by `create` or /// `createNamed`. pub fn getGroup(self: AssertOp) ?[]const u8 { const attr = self.op.getAttrAs(ir.Attribute.StringAttr, attr_names.group) orelse return null; return attr.getValue(); } }; /// The negation of a Boolean formula, as the operation `smt.not`. A checker negates a formula, /// for example to turn a condition that must hold into a search for an input that breaks it. /// `create(ctx, loc, input)` creates the operation with one result of the Boolean type. /// `getResult` returns the result, and `getInput` returns the operand. `operation_name` holds /// `smt.not`, and the struct holds the operation in `op`. `create` checks no type, and /// `ir.verifyOperation` checks the rule the dialect registered: the operand and the result are /// Boolean. `create` returns the errors of building the Boolean type and of creating the /// operation in `ctx`. pub const NotOp: type = op_templates.unaryFixedResult( "not", boolUnaryOptions(.{}), getBoolType, ); /// The conjunction of two Boolean formulas, as the operation `smt.and`. A checker joins two /// conditions that must both hold. `create(ctx, loc, lhs, rhs)` creates the operation with one /// result of the Boolean type. `getResult` returns the result, and `getLhs` and `getRhs` return /// the operands. `operation_name` holds `smt.and`, and the struct holds the operation in `op`. /// `create` checks no types, and `ir.verifyOperation` checks the rule the dialect registered: /// both operands and the result are Boolean. The context records the operation as commutative. /// `create` returns the errors of building the Boolean type and of creating the operation in /// `ctx`. pub const AndOp: type = op_templates.binaryFixedResult( "and", boolBinaryOptions(commutative_op_traits), getBoolType, ); /// The disjunction of two Boolean formulas, as the operation `smt.or`. A checker joins two /// conditions of which one must hold. `create(ctx, loc, lhs, rhs)` creates the operation with /// one result of the Boolean type. `getResult` returns the result, and `getLhs` and `getRhs` /// return the operands. `operation_name` holds `smt.or`, and the struct holds the operation in /// `op`. `create` checks no types, and `ir.verifyOperation` checks the rule the dialect /// registered: both operands and the result are Boolean. The context records the operation as /// commutative. `create` returns the errors of building the Boolean type and of creating the /// operation in `ctx`. pub const OrOp: type = op_templates.binaryFixedResult( "or", boolBinaryOptions(commutative_op_traits), getBoolType, ); /// The implication from the first Boolean formula to the second, as the operation /// `smt.implies`. A checker states that one condition implies another, for example that a /// precondition implies a bound. `create(ctx, loc, lhs, rhs)` creates the operation with one /// result of the Boolean type. `getResult` returns the result, and `getLhs` and `getRhs` return /// the operands. `operation_name` holds `smt.implies`, and the struct holds the operation in /// `op`. `create` checks no types, and `ir.verifyOperation` checks the rule the dialect /// registered: both operands and the result are Boolean. `create` returns the errors of /// building the Boolean type and of creating the operation in `ctx`. pub const ImpliesOp: type = op_templates.binaryFixedResult( "implies", boolBinaryOptions(.{}), getBoolType, ); /// The equality of two values of one type, as the operation `smt.eq`. A checker states that two /// values are equal, for example that a variable holds a given constant or that a result is /// zero. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the Boolean /// type. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.eq`, and the struct holds the operation in `op`. The operands /// may be Boolean values, bit-vectors or arrays. `create` checks no types, and /// `ir.verifyOperation` checks the rule the dialect registered: the two operands share one type /// and the result is Boolean. The context records the operation as commutative. `create` /// returns the errors of building the Boolean type and of creating the operation in `ctx`. pub const EqOp: type = op_templates.binaryFixedResult( "eq", sameOperandBoolResultOptions(commutative_op_traits), getBoolType, ); /// The element of an array at an index, as the operation `smt.array.select`, with the array and /// the index as operands and one result of the element type. A checker reads a memory modeled /// as an array at an index. The struct holds the operation in `op`, creates it with `create`, /// and returns the result with `getResult`. pub const ArraySelectOp = struct { op: *ir.Operation, /// The specification of `smt.array.select`: its full name and its side effects declared as /// unknown. `SmtDialect.spec` lists it among the dialect's operations, so loading the /// dialect registers the operation with the context. The specification registers no type /// rule, so `create` alone checks the operand types. pub const operation_spec = op_specs.define(.{ .mnemonic = "array.select", .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.array.select`. Code that walks operations compares /// each operation's name with this constant to find the array reads. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.array.select` operation that reads `array` at `index` at the source /// location `loc`, and returns the struct holding it. A checker reads an element of a /// modeled memory and gets a value of the element's width. The result is a bit-vector of /// the array's element width. The call returns `error.UnsupportedArrayType` when /// `arrayShape` returns `null` for the type of `array`, `error.UnsupportedBitVectorType` /// when `bitVecWidth` returns `null` for the type of `index`, and /// `error.InvalidArrayIndexType` when the index width differs from the array's index width. /// The new operation belongs to no block until the caller adds `op` to one. The call also /// returns the errors of building the element type and of creating the operation in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, array: *ir.Value, index: *ir.Value) !ArraySelectOp { const shape = arrayShape(ctx, array.type) orelse return error.UnsupportedArrayType; const index_width = bitVecWidth(ctx, index.type) orelse return error.UnsupportedBitVectorType; if (index_width != shape.index_width) return error.InvalidArrayIndexType; return .{ .op = try binary(ctx, loc, operation_name, array, index, try getBitVecType(ctx, shape.element_width)) }; } /// Returns the operation's one result, the element read, so a caller passes it to the /// operations that use it. pub fn getResult(self: *const ArraySelectOp) *ir.Value { return self.op.getResult(0).?; } }; /// The array equal to a given array except that one index holds a new value, as the operation /// `smt.array.store`, with the array, the index and the value as operands and one result of the /// array's type. A checker writes an element into a memory modeled as an array and gets the new /// array. The struct holds the operation in `op`, creates it with `create`, and returns the /// result with `getResult`. pub const ArrayStoreOp = struct { op: *ir.Operation, /// The specification of `smt.array.store`: its full name and its side effects declared as /// unknown. `SmtDialect.spec` lists it among the dialect's operations, so loading the /// dialect registers the operation with the context. The specification registers no type /// rule, so `create` alone checks the operand types. pub const operation_spec = op_specs.define(.{ .mnemonic = "array.store", .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.array.store`. Code that walks operations compares /// each operation's name with this constant to find the array writes. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.array.store` operation that writes `value` into `array` at `index` at /// the source location `loc`, and returns the struct holding it. A checker records a write /// into a modeled memory as a new array value. The result has the type of `array`. The call /// returns `error.UnsupportedArrayType` when `arrayShape` returns `null` for the type of /// `array`, and `error.UnsupportedBitVectorType` when `bitVecWidth` returns `null` for the /// type of `index` or of `value`. The call returns `error.InvalidArrayIndexType` when the /// index width differs from the array's index width, and `error.InvalidArrayElementType` /// when the value's width differs from the array's element width. The new operation belongs /// to no block until the caller adds `op` to one. The call also returns the errors of /// creating the operation in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, array: *ir.Value, index: *ir.Value, value: *ir.Value) !ArrayStoreOp { const shape = arrayShape(ctx, array.type) orelse return error.UnsupportedArrayType; const index_width = bitVecWidth(ctx, index.type) orelse return error.UnsupportedBitVectorType; const element_width = bitVecWidth(ctx, value.type) orelse return error.UnsupportedBitVectorType; if (index_width != shape.index_width) return error.InvalidArrayIndexType; if (element_width != shape.element_width) return error.InvalidArrayElementType; var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{ array, index, value }); state.addTypes(&.{array.type}); const op = try builder.create(state); return .{ .op = op }; } /// Returns the operation's one result, the array after the write, so a caller passes it to /// the operations that read or write it next. pub fn getResult(self: *const ArrayStoreOp) *ir.Value { return self.op.getResult(0).?; } }; /// The sum of two bit-vectors of one width, modulo 2 to the power of the width, as the /// operation `smt.bvadd`. A checker lowers a program's addition into this operation. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of `lhs`. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvadd`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. The context records the operation as commutative. /// `create` returns the errors of creating the operation in `ctx`. pub const BvAddOp: type = op_templates.binarySameType( "bvadd", sameOperandsResultOptions(commutative_op_traits), ); /// The difference of two bit-vectors of one width, the first minus the second, modulo 2 to the /// power of the width, as the operation `smt.bvsub`. A checker lowers a program's subtraction /// into this operation. `create(ctx, loc, lhs, rhs)` creates the operation with one result of /// the type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the /// operands. `operation_name` holds `smt.bvsub`, and the struct holds the operation in `op`. /// `create` checks no types, and `ir.verifyOperation` checks the rule the dialect registered: /// both operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvSubOp: type = op_templates.binarySameType("bvsub", sameOperandsResultOptions(.{})); /// The product of two bit-vectors of one width, modulo 2 to the power of the width, as the /// operation `smt.bvmul`. A checker lowers a program's multiplication into this operation. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of `lhs`. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvmul`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. The context records the operation as commutative. /// `create` returns the errors of creating the operation in `ctx`. pub const BvMulOp: type = op_templates.binarySameType( "bvmul", sameOperandsResultOptions(commutative_op_traits), ); /// The bitwise complement of a bit-vector, as the operation `smt.bvnot`. A checker complements /// every bit of a bit-vector. `create(ctx, loc, input)` creates the operation with one result /// of the type of `input`. `getResult` returns the result, and `getInput` returns the operand. /// `operation_name` holds `smt.bvnot`, and the struct holds the operation in `op`. `create` /// checks no type, and `ir.verifyOperation` checks the rule the dialect registered: the operand /// and the result share one type. `create` returns the errors of creating the operation in /// `ctx`. pub const BvNotOp: type = op_templates.unarySameType("bvnot", sameOperandsResultOptions(.{})); /// The bitwise and of two bit-vectors of one width, as the operation `smt.bvand`. A checker /// masks the bits of a bit-vector. `create(ctx, loc, lhs, rhs)` creates the operation with one /// result of the type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` /// return the operands. `operation_name` holds `smt.bvand`, and the struct holds the operation /// in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule the dialect /// registered: both operands and the result share one type. The context records the operation /// as commutative. `create` returns the errors of creating the operation in `ctx`. pub const BvAndOp: type = op_templates.binarySameType( "bvand", sameOperandsResultOptions(commutative_op_traits), ); /// The bitwise or of two bit-vectors of one width, as the operation `smt.bvor`. A checker sets /// bits of a bit-vector. `create(ctx, loc, lhs, rhs)` creates the operation with one result of /// the type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the /// operands. `operation_name` holds `smt.bvor`, and the struct holds the operation in `op`. /// `create` checks no types, and `ir.verifyOperation` checks the rule the dialect registered: /// both operands and the result share one type. The context records the operation as /// commutative. `create` returns the errors of creating the operation in `ctx`. pub const BvOrOp: type = op_templates.binarySameType( "bvor", sameOperandsResultOptions(commutative_op_traits), ); /// The bitwise exclusive or of two bit-vectors of one width, as the operation `smt.bvxor`. A /// checker flips bits of a bit-vector. `create(ctx, loc, lhs, rhs)` creates the operation with /// one result of the type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` /// return the operands. `operation_name` holds `smt.bvxor`, and the struct holds the operation /// in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule the dialect /// registered: both operands and the result share one type. The context records the operation /// as commutative. `create` returns the errors of creating the operation in `ctx`. pub const BvXorOp: type = op_templates.binarySameType( "bvxor", sameOperandsResultOptions(commutative_op_traits), ); /// The first bit-vector shifted toward its most significant bit by the value of the second, as /// the operation `smt.bvshl`. A checker lowers a program's left shift into this operation. The /// shift amount is a bit-vector of the shifted value's width. `create(ctx, loc, lhs, rhs)` /// creates the operation with one result of the type of `lhs`. `getResult` returns the result, /// and `getLhs` and `getRhs` return the operands. `operation_name` holds `smt.bvshl`, and the /// struct holds the operation in `op`. `create` checks no types, and `ir.verifyOperation` /// checks the rule the dialect registered: both operands and the result share one type. /// `create` returns the errors of creating the operation in `ctx`. pub const BvShlOp: type = op_templates.binarySameType("bvshl", sameOperandsResultOptions(.{})); /// The first bit-vector shifted toward its least significant bit by the value of the second, /// with zeros shifted in, as the operation `smt.bvlshr`. A checker lowers a program's unsigned /// right shift into this operation. The shift amount is a bit-vector of the shifted value's /// width. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of /// `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvlshr`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvLshrOp: type = op_templates.binarySameType( "bvlshr", sameOperandsResultOptions(.{}), ); /// The first bit-vector shifted toward its least significant bit by the value of the second, /// with copies of its sign bit shifted in, as the operation `smt.bvashr`. A checker lowers a /// program's signed right shift into this operation. The shift amount is a bit-vector of the /// shifted value's width. `create(ctx, loc, lhs, rhs)` creates the operation with one result of /// the type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the /// operands. `operation_name` holds `smt.bvashr`, and the struct holds the operation in `op`. /// `create` checks no types, and `ir.verifyOperation` checks the rule the dialect registered: /// both operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvAshrOp: type = op_templates.binarySameType( "bvashr", sameOperandsResultOptions(.{}), ); /// The rotation of a bit-vector toward its most significant bit by a fixed number of positions, /// as the operation `smt.bvrotl`, with the number of positions in its `amount` attribute, for a /// checker that rotates a bit-vector left. The result has the operand's type. The struct holds /// the operation in `op`, creates it with `create`, and reads it with `getResult` and /// `getAmount`. pub const BvRotlOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the rotation amount through `getAmount`. op: *ir.Operation, /// The specification of `smt.bvrotl`: its full name, its one attribute key `amount`, its /// side effects declared as unknown, and the rule that the operand and the result share one /// type. `SmtDialect.spec` lists it among the dialect's operations, so loading the dialect /// registers the operation with the context, and `ir.verifyOperation` checks the type rule. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvrotl", .attrs = &.{attr_names.amount}, .interfaces = &.{unknown_effects}, .dynamic_traits = &.{same_operands_and_result_type_trait}, }); /// The full name of the operation, `smt.bvrotl`, so code that walks operations can compare /// each operation's name with this constant to find the left rotations. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvrotl` operation that rotates `operand` by `amount` positions at the /// source location `loc`, and returns the struct holding it, so a checker can rotate a /// value left by the number of positions it knows when it builds the formula. The call /// returns `error.UnsupportedBitVectorType` when `bitVecWidth` returns `null` for the type /// of `operand`. The call accepts an `amount` at or above the operand's width. The new /// operation belongs to no block until the caller adds `op` to one. The call also returns /// the errors of creating the operation and its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, operand: *ir.Value, amount: u32) !BvRotlOp { return .{ .op = try rotate(ctx, loc, operation_name, operand, amount) }; } /// Returns the operation's one result, the rotated value, so a caller can pass it to the /// operations that use it. pub fn getResult(self: *const BvRotlOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the rotation amount from the `amount` attribute, so code that reads a formula /// back can build the solver's rotation term. The call returns `null` when the operation /// lacks an integer attribute under `amount` or its value lies outside the range of a /// `u32`. pub fn getAmount(self: BvRotlOp) ?u32 { return getU32Attr(self.op, attr_names.amount); } }; /// The rotation of a bit-vector toward its least significant bit by a fixed number of /// positions, as the operation `smt.bvrotr`, with the number of positions in its `amount` /// attribute, for a checker that rotates a bit-vector right. The result has the operand's type. /// The struct holds the operation in `op`, creates it with `create`, and reads it with /// `getResult` and `getAmount`. pub const BvRotrOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the rotation amount through `getAmount`. op: *ir.Operation, /// The specification of `smt.bvrotr`: its full name, its one attribute key `amount`, its /// side effects declared as unknown, and the rule that the operand and the result share one /// type. `SmtDialect.spec` lists it among the dialect's operations, so loading the dialect /// registers the operation with the context, and `ir.verifyOperation` checks the type rule. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvrotr", .attrs = &.{attr_names.amount}, .interfaces = &.{unknown_effects}, .dynamic_traits = &.{same_operands_and_result_type_trait}, }); /// The full name of the operation, `smt.bvrotr`, so code that walks operations can compare /// each operation's name with this constant to find the right rotations. `create` creates /// operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvrotr` operation that rotates `operand` by `amount` positions at the /// source location `loc`, and returns the struct holding it, so a checker can rotate a /// value right by the number of positions it knows when it builds the formula. The call /// returns `error.UnsupportedBitVectorType` when `bitVecWidth` returns `null` for the type /// of `operand`. The call accepts an `amount` at or above the operand's width. The new /// operation belongs to no block until the caller adds `op` to one. The call also returns /// the errors of creating the operation and its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, operand: *ir.Value, amount: u32) !BvRotrOp { return .{ .op = try rotate(ctx, loc, operation_name, operand, amount) }; } /// Returns the operation's one result, the rotated value, so a caller can pass it to the /// operations that use it. pub fn getResult(self: *const BvRotrOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the rotation amount from the `amount` attribute, so code that reads a formula /// back can build the solver's rotation term. The call returns `null` when the operation /// lacks an integer attribute under `amount` or its value lies outside the range of a /// `u32`. pub fn getAmount(self: BvRotrOp) ?u32 { return getU32Attr(self.op, attr_names.amount); } }; /// The unsigned quotient of the first bit-vector by the second, as the operation `smt.bvudiv`, /// for a checker lowering a program's unsigned division into this operation. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of `lhs`. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvudiv`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvUdivOp: type = op_templates.binarySameType( "bvudiv", sameOperandsResultOptions(.{}), ); /// The unsigned remainder of the first bit-vector by the second, as the operation `smt.bvurem`, /// for a checker taking the unsigned remainder of a program's division. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of `lhs`. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvurem`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvUremOp: type = op_templates.binarySameType( "bvurem", sameOperandsResultOptions(.{}), ); /// The two's-complement quotient of the first bit-vector by the second, rounded toward zero, as /// the operation `smt.bvsdiv`, for a checker lowering a program's signed division into this /// operation. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of /// `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvsdiv`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvSdivOp: type = op_templates.binarySameType( "bvsdiv", sameOperandsResultOptions(.{}), ); /// The remainder of the first bit-vector by the second with the sign of the first, as the /// operation `smt.bvsrem`, for a checker taking the signed remainder of a program's division. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the type of `lhs`. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvsrem`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: both /// operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvSremOp: type = op_templates.binarySameType( "bvsrem", sameOperandsResultOptions(.{}), ); /// The modulo of the first bit-vector by the second with the sign of the second, as the /// operation `smt.bvsmod`, for a checker taking a signed modulo whose result follows the /// divisor's sign. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the /// type of `lhs`. `getResult` returns the result, and `getLhs` and `getRhs` return the /// operands. `operation_name` holds `smt.bvsmod`, and the struct holds the operation in `op`. /// `create` checks no types, and `ir.verifyOperation` checks the rule the dialect registered: /// both operands and the result share one type. `create` returns the errors of creating the /// operation in `ctx`. pub const BvSmodOp: type = op_templates.binarySameType( "bvsmod", sameOperandsResultOptions(.{}), ); /// The bits of the first bit-vector above the bits of the second, as the operation /// `smt.bvconcat`, with one result whose width is the sum of the two widths, for a checker /// joining two bit-vectors into a wider one, for example two halves of a word. The struct holds /// the operation in `op`, creates it with `create`, and returns the result with `getResult`. pub const BvConcatOp = struct { op: *ir.Operation, /// The specification of `smt.bvconcat`: its full name and its side effects declared as /// unknown. `SmtDialect.spec` lists it among the dialect's operations, so loading the /// dialect registers the operation with the context. The specification registers no type /// rule, so `create` alone checks the operand types. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvconcat", .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bvconcat`, so code that walks operations can /// compare each operation's name with this constant to find the concatenations. `create` /// creates operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvconcat` operation that joins `lhs` above `rhs` at the source location /// `loc`, and returns the struct holding it, for a checker building a wider value from two /// parts with `lhs` as the high part. The result is a bit-vector whose width is the width /// of `lhs` plus the width of `rhs`. The call returns `error.UnsupportedBitVectorType` when /// `bitVecWidth` returns `null` for the type of either operand, and /// `error.InvalidBitVectorWidth` when the sum of the widths overflows a `u32`. The new /// operation belongs to no block until the caller adds `op` to one. The call also returns /// the errors of building the result type and of creating the operation in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, lhs: *ir.Value, rhs: *ir.Value) !BvConcatOp { const lhs_width = bitVecWidth(ctx, lhs.type) orelse return error.UnsupportedBitVectorType; const rhs_width = bitVecWidth(ctx, rhs.type) orelse return error.UnsupportedBitVectorType; const result_width = std.math.add(u32, lhs_width, rhs_width) catch return error.InvalidBitVectorWidth; return .{ .op = try binary(ctx, loc, operation_name, lhs, rhs, try getBitVecType(ctx, result_width)) }; } /// Returns the operation's one result, the joined value, so a caller can pass it to the /// operations that use it. pub fn getResult(self: *const BvConcatOp) *ir.Value { return self.op.getResult(0).?; } }; /// The bits of a bit-vector from a high position down to a low position, both included, as the /// operation `smt.bvextract`, with the positions in its `high` and `low` attributes, for a /// checker taking a range of bits from a bit-vector, for example to narrow a value to a smaller /// width. The result is a bit-vector of `high - low + 1` bits. The struct holds the operation /// in `op`, creates it with `create`, and reads it with `getResult`, `getHigh` and `getLow`. pub const BvExtractOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the positions through `getHigh` and `getLow`. op: *ir.Operation, /// The specification of `smt.bvextract`: its full name, its attribute keys `high` and /// `low`, and its side effects declared as unknown. `SmtDialect.spec` lists it among the /// dialect's operations, so loading the dialect registers the operation with the context. /// The specification registers no type rule, so `create` alone checks the operand type and /// the range. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvextract", .attrs = &.{ attr_names.high, attr_names.low }, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bvextract`, so code that walks operations can /// compare each operation's name with this constant to find the extractions. `create` /// creates operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvextract` operation that keeps the bits of `operand` from position /// `high` down to position `low` at the source location `loc`, and returns the struct /// holding it, so a checker can narrow a value to fewer bits by keeping its low bits, such /// as positions `7` down to `0`. Position 0 is the least significant bit. The result is a /// bit-vector of `high - low + 1` bits. The call returns `error.UnsupportedBitVectorType` /// when `bitVecWidth` returns `null` for the type of `operand`, and /// `error.InvalidBitVectorRange` when `low` is above `high` or `high` is at or above the /// operand's width. The new operation belongs to no block until the caller adds `op` to /// one. The call also returns the errors of building the result type and of creating the /// operation and its attributes in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, operand: *ir.Value, high: u32, low: u32) !BvExtractOp { const source_width = bitVecWidth(ctx, operand.type) orelse return error.UnsupportedBitVectorType; if (low > high or high >= source_width) return error.InvalidBitVectorRange; var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{operand}); state.addTypes(&.{try getBitVecType(ctx, high - low + 1)}); const op = try builder.create(state); try op.setAttr(attr_names.high, try ctx.getI64Attr(high)); try op.setAttr(attr_names.low, try ctx.getI64Attr(low)); return .{ .op = op }; } /// Returns the operation's one result, the extracted bits, so a caller can pass them to the /// operations that use them. pub fn getResult(self: *const BvExtractOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the highest bit position the extraction keeps, from the `high` attribute, so /// code that reads a formula back can build the solver's extraction term. The call returns /// `null` when the operation lacks an integer attribute under `high` or its value lies /// outside the range of a `u32`. pub fn getHigh(self: BvExtractOp) ?u32 { return getU32Attr(self.op, attr_names.high); } /// Returns the lowest bit position the extraction keeps, from the `low` attribute, so code /// that reads a formula back can build the solver's extraction term. The call returns /// `null` when the operation lacks an integer attribute under `low` or its value lies /// outside the range of a `u32`. pub fn getLow(self: BvExtractOp) ?u32 { return getU32Attr(self.op, attr_names.low); } }; /// A bit-vector widened by zero bits above its most significant bit, as the operation /// `smt.bvzeroext`, with the number of added bits in its `extra` attribute, for a checker /// widening an unsigned value to a larger width, for example before comparing it with a wider /// value. The result is a bit-vector of the operand's width plus `extra` bits. The struct holds /// the operation in `op`, creates it with `create`, and reads it with `getResult` and /// `getExtra`. pub const BvZeroExtOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the added width through `getExtra`. op: *ir.Operation, /// The specification of `smt.bvzeroext`: its full name, its one attribute key `extra`, and /// its side effects declared as unknown. `SmtDialect.spec` lists it among the dialect's /// operations, so loading the dialect registers the operation with the context. The /// specification registers no type rule, so `create` alone checks the operand type. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvzeroext", .attrs = &.{attr_names.extra}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bvzeroext`, so code that walks operations can /// compare each operation's name with this constant to find the zero extensions. `create` /// creates operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvzeroext` operation that adds `extra` zero bits above `operand` at the /// source location `loc`, and returns the struct holding it, so a checker can widen an /// unsigned value by the number of bits the target width needs. The result is a bit-vector /// of the operand's width plus `extra` bits, and an `extra` of 0 keeps the width. The call /// returns `error.UnsupportedBitVectorType` when `bitVecWidth` returns `null` for the type /// of `operand`, and `error.InvalidBitVectorWidth` when the new width overflows a `u32`. /// The new operation belongs to no block until the caller adds `op` to one. The call also /// returns the errors of building the result type and of creating the operation and its /// attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, operand: *ir.Value, extra: u32) !BvZeroExtOp { const source_width = bitVecWidth(ctx, operand.type) orelse return error.UnsupportedBitVectorType; const result_width = std.math.add(u32, source_width, extra) catch return error.InvalidBitVectorWidth; var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{operand}); state.addTypes(&.{try getBitVecType(ctx, result_width)}); const op = try builder.create(state); try op.setAttr(attr_names.extra, try ctx.getI64Attr(extra)); return .{ .op = op }; } /// Returns the operation's one result, the widened value, so a caller can pass it to the /// operations that use it. pub fn getResult(self: *const BvZeroExtOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the number of added bits from the `extra` attribute, so code that reads a /// formula back can build the solver's extension term. The call returns `null` when the /// operation lacks an integer attribute under `extra` or its value lies outside the range /// of a `u32`. pub fn getExtra(self: BvZeroExtOp) ?u32 { return getU32Attr(self.op, attr_names.extra); } }; /// A bit-vector widened by copies of its sign bit, as the operation `smt.bvsignext`, with the /// number of added bits in its `extra` attribute. A checker widens a signed value to a larger /// width and keeps its sign. The result is a bit-vector of the operand's width plus `extra` /// bits. The struct holds the operation in `op`, creates it with `create`, and reads it with /// `getResult` and `getExtra`. pub const BvSignExtOp = struct { /// The operation the struct holds. `create` sets it, and a caller reads it to add the /// operation to a block. A caller that finds an operation named `operation_name` builds the /// struct around it and reads the added width through `getExtra`. op: *ir.Operation, /// The specification of `smt.bvsignext`: its full name, its one attribute key `extra`, and /// its side effects declared as unknown. `SmtDialect.spec` lists it among the dialect's /// operations, so loading the dialect registers the operation with the context. The /// specification registers no type rule, so `create` alone checks the operand type. pub const operation_spec = op_specs.define(.{ .mnemonic = "bvsignext", .attrs = &.{attr_names.extra}, .interfaces = &.{unknown_effects}, }); /// The full name of the operation, `smt.bvsignext`. Code that walks operations compares /// each operation's name with this constant to find the sign extensions, and `create` /// creates operations under this name. pub const operation_name = operation_spec.name; /// Creates an `smt.bvsignext` operation that adds `extra` copies of the sign bit of /// `operand` above it at the source location `loc`, and returns the struct holding it. A /// checker widens a signed value by the number of bits the target width needs. The result /// is a bit-vector of the operand's width plus `extra` bits, and an `extra` of 0 keeps the /// width. The new operation belongs to no block until the caller adds `op` to one. The call /// returns `error.UnsupportedBitVectorType` when `bitVecWidth` returns `null` for the type /// of `operand`, and `error.InvalidBitVectorWidth` when the new width overflows a `u32`. /// The call also returns the errors of building the result type and of creating the /// operation and its attribute in `ctx`. pub fn create(ctx: *ir.Context, loc: ir.Location, operand: *ir.Value, extra: u32) !BvSignExtOp { const source_width = bitVecWidth(ctx, operand.type) orelse return error.UnsupportedBitVectorType; const result_width = std.math.add(u32, source_width, extra) catch return error.InvalidBitVectorWidth; var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{operand}); state.addTypes(&.{try getBitVecType(ctx, result_width)}); const op = try builder.create(state); try op.setAttr(attr_names.extra, try ctx.getI64Attr(extra)); return .{ .op = op }; } /// Returns the operation's one result, the widened value, so a caller can pass the value to /// the operations that use it. pub fn getResult(self: *const BvSignExtOp) *ir.Value { return self.op.getResult(0).?; } /// Returns the number of added bits from the `extra` attribute, so code that reads a /// formula back can build the solver's extension term. The call returns `null` when the /// operation lacks an integer attribute under `extra` or its value lies outside the range /// of a `u32`. pub fn getExtra(self: BvSignExtOp) ?u32 { return getU32Attr(self.op, attr_names.extra); } }; /// Whether the first bit-vector is less than the second as unsigned numbers, as the operation /// `smt.bvult`, for a checker that states an unsigned bound, for example that an index is below /// a length. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the Boolean /// type. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvult`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: the two /// operands share one type and the result is Boolean. `create` returns the errors of building /// the Boolean type and of creating the operation in `ctx`. pub const BvUltOp: type = op_templates.binaryFixedResult( "bvult", sameOperandBoolResultOptions(.{}), getBoolType, ); /// Whether the first bit-vector is at most the second as unsigned numbers, as the operation /// `smt.bvule`, for a checker that states an unsigned bound that includes its limit, for /// example that an end offset is at most a length. `create(ctx, loc, lhs, rhs)` creates the /// operation with one result of the Boolean type. `getResult` returns the result, and `getLhs` /// and `getRhs` return the operands. `operation_name` holds `smt.bvule`, and the struct holds /// the operation in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule /// the dialect registered: the two operands share one type and the result is Boolean. `create` /// returns the errors of building the Boolean type and of creating the operation in `ctx`. pub const BvUleOp: type = op_templates.binaryFixedResult( "bvule", sameOperandBoolResultOptions(.{}), getBoolType, ); /// Whether the first bit-vector is less than the second as two's-complement numbers, as the /// operation `smt.bvslt`, for a checker that states a signed bound, for example that a value is /// below a limit as a two's-complement number. `create(ctx, loc, lhs, rhs)` creates the /// operation with one result of the Boolean type. `getResult` returns the result, and `getLhs` /// and `getRhs` return the operands. `operation_name` holds `smt.bvslt`, and the struct holds /// the operation in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule /// the dialect registered: the two operands share one type and the result is Boolean. `create` /// returns the errors of building the Boolean type and of creating the operation in `ctx`. pub const BvSltOp: type = op_templates.binaryFixedResult( "bvslt", sameOperandBoolResultOptions(.{}), getBoolType, ); /// Whether the first bit-vector is at most the second as two's-complement numbers, as the /// operation `smt.bvsle`, for a checker that states a signed bound that includes its limit, for /// example that an offset is at least zero. `create(ctx, loc, lhs, rhs)` creates the operation /// with one result of the Boolean type. `getResult` returns the result, and `getLhs` and /// `getRhs` return the operands. `operation_name` holds `smt.bvsle`, and the struct holds the /// operation in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule the /// dialect registered: the two operands share one type and the result is Boolean. `create` /// returns the errors of building the Boolean type and of creating the operation in `ctx`. pub const BvSleOp: type = op_templates.binaryFixedResult( "bvsle", sameOperandBoolResultOptions(.{}), getBoolType, ); /// Whether the unsigned sum of two bit-vectors overflows their width, as the operation /// `smt.bvuaddo`, for a checker that asks whether a program's unsigned addition can overflow. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the Boolean type. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvuaddo`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: the two /// operands share one type and the result is Boolean. The context records the operation as /// commutative. Common subexpression elimination keeps two of these operations with swapped /// operands as two, because every SMT operation declares its side effects as unknown. `create` /// returns the errors of building the Boolean type and of creating the operation in `ctx`. pub const BvUaddoOp: type = op_templates.binaryFixedResult( "bvuaddo", sameOperandBoolResultOptions(commutative_op_traits), getBoolType, ); /// Whether the two's-complement sum of two bit-vectors overflows their width, as the operation /// `smt.bvsaddo`, for a checker that asks whether a program's signed addition can overflow. /// `create(ctx, loc, lhs, rhs)` creates the operation with one result of the Boolean type. /// `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvsaddo`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: the two /// operands share one type and the result is Boolean. The context records the operation as /// commutative. `create` returns the errors of building the Boolean type and of creating the /// operation in `ctx`. pub const BvSaddoOp: type = op_templates.binaryFixedResult( "bvsaddo", sameOperandBoolResultOptions(commutative_op_traits), getBoolType, ); /// Whether the two's-complement difference of two bit-vectors, the first minus the second, /// overflows their width, as the operation `smt.bvssubo`, for a checker that asks whether a /// program's signed subtraction can overflow. `create(ctx, loc, lhs, rhs)` creates the /// operation with one result of the Boolean type. `getResult` returns the result, and `getLhs` /// and `getRhs` return the operands. `operation_name` holds `smt.bvssubo`, and the struct holds /// the operation in `op`. `create` checks no types, and `ir.verifyOperation` checks the rule /// the dialect registered: the two operands share one type and the result is Boolean. The /// context records the operation as not commutative. `create` returns the errors of building /// the Boolean type and of creating the operation in `ctx`. pub const BvSsuboOp: type = op_templates.binaryFixedResult( "bvssubo", sameOperandBoolResultOptions(.{}), getBoolType, ); /// Whether the unsigned product of two bit-vectors overflows their width, as the operation /// `smt.bvumulo`, for a checker that asks whether a program's unsigned multiplication can /// overflow. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the Boolean /// type. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvumulo`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: the two /// operands share one type and the result is Boolean. The context records the operation as /// commutative. `create` returns the errors of building the Boolean type and of creating the /// operation in `ctx`. pub const BvUmuloOp: type = op_templates.binaryFixedResult( "bvumulo", sameOperandBoolResultOptions(commutative_op_traits), getBoolType, ); /// Whether the two's-complement product of two bit-vectors overflows their width, as the /// operation `smt.bvsmulo`, for a checker that asks whether a program's signed multiplication /// can overflow. `create(ctx, loc, lhs, rhs)` creates the operation with one result of the /// Boolean type. `getResult` returns the result, and `getLhs` and `getRhs` return the operands. /// `operation_name` holds `smt.bvsmulo`, and the struct holds the operation in `op`. `create` /// checks no types, and `ir.verifyOperation` checks the rule the dialect registered: the two /// operands share one type and the result is Boolean. The context records the operation as /// commutative. `create` returns the errors of building the Boolean type and of creating the /// operation in `ctx`. pub const BvSmuloOp: type = op_templates.binaryFixedResult( "bvsmulo", sameOperandBoolResultOptions(commutative_op_traits), getBoolType, ); fn sameOperandsResultOptions(comptime traits: ir.OperationTraits) ir.dialects.opSpec.Options { return .{ .traits = traits, .interfaces = &.{unknown_effects}, .dynamic_traits = &.{same_operands_and_result_type_trait}, }; } fn boolUnaryOptions(comptime traits: ir.OperationTraits) ir.dialects.opSpec.Options { return .{ .traits = traits, .interfaces = &.{unknown_effects}, .operand_types = &.{operand0_is_bool_constraint}, .result_types = &.{result0_is_bool_constraint}, }; } fn boolBinaryOptions(comptime traits: ir.OperationTraits) ir.dialects.opSpec.Options { return .{ .traits = traits, .interfaces = &.{unknown_effects}, .operand_types = &.{ operand0_is_bool_constraint, operand1_is_bool_constraint }, .result_types = &.{result0_is_bool_constraint}, }; } fn sameOperandBoolResultOptions(comptime traits: ir.OperationTraits) ir.dialects.opSpec.Options { return .{ .traits = traits, .interfaces = &.{unknown_effects}, .result_types = &.{result0_is_bool_constraint}, .dynamic_traits = &.{same_type_operands_trait}, }; } fn deinitBitVecPayload(allocator: std.mem.Allocator, ptr: *anyopaque) void { const payload: *BitVecTypePayload = @ptrCast(@alignCast(ptr)); allocator.destroy(payload); } fn deinitArrayPayload(allocator: std.mem.Allocator, ptr: *anyopaque) void { const payload: *ArrayTypePayload = @ptrCast(@alignCast(ptr)); allocator.destroy(payload); } fn typeParamFallback(ctx: *const ir.Context, typ: ir.Type) ?*const anyopaque { _ = ctx; const type_name = typ.getDialectTypeName() orelse return null; if (!std.mem.eql(u8, type_name, type_names.bv) and !std.mem.eql(u8, type_name, type_names.array)) return null; return &type_param_vtable; } fn parseTypeParams(type_ptr: *const anyopaque, ctx_opaque: *const interfaces.ContextOpaque) anyerror!?interfaces.TypeParamPayload { const ctx = interfaces.castContext(ir.Context, ctx_opaque); const storage: *const ir.Type.DialectTypeStorage = @ptrCast(@alignCast(type_ptr)); if (std.mem.eql(u8, storage.name, type_names.bv)) { if (storage.param_key.len == 0) return null; const width = std.fmt.parseInt(u32, storage.param_key, 10) catch return null; if (width == 0) return null; const payload = try ir.context.typePayloadAllocator(ctx).create(BitVecTypePayload); payload.* = .{ .width = width }; return .{ .ptr = payload, .deinit = deinitBitVecPayload }; } if (std.mem.eql(u8, storage.name, type_names.array)) { const shape = parseArrayParamKey(storage.param_key) orelse return null; const payload = try ir.context.typePayloadAllocator(ctx).create(ArrayTypePayload); payload.* = shape; return .{ .ptr = payload, .deinit = deinitArrayPayload }; } return null; } fn loadSpec(ctx: *ir.Context) !void { ir.dialects.loadDialectSpec(ctx, spec) catch |err| switch (err) { error.ContextFrozen => {}, else => return err, }; } /// Returns the dialect's Boolean type, `smt.bool`, from `ctx`. A caller needs the Boolean type /// to compare a value's type against it, and the Boolean operations' `create` functions build /// it themselves. The call loads the dialect into `ctx` first when the dialect is unloaded. /// Every call on one context returns the same type. On a frozen context that has not loaded the /// dialect and refuses unregistered types (the default), the call returns `error.UnknownType`, /// and the context keeps the dialect marked as loaded with no operations registered. The call /// returns the errors of loading the dialect other than `error.ContextFrozen`, and the errors /// of looking up the type in `ctx`. pub fn getBoolType(ctx: *ir.Context) !ir.Type { try loadSpec(ctx); return ctx.getDialectTypeFromName(type_names.boolean); } /// Returns the bit-vector type of `width` bits from `ctx`: the type `smt.bv` with `width` as /// decimal text. A checker builds the type of each variable and constant from the width of the /// program value it stands for. The call loads the dialect into `ctx` first when the dialect is /// unloaded. Every call with one width on one context returns the same type. The call accepts a /// `width` of 0 and returns a type whose width `bitVecWidth` reads as `null`. On a frozen /// context that has not loaded the dialect and refuses unregistered types (the default), the /// call returns `error.UnknownType`, and the context keeps the dialect marked as loaded with no /// operations registered. The call returns the errors of loading the dialect other than /// `error.ContextFrozen`, and the errors of looking up or creating the type in `ctx`. pub fn getBitVecType(ctx: *ir.Context, width: u32) !ir.Type { try loadSpec(ctx); var buf: [16]u8 = undefined; const key = try std.fmt.bufPrint(&buf, "{d}", .{width}); return ctx.getDialectTypeFromNameWithKey(type_names.bv, key); } /// Returns the array type from `ctx` whose index is a bit-vector of `index_width` bits and /// whose element is a bit-vector of `element_width` bits: the type `smt.array` with the text /// `index_width:element_width`. A checker builds the type of a memory it models as an array /// from the widths of its addresses and of its cells. The call loads the dialect into `ctx` /// first when the dialect is unloaded. Every call with one pair of widths on one context /// returns the same type. The call accepts a width of 0 and returns a type whose widths /// `arrayShape` reads as `null`. On a frozen context that has not loaded the dialect and /// refuses unregistered types (the default), the call returns `error.UnknownType`, and the /// context keeps the dialect marked as loaded with no operations registered. The call returns /// the errors of loading the dialect other than `error.ContextFrozen`, and the errors of /// looking up or creating the type in `ctx`. pub fn getArrayType(ctx: *ir.Context, index_width: u32, element_width: u32) !ir.Type { try loadSpec(ctx); var buf: [32]u8 = undefined; const key = try std.fmt.bufPrint(&buf, "{d}:{d}", .{ index_width, element_width }); return ctx.getDialectTypeFromNameWithKey(type_names.array, key); } /// Returns the width of the bit-vector type `typ`, so code that reads a formula back can pick a /// solver sort and a constant's width from a value's type. The call returns `null` for a type /// of another kind, for a bit-vector type whose width text fails to parse as a `u32` above 0, /// and when the context fails to parse the width. The context parses the width once per type /// and keeps it. The constructors of the rotation, width-changing and array operations call it /// to check their operands. pub fn bitVecWidth(ctx: *ir.Context, typ: ir.Type) ?u32 { const payload = ctx.getTypeParamPayload(typ, BitVecTypePayload) catch return null; return if (payload) |value| value.width else null; } /// Returns the index width and the element width of the array type `typ`, as a copy of its /// `ArrayTypePayload`, so code that reads a formula back can pick an array sort from a value's /// type. The call returns `null` for a type of another kind, for an array type whose text fails /// to parse as two `u32` values above 0 joined by one colon, and when the context fails to /// parse the widths. The context parses the widths once per type and keeps them. /// `ArraySelectOp.create` and `ArrayStoreOp.create` call it to check their array operand. pub fn arrayShape(ctx: *ir.Context, typ: ir.Type) ?ArrayTypePayload { const payload = ctx.getTypeParamPayload(typ, ArrayTypePayload) catch return null; return if (payload) |value| value.* else null; } fn parseArrayParamKey(param_key: []const u8) ?ArrayTypePayload { var parts = std.mem.splitScalar(u8, param_key, ':'); const index_text = parts.next() orelse return null; const element_text = parts.next() orelse return null; if (parts.next() != null) return null; const index_width = std.fmt.parseInt(u32, index_text, 10) catch return null; const element_width = std.fmt.parseInt(u32, element_text, 10) catch return null; if (index_width == 0 or element_width == 0) return null; return .{ .index_width = index_width, .element_width = element_width }; } fn getU32Attr(op: *const ir.Operation, attr_name: []const u8) ?u32 { const attr = op.getAttrAs(ir.Attribute.IntegerAttr, attr_name) orelse return null; const value = attr.getValue(); if (value < 0 or value > std.math.maxInt(u32)) return null; return @intCast(value); } fn unary(ctx: *ir.Context, loc: ir.Location, operation_name: []const u8, operand: *ir.Value, result_type: ir.Type) !*ir.Operation { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{operand}); state.addTypes(&.{result_type}); return builder.create(state); } fn rotate(ctx: *ir.Context, loc: ir.Location, operation_name: []const u8, operand: *ir.Value, amount: u32) !*ir.Operation { _ = bitVecWidth(ctx, operand.type) orelse return error.UnsupportedBitVectorType; const op = try unary(ctx, loc, operation_name, operand, operand.type); try op.setAttr(attr_names.amount, try ctx.getI64Attr(amount)); return op; } fn binary(ctx: *ir.Context, loc: ir.Location, operation_name: []const u8, lhs: *ir.Value, rhs: *ir.Value, result_type: ir.Type) !*ir.Operation { var builder = ir.OperationBuilder.init(ctx); var state = ir.Operation.State.init(operation_name, loc); state.addOperands(&.{ lhs, rhs }); state.addTypes(&.{result_type}); return builder.create(state); }};Also reachable as
Complete caller list for choir.SmtDialect.bitVecWidth
8 direct callers.
tiny.smt.choir.SmtDialect.ArraySelectOp.create[function] atlib/smt/src/choir/dialect.zig:561tiny.smt.choir.SmtDialect.ArrayStoreOp.create[function] atlib/smt/src/choir/dialect.zig:607tiny.smt.choir.SmtDialect.BvConcatOp.create[function] atlib/smt/src/choir/dialect.zig:947tiny.smt.choir.SmtDialect.BvExtractOp.create[function] atlib/smt/src/choir/dialect.zig:998tiny.smt.choir.SmtDialect.BvSignExtOp.create[function] atlib/smt/src/choir/dialect.zig:1132tiny.smt.choir.SmtDialect.BvZeroExtOp.create[function] atlib/smt/src/choir/dialect.zig:1070lib.smt.src.choir.dialect.SmtDialect.rotate[function] — private source atlib/smt/src/choir/dialect.zig:1465in nearest public ownertiny.smt.choir.dialectlib.smt.src.choir.test.test_SMT_dialect_registers_parameterized_bit-vector_type[function] — test source atlib/smt/src/choir/test.zig:21in nearest public ownerlib.smt.src.choir.test
Complete caller list for choir.SmtDialect.getBitVecType
12 direct callers.
tiny.smt.choir.SmtDialect.ArraySelectOp.create[function] atlib/smt/src/choir/dialect.zig:561tiny.smt.choir.SmtDialect.BitVecConstOp.create[function] atlib/smt/src/choir/dialect.zig:280tiny.smt.choir.SmtDialect.BvConcatOp.create[function] atlib/smt/src/choir/dialect.zig:947tiny.smt.choir.SmtDialect.BvExtractOp.create[function] atlib/smt/src/choir/dialect.zig:998tiny.smt.choir.SmtDialect.BvSignExtOp.create[function] atlib/smt/src/choir/dialect.zig:1132tiny.smt.choir.SmtDialect.BvZeroExtOp.create[function] atlib/smt/src/choir/dialect.zig:1070lib.smt.src.choir.test.test_SMT_commutative_traits_grant_no_CSE_permission[function] — test source atlib/smt/src/choir/test.zig:191in nearest public ownerlib.smt.src.choir.testlib.smt.src.choir.test.test_SMT_dialect_creates_bit-vector_array_operations[function] — test source atlib/smt/src/choir/test.zig:141in nearest public ownerlib.smt.src.choir.testlib.smt.src.choir.test.test_SMT_dialect_creates_bit-vector_width-changing_operations[function] — test source atlib/smt/src/choir/test.zig:114in nearest public ownerlib.smt.src.choir.testlib.smt.src.choir.test.test_SMT_dialect_creates_symbolic_bit-vector_assertion_operations[function] — test source atlib/smt/src/choir/test.zig:38in nearest public ownerlib.smt.src.choir.testlib.smt.src.choir.test.test_SMT_dialect_registers_parameterized_bit-vector_type[function] — test source atlib/smt/src/choir/test.zig:21in nearest public ownerlib.smt.src.choir.testlib.smt.src.choir.test.test_SMT_fixed_rotates_remain_unqualified_for_CSE[function] — test source atlib/smt/src/choir/test.zig:236in nearest public ownerlib.smt.src.choir.test
Audit
| Definitions | 118 |
|---|---|
| Public names | 236 |
| Members | 16 |
| Version | 26.7.0 |
| Revision | daab053ee433 |