码迷,mamicode.com
首页 > 编程语言 > 详细

第十二章 APO编程语言

时间:2014-12-01 22:30:05      阅读:421      评论:0      收藏:0      [点我收藏+]

标签:超级计算机   编程语言   面向对象编程   汇编语言   操作系统   

                                                           第十二章      APO编程语言

         APO编程语言是基于汇编语言和面向对象编程。基本指令只有7种:赋值指令、COPY指令、BTX(位X测试为1、或0转移)指令、查表跳转指令switch(RN){….}、移位与循环指令S、三操作数运算指令、调用与返回指令。所有的指令大小、除了32位立即数赋值是2字外;其它都是32位,一个字。 指令执行时间,除了32位立即数赋值是2ns、COPY指令取决于拷贝的长度外;其它指令都是1ns。 应用程序只能使用R0-R4,R8-R15的21个寄存器作为高速的寄存器局部变量;其中R0-R4作为方法参数寄存器,R0通常为返回结果寄存器。

        通常,子程序的形式用C语言表达就是函数;用所谓面向对象语言表达的是函数或方法。APO使用的是汇编语言,通常用伪指令(一组指令的缩写形式宏)来描述调用指令CALL;如:方法名称(参数1,参数2,参数3,参数4,参数5); 由编译器翻译成相关的最多6条汇编指令。其实,参数i是需要对应写入寄存器Ri(Ri = R0----R4);这就需要若干指令;以上的缩写编译器会自动展开成对应的一组指令的。参数可以有一个或多个,或者没有。没有参数的形式:方法名称();编译后就是一条指令CALL。返回的结果:数值可以写到寄存器R0;也可以写到寄存器R8-R31或静态变量中去。 CALL是压入H2的R0—R4及PSR、PRR、PPC;而RET是弹出R1—R4及PSR、PRR、PPC。 R0是不被覆盖的! 所以,R1-R4可看作是自动变量。指针或数据入口参数也可以先写入R8-R31;之后,再调用方法。注意:寄存器R24-R31是系统方法使用的局部变量;用户应用程序使用时;应注意到调用系统方法时,它们可能会被改写。

方法(子程序)的编写形式如下:
方法名称:
    指令1;
……
    指令n;
    RET;

或:方法名称{…}

         一些方法的集合就构成了方法库,方法库是放在只读的保护区域;由内核管理。一些公共的方法库是常驻在保护区域的。用户进程的方法库是动态存在于保护区域,当用户进程退出时,如果用户方法库的引用计数为0;就会使 用户方法库被踢出保护区域。方法库就是一个文件形式。所谓面向对象语言表达的接口概念其实就是指对象的一个方法库。

        CALL指令用来调用一个方法; 此时,下一条指令地址PPC会被压入堆栈,是压入行寄存器H0:R0—R4及PSR、PRR、PPC;以备返回时能恢复执行下条令, RET指令用来从一个方法返回;之前CALL保存的下条指令地址会从栈内弹出到H0行寄存器中,RET是弹出H0的R1—R4及PSR、PRR、PPC。程序转到CALL之前的下条指令处执行。

调用方法格式:
方法名称(参数1,参数2,参数3,参数4,参数5);

与C函数的区别:
1)、无须指明函数返回值类型;返回的值取决于方法或编程员的说明。
2)、没有复杂的形参、实参、指针、Void、数组引用等说法;全看作是32位寄存器参数。你认为是什么样的参数,那是由你决定的。
3)、无须头文件、无须函数原型声明。
4)、除了寄存器变量,就只有你声明的静态变量。

        立即数可以8位或16位一组用“.”号隔开。已经声明的变量,可直接代入寄存器相关位置;实际上,是变量的地址代入寄存器相关位置。如:方法1(3, 5.5, ,6.0.1.5,AAA); 入口参数4个,写入寄存器R0、R1、R3、R4; R2不管;其中R4为变量AAA的地址。

 

        R0-R4是方法内的自动变量,方法返回时被调用前的覆盖。通常是,在方法中,R0-R4只是作为只读的入口参数;而输出结果可用寄存器:R8—R31。在APO中,栈的用途除了CALL,RET就没有什么用了。APO只有寄存器型自动变量、静态变量(代码中申请内存分配的动态变量,本质上还是静态变量)。可以直接:如,R17 = 89; 用户自定义;不一定非要编译器分配变量空间。毕竟,寄存器空间有限;自己分配更为清晰些。使用寄存器变量的好处是无须写变量声明等;如果需要更大的空间来使用,你只能申请内存分配的动态变量了;BUxH  new(YYY); 就可得到x行的内存空间了,对该空间的初始化方法得自己编写了。YYY的释放你无须考虑,YYY一旦不用,系统会立即回收。最好,还是一开始就分配成静态变量并初始化了,省事。

