This is an archive of the discontinued LLVM Phabricator instance.

Add a jumptable attribute and support for creating jump-instruction tables
ClosedPublic

Authored by tmroeder on Apr 11 2014, 3:00 PM.

Details

Reviewers
tmroeder
Summary

This patch provides a new jumptable attribute and associated CodeGen support to generate jump-instruction tables and replace function references for jumptable-annotated functions. The work of generating the tables is now done with an IR pass in CodeGen to replace address-taken functions, along with a change to AsmPrinter to generate the assembly for the tables. This is a followup to the discussion on llvmdev. See http://lists.cs.uiuc.edu/pipermail/llvmdev/2014-April/071866.html

This version of the patch has support for X86 and ARM backends, along with tests for each.

Diff Detail

Event Timeline

So first, a design comment. The 'jumptable' attribute itself has no IR
meaning. It doesn't change anything about the execution of the IR, it
doesn't change whether IR is valid, even. It appears to be an ABI attribute.

What's the right behaviour if you link together two modules where a
function is not "jumptable" in one but is in the other? Is it safe to apply
"jumptable" to a module that didn't have it? It looks like not. I think the
answer is that two modules needed to both have this attribute set the same
way in the two modules before merging, and that this is reasonable because
it's an ABI issue. Similarly, if you generated those two modules into .o
files, they wouldn't be ABI compatible in machine code either, right?

+ Don't do the replacement if this use is a direct call to this
function.
+​ if ((CS.isCall() || CS.isInvoke()) && !isIndirectCall(CS)) {
+​
If the use is not the called value, then replace it.
+​ if (CS.getCalledValue() != GV) {
+​ U->set(V);
+​ return true;
+ }

I think you have a bug here when the value is used both as the callee and
is also an argument. Instead you can use CS.isCallee(U); to determine
whether this particular use of your value is a use as the callee operand or
not.

"CS.isCall() || CS.isInvoke()" simplifies to "CS" used in a bool context.

+ Go through all uses of this function and replace the uses of GV with
the
+
jump-table version of the function. Get the uses as a vector before
+ replacing them, since replacing them changes the use list and
invalidates
+
the iterator otherwise.
+ std::vector<Use*> Uses;
+ for (Use &U : GV->uses())
+ Uses.push_back(&U);
+
+ for(Use *U : Uses)
+ replaceGlobalValueIndirectUse(GV, F, U);

Missing space after for.

The common way to handle the invalidation is more like:

for (Value::use_iterator I = GV->use_begin(), E = GV->use_end(); I != E;)

{

  Use &U = *UI++;
  replaceGlobalValueIndirectUse(GV, F, U);
}

though that can probably be C++11ified now.

+ Type *Int32Ty = Type::getInt32Ty(getGlobalContext());

Nope. Let me explain what a context is for.

Suppose you have a game, and it uses a graphics library and it uses a 3d
audio library. Both of those in turn use llvm under the hood, but they
aren't aware of each other's existence. The purpose of the llvm context is
to keep "global" (or at least, "outside the Module") issues out of the
program-level global context, so that these two users can act independently
and not interfere with each other in any way.

The context for a module must match the context for everything inside it.

Finally, they're used for supporting threading. Types are pointer
comparable, if you construct the same type twice you get the same Type*.
The mapping that holds these is not locked, it's assumed that you only do
one operation on them at a time per-thread. If someone were to create two
threads each with their own LLVMContext to keep things separate, they
couldn't both call JumpInstrTables::transformType() since your function
would cause racy access to the internal map inside the global context.

+ return Functions.size() > 0;

DenseMap has an empty() method.

Nick

Thanks for the comments.

tmroeder updated this revision to Diff 9397.May 14 2014, 11:57 AM

This diff fixes the problems noted in the review. PTAL.

For the for-loop invalidation problem, I didn't see any way to increment the implicit iterator early in C++11 style, so I've gone back to a standard iterator for loop to match the style of other invalidation-sensitive Use-handling loops.

Also, as I was working on these fixes, I found a problem with GlobalAlias: it doesn't support replaceUsesOfWithOnConstant, and it cannot target declaration-only functions. So, I've added some special-case handling for GlobalAlias statements that target jumptable functions. I've also added a new test case for "alias".

tmroeder updated this revision to Diff 9782.May 23 2014, 4:07 PM

This version of the patch fixes the problems from the most recent review. It also adds the requirement that jumptable functions must also be unnamed_addr; this is now enforced by the Verifier. PTAL.

tmroeder accepted this revision.Jul 10 2014, 2:46 PM
tmroeder added a reviewer: tmroeder.
This revision is now accepted and ready to land.Jul 10 2014, 2:46 PM
tmroeder closed this revision.Jul 10 2014, 2:47 PM