At a high level, GCC and LLVM are divided into two ends: the front and the back. The front end takes the source code and turns it into some form that's language agnostic (e.g. LLVM bitcode). The back end takes those structures and turns it into machine code. This allows the compiler to be modular.
Old compilers didn't use this concept. So a C->x86 compiler and a Fortran->x86 one couldn't share code as easily. And if you wanted to expand to, say, ARM, Alpha, etc. targets, it gets worse. You end up needing O(l*t) compilers[a] to be homogenous. It gets even worse if you want to add optimization. With this model, the whole system is working on an AST, and therefore, the optimizers are tailored to the individual compiler varient, so they end up being just as unportable.
However, with a modular design (front and back end), you can have C->IR and Fortran->IR front ends, then a single back end for each target: IR->x86 and IR->ARM. If you want to add, say, Ada support, you only need to write an Ada->IR module, and the back ends handle the rest. In the end, you only need O(l+t) modules. You also get the benefit of agnostic optimizers as they only need to support your internal IR.
[a]: 'l' is languages and 't' is targets
Old compilers didn't use this concept. So a C->x86 compiler and a Fortran->x86 one couldn't share code as easily. And if you wanted to expand to, say, ARM, Alpha, etc. targets, it gets worse. You end up needing O(l*t) compilers[a] to be homogenous. It gets even worse if you want to add optimization. With this model, the whole system is working on an AST, and therefore, the optimizers are tailored to the individual compiler varient, so they end up being just as unportable.
However, with a modular design (front and back end), you can have C->IR and Fortran->IR front ends, then a single back end for each target: IR->x86 and IR->ARM. If you want to add, say, Ada support, you only need to write an Ada->IR module, and the back ends handle the rest. In the end, you only need O(l+t) modules. You also get the benefit of agnostic optimizers as they only need to support your internal IR.
[a]: 'l' is languages and 't' is targets