一、变量与空间

          声明一个变量,实际上就是分配一个一定大小的存储空间;编译器会把变量名字翻译成该存储空间的相对逻辑地址。寄存器变量的地址是固定的实际地址,大小是32位的位容器;所以,无须声明;直接使用就行了。存储空间是有单位的,如BU1[32]  A; 声明变量A的空间是32个的1位数组,与BU32  A; 声明变量A的空间是32位的位容器,与BU1W  A; 声明变量A的空间是32位字的位容器,与BU2Z  A; 声明变量A的空间是2个16位的字符位容器;它们的意义是一样的。变量空间的寻址,是应该用修饰符.Z、.W、.E说明;比如BU256  A;  那么A.0,是变量A的第0个位的位地址?还是变量A的第0个字符地址?还是变量A的第0个字地址?还是变量A的第0个行地址?编译器通常认为是第0位。如果说A.0.Z,那就清楚了,是变量A的第0个字符;A.3.W, 就是变量A的空间以字为单位,是第3个字地址。寄存器变量是32位的位容器;R3.0.Z简写为R3H高半字;R3.1.Z简写为R3L低半字。类似,变量空间中地址的内容赋值,是应该用修饰符 .Z、.W、.E说明;比如BU64K  A; 那么A.3.W = 1; 是赋值给变量A中第3个字地址中的字地址内容?还是字符地址内容?还是位地址内容?如果说A.3.W.1,那就清楚了,是变量A的第3个字地址的第1位; A.3.W.W = 1; 就是赋值给变量A中第3个字地址中的字地址内容。A.3.W.H= 1; 就是赋值给变量A中第3个字地址中的高半字地址内容。A.3.E.E = 0; 就是赋值0给变量A中第3个行地址中的行地址内容。A.256.E.E= 1; 编译器报错,赋值超出变量声明范围;因变量A只是0-255行。

         用户程序只是能操作自己声明的变量空间、和寄存器变量R0-R4、R8-R31;所以,没有指针变量。指针操作,如(RX)、(变量)、在用户应用程序中是不允许的;编译器会报错;指针操作会带来风险。寄存器都是32位的,如R2.230、R3.3.W、(R16)等等、编译器都是报错。但也可以分为高低2个半字操作,如R1H、R1L,和行寄存器方式H1: R8-R15, H2:R16-R23, H3: R24-R31。要注意到H3行寄存器,在调用系统方法时,会被改写;所以,尽可能不使用R24-R31。变量是有空间大小的,赋值操作不能超出其范围;否则编译器报错。

 

二、指令简介:

1、赋值指令:
    
1)、位赋值:如R3.11 = 0; A.B.C.9.W.3 = 1; A.RH.W.6 = 0;  B.R1.W.0 = 1; 等等。
2)、16位赋值:如R4L = 11; A.B.C.5.W.H = 3; 等等。
3)、32位赋值: 如R31 = 11;  A.B.C.9.E.W = 7; 等等。
4)、 行赋值:H2 = 0;  A.B.C.E= 0;  A.B.E = 1; 等等; 行只有全0,或全1赋值。

   除了位数组,表中的成员变量最小是字,所以16位赋值要指明高H、低L半字。
