UXN and Varvara

Someone asked me today if I could make a pretty basic implementation of the VM without all the device code and stuff, just consol I/O. So I did, here it is, it passes all opcode tests:

#include <stdio.h>

typedef unsigned char Uint8;
typedef signed char Sint8;
typedef unsigned short Uint16;

typedef struct {
	Uint8 dat[0x100], ptr;
} Stack;

typedef struct Uxn {
	Uint8 ram[0x10000], dev[0x100];
	Stack wst, rst;
} Uxn;

int uxn_eval(Uxn *u, Uint16 pc);

int
console_input(Uxn *u, char c, int type)
{
	Uint16 vector = u->dev[0x10] << 8 | u->dev[0x11];
	u->dev[0x12] = c, u->dev[0x17] = type;
	return uxn_eval(u, vector);
}

Uint8
emu_dei(Uxn *u, Uint8 addr)
{
	return u->dev[addr];
}

void
emu_deo(Uxn *u, Uint8 addr, Uint8 value)
{
	u->dev[addr] = value;
	switch(addr) {
	case 0x18: fputc(u->dev[0x18], stdout), fflush(stdout); return;
	case 0x19: fputc(u->dev[0x19], stderr), fflush(stderr); return;
	}
}

#define FLIP     { s = ins & 0x40 ? &u->wst : &u->rst; }
#define JUMP(x)  { if(m2) pc = (x); else pc += (Sint8)(x); }
#define POP1(o)  { o = s->dat[--*sp]; }
#define POP2(o)  { o = s->dat[--*sp] | (s->dat[--*sp] << 0x8); }
#define POPx(o)  { if(m2) { POP2(o) } else POP1(o) }
#define PUSH1(y) { s->dat[s->ptr++] = (y); }
#define PUSH2(y) { tt = (y); s->dat[s->ptr++] = tt >> 0x8; s->dat[s->ptr++] = tt; }
#define PUSHx(y) { if(m2) { PUSH2(y) } else PUSH1(y) }
#define PEEK(o, x, r) { if(m2) { r = (x); o = ram[r++] << 8 | ram[r]; } else o = ram[(x)]; }
#define POKE(x, y, r) { if(m2) { r = (x); ram[r++] = y >> 8; ram[r] = y; } else ram[(x)] = (y); }
#define DEVR(o, p)    { if(m2) { o = (emu_dei(u, p) << 8) | emu_dei(u, p + 1); } else o = emu_dei(u, p); }
#define DEVW(p, y)    { if(m2) { emu_deo(u, p, y >> 8); emu_deo(u, p + 1, y); } else emu_deo(u, p, y); }

