Skip to content

Comb Interval Range Analysis and Comb Opt Narrowing pass #8425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

cowardsa
Copy link

Building on the existing MLIR integer interval range analysis framework, build the interface for the comb dialect. Use the interval range analysis to develop a comb opt narrowing pass, that reduces a comb opt based on the interval range of the operation. Currently this is only supported for addition, subtraction and multiplication, but this could be extended in the future. Future work will leverage the interval analysis to validate the combination of consecutive addition operators into a multi-operand addition.

@cowardsa cowardsa marked this pull request as ready for review April 17, 2025 11:49
@cowardsa cowardsa requested a review from darthscsi as a code owner April 17, 2025 11:49
Copy link
Contributor

@TaoBi22 TaoBi22 left a comment

Choose a reason for hiding this comment

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

This looks pretty good to me! Mostly just nits, the only big thing is where this int range analysis should live (expanded on below). @uenoku since this is along the lines of synthesis, is there any chance you could take a quick look at it too?

@@ -0,0 +1,256 @@
//===- InferIntRangeInterfaceImpls.cpp - Integer range impls for comb -===//
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's a little unusual for something like this to live in lib/Dialect/<dialect> since it goes beyond the actual dialect definition, maybe it would make more sense to have it in lib/Analysis?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, when creating an analysis we often create a test pass in this file that just runs the analysis and prints out the results in a checkable format, and then use this to create some FileCheck tests for the analysis. It would be nice to do that here too (and if this moves to Analysis then you could just add it in that file)

Copy link
Author

Choose a reason for hiding this comment

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

@TaoBi22 - agree with this suggestion - there's currently no lib/Analysis/Comb hierarchy and given that I've got an implementation for both HW and Comb would it be sensible to introduce the directory structure in lib/Analysis?

Copy link
Author

Choose a reason for hiding this comment

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

Note that in MLIR these files live here say, lib/Dialect/Arith/IR/InferIntRangeInterfaceImpls.cpp

Copy link
Member

Choose a reason for hiding this comment

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

I think the current location for these files is fine.
One could make an argument that this logic should be tied more to the transformation pass and less to the dialect definition in which case you could implement them as external models and move them to the Transforms folder (or a new Analysis folder). Both seem fine to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

Cool, I don't have strong feelings on this so if it matches what's done upstream then feel free to ignore

Copy link
Author

Choose a reason for hiding this comment

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

Ok will stick with as is for now - if the space of analyses grows then will perhaps make sense to separate as per Bea's suggestion

Copy link
Author

Choose a reason for hiding this comment

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

@TaoBi22 and @uenoku - have added a testing pass as per suggestion that appends the interval analysis data as an operation attribute which we can then check using Filecheck - let me know if this is suitable? This includes testing for the mux interval analysis.

@uenoku uenoku self-requested a review April 18, 2025 20:20
Copy link
Member

@maerhart maerhart left a comment

Choose a reason for hiding this comment

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

Nice work! 🎉
Just added some drive-by comments.

@@ -0,0 +1,256 @@
//===- InferIntRangeInterfaceImpls.cpp - Integer range impls for comb -===//
Copy link
Member

Choose a reason for hiding this comment

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

I think the current location for these files is fine.
One could make an argument that this logic should be tied more to the transformation pass and less to the dialect definition in which case you could implement them as external models and move them to the Transforms folder (or a new Analysis folder). Both seem fine to me.

Copy link
Member

@uenoku uenoku left a comment

Choose a reason for hiding this comment

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

Thank you for interesting work! I think the interface is definitely worth to have 👍
High-level comments:

  • Is it possible to have a test for range interface decoupled from narrowing pass? I think it's a bit difficult to track the correctness of the analysis. i.e. we don't have a test for range interface tests of comb.mux etc.
  • Please use camelBack for variable names

@cowardsa
Copy link
Author

@uenoku/@maerhart - have addressed all the comments above - would you have time to take another pass and approve if you feel its ready?

Major changes:

  • Formatting, newlines, camelBack etc corrected throughout
  • Added interval analysis test pass adding the analysis results as attributes to be checked
  • Made all tests use regex throughout and made each test only stress a single pass

@cowardsa cowardsa requested review from maerhart and uenoku April 24, 2025 14:01
Copy link
Member

@uenoku uenoku left a comment

Choose a reason for hiding this comment

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

Thank you for adding a test pass. Could you add a test for replicate, extract and icmp? There is a correctness issue in add/mul pattern and crash with the following case, once after the issue was fixed PR look good to me.

