Большинство компиляторов имеют внешний интерфейс, некоторый средний код / структуру некоторого вида и внутренний интерфейс. Когда вы берете свою C-программу и используете clang и компилируете так, что в итоге вы получаете не-JIT x86-программу, которую вы можете просто запустить, вы все равно перешли от внешнего интерфейса к среднему и внутреннему. То же самое касается gcc, gcc переходит от внешнего интерфейса к среднему и внутреннему. Средство Gcc не является широко открытым и пригодным для использования, как LLVM.
Теперь одна вещь, которая забавна / интересна в llvm, которую вы не можете сделать с другими, или, по крайней мере, с gcc, - это то, что вы можете взять все свои модули исходного кода, скомпилировать их в байт-код llvms, объединить их в один большой байт-код файл, а затем оптимизировать все целиком, вместо оптимизации по каждому файлу или по функции, которую вы получаете с другими компиляторами, с помощью llvm вы можете получить любой уровень частичной компиляции для оптимизации программы, который вам нравится. затем вы можете взять этот байт-код и использовать llc, чтобы экспортировать его в ассемблер целей. Я обычно делаю встраивание, так что у меня есть собственный код запуска, который я обертываю вокруг этого, но теоретически вы сможете взять этот файл на ассемблере и с помощью gcc скомпилировать и связать его и запустить. gcc myfile.s -o myfile. Я полагаю, что есть способ заставить инструменты llvm сделать это и не использовать binutils или gcc, но я не потратил время.
Мне нравится llvm, потому что он всегда кросс-компилятор, в отличие от gcc, вам не нужно компилировать новый для каждой цели и разбираться с нюансами для каждой цели. Я не знаю, что я использую JIT, потому что я говорю, что использую его как кросс-компилятор и как нативный компилятор.
Итак, ваш первый случай - это начало, середина, конец, и процесс скрыт от вас, вы начинаете с исходного кода и получаете двоичный файл, готово. Второй случай, если я правильно понимаю фронт и середину и остановлюсь на каком-нибудь файле, который представляет середину. Тогда промежуточное звено (конкретный целевой процессор) может произойти как раз вовремя во время выполнения. Разница в этом заключается в том, что бэкэнд, выполнение среднего языка второго случая в реальном времени, скорее всего, отличается от бэкэнда первого случая.