int
uxn_eval(Uxn *u, Uint16 pc)
{
	Uint8 t, kp, *sp, *ram = u->ram;
	Uint16 tt, a, b, c;
	if(!pc || u->dev[0x0f]) return 0;
	for(;;) {
		Uint8 ins = ram[pc++], m2 = ins & 0x20;
		Stack *s = ins & 0x40 ? &u->rst : &u->wst;
		if(ins & 0x80) kp = s->ptr, sp = &kp;
		else sp = &s->ptr;
		switch(ins & 0x1f) {
		case 0x00: case 0x20:
		switch(ins) {
			case 0x00: /* BRK */ return 1;
			case 0x20: /* JCI */ POP1(b) if(!b) { pc += 2; break; }
			case 0x40: /* JMI */ a = ram[pc++] << 8 | ram[pc++]; pc += a; break;
			case 0x60: /* JSI */ PUSH2(pc + 2) a = ram[pc++] << 8 | ram[pc++]; pc += a; break;
			case 0x80: case 0xc0: /* LIT  */ PUSH1(ram[pc++]) break;
			case 0xa0: case 0xe0: /* LIT2 */ PUSH1(ram[pc++]) PUSH1(ram[pc++]) break;
		} break;
		case 0x01: /* INC */ POPx(a) PUSHx(a + 1) break;
		case 0x02: /* POP */ POPx(a) break;
		case 0x03: /* NIP */ POPx(a) POPx(b) PUSHx(a) break;
		case 0x04: /* SWP */ POPx(a) POPx(b) PUSHx(a) PUSHx(b) break;
		case 0x05: /* ROT */ POPx(a) POPx(b) POPx(c) PUSHx(b) PUSHx(a) PUSHx(c) break;
		case 0x06: /* DUP */ POPx(a) PUSHx(a) PUSHx(a) break;
		case 0x07: /* OVR */ POPx(a) POPx(b) PUSHx(b) PUSHx(a) PUSHx(b) break;
		case 0x08: /* EQU */ POPx(a) POPx(b) PUSH1(b == a) break;
		case 0x09: /* NEQ */ POPx(a) POPx(b) PUSH1(b != a) break;
		case 0x0a: /* GTH */ POPx(a) POPx(b) PUSH1(b > a) break;
		case 0x0b: /* LTH */ POPx(a) POPx(b) PUSH1(b < a) break;
		case 0x0c: /* JMP */ POPx(a) JUMP(a) break;
		case 0x0d: /* JCN */ POPx(a) POP1(b) if(b) JUMP(a) break;
		case 0x0e: /* JSR */ POPx(a) FLIP PUSH2(pc) JUMP(a) break;
		case 0x0f: /* STH */ POPx(a) FLIP PUSHx(a) break;
		case 0x10: /* LDZ */ POP1(a) PEEK(b, a, t) PUSHx(b) break;
		case 0x11: /* STZ */ POP1(a) POPx(b) POKE(a, b, t) break;
		case 0x12: /* LDR */ POP1(a) PEEK(b, pc + (Sint8)a, tt) PUSHx(b) break;
		case 0x13: /* STR */ POP1(a) POPx(b) POKE(pc + (Sint8)a, b, tt) break;
		case 0x14: /* LDA */ POP2(a) PEEK(b, a, tt) PUSHx(b) break;
		case 0x15: /* STA */ POP2(a) POPx(b) POKE(a, b, tt) break;
		case 0x16: /* DEI */ POP1(a) DEVR(b, a) PUSHx(b) break;
		case 0x17: /* DEO */ POP1(a) POPx(b) DEVW(a, b) break;
		case 0x18: /* ADD */ POPx(a) POPx(b) PUSHx(b + a) break;
		case 0x19: /* SUB */ POPx(a) POPx(b) PUSHx(b - a) break;
		case 0x1a: /* MUL */ POPx(a) POPx(b) PUSHx(b * a) break;
		case 0x1b: /* DIV */ POPx(a) POPx(b) PUSHx(a ? b / a : 0) break;
		case 0x1c: /* AND */ POPx(a) POPx(b) PUSHx(b & a) break;
		case 0x1d: /* ORA */ POPx(a) POPx(b) PUSHx(b | a) break;
		case 0x1e: /* EOR */ POPx(a) POPx(b) PUSHx(b ^ a) break;
		case 0x1f: /* SFT */ POP1(a) POPx(b) PUSHx(b >> (a & 0xf) << (a >> 4)) break;
		}
	}
}

int
main(int argc, char **argv)
{
	FILE *f;
	int i = 1;
	Uxn u = {0};
	if(i == argc) {
		fprintf(stdout, "usage: %s file.rom [args..]\n", argv[0]);
		return 0;
	}
	f = fopen(argv[i++], "rb");
	if(!f) {
		fprintf(stderr, "Failed to initialize %s\n", argv[1]);
		return 0;
	}
	fread(&u.ram[0x0100], 0xff00, 1, f);
	fclose(f);
	u.dev[0x17] = argc - i;
	if(uxn_eval(&u, 0x0100)) {
		for(; i < argc; i++) {
			char *p = argv[i];
			while(*p) console_input(&u, *p++, 0x2);
			console_input(&u, '\n', i == argc - 1 ? 0x4 : 0x3);
		}
		while(!u.dev[0x0f]) {
			char c = fgetc(stdin);
			if(c == EOF) {
				console_input(&u, 0x00, 0x4);
				break;
			}
			console_input(&u, (Uint8)c, 0x1);
		}
	}
	return u.dev[0x0f] & 0x7f;
}
3 Likes

