This is an archive of the discontinued LLVM Phabricator instance.

[Clang][OpenMP] Fix the issue that `llvm.lifetime.end` is emitted too early for variables captured in linear clause
ClosedPublic

Authored by tianshilei1992 on Aug 5 2022, 10:42 AM.

Details

Summary

Currently if an OpenMP program uses linear clause, and is compiled with
optimization, llvm.lifetime.end for variables listed in linear clause are
emitted too early such that there could still be uses after that. Let's take the
following code as example:

// loop.c
int j;
int *u;

void loop(int n) {
  int i;
  for (i = 0; i < n; ++i) {
    ++j;
    u = &j;
  }
}

We compile using the command:

clang -cc1 -fopenmp-simd -O3 -x c -triple x86_64-apple-darwin10 -emit-llvm loop.c -o loop.ll

The following IR (simplified) will be generated:

@j = local_unnamed_addr global i32 0, align 4
@u = local_unnamed_addr global ptr null, align 8

define void @loop(i32 noundef %n) local_unnamed_addr {
entry:
  %j = alloca i32, align 4
  %cmp = icmp sgt i32 %n, 0
  br i1 %cmp, label %simd.if.then, label %simd.if.end

simd.if.then:                                     ; preds = %entry
  call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %j)
  store ptr %j, ptr @u, align 8
  call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %j)
  %0 = load i32, ptr %j, align 4
  store i32 %0, ptr @j, align 4
  br label %simd.if.end

simd.if.end:                                      ; preds = %simd.if.then, %entry
  ret void
}

The most important part is:

call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %j)
%0 = load i32, ptr %j, align 4
store i32 %0, ptr @j, align 4

%j is still loaded after @llvm.lifetime.end.p0(i64 4, ptr nonnull %j). This
could cause the backend incorrectly optimizes the code and further generates
incorrect code. The root cause is, when we emit a construct that could have
linear clause, it usually has the following pattern:

EmitOMPLinearClauseInit(S)
{
  OMPPrivateScope LoopScope(*this);
  ...
  EmitOMPLinearClause(S, LoopScope);
  ...
  (void)LoopScope.Privatize();
  ...
}
EmitOMPLinearClauseFinal(S, [](CodeGenFunction &) { return nullptr; });

Variables that need to be privatized are added into LoopScope, which also
serves as a RAII object. When LoopScope is destructed and if optimization is
enabled, a @llvm.lifetime.end is also emitted for each privatized variable.
However, the writing back to original variables in linear clause happens after
the scope in EmitOMPLinearClauseFinal, causing the issue we see above.

A quick "fix" seems to be, moving EmitOMPLinearClauseFinal inside the scope.
However, it doesn't work. That's because the local variable map has been updated
by LoopScope such that a variable declaration is mapped to the privatized
variable, instead of the actual one. In that way, the following code will be
generated:

%0 = load i32, ptr %j, align 4
store i32 %0, ptr %j, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %j)

Well, now the life time is correct, but apparently the writing back is broken.

In this patch, a new function OMPPrivateScope::restoreMap is added and called
before calling EmitOMPLinearClauseFinal. This can make sure that
EmitOMPLinearClauseFinal can find the orignal varaibls to write back.

Fixes #56913.

Diff Detail

Event Timeline

tianshilei1992 created this revision.Aug 5 2022, 10:42 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 5 2022, 10:42 AM
tianshilei1992 requested review of this revision.Aug 5 2022, 10:42 AM
Herald added a project: Restricted Project. · View Herald TranscriptAug 5 2022, 10:42 AM
Matt added a subscriber: Matt.Aug 5 2022, 10:45 AM
tianshilei1992 edited the summary of this revision. (Show Details)Aug 5 2022, 10:45 AM
tianshilei1992 edited the summary of this revision. (Show Details)Aug 5 2022, 10:48 AM
ABataev added inline comments.Aug 5 2022, 11:00 AM
clang/lib/CodeGen/CodeGenFunction.h
1070

Better to make it a property of MappedVars. Also, are there any issues if just restore it several times without checking?

tianshilei1992 added inline comments.Aug 5 2022, 11:28 AM
clang/lib/CodeGen/CodeGenFunction.h
1070

Better to make it a property of MappedVars.

Will do.

Also, are there any issues if just restore it several times without checking?

TBH, I don't know clearly, but I feel it should not be a problem. We will not have two scopes at the same time, will we? But I will add an assertion that if the mapped variables have been restored, addPrivate should not be called anymore.

rebase and fix comments

tianshilei1992 marked an inline comment as done.Aug 5 2022, 9:35 PM
tianshilei1992 added inline comments.
clang/lib/CodeGen/CodeGenFunction.h
1070

We don't have to worry about restore for multiple times as every time it is called, the container is cleared.

This revision is now accepted and ready to land.Aug 5 2022, 9:43 PM