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

Часть 2

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

Инструкции виртуального компьютера MMIX

Инструкции машины MMIX будут описаны формально и неформально. Например, неформальное описание инструкции “SUB $X,$Y,$Z”: установи значение регистра $X равным разности значений регистров $Y и $Z. Формальное описание инструкции “SUB $X,$Y,$Z”: ”s($X) <- s($Y) - s($Z)”. Здесь s($X) обозначает знаковое целое. Присваивание s(x) <- N означает установку двоичного значения s(x) равным значению N. Такое присваивание может вызвать целое переполнение, когда значение N слишком велико, чтобы быть размещенным в переменной x. В общем случае, присваивание s(x) <- N устанавливает значение x к двоичному представлению N mod 2n, где n - число битов в x. Данное присваивание вызывает переполнение, когда N < -2n - 1 или N >= 2n - 1.

Инструкции загрузки и сохранения

Данные инструкции передают информацию между регистрами и памятью. Каждая из следующих инструкций имеет адрес памяти A, получаемый сложением регистров $Y и $Z. Формально A = (u($Y) + u($Z)) mod 264. Здесь нотация u(x) аналогична нотации s(x), но значение u(x) рассматривается как беззнаковое двоичное число.

Эти инструкции пересылают данные из памяти в регистр $X, преобразуя знаковый байт, знаковый wyde байт, знаковый tetra байт в знаковый octa байт.

Пример.

BaseOfPoolSegm IS          $2 
baseOfDataSegm IS          $3 
offset         IS          $4  
temporary      IS          $5 
rA6            IS          $6 
rA7            IS          $7 
rA8            IS          $8 
rA9            IS          $9
rA10           IS          $10
enableTripOfIntegerOverflow           IS $11
enableTripOfIntOverflowAndIntDivCheck IS $12
rA15           IS          $15
rA16           IS          $16
cmpResult      IS          $17
workHorse      IS          $255 
**************** Pool_Segment *********************************************** 
* It is not recommended to write an info in Pool_Segment. Use the Data_Segment instead
              LOC               Pool_Segment
              GREG              @
              LOC               @+#1000           % skip past #1000 bytes
Lab2          OCTA              0
**************** Data_Segment *********************************************** 
LOC           Data_Segment      
              GREG              @
Data1         OCTA              #0123456789abcdef 
**************** Main program *********************************************** 
LOC            #100            
Main      LDA           baseOfPoolSegm,Lab2
              LDA           baseOfDataSegm,Data1
          LDA           offset,2
**************** The heading above will be applied to all examples in Instructions chapter
***************************************************************                         
              LDB           temporary,baseOfDataSegm,offset 
              STO           temporary,baseOfPoolSegm,#0 sets #0000000000000045  
***************************************************************                         
              LDW                temporary,baseOfDataSegm,offset   
              STO           temporary,baseOfPoolSegm,#8 sets #0000000000004567  
***************************************************************                         
              LDT           temporary,baseOfDataSegm,offset  
              STO           temporary,baseOfPoolSegm,#10 sets #0000000001234567  
***************************************************************                         
              LDO           temporary,baseOfDataSegm,offset 
              STO           temporary,baseOfPoolSegm,#18 sets #0123456789abcdef  

Следующие инструкции аналогичны инструкциям LDB, LDW, LDT, LDO, но они трактуют данные в памяти как беззнаковые.

Инструкции LDO, LDOU функционально аналогичны. При расширении коротких данных левые битовые позиции устанавливаются в нуль.

Следующая инструкция загружает tetra байт M4[A] в левую часть регистра $X, а правая часть устанавливается в нуль.

LDHT $X,$Y,$Z (load high tetra): u($X) <- u(M4[A]) * (232)

Следующая инструкция загружает адрес памяти (A = (u($Y) + u($Z)) mod 264) в регистр $X.

LDA $X,$Y,$Z (load address): u($X) <- A

Инструкции LDA, ADDU функционально аналогичны.

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

Переполнение возможно, если (знаковое) число в регистре находится вне границ поля памяти.

Следующие инструкции аналогичны предыдущим четырем STB, STW, STT, STO, но переполнение никогда не возникает.

Следующая инструкция сохраняет левую половину регистра $X в tetra байте M4[A].

STHT $X,$Y,$Z (store high tetra): u(M4[A]) <- floor(u($X) / (232))