Bootstrapping thoughts

So I’ve been thinking about this topic a bit lately, and I figured this would be a good place to share my experiments. I program in Uxntal, everyday, it’s the only language I use. And I would love to keep on doing so. I started to wonder what the language actually hinges on, and how I can recover a working compiler if I lost the seed, or just had a hex dump.

Any Virtual Machine

There’s a bunch of implementations of the VM, but the smallest one I know of is still uxnmin.c(compiles to 28kb), but it doesn’t have a file device, so I’ll use the uxncli for everything in what looks like unix, but even non-unix implementations have a way of sending console data between instances, even on DuskOS!

./uxncli
usage: bin/uxncli file.rom [args..]

Hexdump to Binary(49 bytes)

If for some reason I don’t have access to the unix xxd command, I can convert a hexadecimal dump to a rom with the xh.rom. The xh rom reads a console stream of hexadecimal text and output the actual bytes through the console.

The 49 bytes below do one thing, take a stream of characters, and output a stream of bytes, so the characters 2e, emits the byte 0x2e. This is the only binary we need for now.

a001 0a80 1037 e001 0100 8012 1680 3019
0680 0a0b 2000 0c80 2719 0680 100b 2000
0202 0041 dc4f 2000 0480 401f 001d 8018
17

Usable like:

cat drifblim.rom.txt | uxncli xh.rom > drifblim.rom

Hexdump to Binary, but without cat(81 bytes)

Now, if for some reason I don’t have access to the unix cat program, I can read files and output their content through the console with the cat.rom. The cat rom reads a file and outputs the content via the console.

a001 0780 1037 0080 1216 0680 1f0a 2000
1502 6000 1aa0 000f 13a0 0317 1608 2000
04a0 800f 1700 a000 0081 80fb 1331 00a0
0000 80a8 37a0 0001 80aa 37a0 014b 80ac
b780 a316 2000 0302 226c a000 1817 40ff
ef

Usable like:

uxncli cat.rom drifblim.rom.txt | uxncli xh.rom > drifblim.rom

The Assembler(2432 bytes)

Lastly, if I’m unable to build my own copy of drifblim, lost its source file, or simply want to make sure that the assembler is unaltered, I can start from the hexdump, and recover a working compiler:

