Search code examples
c++assemblygccinline-assembly

what does the colon in asm volatile() mean


i'm not sure if i accidently modified the code a bit, but here it is (i'm a beginner to inline assembly, but quite used to assembly in a different file):-

void out8(uint16 port, uint8 data) {asm volatile("outb %0, %1" : "dN"(port) : "a"(data));}
void out16(uint16 port, uint16 data) {asm volatile("outw %0, %1" : "dN"(port) : "a"(data));}
void out32(uint16 port, uint32 data) {asm volatile("outl %%eax, %%dx" : "dN"(port) : "a"(data));}

previously it gave no error but now it is. but can anyone correct this code? and also, tell me on what basis is the colon separating the values, the "dN" and the "a" in the colon separated areas, the "%0" and "%1" in the inline assembly, why are those variables "port" and "data" in brackets next to the "a" and "dN" and what is the difference between "%[reg]" and "%%[reg]" so that i can solve problems like these later when i get them. (tl;du (u stands for understand) the manual page for inline extended assembly is japanese (you know what i mean, right?))


[SOLVED]

used :-

void out(uint16 port, uint8 data) {asm volatile("outb %1, %0" :: "dN"(port), "a"(data));}
void out(uint16 port, uint16 data) {asm volatile("outw %1, %0" : : "dN"(port), "a"(data));}
// Warning, this one's still unsafe, even though it compiles
void out(uint16 port, uint32 data) {asm volatile("outl %%eax, %%dx" : : "dN"(port), "a"(data));}

(Warning to future readers: outl still has a bug, see the answer(s).)


Solution

  • The manual documents the syntax (and much other info; it's worth reading these days since it's gotten a lot of improvements over the years). https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html.

    asm asm-qualifiers ( AssemblerTemplate 
                     : OutputOperands 
                     [ : InputOperands
                     [ : Clobbers ] ])
    

    See also https://stackoverflow.com/questions/tagged/inline-assembly for links to guides other than the official docs. Especially https://gcc.gnu.org/wiki/DontUseInlineAsm which describes some pitfalls.


    You have "dN"(port) in the outputs section, but it's actually an input (no = or + : docs). And yes, the I/O port number should be an input: it's something the asm statement needs to get from the surrounding code of your program, not something the asm statement is providing back to your program. So the compiler needs to know how to provide it to the asm statement, not collect it.

    If you're confused by the fact that out has two "inputs", think of it like a store instruction. Two pieces of data come from the CPU (the data and the address), resulting in a store to memory. Unlike a load or in where the load instruction writes a register, in which case the compiler needs to know where that result is placed.

    You also have the %0, %1 operands in the wrong order for the order of the constraints, assuming this is for AT&T syntax (not gcc -masm=intel). Named constraints like [port] "dN" (port) to match %[port] in the template would avoid that. https://www.felixcloutier.com/x86/out is out dx/imm8, AL/AX/EAX in Intel syntax, or out %al/%ax/%eax, %dx/$imm8 in AT&T.


    Also, you separately broke out32() in another way.

    A "dN" constraint allows the compiler to pick DX or an immediate for that variable (if its value is known at compile time and is small enough).

    But your asm template string doesn't reference that %0 first operand, instead hard-coding the %%dx register name, which is only correct if the compiler picks DX.

    An optimized build that inlines out32(0x80, 0x1234) would not have put the port number in DX with preceding instructions, instead picking the N (unsigned 8-bit) constraint because it's cheaper. But no $0x80 gets filled in anywhere in the final compiler-generated asm because there's no %0 in the template for the compiler to expand.

    Think of asm template strings like printf format strings that the compiler expands, before passing on the whole asm to the assembler. (Including compiler-generated instructions before and after, from compiling other C statements, and for some constraints like "r" or "d" to put a C variable or expression's value into a register if it wasn't already there.)

    %% is just a literal %, so if you want to hard-code an AT&T register name like %eax, you use %%eax in the Extended Asm template.

    You can see that asm on https://godbolt.org/. (Use "binary" mode to see if the resulting compiler-generated asm will actually assemble. That's not guaranteed when using inline asm.)

    For working outb / etc. macros, many codebases define them, and I think some libc implementations have inline wrappers, like maybe MUSL, maybe also glibc. If you just want working code, don't try to write your own when you don't know inline asm.

    Related: