Skip to content

Using C and assembly together

Andrey Eliseev edited this page Oct 2, 2026 · 21 revisions

In this guide you will learn how to include assembly code in your C projects.

Prerequisites

This guide assumes that you have a project set up as described in Setting up a project.

Introduction

Some code, like task switching, is impossible to write in C, and has to be written in assembly. Even when it's not strictly necessary, it is often useful to write code in assembly to ensure the best performance.

Suppose that we have a software multiplication algorithm written in Cocas assembly that we want to use in our C project:

rsect mul_i16

mul_i16>
    ldi r2, 0
    cmp r1, 0
    bz mul_i16_end
mul_i16_loop_body:
    shr r1
    bcc mul_i16_skip_add
    add r0, r2, r2
mul_i16_skip_add:
    shl r0
    cmp r1, 0
    bnz mul_i16_loop_body
mul_i16_end:
    move r2, r0
    rts

end.

There are several ways to do this.

Note

This is just an example. Do not use this in your project. cdm-builtins already provides a software multiplication algorithm which is called when you use the * operator in C.

Separate files

Aside from C, Clang can also recognize and process assembly code and produce regular object files. Create a new file in the src/algorithm directory. Name it arith.asm and put the assembly code in the new file.

Create a corresponding C header file named arith.h in src/algorithm with a prototype for the function. See CDM ABI for information on the calling convention used by the compiler.

int mul_i16(int a, int b);

Now modify your Makefile and add algorithm/arith.asm to the ASM_SOURCES variable, which holds the list of assembly source files.

ASM_SOURCES := algorithm/arith.asm

That's it. The steps are the same for both targets. For the ELF target, the contents of arith.asm would be slightly different. See LLVM targets for information on syntax differences.

Assembly code for the ELF target
.section .text.mul_i16, "ax", @progbits

.global mul_i16
mul_i16:
    ldi r2, 0
    cmp r1, 0
    bz mul_i16_end
mul_i16_loop_body:
    shr r1
    bcc mul_i16_skip_add
    add r0, r2, r2
mul_i16_skip_add:
    shl r0
    cmp r1, 0
    bnz mul_i16_loop_body
mul_i16_end:
    move r2, r0
    rts

Inline assembly

Another way to include assembly code in a C project is by integrating assembly snippets directly in C code with inline assembly blocks.

Define a regular C function and use an inline assembly block in its body:

int mul_i16(int a, int b) {
    int result;
    __asm__("ldi %[res], 0\n\t"
            "cmp %[b], 0\n\t"
            "bz mul_i16_end%=\n\t"
            "mul_i16_loop_body%=:\n\t"
            "shr %[b]\n\t"
            "bcc mul_i16_skip_add%=\n\t"
            "add %[a], %[res], %[res]\n\t"
            "mul_i16_skip_add%=:\n\t"
            "shl %[a]\n\t"
            "cmp %[b], 0\n\t"
            "bnz mul_i16_loop_body%=\n\t"
            "mul_i16_end%=:"
            : [res] "=r"(result), [a] "+r"(a), [b] "+r"(b));
    return result;
}

Inline assembly blocks have input and output parameters. There are different types of parameters, but the most common type is "r", which binds a compiler-allocated register to a local variable. In this example, there is an output register named [res] bound to the variable result, and two in/out registers also bound to variables. These registers are referenced inside the assembly block with %[res], %[a] and %[b].

All labels are suffixed with %=. This pattern expands to a unique identifier for the current instance of the inline assembly block. This is a requirement for inline assembly blocks, because the compiler may insert them multiple times, causing a name conflict. With ELF target, you can also use local numeric symbol names.

For more information on inline assembly blocks, see Extended Asm in GCC docs.

Note

When a function body consists of a single inline assembly block (like in this example), it makes sense to make it a naked function by applying __attribute__((naked)). You will have to write your own prologue/epilogue sequences in assembly, according to the ABI. See Common Attributes in GCC docs.

Clone this wiki locally