下面的代码应该计算几何级数(其比率等于2),并且应显示10个意甲联赛。基准输入编号必须以2的幂选择(在所选编号为7的情况下)。有一些实施要求:
I)所有字符串值必须从基本位置X *(X 100 乘以100),则需要偏移量以接收所有32位值。
II)除数据存储器在RX寄存器中的最后位置外,还应存储最终值。
III)程序应检查值是否小于2,147,483,648级数(1000000000000000000000000000000)2
IV)除了已经可以根据需要使用的ARM unicycle中已经实现的指令之外,还必须一次实现TST指令,LSL和CMP。
START
AND R5, R5, #0 ;Reset Registers
AND R7, R7, #0
AND R0, R0, #0
AND R4, R4, #0
ADD R5, R5, #1 ;R5 receives de base number of GP
ADD R7, R7, R5 ;R7 = register of reference number
ADD R0, R0, #200
ADD R0, R0, #200
ADD R0, R0, #200
ADD R0, R0, #100 ;sets the memory base position in 700
ADD R4, R4, #1 ;starts the count
STR R7, [R0] ;saves the first GP value in the memory
LOOP
TST R7, #2147483648 ;check if GP values is higher than 2^31
BNE FIM ;if so, ends the code
LSL R7, R7, #1 ;else, multiply by 2
ADD R4, R4, #1 ;increments the count
ADD R0, R0, #4 ;increments memory adress
STR R7, [R0] ;saves value in the memory
CMP R4, #10 ;check if 10 interations have been executed
BEQ FIM ;if so, ends the code
B LOOP ;else, restarts loop loop
END
由于我是ASM编程的“新手”,所以我想知道,在遵守上述四个要求的前提下,可以对本代码进行哪些改进?任何改变都是好的,最好的学习方法是,以不同的角度看待它吗?预先感谢
答案 0 :(得分:2)
您的循环分支应为cmp / bne loop
,因此您可以结束循环。这意味着循环内少了一条分支指令。参见Why are loops always compiled into "do...while" style (tail jump)?。
此外,请利用已经需要的指令设置标志,而不要使用单独的TST或CMP指令。
如果要使用与输出指针分开的计数器,请将其向下计数至零,以便可以subs r4, r4, #1
/ bne
。
您的代码中有很多遗漏的优化,尤其是您在寄存器中创建常量的疯狂方式。 ARM具有真实的mov
指令;用它代替与或与零相加。
看看一个好的C编译器会做什么:编译器输出通常是优化的一个很好的起点,或者是一种学习目标机器技巧的方法。(另请参见How to remove "noise" from GCC/clang assembly output?和Matt Godbolt的CppCon2017演讲“What Has My Compiler Done for Me Lately? Unbolting the Compiler's Lid”。)
您的版本存储第一个元素而不检查其高位,因此,如果输入仅设置了高位,则将存储另外9个零。 IDK是您想要的,还是不需要处理的情况。 (即,可能保证您输入的内容是非负号)。
// uint32_t would be more portable, but ARM has 32-bit unsigned int
void store_sequence(unsigned val)
{
unsigned *dst = (unsigned *)700;
unsigned *endp = dst + 10;
// val = 1; // use the function arg so it's not a compile-time constant
for (; dst < endp; dst++) {
*dst = val; // first store without checking the value
val <<= 1;
if (val >= (1UL << 31))
break;
}
}
在移位后立即检查val
会得到很好的asm:否则,编译器并不总是利用移位来设置标志。请注意,即使是for()
循环,编译器也可以在第一时间证明条件为真,并且无需在顶部添加额外的check /分支来查看循环是否应该运行零次。 / p>
我将此代码放在the Godbolt compiler explorer上,以获取ARM的gcc和clang输出。
gcc7.2.1 -O3
完全展开了循环。随着次数的增加,它最终决定进行循环,但是展开的循环很有趣:如果完全展开,则不需要指针增量。使用不同的移位计数来重新移位原始对象也会创建指令级并行性(CPU可以并行运行多个移位指令,因为不依赖于上一个移位指令的结果。)
请注意,lsls
设置了移位标志,ARM的标志包括N
标志,如果设置了结果的高位,则该标志会被设置。如果N==1
,则MInus条件为true。名称来自2的补数负数,但所有内容都只是位,您可以使用它在高位上分支。 (PLus条件的名称很奇怪:对于包含零的非负结果,它是正确的,即,它仅检查N==0
。https://community.arm.com/processors/b/blog/posts/condition-codes-1-condition-flags-and-codes)
编译器决定使用谓词bmi
,而不是实际的bx lr
(如果为负则分支)。即返回MInus,否则以NOP运行。 (使用-mcpu=cortex-a57
会导致bmi
到循环的底部,并在其中带有bx lr
。显然,该微体系结构的调整选项使gcc避免了谓词bx
的指令。)
@ On function entry, val is in r0. Use mov r0, #1 if you want
@ from gcc7.2.1 -O3
store_sequence:
mov r3, #0 @ this is the most efficient way to zero a reg
lsls r2, r0, #1 @ instruction scheduling: create r2 early
str r0, [r3, #700] @ gcc just uses offsets from a zeroed reg
bxmi lr @ if(val<<1 has its high bit set) return;
lsls r1, r0, #2
str r2, [r3, #704] @ 2nd store, using val<<1 after checking it
bxmi lr
lsls r2, r0, #3 @ alternating r1 and r2 for software pipelining
str r1, [r3, #708] @ 3rd store, using val<<2 after checking it
bxmi lr
...
要获得汇总循环,可以增加循环数,或使用-Os
进行编译(针对代码大小进行优化)。
使用endp = dst+100
和gcc -O3 mcpu=cortex-a57
(以避免bxmi lr
),我们得到了一个有趣的循环,该循环是通过跳入中间而进入的,因此它可以掉入底部。 (在这种情况下,仅让cmp
/ beq
运行第一个迭代,或者将cmp / bne放在底部,-Os
会更有效)。
@ gcc -O3 -mcpu=cortex-a57 with loop count = 100 so it doesn't unroll.
store_sequence:
mov r3, #700
movw r2, #1100 @ Cortex-A57 has movw. add would work, too.
b .L3
.L6: @ do {
cmp r3, r2
beq .L1 @ if(p==endp) break;
.L3: @ first iteration loop entry point
str r0, [r3]
lsls r0, r0, #1 @ val <<= 1
add r3, r3, #4 @ add without clobbering flags
bpl .L6 @ } while(val's high bit is clear)
.L1:
bx lr
有了-Os
,我们得到了一个更好看的循环。唯一的缺点是bmi
(或bxmi lr
)在lsls
设置标志后立即在下一条指令中读取标志。不过,您可以在它们之间安排add
。 (或者在“拇指”模式下,您想这样做,因为adds
的编码比add
短。)
@ gcc7.2.1 -Os -mcpu=cortex-a57
store_sequence:
mov r3, #700 @ dst = 700
.L3: @ do{
str r0, [r3]
lsls r0, r0, #1 @ set flags from val <<= 1
bxmi lr @ bmi to the end of the loop would work
add r3, r3, #4 @ dst++
cmp r3, #740
bne .L3 @ } while(p != endp)
@ FIM:
bx lr
对于较大的endp
,它不适合cmp
的立即数操作,则gcc会在循环外的reg中对其进行计算。
它始终使用mov
,或者从内存中的文字池中加载它,而不是使用add r2, r3, #8192
或其他东西。我不确定我构造的情况是add
的立即数是否有效,而movw
的立即数却无效。
无论如何,常规mov
适用于小的立即数,但是movw
是一种较新的编码,不是基线,因此当您使用movw
进行编译时,gcc仅使用-mcpu=
有它。
答案 1 :(得分:1)
这看起来像是作业,所以我只给您一些提示。
那么,您需要在FIM的某个地方贴上标签。我假设它在代码的结尾。
强制寄存器为零,然后向其添加小的立即数,看起来像MIPS代码。了解哪些值可用作立即数,并研究MOV和MOVW指令。
在无条件分支周围有一个条件分支的编程很差,请替换为一个条件分支。
了解有关STR指令的更多高级选项,因此您无需额外的指令即可调整地址指针。