This is an archive of the discontinued LLVM Phabricator instance.

[clang] Fix some clang->llvm type cache invalidation issues
ClosedPublic

Authored by aeubanks on Feb 1 2022, 4:58 PM.

Details

Summary

Take the following as an example

struct z {
  z (*p)();
};

z f();

When we attempt to get the LLVM type of f, we recurse into z. z itself
has a function pointer with the same type as f. Given the recursion,
Clang simply treats z::p as a pointer to an empty struct {}*. The
LLVM type of f is as expected. So we have two different potential
LLVM types for a given Clang type. If we store one of those into the
cache, when we access the cache with a different context (e.g. we
are/aren't recursing on z) we may get an incorrect result. There is some
attempt to clear the cache in these cases, but it doesn't seem to handle
all cases.

This change makes it so we only use the cache when we are not in any
sort of function context, i.e. `noRecordsBeingLaidOut() &&
FunctionsBeingProcessed.empty()`, which are the cases where we may
decide to choose a different LLVM type for a given Clang type. LLVM
types for builtin types are never recursive so they're always ok.

This allows us to clear the type cache less often (as seen with the
removal of one of the calls to TypeCache.clear()). We
still need to clear it when we use a placeholder type then replace it
later with the final type and other dependent types need to be
recalculated.

I've added a check that the cached type matches what we compute. It
triggered in this test case without the fix. It's currently not
check-clang clean so it's not on by default for something like expensive
checks builds.

This change uncovered another issue where the LLVM types for an argument
and its local temporary don't match. For example in type-cache-3, when
expanding z::dc's argument into a temporary alloca, we ConvertType() the
type of z::p which is void ({}*)*, which doesn't match the alloca GEP
type of {}*.

No noticeable compile time changes:
https://llvm-compile-time-tracker.com/compare.php?from=3918dd6b8acf8c5886b9921138312d1c638b2937&to=50bdec9836ed40e38ece0657f3058e730adffc4c&stat=instructions

Fixes #53465.

Diff Detail

Event Timeline

aeubanks requested review of this revision.Feb 1 2022, 4:58 PM
aeubanks created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptFeb 1 2022, 4:58 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
aeubanks edited the summary of this revision. (Show Details)Feb 1 2022, 5:04 PM
aeubanks added reviewers: rnk, rsmith.
aeubanks planned changes to this revision.Feb 1 2022, 5:15 PM

I'm seeing a similar crash with

struct z {
  static z dc(z);
  z (*di)(z);
};
z bar = z::dc({});

going to try to fix that here as well

aeubanks updated this revision to Diff 406077.Feb 4 2022, 12:43 PM

fix more issues, ready to review
I've split the OpenCL change out into D119011, will rebase once that's submitted

aeubanks retitled this revision from [clang] Don't cache function type after clearing clang->llvm type cache to [clang] Fix some clang->llvm type cache invalidation issues.Feb 4 2022, 12:45 PM
aeubanks edited the summary of this revision. (Show Details)
rnk added a comment.Feb 7 2022, 11:16 AM

This seems unfortunately complex, but I think we can live with it for a year or two.

Is it possible to use the compile time tracker to benchmark if this clang->LLVM type cache actually saves compile time? This change disables the cache pretty often, and I'm wondering if "pretty often" is close enough to "all the time" to let us remove the cache altogether.


Since the LLVM type for z::p can't reference z, Clang simply treats z::p as a pointer to an empty struct {}*.

I don't think this is strictly true. LLVM IR struct types can effectively be forward declared when building IR, using the two-phase StructType::create/setBody APIs. Consider the usual linked list example:

struct z { z *next; };
z head;

-->

%struct.z = type { %struct.z* }
@"?head@@3Uz@@A" = dso_local local_unnamed_addr global %struct.z zeroinitializer, align 4, !dbg !0

The IR for function prototypes can work similarly, we could produce this IR struct type:

%struct.z = type { (%struct.z (%struct.z))* }

Maybe that's hard to do today in clang, and ultimately it's probably not worth investing in generating pretty (function) pointer types given the impending transition to opaque pointers.

clang/lib/CodeGen/CodeGenTypes.cpp
34

This cl::opt seems reasonable if we don't expect it to live very long.

This seems unfortunately complex, but I think we can live with it for a year or two.

Is it possible to use the compile time tracker to benchmark if this clang->LLVM type cache actually saves compile time? This change disables the cache pretty often, and I'm wondering if "pretty often" is close enough to "all the time" to let us remove the cache altogether.

Yeah I measured disabling the cache and saw some slight regressions.


Since the LLVM type for z::p can't reference z, Clang simply treats z::p as a pointer to an empty struct {}*.

I don't think this is strictly true. LLVM IR struct types can effectively be forward declared when building IR, using the two-phase StructType::create/setBody APIs. Consider the usual linked list example:

struct z { z *next; };
z head;

-->

%struct.z = type { %struct.z* }
@"?head@@3Uz@@A" = dso_local local_unnamed_addr global %struct.z zeroinitializer, align 4, !dbg !0

Ah you're right, I'll update the description

The IR for function prototypes can work similarly, we could produce this IR struct type:

%struct.z = type { (%struct.z (%struct.z))* }

Maybe that's hard to do today in clang, and ultimately it's probably not worth investing in generating pretty (function) pointer types given the impending transition to opaque pointers.

There's a lot of code around choosing proper types (it's not as simple as mapping a C++ struct z to LLVM %struct.z), as you said it would take some time and it's probably not worth it given opaque pointers.

aeubanks edited the summary of this revision. (Show Details)Feb 7 2022, 2:19 PM
rnk accepted this revision.Feb 7 2022, 3:53 PM

lgtm

This revision is now accepted and ready to land.Feb 7 2022, 3:53 PM
This revision was landed with ongoing or failed builds.Feb 7 2022, 6:59 PM
This revision was automatically updated to reflect the committed changes.