Дана последовательность мнемокодов, которую необходимо преобразовать в машинные коды, занести в ОЗУ ЭВМ, выполнить в режиме Шаг и зафиксировать изменение состояний программно-доступных объектов ЭВМ (табл. 9.1).
Таблица 9.1. Команды и коды
Последовательность | Значения | ||||
Команды | RD#20 | WR30 | ADD #5 | WR03O | JNZ 002 |
Коды | 21 1 020 | 22 0 030 | 23 1005 |
Введем полученные коды последовательно в ячейки ОЗУ, начиная с адреса 000. Выполняя команды в режиме Шаг, будем фиксировать изменения программно-доступных объектов (в данном случае это Асе, PC и ячейки ОЗУ 020 и 030) в табл. 9.2.
Таблица 9.2. Содержимое регистров
PC | Acc | М(30)■.. | М(20) | PC | Acc | М(30) | N1(20) |
Задание 1
1. Ознакомиться с архитектурой ЭВМ (см. часть I).
Адреса памяти в машинном код и в ассемблере
2. Записать в ОЗУ «программу», состоящую из пяти команд — варианты задания выбрать из табл. 9.3. Команды разместить в последовательных ячейках памяти.
3. При необходимости установить начальное значение в устройство ввода IR.
4. Определить те программно-доступные объекты ЭВМ, которые будут изменяться при выполнении этих команд.
5. Выполнить в режиме Шаг введенную последовательность команд, фиксируя изменения значений объектов, определенных в п. 4, в таблице (см. форму табл. 9.2).
6. Если в программе образуется цикл, необходимо просмотреть не более двух повторений каждой команды, входящей в тело цикла.
Таблица 9.3. Варианты задания 1
№ | IR | Команда 1 | Команда 2 | Команда 3 | Команда 4 | Команда 5 |
IN | MUL #2 | WR10 | WR 010 | JNS 001 | ||
X | RD #17 | SUB #9 | WR16 | WR 016 | JNS 001 | |
IN | ADD #16 | WR8 | WR08 | JS 001 | ||
X | RD #2 | MUL #6 | WR 11 | WR 811 | JNZ 00 | |
IN | WR8 | DIV #4 | WR 08 | JMP 002 | ||
X | RD #4 | WR 11 | RD 811 | ADD #330 | JS 000 |
Таблица 9.3 (окончание)
Содержание отчета
1. Формулировка варианта задания.
2. Машинные коды команд, соответствующих варианту задания.
3. Результаты выполнения последовательности команд в форме табл. 9.2.
Контрольные вопросы
1. Из каких основных частей состоит ЭВМ и какие из них представлены в модели?
2. Что такое система команд ЭВМ?
3. Какие классы команд представлены в модели?
4. Какие действия выполняют команды передачи управления?
5. Какие способы адресации использованы в модели ЭВМ? В чем отличие между ними?
Как работает программа? Как компилируется код? (устройство компьютерных программ) [2020]
6. Какие ограничения накладываются на способ представления данных в модели ЭВМ?
7. Какие режимы работы предусмотрены в модели и в чем отличие между ними?
8. Как записать программу в машинных кодах в память модели ЭВМ?
9. Как просмотреть содержимое регистров процессора и изменить содержимое некоторых регистров?
10. Как просмотреть и, при необходимости, отредактировать содержимое ячейки памяти?
11. Как запустить выполнение программы в режиме приостановки работы после выполнения каждой команды?
12. Какие способы адресации операндов применяются в командах ЭВМ?
13. Какие команды относятся к классу передачи управления?
Источник: studopedia.info
Программы в машинный код в памяти
Итак, в сегодняшней статье мы доработаем нашу программу, добавив в нее задержку в 1 секунду. Кроме этого мы познакомимся с основными отладочными инструментами, которые поставляются вместе с MPLAB. Это поможет нам до конца разобраться во внутренней работе МК.
Создаем новый проект и добавляем в него следующий код:
Эта программа не сильно отличается от той, которую мы писали в прошлый раз, однако не стоит расстраиваться. Разбираться во внутренней работе МК лучше на примере простых программ. Итак, приступим. Первое, что мы изменили в нашей программе, это добавили строчки:
#include __CONFIG _HS_OSC _PWRTE_ON _LVP_OFF _CP_OFF
Первая строкой мы подключаем к нашему проекту файл p16f877.inc. Он находится в папке MicrochipMPASM Suite. Чтобы посмотреть содержимое этого файла нажимаем Project -> Add Files to Project… Должно появиться такое окошко:
Указываем тип файлов Header Files и выбираем файл P16F877.INC. Видим, что файл появился в менеджере проектов:
Открываем его и видим, что в нем происходит инициализация регистров специального назначения, а так же часто употребляемых констант и отдельных битов управляющих регистров. Таким образом, нам теперь не нужно каждый раз перед написанием программы вручную инициализировать регистры. Взгляните в самый конец этого файла. Там инициализированы биты конфигурации:
_CP_ALL EQU H’0FCF’ _CP_HALF EQU H’1FDF’ _CP_UPPER_256 EQU H’2FEF’ _CP_OFF EQU H’3FFF’ _DEBUG_ON EQU H’37FF’ _DEBUG_OFF EQU H’3FFF’ _WRT_ENABLE_ON EQU H’3FFF’ _WRT_ENABLE_OFF EQU H’3DFF’ _CPD_ON EQU H’3EFF’ _CPD_OFF EQU H’3FFF’ _LVP_ON EQU H’3FFF’ _LVP_OFF EQU H’3F7F’ _BODEN_ON EQU H’3FFF’ _BODEN_OFF EQU H’3FBF’ _PWRTE_OFF EQU H’3FFF’ _PWRTE_ON EQU H’3FF7′ _WDT_ON EQU H’3FFF’ _WDT_OFF EQU H’3FFB’ _LP_OSC EQU H’3FFC’ _XT_OSC EQU H’3FFD’ _HS_OSC EQU H’3FFE’ _RC_OSC EQU H’3FFF’
Они задают режим работы нашего МК. Подробнее о них уже рассказывал Дмитрий, так что я не буду останавливаться на этом, а перейду сразу к сути. На предыдущих уроках мы писали команду
__CONFIG H’3F72′
Было не совсем понятно, откуда вообще взялось число H’3F72′. В данной программе мы немного изменим эту строчку:
__CONFIG _HS_OSC _PWRTE_ON _LVP_OFF _CP_OFF
Однако если заменить эти имена константами и произвести над ними логическую операцию “AND”, то у нас как раз и получится число H’3F72′. Единственная разница лишь в том, что новая запись стала нагляднее. Поехали дальше:
DelH equ 0x20 DelM equ 0x21 DelL equ 0x22
В прошлый раз для формирования задержки мы использовали одну переменную. В этот раз мы будем использовать целых три – DelH, DelM, DelL с адресами 0x20, 0x21, 0x22 соответственно. Это позволит увеличить длительность задержки между миганиями светодиодов до 1 секунды. Идем дальше:
ORG 0x00 goto Start ORG 0x05 Start:
Для понимания этих строчек придется сделать маленькое отступление. В самой первой статье я уже Вам рассказывал про организацию памяти. Как Вы помните, все инструкции программы у нас хранятся в памяти программ МК, и каждая инструкция занимает 14 бит. Предлагаю на примере разобраться, что же конкретно записано в этих битах.
Допустим, вы написали команду clrf PORTB, которая, как вы уже знаете, очищает содержимое регистра PORTB. Так вот, компилятор переводит эту команду в машинное слово вида:
Бит13 Бит7
Бит6 Бит0
0 0 0 0 0 1 1
f f f f f f f
Первые семь бит этой команды хранят адрес регистра, который нужно отчистить.
Максимальный адрес, который можно в него записать это 0x7F. Теперь, я думаю, стало понятно, почему у нас все четыре банка памяти не превышают этого размера. Вслед за адресом регистра у нас идет код команды, который также занимает семь бит. Для операции clrf, например, код команды имеет вид B’0000011’. На этом примере я описал структуру так называемых байт ориентированных команд.
Такие команды производят действия над целым байтом данных. Однако у нашего контроллера есть несколько бит ориентированные команд (например, bsf или bcf), а также команды управления и операции с константами, причем их представление в машинном виде будет немного отличается от разобранного в примере выше.
Итак, после того, как компилятор перевел ассемблерные инструкции в машинный код их нужно записать в память программ. Однако перед этим нужно указать, с какой именно ячейки памяти мы хотим начать запись нашей программы.
Как раз для этого мы в начале и пишем ORG 0x00, тем самым указывая компилятору, что запись всех последующих команд нужно начинать с нулевого адреса, куда и будет записана команда goto Start. Дальше снова стоит команда ORG 0x05, а за ней команда clrf INTCON (метка Start: в памяти не записывается, помните?). Тем самым по адресу 0x05 записывается команда clrf INTCON. Чтобы наглядно увидеть, как располагаются команды в памяти, скомпилируйте проект и нажмите View->Disassembly Listing. Появится такое окошко:
В первом столбце располагаются адрес ячейки, в которой хранится команда. Дальше идет представление команды в шестнадцатеричной системе счисления. Ну а потом реальная команда, которая там хранится. Посмотрите внимательнее. Мы писали команду goto Start, а компилятор изменил ее на goto 0x5.
То есть компилятор автоматически заменил метку Start на физический адрес 0x5, в которой записана команда clrf INTCON. Тут нет ничего сложного. Единственное о чем осталось упомянуть – это счетчик команд PC (program counter). Счетчик команд – это 13-разрядный регистр, который хранит адрес выполняемой инструкции в памяти программ.
Младший регистр (8-битный) счетчика команд находится по адресу 0x2 (PCL). Он доступен как для записи, так и для чтения. А вот старший регистр (5-битный) для записи и чтения недоступен. Чтобы его изменить, нужно воспользоваться дополнительным регистром PCLATH, который находится по адресу 0x0A.
При каждом старте микроконтроллера регистр PC очищается, поэтому выполнение инструкций всегда начинается с нулевого адреса. Микроконтроллер загружает на выполнение инструкцию, адрес которой находится в регистре PC, а после ее выполнения содержимое регистра PC инкрементируется и загружается следующая инструкция. Другими словами регистр PC просто указывает МК по какому адресу нужно загрузить инструкцию на выполнение. Все просто. Поехали дальше:
clrf INTCON ;запрещаем все прерывания bsf STATUS, RP0 ;переходим в банк 1 movlw B’00000000′ ;помещаем в аккумулятор число 0 movwf TRISB ;устанавливаем линии порта PortB на выход bcf STATUS, RP0 ;переходим в банк 0 clrf PORTB ;очищаем регистр порта PORTB nop ;ничего не делаем nop nop
Здесь Вам уже все знакомо. Единственное добавилось три команды nop. Пока опустим объяснения, для чего они нужны. Идем дальше:
Loop: movlw .26 ;заносим в аккумулятор число .26 movwf DelH ;копируем содержимое аккумулятора в регистр DelH movlw .94 ;заносим в аккумулятор число .94 movwf DelM ;копируем содержимое аккумулятора в регистр DelM movlw .109 ;заносим в аккумулятор число .109 movwf DelL ;копируем содержимое аккумулятора в регистр DelL nop
Эти строки тоже Вам знакомы. Мы просто записываем начальные значения в регистры таймера обратного отсчета. Эти значения были подобраны опытным путем, чтобы обеспечить задержку между миганием светодиодов, ровно в 1 секунду. Едем дальше:
Delay_L: decfsz DelL, F ;уменьшаем содержимое регистра DelL и сохраняем ;результат в регистре DelL, если содержимое регистра ;равно 0, то пропускаем сл. команду. goto Delay_L ;переходим на метку Delay_L Delay_M: decfsz DelM, F ;уменьшаем содержимое регистра DelM и сохраняем ;результат в регистре DelM, если содержимое регистра ;равно 0, то пропускаем сл. команду. goto Delay_L ;переходим на метку Delay_L Delay_H: decfsz DelH, F ;уменьшаем содержимое регистра DelH и сохраняем ;результат в регистре DelH, если содержимое регистра ;равно 0, то пропускаем сл. команду. goto Delay_L ;переходим на метку Delay_L incf PORTB, F ;увеличиваем содержимое регистра PORTB и сохраняем ;результат в регистре PORTB goto Loop ;переходим на метку Loop
Этот цикл очень сильно напоминает тот, который мы использовали в прошлый раз.
Единственная разница в том, что наш однобайтный таймер обратного отсчета превратился в трехбайтный. Принцип его работы почти такой же. Пока содержимое регистра DelL не станет равно нулю, мы будем крутиться в цикле Delay_L, при этом за каждый проход содержимое DelL будет декрементироваться. После этого мы перейдем в цикл Delay_M, в котором проверке подвергнется регистр DelM.
А так как он не равен нулю, то мы опять перескочим на начало цикла Delay_L, и все начнется сначала. После нескольких таких витков содержимое регистра DelM станет равно нулю, мы попадем в цикл Delay_H, где будем проверять на ноль значение регистра DelH. И уж когда оно станет равно нулю, мы наконец-то увеличим значение порта PORTB и вернемся к началу цикла Loop. Все.
Вообще, здесь можно провести аналогию с обычными часами, у которых предусмотрена функция таймера. Представьте, что регистр DelH будет отображать часы, регистр DelM – минуты, а регистр DelL – секунды. Единственная разница лишь в том, что наш таймер будет работать гораздо быстрее обычных часов.
С работой программы мы разобрались, теперь давайте познакомимся и отладочными средствами, предусмотренными в среде MPLAB. Для начала включим симулятор. Нажимаем Debugger -> Select Tools -> MPLAB SIM. Справа вверху появится панель вида:
Нажимаем F6 (Reset) или желтую кнопку на панели справа, тем самым сбрасываем микроконтроллер. Посмотрите на код. Напротив команды goto Start должна появиться зеленая стрелка, которая показывает, какая инструкция готова на выполнение. Теперь нажимаем View -> File Registers. Должно появиться такое окошко:
Это как раз представление нашей карты памяти данных, которую я рисовал на первом уроке, только выглядит она немного по-другому. Все регистры, значение которых изменяется после выполнения команды, будут подсвечиваться красным светом. С помощью карты памяти очень просто отслеживать состояние регистров, в процессе работы микроконтроллера. Щелкаем кнопку F7.
Наш микроконтроллер выполнил команду goto Start, а зеленая стрелка перескочила на команду clrf INTCON. А теперь посмотрите в File Registers. Регистр по адресу 0x2 высветился красным светом, а его значение изменилось и стало равно 0x5. Если Вы помните, это как раз наш счетчик команд. То есть теперь мы будем выполнять инструкции, начиная с адреса 0x5.
Поначалу очень непривычно пользоваться окном File Registers. Однако это не единственный инструмент, которым можно воспользоваться. Нажимаем View -> Watch. Появится вот такое окошко:
Здесь мы можем добавлять только интересующие нас регистры общего или специального назначения, и следить только за их работой. Кстати, здесь можно понаблюдать за содержимым аккумулятора. Для этого нужно добавить SFR регистр WREG. Советую какое-то время понаблюдать за состоянием регистров в процессе работы МК, чтобы немного привыкнуть.
Ну и напоследок давайте познакомимся еще с одним важным инструментом. Нажимаем Debugger -> StopWatch. Появится вот такое окошко:
С помощью этого инструмента мы будем измерять задержку. Но для начала давайте установим BreakPoint’ы. Для этого два раза щелкаем по строчке clrf PORTB и incf PORTB. Слева должна появиться белая буква B в красном кружке. Именно время задержки между ними нам нужно измерить.
Теперь нажимаем кнопку Play на панели симулятора, или щелкаем F9. В этом режиме будет происходить симуляция работы МК на компьютере, а остановится этот процесс тогда, когда мы доберемся до нашего первого BreakPoint’а. Посмотрите на окошко StopWatch. Его значения изменились:
Теперь нажимаем Zero. Этим мы сбрасываем наш секундомер в ноль. И снова жмем F9. Микроконтроллер начал работать дальше, и остановится он только когда доберется до второго BreakPoint’а. В этот момент время задержки будут составлять ровно 1 секунду (взгляните на значение в колонке Time).
Теперь пришло время объяснить, зачем я везде вставлял команды nop. Дело в том, что если вы их уберете, то задержка между миганием светодиодов будет меньше одной секунды. Конечно, это не так критично, особенно в работе нашей программы, однако это очень простой способ точной корректировки задержки, который очень часто применяется.
Источник: kernelchip.ru
Машинный код для интернета. Как Javascript превращается в подобие ассемблера
Закон Атвуда гласит, что любое приложение, которое можно написать на Javascript, однажды напишут на Javascript. Компилятор Emscripten делает это практически неизбежным.
Интро
Чтобы запустить в браузере Linux или игру для старинной видеоприставки, можно использовать виртуальную машину, написанную на Javascript. Но это не единственный и даже не лучший способ.
Первая проблема связана с тем, что эмуляция железа заведомо менее эффективна, чем исполнение нативного кода. Это знают и сами разработчики эмуляторов. Когда скорости пошагового моделирования работы чужого процессора не хватает, им приходится добавлять динамическую рекомпиляцию — автоматический перевод участков эмулируемого кода в Javascript. Это трудно, но после переработки умным JIT-компилятором код становится быстрее.
Кроме того, до эмуляции железа нужно ещё добраться. Это совсем не простая задача, и тот факт, что она зачастую уже решена (просто не на нужном Javascript, а на другом языке программирования), вовсе не прибавляет энтузиазма. Переписывать десятки, а то и сотни тысяч строк кода с Си на Javascript — удовольствие на любителя. Люди, которым интересен этот процесс, безусловно, встречаются, но куда реже тех, кто предпочитает результат.
Frontend и Backend
Один из создателей Javascript однажды заметил, что этот язык превратился в своего рода машинный код для интернета. Существуют компиляторы, которые переводят в Javascript программы, написанные на Python, Java и Smalltalk. Некоторые языки с самого начала рассчитаны на переработку в Javascript — к этой категории относятся Coffeescript и используемый React.js формат JSX.
У подобного подхода долгая история, которая началась задолго до появления Javascript и даже браузеров. В прошлом компиляторы многих языков программирования не могли генерировать машинный код. Результатом их работы были промежуточные исходники на Си. Это позволяло без особых усилий переносить языки на любую платформу, где есть стандартный компилятор Си.
В современных компиляторах сохранилось разделение на фронтенд, поддерживающий определённый язык программирования, и бэкенд, способный генерировать код для нужной платформы. Но для связи между ними Си, как правило, больше не нужен. Чтобы добавить поддержку языка, нужно разработать новый фронтенд. А заменой бэкенда можно добиться генерации кода для другой платформы.
Так устроен, в частности, популярный компилятор LLVM, фронтенды которого понимают большинство распространённых языков программирования. Результат работы фронтенда — байт-код для виртуальной машины, напоминающий ассемблер несуществующего RISC-процессора с бесконечным числом регистров и сильной типизацией данных. Бэкенды LLVM поддерживают, среди прочего, системы команд процессоров x86, ARM, MIPS, PowerPC и даже мейнфреймов IBM.
Бэкенд LLVM, способный генерировать Javascript вместо машинного кода x86 или ARM — настолько очевидная идея, что его появление было вопросом времени. Самый популярный проект такого рода был начат одним из инженеров Mozilla около пяти лет назад. Он называется Emscripten.
Emscripten
Emscripten представляет собой компилятор Си, Си++ и других языков, поддерживаемых LLVM, в Javascript, пригодный для исполнения в браузере. Этот проект также включает в себя реализацию распространённых библиотек для работы с трехмерной графикой, звуком и вводом-выводом на базе браузерных программных интерфейсов.
На что похож Javascript, который получается в результате работы Emscripten? Во многих случаях аналогии между Си и Javascript предельно прозрачны. Переменные есть переменные, функции есть функции, ветвление есть ветвление. Циклы или выбор switch. case в Javascript приходится записывать иначе, но суть та же. Кое-чем приходится жертвовать: например, на все разновидности числовых типов Си у Javascript один ответ — float . Но, в конечном счёте, это почти ни на что не влияет.
Тяжелее приходится с концепциями, которых в Javascript просто нет. Чтобы имитировать указатели и работу с памятью, тут идёт в ход та же уловка, что и в джаваскриптовых эмуляторах игровых приставок: программа создаёт типизированный массив, который играет роль динамически распределяемой памяти, и заменяет указатели на индексы элементов в этом массиве.
Код на Javascript непрерывно следит за тем, чтобы не покинуть отведённые массиву рамки, не выходить за пределы массивов, не переполнить стек. Это тратит уйму ресурсов, но даже с учётом всех накладных расходов программы, скомпилированные Emscripten, работали всего в несколько раз медленнее, чем те же исходники, скомпилированные в машинные коды. А в 2013 году у авторов проекта появилась возможность избавиться и от этих помех.
Asm.js
Недостающий компонент, который позволяет добиться максимальной скорости, называется asm.js. Спецификация asm.js задаёт упрощённое подмножество Javascript, которое может быть передано для исполнения более примитивному и потому очень быстрому интерпретатору. Оно идеально подходит в качестве промежуточного языка для таких генераторов кода, как Emscripten. Поддержка asm.js уже есть в браузерах Google, Mozilla и даже Microsoft.
Рассмотрим пример кода, написанного для asm.js:
function GeometricMean(stdlib, foreign, buffer) < «use asm»; var values = new stdlib.Float64Array(buffer); function logSum(start, end) < start = start|0; end = end|0; var sum = 0.0, p = 0, q = 0; for (p = start function geometricMean(start, end) < start = start|0; end = end|0; return +stdlib.Math.exp(+logSum(start, end) +((end — start)|0)); >return < geometricMean: geometricMean >; >
Вот первое, что бросается в глаза: это обычный Javascript. Ему определённо не нужен ни специальный интерпретатор, ни предварительная обработка. Судя по всему, он заработает и без них. Не так быстро, как в интерпретаторе, знающем о существовании asm.js, но заработает.
Строка use asm , открывающая функцию, уведомляет интерпретатор, что её содержимое может считаться модулем asm.js. Каждая такая функция должна удовлетворять множеству требований.
Первое требование: функция должна принимать три аргумента. Первый аргумент (в примере он называется stdlib , но названия могут быть и другими) — это объект, содержащий функции стандартной библиотеки Javascript (она состоит преимущественно из математики и типизированных массивов). Второй ( foreign ) содержит все остальные внешние функции, к которым допустимо обращаться из модуля. Третий ( buffer ) — это объект ArrayBuffer . В asm.js он заменяет динамически выделяемую память. Для доступа к ней используются типизированные отображения TypedArray , такие как Int32Array или Float64Array . Мы видим, как это происходит в следующей за use asm строчке: программа создаёт отображение buffer, которое состоит из элементов величиной восемь байтов, интерпретируемых как числа с плавающей точкой:
var values = new stdlib.Float64Array(buffer);
Следующая странность — операции битового ИЛИ едва ли не в каждой строчке. Объяснение простое: это костыли, помогающие обойтись без встроенных средств декларации типа. Чтобы гарантировать верные типы переменных, asm.js предписывает конвертировать их принудительно. Если а — аргумент функции, который должен быть числом, ему придётся пройти операцию a|0 , после которой он гарантированно станет числом.
Это не слишком удобно для человека, но нужно понимать, что asm.js — не для людей. Генератору кода всё равно, что генерировать, а интерпретатор, поддерживающий asm.js, понимает, что такая демонстративная конвертация — это вовсе не конвертация, а неуклюжая попытка задать тип переменной, и действует соответственно.
Что это даёт? Во-первых, интерпретатор и JIT-компилятор могут быть уверены, что внутри модуля тип переменных никогда не меняется. Больше не нужны постоянные проверки типа во время исполнения и принудительное приведение типов к общему знаменателю. Во-вторых, нет динамического выделения памяти и, соответственно, сборки мусора.
Наконец, сама реализация объектов и организация памяти может быть более эффективной. Главное — поменьше рефлексии (и прочей динамики).
Всё это значительно повышает эффективность JIT-компиляции. Производительность кода, который выдают последние версии Emscripten, достигает 50-70 процентов от нативной скорости исполнения той же программы. Разработчики утверждают, что на устройствах, работающих под управлением Android, Javascript, который генерирует Emscripten, работает быстрее, чем Java.
Ограничения среды
Самые простые программы Emscripten просто перекомпилирует, не задавая лишних вопросов. Однако во многих случаях к переносу программы в браузер следует подходить так же, как к портированию на другую платформу (и ожидать похожих проблем). Любой код, который использует особенности определённой платформы или среды разработки, может стать серьёзным препятствием.
От него придётся избавиться. Зависимость от библиотек, исходники которых недоступны, должна быть устранена. Не исключено, что в процессе портирования проявят себя ошибки, которых прежде удавалось избежать — другая среда исполнения и другой оптимизатор часто имеют такой эффект.
Кроме того, остаётся ещё одна важная деталь: программы, которые рассчитаны на работу в течение продолжительного времени, как правило, крутятся в цикле — они ждут поступления событий из внешнего мира и реагируют на них. Так продолжается, пока их не выключат. Браузер и, соответственно, программы на Javascript устроены совершенно иначе.
В браузере этот цикл реализован на другом, более высоком уровне, чем пользовательская программа. Он позволяет назначить функции на Javascript, которые должны быть вызваны в случае определённого события, но вызывает их сам и ожидает, что они быстро отработают своё и вернут управление. Они не должны работать долго.
Любая программа на Javascript, которая не только реагирует на события DOM, но и делает нечто большее, быстро упирается в это ограничение. Лазейки, которые позволяют его обойти, тоже давно известны. Джаваскриптовые эмуляторы игровых консолей, о которых рассказывалось в статье «Байт из других миров. Как ретрокомпьютеры эмулируют на Javascript», привязывают исполнение каждого такта виртуального процессора к таймеру. В этом случае каждый виток внутреннего цикла эмулятора активирует сам браузер.
Нечто похожее делает и Emscripten, но он не может делать это совершенно самостоятельно. Перед компиляцией внутренний цикл придётся размотать вручную. В простейшем случае это не так уж сложно: достаточно вынести содержимое цикла в отдельную функцию (зачастую это уже сделано), а затем вызвать emscripten_set_main_loop , передав ему ссылку на неё и количество кадров, к которому мы стремимся, а также сообщив, должен ли цикл быть бесконечным (в последнем случае выйти из него можно будет лишь при помощи исключения):
int main() < #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(mainloop, 60, 1); #else while (1) < mainloop(); >#endif >
Если в программе несколько подобных циклов, и она может переходить из одного в другой по собственному желанию, потребуется более значительная переработка кода. Дело в том, что задать функцию внутреннего цикла при помощи emscripten_set_main_loop можно лишь один раз. Придется собрать все внутренние циклы в одну функцию и всякий раз проверять, в каком именно цикле мы находимся.
Иногда такой вариант неприемлем или нереалистичен. На этот случай в Emscripten предусмотрена опция emterpreter sync . Когда она включена, Emscripten выдаёт байт-код и джаваскриптовый интерпретатор, который будет его исполнять. Фактически происходит возвращение к пошаговой эмуляции, от которой мы пытались уйти. Жертвуя производительностью, он отодвигает проблему множественных внутренних циклов на второй план. Ими будет заниматься интерпретатор байт-кода, который, в свою очередь, имеет один внутренний цикл, с которым уже взаимодействует браузер.
Жертва значительна, но не смертельна. Emterpreter sync используется, в частности, в Em-DOSBox, браузерной версии известного эмулятора MS-DOS. Потерянная производительность не мешает этой программе успешно воспроизводить множество компьютерных игр для PC, написанных в девяностые годы, и даже Windows 95.
Profit
Сфера применения EmScripten не ограничивается эмуляторами и старинными игрушками (помимо DOSBox, на Emscripten перенесли Doom, Quake 3, Dune 2 и ScummVM, виртуальную машину для классических квестов LucasArts и Sierra). Попытки запустить в браузере такие популярные скриптовые языки, как Python, Ruby и Lua, тоже увенчались успехом, хотя их практичость остается сомнительной. Каждый из них требует загрузки многомегабайтного рантайма; для обычного сайта это неприемлемо.
Видеокодеки, просмотрщики PDF, SQLite и система распознавания речи — это уже интереснее. Наконец, нельзя не упомянуть, что существует проект переноса в браузер браузерного движка Webkit. Это не утка: он работает, в том числе внутри Webkit. Трудно подобрать эпитет, в полной мере описывающий характер этого достижения, но с тем, что это достижение, не станет спорить никто.
Если даже после этого (особенно после этого) вы ещё не оценили всю серьёзность происходящего, остаётся добавить, что Emscripten поддержали компании Epic Games и Unity Technologies. Первая портировала на Javascript популярнейший игровой движок Unreal Engine. Он настолько популярен, что проще перечислить игры, где его нет. Другая с помощью Emscripten разработала джаваскриптовую версию Unity 3D. С такой поддержкой эта технология определённо пойдёт далеко.
Сделано в Emscripten
Angry Bots
Демонстрация того, как может выглядеть игра, написанная при помощи портированного на Javascript движка Unity 3D. Спойлер: она может выглядеть, как мобильная игра средней руки. Трёхмерный вооружённый гражданин бежит по железной местности и, разумеется, стреляет. Он не может не стрелять (серьёзно, я не знаю, как его остановить). Местные жители недовольны, и их можно понять.
Старые игры
Не так давно Internet Archive выложил на всеобщее обозрение огромное количество старинных игр для всех возможных платформ, начиная со Spacewar образца 1962 года (считается, что это первая компьютерная игра) и заканчивая, извините, Flappy Bird. Между ними есть всё, что можно придумать. За воспроизведение отвечает джаваскриптовый порт эмулятора MESS/MAME, который поддерживает без малого тысячу исторических игровых платформ.
Интерпретаторы языков программирования
На этой странице выложены интерпретаторы Python, Ruby, Scheme, Lua, Java, QBasic, Forth и множества других языков программирования. Для ценителей есть даже Brainfuck. С каждым можно поиграться прямо в браузере, сохранить введённый код и поделиться им со знакомыми в Facebook и Twitter. Знакомые оценят — особенно, если это Brainfuck.
Dead Trigger 2
Ещё одна демка Unity 3D. В Dead Trigger 2, вместо псевдоизометрии Angry Bots, мы имеем вид от первого лица и большой окровавленный топор. Низкополигональная местность, напоминающая задворки оптового рынка на окраине Москвы, не радует, но Emscripten в этом вряд ли виноват. Виноваты зомби, которые сделали эту игру.
Tappy Chicken
Epic Games, демонстрируя джаваскриптовую версию Unreal Engine 3, пытается впечатлить не публику, а разработчиков. Публике тут смотреть не на что: это клон Flappy Bird с сельскохозяйственным уклоном. Программистов же может впечатлить тот факт, что эта курица без особых проблем конвертируется не только в Javascript, но и в приложения для iOS и Android. Не всех программистов, конечно. Только самых впечатлительных.
Doom
«Дум» даже в DOSBox, переведённом на Javascript, остаётся «Думом». Двадцать лет почти не изменили его (монстры выглядят странно, но, вероятно, не из-за Emscripten, а из-за копирайта), только пиксели стали крупнее. Думал ли Кармак, что его передовую игру будут портировать на каждую платформу, одну другой меньше? Вряд ли. Quake 3 на Javascript, кстати, тоже есть.
VIM
Да, это классический текстовый редактор. Он работает не хуже обычного. Нет, я не знаю, как сохранять файлы.
Источник: xakep.ru