Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelogs/unreleased/th__non_native_felt_attr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
added:
- "Add `function.allow_non_native_field_ops` attribute"
changed:
- "Require `function.allow_non_native_field_ops` attribute on functions that use
non-native field operations, instead of requiring `function.allow_witness` attribute."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing context I think, but does this mean allow_non_native_field_ops subsumes allow_witness everywhere? Or are there still cases where you might want one or the other on @compute but not both?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is more for @constrain, where some ops that are not allowed in the constraint backend (e.g., felt.umod) can be allowed earlier in the pipeline for values that will eventually be flattened into constants (e.g., computing an index i % 4 is allowed in @constrain if i will be known at compile time, but we currently don't have a way to encode that)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raghav198 I added a description on the PR that hopefully explains it fully but let me know if anything remains unclear

6 changes: 6 additions & 0 deletions include/llzk-c/Dialect/Function.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ LLZK_DECLARE_OP_PREDICATE(FuncDefOp, HasAllowWitnessAttr);
/// Sets the allow_witness attribute in the FuncDefOp operation.
MLIR_CAPI_EXPORTED void llzkFuncDefOpSetAllowWitnessAttr(MlirOperation op, bool value);

/// Returns true if the FuncDefOp has the allow_non_native_field_ops attribute.
LLZK_DECLARE_OP_PREDICATE(FuncDefOp, HasAllowNonNativeFieldOpsAttr);

/// Sets the allow_non_native_field_ops attribute in the FuncDefOp operation.
MLIR_CAPI_EXPORTED void llzkFuncDefOpSetAllowNonNativeFieldOpsAttr(MlirOperation op, bool value);

/// Returns true if the `idx`-th argument has the Pub attribute.
LLZK_DECLARE_NARY_OP_PREDICATE(FuncDefOp, HasArgIsPub, unsigned arg);

Expand Down
10 changes: 6 additions & 4 deletions include/llzk/Dialect/Bool/IR/Ops.td
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class BoolUnaryOpBase<string mnemonic, Type resultType, list<Trait> traits = []>
// Boolean operators
//===------------------------------------------------------------------===//

