Skip to content
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

[Calyx] Binary Floating Point MulF Operator #7769

Merged
merged 12 commits into from
Nov 20, 2024

Conversation

jiahanxie353
Copy link
Contributor

Add support for floating point multiplication in Calyx

@rachitnigam
Copy link
Contributor

Okay, I think we should move the floating point operators into their own separate definition because they are so much more complicated. Can we get the syntax: calyx.ieee754.add and calyx.ieee754.mul working instead of adding the floating-point operations directly into the same namespace as everything else?

@jiahanxie353
Copy link
Contributor Author

marking ready for review again!


module attributes {calyx.entrypoint = "main"} {
// CHECK: import "primitives/float/mulFN.futil";
calyx.component @main(%in0: i32, %clk: i1 {clk}, %reset: i1 {reset}, %go: i1 {go}) -> (%out0: i32, %done: i1 {done}) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please add a CHECK-NEXT: for this line.

wait it is added here right? I believe you are looking for std_mulFN_0 = std_mulFN(8, 24, 32)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, can this not use CHECK-NEXT since we know exactly which line produces the primitive?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

you mean we don't need to check std_mulFN_0 = std_mulFN(8, 24, 32) at all?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, can we ensure that the code is generated precisely as a consequence of that one line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, I'm updating to CHECK-DAG to ensure your request of checking the code is generated by the correct line sequentially; as well as avoiding checking some boilerplate code that will be otherwise generated by CHECK-NEXT. Please let me know if it makes sense.

Copy link
Member

@cgyurgyik cgyurgyik left a comment

Choose a reason for hiding this comment

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

A few smaller comments.

@@ -424,6 +424,10 @@ class ComponentLoweringStateInterface {
Block *body = component.getBodyBlock();
builder.setInsertionPoint(body, body->begin());
auto name = TLibraryOp::getOperationName().split(".").second;
if (std::is_same<calyx::AddFNOp, TLibraryOp>::value)
name = "std_addFN";
Copy link
Member

Choose a reason for hiding this comment

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

Nit: isn't the better way to do this is remove the .ieee754 prefix for any F operations?

Copy link
Contributor Author

@jiahanxie353 jiahanxie353 Nov 13, 2024

Choose a reason for hiding this comment

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

The entire prefix + name is calyx.ieee754.add/calyx.ieee754.mul, removing ieee754 will simply become add/mul; but we want std_addFN/std_mulFN after emitting to Calyx native compiler.

Copy link
Member

Choose a reason for hiding this comment

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

More concretely, I think there is a better way to do this than manually write a new if statement for each floating point operation. It'd be nice to avoid this every time:

if (std::is_same<calyx::<MyNewFloatingPointOp, TLibraryOP>::value)
  name = "std_MyNewFloatingPointOp";

Ideally, since we're already distinguishing them without a _std prefix, we could infer this is some floating point operation, and then check what standard it conforms too.

/// Returns the technical floating point this operation conforms to, e.g., "ieee754"
/// (This should probably be an enum, which is subsequently converted to a string.)
std::string standard() { ... }

if (auto fOp = dyn_cast<FloatingPointOp>(op)):
  name.remove(llvm::join(".", fOp.standard()));
  name.prepend("std_");

(I'm not saying you should spend a lot of time doing this, it is a mere suggestion. In general, I do think the conforming technical standard of a floating point operation should be a field of the op so it can referenced during pattern rewrites.)

Copy link
Contributor Author

@jiahanxie353 jiahanxie353 Nov 14, 2024

Choose a reason for hiding this comment

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

I've thought about this, I don't think it's worth it now.
In the foreseeable future, only IEEE754 floating point is integrated; and moreover, only Add and Mul are present in the Berkeley HardFloat.
My opinion is we can address future needs and refactor the code when there is an actual, concrete need.
But I totally get what you are saying.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, let me take it back.
When doing some research on the floating point comparison issue #7821, I found Berkeley HardFloat has division, square root, and comparisons as well.
So I definitely need to use a better design to incorporate all of these calyx::FloatingPointOps, as you suggested.

@@ -1019,11 +1024,15 @@ void Emitter::emitLibraryFloatingPoint(Operation *op) {
return;
}

StringRef opName = op->getName().getStringRef();
std::string opName;
if (isa<calyx::AddFNOp>(op))
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

Copy link
Member

Choose a reason for hiding this comment

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

Still not a fan of this if-else, it implies that there will only ever exist two FN functions, add and mul.

@jiahanxie353 jiahanxie353 force-pushed the float-mul branch 2 times, most recently from 5e94ef3 to 36de962 Compare November 19, 2024 02:34
@jiahanxie353
Copy link
Contributor Author

I'm using FloatingPoint trait to check if an operation deals with floating point values; and use an enum to distinguish between different standards.

May not be the best design, happy to hear folks' opinions.

Copy link
Member

@cgyurgyik cgyurgyik left a comment

Choose a reason for hiding this comment

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

Nice, definitely a big improvement. A few more comments.

Comment on lines 432 to 433
std::string modified = llvm::join_items("", "std_", name, "FN");
name = modified;
Copy link
Member

Choose a reason for hiding this comment

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

To avoid a potential copy, just use the r-value:

Suggested change
std::string modified = llvm::join_items("", "std_", name, "FN");
name = modified;
name = llvm::join_items("", "std_", name, "FN");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think name = llvm::join_items("", "std_", name, "FN"); work
llvm::join_items returns a temporary std::string so it cannot directly assign to a StringRef (name here). So I had to assign it to a variable modified before assigning to name...

Is there any idiomatic way to deal with this, name = StringRef(llvm::join_items("", "std_", name, "FN"))?

Copy link
Member

Choose a reason for hiding this comment

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

Oh then isn't this a dangling reference since modified is destroyed when we exit the if statement? I think the safest course of action is to make name a std::string.

include/circt/Dialect/Calyx/CalyxLoweringUtils.h Outdated Show resolved Hide resolved
Comment on lines 428 to 429
auto standard = TLibraryOp::getFloatingPointStandard();
if (standard == FloatingPointStandard::IEEE754) {
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 a switch would be appropriate here, so that that if someone adds another case the compiler will complain if we don't update this.

Suggested change
auto standard = TLibraryOp::getFloatingPointStandard();
if (standard == FloatingPointStandard::IEEE754) {
switch(TLibraryOp::getFloatingPointStandard()) {
case FloatingPointStandard::IEEE754:
...
}

@@ -1019,11 +1024,15 @@ void Emitter::emitLibraryFloatingPoint(Operation *op) {
return;
}

StringRef opName = op->getName().getStringRef();
std::string opName;
if (isa<calyx::AddFNOp>(op))
Copy link
Member

Choose a reason for hiding this comment

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

Still not a fan of this if-else, it implies that there will only ever exist two FN functions, add and mul.

IEEE754,
};

/// Signals that the following operation operates on floating point values.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Signals that the following operation operates on floating point values.
/// Signals that the following operation operates on floating point values.
/// Unlike other CIRCT dialects, Calyx treats inputs as signless values and
/// leaves their interpretation to the respective operation.


def AddFNOp : ArithBinaryFloatingPointLibraryOp<"addFN"> {
def AddFNOp : ArithBinaryFloatingPointLibraryOp<"ieee754.add"> {
Copy link
Member

@cgyurgyik cgyurgyik Nov 19, 2024

Choose a reason for hiding this comment

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

This is a big improvement. IMO, a better name for this might be: AddFOpIeee754 and similar for below. I imagine future ops would follow similar patterns, e.g., AddFOpBf16.

@jiahanxie353
Copy link
Contributor Author

I have fixed the StringRef vs std::string for name problem;
and have a re-design for distinguish if it's a floating point operation using FloatingPointInterface and SFINAE, so that getting the standard and the Calyx library name is more elegant.

if constexpr (HasGetFloatingPointStandard<TLibraryOp>::value) {
switch (TLibraryOp::getFloatingPointStandard()) {
case FloatingPointStandard::IEEE754: {
std::string prefix = "ieee754.";
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
std::string prefix = "ieee754.";
constexpr char[] prefix = "ieee754.";

case FloatingPointStandard::IEEE754: {
std::string prefix = "ieee754.";
assert(name.find(prefix) == 0 &&
"IEEE754 type operation's name must begin with 'ieee754'");
Copy link
Member

Choose a reason for hiding this comment

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

Code duplication nit: use prefix instead of repeating the string ieee754.

@@ -417,13 +417,37 @@ class ComponentLoweringStateInterface {
}
}

template <typename T, typename = void>
struct HasGetFloatingPointStandard : std::false_type {};
Copy link
Member

Choose a reason for hiding this comment

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

Suggestion: this name is a bit confusing, maybe just IsFloatingPoint?

@jiahanxie353
Copy link
Contributor Author

thanks for the suggestions @cgyurgyik ! I've also changed AddFNOp/MulFNOp to better naming standards

Copy link
Member

@cgyurgyik cgyurgyik 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 good to me. I believe Rachit is on vacation, so I think it would be ok to merge (when CI passes) if this is blocking you, and then address any comments later.

@jiahanxie353
Copy link
Contributor Author

This looks good to me. I believe Rachit is on vacation, so I think it would be ok to merge (when CI passes) if this is blocking you, and then address any comments later.

Sounds good, thank you!

@jiahanxie353 jiahanxie353 merged commit b2aebf0 into llvm:main Nov 20, 2024
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Calyx The Calyx dialect
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants