Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails #1682

Merged
merged 2 commits into from
Apr 26, 2018

Conversation

mpirvu
Copy link
Contributor

@mpirvu mpirvu commented Apr 13, 2018

Compiling OpenJ9 with a more recent compiler (e.g. gcc 5.x) can expose some
long existing bugs in two parts of the code:
(1) Allocating a TR_RelocationRecord and morphing it into a specialized
relocation record
(2) Copying an IlGeneratorMethodDetails object in
TR_MethodToBeCompiled::initialize()

In both cases the code constructs an object of a derived class on top of an
object of a base class using placement new. Of course, for this to have any
chance of success, the size of the two objects, base and derived, must be the
same and we make sure that indeed that's the case.
However, the code suffers from a subtle problem: the C++ compiler sees an
object of a base class being instantiated and it's free to devirtualize calls
off such an object, leading to the wrong code to be executed.

For clarification, let's use a generic example:

class B {
  public:
    B(param); // constructor
    virtual void foo();  
};
class D : public B {
  public:
    virtual void foo();
};
B::B(param) {
  if (param==...)
    new (this) D(); // constructing 'D' on top of existing object of type 'B'
}
---- main code ---
B b(param);
b.foo();  // A virtual call may call D::foo()
          // With devirtualization B::foo() is called always

The pattern used here is that of an object factory: based on some input we
want to create an object of a specific derived type, but return a pointer to
the base class so that it can be handled through virtual functions.
The difference from a traditional object factory is that we use placement
new to construct on top of an existing object (rather than allocating a brand
new object).
For the relocation record case mentioned above this approach has the advantage
of allocating a single TR_RelocationRecord on the stack and reusing that
storage for every record that the code needs to process.
For the ILGeneratorMethodDetails case this approach is somewhat forced by our
decision to embed such an object into TR_MethodToBeCompiled.

The simplest fix for the existing bugs is to use the object factory pattern
and allowing it to construct at a designated address, but always use the value
returned by the factory when calling virtual functions.
The example above could be modified along the following lines:

class B {
  ...
  // My object factory
  static B *create(B *storage, param) {
     if (param == ..) 
        return new (storage) D();
    ...
  }
};  

B storage; // this is where the new object is going to be created
B *b = B::create(storage, param); // create a specific object
b->foo();

@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 13, 2018

@mstoodle Mark, could you please review the first commit related to relocation records? There will be another commit (under the same PR) for IlGeneratorMethodDetails. Thanks

@mpirvu mpirvu changed the title Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails WIP: Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails Apr 13, 2018
Copy link
Contributor

@mstoodle mstoodle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will work as is, but I think the code can be improved and only one of the recommended changes could be called "onerous" but hopefully still not too bad...


static TR_RelocationRecord *create(TR_RelocationRecord *storage, TR_RelocationRuntime *reloRuntime, TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *recordPointer)
{
return decode(storage, reloRuntime, reloTarget, recordPointer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just fold decode() into create and keep create() out of the header? Nothing else calls decode, I don't think, so the extra call doesn't seem valuable to me.

break;
default:
// TODO: error condition
printf("Unexpected relo record: %d\n", reloType);fflush(stdout);
TR_ASSERT(0, "Unexpected relocation record type found");
exit(0);
}
reloRecord->_reloRuntime = reloRuntime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more code to change, but adding these fields to the constructors for all the relocation classes (which pass them into the base class constructor) to initialize would be a lot cleaner. Then you don't need to add the "setBinaryRecord()" function which isn't really something RelocationRecord should be allowing (especially as a public function).

@@ -216,6 +219,9 @@ class TR_RelocationRecord
TR_RelocationRecordBinaryTemplate *_record;

TR_RelocationRecordPrivateData _privateData;
private:
static TR_RelocationRecord * decode(TR_RelocationRecord *storage, TR_RelocationRuntime *reloRuntime, TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *record);
static uint8_t getReloType(TR_RelocationTarget *reloTarget, TR_RelocationRecordBinaryTemplate *rec);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getReloType() is probably better implemented as a nonvirtual instance function on the TR_RelocationRecordBinaryTemplate struct. That's somewhat out of character for those structs, but I like that better than adding this function here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getReloType() is used in a static function, so it has to be static as well (not an instance function). I could move it to TR_RelocationRecordBinaryTemplate, but still as a static function.

@mpirvu mpirvu force-pushed the relorecordfix branch 2 times, most recently from d920d4d to 50dc81e Compare April 13, 2018 18:31
@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 13, 2018

@mstoodle I have addressed the suggestions from the review.

When processing relocation records the JIT allocates a generic
TR_RelocationRecord on the stack and then morphs this object into a
specialized variety using placement new. The expectation is that any call
off the newly created object is going to use the vtable to invoke the
specific code associated with a specialized/derived relocation record.
However, the C++ compiler is allowed to devirtualize calls and if that
occurs, the JIT is going to invoke the implementation in the generic
TR_RelocationRecord.

The solution introduced by this commit is to use an object factory
that builds a specific relocation record at a designated address and
returns a pointer to that address. The newly created object must be
accessed only through the pointer returned by the object factory.

Signed-off-by: Marius Pirvu <[email protected]>
@mstoodle
Copy link
Contributor

Looks good for this first change. Thanks, @mpirvu !

@mpirvu mpirvu force-pushed the relorecordfix branch 2 times, most recently from 1216c84 to 2c9920a Compare April 16, 2018 14:33
@@ -50,7 +50,7 @@ TR_MethodToBeCompiled *TR_MethodToBeCompiled::allocate(J9JITConfig *jitConfig)

void TR_MethodToBeCompiled::initialize(TR::IlGeneratorMethodDetails & details, void *oldStartPC, CompilationPriority p, TR_OptimizationPlan *optimizationPlan)
{
_methodDetails = details;
_methodDetails = J9::IlGeneratorMethodDetails::create(_methodDetailsStorage, details);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not referenced J9::IlGeneratorMethodDetails directly. Only TR::IlGeneratorMethodDetails should be referenced outside of its class hierarchy.

You'll need to expose a static create() call in the TR:: version that directs down to the J9 static implementation.

Copy link
Contributor

@dsouzai dsouzai Apr 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that for code within the trj9 folder it was ok to use J9::, or is it because IlGeneratorMethodDetails is extensible that we have to use TR::. But then I see instances in CompilationThread.cpp of J9::NewInstanceThunkDetails instead of TR::NewInstanceThunkDetails.

@@ -50,8 +50,8 @@ class OMR_EXTENSIBLE IlGeneratorMethodDetails : public J9::IlGeneratorMethodDeta
IlGeneratorMethodDetails(const TR::IlGeneratorMethodDetails & other) :
J9::IlGeneratorMethodDetailsConnector(other) {}

IlGeneratorMethodDetails & operator=(const TR::IlGeneratorMethodDetails & other);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want that create function, you'll need to declare it here

}

}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and you'll need to implement create() here to call J9::IlGeneratorMethodDetails::create()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's exactly what I did, but I hit a strange compilation error in trj9/env/j9method.cpp when using the other create function:
TR::IlGeneratorMethodDetails & details = TR::IlGeneratorMethodDetails::create(storage, this);
It's like the compiler cannot distinguish between
inline static TR::IlGeneratorMethodDetails & create(TR::IlGeneratorMethodDetails & target, TR_ResolvedMethod *method);
and
static IlGeneratorMethodDetails *create(TR::IlGeneratorMethodDetails &storage, const TR::IlGeneratorMethodDetails & other);
I thought that TR_ResolvedMethod *method was considered different than TR::IlGeneratorMethodDetails & other
Changing the name of the second create function to something else solves the problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if you need a using J9::IlGeneratorMethodDetailsConnector::create; in your J9IlGeneratorMethodDetails.hpp. We've seen this before with extensible classes where we're directing the compiler to search for a method from the leaves of the hierarchy upward, and if there are two or more methods with the same name but different signatures this instructs the compiler to keep searching if it encounters a signature that doesn't match. I don't recall the name of this C++ phenomenon. See compiler/x/codegen/OMRCodeGenerator.hpp where we use it for canNullChkBeImplicit

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I am still going to change the name of function I added to 'clone' because it better reflects what it does: it takes an object and it allocates an identical object at the specified address.

@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 16, 2018

Jenkins test all

@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 17, 2018

Off the 6 builds that failed, 2 are connection issues, 2 are disk space issues and for two (JDK8 lnx 390/ppc) I see java.io.FileNotFoundException.

@mpirvu mpirvu changed the title WIP: Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails Fix devirtualization issues with TR_RelocationRecord/IlGeneratorMethodDetails Apr 17, 2018
@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 17, 2018

I verified manually on a Ubuntu 16.04 box (with gcc 5.4.0) that, before this fix, DLT methods were seen as OrdinaryMethods; after the fix they are correctly identified as MethodInProgress.

@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 17, 2018

@mstoodle Mark, the code is ready to be merged, unless you would like more modifications. Thanks

Copy link
Contributor

@mstoodle mstoodle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still a couple things to clean up, please!

{
return self()->J9::IlGeneratorMethodDetailsConnector::operator=(other);
Copy link
Contributor

@mstoodle mstoodle Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this particular forwarding function has been put into the cpp file, which means little chance it will ever be inlined...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the old structure: wherever operator= was present I replaced it with the new clone function. You are right, it's better to move it in the hpp file. Done.

@@ -74,10 +74,10 @@ class OMR_EXTENSIBLE IlGeneratorMethodDetails : public OMR::IlGeneratorMethodDet

IlGeneratorMethodDetails(const TR::IlGeneratorMethodDetails & other);

TR::IlGeneratorMethodDetails & operator=(const TR::IlGeneratorMethodDetails & other);

static TR::IlGeneratorMethodDetails & create(TR::IlGeneratorMethodDetails & target, TR_ResolvedMethod *method);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is no longer any create() implementation, is there? this declaration should be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the old create() function that is used in two places in the code. Note that the second parameter is a TR_ResolvedMethod.

When storing a particular variety of IlGeneratorMethodDetails into
MethodToBeCompiled, the code uses operator = which transmutes the existing
generic IlGeneratorMethodDetails (the base class) into a derived class
similar to the parameter that is passed in, by using placement new.
The expectation is that any call off the generic IlGeneratorMethodDetails
object stored in MethodToBeCompiled is going to be a virtual call.
However, the C++ compiler is allowed to devirtualize calls and if that
occurs, the JIT is going to invoke the implementation in the generic
IlGeneratorMethodDetails.

The solution introduced by this commit is to use an object factory
that builds a specific IlGeneratorMethodDetails at a designated address and
returns a pointer to that address which is then stored in MethodToBeCompiled.

Signed-off-by: Marius Pirvu <[email protected]>
@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 23, 2018

I removed the clone() method from TR::IlGeneratorMethodDetails so that it can call directly the implementation from J9::IlGeneratorMethodDetails through inheritance.

@mstoodle
Copy link
Contributor

jenkins test sanity

@mstoodle mstoodle merged commit c9d35c2 into eclipse-openj9:master Apr 26, 2018
@mstoodle
Copy link
Contributor

Great set of changes; thanks @mpirvu for fixing these long standing obstacles!

@JamesKingdon
Copy link
Contributor

JamesKingdon commented Apr 26, 2018

Time to warm up gcc 7 and see if we get any throughput improvements?

@mpirvu
Copy link
Contributor Author

mpirvu commented Apr 26, 2018

Throughput no, but maybe, maybe some startup time improvements from lower compilation time due to better devirtualization.

@mpirvu mpirvu deleted the relorecordfix branch December 13, 2018 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants