If you needed to use a particular calling convention, inline assembly as understood by Clang (and GCC, since Clang is mostly compatible with GCC) is powerful enough to let you do that. It's useful to be somewhat aware of, even if it's not necessary here.
You should
read the GCC documentation on the features for all the details, but in short:
Code: __asm__(instructions
: outputs
: inputs
: clobbers);
You can write regular old instructions with a little bit of templating, and the other parameters allow you to tell the compiler what values the assembly consumes or generates, as well as the registers that it modifies.
For instance, the x86_64 ABI for syscalls on Linux passes the syscall number in rax, then parameters in other registers in a particular order; the first two are in rdi and rsi. The syscall result is returned in rax and the syscall will modify the values of rcx, r11 and may also modify memory. So this function implements that ABI for a two-argument syscall:
Code: uint64_t syscall2(uint64_t nr, uint64_t param1, uint64_t param2) {
uint64_t result;
__asm__("syscall"
: "={rax}"(result)
: "{rax}"(nr), "{rdi}"(a1), "{rsi}"(a2)
: "rcx", "r11", "memory"
);
return result;
}
While in this example fixed registers are chosen for the input/output constraints, you can also specify other values that allow the compiler to select a register for you, which may allow it to generate better code. For instance, adding two 8-bit values on Z80 (note that I haven't tested this and I'm not certain how the Z80 target's constraints are actually defined; this may not be totally correct):
Code: uint8_t add8(uint8_t x, uint8_t y) {
__asm__("add a,%1"
: "+{a}"(x)
: "r"(y)
);
return x;
}
In this example we've used +{a} to tell the compiler that x is both an input and output value, and constrained it to the a register because add requires that one operand of addition be in that register. The other operand is constrained to simply be in some register, and is referred to as %1 because it is the second value listed among inputs and outputs (it's zero-indexed).
Concretely, while your specific example of RunIndicOff is best done with the provided bindings, it could be implemented easily as inline assembly because it doesn't take any parameters, nor does it return anything.Code: __asm__ volatile("call 020848h");
This may not entirely be sufficient, since it doesn't tell the compiler that memory is modified; you could add a memory clobber to be very generic, or a more specific one that refers to the flag byte that gets modified. The volatile ensures the compiler doesn't remove this code, which it would otherwise be free to do because it doesn't seem to have any side effects: it doesn't return any value, so it seems safe to omit because nothing visibly uses (or indeed, can use) its result.
It's also important to note that the romcall assumes that IY points to the flags (so you may need to ensure it's set), whereas os_RunIndicOff will ensure that invariant is upheld for you.