В состав чего входят транслятор интерпретатор ассемблера

В состав чего входят транслятор интерпретатор ассемблера

Компилятор –система программ, осуществляющая трансляцию всей исходной программы в машинную.

Ассемблер –системная программа, осуществляющая процесс перевода исходной программы, заданной на машинно-ориентированном языке (ассемблере), в машинную программу.

Интерпретатор —системная программа, осуществляющая синтаксический контроль операторов исходной программы и последовательное выполнение ее команд.

Эмулятор – имитация функционирования одной системы средствами другой без потери функциональных возможностей или искажения получаемых результатов.

Супервизор – управляющая программа, определяющая очередность выполнения программирования.

Информационное обеспечение (ИО)– совокупность информационных связей, необходимых для выполнения автоматического выполнения (рис. 3.5).

Система управления базой данных

Прогнозы, планы
Справочные данные
Типовые проектные решения
Система документации
Система классификации и кодирования Файлы, блоки данных

Рис. 3.5. Структурная схема информационного обеспечения

Лингвистическое обеспечение (ЛО) – совокупность языков программирования, включающая термины, определения, правила формализации естественного языка (рис. 3.6).

Правила формализации естественного языка
ЯП1
ЯП2

ЯПn
Методы сжатия и развертывания текстов

Рис. 3.6. Структурная схема лингвистического обеспечения

Техническое обеспечение (ТО) – совокупность взаимодействий и взаимосвязей технических средств САПР. Ориентировочная структура ТС САПР приведена на рис..

При организации структуры ТС САПР сохраняется принцип иерархичности: каждая система может рассматриваться как самостоятельная система, выполняющая определенные функции САПР независимо от другой.

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

Организационное обеспечение (ОО) – совокупность инструкций штатного расписания функций подразделений, порядок взаимодействий в условиях САПР.

В состав ОО входят положения о САПР, инструкции, штатное расписание, функции подразделений и тому подобное.

Рис. 3.7 Структурная схема технических средств САПР

Контрольные вопросы для самопроверки

1. Назовите основные принципы, заложенные в основу автоматизированного проектирования тканей.

2. На каких формах научного познания базируется методология САПР тканей.

3. Что Вы подразумеваете под термином «методология».

4. Назовите преимущества блочно-иерархического принципа проектирования.

5. В чем заключается цель и сущность деления процесса проектирования на стадии и уровни проектирования.

6. Что такое пространство проектирования.

7. Проанализируйте отличия между стадиями и уровнями проектирования.

8. Охарактеризуйте содержание работ на различных этапах проектирования ткани.

9. Назовите признаки классификации САПР.

10. Охарактеризуйте структуру САПР и покажите ее структурный состав автоматизированного комплекса средств проектирования ткани.

11. Назовите и дайте определения структурным подразделениям САПР

