Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 25 Oct 1995 13:47:35 -0700 (MST)
From:      Terry Lambert <terry@lambert.org>
To:        bde@zeta.org.au (Bruce Evans)
Cc:        bde@zeta.org.au, terry@lambert.org, 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:  <199510252047.NAA19391@phaeton.artisoft.com>
In-Reply-To: <199510251506.BAA10257@godzilla.zeta.org.au> from "Bruce Evans" at Oct 26, 95 01:06:56 am

next in thread | previous in thread | raw e-mail | index | archive | help
> >usage from not knowing about the stack?  I think you are going to
> >have to burn the cycles on an opaque function call in any case.
> 
> I hope that null conversions won't cost anything.  Conversions of
> the form `int fd = uap->fd;' (actually machine-generated code to
> load fd from an ABI-dependent offset from `void *uap') may even
> have a negative cost if they happen to load fd into the right
> register at the right time.

It may have zero cost (a *net* negative cost), I agree.

> >> Since we don't control foreign ABI's we shouldn't assume this.  For
> >
> >That's fine.  The size of arguments in iBCS2 and BSD is 'int'.  It's
> >either 'int' or 'long' or '*'.
> 
> Which one?  In NetBSD it's register_t, which may be longer than an
> int.  This causes problems.

Yeah.  They ignored the definition of "int".  That's a problem.

The real problem is the lack of atomic sized types and the use of "short"
as a synonym for "16 bit value", "long" for "32 bit value" and "quad"
for "64 bit value".

The real screwup is when int goes greater than 32 bits, the standard
*stupidly* requires long to go up as well because it can concieve of
a maximally sized type named anything other than "long".

That's a bug in the standard in not having mechanisms for obtaining
sized types.  For a 64 bit int (requiring a 64 bit long), short is
either 16 or 32 bits (undefined) and we lose access to either 32 or
16 bit types (respectively).

I guess the real question is is there an overflow condition for some
(long)lvalue = (int)rvalue; I'd say there isn't, simply because no
one has ever expected int's to get bigger than longs, and so the
content range isn't there.  The only failure modes are in pseudo
randomness depending on integer overflow.

> >So we take a hit when processing these non-standard mechanisms; we do
> >so through the system call table for the ABI, so we will be taking
> >a function encapsulation hit anyway.
> 
> Inlining should remove the hit.  Perhaps given smarter encapsulation
> functions, the hit from syscall() could be removed: call the encapsulation
> function directly from Xsyscall() and duplicate what syscall() does
> (copyin(), etc, iff necessary) in each encapsulation function.

I suppose this assumes that the compiler will correctly adjust the
register graph for the inlined assembly's register usage?  I don't
believe GCC is capable of this now, and I *know* Microsoft's compiler
dies when this happens.