a009 6f80 0637 a001 1f80 1037 a003 1716
1c20 000a a009 ac60 073e a001 0f17 0080
1216 0680 200a 2000 0702 a001 4c40 ffd9
a000 6081 80fb 1306 808f 0b20 000c a00a
56a0 0a17 a000 6060 010b 3100 8012 1606
8020 0a20 0003 6000 58a0 0090 8180 fb13
0680 bf0b 2000 0ca0 0a56 a00a 17a0 0090
6000 e231 0080 a837 a000 0180 aa37 6001
00a0 0000 800f 1620 001c a001 aa80 ac37
80a3 1620 0012 9d20 000c a00a 71a0 0a05
a000 6060 00af 226c 2180 0060 00dc 40ff
d3a0 09ff 6001 20a0 0060 60ff b880 0f16
2000 0f60 0404 800f 1620 0006 6000 0460
05df 6ce0 0000 a005 3134 a023 81a1 2114
8040 1c20 001e 26a0 0003 3894 8041 1980
1a0b 2000 0ea0 09f3 6006 6226 6006 5ea0
0a19 1722 61a0 0003 3860 061b aa20 ffcd
2222 a009 d060 0645 a000 9060 063f a009
db60 0639 a007 b834 a000 ff39 6006 34a0
09e0 6006 286f a000 0139 6006 26a0 09e8
6006 1aa0 800f 176c a000 00a0 0003 3840
0003 a000 0024 6005 ffa0 2019 1724 6005
f7a0 3a19 17a0 2019 1760 05ec a009 db60
05eb a000 3060 05e5 a00a 1917 a001 0f17
6ca0 001f 13a0 0000 116c 0680 200a 2000
1102 a000 0090 2000 0222 6ca0 0312 2e40
ffdf a000 0006 802f 0920 0009 a00a 56a0
0a0a 60ff 9d81 80eb 1331 6ca0 0030 8180
fb13 0680 5f0b 2000 0ca0 0a56 a00a 10a0
0030 60ff 8031 6ca0 30e2 1394 802f 0820
0009 9460 ffd5 2194 20ff f022 80ce 1280
0213 6ca0 00c6 1380 2f60 ffbf 9460 ffbb
2194 20ff f722 a000 306c a003 12a0 029c
356c 9060 0601 a11d 2000 1b22 6004 a920
0427 6005 5e01 2003 b126 6001 15a1 1d20
0383 2240 00ba 2134 2ca0 0345 a002 9c35
a001 0213 6cc0 0014 0680 2909 2000 03c0
0159 8028 0920 0001 41cf c0e9 5320 0003
40ff a76c a003 70a0 029c 35a0 0002 136c
c000 9480 7d09 2000 11c0 0159 cf20 000a
8000 6000 e622 4240 ff80 cf80 0008 2000
06a0 0000 6000 c814 807b 0920 0001 41c0
cf53 6c21 6001 b140 0307 2160 01aa 4002
fc21 2660 ff21 4001 0c21 60ff 3640 0105
8080 6003 b621 6002 ab40 03af 8080 6003
aa21 6002 9340 03a3 80a0 6003 9e21 6002
8e40 0393 2180 2040 0387 2180 4040 0381
8060 4003 7c21 4003 3d21 4002 d222 4000
2a22 40ff 3421 6000 5640 ff58 2140 fd65
226c 8000 8180 fb13 06a0 0a81 a180 fa33
1560 03ce 8018 33a0 043d 6c80 ec32 a000
0139 9460 ffeb 6000 8c80 de33 6cce bb00
0000 2f80 2632 a06b 8126 ef60 03eb 2000
0b60 03d3 aa20 fff1 22a0 ffff 2362 6c94
6000 0821 9420 fff7 2280 20a0 6b81 a180
fa33 1580 f632 a07b 812b 2000 09a0 0a56
a00a 3c40 fdcc 6c2f a005 3134 a023 8126
a000 0338 ef60 03a1 2000 0fa0 0003 3860
0385 aa20 ffe9 22a0 ffff 2362 6c26 60ff
d6a1 1d20 000d 2280 7732 2780 40a0 ffff
6000 4123 6c26 60ff 7921 1d20 002c 2660
ffb5 a11d 2000 0922 8080 6001 cc40 0024
a121 1480 801c 2000 1060 01bd 2735 2121
9480 801d 0505 1522 6c22 22a0 0a67 a00a
1040 fd4e 0460 0028 6000 2560 0022 6002
b720 0038 9460 03ff 211d 2000 2f60 0363
0120 0028 9460 0008 2194 20ff f722 8000
a023 81a1 80fa 3315 80f6 32a0 6b81 2b20
0009 a00a 56a0 0a43 40fd 076c 60ff d5a0
0a5f a00a 1040 fcfa 6002 6d20 027f 4000
0380 3113 b4a0 7b00 2920 0004 2260 fea2
9480 2f09 2000 0421 60fd 7894 8026 0920
0004 2160 fd6d 60ff 24a1 21af 1480 801c
2000 10af 8000 6f60 010f 6000 0f60 000c
6000 0dd4 4f80 401d 6f15 346c 0460 0000
a00b 81a1 80fa 3326 a023 812b 2000 09a0
0a56 a00a 4b40 fc8a 156c 80e4 32a0 0b81
a121 34a0 0249 3580 0f16 2000 0b60 000b
a000 0538 aa20 ffe8 2222 6c26 26a0 0004
3814 0680 0008 2000 1706 8001 0820 0017
0680 0208 2000 1c06 8003 0820 000f 026c
0260 003f 1502 6c02 6000 3835 6c02 6000
3835 6c02 6000 3227 a000 8038 0280 0008
2000 09a0 0a79 a00a 1c60 fc0c 1502 6ca1
2134 b4a1 1d20 0009 a00a 5fa0 0a1c 60fb
f723 6c60 ffe9 4000 0860 ffe3 2734 6000
4424 34a0 7b81 386c 8000 60fe f403 6c80
0140 feed 8002 60fe e8a1 1d20 0004 2280
006c 6000 2460 001d 26a0 0080 3802 2000
0203 6c03 a00a 79a0 0a1c 40fb b580 0360
febf 6000 0421 2139 6ca0 0100 6c60 fff9
3880 f633 6c60 fbc9 2194 8020 0a20 fff8
2194 60fb c521 9420 fff7 2314 40fb bb94
6000 a821 9420 fff7 226c 6001 a680 6304
1980 031b 801f 1c0f b4a0 4c49 2880 701f
0f5d a000 0338 9480 210b 2000 3494 806b
0920 0006 c080 5d40 0023 9480 7209 2000
06c0 405d 4000 1694 8032 0920 0006 c020
5d40 0009 a00a 5fa0 0a26 60fb 2521 40ff
c522 4f40 0045 6000 f406 8004 0880 501f
8080 1d60 0035 4000 0360 00e1 0680 0209
2000 0802 6000 8603 4000 2080 0409 2000
0660 0079 4000 1022 a00a 5fa0 0a2d 40fa
e160 0007 60ff 2604 6000 0006 60ff 2aa1
60ff 2ea0 7b81 3815 2000 016c 60ff 1a07
2000 09a0 0a5f a00a 3460 fab6 26a0 8000
2b20 0009 a00a 56a0 0a05 60fa a580 0833
6ca0 0090 80a8 37a0 0000 a000 ff39 80aa
37a0 7c81 80ae 376c 2660 0041 0120 0004
2280 006c 2194 20ff f022 8001 6ce0 0000
c040 7f60 0027 c000 0f78 2194 20ff f122
6f6c 0680 041f 6000 0104 800f 1c80 0a8b
2000 0519 8061 186c 0280 3018 6c94 8030
1906 800a 0b20 000d 8027 1906 8010 0b20
0003 0280 ff6c 2194 20ff fb21 6c06 c000
4101 9020 fffa 024f 6c2f 9480 210b 2000
0c94 d44f 0920 0005 2161 40ff ed14 8021
0b54 4f80 210b 1c6c 9480 1917 2194 20ff
f722 6ce0 ff00 a000 0abb af3a 396f 419d
20ff f322 0380 3018 8019 1747 58cf 20ff
f362 6cb4 800e 33a1 2114 8012 13a0 0917
a008 b4b4 a000 0029 2000 0ca1 2114 8000
0920 0003 3903 6ca0 0003 38aa 20ff e422
2280 ff6c 4c49 5449 4e43 504f 504e 4950
5357 5052 4f54 4455 504f 5652 4551 554e
4551 4754 484c 5448 4a4d 504a 434e 4a53
5253 5448 4c44 5a53 545a 4c44 5253 5452
4c44 4153 5441 4445 4944 454f 4144 4453
5542 4d55 4c44 4956 414e 444f 5241 454f
5253 4654 4252 4b0f a009 6fa0 0933 94cf
0820 000c a000 0338 aa20 fff2 22a0 ffff
2342 6c7c 03a3 2403 aa40 03b1 2603 b925
0405 2804 012c 03c0 5f03 c52e 03cc 2d03
d13b 03d8 3d03 dd3f 03e4 2103 ea23 03f5
2203 f97d 03fd 7e04 0c5b 0410 5d04 1000
4472 6966 626c 696d 0a55 786e 7461 6c20
4173 7365 6d62 6c65 720a 4279 2044 6576
696e 6520 4c75 204c 696e 7665 6761 0a31
3420 4a61 6e20 3230 3235 0000 7573 6167
653a 2064 7269 6662 6c69 6d2e 726f 6d20
696e 2e74 616c 206f 7574 2e72 6f6d 0a00
4173 7365 6d62 6c65 6420 0020 696e 2000
2062 7974 6573 2800 206c 6162 656c 7329
2e0a 002d 2d20 556e 7573 6564 3a20 0052
4553 4554 0046 696c 6500 546f 6b65 6e00
5379 6d62 6f6c 0050 6174 6800 5265 6665
7265 6e63 6500 4f70 636f 6465 004e 756d
6265 7200 5772 6974 696e 6700 4d61 6372
6f73 0053 796d 626f 6c73 0052 6566 6572
656e 6365 7300 6578 6365 6564 6564 0069
6e76 616c 6964 0064 7570 6c69 6361 7465
006d 6973 7369 6e67 0074 6f6f 2066 6172
2 Likes