def LLZK_AndBoolOp : BoolBinaryOpBase<"and", I1, [Commutative]> {
def LLZK_AndBoolOp
: BoolBinaryOpBase<"and", I1, [NotFieldNative, Commutative]> {
let summary = "logical AND operator";
let description = [{
This operation computes the logical AND (i.e., conjunction) of two `i1` (i.e., boolean)
Expand All @@ -49,7 +50,7 @@ def LLZK_AndBoolOp : BoolBinaryOpBase<"and", I1, [Commutative]> {
}];
}

def LLZK_OrBoolOp : BoolBinaryOpBase<"or", I1, [Commutative]> {
def LLZK_OrBoolOp : BoolBinaryOpBase<"or", I1, [NotFieldNative, Commutative]> {
let summary = "logical OR operator";
let description = [{
This operation computes the logical OR (i.e., disjunction) of two `i1` (i.e., boolean)
Expand All @@ -59,7 +60,8 @@ def LLZK_OrBoolOp : BoolBinaryOpBase<"or", I1, [Commutative]> {
}];
}

def LLZK_XorBoolOp : BoolBinaryOpBase<"xor", I1, [Commutative]> {
def LLZK_XorBoolOp
: BoolBinaryOpBase<"xor", I1, [NotFieldNative, Commutative]> {
let summary = "logical XOR operator";
let description = [{
This operation computes the logical XOR (i.e., exclusive disjunction) of two `i1` (i.e., boolean)
Expand All @@ -69,7 +71,7 @@ def LLZK_XorBoolOp : BoolBinaryOpBase<"xor", I1, [Commutative]> {
}];
}

def LLZK_NotBoolOp : BoolUnaryOpBase<"not", I1, []> {
def LLZK_NotBoolOp : BoolUnaryOpBase<"not", I1, [NotFieldNative]> {
let summary = "logical NOT operator";
let description = [{
This operation computes the logical NOT (i.e., negation) of an `i1` (i.e., boolean)
Expand Down
31 changes: 19 additions & 12 deletions include/llzk/Dialect/Felt/IR/Ops.td
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ def LLZK_MulFeltOp : FeltDialectBinaryOp<"mul", LLZK_FeltType, [Commutative]> {
let description = [{}];
}

def LLZK_PowFeltOp : FeltDialectBinaryOp<"pow", LLZK_FeltType, [WitnessGen]> {
def LLZK_PowFeltOp
: FeltDialectBinaryOp<"pow", LLZK_FeltType, [NotFieldNative]> {
let summary = "exponentiation operator for field elements";
let description = [{

Expand All @@ -122,7 +123,7 @@ def LLZK_DivFeltOp : FeltDialectBinaryOp<"div", LLZK_FeltType> {
}

def LLZK_UnsignedIntDivFeltOp
: FeltDialectBinaryOp<"uintdiv", LLZK_FeltType, [WitnessGen]> {
: FeltDialectBinaryOp<"uintdiv", LLZK_FeltType, [NotFieldNative]> {
let summary = "unsigned integer division operator for field elements";
let description = [{
Treats the operands as if they were unsigned integers with bitwidth
Expand All @@ -131,7 +132,7 @@ def LLZK_UnsignedIntDivFeltOp
}

def LLZK_SignedIntDivFeltOp
: FeltDialectBinaryOp<"sintdiv", LLZK_FeltType, [WitnessGen]> {
: FeltDialectBinaryOp<"sintdiv", LLZK_FeltType, [NotFieldNative]> {
let summary = "signed integer division operator for field elements";
let description = [{
Treats the operands as if they were signed integers with bitwidth
Expand All @@ -148,7 +149,7 @@ def LLZK_SignedIntDivFeltOp
}

def LLZK_UnsignedModFeltOp
: FeltDialectBinaryOp<"umod", LLZK_FeltType, [WitnessGen]> {
: FeltDialectBinaryOp<"umod", LLZK_FeltType, [NotFieldNative]> {
let summary =
"unsigned integer modulus/remainder operator for field elements";
let description = [{
Expand All @@ -158,7 +159,7 @@ def LLZK_UnsignedModFeltOp
}

def LLZK_SignedModFeltOp
: FeltDialectBinaryOp<"smod", LLZK_FeltType, [WitnessGen]> {
: FeltDialectBinaryOp<"smod", LLZK_FeltType, [NotFieldNative]> {
let summary = "signed integer modulus/remainder operator for field elements";
let description = [{
Computes the remainder that would result from the division operation performed
Expand All @@ -171,31 +172,35 @@ def LLZK_NegFeltOp : FeltDialectUnaryOp<"neg", LLZK_FeltType> {
let description = [{}];
}

def LLZK_InvFeltOp : FeltDialectUnaryOp<"inv", LLZK_FeltType, [WitnessGen]> {
def LLZK_InvFeltOp
: FeltDialectUnaryOp<"inv", LLZK_FeltType, [NotFieldNative]> {
let summary = "inverse operator for field elements";
let description = [{}];
}

def LLZK_AndFeltOp
: FeltDialectBinaryOp<"bit_and", LLZK_FeltType, [WitnessGen, Commutative]> {
: FeltDialectBinaryOp<"bit_and",
LLZK_FeltType, [NotFieldNative, Commutative]> {
let summary = "bitwise AND operator for field elements";
let description = [{}];
}

def LLZK_OrFeltOp
: FeltDialectBinaryOp<"bit_or", LLZK_FeltType, [WitnessGen, Commutative]> {
: FeltDialectBinaryOp<"bit_or",
LLZK_FeltType, [NotFieldNative, Commutative]> {
let summary = "bitwise OR operator for field elements";
let description = [{}];
}

def LLZK_XorFeltOp
: FeltDialectBinaryOp<"bit_xor", LLZK_FeltType, [WitnessGen, Commutative]> {
: FeltDialectBinaryOp<"bit_xor",
LLZK_FeltType, [NotFieldNative, Commutative]> {
let summary = "bitwise XOR operator for field elements";
let description = [{}];
}

def LLZK_NotFeltOp
: FeltDialectUnaryOp<"bit_not", LLZK_FeltType, [WitnessGen]> {
: FeltDialectUnaryOp<"bit_not", LLZK_FeltType, [NotFieldNative]> {
let summary = "integer complement (bitwise-not) operator for field elements";
let description = [{
Treats the operand as an integer with a bitwidth equal to the bitwidth
Expand All @@ -204,12 +209,14 @@ def LLZK_NotFeltOp
}];
}

def LLZK_ShlFeltOp : FeltDialectBinaryOp<"shl", LLZK_FeltType, [WitnessGen]> {
def LLZK_ShlFeltOp
: FeltDialectBinaryOp<"shl", LLZK_FeltType, [NotFieldNative]> {
let summary = "left shift operator for field elements";
let description = [{}];
}

def LLZK_ShrFeltOp : FeltDialectBinaryOp<"shr", LLZK_FeltType, [WitnessGen]> {
def LLZK_ShrFeltOp
: FeltDialectBinaryOp<"shr", LLZK_FeltType, [NotFieldNative]> {
let summary = "right shift operator for field elements";
let description = [{}];
}
Expand Down
22 changes: 17 additions & 5 deletions include/llzk/Dialect/Function/IR/Attrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,31 @@ class FunctionDialectAttr<string name, string attrMnemonic,

def LLZK_AllowConstraintAttr
: FunctionDialectAttr<"AllowConstraint", "allow_constraint"> {
let summary = "Marks functions to allow `ConstraintGen` ops";
let summary =
"Marks functions to allow `ConstraintGen` ops within their body";
let description = [{
A unit attribute that can be attached to a `FuncDefOp` to indicate that ops
marked with `ConstraintGen` (ex: emit ops) are allowed within its body.
marked with `ConstraintGen` (e.g., emit ops) are allowed within its body.
}];
}

def LLZK_AllowWitnessAttr
: FunctionDialectAttr<"AllowWitness", "allow_witness"> {
let summary = "Marks functions to allow `WitnessGen` ops";
let summary = "Marks functions to allow `WitnessGen` ops within their body";
let description = [{
A unit attribute that can be attached to a `FuncDefOp` to indicate that ops
marked with `WitnessGen` (i.e., boolean and bitwise ops) are allowed within its body.
A unit attribute that can be attached to a `FuncDefOp` to indicate that ops marked
with `WitnessGen` (e.g., construct and write ops) are allowed within its body.
}];
}

def LLZK_AllowNonNativeFieldOpsAttr
: FunctionDialectAttr<"AllowNonNativeFieldOps",
"allow_non_native_field_ops"> {
let summary =
"Marks functions to allow `NotFieldNative` ops within their body";
let description = [{
A unit attribute that can be attached to a `FuncDefOp` to indicate that ops marked
with `NotFieldNative` (e.g., boolean and bitwise ops) are allowed within its body.
}];
}

Expand Down
11 changes: 11 additions & 0 deletions include/llzk/Dialect/Function/IR/OpTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace llzk::function {

mlir::LogicalResult verifyConstraintGenTraitImpl(mlir::Operation *op);
mlir::LogicalResult verifyWitnessGenTraitImpl(mlir::Operation *op);
mlir::LogicalResult verifyNotFieldNativeTraitImpl(mlir::Operation *op);

/// Marker for ops that are specific to constraint generation.
/// Verifies that the surrounding function is marked with the `AllowConstraintAttr`.
Expand All @@ -38,4 +39,14 @@ class WitnessGen : public mlir::OpTrait::TraitBase<TypeClass, WitnessGen> {
}
};

/// Marker for ops over `llzk.felt` type operands that are not native to finite field arithmetic.
/// Verifies that the surrounding function is marked with the `AllowNonNativeFieldOpsAttr`.
template <typename TypeClass>
class NotFieldNative : public mlir::OpTrait::TraitBase<TypeClass, NotFieldNative> {
public:
inline static mlir::LogicalResult verifyTrait(mlir::Operation *op) {
return verifyNotFieldNativeTraitImpl(op);
}
};

} // namespace llzk::function
8 changes: 8 additions & 0 deletions include/llzk/Dialect/Function/IR/OpTraits.td
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,12 @@ def ConstraintGen : NativeOpTrait<"ConstraintGen">, StructuralOpTrait {
string cppNamespace = "::llzk::function";
}

/// Marker for ops over `llzk.felt` type operands that are not native to finite
/// field arithmetic and thus must be rewritten or folded into a constant after
/// the flattening and unrolling pass. Op classes with this trait are valid only
/// in functions that have the `AllowNonNativeFieldOpsAttr`.
def NotFieldNative : NativeOpTrait<"NotFieldNative">, StructuralOpTrait {
string cppNamespace = "::llzk::function";
}

#endif // LLZK_FUNCTION_TRAITS
8 changes: 8 additions & 0 deletions include/llzk/Dialect/Function/IR/Ops.td
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ def FuncDefOp
/// Add (resp. remove) the `allow_witness` attribute to (resp. from) the function def.
void setAllowWitnessAttr(bool newValue = true);

/// Return `true` iff the function def has the `allow_non_native_field_ops` attribute.
inline bool hasAllowNonNativeFieldOpsAttr() {
return getOperation()->hasAttr(llzk::function::AllowNonNativeFieldOpsAttr::name);
}

/// Add (resp. remove) the `allow_non_native_field_ops` attribute to (resp. from) the function def.
void setAllowNonNativeFieldOpsAttr(bool newValue = true);

/// Return `true` iff the argument at the given index has `pub` attribute.
bool hasArgPublicAttr(unsigned index);

Expand Down
8 changes: 8 additions & 0 deletions lib/CAPI/Dialect/Function.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ void llzkFuncDefOpSetAllowWitnessAttr(MlirOperation op, bool value) {
unwrap_cast<FuncDefOp>(op).setAllowWitnessAttr(value);
}

bool llzkFuncDefOpGetHasAllowNonNativeFieldOpsAttr(MlirOperation op) {
return unwrap_cast<FuncDefOp>(op).hasAllowNonNativeFieldOpsAttr();
}

void llzkFuncDefOpSetAllowNonNativeFieldOpsAttr(MlirOperation op, bool value) {
unwrap_cast<FuncDefOp>(op).setAllowNonNativeFieldOpsAttr(value);
}

bool llzkFuncDefOpGetHasArgIsPub(MlirOperation op, unsigned argNo) {
return unwrap_cast<FuncDefOp>(op).hasArgPublicAttr(argNo);
}
Expand Down
8 changes: 8 additions & 0 deletions lib/Dialect/Function/IR/OpTraits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,12 @@ LogicalResult verifyWitnessGenTraitImpl(Operation *op) {
return success();
}

LogicalResult verifyNotFieldNativeTraitImpl(Operation *op) {
if (!parentFuncDefOpHasAttr(op, &FuncDefOp::hasAllowNonNativeFieldOpsAttr)) {
return op->emitOpError() << "only valid within a '" << FuncDefOp::getOperationName()
<< "' with '" << AllowNonNativeFieldOpsAttr::name << "' attribute";
}
return success();
}

} // namespace llzk::function
11 changes: 11 additions & 0 deletions lib/Dialect/Function/IR/Ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ void FuncDefOp::setAllowWitnessAttr(bool newValue) {
}
}

void FuncDefOp::setAllowNonNativeFieldOpsAttr(bool newValue) {
if (newValue) {
getOperation()->setAttr(AllowNonNativeFieldOpsAttr::name, UnitAttr::get(getContext()));
} else {
getOperation()->removeAttr(AllowNonNativeFieldOpsAttr::name);
}
}

bool FuncDefOp::hasArgPublicAttr(unsigned index) {
if (index < this->getNumArguments()) {
DictionaryAttr res = function_interface_impl::getArgAttrDict(*this, index);
Expand Down Expand Up @@ -476,6 +484,9 @@ struct CallOpVerifier {
if (target.hasAllowWitnessAttr() && !caller.hasAllowWitnessAttr()) {
emitAttrErr(AllowWitnessAttr::name);
}
if (target.hasAllowNonNativeFieldOpsAttr() && !caller.hasAllowNonNativeFieldOpsAttr()) {
emitAttrErr(AllowNonNativeFieldOpsAttr::name);
}
}
return aggregateRes;
}
Expand Down
8 changes: 4 additions & 4 deletions test/Analysis/interval_analysis/edwards2montgomery.llzk
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ module attributes {veridise.lang = "llzk"} {
}
struct.def @EqzExt<[]> {
struct.field @"$super" : !struct.type<@Component<[]>>
function.def @compute(%arg0: !array.type<4 x !felt.type>) -> !struct.type<@EqzExt<[]>> attributes {function.allow_witness} {
function.def @compute(%arg0: !array.type<4 x !felt.type>) -> !struct.type<@EqzExt<[]>> attributes {function.allow_non_native_field_ops, function.allow_witness} {
%felt_const_0 = felt.const 0
%c3 = arith.constant 3 : index
%c2 = arith.constant 2 : index
Expand Down Expand Up @@ -93,7 +93,7 @@ module attributes {veridise.lang = "llzk"} {
struct.field @"$super" : !struct.type<@NondetExtReg<[]>>
struct.field @"$temp" : !struct.type<@EqzExt<[]>>
struct.field @reg : !struct.type<@NondetExtReg<[]>> {column}
function.def @compute(%arg0: !array.type<4 x !felt.type>) -> !struct.type<@ExtReg<[]>> attributes {function.allow_witness} {
function.def @compute(%arg0: !array.type<4 x !felt.type>) -> !struct.type<@ExtReg<[]>> attributes {function.allow_non_native_field_ops, function.allow_witness} {
%c3 = arith.constant 3 : index
%c2 = arith.constant 2 : index
%c1 = arith.constant 1 : index
Expand Down Expand Up @@ -151,7 +151,7 @@ module attributes {veridise.lang = "llzk"} {
struct.def @Div<[]> {
struct.field @"$super" : !felt.type {llzk.pub}
struct.field @reciprocal : !felt.type
function.def @compute(%arg0: !felt.type, %arg1: !felt.type) -> !struct.type<@Div<[]>> attributes {function.allow_witness} {
function.def @compute(%arg0: !felt.type, %arg1: !felt.type) -> !struct.type<@Div<[]>> attributes {function.allow_non_native_field_ops, function.allow_witness} {
%self = struct.new : <@Div<[]>>
%0 = felt.inv %arg1 : !felt.type
struct.writef %self[@reciprocal] = %0 : <@Div<[]>>, !felt.type
Expand Down Expand Up @@ -217,7 +217,7 @@ module attributes {veridise.lang = "llzk"} {
struct.field @"$temp_0" : !struct.type<@Div<[]>>
struct.field @"$temp" : !struct.type<@NondetReg<[]>> {column}
struct.field @v : !struct.type<@Div<[]>>
function.def @compute(%arg0: !array.type<2 x !felt.type>) -> !struct.type<@Edwards2Montgomery<[]>> attributes {function.allow_witness} {
function.def @compute(%arg0: !array.type<2 x !felt.type>) -> !struct.type<@Edwards2Montgomery<[]>> attributes {function.allow_non_native_field_ops, function.allow_witness} {
%felt_const_0 = felt.const 0
%felt_const_1 = felt.const 1
%self = struct.new : <@Edwards2Montgomery<[]>>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ module attributes {veridise.lang = "llzk"} {
struct.def @Div<[]> {
struct.field @"$super" : !felt.type
struct.field @reciprocal : !felt.type
function.def @compute(%arg0: !felt.type, %arg1: !felt.type) -> !struct.type<@Div<[]>> {
function.def @compute(%arg0: !felt.type, %arg1: !felt.type) -> !struct.type<@Div<[]>> attributes {function.allow_non_native_field_ops} {
%self = struct.new : <@Div<[]>>
%0 = felt.inv %arg1 : !felt.type
struct.writef %self[@reciprocal] = %0 : <@Div<[]>>, !felt.type
Expand Down
Loading