Практически во всех трансляторах (и в компиляторах, и в интерпретаторах) в том или ином виде присутствует большая часть перечисленных ниже процессов: лексический анализ; синтаксический анализ; семантический анализ; генерация внутреннего представления программы; оптимизация; генерация объектной программы. Транслятор — это программа, которая переводит входную программу на исход­ном (входном) языке в эквивалентную ей выходную программу на результирую­щем (выходном) языке. В работе транслятора уча­ствуют всегда три программы: 1) сам транслятор является программой обычно он входит в состав системного по вычислительной системы. То есть транс­лятор — это часть по. Он представляет собой на­бор машинных команд и данных и выполняется компьютером, как и все прочие программы в рамках ос. 2)исходными данными для работы транслятора служит текст входной программы — некоторая последовательность предложений входного языка про­граммирования. Этот файл должен содержать текст программы, удовлетворяющий синтаксическим и семантическим требова­ниям входного языка. 3)выходными данными транслятора является текст результирующей программы. Результирующая программа строится по синтаксическим правилам, заданным в выходном языке транслятора, а ее смысл определяется семантикой выходного языка. Важным требованием в определении транслятора является эк­вивалентность входной и выходной программ т.е совпадение их смысла с точки зрения семантики входного языка (для исходной программы) и семантики выходного языка (для результирующей про­граммы). Чтобы создать транслятор, необходимо, прежде всего, выбрать входной и выходной языки. С точки зрения преобразования предложений входного язы­ка в эквивалентные им предложения выходного языка транслятор выступает как переводчик. Результатом работы транслятора будет результирующая программа в том случае, если текст исходной программы является правильным — не со­держит ошибок с точки зрения синтаксиса и семантики входного языка. Если исходная программа неправильная, то резуль­татом работы транслятора будет сообщение об ошибке. Кроме понятия «транслятор»широко употребляется также близкое ему по смыс­лу понятие«компилятор».Компилятор — это транслятор, который осуществляет перевод исходной програм­мы в эквивалентную ей объектную программу на языке машинных команд или на языке ассемблера.т.о. Компилятор отличается от транслятора лишь тем, что его ре­зультирующая программа всегда должна быть написана на языке машинных ко­дов или на языке ассемблера. Результирующая программа компилятора называется«объектной программой»или«объектным кодом». Файл, в который она записана, обычно называется«объ­ектным файлом». Порожденная компилятором программа не может непосредственно выполняться на компьютере, так как она не привязана к конкретной области па­мяти, где должны располагаться ее код и данные..компиляторы, безусловно, самый распространенный вид трансляторов. Они име­ют самое широкое практическое применение, которым обязаны широкому рас­пространению всевозможных языков программирования. Сейчас в современных системах программирования стали появляться компиляторы, в которых результирующая программа создается не на языке машинных команд и не на языке ассемблера, а на некотором промежуточном языке. Он не может непосредственно исполняться на компьюте­ре, а требует специального промежуточного интерпретатора, для выполнения написан­ных на нем программ.интерпретатор — это программа, которая воспринимает входную программу на исходном языке и выполняет ее.в отличие от трансляторов интерпретаторы не порождают результирующую про­грамму — и в этом принципиаль­ная разница между ними. Интерпретатор, так же как и транслятор, анализирует текст исходной программы. Но он не порождает результирующей программы, а сразу же выполняет исходную в соответствии с ее смыслом, заданным семанти­кой входного языка. Т.о, результатом работы интерпретатора будет некоторый желаемый рез-т(если программа правильна) или сообщение об ошибке. Чтобы исполнить исходную программу, интерпретатор должен преобразовать ее в язык машинных кодов. Полученные ма­шинные коды не доступны пользователю. Они порождаются интер-ом, исполняются и уничтожаются по мере надобности. Пользователь видит результат выполнения этих кодов — т.е результат выполнения исходной программы.

Назначение трансляторов, компиляторов и интерпретаторов