Следующая инструкция сохраняет константу X ( 0 <= X >= 255 ) в octa байте M4[A].

STCO X,$Y,$Z (store constant octabyte): u(M8[A]) <- X

Арифметические инструкции

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

Инструкция деления формирует частное и остаток, который размещается в специальном регистре rR. Его значение может быть получено инструкцией GET $X,rR. Если делитель $Z имеет нулевое значение, то $X <- 0 и rR <- $Y; при этом возникает прерывание – целочисленное деление на нуль.

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

иначе $X <- rD, rR <- $Y

Инструкция MULU формирует 16-байтовый результат, и левая половина размещается в специальном регистре rH.

Инструкция DIVU формирует 8-байтовое частное и остаток из 16-байтового делимого и 8-байтового делителя. Верхняя половина делимого размещается в специальном регистре rD, который имеет нулевое значение при старте программы. Значение регистра rD может быть установлено инструкцией PUT rD,$Z. Если значение rD больше или равно значению делителя, то инструкция “DIVU $X,$Y,$Z” устанавливает $X <- rD, rR <- $Y. Этот случай возникает всегда, когда значение $Z нулевое. Инструкция DIVU никогда не инициирует прерывание "целочисленное деление на нуль".

При выполнении инструкции ADDU формируется адрес A = (u($Y) + u($Z)) mod 264; поэтому инструкции LDA, ADDU функционально аналогичны.

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

Быстрее выполнить команду 2ADDU $X,$Y,$Y, чем умножить $Y на 3, если не возникает переполнения.

В следующих инструкциях Y является беззнаковой константой. Обычно значение Y равно нулю. В этом случае мы пишем "NEG $X,$Z" или "NEGU $X,$Z".

Следующими инструкциями являются инструкции сдвига.

Инструкции SL и SLU функционально аналогичны, но SL может вызвать переполнение в отличии от SLU. Инструкция SR расширяет знак при сдвиге вправо, SRU заполняет освободившиеся биты нулем. Инструкции SR, SRU не вызывают переполнения. Поэтому инструкции SL и SLU имеют тот же результат в $X, тогда и только тогда, когда значение $Y не отрицательно и значение $Z отлично от нуля.

Инструкции SL(U) и SR(U) работают намного быстрее, чем инструкции MUL(U) и DIV(U). Нотация y << z используется для обозначения результата сдвига двоичного значения y влево на z бит.

Нотация y >> z используется для обозначения результата сдвига двоичного значения y вправо на z бит.

Следующие инструкции устанавливают значение $X равным –1, или 0, или 1 в зависимости от того, является ли значение регистра $Y меньше, равно или больше значения регистра $Z.

Условные инструкции

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

Если значение $Y удовлетворяет условию, то значение регистра $Z копируется в регистр $X. Регистр отрицателен тогда и только тогда, когда его знаковый бит установлен в 1. Регистр нечетен тогда и только тогда, когда его правый бит установлен в 1.

В следующих инструкциях значение регистра $Z копируется в регистр $X, если значение $Y удовлетворяет условию; иначе значение регистра $X устанавливается в ноль.

Побитовые инструкции

Часто бывает полезно рассматривать octa байт как вектор v(x), состоящий из 64 бит, и осуществлять операции одновременно над 64 битами. Следующие инструкции переполнение не вызывают.

Здесь операция "not v" меняет 0 на 1 и 1 на 0. Бинарные операции ^, V, exc_or определяются следующими правилами:

0 ^ 0 = 0; 0 V 0 = 0; 0 exc_or 0 = 0

0 ^ 1 = 0; 0 V 1 = 1; 0 exc_or 1 = 1

1 ^ 0 = 0; 1 V 0 = 1; 1 exc_or 0 = 1 

1 ^ 1 = 1; 1 V 1 = 1; 1 exc_or 1 = 0

Инструкция MUX комбинирует два битовых вектора путем анализа значения специального регистра rM, выбирая бит регистра $Y, если бит rM установлен в единицу, и выбирая бит регистра $Z, если бит rM установлен в ноль. Инструкция MUX переполнение не вызывает.

MUX $X,$Y,$Z (bitwise multiplex): v($X) <- (v($Y) ^ v(rM)) v (v($Z) ^ v(rM))

0($Y) ^ 1(rM)  = 0; 0($Z) ^ 1(rM)  = 0; v($X) <- 0

