Cool to see F# here! Emulators are a great way to learn a language. On first sight you chose well between more or less idiomatic F# for each job.
Some low hanging fruit to reduce allocations: the discriminated unions in Instructions.fs could be [<Struct>], reusing field names to reuse internal fields.
Also, minor nitpick but I'm confused about some of the registers. They are already of type byte, the setters with `a &&& 0xFFuy` don't add anything over `member val A = 0uy with get, set`. I'm guessing this changed over time.
The Register source has this comment:
So, I think, it's just conservatively cleaning the data due to Fable's widening via js Number on the web target.// Registers can't be a record type because the values need to be truncated to 8 bits when writing, so setters are needed // This is for the web renderer as Fable transpiles uint8 to Number (more than 8 bits) in JS and doesn't apply any truncation // Known non-standard behaviour in Fable (https://fable.io/docs/javascript/compatibility.html#numeric-types)Oof, thanks for pointing that out, I hadn't noticed and I've only ever used F# on .NET.
That's terrible on Fable's part, the least they could do is truncate. I wasn't aware Fable's translation is so naive.
Fable is great but it has a surprising number of these hidden behaviour changes that are really hard to detect when writing code against it.
I haven't used Fable much, but apparently it maps .NET arrays to js TypedArray. Presumably you could keep the registers in 8-element array and fable will properly produce a Uint8Array. I'd like to benchmark that.
It’s really hard to please everyone all of the time on this front.
This kind of thing is why Roc compiles to WASM but not JS.
It's actually discussed in the article in the part where he ports it to fable (he also tried blazor)
I admit I skimmed from there on because I don't find web dev exciting, but you're right, it is. That's a terribly naive translation on Fable's part.