5)、变量、寄存器相互赋值:
R9 = A.B.C.9.W;  R11 = A.B.3.W;  C.9.W = R13;  B.A.10.Z = H3; 等等。
应注意到位容器的大小;否则会有截断。通常以寄存器变量大小为准。

6)、运算赋值:只能是寄存器变量,如:R3 = ALU, #5; 实际是R3 = R3 ALU, #5;
只能是16位的立即数。
ALU有:   ADD 加法 + 、ADC 带进位加法、SUB 减法-、SBC带进位减法、CMP 比较、
          DADD 十进制调整、 BIC(!N and RD) 立即数求反后再“与” & !N、
          BIS(N or RD) “或|”、 AND “与&”、
          BIT(N and RD  “与”但不保存)、 XOR “异或^”、
          ORN(!N or RD) 立即数求反后再“或”、 RSB(N – RD)反减、
          MLU 乘*、DIV 除/、 SSAT饱和运算
另一些写法:RD = ALU N; RD+、RD-; 等等,也可以。

2、COPY 指令:

           将一个变量的部分内容拷贝到另一个变量,长度为立即数length、或寄存器。长度不能超过64K。有Z、W、E的3种拷贝方式。拷贝内容大小不能超过目标声明的空间大小,否则被截断。有3个参数:目标变量、源变量、长度;指令执行时,这3个参数被拷贝到专用寄存器B5、B6、B7L;B7H为编译器自动给出的范围监控参数。用户代码是不能对专用寄存器B0-B7进行写操作;只能是隐含操作。还可以使用“+、-” 2种模式;不注明通常是+模式,(B5) = (B6); B5+、B6+、直到长度为0,-模式就是B5-、B6-、直到长度为0。在代码中,你可能第一次使用COPY时,已经设置了B5、B6;而第二次用时,想利用原设置的B5或B6;那么可以少传一个参数。但编译器会验证是否越界。耗时:3ns + n(长度)/2  ns。

