Kueea Modules are written in Kueea Module Declaration Language.
Programs and memory types
Node programs are executed by a theoretical computer. The processors of the computer do not matter; they are undefined. What matters, however, is the memory that the processors access.
The abstract computer is implemented as a network of Nodes. Memory of all the Nodes is the memory of this abstract computer.
There are three distinct memory spaces within a Node.
Network memory is made of octets, each having a 192-bit address. The higher 128 bits of the address uniquely identifies a Node. The lower 64 bits reference an octet allocated on that Node. Reference to this memory is called a handle.
In other words, network memory is sequence of 2192 octets, which is made of subsequences of 264 octets that are asigned to all possible nodes in the network.
One physical machine may function as multiple Nodes.
At one point, the author considered this memory being accessible by a CPU as if it was physical memory of a machine, but the idea was abandoned. The problem lies in that if the data was physically at another node, fetch operation would complete after the data arrives over the network link. The CPU would be stuck waiting, not being able to be interrupted, because interrupts happen between instructions. This would introduce very long delays, which is unacceptable. Long-distance network links also introduce a bunch of other problems. The 192-bit network memory is thus entirely implemented in software.
Tasks access memory through their virtual memory. The memory space is made of bytes, which must be at least 8-bit long.
Upon calling a function and when returning from one, bytes of objects passed as parameters must have all bits above the lowest 8 cleared. The bits must also be kept cleared whenever an object is, or may be, accessible to more than one task. In other words, the bits are assumed to be cleared when loading.
The bytes are referenced by 64-bit virtual addresses. These addresses are unique to a given task. Virtual addresses are defined to map into network-memory addresses and are what real CPUs refer to when accessing memory.
Current (as of 2019) CPUs reference memory with 64-bit values, of which not even all of the 64 bits are used for the address. The author believes 264 octets is enough for a single task, although one person once thought that 16 MiB of RAM is enough.
Actual object data resides in a machine's physical memory, i.e. the memory space at which physical (real) devices are mapped onto. Reference to this memory is called a physical address.
In practice, virtual addresses are translated by a CPU into these directly. If the referenced memory is not part of the physical memory of the machine, the reference is intercepted and dereferenced by system software.
The point of this is to write programs without thinking about or caring whether an object is physically at the current machine or at another. Physical location of an object is managed by system software. This includes transferring the data from one Node to another. Programs may instruct the system to move an object between Nodes, in which case its network address changes and handles need to be updated. Of cource, not every object can be moved in this way.
Node programs store data in and are themselves objects.
An object has the following traits:
- location of its data (handle and physical address),
- length of the data (in octets),
- type of the object (class),
- level of data confidentiality and other attributes.
The confidentiality level determines what kind of memory to allocate, whether to encrypt it and if the memory needs to be cleared or not. Copying data from a higher level to a lower level is a security error. Confidentiality level is also a property of a data stream.
The lifetime of an object is as follows:
- A task requests the kernel to allocate memory for an object.
- The task gains access to the object’s memory by the kernel mapping it onto the task’s virtual memory space.
- The task invokes the object’s preconstructor; i.e. initializes the object to a safe state. (Precostructors never fail.)
- The task invokes the object’s constructor. If the constructor fails, skip to step 7.
- The object is now created.
- Tasks utilze the object.
- Tasks lose access to the object by the kernel unmapping it from the task’s virtual memory space.
- When there are no more references to the object, the kernel invokes the object’s destructor in a new task.
- The kernel deallocates the object’s memory.
This poses a question: What should the kernel do when a destructor fails? The preferred answer of the author is to log the event and continue.
Classes and interfaces
Every object is an instance of some class. Classes are made of data and function members. Function members are often called methods of the class.
Data members of a class are instances of a class or octets. (There is only one fundamental class – an octet.) Each data member is located at a predefined offset into the object; i.e. a class defines precisely how its data members are laid out in memory.
Modules are technically classes and classes have levels. Higher levels add new members to a class. New data members may only extend the object.
Classes have memory address alignment requirements. Data members should be arranged from longer alignment to shorter alignment. Inserting padding between subsequent members is discouraged.
A class may be declared as an interface. If the interface defines data members, a class that implements it must contain these members somewhere within its data structures and is expected to implement all of the required function members that the interface defines.
Information about classes and their interfaces is stored within special read-only objects called class descriptors.
Tasks may access this descriptor, which allows them to execute interface functions without any prior knowledge about a given object. Programs are written to either manipulate an object based on its type or based on the object’s implemented set of interfaces.
In other words, run-time type information about objects is a part of a Node’s ABI in order to provide system-wide virtual function functionality to every task in the system, regardless of the language a program has been written in.
Tasks are created as part of answering a query to the system. They begin at any function and finish when that function returns. A task may also fail, which is distinct from a successful return.
When a new task is created by the system, the caller task becomes the task owner of the callee. Ownership relations form a hierarchical tree structure. When a task is taken out of the tree, it becomes a detached task.
Task ownership may be transferred to another task, as long as the target task is located deeper (in the tree) than the current owner and the caller. Detached tasks are an exception - any task may become their owner.
The first task a Node executes is called its boot task, which is placed at the root of the task tree.
Tasks may return a value, which other tasks can later obtain. Values returned by detached tasks are forgotten immediately.
Tasks are deleted when in detached state and have returned or failed.
Tasks access objects though their virtual memory, which is composed of three memory spaces called access contexts:
- task context, accessible only to a particular task;
- module context, accessible to all tasks of a given module; and
- kernel context, accessible only when the task is in kernel mode.
A task in kernel mode has priviledged access to the machine. What exactly does this mean depends on a particular CPU architecture.
Objects in module and kernel contexts are independent of tasks.
Function symbols are categorized by which context they need access to:
- task functions access their arguments only;
- module functions access objects in their module’s context; and
- kernel functions access objects in the kernel context.
Tasks and objects
The system assumes objects are big and complex data structures. Copies of objects are never created by the kernel.
All memory is shareable between tasks (including the stack.) The kernel’s main job is controlling access to object memory.
The core functionality of Kueea System is tasks’ ability to gain direct access to objects created by other tasks. This operation also gives access to any referenced subobjects.
A module is technically a special type of class.
There is at most one instance of these classes per Node. This object is referred to as the module’s state.
The module state is destructed at first (no instance). When destructed, the module's constructor may be called. If successful, the module becomes constructed. Once constructed, module functions of the module can be called.
The state is shared between all implementations of a module on a Node.