I am trying to build an LLVM pass that can make predictions about the benefits of certain transformations by exploring them, i.e., sending the respective code sections ‘ahead in time’ and comparing the outcome if this or that choice is made.
To my understanding, because of how the pass pipeline is set up, this does require to build an entirely new pipeline (or at least parts of it), to which I pass the module I create from the code I want to examine.
I haven’t really found any documentation on how the pass pipeline is stitched together, so everything I know about how to do it comes from reading code present in LLVM. Specifically, I’ve been looking at EmitAssemblyHelper::EmitAssemblyWithNewPassManager in clang’s BackendUtil, as I expect the run my pass would be used in to be started from there, and I want to exactly copy what happens to the real code.
What I have achieved so far:
I have imitated the creation of the AnalysisManagers and have the PassBuilder pass information like PTO and PGOOpt to the constructor of my pass, so when the pass is run in the middle-end I can build a new PassBuilder from that information, register all the AnalysisManagers and build a pipeline (using buildPerModuleDefaultPipeline). As the old PassManager structure allows it, I can even hand over a complete CodeGen pipeline to said constructor. So my pass can run the module it creates through the middle-end passes all the way to code generation in the backend with no issues.
The middle-end run that I am getting is a ‘generic’ one, it does not exhibit the characteristics that I expect to see for the concrete architecture I specify on the command line. E.g., it is using a plain TTI instead of the target’s TTI, so for my partial code vectorization does not at all do what it would do for the normal run. Which kind of defeats the whole point of the exploration.
I have identified the issue to be the TargetMachine, which seems to be responsible for any target-specific adaptations in the middle-end. From my understanding, if it is set to nullptr, you get a generic run like I do, if it isn’t, you should get a run fine-tuned for the architecture you’re looking at. The problem is, what is the proper way to acquire a TargetMachine?
The copy-constructor of the class has been deleted, so I cannot simply hand it over to my pass like I do for the other information. In BackendUtil, the TargetMachine is constructed as a singleton from a long range of option information, some of it clang options. From trying and failing I gather that I can not include clang headers in my llvm pass, so I don’t know how to even pass all the required options to my pass if I were to build the machine on my own from scratch, which does not feel like the right thing to do anyway. I have tried passing the whole original PassBuilder to my pass, which works fine, and then taking the TargetMachine from there when creating my new PassBuilder (I added a function getTargetMachine() to the class for testing purposes). All I get is a nullptr.
Can anyone give me a hint on what I am doing wrong, or how to do it right? Or is this simply something that LLVM is not supposed to allow, period?
I do realise I could just manually add the passes I need for the architecture I’m looking at, but the idea was to make the tool usable for any architecture by just doing exactly what the ‘parent run’ is doing. Only, everything is so nicely encapsulated that I’m struggeling to access the information necessary in the place I need it.
Looking forward to any feedback or suggestions!