Первыми компиляторами были компиляторы с языков ассемблера или, как они назывались, мнемокодов. Мнемокоды превратили текст программы, написанный на языке машинных команд в более-менее доступный пониманию специалиста язык. Соз­давать программы стало значительно проще, но исполнять сам мнемокод ни один компьютер неспособен, соответственно, возникла не­обходимость в создании компиляторов. Следующим этапом стало создание языков высокого уровня. Они представля­ют собой промежуточное звено между чисто формальными языками и языками естественного общения людей. От первых им досталась строгая фор­мализация синтаксических структуру предложений языка, от вторых — значи­тельная часть словарного запаса, семантика основных конструкций и выражений. Появление языков высокого уровня существенно упростило процесс программи­рования. Однако преобладают компьютеры традиционной, архитектуры, которые уме­ют понимать только машинные команды, поэтому вопрос о создании компилято­ров продолжает быть актуальным. Компиляторы создавались и продолжают создаваться не только для новых, но и для давно известных язы­ков. С тех пор как большинство теоретических аспектов в области ком­пиляторов получили свою практическую реализацию (это про­изошло в конце 60-х годов), развитие компиляторов пошло по пути их дружественности пользователю, разработчику программ на языках высокого уровня. Логичным завершением этого процесса стало создание систем программирования — программных комплексов, объединяющих в себе кроме непосредственно компиляторов множество связанных с ними компонен­тов по.на сегодняшний день компиляторы являются неотъемлемой частью любой вычислительной системы. Без их существования программирование любой прикладной задачи было бы затруднено, а то и просто невозможно. Да и программирование специализированных системных задач, как правило, ведется если не на языке высокого уровня, то на ассемблере, следовательно, применяется соответствующий компилятор. Компиляторы обычно несколько проще в реализации, чем интерпретаторы. По эффективности они также превосходят их — очевидно, что откомпилированный код будет исполняться всегда быстрее, чем происходит интерпретация аналогичной исходной программы. Кроме того, не каждый язык программирования допускает построение простого интерпретатора. Однако, интерпретаторы имеют одно существенное преимущество — откомпилированный код всегда привязан к архитектуре вычислительной системы, на которую он ори­ентирован, а исходная программа — только к семантике языка программирования, которая гораздо легче поддается стандартизации. Первыми компиляторами были компиляторы с мнемокодов. Их потомки — со­временные компиляторы с языков ассемблера — существую практически для всех известных вычислительных систем. Они предельно жестко ориентированы на архитектуру. Затем появились компиляторы с таких языков, как fortran, algol-68,. Они были ориентированы на большие эвм с пакетной обра­боткой задач. Из вышеперечисленных языков, только fortran продолжает использоваться по сей день, поскольку имеет огромное количество библиотек различного назначения. На рынке программных систем доминируют компиля­торы языков с и c++. Первый из них родился вместе с операционными системами типа unix, а затем перешел под ос других типов. Второй удачно воплотил в себе пример реализации идей объектно-ориентированного программирования на хорошо зарекомендовавшей себя прак­тической базе. Изна­чально интерпретаторам не предавали существенного значения, поскольку почти по всем пара­метрам они уступают компиляторам. Тем не менее сейчас ситуация несколько изменилась, поскольку вопрос о переносимости программ и их аппаратно-платформенной независимости приоб­ретает все большую актуальность с развитием сети интернет. Самый известный сейчас пример — это язык java (сам по себе он сочетает компиляцию и интерпре­тацию), а также связанный с ним javascript. Кроме того, язык html, на ко­тором зиждется протокол http — это тоже интерпретируемый язык.

Читайте также:  Как запустить компьютер через интернет

Этапы трансляции. Общая схема работы транслятора

Процесс компиляции состоит из двух основных этапов — синтеза и анализа. На этапе анализа выполняется распознавание текста исходной программы, соз­дание и заполнение таблиц идентификаторов. Результатом его работы служит внутреннее представление программы, понятное компилятору. На этапе синтеза на основании внутреннего представления программы и инфор­мации, содержащейся в таблице идентификаторов, порождается текст результирующей программы. Результатом этого этапа является объектный код. Кроме того, в составе компилятора присутствует часть, ответственная за анализ и исправление ошибок, которая при наличии ошибки в тексте исходной про­граммы должна максимально полно информировать пользователя о типе ошиб­ки и месте ее возникновения. В лучшем случае компилятор может предложить пользователю вариант исправления ошибки. Эти этапы, в свою очередь, состоят из более мелких этапов, называемых фазами компиляции. Компилятор в целом с точки зрения теории формальных языков выполняет две основные функции. Во-первых, он является распознавателем для языка исходной программы. Т.е он должен получить на вход цепочку символов входного языка, проверить ее принадлежность языку и, более того, выявить правила, по которым эта цепочка была построена. Генератором цепочек входно­го языка выступает пользователь — автор входной программы. Во-вторых, компилятор является генератором для языка результирующей про­граммы. Он должен построить на выходе цепочку выходного языка по опреде­ленным правилам, предполагаемым языком машинных команд или языком ас­семблера. Лексический анализ (сканер) — это часть компилятора, которая читает литеры программы на исходном языке и строит из них слова (лексемы) исходного язы­ка. На вход лексического анализатора поступает текст исходной программы, а выходная информация передается для дальнейшей обработки компилятором на этапе синтаксического разбора. Синтаксический разбор — это основная часть компилятора на этапе анализа. Она выполняет выделение синтаксических конструкций в тексте исходной програм­мы, обработанном лексическим анализатором. На этой же фазе компиляции проверяется синтаксическая правильность программы. Синтаксический разбор играет главную роль — роль распознавателя текста входного языка программи­рования. Семантический анализ — это часть компилятора, проверяющая правильность текста исходной программы с точки зрения семантики входного языка. Кроме непосредственно проверки, семантический анализ должен выполнять преобра­зования текста, требуемые семантикой входного языка. Подготовка к генерации кода — это фаза, на которой компилятором выполняют­ся предварительные действия, непосредственно связанные с синтезом текста ре­зультирующей программы, но еще не ведущие к порождению текста на выход­ном языке. Генерация кода — это фаза, непосредственно связанная с порождением команд, составляющих предложения выходного языка и в целом текст результирующей программы. Это основная фаза на этапе синтеза результирующей программы. Кроме непосредственного порождения текста результирующей программы, гене­рация обычно включает в себя также оптимизацию — процесс, связанный с обра­боткой уже порожденного текста. Таблицы идентификаторов (иногда — «таблицы символов») — это специальным образом организованные наборы данных, служащие для хранения информации об элементах исходной программы, которые затем используются для порожде­ния текста результирующей программы. Таблица идентификаторов в конкрет­ной реализации компилятора может быть одна, а несколько. Элементами исходной программы, информацию о которых нужно хра­нить в процессе компиляции, являются переменные, константы, функции и т. П. — конкретный состав набора элементов зависит от используемого входного языка программирования. В более общем виде: на фазе лексического анализа лексемы выделяются из текста вход­ной программы постольку, поскольку они необходимы для следующей фазы син­таксического разбора. Синтаксический раз­бор и генерация кода могут выполняться одновременно. Таким т.о, эти три фазы компиляции могут работать комбинированно, а вместе с ними может вы­полняться и подготовка к генерации кода.

