This is an archive of the discontinued LLVM Phabricator instance.

[DeadArgElim] Set unused arguments for internal functions
ClosedPublic

Authored by qcolombet on Apr 29 2022, 2:06 PM.

Details

Summary

Prior to this patch we would only set to undef the unused arguments of the
external functions. The rationale was that unused arguments of internal
functions wouldn't need to be turned into undef arguments because they
should have been simply eliminated by the time we reach that code.

This is actually not true because there are plenty of cases where we can't
remove unused arguments. For instance, if the internal function is used in
an indirect call, it may not be possible to change the function signature.
Yet, for statically known call-sites we would still like to mark the unused
arguments as undef.

This patch enables the "set undef arguments" optimization on internal
functions when we encounter cases where internal functions cannot be
optimized. I.e., whenever an internal function is marked "live".

Diff Detail

Event Timeline

qcolombet created this revision.Apr 29 2022, 2:06 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 29 2022, 2:06 PM
qcolombet requested review of this revision.Apr 29 2022, 2:06 PM
Herald added a project: Restricted Project. · View Herald TranscriptApr 29 2022, 2:06 PM
fhahn accepted this revision.Apr 30 2022, 4:16 AM
fhahn added a subscriber: fhahn.

LGTM, thanks!

This revision is now accepted and ready to land.Apr 30 2022, 4:16 AM
This revision was landed with ongoing or failed builds.May 2 2022, 11:16 AM
This revision was automatically updated to reflect the committed changes.
dyung added a subscriber: dyung.May 2 2022, 12:59 PM

Hi @qcolombet, your change seems to be causing 2 test failures on the PS4 build bots, can you please take a look?

https://lab.llvm.org/buildbot/#/builders/139/builds/21146
https://lab.llvm.org/buildbot/#/builders/216/builds/3762

vitalybuka reopened this revision.May 2 2022, 3:32 PM
This revision is now accepted and ready to land.May 2 2022, 3:32 PM

Ah thanks for the heads-up, looking at the clang issue.

Alright, at first I thought it was a latent bug in the dead argument elimination pass and now I think the two failing tests just need updating.
I'll upload a diff with the fix tests. (See below for the diff of the tests themselves.)

Details

I initially thought that we were wrongly optimizing noundef arguments. However, I believe that's not the case, because when we replace an argument with undef, we know that there is no use of this argument, so no undefined behavior is attached to it. This was clearly taken into account because the pass properly removes all the UB implying attributes (see Fn.removeParamAttrs(Arg.getArgNo(), UBImplyingAttributes); later in the method I modified.)

Now, regarding the two clang tests that failed: One is expecting the debug info to contain a reference to an argument that is not used and that the optimized replaced by undef and the other is tripping on the fact that we can use fastcc since we proved that the address of the block function is not used.

Here the full diff for fixing the tests.

diff --git a/clang/test/CodeGen/debug-info-block-vars.c b/clang/test/CodeGen/debug-info-block-vars.c
index dc522a807951..11c899fa3c81 100644
--- a/clang/test/CodeGen/debug-info-block-vars.c
+++ b/clang/test/CodeGen/debug-info-block-vars.c
@@ -11,7 +11,10 @@
 // CHECK: call void @llvm.dbg.declare(metadata i8** %.block_descriptor.addr,
 // CHECK-SAME:                        metadata !DIExpression())
 // CHECK-OPT-NOT: alloca