I think using registers for calls and inlining are antithetical.  You
may be able to allow the optimizer to choose register passing for you
(or not), but the actual choice will vary based on optimization level,
etc..  I think this is a tricky failure mode that would require explicit
register designation (via #pragma?) which would *decrease* portability,
not increase it.

> >They are padded to the default bus transfer size for the machine,
> >which is supposed to be 'int'.
> 
> >I'd argue that 'int' was the wrong size [on the alpha], not that
> >there was extraneous
> >padding.
> 
> 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
and can force alternate argument call gating without much trouble (but
some runtime overhead: to be expected in any case of non-native code
execution anyway).

> >I'd really dealy love to know how there could be an endianess issue,
> >considering system calls are only ever going to run as compiled code
> >on one endianess of machine.
> 
> To run user code on one machine and syscalls on another.  I wouldn't
> want that.

I agree.

The main issue I'm trying to consider here is emulated execution
environments on the order of Xenix 286 support.  Specifically, I
have code for running a process in an emulated x86 environment and
making system calls that are serviced by native code.  The reason
for doing this is running Intel iBCS2 code on PPC and Alpha.  Clearly
it's insufficient at present, without the ability to do a full 386
emulation, but it's sufficient for kernel interfaces to be allowed
to be native rather than emulated as well.  It's a sound concept,
but it goes to hell under close approach, like what you'd have in
wrappering all the system calls, or using register passing.

> >> >What does it do?  What use is the change?
> >> 
> >> It avoids scattering unportable casts and ugly macros to perform them
> >> throughout the "machine-independent" code.  Now we have only unportable
> >> casts.  4.4lite2 has slightly less unportable casts and ugly macros.
> >> NetBSD has much less unportable casts and ugly macros.
> 
> >The structure casts, I presume?
> 
> Yes.

Structure casts are not a portability problem (as below).

> >The answer is to compile with the packing being the default for data
> >matchup -- in the alpha case, 64 bits.  For devices that need it, use
> >packing #pragma's to tighten them up on a case by case basis, in the
> >header file defining the structure.
> 
> That won't help much.  The problem is that syscall args don't form
> a struct.  Even if they are in memory, the packing may be different.

This is not allowed to be the case.  Either the nature of the push
must be changed to ensure a register_t sized push, or the structure
packing must be specifiable at declaration time, or (preferrably) both.

This is the current case with GCC and AIX/Sun/DEC/SGI compilers, so it
shouldn't be an issue.

> This can probably be handled by using __attribute__ ((packed)) for
> every arg to enforce a particular layout.  But that would only work
> with gcc.  I suggest using machine-generated code of the form
> `*(type *)(base + OFFSET)' where `type' and OFFSET depend on the arg.

Code readability is an issue.  The vnode_if.h file is an allowable
exception because of the export layering used in vnode.h's inclusion
of the file.  The general case of machine generated code is a bad one
for code readability.

> >Aligned element accesses are faster anyway.
> 
> OFFSET would usually be a multiple of the alignment so it would be
> easy to calculate :-).

Well, as long as sizeof(int) == sizeof(register_t), it wouldn't ever
be an issue in the first place unless you tried to send arguments as
themselves and the argument in question too multiple pushes to get
there.  That's only the quad arguments on Intel, and since the off_t
definition is a standards violation, there's no problem with making
an interface extension in those cases (already enumerated) to deal with
it.

> The ABI is a convention, and can't be changed.

The ABI is an agreement between user and kernel space, and is abstracted
from implementation by (1) binary file identification, (2) execution
class, and (3) call gate.

That means we can vary it without changing the underlying implementation,
with the cost being restricted to the abstraction layering (already in
place as additional overhead anyway for ABI class emulation) and
additional overhead for non-native ABI's.

> >> There may be no correct size.  A size of 3 ints wouldn't work for
> >> open("foo", 0) if the caller has perversely passed 2 args on the stack
> >> at the top of the address space.  Where are the ABI specs that disallow
> >> this?
> 
> >There are none.  However, you are wrong; it would work, you'd just
> >get a garbage value (stack direction grows the right direction for
> >the third argument to be optional).  Since in that case the garbage
> >value is unreferenced (or the call generated a prototype warning
> >...not 8-)), then it will work.
> 
> I should get an EFAULT return if the args are at the top of the address
> space like I said.

I think this would be hard to enforce without dictating either stack
probes or argument count pushing (ala the VAX Calling Standard) in any
implementation.

Consider a quad first argument on an Intel architecture.

> >I have to point out that it would then be impossible to make system
> >calls without prototype references.  If this is a "fix" for the quad
> >word passing problem (which is a "non-use of system call prototype"
> >problem), then, isn't this just making things worse?
> 
> It requires either prototypes or passing parameters of the correct
> type just like it always did.  Since there is a lot of broken code
> out there, most compilers use inferior parameter passing conventions
> to support the broken code.

And?

I don't see that as a problem.  It's not possible for FreeBSD to dictate
coding practices; unlike Microsoft, it isn't 70% of the market.  Because
of this, it is necessary to compromise to make the code run.  We already
do this in a number of cases.

You might as well mandate that TERMIOS be the only tty control mechanism,
and all applications that expect to run on FreeBSD "must conform".