如:COPY.W( A, B.3.W, #N ); 变量B的第3成员开始,拷贝N个字到变量A;如果是COPY.E,则是拷贝N行到变量A。一到多拷贝,源变量前要加#号固定;COPY.E( A, #H2, #N ); 立即数也可以是寄存器变量,如COPY.W( A, B.3.W, R1L);等等。

 

3、BTX(位测试为X转移)指令:

如:BT0  R3.7, #A; 如果R3的第7位为0跳到A执行。

BT1  A.B.23, #A; 如果A.B的第23位为1跳到A执行。

测试状态寄存器PSR的位后的通用指令,左右2种写法都可以。
JEQ/JZ  标号; 零位被置时转移到标号语句  等于时程序跳转        BT1 PSR.Z标号;

JNE/JNZ 标号; 零位复位时转移到标号语句 不等时程序跳转         BT0PSR.Z标号;

JC/JHS  标号; 进位位被置时转移到标号语句 大于或等于时程序跳转  BT1 PSR.C标号;

JNC/JLO 标号; 进位位复位时转移到标号语句    小于时程序跳转     BT0PSR.C标号;

JN    标号; 负位被置时转移到标号语句       为负时程序跳转      BT1PSR.N标号;

JNN   标号; 负位被清零时转移到标号语句  大于或等于程序跳转  BT0PSR.N标号;

JV   标号;  溢出位被置时转移到标号语句                          BT1PSR.V标号;

JNV  标号; 没有溢出时转移到标号语句                             BT0 PSR.V标号;

JCZ  标号;  C置位,Z清零时转移到标号语句  大于时程序跳转        BT1 PSR.CZ标号;

JZC  标号;  C清零, Z置位时转移到标号语句  小于或等于时程序跳转  BT0PSR.CZ标号;

JL   标号; N .xor. V = 1 时转移到标号      带符号小于时程序跳转  BT1 PSR.NV标号;

JGE  标号; N .xor. V = 0 时转移到标号 带符号大于或等于时程序跳转BT0 PSR.NV标号;

JGT  标号; Z清零且N或V置位, 或N清零、V清零  带符号大于时跳转 BT1 PSR.GT标号;

JLE  标号; Z,或N置位且V零, 或N零且V 1 带符号小于或等时跳转    BT0PSR.GT标号;

JMP  标号; 无条件转移到标号语句                                   BT1 PSR.B1标号;

程序状态寄存器PSR = R5:

31 29 28 27 26  25 24  23 22  21 20  19  18  17  16 15 14 13 12    8   7    0

N  Z C  V   CZ ZC  LT  GE  GT LE  B1  YJ  IN  OUT      保留    执行标志    x号

N:   负号置1标志。      为负
NN:  N = 0。             负数,大于或等于
Z:   结果为0置1标志。  (EQ
NZ:  结果为非0,Z = 0。  结果非0或不等NE
C:   进位置1标志。      大于或等于(或HS
NC:  无进位,C = 0。     LO
V:   溢出位置1标志。
NV:  没有溢出, V = 0。
CZ:  C置1、结果非0(Z = 0)。  大于HI
ZC:  C = 0, 或Z = 1。           小于或等于LS 
LT:  N XOR V = 1;N != V。        带符号LT
GE:  Z XOR V = 0;N = V。         带符号大于或等于GE
GT:  Z = 0,N或V置1;或N = 0、V = 0。       带负号大于GT
LE:  Z置位,或N置位且V= 0;或N = 0, V = 1。带符号小于或等于LE
B1:  为1。                       总是1, AL


4、查表跳转指令: switch(RN){….};
 

有时候,一个方法有n个分支,n值在寄存器RN;这样使用查表跳转指令,就会据RN寄存器中的值而进入到正确的分支了。RN为: R0-R4,R8-R31中的一个。
RN是一个16位或32位寄存器整形变量,可以从表达式得到。这类似于C语言switch()、Case的情形。

5、移位与循环指令S:只能是寄存器变量

 S RD, RN, #N; S为下列之一,N为移动的最多32的位数。
逻辑左移<<(变进位)LSL(C); 逻辑右移>>(变进位)LSR(C);
算术右移S>>(变进位)ASR(C); 循环右移(变进位)ROR(C);

连进位一起循环右移RRX;
如:LSLC  R1, R3,#6;  R1 = R3 C<< 6,即R1 = R3左移6位,高位进C。
移位与循环指令也可以写成:RD = RN S N;

 

6、三操作数运算指令:只能是寄存器变量


RD = RN1 ALU RN2 S #N; // RN2移位或循环N位后与RN1运算的结果存放到RD。

7、调用与返回指令:

   调用CALL则是直接写方法名,方法返回是RET。

      如果要做浮点运算、或更多的其它功能就要用到系统库里的方法了;APO语言就7种基本指令。硬件模块的专用指令在用到再说,内核编程则还包含一些指针操作的指令。

 

三、面向对象编程

    面向对象编程在上一章已经介绍很多,这里只是举一些例子。
例子1:
    BU1W [100]  A;  // 数组变量A的成员都赋值为其下标。
数组成员赋值方法(){ // 指令数6W,占内存空间一行1E,运行时间:402ns。
    R1 = 0;
 L1:
    A.R1 = R1;      // 如果已经声明A是字数组,A.R1.W.W可简写为A.R1
    R1+;
    CMP.W  R1, #100;
    JNZ L1;
    RET
}

 

        汇编语言清晰、简单。可能有人会说,这么简单就要6行代码;其实不然,一些高级语言编译成汇编码,会远超过这里的。汇编语言,指令的空间、时间都可明确掌握、底层清晰;根本不是别的语言可以取代的!其实,简单的东西需要复杂的实现;复杂的东西只是简单的代码。这就是我们的真实世界规律!

例子2:排序

1、不带序号的数的位图式排序

        先介绍多功能硬件模块的比较指令CMP与位查询指令BIX。硬件模块的256E,可以分成2部分:输入128E,输出128E;如果输入128E中符合条件的结果全部给出在输出,那么标志PSR.IN置位,请求再输入128E;如果输出128E中的结果满,那么标志PSR.OUT置位,请求读出结果。这样,就无须多次启动SS1。对于范围在64K以内的无序数列,我们可以构建一个64K位的位图变量;让数列值映射到位图变量中的相应位;再利用多功能硬件模块的位查询指令BIX,很容易就能得到按小到大的有序数列;而且速度极快。对于超过64K、大小为x的无序数列,我们都可以构建有n个64K位的位图变量;让数列映射到位图变量中的相应位;之后,使用同样的方法进行。

  BU16 [X]  M, N; // 声明一个16位的无序数组变量M、和排序后结果变量N。

  BU64K  WTKJ = 0; // 声明一个位图变量,内存空间256H。编译器初始化为0

范围64K内无序数列排序( M, N, X, WTKJ ){ // 占用:25W, 4E;耗时最大约:325us
  R16L = 0;
PTXU1:
  R16H = M.R16L; // 读入无序数列M[X]的一项, 简写M.R16L.Z.Z为M.R16L
  WTKJ.R16H = 1; // 映射到位图变量WTKJ空间中的一位。
  R16L+;  X-;    // X-,非零继续循环。耗时:5Xns
  JNZ  PTXU1;   
  COPY.H( YJMK, WTKJ, 128 ); // 拷贝位图变量WTKJ的128E到硬件模块,67ns
  BIX;   SS1;            // 位查询、排序。
  BT1 PSR.OUT, PTXU2;  // 输出满,跳。
PT1:
  COPY.H(YJMK, WTKJ.128.E, 128 ); // 拷贝位图变量WTKJ的剩下128E到硬件模块
  SS1;
  BT1 PSR.OUT, PTXU3;  // 输出满,跳。
PT2:
  COPY.H( R1, YJMK.128.E, 128 ); // 输出结果。
PT3:
  RET

PTXU2:
  PTX();
  JMP  PT1;

PTXU3:
  PTX();
  JMP PT2;         // 跳,输出最后一次结果

PTX:
  COPY.H( R1, YJMK.128.E, 128 ); // 输出结果。
PT4:
  R1 = B5;                       // 保存结果变量地址序号。
  SS1; BT0  PSR.OUT, PT3;       //输出不满,跳返回。
  COPY.H( , YJMK.128.E, 128 );  // 否,继续输出结果。
  JMP PT4;                      // 循环、继续SS1、直到搜完整个位图。
}

       考虑M[64K]的无序不重复数组所花时间:位映射,320000ns;位排序,4680ns。所以,耗时还是在位映射320us,位排序只是4.68us。位图排序的方法,对于有重复数的情形下;只是保留其中一个数;所以,经位图排序后是数组的成员都是唯一的。位图排序需要使用到公共资源-硬件模块,要不调用系统方法key_YJMK();清YJMK、并锁住进程调度,要不位图排序升级为系统方法。在APO系统中有位图排序方法;但最大数只能到24位,这时要使用256个64K位的位图变量;占一个数据块。32位数的位图排序,应用程序只能先做256种数组分类,再调用系统位图排序方法WTPX( 无序数组变量, 最大数值, 数组成员数 )。排序后,结果还是放在输入无序数组变量;至于位图的大小、建立那是方法据最大数值的事情。

2、带序号的数排序

       这不能再用位图式排序方法了;类似数据库表中,据某字段的数值大、或小顺序排序出相应的记录组来。这只能是基数排序方法;基数是半字,那么32位的无序数组排序就要来2次;基数是字节,就要来4次,但易于硬件实现;我还在研究中。

 

前面章节中,都说行的符号是H;写到本章时,发现行H与高半字的H冲突;所以,行的符号改为E。没法的,写新的一章;可能全面的章节都要改动。晕倒!


第十二章 APO编程语言

标签:超级计算机   编程语言   面向对象编程   汇编语言   操作系统   

原文地址:http://blog.csdn.net/hhhewl/article/details/41653499

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!