hw.module private @InitReg1(in %clock: !seq.clock, in %reset: i1, in %io_d: i32, in %io_en: i1, out io_q: i32) {
  %false = hw.constant false
  %c1_i32 = hw.constant 1 : i32
  %c0_i32 = hw.constant 0 : i32
  %reg = seq.firreg %4 clock %clock sym @__reg__ reset async %reset, %c0_i32 : i32
  %reg2 = seq.firreg %reg2 clock %clock sym @__reg2__ reset sync %reset, %c0_i32 : i32
  %reg3 = seq.firreg %reg3 clock %clock sym @__reg3__ reset async %reset, %c1_i32 : i32
  %0 = comb.concat %false, %reg : i1, i32
  %1 = comb.concat %false, %reg2 : i1, i32
  %2 = comb.add %0, %1 : i33
  %3 = comb.extract %2 from 1 : (i33) -> i32
  %4 = comb.mux bin %io_en, %io_d, %3 : i32
  hw.output %reg : i32
}
circt-opt: /scratch/hidetou/circt/llvm/llvm/lib/Support/APInt.cpp:285: int llvm::APInt::compare(const llvm::APInt &) const: Assertion `BitWidth == RHS.BitWidth && "Bit widths must be same for comparison"' failed.
PLEASE submit a bug report to https://github.com/llvm/circt and include the crash backtrace.
Stack dump:
0.      Program arguments: ./build/bin/circt-opt --comb-int-range-narrowing a.mlir
 #0 0x0000000000cdccc7 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /scratch/hidetou/circt/llvm/llvm/lib/Support/Unix/Signals.inc:804:13
 #1 0x0000000000cdac00 llvm::sys::RunSignalHandlers() /scratch/hidetou/circt/llvm/llvm/lib/Support/Signals.cpp:106:18
 #2 0x0000000000cdd5da SignalHandler(int, siginfo_t*, void*) /scratch/hidetou/circt/llvm/llvm/lib/Support/Unix/Signals.inc:0:3
 #3 0x00007ff6960e3cf0 __restore_rt (/lib64/libpthread.so.0+0x12cf0)
 #4 0x00007ff69519cacf raise (/lib64/libc.so.6+0x4eacf)
 #5 0x00007ff69516fea5 abort (/lib64/libc.so.6+0x21ea5)
 #6 0x00007ff69516fd79 _nl_load_domain.cold.0 (/lib64/libc.so.6+0x21d79)
 #7 0x00007ff695195426 (/lib64/libc.so.6+0x47426)
 #8 0x0000000000cf2ee6 (./build/bin/circt-opt+0xcf2ee6)
 #9 0x0000000002dfbf69 llvm::APInt::ult(llvm::APInt const&) const /scratch/hidetou/circt/llvm/llvm/include/llvm/ADT/APInt.h:1111:58
#10 0x0000000002dfbf69 mlir::ConstantIntRanges::rangeUnion(mlir::ConstantIntRanges const&) const /scratch/hidetou/circt/llvm/mlir/lib/Interfaces/InferIntRangeInterface.cpp:93:35
#11 0x0000000000f5907c llvm::APInt::APInt(llvm::APInt&&) /scratch/hidetou/circt/llvm/llvm/include/llvm/ADT/APInt.h:184:39
#12 0x0000000000f5907c mlir::ConstantIntRanges::ConstantIntRanges(mlir::ConstantIntRanges&&) /scratch/hidetou/circt/llvm/llvm/../mlir/include/mlir/Interfaces/InferIntRangeInterface.h:27:7
#13 0x0000000000f5907c std::_Optional_payload_base<mlir::ConstantIntRanges>::_Storage<mlir::ConstantIntRanges, false>::_Storage<mlir::ConstantIntRanges>(std::in_place_t, mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:225:8
#14 0x0000000000f5907c std::_Optional_payload_base<mlir::ConstantIntRanges>::_Optional_payload_base<mlir::ConstantIntRanges>(std::in_place_t, mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:116:4
#15 0x0000000000f5907c std::_Optional_payload<mlir::ConstantIntRanges, true, false, false>::_Optional_payload<mlir::ConstantIntRanges>(std::in_place_t, mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:358:42
#16 0x0000000000f5907c std::_Optional_payload<mlir::ConstantIntRanges, false, false, false>::_Optional_payload<mlir::ConstantIntRanges>(std::in_place_t, mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:392:57
#17 0x0000000000f5907c std::_Optional_base<mlir::ConstantIntRanges, false, false>::_Optional_base<mlir::ConstantIntRanges, false>(std::in_place_t, mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:484:4
#18 0x0000000000f5907c std::optional<mlir::ConstantIntRanges>::optional<mlir::ConstantIntRanges, true>(mlir::ConstantIntRanges&&) /sifive/tools/gcc/gcc/11.2.0/lib/gcc/x86_64-pc-linux-gnu/11.2.0/../../../../include/c++/11.2.0/optional:707:4
#19 0x0000000000f5907c mlir::IntegerValueRange::IntegerValueRange(mlir::ConstantIntRanges) /scratch/hidetou/circt/llvm/llvm/../mlir/include/mlir/Interfaces/InferIntRangeInterface.h:117:48
#20 0x0000000000f5907c mlir::IntegerValueRange::join(mlir::IntegerValueRange const&, mlir::IntegerValueRange const&) /scratch/hidetou/circt/llvm/llvm/../mlir/include/mlir/Interfaces/InferIntRangeInterface.h:145:12
#21 0x0000000000f58f5e llvm::function_ref<void (mlir::Value, mlir::IntegerValueRange const&)>::operator()(mlir::Value, mlir::IntegerValueRange const&) const /scratch/hidetou/circt/llvm/llvm/include/llvm/ADT/STLFunctionalExtras.h:69:12
#22 0x0000000000f58f5e circt::comb::MuxOp::inferResultRangesFromOptional(llvm::ArrayRef<mlir::IntegerValueRange>, llvm::function_ref<void (mlir::Value, mlir::IntegerValueRange const&)>) /scratch/hidetou/circt/lib/Dialect/Comb/InferIntRangeInterfaceImpls.cpp:227:3
#23 0x0000000000f4d103 mlir::detail::InferIntRangeInterfaceInterfaceTraits::Model<circt::comb::MuxOp>::inferResultRangesFromOptional(mlir::detail::InferIntRangeInterfaceInterfaceTraits::Concept const*, mlir::Operation*, llvm::ArrayRef<mlir::IntegerValueRange>, llvm::function_ref<void (mlir::Value, mlir::IntegerValueRange const&)>) /scratch/hidetou/circt/build/tools/mlir/include/mlir/Interfaces/InferIntRangeInterface.h.inc:131:3

Comment on lines +80 to +82
Value lhs = op.getOperand(0);
Value rhs = op.getOperand(1);

Copy link
Member

Choose a reason for hiding this comment

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

I think this implicitly assumes operations are binary. Add/Mul are variadic so please insert a condition to check op is binary or handle variadic operations as well.
Not sure that's root cause, but I have an example that comb-int-range-narrowing breaks circt-lec. This is basically same logical_ops in test but %7 has 3 operands.

hw.module @logical_ops(in %a : i8, in %b : i9, in %c : i10, in %d : i16, out res : i18) {
  %c0_i2 = hw.constant 0 : i2
  %false = hw.constant false
  %c0_i9 = hw.constant 0 : i9
  %c0_i8 = hw.constant 0 : i8
  %0 = comb.concat %c0_i9, %a : i9, i8
  %1 = comb.concat %c0_i8, %b : i8, i9
  %2 = comb.and %0, %1 : i17
  %3 = comb.concat %false, %2 : i1, i17
  %4 = comb.concat %c0_i8, %c : i8, i10
  %5 = comb.or %3, %4 : i18
  %6 = comb.concat %c0_i2, %d : i2, i16
  %7 = comb.add %5, %6, %6 : i18
  hw.output %7 : i18
}
$ ./build/bin/circt-opt --comb-int-range-narrowing ./test/Dialect/Comb/comb-int-range-narrowing.mlir  -o after.mlir
$ ./build/bin/circt-lec after.mlir ./test/Dialect/Comb/comb-int-range-narrowing.mlir --c1 logical_ops --c2 logical_ops --shared-libs ../z3/build/libz3.so
c1 != c2

Copy link
Author

Choose a reason for hiding this comment

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

Have included a check for binary operators at time of writing - will extend to variadic operators in a subsequent PR, where I'll also add support for narrowing more operators. Good catch!

void comb::ICmpOp::inferResultRanges(ArrayRef<ConstantIntRanges> argRanges,
SetIntRangeFn setResultRange) {
comb::ICmpPredicate combPred = getPredicate();
intrange::CmpPredicate pred = static_cast<intrange::CmpPredicate>(combPred);
Copy link
Member

Choose a reason for hiding this comment

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

Could you explicitly convert comb predicate to intrange predicate? There are comb predicates that are specific hardware so this cast is dangerous.

SetIntRangeFn setResultRange) {
// Right-shift and truncate (trunaction implicitly handled)
auto lowBit = getLowBit();
auto umin = argRanges[0].umin().ushl_sat(lowBit);
Copy link
Member

Choose a reason for hiding this comment

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

shr?

Copy link
Author

@cowardsa cowardsa Apr 25, 2025

Choose a reason for hiding this comment

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

Yes this is the cause of the InitReg1 bug above - thanks for spotting this error

@cowardsa
Copy link
Author

@uenoku - have addressed your remaining comments with the following:

  • Checks for binary operators in Narrowing
  • Adding support in analysis for variadic ops
  • Correcting the incorrect shift bug above
  • Adding tests for cmp, extract and replicate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants