(Replying here, so my upvote on the parent doesn't get lost...)
Working in C means you get restricted to only doing things which C can do, and you're out of luck if you want to do things that C can't do: unaligned accesses, tail calls, saturating arithmetic, overflow detection, exceptions, stuff like that. IR allows you to do all this, at the expense of being considerably more complex and painful to use.
Of course, if your compiler don't want to do any of this, then C is a perfectly valid choice, as it doesn't tie you to the LLVM toolchain --- see Nim, for example. But as soon as you venture outside C's comfort zone, working in IR starts paying off.
But LLVM-C (i.e. C + llvm extensions) does do all the things you just described, plus it's a stable interface for programming. IR is unstable, and therefore unsuitable as a source language for most projects. I say this as someone who writes an enormous amount of both assembly and llvm-c, and has considered and dismissed IR for exactly this reason.
That's all fair and correct, but the amount written for, say, an OS, in assembly - native or IR - is quite minimal and well-controlled, usually written in assembly because of a good reason (exception handlers, for example). At that point, it's just more obvious to write in native assembly, given that you're writing architectural code, and for a different architecture, that code would be sufficiently different.
So again, I see all the value in IR for compilers. Can someone give me at least a reasonable use case for beginning to write in IR, or even using IR for optimizing cerain code paths?
LLVM IR can detect overflow, has exceptions, tail call optimisations etc? I thought it was lower level than C itself. I have kind of a strict hierarchy of abstraction in my head, with LLVM IR sitting a bit below C, but maybe it's not that simple.
Sitting below C is exactly what you want in this case. It's not about tail calls as an automatic optimization - it's about having a tailcall opcode of some sort, that guarantees a tail call when used (but you're responsible for lifetimes etc). That is inherently a low-level facility - lower than what the C standard provides. C implementations can do tail calls, but because this behavior isn't actually guaranteed and is purely a quality-of-implementation issue, a language that needs tail calls for its idiomatic code to not blow up the stack cannot target portable C, unless it redoes the stack itself.
Working in C means you get restricted to only doing things which C can do, and you're out of luck if you want to do things that C can't do: unaligned accesses, tail calls, saturating arithmetic, overflow detection, exceptions, stuff like that. IR allows you to do all this, at the expense of being considerably more complex and painful to use.
Of course, if your compiler don't want to do any of this, then C is a perfectly valid choice, as it doesn't tie you to the LLVM toolchain --- see Nim, for example. But as soon as you venture outside C's comfort zone, working in IR starts paying off.