Writing and understanding inline assembly

Writing inline assembly in KCC is fairly simple.

Let’s say we have a function in plain z80 assembly called getRandom that returns a single random byte in register a (an apt example as such a function exists in the knightos kernel). How can we make this function accessible to C?

Well first, we need to have a C function. Since C functions are prefixed with an underscore (_) when turned into assembly, the name getRandom is perfectly valid.

In KCC (and SDCC, which KCC is derived from), functions that are written purely in inline assembly should be postfixed with __naked to indicate that the compiler shouldn’t “dress” the function with the bits of assembly it normally adds automatically.

At this point, the function should look like this:

unsigned char getRandom() __naked {
	
}

There are two styles you can use to write inline assembly in KCC. The first is the newer __asm__("inline assembly here")_ format. A simple example of this format is __asm__("nop"); which just injects a nop.

The second style is better suited to multiple lines, and is used like this:

__asm
sleep_forever:
	halt
	jr sleep_forever
__endasm;

To call the assembly function getRandom, you could just do the following:

unsigned char getRandom() __naked {
	__asm
	call getRandom
	__endasm;
}

There are, however, multiple small problems with this. First, the ret that is normally generated by KCC is part of the function’s “epilogue,” which is removed due to the __naked attribute. Just adding ret after call getRandom is enough to fix that though. A second issue is that this is KnightOS, so the call needs to be a pcall. The third and arguably most important problem is the KCC ABI, or Application Binary Interface. When a function in KCC returns a char (signage is irrelevant), it has to return it in the l register (16-bit values must be returned in hl). The asm getRandom function returns its value in register a!

Obviously, that means ld l, a is needed before returning.

However, there’s one more small issue: pcall is a macro KnightOS provides for assembly code only. To make pcalls in C bindings, you need to use a different macro, PCALL, which KnightOS provides for C.

Furthermore, all kernel functions, when used in C, need to be in all caps.

The final function, then, is as follows:

unsigned char getRandom() __naked {
	__asm
	PCALL(GETRANDOM)
	ld l, a
	ret
	__endasm;
}

The KCC libc uses the name get_random for the C binding to the getRandom asm function, but if you check system.c, you’ll see this:

unsigned char get_random() __naked {
	__asm
	PCALL(GETRANDOM)
	LD L, A
	RET
	__endasm;
}

Normal instructions (such as ld and ret) are not case-sensitive, so both functions are valid.

Next » « Back