This is an archive of the discontinued LLVM Phabricator instance.

[Clang] Add elementwise min/max builtins.
ClosedPublic

Authored by fhahn on Oct 18 2021, 4:47 AM.

Details

Summary

This patch implements builtin_elementwise_max and
builtin_elementwise_min, as specified in D111529.

Diff Detail

Event Timeline

fhahn requested review of this revision.Oct 18 2021, 4:47 AM
fhahn created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptOct 18 2021, 4:47 AM
fhahn updated this revision to Diff 380409.Oct 18 2021, 8:25 AM

polish tests a bit

fhahn updated this revision to Diff 380946.Oct 20 2021, 7:20 AM

Move type checking to helper function checkMathBuiltinElementType to be reused in D111986.

aaron.ballman added inline comments.Oct 25 2021, 6:00 AM
clang/include/clang/Sema/Sema.h
12727–12728

<nothing to fix>Why oh why did we start slapping Sema as a prefix on a bunch of functions implemented in a class named Sema?</nothing to fix>

clang/lib/Sema/SemaChecking.cpp
1981

Spurious whitespace change?

16529–16530

Sema typically comes first, so this is just to match local style.

16531

I'm a bit surprised we'd need !Ty->getAs<VectorType>() as I would have expected !ConstantMatrixType::isValidElementType(Ty) to cover all the type checking of Ty. Can you explain why the extra check is needed here?

16539

Do we actually need this parameter?

16543–16548

Should these arguments undergo usual conversions (array to pointer decay, integer promotions, etc)?

clang/test/Sema/builtins-elementwise-math.c
21–22

Also:

i = __builtin_elementwise_max(i, i, i); // too many arguments
43

Additional tests I would like to see:

int i;
short s;

__builtin_elementwise_min(i, s); // ok (integer promotions)?

enum e { one, two };
__builtin_elementwise_min(one, two); // ok (using enums)?

enum f { three };
__builtin_elementwise_min(one, three); // ok (different enums)?

_ExtInt(32) ext;
__builtin_elementwise_min(ext, ext); // ok (using bit-precise integers)?

const int ci;
__builtin_elementwise_min(i, ci); // ok (qualifiers don't match)?
fhahn marked 5 inline comments as done.Oct 25 2021, 8:35 AM

Thanks for taking a look!

clang/lib/Sema/SemaChecking.cpp
16529–16530

Thanks, adjusted!

16531

The builtins as implemented accept either vector types or a scalar type, which then must be a valid element type for matrix types. The second check may be a bit confusing, but the restrictions laid out in the spec for scalar/element-types match the matrix element type restrictions, so this seems a convenient helper to use.

Does this in general make sense?

16539

No, it can just return ExprResult(TheCall) instead I think!

16543–16548

