Нужно ли авторам компиляторов «понимать» машинный код? [закрыто]


10

Это может быть странный вопрос.

Парень, пишущий компилятор C ++ (или любой другой язык, не относящийся к VM): должен ли он уметь читать / писать на машинном языке? Как это работает?

РЕДАКТИРОВАТЬ: я специально имею в виду компиляторы, которые компилируются в машинный код, а не в какой-либо другой язык программирования.



1
Нет. Вам даже не нужно это знать, вы можете просто слепо, бездумно скопировать вашу целевую спецификацию ISA: dl.acm.org/citation.cfm?id=1706346
SK-logic

1
Coffescript компилируется в javascript.
Картик

@Kartik Включает ли компилятор CoffeeScript, который компилируется в Javascript, также компилятор Javascript, который компилируется в любой компилятор Javascript? Или он компилируется только в исходный код Javascript и ничего более?
Авив Кон

Компилятор coffeescript просто конвертирует cofeescript в javascript. Javascript не скомпилирован, он обрабатывается браузером. Я хотел сказать, что вы можете написать компилятор, который компилирует один язык на другой, вам не нужно знать машинный язык для этого. Другим примером является компилятор SPL, который компилирует пьесы Шекспира в C ++. shakespearelang.sourceforge.net
Картик,

Ответы:


15

Нет, совсем нет. Вполне возможно (и часто даже предпочтительнее), чтобы ваш компилятор испускал ассемблерный код. Затем ассемблер позаботится о создании фактического машинного кода.

Между прочим, ваше различие между реализацией без виртуальной машины и реализацией виртуальной машины бесполезно.

  • Для начала, использование виртуальной машины или прекомпиляция в машинный код - это просто разные способы реализации языка; в большинстве случаев язык может быть реализован с использованием любой стратегии. Я действительно должен был использовать интерпретатор C ++ один раз.

  • Кроме того, многие виртуальные машины, такие как JVM, имеют двоичный машинный код и некоторый ассемблер, как обычная архитектура.

LLVM (который используется компиляторами Clang) заслуживает особого упоминания: он определяет виртуальную машину, для которой инструкции могут быть представлены в виде байтового кода, текстовой сборки или структуры данных, что позволяет очень легко вывести их из компилятора. Поэтому, хотя это было бы полезно для отладки (и для понимания того, что вы делаете), вам даже не нужно знать о языке ассемблера, только об API LLVM.

Приятной особенностью LLVM является то, что его виртуальная машина является просто абстракцией и что байт-код обычно не интерпретируется, а вместо этого прозрачно JITted. Таким образом, вполне возможно написать эффективно скомпилированный язык, даже не зная набора команд вашего процессора.


И еще одно приятное свойство LLVM заключается в том, что не нужно глубоко понимать целевой ISA для реализации эффективного бэкэнда. Это довольно декларативно, так что можно почти скопировать и вставить спецификацию ISA в файлы .td, даже не пытаясь ее понять.
SK-logic

Спасибо за ответы. Вопрос: Я понимаю из вашего ответа и других ответов, что компилятор не должен понимать машинный код, он может использовать другой инструмент, который выполняет для него фактическое преобразование в машинный код. Однако парень, который написал этот инструмент , должен был понимать машинный язык, верно? Парень, который написал программное обеспечение, которое выполняет фактическое преобразование из некоторого языка в машинный код, должен действительно понимать машинный язык, верно?
Авив Кон

5
@ Лягушка да. Если вы создаете слой абстракции, вам нужно только понять слой ниже вас. Хотя полезно иметь базовое представление обо всех слоях, в этом нет необходимости. Вам не нужно понимать квантовую физику, чтобы использовать транзистор. Вам не нужно понимать дизайн чипа, чтобы использовать процессор. Вам не нужно знать машинный код, чтобы написать сборку. Вам не нужно знать набор инструкций платформы при использовании виртуальной машины и т. Д. Но кто-то должен был построить эти уровни абстракции ниже вашего.
Амон