1($Y) ^ 1(rM)  = 1; 1($Z) ^ 1(rM)  = 0; v($X) <- 1

0($Y) ^ 0(rM)  = 0; 0($Z) ^ 0(rM)  = 0; v($X) <- 0

1($Y) ^ 0(rM)  = 0; 1($Z) ^ 0(rM)  = 1; v($X) <- 1

Инструкция SADD подсчитывает число битовых позиций, в которых биты регистра $Y установлены в единицу, а соответствующие биты регистра $ Z установлены в ноль. Инструкция SADD переполнение не вызывает.

SADD $X,$Y,$Z (sideways add)

Побайтовые инструкции

Часто бывает полезно рассматривать octa байт как вектор b(x), состоящий из 8 байт, каждый из которых представляет целое между 0 и 255. Можно также рассматривать octa байт как вектор w (x), состоящий из 4 wyde байт или как вектор t (x), состоящий из 2 беззнаковых tetra байт.

Здесь y ss z = max(0, y-z) . Побайтовые инструкции имеют важное значение при обработке текстов и графической информации. Побайтовые инструкции переполнения не вызывают.

Следующие инструкции устанавливают значение каждого байта $X путем просмотра соответствующего байта регистра $Z, используя его биты для выбора байтов регистра $Y. Над выбранными байтами осуществляется операция ”или” или ”исключающее или”.

Данные инструкции используются при работе с матрицами.

