Все компиляторы используют низкоуровневое внутреннее представление (LIR) для выполнения низкоуровневых оптимизаций. В GCC это называется "GIMPLE"; в LLVM это называется «LLVM IR». Другими словами, подход LLVM ничем не отличается от других компиляторов в этом отношении.
На самом деле, большинство интерфейсных приложений далее используют некоторое высокоуровневое внутреннее представление (HIR) для выполнения некоторых оптимизаций (особенно в циклах), которые гораздо труднее применить на уровне LIR (или невозможно из-за потери информация).
Поэтому обычно программа проходит не 2, а 3 этапа «компиляции»: C -> HIR -> LIR -> ASM.
Что касается оставшейся части вашего вопроса, то, как уже упоминали другие люди, LIR не предназначен для переносимости между разными архитектурами, а между разными экземплярами одной и той же (или похожей) архитектуры. Есть много причин для этого:
Код C больше не является переносимым после таких шагов, как предварительная обработка или оценка различных размеров.
в зависимости от целевых приложений требуются различные оптимизации, некоторые из которых выполняются на уровне HIR (например, внутри Clang); было бы слишком поздно откатываться после преобразования в LLVM IR.
в любом случае, когда вы оптимизировали код C, он обычно сильно зависит от цели.
В любом случае, если вы заинтересованы в переносимости, вам также следует взглянуть на PTX ISA от NVidia. Это виртуальный ISA, общий для всех графических процессоров NVIdia, который компилируется драйвером графического процессора во время установки в машинный код.