1
@ SK-logic Не соответствует действительности (по крайней мере, если вы хотите хороший код) из того, что я слышал. У людей, которые внедрили бэкэнд Aarch64 для llvm, было довольно много проблем (перемещение для одного, ужасные шаблоны загрузки, ...). И это игнорирует самого большого слона в комнате: модель памяти ISA и модель памяти языка, который вас интересует. Вы можете работать над компилятором, но вы не можете работать с бэкэндом, не разбираясь в архитектуре ...
Во

1
Я бы добавил, что концептуальной разницы между сборкой и машинным кодом действительно нет. На самом деле вы не получите большой пользы, узнав, как использовать конкретную инструкцию, каков код операции этой инструкции. Если вы знаете, как использовать MOV, это не имеет значения, если вы не знаете, что это инструкция 27, которая, я думаю, похожа на то, что описывает @ SK-logic.
whatsisname

9

Нет. Ключевым моментом вашего вопроса является то, что компиляция является чрезвычайно широким термином. Компиляция может происходить с любого языка на любой язык. А ассемблер / машинный код - только один из многих языков для цели компиляции. Например, языки Java и .NET, такие как C #, F # и VB.NET, все компилируются в некоторый промежуточный код вместо машинного кода. Не имеет значения, запускается ли он на виртуальной машине, язык все еще компилируется. Существует также возможность компиляции на какой-то другой язык, например C. C - довольно популярная цель компиляции, и многие инструменты делают это. И, наконец, вы можете использовать какой-нибудь инструмент или библиотеку для тяжелой работы по созданию машинного кода для вас. например, есть LLVM, который может уменьшить усилия, необходимые для создания автономного компилятора.

Кроме того, ваше редактирование не имеет никакого смысла. Это все равно, что спросить: «Каждый инженер должен понимать, как работает двигатель? И я спрашиваю об инженерах, работающих над двигателями». Если вы работаете с программой или библиотекой, которая генерирует машинный код, вы должны это понимать. Дело в том, что вам не нужно делать такие вещи при написании компилятора. Многие люди делали это раньше вас, поэтому у вас должна быть серьезная причина сделать это снова.


И человек, пишущий инструмент или библиотеку, которая выполняет фактическое преобразование в машинный язык, должен полностью понимать машинный язык, верно?
Авив Кон

3
@Prog Вам нужно полностью понимать язык программирования, чтобы программировать на нем? Нет, но вы, возможно, напишите неоптимальный код, и вы не сможете делать определенные вещи, которые другие могут выполнять. Вам нужно полностью понимать машинный язык, если вы пишете компилятор, который переводит на это. Нет, но ваш компилятор будет неоптимальным и неспособным делать определенные вещи.
Sumurai8

@ Sumurai8: хотя в некоторой степени вы можете «понимать» машинный язык по кусочкам, чтобы написать эмиттер машинного кода, который понимает все это лучше, чем вы. Например, если вы пишете хорошую инфраструктуру, вы можете настроить определение каждого кода операции вместе с его стоимостью и соображениями конвейерной обработки, а затем ваша инфраструктура может написать оптимизированный машинный код, даже если у вас нет опыта оптимизации этого конкретного компьютера. Умение грамотно программировать этот машинный код, вероятно, не повредит.
Стив Джессоп

@SteveJessop Если вы понимаете каждый код операции до такой степени, что вы можете научить машину, как связывать этот код вместе с другими кодами операций, чтобы выразить концепцию более высокого уровня, вы полностью понимаете машинный язык. Тогда вам просто лень найти оптимальное решение для каждой проблемы ;-)
Sumurai8

@ Sumurai8: хм, но, по крайней мере, в принципе, я мог бы «кратко» понять каждый код операции за пять минут, которые требуются мне для его настройки, а затем забыть его к тому времени, когда я «пойму» код операции после следующего. Это, вероятно, не то, что спрашивающий подразумевает под «умением читать / писать на машинном языке». Конечно, я предполагаю, что чертовски хорошая структура здесь достаточно настраиваемая, чтобы определять и использовать всю полезную информацию о каждом коде операции набора команд. LLVM в некоторой степени стремится к этому, но, по словам "Voo" (в комментарии ниже), не достиг этого.
Стив Джессоп

3

