0

I'm working on STM32CubeIDE.

To mix C and arm assembly, we initially used EXPORT.

Below is main.c:

#include <stdio.h>

extern int calc(int num, int* cnt);
int main()
{
    int cnt = 5;
    calc(4, &cnt);
    return 0;
}

And then calc.s:

AREA calculator, CODE
EXPORT calc
ALIGN
calc:PROC
    PUSH {r4,r5,lr}
    MOV r5, #13
    UDIV r4, r0, r5
    MUL r5, r4, r5
    SUB r4, r0, r5
    CMP r4,#0
    BEQ ace
    CMP r4,#8
    BGT jqk
then:   ADD r4, r4, #1
    B exit
ace:ADD [r1],#1
    MOV r4, #11
    B exit
jqk:MOV r4, #10
exit:MOV r0, r4
    POP {r4, r5, pc}
    ENDP

I put the two files in the same place and built main.c, but I get an error that says it's an unknown external reference.

So after giving up, I tried to put the ASM sentence in the c file using inline assembly.

calc    PROC
    PUSH    {r4, lr}
    AND     r4, r0, #12
    CMP     r4, #0
    BEQ ace
    CMP r4, #8
    BGT jqk
then    ADD r4, r4, #1
    B   exit
ace ADD r1, r1, #1
    MOV r4, #11
    B   exit
jqk MOV r4, #10
exit    MOV r0, r4
    ENDP

I've written these assembly codes, and I've adapted them to inline grammar. reference : Labels in GCC inline assembly

int calc(int num, int *cnt)
{
    int rst=0;
    int c = *cnt;
    __asm volatile("MOV R0, %0": :"r"(num));
    __asm volatile("AND R0, R0, %0": :"r"(0x12));
    __asm volatile("MOV R1, %0": :"r"(c));
    __asm volatile("CMP R0, #0");
    __asm volatile("BEQ ace%=");
    __asm volatile("CMP R0,#8");
    __asm volatile("BGT jqk%=");
    __asm volatile("ADD R2, R0, #1");
    __asm volatile("B exit%=");
    __asm volatile("ace%=: ADD R1, R1, #1");
    __asm volatile("MOV R2, #11");
    __asm volatile("B exit%=");
    __asm volatile("jqk%=: MOV R2, #10");
    __asm volatile("exit%=: LDR %0, R2":"=r"(rst):);
    __asm volatile("LDR %0, R1":"=r"(c):);
    __asm volatile("POP {R0, R1, R2}");
    *cnt = c;
    return rst;
}

But even in this case, an error appears. What should I change in my code?