Понятие прохода. Многопроходные и однопроходные компиляторы

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

Проход — это процесс последовательного чтения компилятором данных из внеш­ней памяти, их обработки и помещения результата работы во внешнюю память. Чаще всего один проход включает в себя выполнение одной или нескольких фаз компиляции. Результатом промежуточных проходов является внутреннее пред­ставление исходной программы, результатом последнего прохода — результи­рующая объектная программа.

В качестве внешней памяти могут выступать любые носители информации — оперативная память компьютера, накопители на магнитных дисках, магнитных лентах и т. П. Современные компиляторы, как правило, стремятся максимально использовать для хранения данных оперативную память компьютера, и только при недостатке объема доступной памяти используются накопители на жестких магнитных дисках.

При выполнении каждого прохода компилятору доступна информация, полу­ченная в результате всех предыдущих проходов. Он стремится ис­пользовать в первую очередь только информацию, полученную на проходе, не­посредственно предшествовавшем текущему, но в принципе может обращаться и к данным от более ранних проходов вплоть до исходного текста программы. Информация, получаемая компилятором при выполнении проходов, недоступна пользователю. Она либо хранится в оп, которая освобождает­ся компилятором после завершения процесса трансляции, либо оформляется в виде временных файлов на диске, которые также уничтожаются после завер­шения работы компилятора. Поэтому человек, работающий с компилятором, мо­жет даже не знать, сколько проходов выполняет компилятор — он всегда видит только текст исходной программы и результирующую объектную программу. Но количество выполняемых проходов — это важная техническая характеристика компилятора, солидные фирмы — разработчики компиляторов обычно указыва­ют ее в описании своего продукта.

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

Однако сократить число проходов не всегда удается. Количество необходимых проходов определяется, прежде всего, грамматикой и семантическими правилами исходного языка. Чем сложнее грамматика языка и чем больше вариантов пред­полагают семантические правила — тем больше проходов будет выполнять ком­пилятор

Однопроходные компиляторы — редкость, они возможны только для очень про­стых языков. Реальные компиляторы выполняют, как правило, от двух до пяти проходов. Т,о, реальные компиляторы являются многопроходными. Наиболее распространены двух- и трехпроходные компиляторы, например: пер­вый проход — лексический анализ, второй — синтаксический разбор и семанти­ческий анализ, третий — генерация и оптимизация кода (варианты исполнения, конечно, зависят от разработчика). В современных системах программирования нередко первый проход компилятора (лексический анализ кода) выполняется параллельно с редактированием кода исходной программы.

