我正试图在Y86-64上进行正确的转变
要做左移,我知道我需要乘以2 ^ n,其中n是我们想要的位移数,例如,如果我们想要移位4,它是2 ^ 4 = 16并且做一个加法循环来执行乘法在它上面,但我不知道该如何做正确的转变,我认为我需要进行分工,但不确定如何处理
pcount_do:
movq $0, %rax
.L2: movq %rdi, %rdx
shrq %rdi
ret
鉴于Y86的指令集错过了移位和划分,我会寻找与此C代码等效的东西:
uint64_t lut[] = {
1,
2,
4,
8,
16,
32,
64,
128,
256,
512,
1024,
2048,
4096,
8192,
16384,
32768,
65536,
131072,
262144,
524288,
1048576,
2097152,
4194304,
8388608,
16777216,
33554432,
67108864,
134217728,
268435456,
536870912,
1073741824,
2147483648,
4294967296,
8589934592,
17179869184,
34359738368,
68719476736,
137438953472,
274877906944,
549755813888,
1099511627776,
2199023255552,
4398046511104,
8796093022208,
17592186044416,
35184372088832,
70368744177664,
140737488355328,
281474976710656,
562949953421312,
1125899906842624,
2251799813685248,
4503599627370496,
9007199254740992,
18014398509481984,
36028797018963968,
72057594037927936,
144115188075855872,
288230376151711744,
576460752303423488,
1152921504606846976,
2305843009213693952,
4611686018427387904,
9223372036854775808};
uint64_t rshift(uint64_t source, int amount) {
uint64_t result = 0;
for(int i = amount; i < 64; ++i) {
if(source & lut[i]) result |= lut[i-amount];
}
return result;
}
只需添加/ sub /和/或加上查找表,这一切都应该可行。
如果我们想变得更聪明,正如@PeterCordes建议我们可以使用8个条目查找表并处理整个字节,但这需要比循环每个位更多的簿记。
---更新---
@PetreCordes正确地指出查找表实际上是无用的,因为我正在循环比特,所以使用和来计算下一个2的幂是微不足道的:
uint64_t rshift(uint64_t source, int amount) {
uint64_t result = 0;
uint64_t read_bit = 1;
uint64_t write_bit = 1;
for(int i = 0; i < amount; ++i) read_bit = read_bit + read_bit;
for(int i = amount; i < 64; ++i) {
if(source & read_bit) result |= write_bit;
read_bit = read_bit + read_bit;
write_bit = write_bit + write_bit;
}
return result;
}
与Matteo演示一样,您可以一次循环一位,在一个位置读取并在另一个位置写入位。
Matteo的答案通过移动一个掩码来读取一个可变位置,并在一个以锁定步骤移动的位置写入,从寄存器的底部开始(移动另一个掩码)。
读取输入的MSB更容易,然后左移输入左移输入add same,same
并重复。所以我们读取从顶部位开始的位,并从其MSB开始构造结果。 (我们一次将1位移位到目的地,ADD到左移,有条件地添加以设置新的位位置。)
我们可以使用2的补码签名比较来读取寄存器的最高位。如果它设置,x < 0
,否则它不是。
x86和y86有一个名为SF的标志,它根据结果的MSB(ALU操作)设置。 x86有js
/ cmovs
/ sets
指令直接检查SF
条件。 y86只有jl
/ jge
和其他签名比较条件来检查SF!=OF
,所以我们需要对零进行额外的比较以清除OF(x - 0
不能溢出)。
或者在语义上,实际上与零进行比较而不是仅仅读取SF。 (除了我们can optimize compare-against-zero into andl %eax,%eax
or andq %rax,%rax
,如果你的y86版本没有sub-immediate.Y86也缺少x86的非破坏性test
和cmp
指令,如and
和sub
,但只写标记。)
移植到86-64应该是微不足道的。 (更改reg名称,32变为64)。
测试案例:0x12345 >> 1 = 0x000091a2
。 (我没有看到一种方法来永久链接该网站上的代码,就像Godbolt编译器浏览器所允许的那样。)
# constant input test case
irmovl 0x12345, %eax
# irmovl 3, %ecx # this could trivial handle variable counts, but doesn't.
# start of right-shift block:
# input: EAX = number to be shifted
# output: EDX = number >> 1
# clobbers: EAX, ECX, EDI. (EDI=1 to work around lack of add-immediate)
xorl %edx, %edx # dst = 0. like # irmovl $0, %edx
irmovl 1, %edi # y86 is missing immediate add?
# shift 32-n bits from EAX into the bottom of EDX
# one at a time using SF to read them from the MSB
irmovl 31, %ecx # hard code count = 32 - 31
# or calculate this as 32 - count with neg / add or equivalent
rshift: # do {
addl %edx, %edx # dst <<= 1
andl %eax, %eax # compare against zero because y86 is missing js / cmovs that tests just SF
jge MSB_zero # jge = jnl = not lower
xorl %edi, %edx # edx ^= 1. y86 is missing OR? low bit = 0 so we can ADD or XOR to set it
MSB_zero:
addl %eax, %eax # src <<= 1
subl %edi, %ecx
jne rshift # }while(--ecx); # semantically jnz
halt # result in EDX
#shr $1, %eax
我使用了xor-zeroing,因为y86模拟器组装成可变长度的机器代码,如x86。 (所以irmovl 0, %edx
的效率会降低)。
或者用CMOVL从EAX的MSB到EDX的LSB进行无分支进位
# loop body:
addl %edx, %edx # dst <<= 1
xorl %esi, %esi # esi = 0
sub %esi, %eax # src -= 0 to set flags
cmovl %edi, %esi # esi = (src<0) ? 1 : 0 = MSB of EAX
addl %esi, %edx # copy the bit into EDX (can't carry to higher bits)
addl %eax, %eax # src <<= 1
如果您的y86模拟器模拟分支错误预测的性能损失,请使用此方法。否则分支是较少的指令。
或者,如果您关心性能,则应该可以一次使用查找表来查找整个字节,并在字节边界上使用修正。
但是没有左移来有效地组装单独的字节,你需要为每个字节位置单独的256项qUT的qwords!或者从偏移量加载然后屏蔽掉“垃圾”字节。
哦,你需要一个正确的移位来从qword中提取字节以提供数组索引。如果y86可以执行字节加载,那么您可以将输入整数存储到内存中,并一次重新加载1个字节。或者再次使用未对齐的qword加载模拟字节加载,使用0x00...0FF
模拟AND以隔离寄存器底部的字节。
哦废话,但我们有运行时变量计数的鸡/蛋问题。我们需要count / 8
作为字节偏移量,因为一个字节中有8位。但是计数很小,所以我们可以使用重复减法循环。 (您可能希望AND
使用0x3f或0x1f(取决于操作数大小)将计数包装在64或32,就像x86硬件转换那样。这样可以避免将内存索引到正确范围之外且计数太大。)
无论如何,你可以通过向上舍入(移出太多位)来扩展它以处理不是8的倍数的右移计数,然后将所需的位一次又一次地放回到第一部分中的循环中。这个问题。 (在未对齐的负载之后将这些位置于寄存器的顶部。)
或者也许使用Matteo的方法来行走面具,使用LUT作为起点。但是如果我们已经在进行字节移位的存储/未对齐重载,那么另一个重载可能就好了。我们可以计算相对于第一个未对齐重载的正确偏移量,即4或8个字节之前,因此起始MSB是刚刚低于第一个负载的最低位的位。