Hi Bjoern,
Woah! That mail contains a lot of information and things I never tried yet… Actually… the entire MaterializationUnit and MaterializationResponsibility part is… quite… overwhelming >O<
The names sound intimidating, but these classes are actually relatively simple:
– MaterializationUnit wraps a program representation, a map describing the symbols that the program representation provides, and a callback to materialize the program representation. For example a materialization unit for an object file would contain the object file memory buffer, a list of symbols defined in the object file, and a callback to an object linking layer to link the object file. The most important method on MaterializationUnit is the materialize method: The JIT will call this when you look up any symbols that the unit provides.
– MaterializationResponsibility objects get created when the JIT calls the materialize method on a MaterializationUnit. They track the symbols being materialized and provide a way to update the ExecutionSession, unblocking any queries waiting on these particular symbols. Usually you just have to pass them along to the JIT linker, but you can also interact with them directly. The most important methods are notifyResolved, which assigns addresses to symbols; notifyEmitted, which tells the ExecutionSession that these symbols have been completely emitted; addDependencies, which describes dependencies between symbols so that the ExecutionSession can hold queries until symbols and all of their dependencies are safe to access; and finally failMaterialization which can be used to tell ExecutionSession that some symbols really couldn’t be provided.
With “pop up” I mean… the process which is waiting for Module “Planschi” to “pop up” can not do a thing about it. It just waits until there is an table entry for it, indicating that the object file was loaded by another process – or not.
How do you decide when to stop waiting? There must be some way to detect that the process has been completed otherwise a missing module would cause you to wait forever. Do you just have a timeout? Or does it complete when all processes have been asked whether they can produce Planschi and have returned “no”?
To understand the MU and MR things better…. I try now to explain the flow of program…
Yes – Everything you described is correct, so I’ll skip ahead to where things get fuzzy.
Normally I would call the “define” function of the DyLib I got (in the tryToGenerate function) and providing an absolute value. But I can’t because “Planschi” didn’t popped up yet. So I create instead a “MyTentativeDefinitionMaterializationUnit" and stuff it into the define function right?
„MyTentativeDefinitionMaterializationUnit“ is something that inherits from „TentativeDefinitionMaterializationUnit" right? Now I have those overloaded functions where I need to provide implantation for it. This is kinda shady to me, but this will make waaaay more sense when I looked at it I guess.
Let’s define a basic MyTentativeMaterializationUnit (there’s no TentativeMaterializationUnit – that name was just an example to convey the concept):
class MyTentativeDefinitionMaterializationUnit
: public MaterializationUnit {
public:
// Constructor tells us the name of the module (e.g. Planschi) that
// we want to try to load, and the ObjectLinkingLayer that we will
// use to load it.
MyTentativeDefinitionMaterializationUnit(StringRef ModuleName,
ObjectLinkingLayer &LinkLayer,
SymbolFlagsMap InitalSymbolFlags,
SymbolStringPtr InitSymbol,
VModuleKey K)
: MaterializationUnit(std::move(InitialSymbolFlags),
std::move(InitSymbol), K),
ModuleName(ModuleName), LinkLayer(LinkLayer) {}
// getName is used in JIT debug logging.
StringRef getName() const override {
return “MyTentativeDefinitionMaterializationUnit”;
}
// Our materialize method will be called by the JIT outside the session lock
// to materialize the symbols contained in R->getSymbols() (which is the same
// set of symbols passed in to the constructor as InitialSymbolFlags).
void materialize(std::unique_ptr R) override {
dbgs() << "I am a tentative definition unit trying to materialize "
"the following symbols for module " << ModuleName << ": "
<< R->getSymbols() << “\n”;
// Try to produce an object file for ModuleName. This is where all the work
// specific to your use-case will happen.
Expected<std::unique_ptr> ObjectForModule =
requestObjectForModule(ModuleName);
// If we didn’t get an object file then register the failure and bail out.
if (!ObjectForModule) {
LinkLayer.getExecutionSession().reportError(ObjectForModule.takeError());
R->failMaterialization();
return.
}
// If we did get an object file back then we need to check the set of
// definitions that it provided: There might be more than just the symbols
// we were searching for (e.g. if module Planschi defines “test” and “foo”
// and we only created this tentative unit to cover “test” then we need to
// tell the JIT that we’re taking responsibility for “foo” too.
auto ProvidedSymbols = getObjectSymbolInfo(LinkLayer.getExecutionSession(),
*ObjectForModule);
// If there was something wrong with the object and we couldn’t get the set
// of symbols that it defines then bail out.
if (!ProvidedSymbols) {
LinkLayer.getExecutionSession().reportError(ProvidedSymbols.takeError());
R->failMaterialization();
return;
}
// Otherwise we can define the set of new symbols as the set of all defined
// symbols minus the ones we already knew about:
SymbolFlagsMap NewSymbols = std::move(ProvidedSymbols->first);
for (auto &KV : R->getSymbols())
NewSymbols.erase(KV.first);
// If the set of new symbols is non-empty then notify the JIT about the
// new symbols.
if (!NewSymbols.empty()) {
if (auto Err = R->defineMaterializing(std::move(NewSymbols))) {
// If there was an error, for example one of our new symbols clashed
// with an existing definition, then bail out.
LinkLayer.getExecutionSession().reportError(std::move(Err));
R->failMaterialization();
return;
}
}
// Otherwise we’re all done: hand the object off to be linked.
LinkLayer.emit(std::move(R), std::move(*ObjectForModule));
}
private:
StringRef ModuleName;
ObjectLinkingLayer &LinkLayer;
};
All the interesting stuff for your specific system goes in to the call “requestObjectForModule”. It should return an object file buffer for the module if it is able, or an error otherwise (if the module can’t be found).
If you want to make all of this asynchronous you can always write requestObjectForModule as an asynchronous operation and do the rest of the work contained in the materialize method above in a callback.
However… Now I’m lost…
What will happen now? So I gave „MyTentativeDefinitionMaterializationUnit“ To “Planschi_test” while doing the lookup and…. For some reason “Planschi” has not popped up yet. Where will the waiting for it happen? In one of the “MyTentativeDefinitionMaterializationUnit" functions? Will the lookup I did in Step 3 return or will it be blocked until I used the MaterializationResponsibility To finally resolve the symbol?
I’m also not sure yet where exactly the code snippet you showed would go to…. But I guess it is in one of the functions of “MyTentativeDefinitionMaterializationUnit" right?
The MyTentativeDefinitionMaterializationUnit represents an attempt to materialize the symbol you’re looking up (“test”) from an as-yet-unknown module (“Planschi”). The motivatino for doing this in a MaterializationUnit rather than the definition generator is that the definition generator runs under the session lock, so it blocks any other JIT operations from continuing. If you have any circular dependencies between modules this will deadlock the JIT.
Uh… Thank you again and sorry for any stupid question in this…
Nope. No stupid questions – this is tricky (and not as well documented as I’d like). Hopefully the discussion has been helpful to you, and it’s definitely useful to me to hear what JIT clients are trying to do and where the APIs are unclear.
– Lang.