Система программирования MMIXAL.NET
    Сообщений 0    Оценка 0        Оценить  
Система Orphus

Система программирования MMIXAL.NET

Часть 1

Автор: Никулин Петр Михайлович
Источник: RSDN Magazine #4-2010
Опубликовано: 06.02.2011
Исправлено: 10.12.2016
Версия текста: 1.0
Введение
Архитектура виртуального компьютера MMIX
Адресное пространство
Прерывания
Стек регистров
Специальные регистры
Заключение
Список литературы

Введение

Система программирования MMIXAL.NET предназначена для ассемблирования, отладки и интерпретации программ, написанных на языке ассемблер MMIXAL [2], который был разработан профессором Стенфордского университета Дональдом Кнутом. Все программы, содержащиеся в книге ИСКУССТВО ПРОГРАММИРОВАНИЯ [1], будут переписаны на ассемблере MMIXAL.

MMIXAL-программы интерпретируются виртуальным компьютером MMIX. Язык ассемблер MMIXAL предназначен для изучения компьютерных алгоритмов, а также может служить в качестве back-end трансляторов с языков высокого уровня (C++, C#, Pascal, Java, Nemerle, F#…). Проект MMIXAL.NET был разработан в рамках платформы Microsoft.NET и содержит порядка 30000 строк кода на языке C#. Цель проекта MMIXAL.NET – дать мощное и удобное средство разработки, интерпретации и отладки MMIXAL-программ в рамках платформы Microsoft.NET. Цель данной статьи – описание основных возможностей, которые предоставляет MMIXAL.NET.

Архитектура виртуального компьютера MMIX

MMIX является 64-битовым RISC-компьютером, содержащим 256 регистров общего назначения и 64-битовое адресное пространство.

Все инструкции машины MMIX имеют длину 4 байта и следующую форму: “CI X Y Z”. CI представляет собой код инструкции (code of instruction) и занимает один байт. Существует 256 различных кодов инструкций. Операнды X ,Y, Z занимают по одному байту. Обычно операнды X ,Y, Z независимы друг от друга. Несколько инструкций могут комбинировать операнды Y, Z в 16-битовое поле YZ. Несколько инструкций перехода используют 24-битовое поле XYZ.

Ассемблер MMIXAL представляет инструкции в символической форме, где каждый код инструкции имеет мнемоническое имя. Например, инструкция “24 05 06 07” имеет символическую форму SUB $5,$6,$7. Эта инструкция вычитает из значения регистра 6 значение регистра 7, присваивая результат регистру 5. Знак доллара означает номер регистра. Инструкция с двумя запятыми имеет три операнда X ,Y, Z. Инструкция с одной запятой имеет два операнда X и YZ. Инструкция без запятой имеет один операнд XYZ. Инструкция без операндов имеет X=Y=Z=0.

Большинство инструкций имеет две формы, в одной из которых в поле Z содержится номер регистра $Z, а в другой в поле Z содержится беззнаковая константа. Такие константы всегда неотрицательны. Например, имеются две инструкции SUB $X,$Y,$Z и SUB $X,$Y,Z. Код первой инструкции 24, а второй 25. Ассемблер MMIXAL выбирает правильный код, анализируя третий операнд на наличие в нем номера регистра.

Номерам регистров и константам могут быть даны символические имена. Например, инструкция “y IS $10” присваивает десятому регистру символическое имя y. Аналогично, инструкция “SIX IS 6” присваивает символическому имени SIX значение 6. Так, инструкция SUB y,y,SIX уменьшит значение десятого регистра на 6. Символические имена, соответствующие номерам регистров, по умолчанию начинаются с буквы, набранной на нижнем регистре, в то время как символические имена, соответствующие константам, начинаются с буквы, набранной на верхнем регистре. Но это соглашение не является обязательным.

Nybble байт представляет 4-битовое значение, byte байт (или просто байт) представляет 8-битовое значение, wyde байт представляет 16-битовое значение, tetra байт представляет 32-битовое значение и octa байт представляет 64-битовое значение. Tetra байт состоит из двух wyde байтов, octa байт состоит из двух tetra байтов. Можно рассматривать любой регистр, состоящий либо из одного octa байта, либо из двух tetra байтов, либо из четырех wyde байтов, либо из восьми байтов, либо из шестнадцати nibble байтов.

Беззнаковый байт представляет число из интервала [0, 28 -1 = 255], беззнаковый wyde байт представляет число из интервала [0, 216 - 1 = 65535], беззнаковый tetra байт представляет число из интервала [0, 232 - 1 = 4,294,967,295], беззнаковый octa байт представляет число из интервала [0, 264 - 1 = 18,446,744,073,709,551,615].

Знаковые числа представлены в виде дополнения до двух. Знаковый байт представляет число из интервала [-128, 127], знаковый wyde байт представляет число из интервала [-32768, 32767], знаковый tetra байт представляет число из интервала [-2,147,483,648, +2,147,483,647], знаковый octa байт представляет число из интервала [-9,223,372,036,854,775,808, +9,223,372,036,854,775,807].

Адресное пространство

Виртуальная память машины MMIX представляет массив M, состоящий из 264 байтов. Если k – любой беззнаковый octa байт, то M[k] представляет однобайтовое значение. M2t[k] означает значение, состоящее из 2t последовательных байтов, начиная с позиции k ^ (264  - 2t). Например, M4[10] означает tetra байт, расположенный по адресу 8. Аналогично, выражение k v (2 t - 1) означает, что наименее значимые t бит значения k установлены в единицу. 2t – байт значения выравниваются на позицию с адресом, кратным значению 2 t.

При адресации используется big-endian нотация, то есть наиболее значимый (самый левый) байт значения M2t[k] является M1[k ^ (264 - 2t)] и наименее значимый (самый правый) байт значения M2t[k] является M1[k v (2t - 1)].

Если 2t – байт значения рассматриваются как знаковые целые, то это обозначается как s(M2t[k]). Формально, если j=2t, то s(Mj[k]) = (M1[k ^ (j)]  M1[k ^ (-j) + 1] ... M1[k v (j - 1)])256 - 28l[M1[k ^ (-j)] >= 128].

Виртуальная память машины MMIX разбита на две части:

Знак # означает шестнадцатиричную систему счисления .

Пространство пользователя, в свою очередь, разбито на четыре части. Первая часть представляет текстовый сегмент. Здесь размещается MMIXAL-программа. Вторая часть представляет сегмент данных, который начинается с виртуального адреса #2000000000000000. Здесь размещаются пользовательские переменные. Третья часть представляет сегмент пула, который начинается с виртуального адреса #4000000000000000. Здесь размещаются аргументы командной строки и другие динамически распределяемые данные. Наконец, сегмент стека занимает четвертую часть, которая начинается с виртуального адреса #6000000000000000. Сегмент стека поддерживает стек регистров, который используется инструкциями PUSH, POP, SAVE, UNSAVE. Ничего не должно ассемблироваться в сегмент стека или в сегмент пула, хотя программа может ссылаться на данные в этих сегментах. Для удобства предопределены три символа:

Ссылки на адреса, располагающиеся в начале сегмента, могут быть более эффективны, чем ссылки на адреса, располагающиеся в конце сегмента.

Программист должен рассматривать текстовый сегмент как доступный только для чтения: адресное пространство с адресами, меньшими #2000000000000000, должно оставаться постоянным после ассемблирования и загрузки программы. Поэтому таблицы и выходные буфера должны располагаться в сегменте данных. Текстовый сегмент и сегмент данных содержат нули в начале выполнения программы, за исключением загруженных инструкций и данных. Если два или более байт загружаются по одному и тому же виртуальному адресу, то загрузчик поместит по этому адресу байт, составленный из исходных байт операцией "исключающее или".

Содержимое tetra байта по адресу #90 играет специальную роль. Любая MMIXAL-программа должна содержать строку, поле метки которой содержит метку Main (структура MMIXAL-программы будет подробно описана в пятой части статьи). При загрузке программы загрузчик анализирует значение tetra байта по адресу #90. Если его значение отлично от нуля, то выполнение программы начинается с адреса #90, иначе с метки Main. Это дает возможность выполнить специальные действия (например, инициализировать библиотеку подпрограмм) перед началом выполнения программы.

Прерывания

Нормальное выполнение инструкций может быть изменено не только командами переходов, но и внешними сигналами. Машина MMIX различает два вида программных прерываний trip и trap. Trip передает управление trip-обработчику, который является частью программы. Trap передает управление trap-обработчику, который является частью операционной системы.

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

Специальный регистр состояния арифметики rA содержит текущую информацию обо всех этих исключениях.

Восемь бит его самого правого байта называются битами событий. Каждый бит имеет свое имя: D_BIT(#80), V_BIT(#40), W_BIT(#20), I_BIT(#10), O_BIT(#08), U_BIT(#04), Z_BIT(#02), X_BIT(#01). Восемь левых бит от битов событий называются битами разрешения. Они появляются в том же порядке: DVWIOUZX.

Когда возникает исключительная ситуация при выполнении арифметической операции, машина MMIX анализирует соответствующий бит разрешения прежде чем выполнить следующую инструкцию. Если значение соответствующего бита разрешения равно нулю, то соответствующий бит события устанавливается в единицу; иначе машина MMIX инициирует trip-обработчик согласно следующим адресам:

Таким образом, биты событий запоминают исключительные ситуации, которые не передавали управление trip-обработчику. Если возникает одновременно более одной исключительной ситуации, то более левая имеет преимущество. Например, при одновременном возникновении двух исключительных ситуаций O, X управление будет передано только по адресу #50 (trip-обработчик плавающего переполнения).

Два левых бита от битов разрешения содержат текущий режим округления. Остальные 46 битов специального регистра состояния арифметики rA должны быть нулевыми. В любой момент состояние регистра rA может быть изменено инструкцией PUT.

Пример обработки прерываний.

...
EnableAllTreaps     GREG        0
ResultOfTreap       GREG        0
...
Main                SET         EnableAllTreaps,#ff00   DVWIOUZX   
                    PUT         rA,EnableAllTreaps      
...
**************** 
                    PREFIX      :IntDivideCheck:
                    LOC         @+7)&-8
Text                BYTE        "intgdivd" 
IntDivideCheck      LDO         :ResultOfTreap,Text 
                    POP         0,0
                    PREFIX      :
                    PREFIX      :IntOverflowCheck:
                    LOC         (@+7)&-8
Text                BYTE        "intovflw" 
IntOverflowCheck    LDO         :ResultOfTreap,Text 
                    POP         0,0
...
********** (D) trip-обработчик целочисленного деления на нуль
                    LOC         #10   
                    PUSHGO      $0,IntDivideCheck:IntDivideCheck
                    RESUME      0 
********** (V) trip-обработчик  целочисленного переполнения
                    LOC         #20         
                    PUSHGO      $0,IntOverflowCheck:IntOverflowCheck
                    RESUME      0 
...

Стек регистров

Стек регистров служит для эффективной реализации как передачи управления из программы в подпрограмму, так и возврата управления из подпрограммы.

Стек регистров состоит из octa байт S[0] , S[1], ..., S[t-1] для целого t >= 0. L верхних octa байт стека (S[t-L], S[t-L+1], ..., S[t-1]) являются текущими локальными регистрами ($0, $1,..., $(L-1)). Оставшиеся t - L octa байт стека недоступны программе. Говорят, что S[t-1] octa байт является верхушкой стека. Текущее число локальных регистров L хранится в специальном регистре rL. При старте программы L=2, t=2 и локальные регистры $0, $1 представляют командную строку MMIXAL-программы. Командная строка состоит из аргументов. Первым аргументом является имя обьектного файла, который будет интерпретироваться. Следующие аргументы содержат опции программы. Например, опция “–P” указывает на необходимость вывода профиля выполнения программы. Каждая опция должна начинаться со знака минус. Аргументы, которые следуют за опциями, могут содержать, например, имена файлов с входной информацией. Локальный регистр $0 содержит количество аргументов командной строки. Локальный регистр $1 содержит адрес первого аргумента командной строки.

Машина MMIX имеет глобальные регистры $G, $(G+1), ..., $255. Значение G содержится в специальном регистре rG. Всегда выполняются следующие соотношения: 0 <= L < G <= 255, G >= 32. Глобальные регистры не являются частью стека регистров. Регистры, которые не являются ни локальными, ни глобальными, называются маргинальными. Значения маргинальных регистров ($L, $(L+1), ..., $(G-1)) являются нулевыми, когда они используются в качестве операндов инструкций. Стек регистров растет, когда маргинальному регистру присваивается значение. В этом случае маргинальный регистр становится локальным, равно как и все маргинальные регистры с меньшими значениями. Например, если используется 8 локальных регистров, инструкция "SUB $10,$20,5" превращает регистры $8, $9 и $10 в локальные, то есть, если rL = 8, то инструкция " SUB $10,$20,5" устанавливает $8<-0, $9<-0, $10 <- -5 ($20(0 значение)-5), rL<-11. Регистр $20 остается маргинальным.

Стек регистров реализован в виде массива octa байтов и называется кольцом локальных регистров(LRR - Local Register Ring). Пусть LRR имеет p элементов: l[0], l[1], ..., l[p-1]. Тогда мы храним локальный регистр $k в l[(k + y) mod p], где y – соответствующее смещение. Значение p выбирается кратным степени двойки с целью простоты вычисления операции mod p. Значение p должно быть, по меньшей мере, равным 256 так, чтобы LRR мог содержать все локальные регистры. Инструкция PUSHJ перенумеровывает локальные регистры (например, $0 перенумеровывается в $4, увеличением значения y на 4) . Инструкция POP восстанавливает предыдущее состояние, уменьшением значения y. Перенумерация регистров позволяет избежать перемещения данных в LRR, что делает передачу управления в подпрограмму быстрее (нижеследующий пример поясняет данный механизм в деталях). Когда стек регистров становится достаточно большим, используется сегмент стека Stack_Segment в качестве устройства для сохранения.

Если $X – локальный регистр, то инструкция "PUSHJ $X,Sub" уменьшает число локальных регистров и изменяет их номера. Локальные регистры $(X+1), $(X+2), ..., $(L - 1) становятся $0, $1, ..., $(L - X - 2) внутри подпрограммы, и значение L уменьшается на X + 1. Таким образом, стек регистров остается без изменения, но X + 1 его элементов становятся недоступными из подпрограммы.

Если X >= G (X – глобальный регистр), то инструкция "PUSHJ $X,Sub" аналогична, но новый элемент создается на стеке регистров и L+1 регистров помещаются в стек. В этом случае, значение L нулевое при старте подпрограммы.

Стек регистров уменьшается после выполнения инструкции POP или когда программа уменьшает число локальных регистров инструкцией PUT (например, "PUT rL,5"). В общем случае, операнд X инструкции POP указывает число значений, возвращаемых из подпрограммы, если X <= L. Если X > 0, то $(X - 1) является главным возвращаемым значением. Это значение удаляется из стека регистров вместе со всеми значениями перед ним, и возвращаемое значение размещается в регистре, указанным в инструкции PUSHJ. Если X > L, поведение инструкции POP аналогично, но стек регистров остается неизменным, и ноль размещается в регистре, указанном в инструкции PUSHJ.

Пример.

Пусть программа A вызывает подпрограмму B. Пусть программа A имеет 5 локальных регистров ($0, $1, $2, $3, $4), которые не доступны подпрограмме B. Регистр $5 зарезервирован для хранения главного результата подпрограммы B. Пусть подпрограмма B имеет три параметра. Тогда установим $6<-arg0, $7<-arg1, $8<-arg2 и вызовем подпрограмму B инструкцией "PUSHJ $5,B". В начале выполнения подпрограммы B аргументы будут находиться в локальных регистрах $0, $1, $2.

Если подпрограмма B не возвращает результата инструкцией "POP 0,YZ", то локальные регистры $0, $1, $2, $3, $4 восстанавливают свое значение и L<- 5.

Если подпрограмма B возвращает единственный результат x инструкцией "POP 1,YZ", то она разместит x в локальный регистр $0. После возврата управления в программу A локальные регистры $0, $1, $2, $3, $4 восстанавливают свое значение и $5 <- x, L <- 6.

Если подпрограмма B возвращает два результата x, y инструкцией "POP 2,YZ", то она разместит главный результат x в локальный регистр $1, дополнительный результат y в локальный регистр $0. После возврата управления в программу A локальные регистры $0, $1, $2, $3, $4 восстанавливают свое значение и $5 <- x, $6 <- y и L <- 7.

Если подпрограмма B возвращает десять результатов (x, y0, ... , y8), то она разместит главный результат x в локальный регистр $9, дополнительные результаты в первые девять локальных регистров $0<-y0, $1<-y1, ..., $8<-y8. Инструкция "POP 10,YZ" восстановит $0, $1, $2, $3, $4 и $5<-x, $6<-y0, ..., $14<-y8. Перестановка регистров кажется странной с первого взгляда, но это позволяет хранить стек регистров без изменений за исключением главного результата.

Если подпрограмма B хочет, чтобы аргументы arg0, arg1, arg2 вновь появились в локальных регистрах $6, $7, $8 после возврата управления в программу A, то она может разместить их как дополнительные результаты в локальных регистрах $0, $1, $2 и возвратить управление в программу A инструкцией "POP 4,YZ".

Поле YZ инструкции POP обычно нулевое, но в общем случае инструкция "POP X,YZ" возвращает управление ниже инструкции PUSHJ на YZ+1 tetra байтов. Это удобно для подпрограмм с более чем одним выходом.

Специальные регистры

Кроме локальных и глобальных регистров, машина MMIX имеет специальные регистры. Cпециальные регистры используются для реализации архитектуры и инструкций машины MMIX. Cпециальные регистры хранятся в том же массиве g, что и глобальные регистры и занимают первые 32 элемента массива g. Например, специальный регистр rA хранится в g[21], специальный регистр rB хранится в первом элементе g[0] массива g.

Ниже приведен список специальных регистров и их соответствующие номера.

Заключение

В первой части статьи была описана архитектура виртуального компьютера MMIX. Рассмотрено адресное пространство, прерывания, стек регистров, общие и специальные регистры. Во второй части статьи будут приведены инструкции машины MMIX.

Список литературы

  1. Knuth D.E. THE ART OF COMPUTER PROGRAMMING, Vol. 1-3, 3 ed., Addison-Wesley, 1998
  2. Knuth D.E. MMIXware, LNCS 1750. Springer-Verlag, Berlin Heidelberg, 1999. pp. 422-493
  3. Knuth D.E. THE ART OF COMPUTER PROGRAMMING, FASCICLE 1, MMIX. Addison-Wesley, 2005. 144 p.
  4. Sedgewick Robert. Algorithms in C++, Parts 1-4, 3 ed., Addison-Wesley, 2002, 716 p.
  5. http://en.wikipedia.org/wiki/Treap - структура данных treap
  6. http://peternikmmixal.blogspot.com/2010_07_01_archive.html - блог, посвященный проекту MMIXAL.NET


Эта статья опубликована в журнале RSDN Magazine #4-2010. Информацию о журнале можно найти здесь
    Сообщений 0    Оценка 0        Оценить