Читайте также:  D link dvg n5402sp настройка

Интерпретаторы. Особенности построения интерпретаторов

Интерпретатор — это программа, которая воспринимает входную программу на исходном языке и выполняет ее. Основное отличие интерпретаторов от трансляторов и компиляторов заключается в том, что интер­претатор не порождает результирующую программу, а просто выполняет исход­ную программу. Термин «интерпретатор» (interpreter) означает«перевод­чик». Простейшим способом реализации интерпретатора можно было бы считать ва­риант, когда исходная программа сначала полностью транслируется в машинные команды, а затем сразу же выполняется. В такой реализации интерпретатор, мало бы, чем отличался от компилятора с той лишь разницей, что результи­рующая программа в нем была бы недоступна пользователю. Недостатком тако­го интерпретатора было бы то, что пользователь должен был бы ждать компиля­ции всей исходной программы прежде, чем начнется ее выполнение. По сути, в таком интерпретаторе не было бы никакого особого смысла — он не давал бы никаких преимуществ по сравнению с аналогичным компилятором. Поэтому подавляющее большинство интерпретаторов действует так, что испол­няет исходную программу последовательно, по мере ее поступления на вход ин­терпретатора. Тогда пользователю не надо ждать завершения компиляции всей исходной программы. Более того, он может последовательно вводить исходную программу и тут же наблюдать результат ее выполнения по мере ввода команд. При таком порядке работы интерпретатора проявляется существенная особен­ность, которая отличает его от компилятора, — если интерпретатор исполняет команды по мере их поступления, то он не может выполнять оптимизацию ис­ходной программы. Следовательно, фаза оптимизации в общей структуре интерпретатора будет отсутствовать. В остальном же она будет мало отличаться от структуры аналогичного компилятора. Далеко не все языки программирования допускают построение интерпретаторов, которые могли бы выполнять исходную программу по мере поступления команд. Для этого язык должен допускать возможность существования компилятора, выполняющего разбор исходной программы за один проход. Кроме того, язык не может интерпретироваться по мере поступления команд, если он допускает по­явление обращений к функциям и структурам данных раньше их непосредствен­ного описания. Отсутствие шага оптимизации ведет к тому, что выполнение программы с помо­щью интерпретатора является менее эффективным, чем с помощью аналогично­го компилятора. Т.о, интерпретаторы всегда проигрывают компиляторам в про­изводительности. Преимуществом интерпретатора является независимость выполнения програм­мы от архитектуры целевой вычислительной системы. В результате компиляции получается объектный код, который всегда ориентирован на определенную архи­тектуру. Для перехода на другую архитектуру целевой вычислительной системы программу требуется откомпилировать заново. А для интерпретации программы необходимо иметь только ее исходный текст и интерпретатор с соответствующе­го языка. Интерпретаторы существовали для ограниченного кру­га относительно простых языков программирования (basic). Высокопроизводительные профессиональные средства разработки программно­го обеспечения строились на основе компиляторов. Новый импульс развитию интерпретаторов придало распространение глобаль­ных вычислительных сетей. Такие сети могут включать в свой состав эвм раз­личной архитектуры, и тогда требование единообразного выполнения на каждой из них текста исходной программы становится определяющим. Поэтому с разви­тием глобальных сетей и распространением всемирной сети интернет появилось много новых систем, интерпретирующих текст исходной программы. В современных системах программирования существуют реализации по, сочетающие в себе и функции компилятора, и функции интер­претатора — в зависимости от требований пользователя исходная программа либо компилируется, либо исполняется (интерпретируется). Некото­рые современные языки программирования предполагают две стадии разработ­ки: сначала исходная программа компилируется в промежуточный код, а затем этот результат компиляции выполняется с помощью интерпретатора данного промежуточного языка. Примером интерпретируемого языка может служить html (hypertext markup language) — язык описания гипертекста или языки java и javascript — сочетают в себе функции компиля­ции и интерпретации.

HCF, n. Mnemonic for ‘Halt and Catch Fire’, any of several undocumented and semi-mythical machine instructions with destructive side-effects
Jargon File