Примеры.

                  LDA  baseOfPoolSegm,Lab2
                  LDA  offset,#f30
                  LDA  $13,#02ab396476abfdaf
                  LDA  $14,#0102040810204080
                  SLU  temporary,temporary,64
                  MOR  temporary,$13,$14
                  STO  temporary,baseOfPoolSegm,offset sets M8[#400000000001f30]=#affdab766439ab02
***************************************************************                   
                  LDA  baseOfPoolSegm,Lab2
                  LDA  offset,#f38
                  LDA  $13,#02ab396476abfdaf
                  LDA  $14,#00000000000000ff
                  SLU  temporary,temporary,64
                  MOR  temporary,$13,$14
                  STO  temporary,baseOfPoolSegm,offsetsets M8[#400000000001f38]=#00000000000000ff
**************************************************************                   
                  LDA  baseOfPoolSegm,Lab2
                  LDA  offset,#f40
                  LDA  $13,#02ab396476abfdaf
                  LDA  $14,#0102040810204080
                  SLU  temporary,temporary,64
                  MXOR temporary,$13,$14
                  STO  temporary,baseOfPoolSegm, offsetsets M8[#400000000001f40]=#affdab766439ab02
***************************************************************                   
                  LDA  baseOfPoolSegm,Lab2
                  LDA  offset,#f48
                  LDA  $13,#02ab396476abfdaf
                  LDA  $14,#00000000000000ff
                  SLU  temporary,temporary,64
                  MXOR temporary,$13,$14
                  STO  temporary,baseOfPoolSegm, offsetsets M8[#400000000001f48]=#000000000000007b

Непосредственные константы в инструкциях

В инструкциях машины MMIX значение третьего поля может трактоваться двояко: либо оно содержит номер регистра $Z, либо байтовую константу Z со значениями в интервале [0, 255]. Например, существуют две инструкции “ADD $X,$Y,$Z“ и “'ADD $X,$Y,Z“. Код первой инструкции #20, а второй #21. В общем случае, код инструкции с непосредственной константой в третьем операнде на единицу больше кода инструкции с номер регистра в третьем операнде. Некоторые инструкции имеют непосредственную константу со значениями в интервале [0, 65535], располагающуюся во втором и третьих операндах. Эти wyde-константы могут быть сдвинуты в различные позиции octa байта. При этом переполнения не происходит.

Ассемблер машины MMIX позволяет писать SET вместо SETL и “SET $X,$Y“ вместо “OR $X,$Y,0“.

Инструкции переходов

Обычно инструкции выполняются в естественной последовательности: после выполнения инструкции по адресу @ выполняется инструкция по адресу @ + 4. Символ @ играет роль счетчика команд. Но инструкции перехода нарушают эту последовательность.

JMP RA (jump): @ <- RA

Здесь RA обозначает трехбайтовый относительный адрес @+4*XYZ. Например, инструкция “JMP @+4*2“ является символической формой tetra байта #f0000002. Если адресом инструкции является значение #1000, то адресом следующей для выполнения инструкции будет значение #1008.

Относительные значения могут быть отрицательными. В этом случае код инструкции увеличивается на единицу и значение поля XYZ равно смещению плюс 224. Например, инструкция “JMP @-4*2“ является символической формой tetra байта #f1fffffe. Код операции #f0 говорит программе перейти вперед, а код операции #f1 говорит программе перейти назад; хотя в символической форме для кода операции используется одна и та же мнемоника JMP.

Обычно мы пишем “JMP Addr“, чтобы перейти на адрес Addr, и ассемблер сам высчитывает код инструкции и значение поля XYZ. Максимальное смещение для такого перехода равно 67 миллионов байт.

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

Приведем список ограничений:

Следующие инструкции являются условными переходами, зависящими от значения регистра $X.

Границы адреса перехода RA: [@-218, @+218-4]. Это меньше, чем для инструкции JMP, так как только два байта доступны для размещения относительного адреса .

Когда вероятность того, что будет выполнен условный переход, превышает 50%, желательно использовать инструкцию PB вместо инструкции B.

Вызов подпрограмм

MMIX имеет несколько инструкций, которые эффективно вызывают подпрограммы (и возвращают управление из них) с помощью стека регистров.

Инструкция PUSHJ вызывает подпрограмму путем передачи управления инструкции по относительному адресу RA. Перед переходом адрес инструкции, которая выполнилась бы следующей в обычном порядке, помещается в специальный регистр rJ. Операция push(X) означает, что локальные регистры [$0, $X] сохранены в стеке регистров и временно недоступны. Специальный регистр rJ содержит адрес возврата из подпрограммы.

PUSHJ  $X,RA (push registers and jump): push(X) и rJ<-@+4, @ <- RA(Relative Address)

Инструкция GO позволяет перейти на абсолютный адрес памяти A = (u($Y) + u($Z)) mod 264. Перед переходом адрес инструкции, которая выполнилась бы следующей в обычном порядке, помещается в регистр $X. Позже мы можем вернуться по этому адресу при помощи, например, инструкции “GO $X,$X,0“.

GO $X,$Y,$Z (go): u($X) <- @+4, @ <- A(Absolute Address)

Инструкция GO немного медленнее инструкции PUSHJ но не медленнее инструкции POP.

Инструкция PUSHGO аналогична инструкции PUSHJ, но передает управление по абсолютному адресу.

PUSHGO $X,$Y,$Z (push registers and go):   push(X) and set rJ<-@+4, then set @ <- A(Absolute Address) .

Регистр $(X+1) становится регистром $0, регистр $(X+2) становится регистром $1 и так далее. Но глобальные регистры $k (k >= rG) остаются неизменными.

Инструкция POP возвращает управление из подпрограммы путем передачи управления на инструкцию по адресу rJ + (4*YZ). Операция pop (X) означает, что все (кроме X) локальные регистры становятся маргинальными. Локальные регистры, сохраненные в стеке регистров операцией push(X), восстанавливаются к своим прежним значениям.

POP X,YZ (pop registers and return): pop(X), @ <- rJ + (4*YZ)

Если стек регистров становится очень большим, MMIX сохранит элементы, находящиеся на дне стека регистров, в сегменте стека (который начинается с виртуального адреса #6000000000000000). MMIX также сохраняет элементы стека регистров в сегменте стека, когда инструкция SAVE сохраняет текущий контекст программы. Сохраненные элементы стека регистров восстанавливаются из сегмента стека, когда инструкция POP нуждается в них, или когда инструкция UNSAVE восстанавливает сохраненный контекст программы.

Пример.

...
Lab115 …
...
               LOC         Data_Segment      
               GREG        @
               LOC         @+#5000                 
Data10         OCTA        1                       
Data11         OCTA        2                       
Data12         OCTA        12                      
Data13         OCTA        13                      
Switch1        OCTA        1
Switch2        OCTA        2
Data14         OCTA        #0123456789abcdef                   
Data15         OCTA        #0123456789abcdef                   
           LOC         Lab115+20
               GREG        @
               JMP         Lab116
Subroutine     ADD         $0,$0,$1 sets main result
               POP         1,0      returns one result
%%%%%%%%%%% end of subroutine
Lab116         LDA         $0,#112233445566778a
               LDA         $1,#112233445566778b
               LDO         $17,Data10 first  arg
               LDO         $18,Data11 second arg 
               PUSHJ       $16,Subroutine $16 will contains the result
               LDA         baseOfPoolSegm,Lab2
               LDA         offset,#11a8 
               STO         $16,baseOfPoolSegm,offset main result M8[#40000000000021a8]=#3                                
               LDA         offset,#11b0 
               STO         $0,baseOfPoolSegm, offset M8[#40000000000021b0]=#112233445566778a                                
               LDA         offset,#11b8 
               STO         $1,baseOfPoolSegm, offset M8[#40000000000021b8]=#112233445566778b 

Инструкция SAVE сохраняет все текущие регистры в cегменте стека и присваивает адрес наиболее верхнего сохраненного octa байта в регистре $X. Регистр $X должен быть глобальным (X >= rG).

SAVE $X,0; (save process state): u($X) <- context

Машина MMIX имеет специальные регистры rO (смещение в стеке регистров) и rS (указатель на стек регистров), которые управляют выполнением инструкций PUSH, POP, SAVE, UNSAVE.

Порядок выполнения инструкции SAVE:

Инструкция SAVE обнуляет значение специального регистра rL, значение которого восстанавливается инструкцией UNSAVE.

Инструкция UNSAVE берет адрес наиболее верхнего сохраненного octa байта в регистре $X и восстанавливает контекст, сохранный инструкцией SAVE.

UNSAVE $Z   (restore process state): context <- u($Z)

Инструкция UNSAVE восстанавливает значение специального регистра rL.

Системные инструкции

Следующие инструкции предназначены в основном для сверхбыстрой (параллельной) архитектуры машины MMIX. Некоторые из данных инструкций аналогичны инструкциям типа PBN, PBZ,… , то есть они подсказывают машине MMIX, как планировать выполнение инструкций с целью достижения максимальной эффективности.

Инструкции LDUNC, STUNC функционально аналогичны инструкциям LDO и STO, но они также информируют машину MMIX, что загруженный (сохраненный) octa байт и его ближайшие соседи, вероятно, не будут прочитаны (записаны) в ближайшее время.

Следующая инструкция говорит, что байты M[A], …, M[A+X] будут, определенно, загружены в ближайшее время.

PRELD X,$Y,$Z (preload data)

Следующая инструкция говорит, что байты M[A], …, M[A+X] будут, определенно, записаны(сохранены) перед следующей загрузкой.

PREST X,$Y,$Z (prestore data)

Следующая инструкция говорит, что байты M[A], …, M[A+X] будут, определенно, использованы в качестве инструкций в ближайшее время.

PREGO X,$Y,$Z (prefetch to go)

Следующая инструкция говорит, что байты M[A], …, M[A+X] должны быть заполнены снова перед тем, как быть интерпретированы в качестве инструкций.

SYNCID X,$Y,$Z (synchronize instructions and data)

Следующая инструкция говорит, что байты M[A], …, M[A+X] должны быть обновлены в физической памяти, чтобы другие компьютеры или устройства ввода/вывода могли их прочитать.

SYNCD X,$Y,$Z (synchronize data)

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

SYNC XYZ (synchronize) XYZ может принимать одно из значений: 0, 1, 2, 3.

Следующая инструкция является привилегированной. Используется только операционной системой.

LDVTS $X,$Y,$Z (load virtual translation status)

Следующие инструкции позволяют прочитать (сохранить) данные из (в) специальных регистров.

Инструкция PUT имеет следующие ограничения:

Инструкция GETA загружает в регистр $X относительный адрес, который определяется по тем же правилам, что и для инструкций переходов.

GETA $X,RA (get address): u($X) <- RA

Например, инструкция “GETA $1,@” установит значение регистра $1, равное адресу этой инструкции.

Следующая инструкция сравнивает и обменивает значения octa байт.

CSWAP $X,$Y,$Z (compare and swap octabytes)

Если (M8[A]) = u(rP), u(M8[A]) <- u($X) , u($X) <- 1

Если u(M8[A]) != u(rP), u(rP) <- u(M8[A]) , u($X) <- 0

Это атомарная операция, которая используется независимыми компьютерами с общей памятью.

Следующая инструкция SWYM не осуществляет какой-либо операции. Операнды X,Y,Z игнорируются.

Например, “SWYM 0”.

В третьей части статьи будет обсуждаться арифметика с плавающей точкой, временные характеристики MMIXAL программ, операции ввода-вывода ассемблера 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


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