'''

int calc(int num, int *cnt)
{
    int tmp = num % 13;
    if (tmp == 0)
    {
        tmp = *cnt;
        *cnt = tmp+1;
        return 11;
    }
    else if (tmp > 8)
        return 10;
    else
        return tmp + 1;
}

'''

  • 4
    This is really, really not at all how you write inline asm in GCC. You cannot modify specific registers without notifying the compiler via a clobber, and you cannot expect that register contents are preserved between successive asm statements. It is far more than "grammar". See the notes at https://stackoverflow.com/tags/inline-assembly/info to get started, and in particular https://gcc.gnu.org/wiki/DontUseInlineAsm for cautions which must be taken seriously. – Nate Eldredge Jun 09 '22 at 13:32
  • 5
    Also, never say “an error appear.” Instead, post the exact error message you received. Chances are the problem is wrong symbol decoration. Try naming your function `_calc` in the assembly file (but `calc` in the C file). Also make sure the assembly object is actually linked into the binary. – fuz Jun 09 '22 at 13:33
  • 3
    To do this properly, the entire code needs to be in a single inline asm statement, with all its inputs, outputs and clobbers carefully declared. It is up to you to get it right, as the compiler does not check. If you should accidentally omit one, the code may very well "work fine"; until someday changes in the surrounding code, or in the compiler itself, cause that register to be used in a different way, and then you get very severe and subtle bugs. Inline asm is a huge footgun, even compared to the rest of C which is a pretty big footgun to begin with. – Nate Eldredge Jun 09 '22 at 13:36
  • 3
    I actually don't understand why you are writing this in assembly to begin with. It doesn't seem to do anything except simple arithmetic, so why not just write it in C? A modern compiler will probably generate assembly that is very close to optimal. And if it is suboptimal, to the extent that it meaningfully impacts performance, there can be ways to optimize at the level of C without resorting to assembly. – Nate Eldredge Jun 09 '22 at 13:38
  • 3
    Inline assembly's main purpose is to wrap single or a few assembly instructions as an *intrinsic* function/macro like the Intel SIMD intrinsics. Anyway your first method should work, and it is the normal way to do it. I'd repeat what fuz said. Show us the exact error message of your first try, and also check the object file whether the necessary code is actually linked with the correct symbol reference. – xiver77 Jun 09 '22 at 13:45
  • @NateEldredge I'd like to defend the OP in using assembly in this case because you can always learn by trying something and solve whatever goes wrong, whether or not the task has practical value. – xiver77 Jun 09 '22 at 13:51
  • 2
    @xiver77: I certainly support learning, but trial-and-error is not a good way to learn this particular skill, for the basic reason that it is not easy to tell whether or not there *is* an error. Instead one has to start by reading the documentation very carefully and in its entirety. And the GCC inline asm documentation is not an easy read; it assumes a strong understanding of CPU architecture in general, your own architecture in particular, and compiler backend design principles. – Nate Eldredge Jun 09 '22 at 13:58
  • @NateEldredge Thanks. I cannot disagree. But since programming is basically applying a set of patterns repeatedly, a lot can be learned and achieved by simply writing code until it works, even for (inline) assembly. Still, I do agree what you wrote is the correct approach. – xiver77 Jun 09 '22 at 14:37
  • 1
    @xiver77: Yes, trial and error can be *part* of learning (inline) asm, but it must not be the only thing. Even more than C in general, inline asm has a huge amount of room for undefined behaviour that happens to work. So lack of error in a particular test case does *not* indicate correctness. For example, look at the last code block in this question. You can't safely jump from one inline asm statement into the middle of another, and you can't rely on the compiler not to step on registers between asm statements. (And you can't write regs at all without clobbers or output operands). – Peter Cordes Jun 09 '22 at 18:25
  • What compiler and assembler are you using? Your first code with `proc` stuff looks like MASM syntax; is that what ARMasm uses? Also, what's `ADD [r1],#1` supposed to do? ARM is a load/store machine; you can't use memory operands for instructions other than load/store like LDR/STR. If your asm didn't assemble, no wonder the build couldn't link with it. Also, what's this supposed to do? One version does `x % 13` I think (inefficiently, not using ARM's multiply-subtract instruction), but the other does `AND r4, r0, #12`. `x % n` = `x & (n-1)` only works for power-of-2 `n`. – Peter Cordes Jun 09 '22 at 18:33
  • @Peter Cordes : ADD [r1], #1 -> I put in the int parameter to use as a pointer. (I wrote additional c functions in the body.) It is written [r1] simply because the address of r1 is added. Do you happen to know the correct notation for this? I work on STM32CubeIDE and use stm32L4 board and arm cortex-m4. – Jo sunghoon Jun 10 '22 at 04:32
  • And the reason why I wrote MUL, UDIV, and SUB without AND in the text is because when I wrote AND, the following error phrase came out. ../Core/Src/calc.s:6: Error: unshifted register required -- `and r4,r0,#12' – Jo sunghoon Jun 10 '22 at 04:37
  • @Nate Eldredge : The reason for mixing C and assembly is simply because it is a project in a class with instructions to use mixing. But it doesn't work. – Jo sunghoon Jun 10 '22 at 04:39
  • `add` can't use a memory destination, like I said. Look at optimized compiler output if you don't know how to write something in ARM assembly yourself, or can't think of a good way to make it efficient. https://godbolt.org/z/nrvsf4beb. Also, you can't do `tmp = num % 13` using AND. You may need `.syntax unified` for some of that to assemble correctly, or the equivalent directive for ARMasm. (Godbolt filters compiler directives by default, but to get the full GAS directives, uncheck that in the dropdown. Or for the compiler you're using locally, look at its asm output for your C function.) – Peter Cordes Jun 10 '22 at 04:45
  • @Peter Cordes : I looked it up and solved the problem! Thank you. – Jo sunghoon Jun 10 '22 at 06:14

1 Answers1

2

You actually did the right thing initially, putting the asm code in a separate .s file and not using inline asm at all. The only thing is that you need to explicitly compile and link the calc.s file along with main.c

cc -o program main.c calc.s

should compile and assemble both files and link them. If you're using an IDE, you need to specify both main.c and calc.s as source files of the project.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • If your `cc` is GCC, it won't assemble the asm source in the question. That's some other syntax, perhaps ARMasm. And `ADD [r1],#1` is probably an error even for ARMasm, so I could imagine the build just failed but tried to link the object files that were available, thus not including one built from the asm. But without a [mcve] with error messages, we don't know. – Peter Cordes Jun 09 '22 at 19:21
  • True, if the OP is using the wrong tooling for their target, things won't work. But the statements they "built main.c" and got "unknown external reference" suggests that the problem is they failed to include calc.s in their build. My main point is that inline asm is the wrong approach -- what they are doing should be done with a separate asm file – Chris Dodd Jun 09 '22 at 22:27
  • True enough; it's unlikely that a build system would try to link at all if some of the `.o` files couldn't be built. So this is a good guess at what's wrong. Totally agreed that inline asm is the wrong approach, especially if you don't understand how it's designed to be used, like naively slapping each instruction into a separate asm statement, with or without constraints to describe them accurately to the compiler. – Peter Cordes Jun 09 '22 at 22:30
  • ../Core/Src/calc.s: Assembler messages: ../Core/Src/calc.s:1: Error: bad instruction `area calculator,CODE' ../Core/Src/calc.s:2: Error: bad instruction `export calc' ../Core/Src/calc.s:3: Error: bad instruction `align' ../Core/Src/calc.s:4: Error: bad instruction `proc' ../Core/Src/calc.s:16: Error: ARM register expected -- `add [r1],#1' ../Core/Src/calc.s:22: Error: bad instruction `endp' make: *** [Core/Src/subdir.mk:46: Core/Src/calc.o] Error 1 "make -j16 all" terminated with exit code 2. Build might be incomplete. – Jo sunghoon Jun 10 '22 at 04:21
  • This is the error that appears in the first code. Error in calc.s file, STM32CubeIDE is being used I built the entire project, but I can't build it with those words. – Jo sunghoon Jun 10 '22 at 04:21
  • Your asm code appears to be the wrong flavor for whatever assembler you're using. – Chris Dodd Jun 10 '22 at 17:38