С ассемблером в сердце — ядро симулятора

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

Интерпретатор и интринсики

Intel C/C++ Compiler Intrinsic Equivalent
LZCNT:
unsigned __int32 _lzcnt_u32(unsigned __int32 src);
LZCNT:
unsigned __int64 _lzcnt_u64(unsigned __int64 src);

Эти же интринсики работают и в GCC. Ниже я провёл небольшой эксперимент:

С флагом оптимизации -O3 компилятор всё сделал без нареканий: от «функции» _lzcnt_u64() не осталось ни пролога, ни эпилога, одна только машинная инструкция, которая нам и нужна.
Как и машинных инструкций, интринсиков обычно много (но всё же меньше, чем инструкций). Каждый компилятор предоставляет свой набор, в чём-то похожий, в чём-то отличающийся от остальных.

  • Интринсики, присутствующие в компиляторах компании Microsoft, описаны в MSDN отдельно для x86 и x64.
  • Документация к интринсикам компилятора Intel C/C++ уже несколько лет доступна в удобном интерактивном формате на веб-странице. Довольно удобно получается фильтровать их по классу расширения (SSE2, SSE3, AVX и т.д.) и по функциональности (операции над битами, логические, криптографические и т.д.), а также получать справку по семантике и по скорости работы (в тактах).
  • Интринсики компилятора GCC для IA-32 в основном совпадают с описанными для ICC.
  • Для Clang я не нашёл внятной документации на доступные интринсики для какой-либо архитектуры. Если у читателя есть актуальная информация по этому вопросу, то прошу поделиться ей в комментариях.

По сравнению с рукописными секциями inline-ассемблера, интринсики обладают следующими преимуществами.

  1. Вызов функции гораздо привычнее, его легче понять и меньше шансы напортачить в нём при написании. Интринсики переносят работу по выделению входных и выходных регистров на компилятор, а также позволяют ему провести проверку синтаксиса, соответствие типов и прочие полезные вещи и при необходимости сообщить о проблемах. В случае inline-кода диагностика ассемблера будет куда более загадочной. Тот, кому часто приходится выписывать clobber-спецификации для GNU as (и ошибаться в них), со мной согласится.
  2. Интринсики не являются для компилятора «чёрными ящиками» inline-ассемблера, в которых происходят неизвестные ему обновления регистров и памяти. Соответственно его алгоритмы распределения регистров могут учитывать это в процессе обработки кода процедуры. В результате легче получить более быстрый код.
  3. Интринсики имеют хоть и слабую, но переносимость между компиляторами (но не хозяйскими архитектурами). В крайнем случае можно написать по прототипу свой вариант реализации, если хозяйская архитектура не поддерживает инструкцию напрямую. Пример из практики: SSE2-инструкция CVTSI2SD xmm, r/m64 не имеет валидной кодировки в 32-битном режиме процессора. Соответственно нет и интринсика, тогда как в 64-битном режиме, для которого изначально разрабатывался некий инструмент, он был, и код его использовал. При компиляции кода на 32-битном хозяине выдавалась ошибка. Поскольку процедура, завязанная на этот интринсик, не была «горячей» (скорость работы приложения слабо от неё зависела), была написана своя реализация _mm_cvtsi64_sd() на Си, которая подставлялась в случае 32-битной сборки.

По этим или каким-то иным причинам компания Microsoft прекратила поддержку inline-ассемблера в MS Visual Studio 2010 и более поздних для архитектуры x64. Для вставки машинного кода в файлы с Си/C++ в этом случае остаются доступны только интринсики.
Однако я пошёл бы против правды, сказав, что использование интринсиков является панацеей. Всё же необходимо приглядывать за кодом, генерируемом компилятором, особенно когда требуется выжать из него максимум производительности.

Двоичный транслятор и кодогенерация