I’m wondering, if we took Uxn and removed either the Keep or Return modes, we could have two bits to specify the word sixe (8, 16, 32 or 64 bits).

This would give us a tiny stack-and-RAM vm that scales from 256 byte up to 64-bit address spaces.

This wouldn’t be bit-compatible with Uxn, and with no memory protection and no stack typechecking, I think this would be a thermonuclear foot-cannon to program. It would serve mainly as a bad example.

But it would be doable.

1 Like

Haha, yeah that’d be pointless(it would be a bad example showing how NOT to do it), I’ve never written a program that remotely approached running out of space, so I can’t imagine what that would serve, but I’ve seen some people use POPk(NOP) and POP2k(NOP2) to toggle between 16 and 32 bit modes. But even that developer realized that 64kb of working memory is plenty when you use self-modification efficiently.

Think of the RAM as a private working surface for an agent, doing one task.

Then you’ve never written, as I have, little Node.js scripts which query, to give two real examples, the Unicode Chinese character database, or the Astronexus Augmented Tycho database of stars. I currently run those on my phone, in RAM.

If you can’t imagine other datasets larger then 64KB and also larger then 2GB that home hobbyists might like to run as a single agent process in contiguous RAM, then I’m not sure how to convince you that these computing tasks exist. But they do.

Yes, we could go back to the 1960s days of VAXes with kilobytes of RAM and indexed-serial databases with no random access computation. Carefully sorting everything and doing multiple passes. But is that really the future of personal data?

