OS design...
Ray Dillinger
bear@sonic.net
Mon, 19 Oct 1998 20:34:59 -0700 (PDT)
On Tue, 20 Oct 1998, Francois-Rene Rideau wrote:
>> What I'm talking about here is how programs establish and access
>> services, including but not limited to those services provided
>> by a typical operating system.
>Do you mean a foreign function interface to existing systems/programs,
>or just a standard library for your system?
<snip>
>Sure. And the foundation is: the compiler. Not anything in the "kernel".
>Choose an existing compiler, or implement a new one; calling conventions
>for "system services" will follow automatically from that choice.
Here, I think, is our fundamental conflict of opinion. You are
assuming that system services will and must be accessible as
procedure calls.
In writing compilers though, I have learned that each language
embodies assumptions about the semantics of a procedure call.
Different semantics mean that different underlying "low level
details" are needed for the most efficient support.
The most general procedure call in the LISP family is the Scheme
procedure call. It is lexically scoped and can be captured by a
continuation; it needs a scope pointer as well as an activation
record pointer, and, if the compiler can prove that it can't be
captured in a continuation, and the hardware provides stack
support (the intel family of chips do), then it is most
efficiently implemented on a stack. If however it *can* be
captured in a continuation, then the most efficient
implementation is to dynamically allocate it and garbage collect
it when there is no longer a continuation that has a live
pointer to it.
The Common Lisp procedure call is lexically scoped and cannot be
captured in a full continuation (though it may be "unwound" by
the use of catch/throw, and with very little effort it is
possible to implement an almost-continuation that can be used
exactly once). Its most efficient implementation in machines
that have hardware stack support is on the stack, and it
requires a scope pointer in each invocation frame.
The Elisp or Emacs-Lisp procedure call is dynamically scoped and
cannot be captured in a continuation; its most efficient
implementation on machines with hardware stack support is on the
stack, without a scope pointer.
I am sick to death of building FFI's that map the semantics of
one language's procedure calls to the semantics of another's in
order to access services. I think it is a mistake to make one
compiler more priveleged than another, or complicate the task
of implementors by forcing them to learn the internals of some
particular compiler in order to build effective compilers for
the system, and then force them to track changes in that other
compiler on penalty of having theirs break. (this is where most
LISP FFI's that call C++ code are today).
Also, I don't want to tie the system to my compiler's choices:
three years from now I may learn something new and cool and
change the way I implement procedure calls in my scheme
compiler. When that happens, there are a bunch of things I don't
want to break;
1) Compiled code produced by earlier versions of the
compiler. A commercial application's customers
don't always have the source lying around to
recompile, and they may have very costly
recertifications they'd prefer to avoid if
they do.
2) Compiled code produced by Common Lisp and Elisp
compilers (not to mention C compilers) that
used the "interface" defined by the system
up until that time.
3) Other compilers. For instance I may discover that
static testing to see whether an invocation
frame can be captured in a continuation is
wasting more time than stack-allocating it
saves. I would then start heap-allocating
all invocation frames in Scheme programs.
If the Common Lisp compiler on the system
compiles to Scheme, then suddenly CL programs
will start running slower -- because cutting that
test didn't save *them* any time at all and
they *NEVER* need heap-allocation of invocation
frames. Common Lisp compilers have no such
worries, so why should they pay a penalty for
something that buys scheme programmers a little
more speed?? Why indeed should the time spent on
the test needed to eliminate the penalty from
scheme code be built into their development loop
in the first place?
Right now, on systems where services are defined in terms of C++
procedure calls (which is most of them) LISPS pay an overhead
for "glue code" in their FFI because C++ is statically typed and
doesn't use long numbers!
If C++ were implemented by a system that compiles to LISP, then
C++ would pay either a killer compile-time overhead as the LISP
compiler laboriously and exhaustively eliminated the dynamic
types from the LISP code, duplicating effort already expended by
the C++ programmer, or a killer run-time overhead as the
compiled code generated and checked dynamic type tags it didn't
need.
You want to think about how silly the machine code would be if
we compiled FORTH procedure calls to Scheme procedure calls??
My point is, let's just not go there, that would be continuing
the kind of mistakes that current OS's make by priveling one
language (or at least one set of calling conventions) over all
others. Instead, let's have some standard method for accessing
services that is reasonably language-neutral and easy for a
compiler writer (compiling *whatever* language) to cope with.
Ray