20 ms·
LLVM Intermediate Representation is better than assembly
- crb002 13y agoglibc is nuts for not having an LLVM port.
- kwantam 13y agoThis is interesting and pretty awesome. I can imagine having a lot of fun making a toy compiler that emits LLVM IR directly. Practically speaking, is there a good reason to do this in non-toy software rather than the more-or-less standard practice of using C as an IR? That gives you, in addition to compilation via LLVM, the ability (in principle) to use gcc, icc, et al. (In practice, in many cases people end up using compiler-specific extensions in the generated C, which does impose limitations on portability to other compilers, but that's certainly avoidable.) I suppose if you are dynamically generating things that need to be compiled as quickly as possible, eliminating an unnecessary compilation step could be a significant advantage. It doesn't seem like such a constraint would apply in most cases, though.
- pbsdp 13y agoC is a heavyweight intermediary language which brings with it a /lot/ of baggage. There's no valid direct technical reason other than financial resource constraints to justify compiling to C (... or JS, for that matter) instead of a more suitable and expressive IR/bytecode. It's impossible to implement a variety of useful abstractions in portable C -- for example, function trampolines that do not permute register state and do not appear in the callstack after execution.
- maaku 13y agoOh come now, there are good reasons to compile to JS if you are doing things in a browser.
- pbsdp 13y agoNot direct technical reasons, no. The only indirect technical justification is in maintaining compatibility with existing browsers, but that falls over pretty fast when you're a browser maker (Mozilla) and refusing to work with another browser maker (Google) on jumping over that compatibility hurdle.
- mistercow 13y agoCan you explain specifically what you mean by a "direct" vs. an "indirect" technical reason?
- pbsdp 13y agoDirect: Constraints/requirements directly related to implementing a maximally effecient runtime architecture. Indirect: Market constraints that drive technical limitations. When it comes to browsers, Mozilla both creates and exists within market constraints that drive indirect technical reasoning. Lashing your industry to JS for another 10 years as a bytecode target is clearly not an optimal long-term solution as compared to the current state of the art, but it might make sense as a legacy support mechanism given the indirect technical constraints.
- azakai 13y ago> compared to the current state of the art What do you consider the current state of the art?
- pbsdp 13y agoJVM, CLR, Mono AOT complation, NaCL/PNaCL. A future in which we wind up with architectural support for NaCL-style sandboxing intrinsics, in the same way we saw them be developed for VT-(x|d). Sticking us with JS for another decade and expecting us to compete against better application runtimes? No.
- 13y ago
- mistercow 13y ago>(... or JS, for that matter) I have to disagree with that. There are lots of languages whose purpose is to produce JS. For example, it would be very silly for CoffeeScript to use LLVM IR. It would end up reïmplementing all of the things that it shares with JS (that is, most of JS) with no conceivable benefit. Maybe if/when PNaCl is everywhere, it would make sense for it to do that, but even then it's a hard sell as long as four giant companies are competing their asses off to optimize JS. But I do agree with you if the language doesn't correspond well with JS. In that case, it's probably better to target LLVM and then compile to JS via emscripten.
- gngeal 13y agoThere's no valid direct technical reason other than financial resource constraints to justify compiling to C (... or JS, for that matter) instead of a more suitable and expressive IR/bytecode. How does one do call/cc in LLVM, or delimited continuations, for that matter?
- pbsdp 13y agoThe same way you'd do it in any runtime? Adopt an adequate representation model of the machine state, and save that state.
- gngeal 13y agoWhy would I save "a representaion model" when I can simply save the process/thread state?
- pbsdp 13y agoYou can't do that in portable C as-is. Watch what happens when you try to restore thread state on a different system thread using an OS that instrisically ties system threads to user threads to thread local variables to the stack. And no, getcontext() and setcontext() aren't portable.
- gngeal 13y agoWhy would I care if it's portable or not? I've never understood the mentality of "but it's not portable". If something isn't not portable, you put #+ and #- on top of it. I'd rather go for "straight-forward and performant" than for some abstract code transformations to make it runnable on platforms that almost nobody uses to the detriment of everyone else. LLVM is the lowest common denominator. I'm really not fond of lowest common denominators. (Not to mention the fact that it's bloatware in the first place - it's about twice the size of my favourite compiler, and that's before you add the actual language you're trying to compile.)
- 13y ago
- azakai 13y ago> C is a heavyweight intermediary language which brings with it a /lot/ of baggage. There's no valid direct technical reason other than financial resource constraints to justify compiling to C (... or JS, for that matter) instead of a more suitable and expressive IR/bytecode. 1. In theory. But what is that "more suitable and expressive IR/bytecode"? I would argue such a bytecode should be portable, but LLVM IR is not that. 2. There are lots of reasons to target C. C can be compiled by many compilers, while LLVM IR can only be compiled by LLVM to things LLVM can compile to. For example, game consoles, various new embedded platforms, etc. - they all have C compilers, but LLVM might not support them (and possibly cannot support them).
- pbsdp 13y ago> 2. There are lots of reasons to target C. C can be compiled by many compilers, while LLVM IR can only be compiled by LLVM to things LLVM can compile to. For example, game consoles, various new embedded platforms, etc. - they all have C compilers, but LLVM might not support them (and possibly cannot support them). Is the point to produce something optimal for users, or produce something optimal for developers? Most successful game and application platforms trend towards the former, whereas your work on web technologies trends towards the latter.
- eliben 13y agoIt's not difficult to restrict LLVM IR to a portable subset, though ;-)
- 13y ago
- vidarh 13y agoLLVM IR is less portable than C. That's a valid direct technical reason. Generating C (or another high level language) also makes integration with tools in that language potentially trivial (depending on the semantics of the language you're compiling), and depending on your preference you may find it far easier to debug the output. As for portability, while you're right that a number of things can't be implemented in portable C, you gain the benefit that C has been ported to far more systems than LLVM - there are C compilers even for the Commodore 64. I agree with you that C is heavyweight, but LLVM is too. Personally I prefer to build code generators directly - the LLVM infrastructure may be great, but I much prefer to write compiler that are self contained and bootstrapped in the language they are intended to compile. Then again I might just be difficult.
- hga 13y agoC as a target language locks you into a lot of things, like the C calling convention. Might you prefer being able to do tail call optimization without trampolines to C's varargs? It's also an issue WRT to precise garbage collection, although that's currently about the same with LLVM, which discards the distinction between pointers and ints. But in theory you or someone might enhance LLVM someday.
- cliffbean 13y agoThe flip side of this is that using C gives you the C calling convention for free. LLVM IR doesn't abstract away all the details of the C calling convention, and requires IR producers to produce platform-specific code for it.
- pbsdp 13y agoCalling conventions aren't the hard part; it's just a bit of register/stack allocation.
- cliffbean 13y agoI have actually written code to produce LLVM IR to conform to a platform's native C ABI. It's quite a bit more work than "just a bit of register/stack allocation". For one thing, LLVM IR doesn't expose the concept of a stack pointer, so you can't just put things on the stack where they need to go. You need to put things in LLVM's abstractions in places where you happen to know the codegen will lower them to where they need to go. And it's different for every platform.
- pbsdp 13y agoIn the relative context of implementing a production runtime, that's "just a bit of register/stack allocation". If you're hobby-hacking a project, then you may as well just target something that already exists, because micro-inefficiencies that matter on the scale of a deployed production platform probably don't matter for your human-resource-constrained hobby project. If you're designing the next browser language runtime, or a language meant to be used outside of hobby deployments, there's no point in permanently hamstringing your implementation architecture to save a small amount of implementation time within what is already a large and complex problem space.
- 0x09 13y ago> I can imagine having a lot of fun making a toy compiler that emits LLVM IR directly. Practically speaking, is there a good reason to do this in non-toy software rather than the more-or-less standard practice of using C as an IR? Well what you're describing is the whole living purpose of LLVM as a compiler framework. In other words, clang, LDC, GHC, and all other LLVM front-end compilers are non-toy software that transform their respective language into IR and hand it to libLLVM* for optimization/codegen/etc. Targeting C puts a relatively large layer and potentially good deal of ambiguity in between your compiler and the machine code, rather it seems more fitting for experimental or "toy" software to use that in lieu of a real compiler or LLVM frontend.
- caf 13y agoA compiler frontend that emits LLVM IR has to worry about things that one that emits C doesn't, though - for example, target alignment requirements.
- eli_gottlieb 13y agoI can imagine having a lot of fun making a toy compiler that emits LLVM IR directly. Well, I'm still plugging away at the Deca compiler that outputs to LLVM IR directly. It's loads of fun! Practically speaking, is there a good reason to do this in non-toy software rather than the more-or-less standard practice of using C as an IR? I can tell you why I use it: * Even through the C interface, I have an actual library API that outputs the IR code directly rather than having to transform my syntax trees into C syntax trees and then output through a C parser library. * You recursively walk your syntax tree passing an LLVMInstructionBuilder* around to different instruction functions in the API. The result is that your code is a recursive tree-walk (rather than an ordered tree-walk), and the builder object serializes it all for you (including using one instruction's output as input to another) according to the natural order of evaluation in your higher-level language.
- marssaxman 13y agoI am not sure what you mean by "standard practice of using C as an IR". I was a compiler engineer for a decade or so and I have only seen C used as an IR for quick and dirty hack projects which don't care about things like debugging or compile time. The commercial compilers I have worked on each used their own intermediate representation; none used C.
- ygra 13y agoAnecdotal, but at least the Eiffel compiler (EiffelStudio, if I recall correctly) we used in university compiled to C.
- marssaxman 13y agoInteresting. How did they handle debugging?
- ygra 13y agoApparently they include an interpreter as well which probably could be used for debugging. And hopefully interpreter behaviour matches compiled behaviour, then ;-)
- duaneb 13y agoIR is a superset of C without the massive overhead, you really need a good portability reason to use C instead. And if llvm doesn't support it, chances are you can't write very portable c, either.
- pash 13y agoLennart Augustsson gave a talk [0] at last year's Haskell Exchange showing how to embed a DSL in Haskell and then use Haskell's LLVM bindings to generate LLVM IR for it. If you need a blazingly fast DSL, give it a try! 0. http://skillsmatter.com/podcast/home/making-edsls-fly http://skillsmatter.com/podcast/home/making-edsls-fly
- dbecker 13y agoHow many people still write assembly, and for what purposes?
- majke 13y agoKnuth in The Art of Computer Programming uses (artificial) MIX assembly to describe algorithms. To me it was awkward at the first, but it does make a lot of sense after first few chapters. Assembler has real world users, but also it has a niche in academic community. https://en.wikipedia.org/wiki/MIX https://en.wikipedia.org/wiki/MIX
- mjn 13y agoThe most common modern cases I've run across are handwritten inner loops that make careful use of SIMD instructions, since auto-vectorization isn't quite there yet. For example, the x264 encoder has a lot of assembly in it. In x264's case they even wrote their own assembly IR, though targeted only at x86 variants: http://x264dev.multimedia.cx/archives/191 http://x264dev.multimedia.cx/archives/191
- deleted 13y ago[deleted]
- thangalin 13y agoOperating systems - http://www.menuetos.net/ http://www.menuetos.net/ http://www.returninfinity.com/baremetal.html http://www.returninfinity.com/baremetal.html http://mikeos.berlios.de/write-your-own-os.html http://mikeos.berlios.de/write-your-own-os.html Embedded systems - http://thinkingeek.com/2013/02/02/arm-assembler-raspberry-pi-chapter-9/ http://thinkingeek.com/2013/02/02/arm-assembler-raspberry-pi...
- jws 13y agoThe last time I wrote assembly was for clock cycle accurate timing manipulating some IO pins at rates very near the limits of the processor. No time or tolerance for interrupt handlers and timers. Before that it was to program a pair of limited range timers to run with slightly different periods so I could lazily read them from C, then by examining their phase and values determine if I got an uninterrupted pair of data, and if so, how many times the timers had rolled over, thus implementing a single, high resolution, extended range timer. It is also used to exploit processor instructions which do not yet have compiler support.
- zqfm 13y agoPerhaps I am mistaken, but I was under the impression that the LLVM IR was subject to change at a whim. That may be something to keep in mind if you intend to target it directly.
- octo_t 13y agothe text format changes, the API stays relatively static (afaik). Most tools use something similar to: LLVMContext &Context = getGlobalContext(); SMDiagnostic Err; Module *Mod = ParseIRFile(argv[1], Err, Context);
- jevinskie 13y agoThe C API is deemed stable. The C++ API changes every release. The textual IR has so far been forwards compatible. Bit code is also supposed to be forwards compatible but some bugs have prevented this in the past. Backwards compatibility is right out which can be frustrating.
- mjn 13y agoI'm intrigued by this idea, but it seems tricky to really use portably if your goal for writing asm is to carefully manage register usage and SIMD instructions. Sure, the syntax is portable, but to get the "right" result, i.e. one that maps directly onto the fast machine code you had in mind, you need to know something about the target architecture, e.g. how wide its SIMD instructions are, and how many registers it has. And if you want it to be fast on two architectures that differ considerably, you may have to write two versions of your LLVM IR, at which point you're losing some of the benefit of an IR. You do still get the advantage that it will at least compile to something on platforms you hadn't previously heard of, but most projects will just use a straight-C fallback function for those platforms.
- rinon 13y agoManaging register allocation by hand is unlikely to provide significant speedups, considering that modern register allocators are "pretty good." However, if you are wanting to optimize for SIMD, this can be easily done with vector instructions in IR, without needing to worry about register allocation at all.
- mjn 13y agoAt least as of a few years ago, the x264 team claimed they didn't use C intrinsics for their inline asm because automatic register allocation wasn't very good: http://x264dev.multimedia.cx/archives/191#comment-1763 http://x264dev.multimedia.cx/archives/191#comment-1763 (exchange between comments 3 and 4). Sure, you can write SIMD in LLVM IR, but to do it efficiently you need some knowledge of the target. Should you use i32, or rework your code to use more i16, or throw in some parallel i64 multiplications? Those aren't really portable choices, unless you have a really good auto-vectorizer, in which case the advantage of writing in asm at all disappears, since you can just rely on auto-vectorized C.
- stephencanon 13y agoRegister allocators use heuristics that are “pretty good” for most usage, but sometimes fail spectacularly in edge cases with very high pressure. Register allocation is, after all, NP-complete. Even when it doesn't fail completely, if you’re tuning a load-store-bound computation, a few extra register spill-fill pairs can easily halve the throughput that you achieve. That said, for most programmers, most of the time: use intrinsics and let the compiler handle register allocation.
- ihnorton 13y agoJulia is a pretty cool platform for playing with this kind of thing in a REPL environment - you can dump LLVM IR and corresponding disassembly for any function, with source line annotation (some things are rearranged by the LLVM optimizer, but it is usually close). No inline IR support yet, but it's a great way to understand the relationship between high-level constructs and machine code.
- _delirium 13y agoInteresting, I didn't realize Julia had that. Fwiw, many Lisp systems also have this available: if you (disassemble 'foo) in SBCL, it'll show you the asm the function compiled to.
- kodablah 13y agoI think http://terralang.org/ http://terralang.org/ also does a very good job at this. You can just call disas() on any function.
- nullc 13y agoIt would be nice if the substance of the article actually backed up the title... it doesn't really show this: Yes sure, it's more portable: but plain C is way more portable still. The places where assembly is justified these days tend to be SIMD inner loops where intrinsic use isn't effective (e.g. due to compilers being pretty lame at vector register allocation) and where a lot of gains can be had by bitops level micro-optimization that depend very precisely on the instruction behavior. I would be surprised if writing LLVM IR was actually better than using intrinsics in these cases. If it were so it would be neat to see it— an example of taking some well optimized multimedia codec library and converting (some of) the SIMD asm into LLVM IR and showing equal performance would be impressive.
- majke 13y agoI personally think that IR is _more_ expressive than C. There are two points I wanted to make in the blog post: - LLVM IR is cool and in many cases it could be beneficial to use it instead of assembler - If LLVM is bad for any reason, writing an optimizer is relatively simple. And, as opposed to hand crafted optimizations, it does scale.
- acqq 13y agoIn the examples given under "vectors" I don't see if they are also multiplatform -- not every platform has "process 4 floats at once this way." Also I don't see the encoding of assumptions of alignment. Not every processor has the same even when the "main" architecture is the same (even x86 generations differ). I remain suspicious that IR is the only representation you need if you do want to do the things on the assembly level. I welcome examples of somebody in the know. The another topic is how often IR is going to change.
- majke 13y ago> ... not every platform has "process 4 floats at once this way Sure. The point is - you can write IR code that multiplies _any_ number of floats and the backend "should" generate reasonable machine code for any architecture. > Also I don't see the encoding of assumptions of alignment Yeah, no memory loads there, data passed in xmm0 and xmm1, plenty of simplifications. > The another topic is how often IR is going to change. Fair question. I don't know but given the number of projects that currently use it I'm quite sure it'll be well maintained. Also, keep in mind that adapting IR to a future version (if one is created) is still IMO simpler than adapting assembler. Additionally - you can use IR to generate machine code once (for every architecture). That way you won't depend on your users having LLVM installed.
- cliffbean 13y ago> you can write IR code that multiplies _any_ number of floats and the backend "should" generate reasonable machine code for any architecture. Except that it doesn't. If you write your code with <2 x float> and codegen for SSE, you'll get <4 x float> code, with two elements going unused. It's functionally correct, but you're potentially missing out on half the throughput. If you write your code with <8 x float>, you'll get two registers for each value, but this can create extra register pressure without actually giving you any increased throughput in return.
- jevinskie 13y agoOther optimization passes will widen the vecorization width to 4 if possible by unrolling the loop!
- gsg 13y agoThe author invokes the idea that writing assembly and writing compiler passes are similar enough activities that you can substitute one for another, but that is not the case. Good thing, too - I don't even want to think about how buggy and slow compilers would become if random people started jamming passes into them based on nothing but expediency.
- octo_t 13y agoThe cool thing about LLVM is that "jamming passes" in, is something an individual team can do, adding a pass simply involves running opt -load path/to/pass.so < IR.bc Which emits (hopefully) optimised IR. Neat huh?
- munin 13y agoA few points: -LLVM at the IR level still has platform specific information - calling conventions, vector types, pointer widths, etc. -what was true then is still true now: http://lists.cs.uiuc.edu/pipermail/llvmdev/2011-October/043719.html http://lists.cs.uiuc.edu/pipermail/llvmdev/2011-October/0437... -typed assembly languages have existed for a while, no one uses them
- majke 13y ago> platform specific stuff Yes. If I were to write IR for a real project, I'd probably keep a few pretty large functions to avoid overhead caused by calling conventions. Very interesting link. Thank you! Though I find the arguments unconvincing. More data needed... > typed assembly Yes. But now _every_ mac user and plenty of other people have a typed assembly compiler on their machines! IR is reasonably popular and quite well documented. I'm not saying it's new, I'm saying it's cool :)
- azakai 13y agoIt's not just calling conventions. LLVM IR also bakes in various assumptions about the target platform, from endianness to structure alignment to miscellaneous facts like whether char is signed or unsigned. In many of those there is no going back to the original representation, they are one-way. If you have IR that you can compile to various archs and have it work there, that is a lucky thing in that particular case. But it is not what LLVM IR was designed for nor should that work in general.
- phaemon 13y agoI don't understand what this means. Could you please give an example of some code that loses information in this way when compiled with LLVM?
- azakai 13y agoSay you have a struct type X with properties int, double, int. The offset of the last property depends on how alignment works on the target platform - it could be 12 or 16 on some common ones. LLVM IR can contain a read from offset 12, hardcoded. Whereas the C code contains X.propertyThree, which is more portable.
- monstrado 13y agoCloudera's Impala SQL query engine uses LLVM to maximize performance, here's an article published recently talking about how it's used. http://blog.cloudera.com/blog/2013/02/inside-cloudera-impala-runtime-code-generation/ http://blog.cloudera.com/blog/2013/02/inside-cloudera-impala...
- astral303 13y agoThe exciting thing would be to see applications compiled to LLVM IR get JIT'ed. Finally C or C++ apps that could inline through function pointer or virtual function calls at runtime.
- monstrado 13y agoYou might find this interesting, a database query engine using LLVM IR to speed up performance. http://blog.cloudera.com/blog/2013/02/inside-cloudera-impala-runtime-code-generation/ http://blog.cloudera.com/blog/2013/02/inside-cloudera-impala...
- ihnorton 13y agoIt's an interesting idea but the current LLVM JIT is pretty slow so it hurts my head to think about JIT'ing average size C++ programs. Leaving that aside, the cost of doing this for every codepath would probably outweigh the benefits except in very specific use cases (especially on a cpu with decent indirect prediction). This would be an ideal place for a tracing JIT, which LLVM does not have at the moment.
- gsg 13y agoThrowing away sharing of code pages (and the security benefits associated with PIC) for a bit of inlining doesn't seem like the best idea.
- carterschonwald 13y agoFor those wanting to play with llvm as a library but would rather not deal with C++, the llvm-general Haskell library gives you nearly every power of llvm (aside from link time optimization, and writing new optimization passes) in a nice high level lib that's already being used for a few interesting compilers out there.
- iskander 13y agoSimilarly, Siu from Continuum wrote some nice Python bindings (http://www.llvmpy.org/ http://www.llvmpy.org/) and LLVM already ships with OCaml bindings (http://llvm.org/docs/tutorial/OCamlLangImpl1.html http://llvm.org/docs/tutorial/OCamlLangImpl1.html)
- ezy 13y agoThis needs better examples. I find: v4si multiply_four(v4si a, v4si b) { return a*b; } Much more readable, and just as portable. There's a typedef for v4si, of course, but you do that exactly once and then ignore it. The IR produced is identical, and therefore just as portable.
- syncopate 13y agoIt should be noted that on the official llvm irc channel (#llvm@oftc.net) people do not like llvm's IR being called a "progamming language". Rather it's intended to be a representation of llvm's internal data structures. It may look similar to a programming language for sure but it's not the intent behind it. It's a representation that should help compiler engineers debugging their llvm-using parsers. Also, if you've ever looked at clang's "-emit-llvm" output you will notice that there are some parts of the IR which are hard to be generated by humans, e.g. dgb by sequential number references. See e.g.: http://llvm.org/docs/SourceLevelDebugging.html#object-lifetimes-and-scoping http://llvm.org/docs/SourceLevelDebugging.html#object-lifeti...
- 0x09 13y agoTo be fair to outsiders, http://llvm.org/docs/LangRef.html http://llvm.org/docs/LangRef.html is linked from the main page where the human-readable IR format is referred to as "LLVM assembly language", and with the in-memory representation listed as an equivalent form but not with particular privilege.
- waynecochran 13y agoThe problem with writing LLVM IR code by hand is that is must be in SSA (single status assignment) form which is a pain for humans, and requires graph algorithms to insert phi nodes in the basic blocks. What we need is a non-SSA LLVM IR assembler that converts to SSA -- this would be nice for human authors. BTW, I taught a compiler course where I gave a project that translated a "turtle graphics" functional language into LLVM IR: http://ezekiel.vancouver.wsu.edu/~cs452/projects/turtlecomp/turtlecomp.pdf http://ezekiel.vancouver.wsu.edu/~cs452/projects/turtlecomp/....
- swah 13y agoAnd C is better than LLVM, from a post a few days ago :)
- cylinder714 13y agoOn a related subject, Dan Bernstein of qmail fame created a portable assembly language a few years ago, called qhasm: http://cr.yp.to/qhasm.html http://cr.yp.to/qhasm.html The idea is that processors do pretty much the same things, but with different assembly language syntaxes. With a portable subset, one could then use translators to convert qhasm source into whatever dialect required by ARM, Intel, PowerPC, et cetera. The latest source code dates from 2007, and it's only a prototype, but it's a clever idea.
- AYBABTME 13y agoThis reminds me that I need to learn some assembler. I have a hard time finding resources on conventions, project layout, best practices, tooling. If anybody's experienced in an assembler, I'd love to have some tips and pointers.
- acuozzo 13y agoI can provide many tips and pointers for 6502 ASM.
- josteink 13y agoTo be fair x86 assembly is horrifying. I've worked with other (mostly Motorola-based or some sort of embedded) architectures, and while a bit cumbersome to work with, the assembly was clean and understandable. Back in the days assembly was my primary way of getting things done, in part because C-compilers cost money back then and I barely had money left after buying a computer. Once the "PC" (and thus Intel) had won the war and I set foot in X86-country, I started looking at the assembly. It took me less than a week to decide to give up assembly forever. So yeah. It doesn't take much to be better than X86 assembly.
- artagnon 13y agoI doubt it has any practical application outside llvm.git. Why would you want to write assembly by hand in the first place? 1. To bootstrap. There's various architecture-specific bootstrap code to load the kernel into memory in linux.git. The project largely depends on GNU as to assemble reliably; llvm-mc doesn't work half as well. 2. To try out new processor extensions. When new instruction mnemonics come out, assembler have to catch up. I'm not sure why anyone would want to use LLVM IR here, because those instructions are architecture-specific anyway.