Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 28 Oct 1995 05:03:48 +1000
From:      Bruce Evans <bde@zeta.org.au>
To:        bde@zeta.org.au, terry@lambert.org
Cc:        CVS-commiters@freefall.freebsd.org, bde@freefall.freebsd.org, cvs-sys@freefall.freebsd.org, hackers@freebsd.org, swallace@ece.uci.edu
Subject:   Re: SYSCALL IDEAS [Was: cvs commit: src/sys/kern sysv_msg.c sysv_sem.c sysv_shm.c]
Message-ID:  <199510271903.FAA24658@godzilla.zeta.org.au>

next in thread | raw e-mail | index | archive | help
>> >I think using registers for calls and inlining are antithetical.  You
>> 
>> Calls to inline functions don't use the standard calling convention.

>Calls to system calls *must* use *some* calling convention agreed upon
>in advance and invariant under optimization, or it will be indeterminate
>under optimization.  This is obvious.

I was talking about inline functions in the kernel.

>> >> This may be true if you control the ABI.
>> 
>> >You *do* control the ABI.  You are either running a known ABI paradigm
>> >(ie: int push, sizeof(int) == sizeof(register_t), etc.), or you are
>> >running a compatability ABI, in which case you know at binary load time
>> 
>> You know it but you don't control it.

>Excuse me?  You are attempting to assert exactly that control on the
>Intel ABI and you are arguing that it can't be done?

I was talking about ABI-decoding functions in the kernel (until this
discussions got sidetracked into talking about changes to the FreeBSD
ABI that we control (FreeBSD != ibcs2)).

>> Er, you have this exactly backwards.  My wrappers provide part of
>> what is required to handle nontrivial conversions from a 16 bit
>> ABI to a 32 bit one.

>If I produce new code for an emulated environment, I'm *not* going
>to be doing so in a native environment.  The code production will
>be in the cross environment as well.

>Wrappering is a non-issue for conversions.  Wrappering *is* an issue
>in code complexity.  I believe complexity should be reduced wherever
>possible -- and this is not done by imposing ever more rigid standards
>on the programmer, it's done by making the code environment more
>flexible.

I was talking about reducing code complexity by doing all conversions
automatically.

>What wall time differentials do you expect from the conversion, and
>why?

Small.  The current unportable code is close to optimal.  I wouldn't
make it slower.  The machine generated code for the current FreeBSD
i386 ABI might even be exactly the same as now because all conversions
can be reduced to no-ops (this must be possible because the unportable
code actually works).

>Consider for example a PPC port of FreeBSD capable of running Intel
>FreeBSD binaries.  The capability and the endianess issues must be
>handled in the ABI call emulation layers.   The code will execute
>in an emulated 386 environment, but all kernel support will be
>native.

>This is on the order of an XDR interface that is contextualized for
>the native byte/word order of the machine instead of network byte
>order.

>The point is that the call conversion is layered seperately from the
>call implementation.

Conceptually it's a different layer but it shouldn't be implemented as
a separate layer.  My idea is to introduce such a conceptual layer
somewhere between the syscall entry point (Xsyscall()) and the syscall
implementing functions (e.g., read()) and then merge the layers as
much as possible using machine-generated code.

>> >packing must be specifiable at declaration time, or (preferrably) both.
>> 
>> Packing can't be specified in C.  That's why my my machine generated
>> wrappers are required - to provide portability.  It isn't worth
>> supporting both because if you support the worst case (weird packing)
>> then it just takes more code to support the array case.

>#pragma.  If we are talking about compiler modifications, packing #pragma's
>are much more generally useful than register call wrappings and the
>internal code you have suggested for varargs.

All #pragmas are unportable.

>What is your wrapper for open( const char *, int, ...)?

On the user side (so that the open syscall doesn't have a variable number
of args:

int open(char const *path, int flags, ...)
{
    va_list argp;
    int retval;

    va_start(argp, flags);
    if (flags & O_CREAT) {
	/*
	 * This code is written for maximal portability to expose the full
	 * braindamage of the open() interface.  Actual implementations
	 * should use one of the easier alternatives and shoot themselves
	 * in the foot by using e.g., `long double' for mode_t.  mode_t
	 * is guaranteed to be an arithmetic type (POSIX.1 2.5) and
	 * va_arg is only guaranteed to work for types that are their
	 * own default promotion, so there are many cases.
	 */
	/*
	 * Actually I'm too lazy to write all the tests to decide the
	 * promotion of mode_t.  Assume it is `promoted_mode_t'.  Also
	 * assume that we pass args of fixed size to syscalls and that
	 * registered_mode_t is a type that has that size and holds
	 * mode_t's without loss of information.
	 */
	promoted_mode_t mode;
	registered_mode_t rmode;

	mode = va_arg(argp, int);
	rmode = = (registered_mode_t)mode;
	retval = three_arg_open_syscall(path, flags, rmode);
    } else
	retval = two_arg_open_syscall(path, flags);
    va_end(argp);
    return retval;
}

On the kernel side: I can't write it without knowing the target machine.
Assuming a simple case where the user side stored the args in a uniform
way on the stack or in registers and the lower level kernel code has
copied the args from to memory:

int syscall_entry_open(struct proc *p, void *argp, int *retval)
{
    char const *path;
    int flags;
    promoted_mode_t mode;

    path = *(char const *)((char *)argp + PATH_OFFSET);
    flags = *(int *)((char *)argp + FLAGS_OFFSET);
    if (flags & O_CREAT) {
	mode = *(promoted_mode_t *)((char *)argp + MODE_OFFSET);
	return open(p, retval, path, flags, mode);
    }
    return open(p, retval, path, flags);
}

>> My ideas for syscalls are based on what is done for vnodes :-).  It

