A big part of the programming model is the memory model : the organisation of data in memory, their roles and restrictions...
POSEVEN follows the CDI (Control/Data/Instructions) model and has three memory spaces that are independent from each other : a pointer for one area has no meaning in another area. All areas can be paged out and cached.
1) Stack
The first and most simple memory space is the control stack. It is not accessed directly or indirectly by the program, hardware manages the addressing and the pointers have tags automatically added to them to prevent any confusion. Each level of stack contains two words : a data word and a pointer+tags word. Each level counts as "one" increment of the stack pointer, there is also a couple of "limit" indices to help catch abnormal conditions. This separate and protected stack is essential for the implementation of fast and efficient calls to other modules. There is no special constraint to the size of the index or the stack area, but it is usually limited by the word size (32 or 64 bits).
The stack space is specific to the thread. Nothing else (except thread #0 and its surrogate) can access it, and the only reasons to access it outside of the running thread is for setup/diagnosis/debugging.
2) Program
The second area is the program space. All instructions are supposed to have the same size so incrementing the instruction pointer (PC) directly points to the next instruction (sorry : no gadget for your ROP). There is no data and the program can not read "data" from this space. This is a "black box" with no "introspection", meaning that no ROP (or other hacking) can occur. The code is immutable and only the source code can know what is where, apart from the IPE in the trampoline (see below).
The address size for a module is limited to 24 bits, probably even 22 bits (amounting to 16MB). Or 20 maybe. With such a small size, relative addressing is pointless and the module can address itself with direct addresses embedded in the opcodes. Indirect or computed addressing is strictly limited. If you need more, modularise your program, don't let it bloat!
Each program space is specific to a module. An IPC instruction switches the current thread to a different module with its new program space. Pointers/instruction addresses for one module are irrelevant to a different module.
The program space is mostly homogeneous. The exception is the first 64K instructions which, at certain aligned addresses, can contain IPE instructions to allow jumping from a different module.
3) Data
Data belong to their own space. It is not homogeneous in its function, but the structure is always the same: a linear space made of 8-bit bytes, with a pointer that is as wide as words (32 or 64 bits).
The MSB of the address/pointer directs to one of four main areas: The top MSB is the private(0)/Public(1) flag. Other lower MSB divide this space even more, and the address generators can trap when a pointer calculation overflows or underflows, creating a pointer to a different sub-area.
00 : Data Stack, where parameters and recursive short-term blocs are stored.
01 : Local/Thread-private data (private heap, local/private data, variables)
10 : Module shared data, constants, holding the module's state, semaphores, configuration...
11 : Interchange area, used to allocate buffers that can be sent to other modules (message passing, "shuttles")
A pointer or address has a different meaning and shareability depending on the area/MSB:
- a private pointer (MSB=0) can't be shared at all and will have a totally different meaning in another thread.
- a module-shared area pointer (10) can be shared among threads running said module, but it's irrelevant in any other module.
- The interchange area (11) is the only type of pointer that can have meaning across all the threads and modules, BUT a given page can only be owned by ONE thread, which can then "yield" it to another recipient of its choice to transfer data (during IPC usually).
....
Overall, it's not simple but it's not overly complicated either. It does not require a complex support hardware/circuit/mechanism and it is reasonably efficient for 32-bit and 64-bit machines. Unlike a monokernel, it starts to make sense when the system implements tens or hundreds of threads and modules simultaneously, even in an embedded system.
......................
Update 20250304:

I have swapped the "high" and "low" parts so constants can be sign-extended easily for the most common/frequent use cases : accessing global module variables as well as thread private variables.
......................
Update 20260208:
Swapping the MSB of the data pointer is pointless, since the NULL page could as easily be mapped to the bottom of the Data Stack.
I also like how "privacy" decreases as the pointer's numerical value augments.
Also not shown is the different granularity of the pointers :
- Control/Stack pointers have a granularity of 2 words (2×32 or 2×64 bits)
- Data pointers point to individual 8-bit bytes
- Instruction pointers are undefined, usually 32-bit words with RISC but could be wider or narrower if needed.
.
Yann Guidon / YGDES
Discussions
Become a Hackaday.io Member
Create an account to leave a comment. Already have an account? Log In.
In conventional operating systems, every line of code knows whether a particular address argument can point into userspace or only into kernel space. Whether the address can be trusted or not. A machine's single native address type is treated as two or more distinct data types by the programmer. In tagged or segmented machines that deal with these issues automatically, the source code and object code are in effect getting interpreted at run time, with different paths taken depending on the address values. The source code for some called routine can look simpler by being more general. But that generality costs cycles or machine resources. If the programmer knew the exact situation anyhow, the source code and instruction pipeline may as well reflect that.
Are you sure? yes | no
There is so much to unwrap here, but what you say is generally true.
now, true does not always mean "right" or "appropriate/desirable".
And there is always a compromise between speed and security but there is the urgent need to find the compromise that maximalises both. This requires re-thinking so many accepted notions and paradigms but... who can still afford to not do it ? :-/
Are you sure? yes | no