Directly modify compiled method without recompilation

I’ve never used it myself (because I never needed to), but there is llvmcall. It, well, calls LLVM IR represented by a Julia instance like a String, which provides a similar functionality to how C can inline assembly. I’m not aware of an analogous method for other forms of lowered code. I think the code_llvm method can get you LLVM IR represented like that, but I don’t know exactly how.

So hypothetically, you could compile one original function, get the code_llvm string, and the rest of your functions are made by editing that string and evaluating an expression for a function llvmcalling the edited string. $-interpolation might come in handy for representing an edited string, not sure. Each function still goes through the entire compilation process, it’s just that many compilation steps can’t do anything to LLVM IR.

Again, I would only consider this as a last resort; ask about and consider alternative pure-Julia approaches to your problem for a little while longer. Because writing and editing assembly-like language is really REALLY hard. I wouldn’t be surprised if you get stuck trying to figure out how to edit the LLVM IR to correctly reflect an imagined edit to the source code. High level languages and heavily layered compilers exist for this reason, and all the examples of llvmcall I’ve seen in passing are very small code snippets.

1 Like

Technically, if you could skip some compilation steps it might save some small amount of time. But I wouldn’t expect much benefit and this would be horrifically difficult for anyone except the people that literally wrote these compilation steps in the first place.

Without herculean effort and significant expertise, I suspect your best bet is your current approach to generate Exprs and eval them.

That said, it’s hard to believe you’re generating truly arbitrary expressions - e.g., generating random strings until you get a legal Julia expression. For example, your functions above can all be represented in the form
plusops(v1,v2,v3) = dot([c1,c2,c3],[v1,v2,v3])
mulops(v1,v2,v3,v4) = exp(dot([c1,c2,c3,c4],log.([v1,v2,v3,v4])))
for vectors [c1,c2,c3]/[c1,c2,c3,c4] of certain structure (in your examples, two entries with values that are chosen from among +1 and -1 and the rest 0).

3 Likes

There are some usage examples here:

Since I’m using evolutionary search, my goal is to be able to evaluate as many different Expr as possible, even somewhat sacrificing readability of just that part of the code.

Yeah, there are not completely arbitrary, but the real-case examples are quite more complex and flexible than any of the examples in this thread.

See my latest example for closer representation than the top message:

Putting the intimidating difficulty of editing LLVM IR aside for a minute, generating and editing Expr to go through the entire parsing and compilation process has an advantage. The compiler takes up time, sure, but it’s doing optimizations with that time. A small edit to an Expr may open up the compiler to vastly simplify the LLVM IR. If you don’t let the compiler do that work, you either have to do it manually (very unlikely) or you have to accept sub-optimal LLVM IR, something you might expect from an interpreter. Conversely, a small edit may make some optimizations invalid, in which case you have to add LLVM IR lines to prevent bugs, which is a whole next level of difficult.

2 Likes

Maybe it’s just because I’m not familiar with RuntimeGeneratedFunctions, but it looks like a lot of what you’re doing in the latest example to prepare each method is selecting values, selecting an operation, and permutating variable names, all randomly. I would choose to do all that at runtime in 1 vanilla method, I’m not sure there would be an overall benefit in generating 10x100xN methods that each run a bit faster, no matter how many tricks I use to save compilation time. If the idea is that each method is repeatable, I could store the random selections for repeatability. Instead of randomly selecting a variable to reassign :($x = ..., which does seem impossible to do dynamically, I would rather use a different reference, like an element of a Vector whose indices I can dynamically select. If I needed to make a special method for that extra bit of optimization, I would reserve that effort for bestalgo, bestres, since that’s the only method I could run repeatedly afterward. Just food for thought, it does sound like you’ve considered something similar already anyway.

1 Like