>What about non-native architecture ABI support?

It requires more code to generate the conversion code.  In particular,
sizeof() and `struct' can't be used, because the non-native compiler
may have completely different behaviour (of course, it's not practical
to emulate a machine with a larger address space or ints larger than
your quads).

>> You can't vary Xenix 286's syscall parameter passing conventions!

>What have I said that implied this?  The abstraction layering, since
>it is *in the kernel* in the class-call handling code, would be
>transparent.

Perhaps I mean something nonstandard by the ABI.  I mean the parameter
passing conventions more than the syscall semantics...

>The point is to have a native ABI that matches the kernel exported ABI
>to reduce overhead for native apps.

... and I mean `I' to stand for interface, not implementation.  By
definition, the ABI is whatever the kernel exports, and native apps use
it.

>By going to a register passing mechanism, you destroy this, and complicate
>the non-native ABI's at the same time... don't you see this as well?

The register passing mechanism for syscalls is a side issue.  I only
mentioned it as an example of an ABI that we have to emulate (for Linux)
and which is better so we should use it.  There would be no new
complications (except for improvements) because we already support the
Linux ABI.

>> >I object to needing to include standard system call prototypes to allow
>> >their use.  I put up with lseek/truncate/ftruncate/mmap BS because it's
>> 
>> It isn't required.  However, passing of args that have the correct type
>> (after the default promotions) is required.  The second arg to lseek
>> must be off_t, not long, except of course if off_t is long.

>??? If it's not required, then either you *aren't* talking about passing
>arguments to system calls in registers *or* you expect the compiler to
>"do the right thing" magically.

I'm talking about the requirement in C to pass parameters of the correct
type (if a prototype is in scope, then there are more correct types).

>Right now I can write code that makes system calls in assembly.  With
>register passing conventions, I will need to either use library or
>machine generated external routine calls (unacceptable), or I will have
>to have documentation of what the compiler has done so that I can do
>the same thing (non-portable as hell -- ever use M4 to write machine
>independent assembly code?).

It's always been necessary to know what what the compiler does if you
write glue functions in assembler.  (gcc) inline assembler has many
advantages here.  You don't need to know what the compiler does (you
just tell it how to load syscall args), and one layer of glue can be
avoided.

>> >Note that prototypes of system calls screw up your ability to properly
>> >utilize the syscall(2) call gate mechanism the way it was intended.  A
>> 
>> The use of syscall() in general requires handling all the messy conversion
>> issues that we have been discussing in your own code.

>Why is that?  A push is a push.  A trap is a trap.

>The only "messy conversion" is in pushing the arguments on the stack,
>and I still fail to see the benefit of playing "keep up with Linux"
>in this regard.

syscall() is written in C, so the compiler can pass args to it anywhere it
wants.  syscall() then has the task of putting the args where the kernel
expects them.  In general, it would have to know about all syscalls and
do the inverse of the conversions that I'm talking about doing in the
kernel.

Consider a 68K compiler that passes the first 2 pointer args (if any)
in a0-a1 and the first 2 integer args (if any) in d0-d1 and the other
args on the stack.  How are you going to push the args in syscall()?
Hint: you'll need to know the arg types of all syscalls.

>> (2) choose a different violation of the ANSI C
>> >and POSIX standards -- interface extension -- instead of passing quad's
>> 
>> POSIX allows most reasonable extensions.

>Yeah, well using quad for off_t isn't one of them.

Would you prefer double?  Allowing it seems to be a bug in POSIX.1-1990.
More things would break.

>> Linux has llseek.  That way leads to many ifdefs.

>It leads to:

>#ifdef HAS_QUADS
>#ifndef INT_IS_64
>#if BSD44
>#define	lseek(filedes,offset,whence)	qlseek(filedes,offset,whence)
>#define	off_t				quad_t
>#endif	/* BSD44*/
>#if LINUX
>#define	lseek(filedes,offset,whence)	llseek(filedes,offset,whence)
>#define	off_t				quad_t
>#endif	/* LINUX*/
>#endif	/* !INT_IS_64*/
>#endif	/* HAS_QUADS*/

>in one app-specific header file.

It's worse than that.  Linux doesn't have quads.  Applications have to use
pairs of longs and do their own (equivalent to quad) arithmetic on them.
So ifdefs like the above all through the code might be necessary.

>I believe the wrappering to constitute obfuscation, not simplification.

>There is a difference between conceptual simplification (but no
>document to reference to determine conception) and implementational
>simplification.

>I'm opposed to additional obfuscation of the call interface to
>allow questionable processor specific optimization to take place
>at the expense of ABI emulation portability.

We're in violent agreement :-).  I'd like to hide the current
unportabilities and scattered casts in a conceptually simple layer.
This requires a complicated and unportable implementation for the 
layer.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199510271903.FAA24658>