c4 was "An exercise in minimalism."
C4KE is "an exploration in over-extending minimalism." It provides a multitasking kernel, shell, compiler, and utilities.
C4KE runs under a modified c4
interpreter called c4m
, which itself can be run be in an unmodified c4
interpreter.
It does however run much faster under a natively compiled c4m
.
C4KE is a pre-emptive multitasking operating system. It uses a cycle-based trap to switch between tasks automatically, or custom opcodes that can manually switch tasks (cooperative multitasking.)
c4m
can trap on unknown opcodes, and this feature is used to implement C4KE's extended opcodes. All registers, including
return addresses, can be modified inside these trap handlers. By swapping register values from one task with those from another,
task switching is accomplished transparently to running tasks.
C4KE uses a custom executable format, the C4 Relocatable. This format contains symbols and runtime patch information.
To run under the extended c4m
interpreter, use:
make run
To run under the original c4
interpreter, use:
make run-c4
See the Makefile for additional make
targets.
Once in C4KE, type help
for a command listing, or ls
to display runnable programs.
Try bench
or innerbench
to test out how fast C4KE runs on your system.
c4m
is the C4 Multiloader
, an extended version of c4 which apart from adding a few new opcodes, can also read multiple .c source files sequentially to output a single program.
Changes over C4:
-
Designed to run under unmodified C4: any version of
c4
should be able to runc4m
without changes. -
Designed to be enhanced when compiled natively: basic signal handling is supported (SIGINT), terminal control to come in the future.
-
Reads all given source files on the command line, as if they were one big file.
-
Related to the above, allows multiple instances of functions and variables to exist. If there are multiple
main
's, it is the last one seen that gets used. This functionality was a hack to enable modularization before a preprocessor was implemented, and will be replaced by proper preprocessing soon. -
Can use the address of functions (
&func
) to store intoint *
variables. -
Can call C4 functions stored in
int *
:int *func; func = (int *)&test; func(1, 2);
. -
Trap handlers: C code called when an event occurs.
c4m
additionally supports some POSIX signals, like SIGINT. -
Traps on unknown opcodes for custom handling or new opcodes implemented in C.
-
Cycle based trap for task switching.
-
Opcodes
JSRI
,JSRS
:JSR
variants that use a global variable (I
) or a stack variable (S
). Generated byc4m
. -
Builtin
__c4_opcode
: Allows custom opcodes to be executed, used heavily by u0.h. -
Builtin
__c4_jmp
: Unconditional jump to target address, used by c4ke.c for custom opcode handling. -
Builtin
__c4_adjust
: Adjust the stack manually, allowing stack allocation/deallocation at will. -
Builtin
__opcode
,__builtin
: Allows querying the interpreter's opcodes and builtin functions. -
Fixes a potential stack overflow when using printf()
-
c4ke.c - The C4 Kernel Experiment, main source file.
-
load-c4r.c - The "C4 Relocatable" executable loader and runner.
-
c4cc.c - A compiler for C4 - see also asm-c4r.c, for compiling to
C4R
format, or asm-js.c for compiling to JavaScript. -
c4le.c - A C4 Line Editor.
-
c4m.c - An updated C4 interpreter with new opcodes and support for traps. Can be run under C4.
-
c4.c - The original C4 interpreter, with only enough modifications to compile portably.
-
u0.h - User runtime for C4KE programs, interfaces with C4KE via extended opcodes.
-
mandel.c - A port of Johnlon's Integer Mandelbrot to run on C4KE
-
tests - Various test programs
-
asm-js.c - A module for
C4CC
that compiles to files than can run underC4JS
. -
libjs/ - A JavaScript port of C4. Incomplete, slow, and cannot run C4KE.
-
OISC4: One Instruction Set Computer for C4, an attempt to implement C4 using a single instruction. The compiler does most of the heavy lifting. The interpreter is fairly simple.
-
Only partially implemented. Most of the logic is there but subtle bugs prevent working code.
-
The instruction is:
add source, incremental, destination
.-
source
is a memory address to read from. -
incremental
is a literal value to add to the value read fromsource
. -
destination
is the memory address to write the result to.
-
-
Additionally, flags are set to indicate whether the final value written to
destination
was:-
LT0
: less than0
-
EQ0
: equal to0
-
GT0
: greater than0
-
Flags are either
0
to indicatefalse
, or3
(the size of an instruction in words) to indicatetrue
-
-
Code can arbritrarily jump by storing results into
PC
, or add the value of one of the above flags followed by two jump instructions. The first would execute if the flag was false, and the second would if the flag was true:EQ0, LABEL_1, PC ; Jump using flag and address of LABEL_1 LABEL_1: ABS0, NOT_EQ0, PC ; was false, jump to NOT_EQ0 ABS0, WAS_EQ0, PC ; was true, jump to WAS_EQ0 NOT_EQ0: ; do something ABS0, DONE, PC ; unconditional jump WAS_EQ0: ; do something else DONE:
-
Instructions in memory are able to be altered, and combined with jumping allow for later instructions to be modified based on conditions. This is used heavily by the compiler to implement many C4 instructions.
-
For example, to implement the
LOAD INT
C4 instruction (LI
), the value of a memory location needs to be dereferenced. This is done by altering a future instruction to change thesource
to the value read from memory:ABS0, some_variable, LOC1 ; Read from a variable (a memory address really), ; which must use ABS0 to get the true memory address, ; and alter the source in the next instruction. LOC1: PLACEHOLDER, 0, R0 ; Read from altered source and store into register R0.
PLACEHOLDER
is just a visual reference to know where something will be updated. It could also be a different part of the instruction.
-
-
By using such logic, all of C4 is able to be implemented, though cheated a little by the use of
DEVICE
s. -
DEVICE
s: theoratical hardware devices listening on specific memory addresses, and triggering some sort of behaviour when read or written.- Currently all math and IO operations are implemented as
DEVICE
s.
- Currently all math and IO operations are implemented as
-