Uxn would be a very odd choice to pick for doing tasks like this.

But at the same time, my wiki, uses hundreds of source files as dataset, uxn has no trouble crunching it through and spitting out a website. I don’t think that expanding the memory of each uxn instance would improve this.

The entire point of universal runtimes for software preservation is to not be arbitrarily forced into a choice of non-interoperable runtimes/platforms just because you’re doing something slightly different.

I mean, it’s nice that you’re personally chaining lots of Uxn VMs together to get work done that Uxn by itsself, by design, can’t do.

But doesn’t that mean that you’ve created a new underlying OS layer that can’t itself be expressed in your universal machine? What does that OS layer run on? I feel like requiring such an external OS layer kind of defeats the point of specifying a bootstrappable universal machine?

My queries generally involve megabytes rather than gigabytes of data, and only take several seconds to run. I’m honestly wondering how I could restructure them to fit in a 64k working set. The complexity would escalate enormously. And I’d still need a filesystem or a database, right? So something’s gotta be running that filesystem, with itself more than 64K of RAM just to handle the caching. So what’s implementing that system?

Heck, just the framebuffer for an Android screen is much more than 64K. Do I really want to divide my literal screen into 64K chunks read from disk files on each screen refresh?

I get than Uxn is about minimalism more than software preservation, so this isn’t about Uxn itself, but something sideways to it.

It depends what you mean by OS. If you mean it to mean that Uxn can interface with the outside world in some way, like writing to a file device for example, or reading from a console device, then yes, Uxn itself is quite incapable.

In fact, Uxn has no device specification whatsoever. Uxn on its own evaluates a single event and goes to sleep until the next event comes in, but has no notion of an outside world. Unlike Chifir, it has no readchar or writechar opcodes.

