zhaomo added a comment.
Addressed @rsmith's comments.
================
Comment at: clang/include/clang/Driver/CC1Options.td:356-357
+ HelpText<"Enable VTable interleaving">;
+def disable_vtable_interleaving : Flag<["-"], "disable-vtable-interleaving">,
+ HelpText<"Disable VTable interleaving">;
//===----------------------------------------------------------------------===//
----------------
rsmith wrote:
> We usually only expose the non-default flag in `-cc1`, so that there are no
> ordering concerns and we can determine whether a feature is enabled with just
> `hasArg`. Also, `-fvtable-interleaving` would seem like a more natural flag
> name to me.
Changed it in the latest patch.
================
Comment at: clang/include/clang/Frontend/CodeGenOptions.h:274
+ /// the interleaving layout is decided.
+ bool EnableVTableInterleaving;
+
----------------
rsmith wrote:
> `Enable` here seems redundant. Is thee a reason to declare this explicitly
> rather than adding it to CodeGenOptions.def?
Changed it in the latest patch.
================
Comment at: clang/lib/CodeGen/ItaniumCXXABI.cpp:973-976
+ // The name of a offset global variable has the format "__[type
+ // id]$[offset]".
+ std::string Name =
+ "__" + RD->getNameAsString() + "$" + std::to_string(Offset);
----------------
rsmith wrote:
> The `__`s here are atypical. If you want a name that can't collide with a
> name generated by the frontend, putting a period in it is the usual strategy.
> Also, a name prefix that indicates what this global is for would make the IR
> more readable and make this less likely to collide with other things. Maybe
> `clang.vtable.index.<CLASS>.<OFFSET>` or similar?
>
> Also, there's no guarantee that `RD->getNameAsString()` produces something
> unique within the translation unit, so you'll need to make sure that name
> collisions are handled somehow (I *think* `GlobalVariable` already deals with
> this for you, but I'm not sure... however, I think its strategy is to add a
> number to the end of the mangling, which will lead to some quite peculiar
> results given that you already have a number there). It might be easier to
> base the name of this symbol on the mangled name of the class; then you could
> give the global variable the same linkage as the class, which might save your
> interleaving pass a little bit of work later on as the duplicates will
> already have been combined -- but that's up to you, and I don't have enough
> of the big picture here to give you advice.
The encoding of the name of such a global variable is just for debugging
purposes. The back-end pass does not care about the name. Instead, it relies on
the metadata associated with the global variable, and the metadata does not
rely on `RD->getNameAsString() `. Also, the back-end pass does not care how
many global variables associated with the same metadata.
I did not know that it is using periods is a convention in LLVM. I changed the
name to `clang.vtable.offset.<CLASS>.<OFFSET>`.
================
Comment at: clang/lib/CodeGen/ItaniumCXXABI.cpp:987
+ }
+ return llvm::ConstantExpr::getPtrToInt(OffsetGV, OffsetType);
+}
----------------
rsmith wrote:
> Representing this offset as a `ptrtoint` on a global variable seems really
> strange. Is there really no better way to model this in IR? (I mean, if not,
> then carry on as you are, but this seems like a hack.)
The global variable we create for each referenced vtable offset is actually a
zero-length array, and we use the address of the array as the placeholder for a
referenced vtable offset. I was told that this is a common trick used in LLVM.
@pcc: Peter, would you provide more information here?
================
Comment at: clang/lib/CodeGen/ItaniumCXXABI.cpp:1767
+ bool InBounds = shouldInterleaveVTables(VTableClass) ? false : true;
return llvm::ConstantExpr::getGetElementPtr(VTable->getValueType(), VTable,
----------------
rsmith wrote:
> zhaomo wrote:
> > pcc wrote:
> > > Remind me why this needs to be here? (And the explanation needs to be in
> > > a comment.)
> > The calculation of address point is essentially base_vtable_addr + offset,
> > where offset is from the indices of gep.
> > In the interleaving pass, we replace base_vtable_addr with
> > (addr_point_in_interleaved_layout - offset).
> >
> > The LLVM language reference says that the base address of a inbounds gep
> > must be an in bound address of the object. The new base address
> > addr_point_in_interleaved_layout - offset, however, may not be an in bound
> > address.
> I still find the need for this confusing. Suppose we have:
>
> ```
> struct A { virtual void f(); };
> struct B { virtual void g(); virtual void h(); };
> struct C : A, B {};
> ```
>
> such that `C` has a naive vtable containing 7 elements {{offset, typeid,
> &A::f}, {offset, typeid, &B::f, &B::h}}, with address point indexes (0, 2)
> and (1, 2). Here we emit a `gep inbounds @vtable, 0, 1, 2` to get the B-in-C
> vtable. Now, if this were instead emitted as `gep inbounds (cast @vtable to
> i8*), 24`, and the interleaving process were replacing @vtable with a
> non-`inbounds` GEP on its interleaved ubervtable, I could understand the need
> to drop `inbounds` here -- because the replacement for @vtable might be
> before the start of the ubervtable. But based on my reading of the algorithm
> description, that's not what happens: first we split `@vtable` into multiple
> individual vtables, and that splitting process presumably must identify these
> GEPs to figure out which slice of the vtable they need. That presumably
> either removes this GEP entirely, or leaves this being a correct and trivial
> `inbounds` GEP on a split-out piece of the vtable. (I'm imagining we
> effectively rewrite `gep inbounds @vtable, 0, 1, 2` as `@vtable-slice-2` as
> part of the splitting.) And then after interleaving, I'm imagining we replace
> all uses of `@vtable-slice-2` with the appropriate address point in the
> ubervtable, which is again an `inbounds` GEP (but that would be a new GEP,
> unrelated to this one).
>
> That is, if you need this change, it seems like that indicates a bug in the
> vtable splitter. But maybe I'm misunderstanding how this all fits together.
GlobalSplit, the pass that splits vtables, relies on `inrange` instead of
`inbounds`.
From the language reference:
> If the inrange keyword is present before any index, loading from or storing
> to any pointer derived from the getelementptr has undefined behavior if the
> load or store would access memory outside of the bounds of the element
> selected by the index marked as inrange.
`inbounds` means something different. Again from the language reference:
> If the inbounds keyword is present,** the result value of the getelementptr
> is a poison value if the base pointer is not an in bounds address of an
> allocated object**, or if any of the addresses that would be formed by
> successive addition of the offsets implied by the indices to the base address
> with infinitely precise signed arithmetic are not an in bounds address of
> that allocated object.
We need to drop `inbounds` because that the **base pointer** may not be in
bounds address of an interleaved vtable.
Here is how an address point is calculated in an original vtable.
`original_address_pt` = `start_original_vtable` + `original_offset`, where
`start_original_vtable` is the address of the original vtable and
`original_offset` is the offset of the address point in the original vtable.
Our interleaving pass calculates the address of this address point in the
interleaved object. Let's call it `new_address_pt` and `new_address_pt` =
`start_interleaved_vtable` + `new_offset`.
Then we replace `start_original_vtable` with `(new_address_pt -
original_offset)`. Because `start_original_vtable` is the **base pointer** of
the `gep`, and `new_offset` may be smaller than `original_offset`, we have to
drop inbounds.
> I'm imagining we effectively rewrite gep inbounds @vtable, 0, 1, 2 as
> @vtable-slice-2
gep inbounds @vtable, 0, 1, 2 is rewritten to gep inbounds @vtable-slice-2, 0,
2 in your example.
================
Comment at: clang/lib/CodeGen/ItaniumCXXABI.cpp:1832
+ auto *RecordDecl = MethodDecl->getParent();
+ llvm::Value *VTable = CGF.GetVTablePtr(This, CGM.Int8PtrTy, RecordDecl);
----------------
rsmith wrote:
> Do you really need to change this to load the vptr at the "wrong" type and
> then cast it? (Could the cast, if necessary at all, live in
> `EmitVTableTypeCheckedLoad` instead?)
I have to because both branches of the if statement rely on `OffsetConstant`,
which is a byte offset.
================
Comment at: clang/lib/CodeGen/ItaniumCXXABI.cpp:3618-3622
+ if (!CXXABI.shouldInterleaveVTables(RD)) {
+ uint64_t UpperBits = (uint64_t)Offset << 8;
+ uint64_t OffsetFlags = UpperBits | LowerBits;
+ return llvm::ConstantInt::get(OffsetFlagsTy, OffsetFlags);
+ }
----------------
rsmith wrote:
> Shouldn't you also do this if the base is non-virtual? In that case,`Offset`
> is an offset within the class layout, not an index into the vtable.
You are right. This bug is fixed in the latest patch.
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D51905/new/
https://reviews.llvm.org/D51905
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits