汇编ARM编程/ Monocycle处理器指令

时间:2018-07-01 14:47:15

标签: assembly arm processor

下面的代码应该计算几何级数(其比率等于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编程的“新手”,所以我想知道,在遵守上述四个要求的前提下,可以对本代码进行哪些改进?任何改变都是好的,最好的学习方法是,以不同的角度看待它吗?预先感谢

2 个答案:

答案 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==0https://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指令的更多高级选项,因此您无需额外的指令即可调整地址指针。