-// CHECK-OPT: call void @llvm.dbg.value(metadata i8* %.block_descriptor,
+// Since the block address is not used anywhere in this function,
+// the optimizer (DeadArgElim) has replaced all the false uses
+// (i.e., metadata users) with undef.
+// CHECK-OPT: call void @llvm.dbg.value(metadata i8* undef,
 // CHECK-OPT-SAME:                      metadata !DIExpression())
 void f(void) {
   a(^{
diff --git a/clang/test/CodeGenObjCXX/nrvo.mm b/clang/test/CodeGenObjCXX/nrvo.mm
index 89d9ae9639cc..0e4b98996965 100644
--- a/clang/test/CodeGenObjCXX/nrvo.mm
+++ b/clang/test/CodeGenObjCXX/nrvo.mm
@@ -22,7 +22,11 @@ - (X)getNRVO {
 
 X blocksNRVO() {
   return ^{
-    // CHECK-LABEL: define internal void @___Z10blocksNRVOv_block_invoke
+    // With the optimizer enabled, the DeadArgElim pass is able to
+    // mark the block litteral address argument as unused and later the
+    // related block_litteral global variable is removed.
+    // This allows to promote this call to a fastcc call.
+    // CHECK-LABEL: define internal fastcc void @___Z10blocksNRVOv_block_invoke
     X x;
     // CHECK: call void @_ZN1XC1Ev
     // CHECK-NEXT: ret void
qcolombet updated this revision to Diff 426555.May 2 2022, 7:10 PM
  • includes fixes to clang tests that were missed in the original commit.

If someone knows who we can add from the clang side to double check the tests update that would be great!

Herald added a project: Restricted Project. · View Herald TranscriptMay 2 2022, 7:10 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript

Hi @thakis , @dyung , @vitalybuka,

Thanks for the heads-up and the revert.

Fixed clang tests included in the diff.

Cheers,
-Quentin

Updating the tests makes sense to me, fwiw.

Thanks @thakis !

@fhahn are you okay with the clang tests update as well?

fhahn added a comment.May 11 2022, 1:54 PM

Thanks @thakis !

@fhahn are you okay with the clang tests update as well?

Yes looks good, thanks!

This revision was landed with ongoing or failed builds.May 12 2022, 8:56 AM
This revision was automatically updated to reflect the committed changes.

This change is breaking memory sanitizer in some cases. We observed that when the argument is not actually removed the pass is dropping the noundef attribute. See the diff snippet below. Is that an intended behavior?

We would like to revert this as it is breaking MSan tests.

Thanks,
Kirill

< define internal %struct._object* @coro_wrapper_close(%struct.PyCoroWrapper* noundef %cw, %struct._object* noundef %args) #0 !dbg !1065 {
---
> define internal %struct._object* @coro_wrapper_close(%struct.PyCoroWrapper* noundef %cw, %struct._object* %args) #0 !dbg !1065 {

This change is breaking memory sanitizer in some cases. We observed that when the argument is not actually removed the pass is dropping the noundef attribute. See the diff snippet below. Is that an intended behavior?

We would like to revert this as it is breaking MSan tests.

Thanks,
Kirill

< define internal %struct._object* @coro_wrapper_close(%struct.PyCoroWrapper* noundef %cw, %struct._object* noundef %args) #0 !dbg !1065 {
---
> define internal %struct._object* @coro_wrapper_close(%struct.PyCoroWrapper* noundef %cw, %struct._object* %args) #0 !dbg !1065 {

I guess I realized why msan, is broken. It's indirectly cased by this patch, but the problem is in our code.
This patch poisons more msan arguments (see NOTE below), but still correctly.
However I just noticed that the issues we are working with Kirill involves a peace of notinstrumented assembly, which fails to propagate Msan shadow. @kstoimenov and me will find a different solution.

*NOTE* This optimization is negative for msan with noundef mode (-fsanitizer-memory-param-retval)
If we remove noundef, then msan must to setup TLS for arguments. If it's undef, we set that into -1 to poison arg.
For msan it could be more efficient to keep noundef and pass null into that argument.
But that's not a problem of this patch, and we will address that later if needed.

llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp
268

@kstoimenov Offline I guessed that maybe our problem because we lost noundef on weak function arg, however this check protects against that.

But that's not a problem of this patch, and we will address that later if needed.

Thanks for looking into it.