what does the colon in asm volatile() mean

i’m not sure if i accidently modified the code a bit, but here it is:-

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 what is the colon, the “dN’, the “a”, the “%0” and “%1”, why are those variables “port” and “data” in brackets and what is the difference between “%[reg]” and “%%[reg]” so that i can solve future problems like these.


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).)


Read the manual for the syntax, obviously. 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.

You have "dN"(port) in the outputs section, but it’s actually an input (no = or +). And yes, the I/O port number should be an input: it’s something the asm statement needs to get from the compiler, not something the asm statement is providing back to the compiler.

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, and 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.