diff --git a/README.md b/README.md index 094a190..0c7f264 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,9 @@ repository, or even by hand (by numbering lines, resolving the labels and replacing mnemonics with corresponding opcodes). The "Hellorld!" example only works on the platforms supporting I/O port 1 (character output). +If you just want to test an implementation, assembled MU8 machine code files +(in the plaintext format) are stored in the `examples/assembled` subdirectory. + Reference implementations ------------------------- @@ -266,6 +269,11 @@ Reference implementations character input port correctly on desktop OSes but falls back to a simple stream file read in case the library is not found). Accepts a file name to preload a MU8 (plain text) machine code program from the OS command line. +* [ISO C99 implementation](mu808.c): supports the entire specification the + same way as the Python implementation, and also makes use of the optimized + `fma()` call that appeared in the C99 standard. Accepts a file name to preload + a MU8 machine code program from the OS command line. Compile the source with: + `cc -std=c99 -O2 -s -lm -o mu808 mu808.c` ### Assembler reference implementations diff --git a/examples/assembled/compound.mu8 b/examples/assembled/compound.mu8 new file mode 100644 index 0000000..9aa94e6 --- /dev/null +++ b/examples/assembled/compound.mu8 @@ -0,0 +1,9 @@ +1 5 100 0 1 +2 4 2 3 0 +3 9 2 1 4 +4 5 1 0 5 +5 7 5 4 5 +6 13 1 5 5 +7 7 0 3 5 +8 13 0 5 5 +9 3 5 5 0 diff --git a/examples/assembled/hellorld.mu8 b/examples/assembled/hellorld.mu8 new file mode 100644 index 0000000..c63bd19 --- /dev/null +++ b/examples/assembled/hellorld.mu8 @@ -0,0 +1,19 @@ +1 5 72 0 1 +2 5 101 0 2 +3 5 108 0 3 +4 5 108 0 4 +5 5 111 0 5 +6 5 114 0 6 +7 5 108 0 7 +8 5 100 0 8 +9 5 33 0 9 +10 5 10 0 10 +11 5 1 0 50 +12 5 1 0 10 +13 5 10 0 11 +14 5 12 0 12 +15 6 2 10 12 +16 3 12 12 1 +17 8 11 50 11 +18 7 50 50 10 +19 1 1 11 14 diff --git a/examples/assembled/linreg.mu8 b/examples/assembled/linreg.mu8 new file mode 100644 index 0000000..f2f158c --- /dev/null +++ b/examples/assembled/linreg.mu8 @@ -0,0 +1,49 @@ +1 5 0 0 10 +2 5 0 0 11 +3 5 0 0 12 +4 5 0 0 13 +5 5 0 0 14 +6 5 0 0 15 +7 5 1 0 50 +8 4 1 2 0 +9 6 1 1 3 +10 7 0 3 3 +11 6 1 2 4 +12 7 0 4 4 +13 6 1 2 5 +14 7 0 1 5 +15 7 1 50 10 +16 7 2 50 11 +17 7 3 50 12 +18 7 4 50 13 +19 7 5 50 14 +20 7 50 50 15 +21 6 1 3 6 +22 7 4 50 6 +23 1 1 6 8 +24 8 15 50 15 +25 6 1 15 1 +26 7 0 14 1 +27 6 1 11 5 +28 7 0 10 5 +29 8 1 5 6 +30 6 1 15 3 +31 7 0 12 3 +32 6 1 10 4 +33 7 0 10 4 +34 8 3 4 3 +35 9 6 3 21 +36 6 1 10 1 +37 7 0 21 1 +38 8 11 1 1 +39 9 1 15 20 +40 6 1 11 2 +41 7 0 11 2 +42 6 1 15 5 +43 7 0 13 5 +44 8 5 2 2 +45 6 1 3 1 +46 7 0 2 1 +47 12 0 1 1 +48 9 6 1 22 +49 3 20 22 0 diff --git a/examples/assembled/lunar.mu8 b/examples/assembled/lunar.mu8 new file mode 100644 index 0000000..a151ce9 --- /dev/null +++ b/examples/assembled/lunar.mu8 @@ -0,0 +1,63 @@ +1 5 1 0 50 +2 5 10 0 52 +3 5 16 2500 53 +4 5 7480 0 54 +5 5 3 4483 55 +6 5 2900 0 56 +7 5 1930 0 40 +8 5 100 0 57 +9 7 0 57 40 +10 5 1609 0 41 +11 5 7260 0 42 +12 5 4444 0 30 +13 5 5555 0 31 +14 5 6666 0 32 +15 5 7777 0 33 +16 5 8888 0 34 +17 5 26 6667 0 +18 5 9 7300 0 +19 5 4 4445 0 +20 5 0 4500 0 +21 3 40 42 0 +22 5 0 0 2 +23 1 4 42 28 +24 4 1 1 0 +25 6 1 1 2 +26 7 0 55 2 +27 6 1 42 3 +28 7 54 50 3 +29 8 3 2 4 +30 9 3 4 1 +31 13 1 1 1 +32 7 0 56 1 +33 8 41 1 41 +34 7 53 50 41 +35 6 1 41 3 +36 7 0 52 3 +37 8 40 3 40 +38 8 42 2 42 +39 1 1 42 41 +40 5 0 0 42 +41 1 1 40 43 +42 5 0 0 40 +43 8 40 52 1 +44 1 1 1 21 +45 3 40 42 0 +46 8 41 39 1 +47 1 1 1 50 +48 3 34 34 0 +49 1 6 0 63 +50 8 41 38 1 +51 1 1 1 54 +52 3 33 33 0 +53 1 6 0 63 +54 8 41 37 1 +55 1 1 1 58 +56 3 32 32 0 +57 1 6 0 63 +58 8 41 36 1 +59 1 1 1 62 +60 3 31 31 0 +61 1 6 0 63 +62 3 30 30 0 +63 0 0 0 0 diff --git a/examples/assembled/moo.mu8 b/examples/assembled/moo.mu8 new file mode 100644 index 0000000..9d9c112 --- /dev/null +++ b/examples/assembled/moo.mu8 @@ -0,0 +1,60 @@ +1 5 0 0 60 +2 5 1 0 61 +3 5 2 0 62 +4 5 3 0 63 +5 5 4 0 64 +6 5 5 0 65 +7 5 6 0 66 +8 5 7 0 67 +9 5 8 0 68 +10 5 9 0 69 +11 5 1 0 50 +12 5 4 0 11 +13 5 60 0 70 +14 5 80 0 71 +15 5 10 0 3 +16 5 9 0 1 +17 15 0 1 1 +18 7 70 50 1 +19 5 2 0 2 +20 6 2 1 2 +21 8 2 3 4 +22 1 0 4 16 +23 2 0 3 1 +24 6 0 0 0 +25 6 1 11 1 +26 7 71 50 1 +27 5 2 0 5 +28 6 2 5 1 +29 8 11 50 11 +30 1 1 11 16 +31 5 0 1000 51 +32 5 7 0 11 +33 4 91 94 0 +34 5 90 0 70 +35 5 0 0 20 +36 5 4 0 21 +37 5 4 0 22 +38 6 1 21 15 +39 7 71 50 15 +40 6 1 22 16 +41 7 70 50 16 +42 2 15 16 50 +43 8 0 0 0 +44 1 5 1 50 +45 8 21 22 2 +46 1 5 2 49 +47 7 50 50 20 +48 1 6 0 50 +49 7 51 50 20 +50 8 22 50 22 +51 1 1 22 38 +52 8 21 50 21 +53 1 1 21 37 +54 3 20 20 0 +55 5 4 0 7 +56 8 20 7 1 +57 1 0 1 60 +58 8 11 50 11 +59 1 1 11 33 +60 3 81 84 0 diff --git a/mu808.c b/mu808.c new file mode 100644 index 0000000..c68346b --- /dev/null +++ b/mu808.c @@ -0,0 +1,195 @@ +/** + * mu808: an ultralight numeric-only VM + * ANSI C99 reference implementation + * Compile with: cc -std=c99 -O2 -s -lm -o mu808 mu808.c + * See the README.md file for all documentation + * Created by Luxferre in 2025, released into public domain + */ + +#include +#include +#include +#include +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/* POSIX-specific terminal stuff for unbuffered input for I/O port 2 */ +#if defined (__unix__) || (defined (__APPLE__) && defined (__MACH__)) +#define NLC "\n" +#include +#include +struct termios orig_termios; +void disable_raw_mode() { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); +} +void enable_raw_mode() { + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(disable_raw_mode); + struct termios raw = orig_termios; + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} +#else +#define NLC "\r\n" +void disable_raw_mode() {} +void enable_raw_mode() {} +#endif + +/* core memory */ +#ifndef MEMLIMIT +#define MEMLIMIT 16384 +#endif +#define ushort unsigned short +ushort PMEM[MEMLIMIT*4] = {0}; /* program memory */ +double DMEM[MEMLIMIT] = {0.0}; /* data memory */ +static unsigned int runlimit = MEMLIMIT; +static unsigned char traceflag = 0; +static unsigned int runcount = 0; + +/* port output function */ +void portout(ushort port, double data) { + if(port == 0) /* standard numeric output port */ + printf("%f" NLC, data); + else if(port == 1) /* character output port */ + fputc(((int)floor(data)) & 255, stdout); +} + +/* port input function */ +double portin(ushort port) { + double val = 0.0; + if(port == 0) /* standard numeric input port */ + scanf("%lf", &val); + else if(port == 2) { /* character input port */ + enable_raw_mode(); + val = (double) (fgetc(stdin) & 255); + disable_raw_mode(); + } + return val; +} + +/* instruction line execution function (the main mu808 logic is defined here) */ +void ilexec(ushort lno, ushort cmd, ushort x, ushort y, ushort z) { + unsigned char halt = 0, data_override = 0, bcheck; + ushort i; + double v1, v2, v3; + while(halt == 0) { + if(traceflag) printf("PC: %hu INSTR: %hu %hu %hu %hu" NLC, lno, cmd, x, y, z); + DMEM[0] = data_override = 0; /* force the value at 0 to be always 0 */ + bcheck = (x < MEMLIMIT) && (y < MEMLIMIT) && (z < MEMLIMIT); + if(bcheck) { + v1 = DMEM[x]; v2 = DMEM[y]; v3 = DMEM[z]; /* prefetch the memory values */ + if(cmd == 1) { /* JMP */ + if((v2 == 0 && x == 0) || (v2 > 0 && x == 1) || (v2 < 0 && x == 2) \ + || (v2 >= 0 && x == 3) || (v2 <= 0 && x == 4) || (v2 != 0 && x == 5) \ + || x == 6) { halt = 0; lno = z - 1; } + else if((v2 == 0 && x == 7) || (v2 > 0 && x == 8) || (v2 < 0 && x == 9) \ + || (v2 >= 0 && x == 10) || (v2 <= 0 && x == 11) || (v2 != 0 && x == 12) \ + || x == 13) { halt = 0; lno = (int)floor(v3) - 1; } + } else if(cmd == 2) data_override = 1; /* IAT */ + else if(cmd == 3) for(i=x;i<=y;i++) portout(z, DMEM[i]); /* OUT */ + else if(cmd == 4) for(i=x;i<=y;i++) DMEM[i] = portin(z); /* INP */ + else if(cmd == 5) DMEM[z] = (x % 10000) + (y % 10000) / 10000.0; /* SET */ + else if(cmd == 6) { /* CPY */ + if(x == 0) DMEM[z] = y; + else if(x == 1) DMEM[z] = v2; + else if(v2 < MEMLIMIT && v3 < MEMLIMIT) + DMEM[(int)floor(v3)] = DMEM[(int)floor(v2)]; + } else if(cmd == 7) DMEM[z] = fma(v3, v2, v1); /* FMA */ + else if(cmd == 8) DMEM[z] = v1 - v2; /* SUB */ + else if(cmd == 9) DMEM[z] = (v2 == 0) ? 0 : (v1 / v2); /* DIV */ + else if(cmd == 10) DMEM[z] = (v2 == 0) ? floor(v1) : fmod(v1, v2); /* MDF */ + else if(cmd == 11) DMEM[z] = fabs(v2); /* ABS */ + else if(cmd == 12) DMEM[z] = sqrt(fabs(v2)); /* SQR */ + else if(cmd == 13) { /* NEL */ + if(x == 0) DMEM[z] = exp(v2); + else DMEM[z] = (v2 == 0) ? 0 : log(fabs(v2)); + } else if(cmd == 14) { /* TRI */ + if(x == 0) DMEM[z] = sin(v2); + else if(x == 1) DMEM[z] = cos(v2); + else DMEM[z] = atan(v2); + } else if(cmd == 15) { /* RND */ + v1 = floor(v1); + DMEM[z] = v1 + (rand() % (int)(floor(v2) + 1 - v1)); + } + } + lno++; /* increment the program counter */ + if(lno >= MEMLIMIT || lno < 1 || (runlimit > 0 && runcount > runlimit)) { + halt = 1; + if(traceflag) puts("Memory limit or runlimit hit, halting..."); + } else { /* fetch the next instruction */ + runcount++; + if(data_override && bcheck) { + cmd = PMEM[lno << 2]; + x = ((int) floor(v1)) % MEMLIMIT; + y = ((int) floor(v2)) % MEMLIMIT; + z = ((int) floor(v3)) % MEMLIMIT; + } else { + cmd = PMEM[lno << 2]; + x = PMEM[(lno << 2) + 1]; + y = PMEM[(lno << 2) + 2]; + z = PMEM[(lno << 2) + 3]; + } + } + } +} + +/* instruction line entry function */ +void ilenter(int lno, ushort cmd, ushort x, ushort y, ushort z) { + if(lno > 0) { /* record the instruction in memory */ + PMEM[lno << 2] = cmd; + PMEM[(lno << 2) + 1] = x; + PMEM[(lno << 2) + 2] = y; + PMEM[(lno << 2) + 3] = z; + } else if(lno == 0) { /* immediate execution */ + runcount = 0; + ilexec(lno, cmd, x, y, z); + } else if(lno == -1) { /* display a range of instructions from cmd to x */ + if(cmd < MEMLIMIT && x < MEMLIMIT) for(y=cmd;y<=x;y++) { + z = y << 2; + printf("@%hu:\t%hu %hu %hu %hu" NLC, + y, PMEM[z], PMEM[z+1], PMEM[z+2], PMEM[z+3]); + } + } else if(lno == -2) { /* clear a range of data or instructions from x to y */ + if(cmd < MEMLIMIT && x < MEMLIMIT) for(z=x;z<=y;z++) { + if(cmd == 0) + PMEM[z<<2] = PMEM[(z<<2)+1] = PMEM[(z<<2)+2] = PMEM[(z<<2)+3] = 0; + else DMEM[z] = 0.0; + } + } else if(lno == -3) { /* tracing on/off */ + if(cmd == 0) {traceflag = 0; puts("Tracing off");} + else {traceflag = 1; puts("Tracing on");} + } else if(lno == -4) { + printf("Runlimit set to %u" NLC, runlimit = cmd); + } else if(lno == -5) {puts("Bye!"); exit(0);} +} + +/* entry point to the VM REPL */ +void main(int argc, char* argv[]) { + srand(time(NULL)); + printf("mu808 v1 by Luxferre" NLC "PROGMEM: %u steps" NLC "DATAMEM: %u floats" NLC, + MEMLIMIT, MEMLIMIT); + int lno; /* line number can be negative at this point */ + ushort cmd, x, y, z; /* commands and their parameters */ + if(argc > 1) { /* preload the file from a command line parameter */ + FILE *fd = fopen(argv[1], "r"); + if(fd != NULL) { /* opened successfully */ + while(!feof(fd)) { + fscanf(fd, "%d %hu %hu %hu %hu", &lno, &cmd, &x, &y, &z); /* read the five numbers */ + ilenter(lno, cmd, x, y, z); /* run the entry routine */ + } + fclose(fd); + } else puts("Warning: no file could be preloaded!"); + } + while(1) { /* main interactive loop */ + printf("> "); + lno = cmd = x = y = z = 0; + scanf("%d %hu %hu %hu %hu", &lno, &cmd, &x, &y, &z); /* read the five numbers */ + while(getchar() != '\n'); /* ignore the rest of the line */ + ilenter(lno, cmd, x, y, z); /* run the entry routine */ + } +} +