Primula C Compiler     Xameleon Project        |        In English

Система команд «Эверест» версия 1 редакция 2

Comment are off

Второе расширение системы команд. Причиной расширения явилась потребность оптимизировать микрокод процессора. В результате удовлетворения этой потребности машинные инструкции из диапазона от 0xd0 до 0xd7 расширили семантику с помощью бита F (седьмой бит в третьем байте инструкции). На рисунке показаны возможные формы инструкций этого диапазона.

0xd0
Потребность расширить семантику возникла в результате анализа микро-кода, с помощью которого реализованы несколько инструкций системы команд. Рассмотрим проблему на примере фрагмента кода. Для начала именуем регистры чтобы с ними было проще работать — макроассемблер реализует данную возможность с помощью встроенной директивы assign:

 assign        r0     TCB     ; Указатель на область данных задачи
 assign        r2     TaskPtr ; Указатель на задачу
 $task_child   equ    2       ; Смещение поля с указателем на голову списка подзадач

С помощью директивы equ определена константа, указывающая смещение поля task_child, служащего для указания на голову списка дочерних задач. Далее идёт классическая работа со списками — читаем поле-указатель на голову списка и проверяем — не пустая ли она:

               mov      TaskPtr, TCB.task_child
               or       TaskPtr, TaskPtr ; Существуют ли подзадачи?
               je       brother_task 

Который трансформируется в следующий двоичный код:

0000: d0 20 00 02 ; MOV R2, R0[2]
0004: 23 22       ; OR R2, R2
0006: 83 00 11    ; JNZ 0x0011
 ; 9 bytes

В этом примере TCB это синоним регистра R0, который указывает на область данных задачи — Task Control Block (TCB).
Вышеприведённый код загружает в 32-х битный регистр R2 данные, на которые ссылается смещение, указанное в теле инструкции, с прибавленным к нему значением регистра R0. Данная форма инструкции типична и оптимальна при работе с полями типа данных struct языка Си и типа данных record языка Паскаль. За загрузкой указателя следует проверка на нулевое значение — операция логическое «ИЛИ» регистра с самим собой установит флаг Z только в том случае, если регистр содержит нулевое значение, т.е. нулевой указатель. Если существуют подзадачи, то произойдёт дальнейшее исполнение кода, если список подзадач пуст, то произойдёт ветвление для работы с задачами одинакового приоритета планирования.

Вас ничего не смущает в вышеприведённом коде? При работе со структурированными данными, а чаще всего со списками и деревьями, существует высокая повторяемость процитированной последовательности — загрузка, проверка на ноль, условный переход — помимо роста размера код терял читабельность. Суть оптимизации сводилась к избавлению от лишней проверки на ноль — исключение инструкции OR. Сделать это можно двумя способами — реализовать эту инструкцию в виде расширения соседних инструкций — MOV или JE|JZ. В результате проведённого анализа и консультаций было принято решение расширить синтаксис с помощью новой инструкции MOVT (образована от MOVe and Test — подсказал https://www.facebook.com/snark13). Таким образом вышеприведённый код вырождается в следующий код системы команд «Эверест» редакции 2:

                movt   TaskPtr, TCB.task_child ; Чтение указателя на дочернюю задачу
                jz     brother_task            ; Если нет детей - ищем братьев

На основе которого сгенерирован следующий код:

; 0000:  d0 20 00 02     ; MOVT 	R2, R0[2] 
; 0004:  82 00 14        ; JZ 	0x0017
; 7 bytes

Вместо девяти получили семь байт — возможность проверки на ноль во время загрузки значения сэкономила нам 2 байта и одну инструкцию.

К сожалению это расширение ломает совместимость с предыдущим редакциями — на уровне машинного кода изменилась семантика инструкции MOV c кодом операции 0xd0 — в предыдущей редакции инструкция не меняла значения флагов процесса, в текцущей редакции инструкция 0xd0 устанавливает флаг нуля если пересылалось нулевое значение и сбрасывает в противном случае. Таким образом предыдущая форма инструкции вырождается в инструкцию MOVT, а новая форма, реализующая инструкцию MOV 0xd0, образуется путём установки бита F — если он установлен в единицу, то инструкция выполняется, но не изменяет значение флагов. Тонкости реализации скрывает макроассемблер. Ниже пример двух форм инструкции MOV 0xd0 (отличие в третьем байте инструкции — именно бит 7 в третьем байте поломал бинарную совместимость):

0006:   d0 20 40 02     ; MOV 	R2, R0[2] 
000a:   d0 20 00 02     ; MOVT 	R2, R0[2] 

Можно было бы не ломать бинарную совместимость? Разумеется, можно за счёт незначительного усложнения декодера. Однако, текущее состояние проекта позволяет нам безболезненно перекраивать систему команд не озираясь на совместимость. При том, что сохранилась совместимость на уровне макроассемблера — с новой прошивкой придётся обновить и макроассемблер. При этом охраняется совместимость на уровне исходного кода — достаточно «ассемблировать» программы заново и они не потеряют работоспособность.

Помимо упрощения работы со связанными структурами данных, расширение системы команд ревизии 2 открывает новые возможности оптимизации команд — появилась возможность сохранения флагов при выполнении арифметических и логических операций. Бывают ситуации, когда при выполнении операций нам не хочется менять состояние флагов, а хочется позже использовать ранее установленные флаги. Все операции региона с 0xd0 по 0xd7 могут быть выполнены без воздействия на флаги — для этого в поле операции должен быть установлен бит F.


Оставить комментарий