如何在没有"ADD"指令的情况下将两个寄存器加在一起



首先,不要纠结于"你使用AT&t还是英特尔汇编?">之类的问题。

我想知道是否可以在不使用ADDADC指令的情况下将两个寄存器(如AX和BX)的内容添加到一起。我只想使用这组指令:

MOV
CMP
JMP
JE
JNE

我希望看到一个简单的例子,即使它只是没有任何实际代码的算法,或者是一个很好的教程。

如果你想知道我为什么要问这个问题,那是因为我正在创建一台非常基本的计算机,目前只提供这些指令,我想知道它是否已经能够将两个寄存器添加在一起。

理论上是的。以下庞大的程序可以使用您的指令集来表达,并将执行添加:

if ax = 0 and bx = 0:
result = 0
else if ax = 0 and bx = 1:
result = 1
...
else if ax = 42 and bx = 24:
result = 66
...

在任何实际意义上,你的问题的答案都是"不"。

编辑:任何加法或减法都可以使用258字节的查找表,并且只使用movcmpjne。根本不需要巨大的查找表。使用相同的查找表分别更新较低的8位和较高的8位。

以下代码仅使用一个258字节的查找表,仅使用movcmpjneaxbx求和:

[位64];有效指令:mov、cmp、jmp、je、jne;常用指令:mov、cmp、jnesection.text全局启动(_st);此代码将ax和bx相加_开始:;定义要求和的值(以ax&bx为单位)。mov ax,12853;示例被加数1。mov bx,33276;示例被加数2。;求和很简单:只需递减每个被加数,直到它变为零,;并且对于每个递减,递增总和(以ax为单位)。cmp bx,0jne start_summing;通常情况下,我会做好准备;接下来的2条指令将被删除。cmp-bx,1;这表明jne是足够的。jne就绪;这个条件跳转总是分支。开始汇总(_S):mov ecx,0summing_loop:mov cl,blmov bl,[rcx+(number_line-1)];递减bl。cmp bl,255jne bl_not_carrymov cl,bhmov bh,[rcx+(number_line-1)];递减bh。bl_not_carry:mov cl,almov al,[rcx+(number_line+1)];增量al。cmp al,0jne al_not_carrymov cl,啊mov ah,[rcx+(number_line+1)];增量啊。_not_carry:cmp bx,0jne summing_lop准备就绪:;sum现在在eax中。第节数据最大值equ 255max_value_plus_1 equ(max_value+1)db最大值;0-1=255number_line:%分配myValue 0%rep最大值加1数据库myValue%指定myValue(myValue+1)%endrepdb 0

编辑:答案的其余部分涉及其他需要更多内存的解决方案。

编辑:一维128 KiB查找表足以进行16位操作数的任何加法或减法运算。不需要巨大的查找表。编辑:修复了导致通常设置进位标志的添加产生错误结果的错误。

这是x86-64汇编中的代码,用YASM汇编,可能也用NASM汇编。实现add ax,bx,仅使用movcmp&jne

[位64];有效命令:mov、cmp、jmp、je、jne;使用的命令:mov、cmp、jnesection.text全局启动(_st);此代码将ax和bx相加_开始:;定义要求和的值(以ax&bx为单位)。mov ax,12853;示例被加数1。mov bx,33276;示例被加数2。;求和很简单:只需递减每个被加数,直到它变为零,;并且对于每个递减,递增总和(以ax为单位)。mov edx,0mov dx,axmov eax,edx;eax=axmov ecx,0mov cx,bx;ecx=bxsumming_loop:mov cx,[2*rcx+(number_line-2)];递减ecx。mov ax,[2*rax+(number_line+2)];增加eax。cmp ecx,0jne summing_lop;sum现在在eax中。第节数据最大值equ 65535dw最大值;0-1=65535number_line:%分配myValue 0%rep最大值dw-myValue%指定myValue(myValue+1)%endrepdw 0

编辑:答案的其余部分涉及我第一次提出的一个更有限的解决方案。

这可以通过二维查找表来完成。

对于8位寄存器,例如al&bl,很简单。对于16位寄存器,这是可以做到的,但查找表将是巨大的(几乎是1字节),原因如下。查找表的每个单元格包含对应的X&Y坐标(X和Y坐标是被加数)。

对于8位和,查找表(256*256矩阵)如下所示:

db   0,   1,   2, ... , 253, 254, 255
db   1,   2,   3, ... , 254, 255,   0
db   2,   3,   4, ... , 255,   0,   1
.    .    .  .       .    .    .
.    .    .   .      .    .    .
.    .    .    .     .    .    .
db 253, 254, 255, ... , 250, 251, 252
db 254, 255,   0, ... , 251, 252, 253
db 255,   0,   1, ... , 252, 253, 254

在x86和x86-64mov可以用于乘以256^n,即:25665536、16777216。。。

mov乘以256很容易计算ax = 256 * bl:

mov ax,0
mov ah,bl

添加例如al&bl,我们需要得到正确的偏移量,它是256 * al + bl256 * bl + al(因为查找表是对称矩阵,它是对称的,因为加法是交换运算)。

在x86/x86-64中仅使用mov乘以65536和更大的数字需要使用内存,因为无法直接寻址32位通用寄存器(如eax)的16位高位或64位通用寄存器的32位高位(如rax)。

仅使用mov计算eax = 65536 * bx

mov [temp], dword 0
mov [temp+2], bx
mov eax, [temp]
...
temp dd 0

