首先,不要为“您使用 AT&T 还是 Intel 组件?”而烦恼。或类似的事情。
我想知道是否可以在不使用ADD
orADC
指令的情况下将两个寄存器的内容相加,比如 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 字节查找表并仅使用mov
,cmp
和jne
. 完全不需要巨大的查找表。低 8 位和高 8 位使用相同的查找表分别更新。
这是总结ax
并bx
仅使用一个 258 字节查找表并且仅mov
,cmp
和的代码jne
:
[位 64] ; 有效指令:mov、cmp、jmp、je、jne ; 使用指令:mov、cmp、jne 部分 .text 全局_start ; 这段代码总结了 ax & bx _开始: ; 定义要求和的值(在 ax 和 bx 中)。 移动斧头,12853;示例总和 1。 mov bx,33276 ; 示例总和 2。 ; 求和很容易:只需递减每个求和,直到它变为零, ; 并且对于每次递减,增加总和(以 ax 为单位)。 cmp bx,0 jne start_summing ; 通常这将是 je 准备好并且 ; 接下来的 2 条指令将被删除。 cmp bx,1 ; 这表明 jne 就足够了。 准备好了;这个条件跳转总是分支。 开始求和: 移动 ecx,0 求和循环: mov cl,bl mov bl,[rcx+(number_line-1)] ; 减少bl。 CMP BL,255 jne bl_not_carry mov cl,bh mov bh,[rcx+(number_line-1)] ; 减少 bh。 bl_not_carry: mov cl,al 移动,[rcx+(number_line+1)]; 增加的。 cmp al,0 jne al_not_carry mov cl,ah mov 啊,[rcx+(number_line+1)] ; 增量啊。 al_not_carry: cmp bx,0 jne summing_loop 准备好: ; sum 现在在 eax 中。 节 .data 最大值等于 255 max_value_plus_1 equ (max_value + 1) 数据库最大值;0 - 1 = 255 number_line: %分配我的价值 0 %rep max_value_plus_1 数据库 myValue %assign myValue (myValue + 1) %endrep 分贝 0
编辑:答案的其余部分涉及需要更多内存的其他解决方案。
编辑:一维 128 KiB 查找表足以用于 16 位操作数的任何加法或减法。不需要巨大的查找表。 编辑:修复了导致通常设置进位标志的添加产生错误结果的错误。
这是 x86-64 汇编中的代码,用 YASM 组装,也可能用 NASM 组装。实现add ax,bx
,仅使用mov
,cmp
& jne
。
[位 64] ; 有效命令:mov、cmp、jmp、je、jne ; 使用的命令:mov、cmp、jne 部分 .text 全局_start ; 这段代码总结了 ax & bx _开始: ; 定义要求和的值(在 ax 和 bx 中)。 移动斧头,12853;示例总和 1。 mov bx,33276 ; 示例总和 2。 ; 求和很容易:只需递减每个求和,直到它变为零, ; 并且对于每次递减,增加总和(以 ax 为单位)。 移动 edx,0 mov dx,ax 移动 eax,edx ; eax = 斧头 移动 ecx,0 mov cx,bx ; ecx = bx 求和循环: mov cx,[2*rcx+(number_line-2)] ; 递减 ecx。 mov ax,[2*rax+(number_line+2)] ; 增加eax。 cmp ecx,0 jne summing_loop ; sum 现在在 eax 中。 节 .data 最大值等于 65535 dw 最大值;0 - 1 = 65535 number_line: %分配我的价值 0 %rep 最大值 dw 我的价值 %assign myValue (myValue + 1) %endrep 0
编辑:答案的其余部分涉及我首先提出的一个更有限的解决方案。
它可以通过二维查找表来完成。
对于 8 位寄存器,例如al
& bl
,这很容易。对于 16 位寄存器,可以这样做,但是查找表会很大(几乎 1 tebibyte),原因见下文。查找表的每个单元格都包含相应 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,即:256, 65536, 16777216, ...
乘以 256mov
很容易计算ax = 256 * bl
:
mov ax,0
mov ah,bl
添加例如。al
& bl
,我们需要得到正确的偏移量,它是256 * al + bl
,或者256 * bl + al
(因为查找表是一个对称矩阵,它是对称的,因为加法是一个交换操作)。
仅在 x86/x86-64 中使用乘以 65536 和更大的数字mov
需要使用内存,因为无法直接寻址 32 位通用寄存器(例如 eax)的高 16 位或 64- 的高 32 位位通用寄存器(如 rax)。
eax = 65536 * bx
仅使用计算mov
:
mov [temp], dword 0
mov [temp+2], bx
mov eax, [temp]
...
temp dd 0
但是 16 位值的真正问题是,在 x86/x86-64 中,内存是使用字节偏移量来寻址的,而不是使用 word/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 个单元,每个是 dword,所以每一行占用 2 * 65536 字节 = 131072 字节。有 65536 行,所以它是一个65536 * 65536 矩阵。
字大小的单元格对于 X (水平索引,任一和数)都不是问题,因为 x86 程序集允许比例因子为 1、2、4 和 8。
编辑:更正了数组大小的文本,它实际上比 1 TiB 小一点。
mov
这里的问题是,仅使用 是不可能将 Y(垂直索引,另一个和)乘以 131072 因此,查找表的每一行必须重复 32768 次,或者更准确地说,在任何实际数据行之间必须有 32767 个未使用的填充行。为什么是 32767?因为mov
只能用于乘以 256、65536、16777216 ... 所以我们需要将 Y(垂直索引,另一个被加数)乘以 16777216。由于每行占用 131072 字节,因此要使新数据行每隔 16777216 字节开始,每个数据行之后必须有 32767 个未使用的填充行(每个占用 131072 个字节)。在最后一个数据行填充行之后不需要,因此数组大小总计为:
65535 * 16777216 + 131072 = 10.99 * 10^12 字节 = 几乎 1 TB (1 TiB)。
不幸的是,我的计算机中没有那么多内存,但在 x86-64 中是可能的。
这是仅使用 8 位加法mov
和256 * 256 查找表的代码(使用 YASM 测试,也应使用 NASM 组装):
[位 64] ; 有效指令:mov、cmp、jmp、je、jne ; 使用说明:mov 部分 .text 全局_start ; al & bl 必须保留 ; 这段代码总结了 al 和 bl _开始: ; 定义要求和的值(在 al 和 bl 中)。 移动,47; 示例第一个加法 mov bl,55 ; 第二个加法示例 ; 求和代码从这里开始。 移动 ecx,0 移动 cl,al ; ecx = 人 mov ch,bl ; ecx = 256 * bl + al 移动,[rcx+sum_look_up_table] ; 从查找表中获取总和。 ; 对于 32 位代码,rcx -> ecx ; 总和现在是 al。 节 .data y_time 等于 256 x_times equ 256 sum_look_up_table: % 分配我的 0 %rep y_times %分配我的X 0 %rep x_times %assign myValue (myX + myY) %rep y_times %if myValue >= 256 %assign myValue (myValue - 256) %万一 %endrep 数据库 myValue %assign myX (myX + 1) %endrep %assign myY (myY + 1) %endrep
因为我正在创建一台非常基本的计算机,目前只提供这些指令,我想知道它是否已经能够将两个寄存器加在一起。
在这种情况下,您应该阅读什么是任何汇编语言被认为有用所需的最低指令集?
简而言之,最小可能的架构只有一条指令。然而,为了更有用,一台非常基本的计算机应该至少有一个位操作指令。只需一个NAND
orNOR
就足以完成所有逻辑计算。有了它,您也可以进行算术运算,但不如单独的 ADD/SUB 高效。此外,您还需要另一个条件跳转。总共有 3 条指令。但是,如果您可以有 5 条这样的指令,那么您可以在另一个问题上阅读许多更好的指令选择
也就是说,仅MOV
在 x86 和许多其他架构(如8051 )中就可以做任何事情,因为它已被证明是图灵完备的。甚至还有一个编译器可以将有效的 C 代码编译成只有 MOV(或只有 XOR、SUB、ADD、XADD、ADC、SBB、AND/OR、PUSH/POP、1 位移位或 CMPXCHG/XCHG 之一)的程序命名为movfuscator。Break Me00 The MoVfuscator Turning mov into a soul crushing RE nightmare Christopher Domas中解释了编译器如何工作的详细信息,或者如果您没有时间,请阅读幻灯片。基本上它为大多数目的使用查找表。这是一个关于如何实现8位加法的示例
static void alu_add8(char* s, char* x, char* y, char* c, int off)
{
/* requires dword carry initialized */
print("# alu_add8\n");
debug_id();
print("movl $0, %%eax\n");
print("movl $0, %%ebx\n");
print("movl $0, %%ecx\n");
print("movl $0, %%edx\n");
print("movb (%s+%d), %%al\n", x, off);
print("movb (%s+%d), %%cl\n", y, off);
print("movl (%s), %%ebx\n", c);
print("movb alu_add8l(%%eax,%%ecx), %%dl\n");
print("movb alu_add8h(%%eax,%%ecx), %%dh\n");
print("movb alu_add8l(%%edx,%%ebx), %%al\n");
print("movb %%al, (%s+%d)\n", s, off);
print("movb alu_add8h(%%edx,%%ebx), %%al\n");
print("movb %%al, (%s)\n", c);
print("# end alu_add8\n");
}
进一步阅读