LLVM is pretty low-level. It's a bit lower-level than C and has a very simple type system. It has no idea about unique pointers versus references, has no concept of borrowing, etc. I bet you could encode the necessary information for performing say borrow-checking into LLVM IR, but working with that would not necessarily be pleasant.
A good example of this is ARC optimization in Swift, which gets rid of unnecessary reference counting operations. That optimization operates on SIL, which has special instructions for reference counting. You could represent those instructions as magic intrinsics in LLVM IR, but you have no way of imposing any structural restrictions on how those intrinsics are used. Moreover, any pass which operated on LLVM IR to optimize uses of those intrinsics, would have to make deep assumptions about the semantics of those operations. Those assumptions, being Swift-specific, would mean the pass could not be used by other languages, which removes a lot of the benefit of making it an LLVM pass in the first place.
Some language rules and optimizations are easy to express in LLVM IR, but there are plenty that are not.
You could add metadata, but you'd probably have to extend how metadata works to support a bunch of things, and even then, the community would want metadata designs that multiple language frontends can use, not just yours.
Not sure what you're talking about here; what's wrong with negative indices in C?
Second, remember that even in C, you can't actually dereference things outside of the bounds of an object, even if you can form addresses to it.
In any case, the point was more than something like
struct foo C;
char *foo = &C
foo -= 7;
char whee = *foo;
Is legal in llvm, but not in say, C++ (and in fact, in most languages it's not legal because it's not aligned properly) int a[100];
int *p = &a[50];
a[0] = 666;
printf("%d\n", p[-50]);
Remember that x[y] in C just means *((x)+(y)). You can offset pointers with positive and negative offsets so long as the resulting pointer remains within the bounds of an object.(EDIT: formatting so HN doesn't misinterpret code point U+2A.)
If you implement a stack using an array and you track the top of stack by a pointer just past the last element, then you can index negatively into that pointer to access items on the stack:
// Create an empty stack.
int stack[100];
int* top = &stack;
// Push a couple of values.
*top++ = 123;
*top++ = 456;
// Peek the values.
printf("%d\n", top[-1]); // 456.
printf("%d\n", top[-2]); // 123.
You could make the pointer arithmetic more explicit like this: printf("%d\n", *(top - 1)); // 456.
But I think that's needlessly verbose.[1]: https://github.com/munificent/wren/blob/master/src/vm/wren_v...
See section 6.5.6, paragraph 8, of the ISO C standard, if you want the legalese. But this is pretty basic C that dates back to the beginning of the language.
"Faster execution time. You may have noticed that in the new compiler pipeline, optimization appears twice. That’s no accident: previously, the compiler relied solely on LLVM to perform optimizations, but with MIR, we can do some Rust-specific optimizations before ever hitting LLVM – or, for that matter, before monomorphizing code. Rust’s rich type system should provide fertile ground for going beyond LLVM’s optimizations."