Hi,
bewing wrote:I haven't taken my design quite to your extreme, yet -- and I'm not quite sure I'll go that far. Considering that ecx, edi, esi, eax, edx (and to some extent ebp) have "special uses" in intel CPUs -- I don't really want to just assign "input #4" to one of them, forever.
For special uses, even though ECX and EDX are useful (I/O port access and LOOP/JECXZ) they're trashed by SYSENTER/SYSEXIT and need to be reserved. For string instructions (EAX, ESI and EDI), most string instruction are slower than similar code implemented as smaller instructions (except for string copy, like REP MOVSD) and the cost of shifting things between registers (if/when necessary) is relatively small compared to the CPU's setup costs. That only really leaves EBP (used for stack frames in high level language), but it's not too hard to use EBP from inline assembly within high level language code without problems and most kernel functions don't need more than 4 input or output parameters (and therefore don't need EBP).
bewing wrote:Since there is usually something like an optimal register ordering for any particular kernel function, I'll be content with the other method, of just creating a per-function translation between a stack-oriented calling structure, and each assembly-based kernel function.
That would work too, but the most efficient place for this per-function translation between stack and registers is within the libraries used by the high level languages. This is partly because the libraries usually need to more than just the low-level kernel function call, and partly because the compiler can optimise register usage so things are in the right registers before the function call anyway.
bewing wrote:As far as returns go, however -- I think there is an important addition to look at. The one huge advantage that assembly routines have over traditional high-level languages is that you can return many values (up to 7 values plus 2 testable flag bits on 32bit intel!) as the "result" of a function. Every high level language that I've seen limits the # of explicit returns to one.
IMHO this is what makes most high level languages inherently broken. Consider C, where programmers use "pointers to output parameters" as input parameters, which can make it hard to tell the difference between input parameters and output parameters (and can also increase overhead). For an example, compare "status = foo(&a, &b, &c);" to something like "(status, a, b) = foo(&c);". For the normal C code it's hard to tell that "a" and "b" are actually output parameters.
bewing wrote:Which leads to my point: I think it is possible, and reasonable, to ALWAYS return a 2nd, implicit value from all defined and undefined kernel calls -- when doing a stack-based return to a high level language. The first return would be the expected return value. The second return is always a pre-defined system error code -- which can include "_UNDEFINED_FUNCTION." It seems to me that this extends some needed assembly functionality a little further into the high level language realm.
This is a little annoying to implement in assembler, of course. You would probably have to pre-define a particular register as always holding the system error code. This sounds like an extension to what you are doing with eax?
That is exactly what I do - EAX always returns a standardised status code (0 = OK, 1 = function undefined, 2 = bad input parameters, 3 = permission denied, etc).
bewing wrote:Obviously, supporting this requires tweaking the calling/return strucure of every compiler that gets loaded on your OS, and those "ugly" assembly stubs -- but I think it's worth it, for the extra benefit of permanently separating error codes from return values, and allowing the use of both in all situations.
That's another reason why the most efficient place for the per-function translation between stack and registers is within the libraries used by the high level languages. For example (for C) the kernel can return 2 parameters (the status code and something else) and the library can store the status output parameter into "errno" and only return one parameter to the caller. For example:
Code: Select all
_open:
push ebx
push esi
mov ebx,[esp+4]
mov esi,[esp+8]
mov eax,0x00001234
int 0x80
test eax,eax
je .worked
mov [_errno],eax
mov eax,-1
pop esi
pop ebx
ret
.worked:
mov eax,ebx
pop esi
pop ebx
ret
Of course you'd want to define this as a macro in a header file instead, so that the optimiser can do it's thing...
The same thinking applies to other functions - for example, the kernel could return many parameters and the library could put most of them into a structure or something. It's also "high level language neutral" as it's not designed for any specific high level language calling convention (and doesn't penalise languages that use other calling conventions) - the code needed in a C library isn't much different to the code needed in a PASCAL library (even though both libraries might do completely different things with input and output parameters).
The other nice thing is that an assembly programmer can skip all the "fluff" and directly use multple return parameters without messing about with the stack, but it doesn't really increase overhead for high level languages (assuming that the low level kernel API calls are done as inline assembly macros that are optimised by the compiler).
Cheers,
Brendan