I intentionally went with not performing conversions, because it might lead to surprising results. If the arguments have different types, it is not clear to me which type should be chosen to try convert the other one; e.g. if we have __builtin_elementwise_max(float, int) should the first argument be converted to int` or the second one to float?

Forcing the types to match without conversion seem to make them less error-prone to use, but I might be missing some general rule to handle type conflicts here?

clang/test/Sema/builtins-elementwise-math.c
21–22

I'll add it

43

Thanks I'll add those!

at the moment __builtin_elementwise_min(i, s); // ok (integer promotions)? would be rejected, as per my response in Sema.

The other currently are not handled properly, which I'll fix!

aaron.ballman added inline comments.Oct 25 2021, 9:21 AM
clang/lib/Sema/SemaChecking.cpp
16531

Thanks for the explanation, that makes sense.

16539

Could also return a bool and have the caller fiddle with ExprResult, too. I don't have strong opinions on which way to go though.

16543–16548

I'm not certain how these builtins are expected to be used, but if they're likely to be used with literals, I think we may want integer promotions because of that alone.

Btw, given that these only have two arguments, it seems like we could use the usual arithmetic conversions to pick a common type the same way as happens for binary operators.

If you end up not adding any conversions here, we should have a test case to cover passing in two array objects (which won't decay to pointers).

clang/test/Sema/builtins-elementwise-math.c
43

I'd still like to see the test case added so it's clear we intend to reject it. It may also be wise to add a test case involving an integer literal and a short variable cast to int to make it clear that you have to add casts sometimes to make this work.

Another interesting test is where sugars don't match. e.g.,

int i;
__attribute__((address_space(0))) int addr_space_i;
typedef int foo;

__builtin_elementwise_min(i, addr_space_i); // ok (attributes don't match)?
__builtin_elementwise_min(i, foo); // ok (sugar doesn't match)?
aaron.ballman added inline comments.Oct 25 2021, 9:28 AM
clang/test/Sema/builtins-elementwise-math.c
43

You might also need to handle something like:

int (i);
int j;
__builtin_elementwise_min(i, j);  // May get a type mismatch here

So you may need to do some type canonicalization to avoid these sorts of issues.

fhahn marked 6 inline comments as done.Oct 25 2021, 10:53 AM
fhahn added inline comments.
clang/lib/Sema/SemaChecking.cpp
16543–16548

I'm not certain how these builtins are expected to be used, but if they're likely to be used with literals, I think we may want integer promotions because of that alone.

Yes, they should ideally work with literals! I think it should be relatively straight-forward to add promotion of literals.

Btw, given that these only have two arguments, it seems like we could use the usual arithmetic conversions to pick a common type the same way as happens for binary operators.

IIUC this happens in SemaOverload.cpp, but I am not sure how builtins would hook into the infrastructure there. Are there other builtins that are similarly overloaded by any chance?

aaron.ballman added inline comments.Oct 25 2021, 12:14 PM
clang/lib/Sema/SemaChecking.cpp
16543–16548

Yes, they should ideally work with literals! I think it should be relatively straight-forward to add promotion of literals.

I was thinking of the literal being an int and the variable being a short, so the literal isn't what needs promotion in that case. That said, I suppose character literals in C++ would promote from char to int, so there are some literals that could promote. That'd be a somewhat amusing test case for C and C++ both (__builtin_elementwise_max('a', 1);).

IUC this happens in SemaOverload.cpp, but I am not sure how builtins would hook into the infrastructure there. Are there other builtins that are similarly overloaded by any chance?

Sema::SemaBuiltinUnorderedCompare() uses this, for example:

ExprResult OrigArg0 = TheCall->getArg(0);
ExprResult OrigArg1 = TheCall->getArg(1);

// Do standard promotions between the two arguments, returning their common
// type.
QualType Res = UsualArithmeticConversions(
    OrigArg0, OrigArg1, TheCall->getExprLoc(), ACK_Comparison);
if (OrigArg0.isInvalid() || OrigArg1.isInvalid())
  return true;
fhahn updated this revision to Diff 382128.Oct 25 2021, 2:21 PM
fhahn marked an inline comment as done.

Address comments @aaron.ballman, thanks!

The most notable changes are using UsualArithmeticConversions for argument conversion and checking the canonical types. Also added a bunch of additional Sema tests as suggested, and some codegen tests for a couple of conversions.

fhahn marked 4 inline comments as done.Oct 25 2021, 2:24 PM
fhahn added inline comments.
clang/lib/Sema/SemaChecking.cpp
16539

updated to return a bool

16543–16548

hat'd be a somewhat amusing test case for C and C++ both (__builtin_elementwise_max('a', 1);).

I added i1 = __builtin_elementwise_max(1, 'a'); to the codegen tests.

Sema::SemaBuiltinUnorderedCompare() uses this, for example:

Thanks a lot! I updated to code to use UsualArithmeticConversions and added the tests you suggested (hope I didn't miss any important ones). Looks like all should work as expected now.

clang/test/Sema/builtins-elementwise-math.c
43

Thanks, I updated the code to check the canonical type.

aaron.ballman added inline comments.Oct 26 2021, 4:56 AM
clang/lib/Sema/SemaChecking.cpp
16564

I think you want to set this to Res, because that's the common type between TyB and TyA, right? That will also ensure that qualifiers are stripped, I believe. e.g.,

const int a = 2;
int b = 1;
static_assert(!std::is_const_v<decltype(__builtin_elementwise_max(a, b))>);
static_assert(!std::is_const_v<decltype(__builtin_elementwise_max(b, a))>);
aaron.ballman added inline comments.Oct 26 2021, 5:10 AM
clang/include/clang/Basic/DiagnosticSemaKinds.td
8747–8748

Does err_typecheck_call_different_arg_types suffice?

8750–8751

It'd be nice to avoid this entirely as the diagnostic situation is one we would have elsewhere (so we should be able to use common diagnostic checking code from here and places like SemaBuiltinMatrixColumnMajorLoad() ideally).

clang/lib/Sema/SemaChecking.cpp
16531–16536

Related to the comment above on the diagnostic, I'm wondering if we want to abstract this into a helper function that gets used by all the elementwise builtins you're adding?

I thought of another test case -- should these new functions work on complex types? (Those are weird enough we may want to consider adding both Sema and CodeGen tests if we do support them, and Sema tests alone if we don't.)

fhahn updated this revision to Diff 382300.Oct 26 2021, 6:53 AM
fhahn marked 3 inline comments as done.

Address latest comments, thanks

  • Added a generic err_builtin_invalid_arg_type diagnostic kind, which can also be used for some of the matrix type mismatches (see D112532).
  • Dropped err_elementwise_math_arg_types_mismatch in favor of the existing err_typecheck_call_different_arg_types.
  • Use Res to set the call type
  • Add tests with _Complex, which gets rejected.
  • add C++ test that check constness of builtins.

I thought of another test case -- should these new functions work on complex types? (Those are weird enough we may want to consider adding both Sema and CodeGen tests if we do support them, and Sema tests alone if we don't.)

At the moment they should not work with complex types (like C99's _Complex). The problem there is that there is no dedicated IR type and the LLVM intrinsics do not support complex types. So at the moment we cannot lower such operations effectively.

I think it would make sense to extend them to complex types once we can better model those operations in LLVM IR.

fhahn marked 3 inline comments as done.Oct 26 2021, 6:58 AM
fhahn added inline comments.
clang/include/clang/Basic/DiagnosticSemaKinds.td
8747–8748

err_typecheck_call_different_arg_types is sufficient, thanks!

8750–8751

I turned the diagnostic kind into a more generic one and also put up a patch to use them instead some of the custom matrix diagnostic kinds (D112532). WDYT?

I'm not sure if there's much potential for sharing the checking code, as it looks like most places check for slightly different things (or the check is quite compact already).

clang/lib/Sema/SemaChecking.cpp
16531–16536

Unfortunately I am not sure which code this is referring to. The element type check is already a generic function to be used to check the new builtins with 1 arg and the reduction builtins.

16564

Agreed, using TyB is confusing, so I updated it. I *think* it was still doing the right thing, because TyB is to B's type after conversion, so I *think* that would be to common type already.

I also added a .cpp Sema test file to check the constness.

aaron.ballman accepted this revision.Oct 26 2021, 7:21 AM

LGTM, thank you!

This revision is now accepted and ready to land.Oct 26 2021, 7:21 AM
This revision was automatically updated to reflect the committed changes.
fhahn marked 3 inline comments as done.