My single biggest disagreement with Uxn ^^

Gotos: yes! No types: ugh, ok. Self-modifying code: I run screaming for the hills. As you can see I’m pretty broad-minded :laughing: but I have my limits.

2 Likes

Haha, that’s alright.
That used to be me too, I even had heated arguments with others against SMC!!

I can still remember that stomach turning feeling when I started learning about this, like “the code is, what… shifting under your feet?!”

Oh dang, sorry, I think I’ve missed an important detail here, I thought you had some familiarity with Uxn at least superficially. Here are so introduction pages that cover some of the surface technical details:

1 Like

I do. I’ve written Uxntal code for Varvara. Not great code, but I’ve poked at it. It’s fun, like coding 6502 assembler for a Commodore 64 was back in the 1980s.

And by the mid-1980s we had already advanced past what a C64 could hold in RAM, with the Amiga 512. (Edit: I mean Amiga 500 of course).

And so I ask again: How would I use Uxn to display a screen at a resolution even of an Amiga?

If I can’t even display a screen in one agent, then I would really need a slightly larger RAM size in my virtual machine, wouldn’t I?

This is a great question!

Okay, in simple terms, Uxn itself doesn’t know what a ppu is.

It doesn’t store memory for potential for screen buffers, and has no notion of pixels, sprites, even blitting functions. What it does have is a way of contacting the outside world via 16 nodules that sticks out, and these can also listen to incoming messages. But itself, it has no specification for a screen, a mouse, a keyboard, a speaker, a harddrive, etc.

Sometimes these are hooked on a mouse peripheral, to a playdate screen, to a nintendo ds R4 filesystem, or even an amiga screen. The screen, for example, stores its framebuffer some place in the system, it is NOT inside of a Uxn instance, and Uxn cannot write to that memory. Each Uxn instance occupies a length of memory in a system, and evaluates events, but its memory, is private, it’s its working surface to solve some task, but it doesn’t share it with anything else.

It’s not unlike the 6502 systems you’re familiar with, the PPU framebuffer is not mapped in accessible memory, there’s only a register mapped API.

When the screen is ready to be updated, it can tell a Uxn instance, and that Uxn instance might have an event handler at the ready, otherwise it does nothing. During that vector, the Uxn instance may send some messages to some screen’s API(always via its device page, nothing else), and then pixels might be drawn by uxn, to the screen.

Yes, I understand that this is how Uxn is designed.

I’m obviously not making myself clear here, so I’ll stop pressing the point. But just in case there’s a small possibility of being understood: What I’m trying to get at is that due to its builtin design limitations, Uxn is not a system in which another system that emulates, hypervises, or provides operating-system-layer services for a cluster of Uxn agents, could be built. A completely different language or platform (C and Linux, for example, or Javascript, or Dusk) must be built for such OS-level tasks, or even for large user tasks. So - just as with current architectures - there is a massive “impedance gap” as we move from OS-level services up the stack to user-level services - unlike, say, a 1980s Lisp or Smalltalk or even ANSI Forth machine with a large, unified address space, which could at least thoretically scale from chip to cloud without changing programming paradigms. Despite Uxn superficially resembling an extremely low-level close-to-the-bits machine of the kind that usually runs raw OS-level code, it is not intended for actually writing the OS level code that it needs in order to host itself.

And this, only because it can’t fit a 1980s machine into its address space, while Javascript running inside a web browser can.

I’ll bow out now since filling the role that I’m looking for is obviously not in Uxn’s design parameters now or in the future. And that’s fine. Uxn is very beautiful in its way and gives me strong nostalgia vibes. Sadly though, despite being beautiful, it’s just not the particular thing with the particular shape I need.

Dusk notwithstanding, I agree that a 64-bit contiguous RAM machine isn’t that shape either. The author of Retro had an abandoned concept for a stack machine based on arrays with overwrite-protected access. That’s the sort of shape of thing I might be looking for; I might try to take a further look there.

1 Like