I wouldn't have a problem with an alternate execution class, and potentially
trap gate, to cause there to be a "very fast" calling mechanism that is
there *as*an*aternative*to*the*default* "portable" calling mechanism.
But mandating that the default be such that the majority of code on the
net would require patching to make it run (admittedly, mostly header
files) is *bogus*.

> >Callee pop only works when using the same stack.  When you get to the
> >kernel, the kernel thread (or just process) will be using its own stack,
> >so that argument won't wash.  My complaint on callee pop as a more
> >fruitful pursuit was based on kernel-kernel calls, not user-kernel calls.
> 
> Of course you wouldn't want to use it across interfaces, but how do
> you stop a C compiler that is optimized for compiling user code from
> producing wrong code for interfaces that it doesn't support?  (Writing
> masses of interface code in assembler isn't acceptable.)

This is an interesting question... but I can't see a situation where it
could be applicable.  There *must* be a mechanism for making OS service
requests to implement the standard libraries.  There is no other option
to avoid isolating the program from all of it's I/O (I/O, at least, must
use system interfaces, even if everything else is faked, including lying
about the time, etc.).

The answer to your question is that the compiler must know about some
system interface mechanism, then the system must provide the requested
mechanism.

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
impossible to support all of quads, 32 bit ints, and trap-based call
mechanisms and not violate either ANSI C, POSIX or both.  I just happen
to disagree with where the violation takes place.


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
user space checkpointing mechanism that defines it's own open interface
and calls the real open call via syscall (to remember the file names)
will fail to compiler when the inlined open conflicts with the user
definition.

And then you're back to unprototyped or buggered header files (which are
*very* system dependent) to implement your application interface.  It's
a reduction in "portability to BSD".

As a "marketing strategy", on should make an OS easy to port to and
hard to port from.  Idealogically, I'm opposed to the "hard to port
from" part of this, but no matter what ideology you follow, making
it "hard to port to" is a big, big mistake.


> >Right now the screwable functions are truncate, ftruncate, seek, lseek,
> >and mmap -- and mmap() is bogus because of the kernel address space
> >restrictions currently on "vmio".  The others are in violation of one
> >or more standards because "quad" isn't an allowable type.  Might as
> >well violate them further by using inline references to the __syscall(2)
> >instead of syscall(2) to get to them so that: (1) they are undefined
> >without proper header inclusion, and (2) the padding is guaranteed
> >(as the __syscall(2) states in the man page).  That at least would solve
> >the screwups without adding to them.
> 
> I've thought of changing the compiler to always check format args and
> print a diagnostic if there is a mismatch for a quad arg.  Something
> similar could be done for the above functions - make them builtins
> and complain about type mismatches for them.  For functions it's
> easier to silently DTRT (promote the args).

That's one approach, on the order of printf argument checking, and
probably requiring a higher-than-default error/warning level to cause
to be active.  I think most people will fail this test.

If you put a mechanism like this in, it's hard to argue that the
compiler should bitch at the user instead of just pushing a high order
0 and "making the code work" -- turn the error to a warning.


Probably a "correct" approach would be to either (1) push 64 bits per
for all arguments, using type identification to decide on pushing a 0
for the quad high order dword or pushing a value that really exists at
the time of the call, or (2) choose a different violation of the ANSI C
and POSIX standards -- interface extension -- instead of passing quad's
for the default truncate/seek/mmap call interfaces.  The additional
calls would only exist as inlines, not in libc, and would thus be unusable
without prototypes (qtruncate/qftruncate/qseek/qlseek/qmmap).  And
you wouldn't even implement qmmap until the vmio file windowing is fixed
in the kernel VM system.

I think that changing the interface in such a way as to cause large
amounts of "third party" (ie: ftp'able and/or commercial) code to
"show its brokenness" is a mistake.  The quad system call arguments
are mistakes of this family.


In any case, I think the benefits are questionable, and should be
explored as an execution class other than the default execution class
(ABI) before they are integrated.  Even after they are integrated,
portability would dictate that their use be optional.


					Regards,
					Terry Lambert
					terry@lambert.org
---
Any opinions in this posting are my own and not those of my present
or previous employers.



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