首页 > 代码库 > 第十二章 APO编程语言
第十二章 APO编程语言
第十二章 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 标号; 无条件转移到标号语句 BT1PSR.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编程语言
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。