Классически компилятор состоит из трех частей: лексического анализа, анализа и генерации кода. Лексический анализ разбивает текст программы на ключевые слова, имена и значения языка. Анализирует, как токены, полученные из лексического анализа, объединяются в синтаксически правильные выражения для языка. Генерация кода берет структуры данных, созданные синтаксическим анализатором, и переводит их в машинный код или другое представление. В настоящее время лексический анализ и анализ могут быть объединены в один шаг.

Очевидно, что человек, пишущий генератор кода, должен понимать целевой машинный код на очень глубоком уровне, включая наборы команд, конвейеры процессора и поведение кэша. В противном случае программы, созданные компилятором, будут медленными и неэффективными. Они вполне могут читать и писать машинный код, представленный восьмеричными или шестнадцатеричными числами, но обычно они пишут функции для генерации машинного кода, ссылаясь внутренне на таблицы машинных инструкций. Теоретически, люди, пишущие лексер и парсер, могут ничего не знать о генерации машинного кода. Фактически, некоторые современные компиляторы позволяют вам подключать свои собственные процедуры генерации кода, которые могут генерировать машинный код для некоторого процессора, о котором никогда не слышали авторы лексера и анализатора.

Однако на практике разработчики компиляторов на каждом этапе много знают о различных архитектурах процессоров, и это помогает им спроектировать структуры данных, необходимые для этапа генерации кода.


2

Давным-давно я написал компилятор, который конвертировал два разных сценария оболочки. Это не подходило к машинному коду.

Запись компилятора должна понимать их вывод , но это часто не машинный код.

Большинство программистов никогда не напишут компилятор, который выводит машинный код или код сборки, но пользовательские компиляторы могут быть очень полезны во многих проектах для получения других выходных данных.

YACC - один из таких компиляторов, который не выводит машинный код…


0

Вам не нужно начинать с детального знания семантики ваших входных и выходных языков, но лучше закончить с изящно детальным знанием обоих, иначе ваш компилятор будет ошибочно работать. Поэтому, если ваш ввод - C ++, а ваш вывод - какой-то конкретный машинный язык, вам в конечном итоге нужно будет знать семантику обоих.

Вот некоторые тонкости при компиляции C ++ в машинный код: (я уверен, что есть еще кое-что, о чем я забыл).

  1. Какой размер будет int? «Правильный» выбор здесь - это искусство, основанное как на естественном размере указателя машины, производительности АЛУ для различных арифметических операций, так и на выборе, сделанном существующими компиляторами для машины. Есть ли у машины даже 64-битная арифметика? Если нет, то добавление 32-разрядных целых чисел должно переводиться в инструкцию, а добавление 64-разрядных целых чисел должно переводиться в вызов функции для выполнения 64-разрядного сложения. Есть ли в машине 8-битные и 16-битные операции добавления, или вам нужно имитировать операции с 32-битными операциями и маскированием (например, DEC Alpha 21064)?

  2. Какое соглашение о вызовах используется другими компиляторами, библиотеками и языками на компьютере? Параметры помещаются в стек справа налево или слева направо? Некоторые параметры входят в регистры, в то время как другие идут в стек? Находятся ли ints и float в разных пространствах регистров? Нужно ли обрабатывать параметры, выделенные регистру, при вызовах varargs? Какие регистры сохраняются вызывающим абонентом, а какие сохраняются вызываемым абонентом? Можете ли вы выполнить оптимизацию листового вызова?

  3. Что делает каждая из инструкций по смене машины? Если вы попросите сдвинуть 64-битное целое число на 65 бит, каков результат? (На многих машинах результат аналогичен сдвигу на 1 бит, на других - «0».)

  4. Какова семантика согласованности памяти машины? C ++ 11 имеет очень четко определенную семантику памяти, которая в некоторых случаях накладывает ограничения на некоторые оптимизации, но разрешает оптимизацию в других случаях. Если вы компилируете язык, который не имеет четко определенной семантики памяти (как каждая версия C / C ++ до C ++ 11 и многих других императивных языков), вам придется изобретать семантику памяти по мере продвижения, и обычно Вы захотите изобрести семантику памяти, которая лучше всего соответствует вашей семантике машины.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.