
x86CSS is a working CSS-only x86 CPU/emulator/computer. No JavaScript required!
x86CSS is a working CSS-only x86 CPU/emulator/computer. Yes, the Cascading Style Sheets CSS. No JavaScript required.
What you're seeing above is a C program that was compiled using GCC into native 8086 machine code being executed fully within CSS.
GitHub ⧸ Fedi, Bluesky, Twitter
Do you really need to ask at this point?
I plan on writing a blog post that explains how this works as well as many of the tricks used. Bookmark my blog or add it to your RSS reader.
Nope, this is CSS-only!
There is a script tag on this site, which is there to provide a clock to the CSS - but this is only there to make the entire thing a bit faster and more stable. The CSS also has a JS-less clock implementation, so if you disable scripts on this site, it will still run. JavaScript is not required.
My CSS clock uses an animation combined with style container queries, which means you don't need to interact with anything for the program to run, but it also means its a bit slower and less stable as a result. A hover-based clock, such as the one in Jane Ori's CPU Hack, is fast and stable, but requires you to hold your mouse on the screen, which some people claim does not count as turing complete for whatever reason, so I wanted this demo to be fully functional with zero user input.
Not really... well, kind of?
This entire CPU runs in just CSS and doesn't require any HTML code, but there is no way to load the CSS without a <style> tag, so that much is required. In Firefox it is possible to load CSS with no HTML, but atm this demo only works in Chromium-based browsers.
I straight up just write CSS! The CSS in base_template.html is handwritten in Sublime Text, but for the more repetitive parts of the code I wrote a python script.
Not really, you can get way better performance by writing code in CSS directly rather than emulating an entire archaic CPU architecture.
It is fun though, and computers are made for art and fun!
Yes, but you'll have to compile them yourself. See below.
x86 is the CPU architecture most computers these days run on. Heavily simplified, this demo runs the same machine code in CSS that your computer does in its processor. To be fair, modern x86 is 64bit and has a bunch of useful extensions, so it's not quite the same - this here is the original 16bit x86 that ran on the 8086.
This project implements most of the x86 architecture, but not literally every single instruction and quirk, because a lot of it is unnecessary and not worth adding.
The way I approached this project was by writing programs I wanted to run in C, compiling them in GCC with various levels of optimization, and then implementing every instruction I needed. This way I know I have everything I need implemented.
There is some behaviour that's wrong, and some things are missing (e.g. setting the CF/OF flag bits). That's okay.
| - | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | ADD | ADD | ADD | ADD | ADD | ADD | PUSH | POP | OR | OR | OR | OR | OR | OR | PUSH | -- |
| 1 | ADC | ADC | ADC | ADC | ADC | ADC | PUSH | POP | SBB | SBB | SBB | SBB | SBB | SBB | PUSH | POP |
| 2 | AND | AND | AND | AND | AND | AND | ES: | DAA | SUB | SUB | SUB | SUB | SUB | SUB | CS: | DAS |
| 3 | XOR | XOR | XOR | XOR | XOR | XOR | SS: | AAA | CMP | CMP | CMP | CMP | CMP | CMP | DS: | AAS |
| 4 | INC | INC | INC | INC | INC | INC | INC | INC | DEC | DEC | DEC | DEC | DEC | DEC | DEC | DEC |
| 5 | PUSH | PUSH | PUSH | PUSH | PUSH | PUSH | PUSH | PUSH | POP | POP | POP | POP | POP | POP | POP | POP |
| 6 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| 7 | JO | JNO | JB | JNB | JZ | JNZ | JBE | JA | JS | JNS | JPE | JPO | JL | JGE | JLE | JG |
| 8 | GRP1 | GRP1 | GRP1 | GRP1 | TEST | TEST | XCHG | XCHG | MOV | MOV | MOV | MOV | MOV | LEA | MOV | POP |
| 9 | NOP | XCHG | XCHG | XCHG | XCHG | XCHG | XCHG | XCHG | CBW | CWD | CALL | WAIT | PUSHF | POPF | SAHF | LAHF |
| A | MOV | MOV | MOV | MOV | MOVSB | MOVSW | CMPSB | CMPSW | TEST | TEST | STOSB | STOSW | LODSB | LODSW | SCASB | SCASW |
| B | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV | MOV |
| C | -- | -- | RET | RET | LES | LDS | MOV | MOV | -- | -- | RETF | RETF | INT | INT | INTO | IRET |
| D | GRP2 | GRP2 | GRP2 | GRP2 | AAM | AAD | -- | XLAT | -- | -- | -- | -- | -- | -- | -- | -- |
| E | LOOPNZ | LOOPZ | LOOP | JCXZ | IN | IN | OUT | OUT | CALL | JMP | JMP | JMP | IN | IN | OUT | OUT |
| F | LOCK | -- | REPNZ | REPZ | HLT | CMC | GRP3a | GRP3b | CLC | STC | CLI | STI | CLD | STD | GRP4 | GRP5 |
| G | ADD | OR | ADC | SBB | AND | SUB | XOR | CMP | ROL | ROR | RCL | RCR | SHL | SHR | -- | SAR |
| G | TEST | -- | NOT | NEG | MUL | IMUL | DIV | IDIV | TEST | -- | NOT | NEG | MUL | IMUL | DIV | IDIV |
| G | INC | DEC | -- | -- | -- | -- | -- | -- | INC | DEC | CALL | CALL | JMP | JMP | PUSH | -- |
You can run your own software in this emulator!
If you have 8086 assembly ready to go, clone my repo, and put the assembly in a file called program.bin. Then, put the path to the _start() function in program.start as a number. Once that's set, you can just run build_css.py with Python3 (no dependencies required!) and the output will be in x86css.html.
If you want to write C code, you can do so using gcc-ia16 (you can build it yourself or install it from the PPA). The build_c.py script does the build with the correct flags and also makes the program.bin/start files. Don't forget to run build_css.py after! This building setup works on both Linux and WSL1/2 (I haven't tried on macOS).
By default there is 0x600 bytes (1.5kB) of memory, but this can be increased in the build_css.py file as necessary. The program gets loaded at memory address 0x100. There's some custom I/O addresses for you to be able to interface with the program.
Here's an example program:
static const char STR_4BYTES[] = "hell";
static const char STR_8BYTES[] = "o world!";
void (*writeChar1)(char);
void (*writeChar4)(const char[4]);
void (*writeChar8)(const char[8]);
char (*readInput)(void);
int _start(void) {
// Set up custom stuff
writeChar1 = (void*)(0x2000);
writeChar4 = (void*)(0x2002);
writeChar8 = (void*)(0x2004);
readInput = (void*)(0x2006);
int* SHOW_KEYBOARD = (int*)(0x2100);
// Write a single byte to screen
writeChar1(0x0a);
// Write 4 bytes from pointer to screen
writeChar4(STR_4BYTES);
// Write 8 bytes from pointer to screen
writeChar8(STR_8BYTES);
// Write a character from custom charset
writeChar1(0x80);
while (1) {
// Show numeric keyboard
*SHOW_KEYBOARD = 1;
// Read keyboard input
char input = readInput();
if (!input) continue;
*SHOW_KEYBOARD = 0;
// Echo input
writeChar1(input);
break;
}
while (1) {
// Show alphanumeric keyboard
*SHOW_KEYBOARD = 2;
char input = readInput();
if (!input) continue;
*SHOW_KEYBOARD = 0;
writeChar1(input);
break;
}
return 1337;
} Greetz/thanks to:
Your browser is unable to run this demo. Please try with an up-to-date Chromium-based browser.
> A hover-based clock, such as the one in Jane Ori's CPU Hack, is fast and stable, but requires you to hold your mouse on the screen, which some people claim does not count as turing complete for whatever reason, so I wanted this demo to be fully functional with zero user input.
That hover clock post is from 2023 and the "some people claim does not count" post is 2022. They were probably talking about the ones that make you check thousands of boxes to drive the logic forward.
Anyway, very cool advancement.
I wasn't sure whether to address the disconnect in the FAQ - I wanted it to be short and readable.
The idea is that, since a long time ago, there has always been demos that prove turing completeness and other programmy qualities in CSS, but that which people dismiss as requiring user inputs. The ones around by the time the comment got made were definitely at the "keep on clicking on the same spot on the screen" level - essentially just providing a clock.
And seeing discussion from after Jane Ori's hack, many still claim that even as much as hovering your mouse on a specific part of the screen makes css not a programming language.
> essentially just providing a clock
"providing a clock" is not something to dismiss though. Arithmetic plus looping will give you a Turing machine, so you do need both or you're just showing the ability to do arithmetic.
And a proper Turing machine doesn't need an extra line of template html for each iteration. It's much easier to forgive finite memory, since a small amount of memory can go for billions of years while an iteration limit runs out fast.
This one passes all the bars, but I do think the bars were overall legitimate.
> many still claim that even as much as hovering your mouse on a specific part of the screen makes css not a programming language.
That bar is pretty silly.
clock != looping, those examples already loop (dont need a line per iteration), but just dont have a built-in clock
and requiring a clock is imo dismissable, because pretty much all modern technology needs a clock too (either from the power grid, or from a hardware component designed for it)
Sure, we can separate loops from clocking for the most part. But it doesn't really change the analysis. These loop. The stuff from several years ago didn't loop properly.
As a tangent though, the system is already powered, you shouldn't need a secondary power source to make your Turing machine go. Something there still feels incomplete, like it probably passes but with an asterisk. But that distinction doesn't matter for CSS since it can self-clock.
This is a cool demo, but it tells me that CSS might be too complex now. Why should you be able to emulate a CPU with a styling language? I’m not sure what you get by using a Turing complete language for visual styling.
I don't know much about CSS, but Turing completeness is notorious for showing up in systems unintentionally.
It doesn't take much to be Turing-complete - if a system provides unbounded read/write memory plus branching or conditional recursion you're usually there.
As an example, Magic The Gathering (the card game) is Turing-complete: https://arxiv.org/abs/1904.09828 . You can use creature tokens as memory and various game mechanics to do flow control. Was this intentional by the designers? Most likely not...
* MOV x86: using memory mapped lookup tables, you can simulate logic gates and branching using only MOV.
* PowerPoint (Without Macros): using On-Click Animations and Hyperlinks, shapes on slides act as the tape and clicking them triggers animations that move the head or change the state of the slide.
* find and mkdir (Linux Commands): find has a -execdir flag executes commands for directories it finds. By using mkdir to create specific folder structures, you can create a feedback loop that functions as a Tag System (aka universal computation).
* Soldier Crabs: Researchers showed that swarms of Mictyris guinotae can be funneled through gates to implement Boolean logic. While a full computer hasn't been built with them, the logic gates (AND, OR, NOT) are the building blocks for one.
Even water is Turing Complete:
* Fluidic Logic Gates: the Coandă effect is the tendency of a fluid jet to stay attached to a convex surface. By using tiny air or water jets to push a main stream from one channel to another, you can create the fluid equivalent of a transistor.
* MONIAC (Monetary National Income Analogue Computer)
* Navier-Stokes equations describe how fluids move are TC.
* In 2015, Stanford researchers developed a computer that operates using the physics of moving water droplets. Tiny iron-infused water droplets moved by magnetic fields through a maze of tracks. The presence or absence of a droplet represents a 1 or a 0. By colliding or diverting each other, the droplets interact perform calculations.
> Turing completeness is notorious for showing up in systems unintentionally
Greenspun's 10th law.
That's on the opposite end of the complexity spectrum.
Perhaps so, but ISTM that it encapsulates the same basic point. Try to make something rich and general and you often end up re-implementing a whole computer inside your computer.
Which is why these days it's easier in many cases to just embed an Arm core and implement your controller's functionality in software.
> Try to make something rich and general
It's also easy to stumble into Turing completeness when you're just trying to let one setting modify another setting in a basic way.
Less JavaScript is a bad thing now?
There is absolutely no reason for css to be turing complete. None. That being said, well done
Can an argument be bade that CSS only exists becuase javascript failed to develop a styling component to displace it?
I like to think webassembly is the right track. But ECMAScript and CSS alike need(ed) to devolve into a simpler byte-code like intermediary language syntax.
Browsers supporting complex languages has always been a bad idea, what they need to support is capabilities, and access and security primitives. wasm hasn't displaced javascript, because afaik, the wasm spec for browsers doesn't require them to implement javascript (and ideally, CSS) via wasm.
Instead of distilling, simplifying and speccing CSS and Javascript, browsers caked on layers upon layers of complicated features. The idea that browsers should define and regulate the languages developers use to write front-end code needs to die.
The complex parts of JavaScript are the semantics, not the syntax. You could reasonably easily spec a bytecode for JS to get rid of the syntax part, but nothing would change in the complexity (almost all modern engines parse to bytecode as the first step and operate on bytecode from then on).
If you wanted to implement JS in wasm, you'd either need a bunch of wasm extensions for JS semantics (dynamic object shape, prototypal inheritance, etc), or you'd need to implement them in wasm from scratch and basically ship a JS runtime written in wasm. Either that, or you need to change the language, which means de facto adding a new language since the old JS still has to stick around for old pages.
I admit I don't have depth of knowledge with how JS and Wasm work behind the scenes, but you can already compile JS to wasm. Forgive the ignorance, but what am I missing here? When you compile JS to wasm, aren't the semantics and language behaviors already addressed?
My understanding was that things like DOM manipulation APIs would need to be (and are) exposed to the WASM runtime so that the JS-to-WASM compiler could do all the right bytecode "linking".
My idea is that if JS itself was require to in WASM, and native JS support didn't exist, the complexity in browsers would be reduced dramatically. And developers will be free to use different languages. Browsers will focus on APIs instead of language semantics. no more V8!
You can't currently "compile JS to Wasm", you can compile a JS engine (likely written in C++) to Wasm and run JS inside that (almost certainly at lower speed). So instead of "no more V8", it's closer to "ship V8 as part of the website, instead of part of the browser". Exposing DOM APIs to Wasm is a completely different question, though even those would actually have to be new APIs with a new API design that works for Wasm, since the existing DOM APIs are effectively defined in terms of JS objects being passed around.
I didn't know this, thanks for explaining. If DOM APIs could be reworked for wasm support, and JS can work with those APIs, that would suffice. It won't get sites off normal JS any time soon. As far as speed, is it an optimization problem? I'd think just running bytecode is faster than interpreting a script. It doesn't need to be backwards compatible with existing JS code, it just needs to be possible to write front end code in JS for WASM. Migrating to the new approach could be similar to migrating away from Flash, ActiveX and Applets, but at a much larger scale.
10-20 years from now, is it really ok to be stuck with JS and CSS? The complexity is compounding, ever newer webapi's get encoded as JS APIs, and the dependency mess will only increase. Browsers are about as complex in some ways as an operating system. If we think about the world in terms of decades, what is the plan? This is a very serious issue, because of how much the world depends on browser tech, and effectively, Google is the arbiter and overseer of that technology. I don't think this will work out well, unless there is some timely foresight in the architecture.
> CSS only exists becuase javascript failed to develop a styling component to displace it
there is no sortage of projects that do it (especially during the react era, people wanted to get rid of both html and css) but they get pushed down by dogma/inertia mostly. There was iOS constraint layout language ported to js. Seemed pretty cool, but the guy behind it decided to give up and everyone was like welp we tried, didn't work.
It sounds like the consistent pattern is the requirement for browsers to support it. If browsers supported intermediary languages instead, that might be ideal?
browser is the new os. It should just have first class support for a common bytecode target (wasm), everything follows.
I think the issue with CSS was that the low level APIs wasn't exposed which was the point of project Houdini https://developer.mozilla.org/en-US/docs/Web/API/Houdini_API...
Well, redstone was designed to be able to do logic from the start. The first version had wires, a couple input options, a couple output options, and NOR gates, already updating on a global clock. The ability to make computation circuits was clear.