但16位值的真正问题是,在x86/x86-64中,内存是使用字节偏移量来寻址的,而不是使用字/dword/qword偏移量,并且我们只能乘以256^n。但让我们先来看看,如果我们在乘法和字节偏移寻址方面没有这个问题,查找表会是什么样子。查找表可以是这样的:

dw     0,     1,     2, ... , 65533, 65534, 65535
dw     1,     2,     3, ... , 65534, 65535,     0
dw     2,     3,     4, ... , 65535,     0,     1
.      .      .  .         .      .      .
.      .      .   .        .      .      .
.      .      .    .       .      .      .
dw 65533, 65534, 65535, ... , 65530, 65531, 65532
dw 65534, 65535,     0, ... , 65531, 65532, 65533
dw 65535,     0,     1, ... , 65532, 65533, 65534

这里,每行有65536个单元格,每个单元格都是双字,因此每行占用2*65536字节=131072字节。共有65536行,因此它是一个65536*65536矩阵

字大小的单元格对于X(水平索引,任意一个被加数)来说都不是问题,因为x86汇编允许比例因子为1、2、4和8

编辑:更正了数组大小的文本,它实际上比1TiB小一点。

这里的问题是,仅使用mov不可能将Y(垂直索引,另一个被加数)乘以131072。因此,查找表的每一行必须重复32768次,或者更准确地说,在任何实际数据行之间必须有32767个未使用的填充行。为什么是32767因为mov只能用来乘以25665536616777216。。。因此,我们需要将Y(垂直索引,另一个被加数)乘以16777216。由于每行占用131072个字节,要使新的数据行每16777216个字节开始一次,每个数据行之后必须有32767个未使用的填充行(每个填充行占用131082个字节)。在最后一个数据行之后,不需要填充行,因此总的阵列大小为:

65535*16777216+131072=10.99*10^12字节=几乎1字节(1 TiB)

不幸的是,我的电脑没有那么多内存,但在x86-64中是可能的。

以下是仅使用mov256*256查找表的8位加法代码(使用YASM测试,也应使用NASM组装):

[位64];有效指令:mov、cmp、jmp、je、jne;使用的指令:movsection.text全局启动(_st);必须保留al和bl;此代码对al&bl求和_开始:;定义要求和的值(以al&bl为单位)。mov al,47岁;示例第一被加数mov bl,55;示例第二被加数;求和代码从这里开始。mov ecx,0mov cl,al;ecx=almov ch,bl;ecx=256*bl+almov al,[rcx+sum_look_up_table];从查找表中取和。;对于32位代码,rcx->ecx;现在总数是al。第节数据y_times equ 256x乘以equ 256sum_look_up_table:%分配myY 0%重复次数%分配myX 0%重复x次%指定myValue(myX+myY)%重复次数%如果myValue>=256%指定myValue(myValue-256)%endif%endrep数据库myValue%指定myX(myX+1)%endrep%指定myY(myY+1)%endrep

因为我正在创建一台非常基本的计算机,目前只提供这些指令,我想知道它是否已经能够将两个寄存器添加在一起。

在这种情况下,您应该阅读任何汇编语言被认为有用所需的最低指令集是什么?

简而言之,最小可能的体系结构只有一条指令。然而,要想更加有用非常基本的计算机应该至少有一个位操作指令。仅一个NANDNOR就足以用于所有逻辑计算。有了它,你也可以做算术,但不如单独的ADD/SUB有效。此外,你还需要另一次有条件的跳跃。总共有3条指令。但是,如果你可以有5条这样的指令,那么你可以在另一个问题上阅读许多更好的指令选择

也就是说,在x86和许多其他架构(如8051)中,只使用MOV就可以做任何事情,因为它被证明是图灵完备的。甚至还有一个编译器可以将有效的C代码编译成一个只有MOV(或者只有XOR、SUB、ADD、XADD、ADC、SBB、AND/or、PUSH/POP、1位移位或CMPXCHG中的一个)的程序,名为movfuscator。关于编译器如何工作的详细信息,请参阅Break Me00 the MoVfuscator将mov变成一场毁灭灵魂的RE噩梦Christopher Domas,或者如果你没有时间,只需阅读幻灯片。基本上,它将查找表用于大多数目的。下面是一个关于如何实现8位加法的例子

static void alu_add8(char* s, char* x, char* y, char* c, int off)
{
/* requires dword carry initialized */
print("# alu_add8n");
debug_id();
print("movl $0, %%eaxn");
print("movl $0, %%ebxn");
print("movl $0, %%ecxn");
print("movl $0, %%edxn");
print("movb (%s+%d), %%aln", x, off);
print("movb (%s+%d), %%cln", y, off);
print("movl (%s), %%ebxn", c);
print("movb alu_add8l(%%eax,%%ecx), %%dln");
print("movb alu_add8h(%%eax,%%ecx), %%dhn");
print("movb alu_add8l(%%edx,%%ebx), %%aln");
print("movb %%al, (%s+%d)n", s, off);
print("movb alu_add8h(%%edx,%%ebx), %%aln");
print("movb %%al, (%s)n", c);
print("# end alu_add8n");
}

进一步读取

  • 构建图灵完整处理器所需的绝对最小指令集是什么
  • MOV是图灵完备的:4位加法器实现
  • MOV是图灵完备的
  • X86 mov正在图灵完成:仅限mov的编译器

最新更新