Speaker 1: Okay, let's unpack LLVM. It's incredibly powerful, right, underpins so
Speaker 1: much stuff, apples, x code, game engines, you name it.
Speaker 2: Absolutely it's everywhere.
Speaker 1: But you know, for developers already deep in the ecosystem
Speaker 1: or maybe looking to get deeper into compiler engineering, tackle
Speaker 1: the docs, well, it can feel less like learning and
Speaker 1: more like drinking from a fire hose.
Speaker 2: That's a perfect description. It's famously scattered, right.
Speaker 1: So our source today, this book LLVM Techniques, tips and
Speaker 1: bust practices, it promises to sort of rain that beast in.
Speaker 1: So what's our big mission for you today? What are
Speaker 1: we trying to achieve here?
Speaker 2: Well, our mission is really to give you this streamlined,
Speaker 2: comprehensive overview to cut through all that documentation sprawl. We're
Speaker 2: going to unearth some key techniques, maybe some surprising insights,
Speaker 2: to help you build, tests, optimize, and importantly debug your
Speaker 2: LLVM projects much more efficiently. Fewer headaches.
Speaker 1: Fewer headaches are always good.
Speaker 2: Definitely think of this deep dive as like your shortcut,
Speaker 2: making LLVM development faster, more reliable, and well more insightful.
Speaker 2: We're aiming straight at those common pain points, those really
Speaker 2: long build times, complex testing opaque debugging, that kind of thing.
Speaker 1: Yeah, long build times. That's off for the first mountain
Speaker 1: you hit, isn't it. Especially with a huge project like LLVM,
Speaker 1: defaults can take hours, huge productivity killer.
Speaker 2: Oh absolutely, it's a major drag.
Speaker 1: So what's the secret? How do we cut down this
Speaker 1: build time? Beast?
Speaker 2: Okay, so the book immediately points to replacing slower, older tools.
Speaker 2: Makes sense, right, right? Take build systems Ninja. It just
Speaker 2: runs significantly faster than say, g and you make on
Speaker 2: big code bases. LLVM is a perfect example.
Speaker 1: Why is Ninja faster? What's the magic there?
Speaker 2: The key is it's build dot ninjascript. It's it's almost
Speaker 2: like assembly language for builds. It gets generated by higher
Speaker 2: level systems like Seamake, and because it's so low level,
Speaker 2: it allows for loads of optimizations under the hood. Plus
Speaker 2: it handles dependencies much better. You just tell Sea make
Speaker 2: desh g ninja as simple as that.
Speaker 1: So just one command line flag can make a big difference.
Speaker 1: We're basically upgrading the engine. But it's not just the
Speaker 1: build system, is it. The linker is often a problem too.
Speaker 2: Precisely, the default linker BFD. It's mature, sure, but it
Speaker 2: wasn't really built for modern speed or memory needs.
Speaker 1: How bad can it get?
Speaker 2: It can show up that this up to twenty gigabytes
Speaker 2: of memory building LLVM.
Speaker 1: Wow. Okay, that's a bottleneck.
Speaker 2: Definitely a performance hurdle. But thankfully there are much better
Speaker 2: alternatives g and you gold, Google developed that one, and
Speaker 2: LVM's own linker ld LED Yeah. Ld is often even faster,
Speaker 2: and it's got experimental parallel linking too, which is pretty cool.
Speaker 2: And again easy sea make flags ds DLVM muslinker Gold
Speaker 2: or dz dlv musle ankored. Okay, the speed up from
Speaker 2: just those two changes Ninja and a faster linker, it's substantial,
Speaker 2: really noticeable.
Speaker 1: That's huge just swapping out a couple of underlying tools.
Speaker 1: But we can also tweak c make itself right, fine
Speaker 1: tune the build arguments.
Speaker 2: Absolutely. Tweaking CE make arguments is crucial for efficiency, like
Speaker 2: choosing the right build type. A roll with deb info
Speaker 2: is off in the sweet spot. Why that one, Well,
Speaker 2: it gives you optimized code so it's fast, but it
Speaker 2: keeps the debug information so you get a great balance
Speaker 2: between space speed and you know, being able to actually debug.
Speaker 1: It makes sense.
Speaker 2: You generally want to avoid a full debug build unless
Speaker 2: you absolutely have to. It just creates so much unnecessary
Speaker 2: storage waste, huge binaries and targets.
Speaker 1: LVM supports what nearly two dozen hardware targets. Most people
Speaker 1: don't need all of those really exactly.
Speaker 2: That's another massive time sink. If you build them all.
Speaker 2: You can save a ton of time by just building
Speaker 2: the ones you actually need. Use DLLLVM target.
Speaker 1: Still build How does that look?
Speaker 2: Something like Della's DLVM targets to build X eighty six
Speaker 2: R sixty four. Just list the ones you care about.
Speaker 1: And there's a catch with shells you mentioned.
Speaker 2: Ah yeah, good point. In some shells like BSh, you
Speaker 2: have to remember the double quotes around the list, otherwise
Speaker 2: the command gets cut off part way through. Little gotcha?
Speaker 1: Good tip? What else? Shared libraries?
Speaker 2: Yes, another great strategy, especially during development. Build LVM components
Speaker 2: as shared libraries. Use LVM components as shared libraries.
Speaker 1: Use aad build shared libs.
Speaker 2: On Why is that better? Because LVM is so modular,
Speaker 2: Building shared library saves a significant amount of storage space
Speaker 2: and really speeds up the linking part of the build
Speaker 2: process compared to static libraries. Much faster iteration.
Speaker 1: Okay, and what do LVM dash tubulliging. That one comes
Speaker 1: up a lot as being slow.
Speaker 2: It does. It can really impact build times. But there's
Speaker 2: a trick. You can build an optimized version of just
Speaker 2: lavmt bulgein itself even if the rest of your build
Speaker 2: is in debug mode. Use h dlll V optimized stable gen.
Speaker 1: Eldve one nice. So optimizing the tool that helps build the.
Speaker 2: Tools exactly, it shaves off more time.
Speaker 1: So it really feels like we're swapping out a rusty
Speaker 1: old tractor for a soup up racing machine just by
Speaker 1: changing a few settings and tools. Speaking of alternatives, the
Speaker 1: source mentioned another build system gn it.
Speaker 2: Does generate Ninja or gn used a lot by Google projects.
Speaker 2: It's like Chromium.
Speaker 1: What's its advantage.
Speaker 2: It's known for really fast configuration time and reliable argument management.
Speaker 2: The book says it's especially useful if your developments make
Speaker 2: changes to build files, or if you're constantly trying out
Speaker 2: different build options. Much quicker reconfiguration, so.
Speaker 1: Good for rapid iteration on the build itself exactly.
Speaker 2: It's more of an alternative for those specific scenarios. Maybe
Speaker 2: not a full replacement for everyone, but very handy when
Speaker 2: you're tweaking build files a lot.
Speaker 1: Okay, it makes sense. So once you've got your compiler
Speaker 1: built fast, the next big hurdle is reliability testing. How
Speaker 1: do you make sure it's actually, you know, correct?
Speaker 2: Yeah, testing is critical, and LVM provides its own framework
Speaker 2: for this, LVM LIT like LLVM Integrated Tester. The book
Speaker 2: calls it an easy to use yet general framework, and importantly,
Speaker 2: while it started for LVM's own tests, it's actually a
Speaker 2: generic testing framework. You can use it outside LVM for
Speaker 2: other projects too.
Speaker 1: Very versatile and inside RIT there's this utility file check
Speaker 1: that sounds key for compiler testing. What's special about it?
Speaker 2: File check is really powerful. It does advance pattern checking
Speaker 2: on output files. It goes way beyond just diffing text,
Speaker 2: so you embed directives right in your test files. Gacheck
Speaker 2: is basic rajex matching, simple enough, but then you get
Speaker 2: directives like check next t that makes sure a pattern
Speaker 2: is found on the very next line after the previous match.
Speaker 2: Super useful for checking sequential.
Speaker 1: Output ah right, controlling the order.
Speaker 2: Exactly and check same that matches patterns that must be
Speaker 2: on the exact same line. Brilliant for avoiding really long
Speaker 2: messy check lines when you need multiple things on one
Speaker 2: line keeps tests readable.
Speaker 1: Yeah, I can see that. Verbos ir needs concise checks.
Speaker 1: What if you want to ensure something isn't there or
Speaker 1: if the order doesn't matter?
Speaker 2: Good questions. For negative checks, there's check not. It asserts
Speaker 2: a pattern does not exist. Really handy for saying Okay,
Speaker 2: I expect why, but I definitely don't want to see X.
Speaker 1: Makes sense asserting the absence of something.
Speaker 2: And for when the order might change, maybe due to optimizations.
Speaker 2: Shuffling code around you use check DAG. That stands for
Speaker 2: a directed ecyclic graph, but here it means it allows
Speaker 2: matching texts and arbitrary orders. Super flexible for testing nondeterministic output.
Speaker 1: Wow, check DAG. That's really flexible. It seems like you
Speaker 1: can test the intent behind the code changes, not just
Speaker 1: the literal output strengths.
Speaker 2: That's exactly the point. It's about semantic checking, not just
Speaker 2: textual matching.
Speaker 1: So, speaking of describing intent and structure, compilers deal with
Speaker 1: incredibly complex structured data instruction sets, optimization rules. How does
Speaker 1: LLVM handle describing that efficiently? Is that table gen You've
Speaker 1: nailed it.
Speaker 2: Tablegen is the answer. There, it's a domain specific language
Speaker 2: a DSL ESL. Yeah. It originally started within LVM for
Speaker 2: describing things like process or instruction sets, the ISA, and
Speaker 2: other hardware details, but its use has just exploded.
Speaker 1: How so what else does it use for?
Speaker 2: Oh? Everything, managing Clang's command line options, defining complex optimization
Speaker 2: rules like the inst combine people optimizations. The book says
Speaker 2: it's basically for any tasks that involve non trivial static
Speaker 2: and structural data.
Speaker 1: So much broader than just hardware. Now it's a general
Speaker 1: tool for this kind of static data. Can you give
Speaker 1: us a quick feel for the syntax? How does it work?
Speaker 2: Sure? At its core, you define a class. Think of
Speaker 2: it like a C plus plus struct It defines a layout,
Speaker 2: fields and types. Then you use def to create an
Speaker 2: instance of that class called a record.
Speaker 1: Like creating an object from a class blueprint exactly.
Speaker 2: And you can override specific fields in that instance using
Speaker 2: the let keyword.
Speaker 1: Okay, and what about these bang operators I've heard about,
Speaker 1: dot AD, dot mole Ah, Yes.
Speaker 2: Those aren't run time functions. They're more like macros that
Speaker 2: get evaluated during build time buil tag. Yeah, so you
Speaker 2: can do simple computations right in the table gen file itself.
Speaker 2: The example given is dot mole kilogram one thousand to
Speaker 2: maybe convert units. It happens when table gen runs, not
Speaker 2: when the compiler runs.
Speaker 1: Later clever build time computation, or if you have lots
Speaker 1: of similar records, is there a shortcut?
Speaker 2: Yes, that's where multi class comes in. It's a way
Speaker 2: to define multiple records at once by factoring out common
Speaker 2: parameters like a template sort of. Yeah. The book uses
Speaker 2: an autopart and car example. You define a multi class
Speaker 2: for parts, then use defen to instantiate multiple cars, and
Speaker 2: it automatically generates all the individual part records like car one,
Speaker 2: fuel tank, carto, engine, et cetera from one definition.
Speaker 1: Very concise, nice as boilerplate right and complex relationships.
Speaker 2: Yeah, graphs for that. Tablegen has a specific DAG data
Speaker 2: type that lets you define directed cyclic graph instances explicitly,
Speaker 2: super important for things like instruction selection patterns or optimization
Speaker 2: rules where you have dependencies. You can even use tags
Speaker 2: like the upper term dollars to give parts of the
Speaker 2: daglogical names.
Speaker 1: A DAG type built in. Yeah, that's powerful, and the
Speaker 1: source uses this amazing analogy to make a concrete right
Speaker 1: a donut recipe it does.
Speaker 2: It's a brilliant example. The book uses a delicious doughnut
Speaker 2: recipe to show tablegen's power.
Speaker 1: How does that work? A doughnut recipe and a piler book.
Speaker 2: It defines unit classes like gramunit peb's peanut, then ingredient
Speaker 2: base records, and finally step records. These step records form
Speaker 2: a DAG representing the cooking actions. Makes this add that
Speaker 2: complete with ingredients and amounts. Wow, it's a perfect analogy
Speaker 2: because it takes this abstract idea of describing structured data
Speaker 2: and makes it totally tangible. You immediately see how it
Speaker 2: parallels describing instruction patterns or optimization steps.
Speaker 1: A donut recipe in compiler engineering. That's definitely an aha moment,
Speaker 1: makes total sense. So, okay, you've described your donut recipe
Speaker 1: or your instruction set in table gen. How do you
Speaker 1: actually use that data like print the recipe?
Speaker 2: Right? For that, you need a custom table gen back end?
Speaker 2: Now important distinction. This isn't an LVM back end like
Speaker 2: for generating machine code.
Speaker 1: Different kind of back end, totally different.
Speaker 2: A table gen back end is a piece of code,
Speaker 2: usually C plus A that convert or transpiles table gen
Speaker 2: files into an arbitrary, textual.
Speaker 1: Content arbitrary, so anything pretty much.
Speaker 2: It could generate a C plus plus header file documentation
Speaker 2: or in the donut example, just plain text for the
Speaker 2: recipe you use C plus plus APIs provided by tablegen
Speaker 2: like recordkeeper dot get all the rive definitions to get
Speaker 2: all the defined steps and record dot get value restring
Speaker 2: to pull out specific values like ingredient names or amounts.
Speaker 1: So you turn the table genstructure into usable code or data.
Speaker 2: Exactly, you transform the structured description into whatever format you
Speaker 2: need downstream.
Speaker 1: That ability to generate code is huge. Yeah, it feels
Speaker 1: like that opens the door to extending client itself, maybe
Speaker 1: injecting custom logic into the frontend.
Speaker 2: It absolutely does. The front end is a prime place
Speaker 2: for customization. Think about the preprocessor, the very first stage
Speaker 2: handling macros includes you can customize that. Oh yeah, you
Speaker 2: can write custom Pragma handler extensions, so you can invent
Speaker 2: your own hashtag pragma directives. The book shows an example
Speaker 2: hashtag pragma macro or guard.
Speaker 1: What would that do?
Speaker 2: Well? When the preprocessor sees your prag your handler code runs.
Speaker 2: It can parse the Pragma arguments and even register something
Speaker 2: called PEP callbacks. Callbacks, yeah, pp callbats let you hook
Speaker 2: into various preprocessor events, so you can insert custom logic
Speaker 2: whenever a preprocessor event happens. In the example, a macroguard
Speaker 2: validator uses the macro defined callback to automatically check if
Speaker 2: arguments in certain macros are properly wrapped in parentheses.
Speaker 1: Wow, that's fine grain control. Right at the start. What
Speaker 1: of the driver, the thing that orchestrates GCC or Clang?
Speaker 1: Can you customize that too?
Speaker 2: You can? The driver is basically the dispatcher, right, it
Speaker 2: passes flags and manages the different compilation phases. And guess
Speaker 2: what Clang uses to define its driver flags.
Speaker 1: Let me guess tablechen.
Speaker 2: You got it, tablegen again. You can declare custom flags,
Speaker 2: even paired flags like tay flag and NAM flag, using
Speaker 2: things like the booleion f flag, multi class and table gen.
Speaker 1: So you define your flag in table gen and Klang
Speaker 1: understands it exactly.
Speaker 2: The source gives an example of a custom fuse simple
Speaker 2: log flag. Defining this in tablegen allows the driver to
Speaker 2: recognize it, and then your custom logic can make it
Speaker 2: implicitly include a specific header simplelog dot H and maybe
Speaker 2: define macros to control log levels all driven by that
Speaker 2: one flag.
Speaker 1: That's really neat centralized control via custom flag. But can
Speaker 1: you go even deeper, like fundamentally change how Clang interacts
Speaker 1: with the system's tools, make it output something totally different.
Speaker 2: You absolutely can using custom toolchains. The toolchain normally adapts
Speaker 2: Clang for different platforms like different ozes or architectures, but
Speaker 2: you can make it do completely customed things like what
Speaker 2: The book has this fantastic, almost wild example called the
Speaker 2: zipline toolchain. It's a demo obviously, but it shows the
Speaker 2: power zipline.
Speaker 1: What does it do?
Speaker 2: Instead of normal compilation, it uses Clang, but then it
Speaker 2: encodes the generated assembly code using base sixty four during
Speaker 2: the assembling phase.
Speaker 1: Base sixty four why, just to show it can.
Speaker 2: And then during the linking phase it packages those base
Speaker 2: sixty four files into a ZP archive.
Speaker 1: Okay, that is wild? How does it that? In?
Speaker 2: Through the tool chain definition, you override methods like ad
Speaker 2: Clang system include ARGs can add custom include paths. Build
Speaker 2: assembler gets overridden to call open cell base sixty four
Speaker 2: instead of the normal assembler, and build linker gets overridden
Speaker 2: to call zip or tar instead of the linker.
Speaker 1: Wow. So you're completely replacing standard build steps with custom commands.
Speaker 2: Exactly. It perfectly illustrates how deeply you can customize the
Speaker 2: entire pipeline if you need to.
Speaker 1: So if you thought compilers where a black box, definitely
Speaker 1: think again. We're not just peeking inside. We're fundamentally changing
Speaker 1: how they work, how they talk to the OS. That
Speaker 1: level of control it must open up amazing possibilities for
Speaker 1: optimization and analysis. Right.
Speaker 2: Absolutely, that's where the real power of LLVM shines. Sophisticated
Speaker 2: optimizations need deep program understanding, and this happens primarily in llvm.
Speaker 1: IR, right, the intermediate representation.
Speaker 2: Exactly, it's the target independent intermediate representation. It's the core
Speaker 2: of the entire LLVM framework where most analysis and transformation happens.
Speaker 1: And the mechan is and for doing these transformations is passes. Right,
Speaker 1: what's a pass and what's this new pass manager deal?
Speaker 2: Think of an LLVM pass as a module, a basic
Speaker 2: unit that performs certain actions against LLVMI are like one
Speaker 2: step on a factory assembly.
Speaker 1: Line, Okay, a modular step, right.
Speaker 2: And the new pass manager is a significant redesign compared
Speaker 2: to the older system. The book highlights it runs faster
Speaker 2: and generates results with better quality, partly due to a
Speaker 2: cleaner interface.
Speaker 1: Can you give an example of a simple pass.
Speaker 2: Sure the source shows a strict up pass. Its goal
Speaker 2: is simple, add the nolias attribute to function arguments that
Speaker 2: are pointers no alias.
Speaker 1: What does that tell the compiler?
Speaker 2: It's a powerful hint. It guarantees that pointer does an alias,
Speaker 2: meaning it doesn't point to the same memory location as
Speaker 2: any other pointer accessible in that scope. This lets the
Speaker 2: optimizer be much more aggressive, assuming less potential overlap, which
Speaker 2: can unlock significant speed ups.
Speaker 1: How does the pass know what other passes have done?
Speaker 2: Ah, that's key to the new manager. When you write
Speaker 2: a pass, you have to clear what analysis it preserves.
Speaker 2: You use preserved analyzes, so if your pass adds no alias,
Speaker 2: it might invalidate alias analysis results. You tell a manager,
Speaker 2: maybe AA manager results are no longer valid.
Speaker 1: So you explicitly state what your pass.
Speaker 2: Breaks, well, rather what it doesn't break. By default, it
Speaker 2: assumes you break everything you specify what's preserved. This avoids
Speaker 2: costly recomputation of analyzes that are still perfectly valid. It's
Speaker 2: like a librarian keeping track much more efficient.
Speaker 1: Makes sense. So passes are the workers, but they need
Speaker 1: information to do complex jobs, they need a brain, right
Speaker 1: is that the analysis manager?
Speaker 2: Precisely, you nailed it. Modern compiler optimizations can be complex.
Speaker 2: They require lots of information and often getting that information
Speaker 2: is expensive to evaluate.
Speaker 1: So the analysis manager helps with that.
Speaker 2: Yes, it handles all tasks related to program analysis. It
Speaker 2: runs the analysis passes, and crucially caches their results so
Speaker 2: they don't have to be rerun constantly.
Speaker 1: Can you give an example of an analysis?
Speaker 2: It might manage the source mentions a hal tantalizer project.
Speaker 2: Its goal is to find code that's unreachable because a
Speaker 2: special function like my halt gets called earlier.
Speaker 1: Okay, dead god detection.
Speaker 2: Sort of yeah. And to do this it relies on
Speaker 2: one of the fundamental analyzes. LVM provides the dominator tree.
Speaker 1: Or DT dominator tree. How does that help find unreachable
Speaker 1: code after my halt?
Speaker 2: Okay? So the dominator tree tells you control flow relationships.
Speaker 2: If basic block A dominates basic block B, it means
Speaker 2: every possible path to B must go through A first.
Speaker 1: Ah.
Speaker 2: I see, So if the block containing my halt dominates
Speaker 2: another block and my halt stops execution, then that dominated
Speaker 2: block is definitely unreachable. Dominator tree analysis computes this tree structure,
Speaker 2: and halt tantalizer just needs to query it.
Speaker 1: That's really clever. Leveraging fundamental graph analysis.
Speaker 2: Exactly and understanding these core analyzes like dominator trees is
Speaker 2: what lets you build much smarter, much more effective custom
Speaker 2: optimization or analysis passes. You're building on solid theory foundations.
Speaker 1: That's a powerful concept. But okay, even with great optimizations,
Speaker 1: things go wrong. You need to debug, diagnose issues, check
Speaker 1: run time behavior. What tools does LVM offer there?
Speaker 2: Right, optimization isn't everything. LVM has some essential support utilities
Speaker 2: for debugging your passes themselves. There's lvmd bug.
Speaker 1: How does that work?
Speaker 2: You sprinkle LLVMD e bug DDGSS calls in your passcode.
Speaker 2: These messages only get printed if you run the optimizer
Speaker 2: tool with the ededbug or dbug only your past name
Speaker 2: flag keeps your production builds clean, but gives you detailed
Speaker 2: logs when you need them.
Speaker 1: Nice conditional logging. What about tracking numbers like how many
Speaker 1: times an optimization fired?
Speaker 2: For that, you use the statistic macro. You just declare
Speaker 2: statistic counter name description and then increment counter name in
Speaker 2: your code. LVM automatically collects these organizes them and can
Speaker 2: print them out even in formats like.
Speaker 1: Chason Jason output. That's useful for automation.
Speaker 2: Very useful turns ad hoc counting into structured data for analysis.
Speaker 2: Let's you see if you're pass is actually doing what
Speaker 2: you thought or hitting unexpected bottlenecks.
Speaker 1: Okay, what if an optimization tries to do something but fails,
Speaker 1: like it wants to vectorize a loop but can't, how
Speaker 1: do you find out why?
Speaker 2: That's exactly what optimization remarks are for you use optimization
Speaker 2: remark emitter in your past to report why something happened
Speaker 2: or didn't.
Speaker 1: Happen, So notes from the optimizer pretty much.
Speaker 2: The book uses the example of loop invariant code motion LICM.
Speaker 2: If it fails to hoist an instruction out of a loop,
Speaker 2: it can emit a remark saying why maybe there's a
Speaker 2: potential side effect it couldn't ignore.
Speaker 1: You can see these remarks yes.
Speaker 2: And even better, there's a tool Optviewer dot kei that
Speaker 2: takes these remarks and generates a webpage. It highlights the
Speaker 2: relevant source code lines and shows the remarks right next
Speaker 2: to them. It's like a visual debugger for your optimizations.
Speaker 1: Wow from pinpointing buffer overflows, which we'll get to to
Speaker 1: visualizing optimization decisions. Yea LVM really does feel like it
Speaker 1: gives you X ray vision into your code.
Speaker 2: It's a good way to put it. And beyond these
Speaker 2: utilities there's the whole world of instrumentation.
Speaker 1: Instrumentation, you mean adding code to collect data at runtime.
Speaker 2: Exactly, inserting some probes into the code we are compiling
Speaker 2: in order to collect runtime information. Two massive areas here
Speaker 2: are sanitizers and profile guided optimization or PGO.
Speaker 1: Sanitizers sound like they're about fixing problems or finding them.
Speaker 1: Give us a dramatic example.
Speaker 2: Okay, address sanitizer classic example, buffer overflow dot C. Normally
Speaker 2: it might just crash cryptically or worse, seem to work
Speaker 2: but corrupt memory right hard to debug. But compile it
Speaker 2: with Clang dash sanitize address. Now when you run it
Speaker 2: and hit the overflow asan doesn't just crash. It prints
Speaker 2: a detailed report that points out the problematic area with
Speaker 2: high accuracy, right away tells you the exact line the
Speaker 2: variable everything.
Speaker 1: How does it do that?
Speaker 2: It works by having the compiler inserting a boundary check
Speaker 2: into the array index access in the LVMR. It adds
Speaker 2: runtime guards. I remember this one bug. We spent days
Speaker 2: stepping through GDB asan found it instantly on the Total
Speaker 2: game Changer.
Speaker 1: That's incredible. Are there other sanitizers custom ones?
Speaker 2: Yes? Many built in ones, and the source describes building
Speaker 2: a custom one loop counter sanitizer or LPC SAND.
Speaker 1: What does that do?
Speaker 2: It's designed to collect the exact trip count of every
Speaker 2: loop in a module. It does this by using an
Speaker 2: LVM pass to insert function calls like LPC sandst loopstart
Speaker 2: and LPC senate loop in into the IR around loops.
Speaker 2: These functions part of the compiler RT runtime library, then
Speaker 2: record the counts when the instrumented code runs.
Speaker 1: You build your own runtime analysis tools using the same infrastructure.
Speaker 1: Very cool. And the other big area.
Speaker 2: Was PGO profile guided optimization. The idea here is to
Speaker 2: use runtime information not just for debugging, but to enable
Speaker 2: more aggressive compiler optimizations.
Speaker 1: How does runtime info help optimize?
Speaker 2: It tells the compiler how the code is actually used,
Speaker 2: which paths are hot, which are cold, which branches are
Speaker 2: almost always taken. This lets it make smarter decisions about
Speaker 2: things like function in lining, block lege out register allocation
Speaker 2: optimizations that are risky without knowing typical behavior.
Speaker 1: So it learns from real usage. How do you get
Speaker 1: that usage data?
Speaker 2: Two main ways. Instrumentation based PGO adds counters directly into
Speaker 2: the code. It's very precise, but adds overhead during the
Speaker 2: profiling run. Sampling based PGO uses external tools like Linux
Speaker 2: perf to statistically sample the program counter during execution. Lower
Speaker 2: overhead but less precise data.
Speaker 1: Okay, so you gather the data than what.
Speaker 2: First, you compile with a flag like clang def profile
Speaker 2: generat for instrumentation. This creates a profiled data file when
Speaker 2: you run the program. Then you can use llvm def
Speaker 2: prof data to merge and maybe inspect this data. It
Speaker 2: can show you things like the execution frequency of all
Speaker 2: the enclosing basic.
Speaker 1: Blocks to see the hotspots exactly.
Speaker 2: Finally, you recompile your program, but this time with clang
Speaker 2: def profile use, feeding that profile data back into the.
Speaker 1: Compiler and the compiler uses it. Yes.
Speaker 2: Yeah, the LVMIR actually gets annotated with metadat. You'll see
Speaker 2: things like dot prop point seven to one or dot
Speaker 2: prof point seven to two attached to instructions or branches.
Speaker 2: These represent the collected frequencies and probabilities directly guiding the
Speaker 2: optimization passes, the compiler literally learns from the profile that.
Speaker 1: Closes the loop nicely, compiler learns from runtime. Okay, wow,
Speaker 1: we have covered a ton of ground today.
Speaker 2: We really have.
Speaker 1: From you know, speeding up those massive builds, mastering testing
Speaker 1: with lat and file check's crazy pattern matching.
Speaker 2: To crafting custom dsls using table gen even from doughnut
Speaker 2: recipes right.
Speaker 1: And extending Klang's front end building whole custom tool chains,
Speaker 1: then diving deep into LLVMR, the new pass manager analysis
Speaker 1: like dominator trees, and.
Speaker 2: Finally using runtime techniques like sanitizers for correctness and PGO
Speaker 2: for performance, plus all those handy debug utilities.
Speaker 1: It's been a whirlwind tour. But the key takeaway, I
Speaker 1: think is that this deep dive using a guide like
Speaker 1: the source book really offers you a shortcut. Doesn't it
Speaker 1: a way to get a handle on LVM's huge, huge capabilities.
Speaker 2: Absolutely, it turns these conmplex compiler engineering problems into challenges
Speaker 2: you can actually tackle. It gives you that mental framework,
Speaker 2: the specific tools, the techniques to really engage with LVM effectively.
Speaker 1: We've seen how LVM lets you dissect code, rebuild it,
Speaker 1: get incredible control, even less the compiler learn from how
Speaker 1: programs run in the real world. With PGO, it's quite powerful,
Speaker 1: which leads to a final thought. If the compilation process
Speaker 1: itself can learn and adapt based on actual usage, what
Speaker 1: fundamental assumptions that we currently make about software design and
Speaker 1: development might we need to reconsider next? Something to chew on.
Speaker 2: Definitely something to think about and if you want to
Speaker 2: dive deeper. The LVM community is very active. Check out
Speaker 2: the mailing lists on lists at LVM dot org, or
Speaker 2: the discourse forums at LVM dot discourse dot group, or.
Speaker 1: Even attended LVM Developers Meeting LVM dot org DDVMTG. Lots
Speaker 1: of ways to keep learning and engage.
Speaker 2: The resources out there once you know where to look,
Speaker 2: are invaluable