In Keykos we used standard compilers which knew nothing of invocation of capabilities. The compilers were provided mostly by IBM and required nothing beyond that provided by any of IBM’s operating systems. The compilers were delivered in a relocatable form with clean semantics. It was easy to provide enough routines to suit the needs of those compilers in a capability savvy manner. They were loaded along with the compiler. The result was a Keykos object that compiled source code and yielded binder objects which was an abstraction of relocatable programs, but much more flexible than available on other systems.
Each of the compilers that we used had a call semantics and such calls never crossed protection domains as defined by Keykos. Keykos protection boundary crossing was by capability invocation and done by specific languages extensions which were achieved by general facilities already provided for extending the language. Call sites to invoke capabilities were thus Keykos savvy.
How programs referred to capabilities depended on the language. The most sophisticated version was for PL/I where capabilities were denoted by variables that the compiler viewed as integers. A Keykos library routine acted as cache logic to keep recently used capabilities in a cache of 16 capability slots.
This provides more detail about these and other Keykos extensions for PL/I.
The packaging of the PL/I compiler was extended further into the Keykos world by a few conventions and routines which papered over some of the more grubby details memory management. If an object which was defined in PL/I received a capability to a memory segment, it could pass that capability to a local magic subroutine that caused the segment’s content to be accessible to the compiled code thru an external variable known by the PL/I language for conventional purposes. It was a good semantic match.
In short it was never intended that programs not written with capabilities in mind could exploit the benefits of capabilities and be run without at least refurbishing the code. Granted that Burroughs machines had accomplished much of this in the early 60’s.
It is unfortunate to support two concepts for a program calling another program such as we describe here. On another level Keykos unified two other concepts in that any invocable object within the system, including those defined in the kernel, was callable from any of the several supported languages. Conventional languages raise obstacles to general interoperation. A Linux shell script can append a line to a file but only with means specific to files and a particular shell. I don’t know how to send a UDP packet from a shell script. The code to support such general interoperation is thus proportional to the product of the number of languages and the number of sorts of objects to be invoked. The documentation for these functions is likewise proportional to this product. Most members of this large matrix are largely unpopulated. I think that this situation is largely to blame for the general feeling that two sorts of languages are needed, scripting languages and application languages. When any functionality to be invoked is accessible via a capability and each of several languages can perform arbitrary capability invocations then we have orthogonality and the necessary system code to provide this is proportional to the sum, not the product of these two values. Ditto documentation and learning costs.
One inhomogeneity in Keykos was the distinction between invoking a Keykos object thru a call like operation, and referencing the memory defined by a capability. It seemed as if the hardware forced this distinction on us. Some languages lack a notion of accessing memory. How is a Python program to plant some bit pattern in the middle of an array access to which it has via a capability that it holds? The Callseg hack solved just this general problem. Callseg, being a system object could be invoked by any language that could invoke a capability, and any Keykos segment could be wrapped in a callseg object. PL/I did not need this hack but it was often convenient even there.
The general invocation of a Keykos capability requires a string of bytes which may include the byte 0. Fortunately PL/I character string can take on any of the 256 values. PL/I also supports strings as first class string values—there was a concatenation operator. C supports only string addresses.