Двоичный транслятор (далее ДТ) как правило работает быстрее интерпретатора, потому что преобразует целые блоки гостевого машинного кода в эквивалентные им блоки хозяйского машинного кода, которые затем, в случае горячего кода, многократно запускаются. Интерпретатор же (если в нём не реализовано кэширование) вынужден обрабатывать каждую встретившуюся гостевую инструкцию с нуля, даже если он совсем недавно с ней работал.
И, в отличие от интерпретатора, который можно от начала и до конца написать, не вникая в особенности хозяйской архитектуры, ДТ потребует знание и ассемблера, и кодировок машинных инструкций. При переносе своего симулятора на новую хозяйскую систему существенную часть его, отвечающую именно за кодогенерацию, придётся переписать. Такова цена скорости работы.
В этой статье я опишу один из простых способов построения так называемого шаблонного транслятора. Если будет интерес, то как-нибудь в другой раз я постараюсь рассказать о более продвинутом способе двоичной трансляции.
Получив от декодера информацию о гостевой инструкции, ДТ генерирует для неё кусочек машинного кода — капсулу. Для нескольких инструкций, исполняющихся последовательно, создаётся блок трансляции, состоящий из их капсул, записанных последовательно. В результате, когда в гостевой системе управление передаётся на первую оттранслированную инструкцию, для симуляции этой и последующих команд достаточно исполнить код из блока трансляции.
Как сгенерировать код для гостевой инструкции, зная её опкод и значения операндов? По опкоду симулятор выбирает шаблон — заготовку хозяйского машинного кода, реализующую нужную семантику. От процедур, обычно создаваемых компилятором, её отличает отсутствие пролога и эпилога, так как мы напрямую «склеиваем» такие шаблоны в единый блок трансляции. Однако этого ещё недостаточно для того, чтобы пометить блок трансляции как готовый.
Осталась невыполненной ещё одна задача — передать значения операндов как аргументы шаблону, таким образом его специализировав и превратив в капсулу. Причём передавать операнды чаще всего надо именно на этапе трансляции: они уже известны. То есть надо «зашить» их прямо в хозяйский код капсулы. С неявными операндами (например, лежащими на стеке значениями) это не получится, и их, конечно, придётся обрабатывать на этапе симуляции, тратя при этом время.
Если размерность множества (= число комбинаций) явных операндов невелика, то их можно «вшить» в группу шаблонов для данной инструкции — по одному на каждую комбинацию. В результате для каждого гостевого опкода придётся выбирать из N шаблонов согласно тому, какие значения приняли операнды в каждом конкретном случае.
К сожалению, не всё так просто. На практике чаще всего генерировать шаблоны для всевозможных значений операндов нереально из-за комбинаторного взрыва их числа. Так, трёхоперандная команда на архитектуре с 32 регистрами потребует по 32×32×32 = 2¹⁵ блоков кода. А если гостевая архитектура имеет операнды-литералы (а все важные имеют) шириной так в 32 бита, то придётся хранить 2³² вариантов капсулы. Надо что-то придумать.
На самом деле нет нужды хранить кучу почти одинаковых шаблонов — все они содержат одни и те же хозяйские инструкции. При вариации гостевых операндов в них лишь изменяются некоторые хозяйские операнды (но иногда и длина инструкции, см. мой предыдущий пост), описывающие, где хранится моделируемое состояние или какой передаётся литерал. При формировании капсулы из шаблона надо «просто» пропатчить биты или байты по соответствующим смещениям:

Вопрос знатокам: какие архитектуры в примере выше используются в качестве гостевой и хозяйской?

Читайте также:  Не запускается виндовс после установки оперативной памяти

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

Прямое исполнение и виртуализация

Третий рассматриваемый мной механизм симуляции — прямое исполнение. Принцип его работы напрямую следует из названия — симулировать гостевой код, без изменений запуская его на хозяине. Очевидно, что этот способ потенциально даёт самую высокую скорость симуляции; однако он и самый «капризный». Необходимо выполнение следующих требований.

  1. Архитектура гостя и хозяина должна совпадать. Другими словами, не получится напрямую моделировать код для ARM на MIPS и наоборот; во всяком случае, это будет уже не прямое исполнение.
  2. Хозяйская архитектура должна удовлетворять условиям эффективной виртуализации.

