Steve has hit this on the head, there are lots of places where we are using older abstractions that we have had to slowly refactor out of the compiler and like all other software development this won't be instantaneous. We definitely have some less then optimal code in the compiler from a design standpoint.
I think it is also important to recognize that all compilers have this problem and they all stabilize over time. It is an issue orthogonal of safety. These aren't segmentation faults or some kind of memory error but the compiler signaling an internal invariant was violated.
This can happen easily because we are writing a very large program that reads user input, interprets it, and tries to emit a program.
This can happen in multiple situations. One example is the compiler may have a logic bug that violates an internal invariant resulting in an ICE (instead of emitting incorrect code).
Other times it is as simple as the code we have been given is invalid and the error wasn't properly handled, and you end up seeing it in a non-user friendly way (as an ICE).
We are working hard to move the compiler to a more modern, robust design as soon as possible but even with a great design it will be an iterative process that we continue to do over the lifetime of the language, just like everyone else.