At an early stage in the Pegasus project it was decided to define all
interface types in the system in an
interface definition language which came to be known as Middl. While
aiding system development this has another benefit: it naturally leads
to a model
of programming where an invocation across an interface passes a
reference to the interface as an argument. The interface structure in
memory contains a reference to state unavailable to the client. This
state together with the other arguments to the call constitute the
complete calling environment. This device is known as a closure. The
style of programming is known as object-based.
Figure: Interface data structures
A closure is a pair of pointers; one points to some state record, the
other to a method table within a module (see figure ).
Since interfaces are typed, the format of
the method table can be derived from the interface type specification.
The state record is opaque to any client of the interface; it is only
manipulated by code within the module. Any call across an interface
adheres to a particular calling
convention, and must pass the closure as the first argument. Thus
the complete environment for the call is passed as arguments.
This technique solves the problem of identifying the calling environment: it is the state in registers and that accessible on the call stack or via pointers contained therein. All mutable state in a domain is encapsulated behind interfaces instantiated at runtime. In some ways our system is similar to Opal ([]), although the use of Middl allows a rather more flexible computational model (for example, we can have multiple interfaces attached to object state, efficient cross-module exception handling, and a cleaner syntax for multiple return values). Nemesis is also a native operating system, rather than one running over another operating system such as Mach.
Writing programs in this manner is not terribly alien; closures are after all the mechanism used by many object-oriented languages. As a result, the performance penalty due to the indirection involved is no more than virtual function lookup in C++, for example.
By making closures explicit we gain modularity in the system and at the same time do away with the many of the problems of addressing state in the system. We can share code at a fine level of granularity and freely pass real pointers between components of the system with compile-time type checking.
At a pinch, we can support existing UNIX-oriented C and C++ programs in the same way that other operating systems do: by linking the program with a set of stub functions with interfaces to our libraries and running the whole thing in a single call environment.