Допустим, что гостевая архитектура удовлетворяет указанным условиям, например, это Intel IA-32/Intel 64 с расширениями Intel® VT-x. Следующая задача, возникающая при добавлении поддержки прямого исполнения в симулятор — это написание модуля ядра (драйвера) операционной системы. Без него не обойтись: симулятор необходимо будет исполнять привилегированные инструкции и манипулировать системными ресурсами, такими как таблицы страниц, физическая память, прерывания и прочее. Из пространства пользователя до них не дотянуться. С другой стороны, полностью «окапываться» в ядре вредно: программирование и отладка драйверов значительно затратнее по времени и по нервам, чем написание прикладных программ. Поэтому в ядро обычно выносят только самый минимум функциональности симулятора, к которому обращаются через интерфейсы системных вызовов. Все известные мне виртуальные машины и симуляторы, задействующие прямое исполнение, так и устроены: модуль ядра + пользовательское приложение, его использующее.
Так как модуль ядра пишется к определённой ОС, необходимо понимать, что при переносе приложения на другую ОС его придётся переписывать, возможно довольно сильно. Это ещё одна причина для того, чтобы минимизировать его размер.
В принципе, использование ассемблера в ядре оправдано примерно в таких же условиях, как и в юзерлэнде — то есть когда без него не обойтись. Виртуальные машины работают с системными структурами, такими как VMCS (virtual machine control structure), контрольные, отладочные и модель-специфичные регистры, которые доступны только через специализированные инструкции. Самым разумным было бы использовать для них интринсики, но…
Не все машинные инструкции имеют готовые интринсики. В компиляторах, предназначенных для сборки преимущественно пользовательского кода, про нужды писателей драйверов как-то забывают. Для обращения к ним приходится использовать встроенный (inline) ассемблер. В исходном коде виртуальной машины KVM, например, есть такое определение для функции чтения полей VMCS:

Честно говоря, я ожидал увидеть здесь вызов VMREAD по мнемонике vmread , но почему-то используется её «сырое» представление в виде байт. Может быть, таким образом авторы хотели поддержать сборку компиляторами, не знающими о такой инструкции.
Кстати, пример с интринсиком для LZCNT из примера выше может быть переписан с помощью формата inline-ассемблера в следующем виде. Машинный код в этом простом случае генерируется тот же.

Хотя изначально я планировал описать в этой статье в подробностях особенности формата GNU-inline ассемблера, я решил этого не делать, т.к. в Интернете достаточно много информации по этой теме. Если всё же возникнет потребность, я могу сделать это в своей следующей статье.
Случается, что выгоднее собрать весь ассемблер в один файл, чем пытаться уместить его среди Си-кода. Примеров для KVM я не нашёл, но зато они были для Xen. Замечу, что в этом файле собственно ассемблера не более четверти по объёму, остальное — препроцессорные директивы и комментарии, документирующие, что этот код делает и каков его интерфейс.

Итоги

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

  1. Интринсики — обёртки для отдельных машинных инструкций с интерфейсом обыкновенных функций C/C++.
  2. Ассемблерные вставки — специфичные для выбранного компилятора/ассемблера фрагменты ассемблерного кода, согласованные с окружающим их кодом высокого уровня.
  3. Файлы, целиком написанные на ассемблере — используемые в тех (редких) случаях, когда удобнее выразить некую последовательность действий целиком на ассемблере. С внешним миром они взаимодействуют либо через интерфейс функций (самостоятельно реализуя ABI той платформы, для которой они предназначены), либо никак не взаимодействуя (в случае независимых юнит-тестов).
Ссылка на основную публикацию
В каких случаях треугольник не существует
Задача Треугольник существует только тогда, когда сумма любых двух его сторон больше третьей. Дано: a , b , c –...
Бесплатные сайты для знакомства и серьезных отношений
Только популярные сайты знакомств в России и на всей территории постсоветского пространства. Badoo Badoo по праву занимает первое место в...
Бесплатные стикеры про любовь
А помните День Валентина, когда яркие сердечки вы дарили друг другу на праздник? Или в этот день вы завидовали всем...
В какое время говорить добрый день
День со скольки до скольки Автор Пользователь удален задал вопрос в разделе Образование Со скольки и до скольки часов говорить...
Adblock detector