Having trouble getting started on writing a WDC 65816 backend

I asked about this in IRC ~2.5 hours ago:

<srnb> I'm trying to write an LLVM backend for the WDC 65816 (for usage in compiling SNES code) but I have very little experience with how C++ works and LLVM's CMakeLists. I'm trying to follow the guide but I don't know how to check if the project builds after my changes or how to test a small amount of C with it once I get it working.
<srnb> I've run into some things where I'm questioning if it makes sense for an LLVM backend:
<srnb> - How do I handle ROM banks? ( https://stackoverflow.com/questions/56925635/equivalent-of-org-in-llvm-ir )
<srnb> - Does it make sense to use `ENVIRONMENT` (where it's usually either `gnu` or `musl`) for the different ROM types? (`lorom`, `hirom`, `sa1`, etc)
<srnb> - The assembler I want to target is one that's not very known outside of the Super Mario World RomHacking community. Who's job is it to assemble what LLVM outputs, and how?

It got buried under join/leave messages and build alerts.

I'm using CLion on Windows with Msys2-mingw64 (my VoidLinux-musl installation isn't that great for development yet). Importing the project works correctly, but I don't know how to build the project or test the project or test my backend.

This is my first time ever using a mailing list, apologies if I get something wrong.

Hi,

<srnb> I'm trying to write an LLVM backend for the WDC 65816 (for usage in compiling SNES code) but I have very little experience with how C++ works and LLVM's CMakeLists. I'm trying to follow the guide but I don't know how to check if the project builds after my changes or how to test a small amount of C with it once I get it working.

You might try searching the list for past discussions of 6502 since
many of the problems will be similar.

As I recall the big one is that LLVM isn't well adapted to instruction
sets without fungible registers. One idea is to use some of bank 0 as
a source of registers, maybe with a custom pass to promote things to
real registers where possible after the fact. The other main one was
to design a virtual machine runtime on top of the basic instructions
with a more friendly instruction set; LLVM would then generate VM
opcodes, and you'd always link in the runtime to actually execute it.

I'm afraid I haven't thought enough to decide which approach I'd take
(or why) if it was me.

<srnb> - How do I handle ROM banks? ( https://stackoverflow.com/questions/56925635/equivalent-of-org-in-llvm-ir )
<srnb> - Does it make sense to use `ENVIRONMENT` (where it's usually either `gnu` or `musl`) for the different ROM types? (`lorom`, `hirom`, `sa1`, etc)

I think it could be made to work, but the physical start address would
normally be handled by a linker. If you're trying to avoid a linker
entirely, many hacks are possible.

The wider issue of different ROM banks (how pointers must be defined
and accessed so that all possible values are in range) looks more like
a CodeModel on other targets to me (with existing values of tiny,
small, medium, ... but you could make your own if you don't like
those).

<srnb> - The assembler I want to target is one that's not very known outside of the Super Mario World RomHacking community. Who's job is it to assemble what LLVM outputs, and how?

Most LLVM targets implement an assembler too so that they don't have
to call out to an external program. But you don't have to do that, and
if you want to start out using an external assembler then at some
point you'll probably want to teach Clang how to invoke it properly
(the alternative is telling your users they have to add lines to their
build system to do it, which is also fine to begin with). I believe
the key code is in clang/lib/Driver/ToolChains, for copy/paste
purposes.

It got buried under join/leave messages and build alerts.

A perennial pain in the neck. I have join/leave suppressed and even
put llvmbb on ignore most of the time. It's astonishing how much more
readable IRC becomes.

I'm using CLion on Windows with Msys2-mingw64 (my VoidLinux-musl installation isn't that great for development yet). Importing the project works correctly, but I don't know how to build the project or test the project or test my backend.

That I can't help you with I'm afraid. I get the impression most of us
here use CMake/ninja directly to do builds (see
https://llvm.org/docs/GettingStarted.html), and maybe an IDE to edit
the source files. I certainly do.

Cheers.

Tim.

Sorry about the "Sent by Migadu" message at the bottom of these emails. I looked through settings but I don't know how to get rid of it.

As I recall the big one is that LLVM isn't well adapted to instruction
sets without fungible registers.

What does this mean exactly? How does the WDC 65816 not have fungible registers, while other processors do?

One idea is to use some of bank 0 as
a source of registers, maybe with a custom pass to promote things to
real registers where possible after the fact.

How would I end up doing this? Do you mean bank 0 of the actual SNES ROM?

<srnb> - How do I handle ROM banks? (
https://stackoverflow.com/questions/56925635/equivalent-of-org-in-llvm-ir )
<srnb> - Does it make sense to use `ENVIRONMENT` (where it's usually either `gnu` or `musl`) for
the different ROM types? (`lorom`, `hirom`, `sa1`, etc)

I think it could be made to work, but the physical start address would
normally be handled by a linker. If you're trying to avoid a linker
entirely, many hacks are possible.

I wouldn't mind a linker.

The big five issues right now are:
1. Code and data cannot be split across a ROM bank
2. Code and data need to be properly addressed by the specifics mentioned in the SO post
3. The bank code should be in can't be specified by the code itself
4. The SNES starts executing instructions at $008000 (bank 0, rom$ 0)
5. Hardware addresses; SRAM, VRAM writing, etc.

How would a linker solve these problems though?

The wider issue of different ROM banks (how pointers must be defined
and accessed so that all possible values are in range) looks more like
a CodeModel on other targets to me (with existing values of tiny,
small, medium, ... but you could make your own if you don't like
those).

Would it be efficient then to implement a CodeModel for each Environment detailing where code/data and hardware addresses should be?

Most LLVM targets implement an assembler too so that they don't have
to call out to an external program. But you don't have to do that, and
if you want to start out using an external assembler then at some
point you'll probably want to teach Clang how to invoke it properly
(the alternative is telling your users they have to add lines to their
build system to do it, which is also fine to begin with). I believe
the key code is in clang/lib/Driver/ToolChains, for copy/paste
purposes.

Does another target have an example of needing an external assembler?

I'm using CLion on Windows with Msys2-mingw64 (my VoidLinux-musl installation isn't that great for
development yet). Importing the project works correctly, but I don't know how to build the project
or test the project or test my backend.

That I can't help you with I'm afraid. I get the impression most of us
here use CMake/ninja directly to do builds (see
https://llvm.org/docs/GettingStarted.html), and maybe an IDE to edit
the source files. I certainly do.

The targets listed in CLion are the same ones that you can call in CMake or Make I'm fairly certain.

What does this mean exactly? How does the WDC 65816 not have fungible registers, while other processors do?

The targets LLVM supports right now have at lest 8 roughly general
purpose registers that LLVM can put values in (most have more) and use
for different instructions.

Below that, LLVM starts to struggle. I've certainly seen compilation
failures when the number is artificially constrained for some reason
(certain kinds of inline assembly will do it), but I don't think
anyone is really sure of how big the problem is.

> One idea is to use some of bank 0 as
> a source of registers, maybe with a custom pass to promote things to
> real registers where possible after the fact.

How would I end up doing this? Do you mean bank 0 of the actual SNES ROM?

I believe the first 256 bytes of memory are directly accessible from
instructions, so part (or all) of that might be usable as pretend
registers. Now that I've looked it up the term is zero "page" rather
than "bank".

The big five issues right now are:
1. Code and data cannot be split across a ROM bank
2. Code and data need to be properly addressed by the specifics mentioned in the SO post
3. The bank code should be in can't be specified by the code itself
4. The SNES starts executing instructions at $008000 (bank 0, rom$ 0)
5. Hardware addresses; SRAM, VRAM writing, etc.

How would a linker solve these problems though?

The linker's job is to lay objects out in memory. That seems to cover
1 and 3-5 pretty directly. 2 sounds like an issue for the compiler.

Would it be efficient then to implement a CodeModel for each Environment detailing where code/data and hardware addresses should be?

The CodeModel would determine how far apart things can be, which
affects the instructions the compiler must use to access distant
objects. The actual final location of objects is still a linker issue.

Does another target have an example of needing an external assembler?

I think NVPTX might, but I'm not sure that needing an external
assembler is the critical point. Most targets are capable of invoking
an external assembler if you tell them to via "clang
-no-integrated-as" (usually it's GNU as). To make a target "need" one
you just don't bother implementing the assembler bits
(MCTargetDesc/XYZMCCodeEmitter.cpp principally).

Cheers.

Tim.

That comes directly from the dictionary definition of fungible. You
can not substitute one register for another.

Take, for example, RISC-V. You have 32 registers that, in the base
fixed-length 32 bits long instruction set, are absolutely
interchangeable with each other. No instructions use implicit source
or destination registers, any register can be used for anything. There
is no fixed stack pointer register -- any register can equally well be
used as a stack pointer. There is no fixed function return link
register -- any register can equally well be used as a link register.
The only exception is that reads from register 0 always return 0 and
writes to it are discarded. In general, if the compiler needs a
register or some purpose it does not matter what the purpose is, the
compiler can just grab the next available register and use it, without
regard for what other registers are in use or what they are being used
for.

Compare to 6502. There is a stack pointer that can't be directly
accessed, but only used in push and pop and transferred to or from
another register. There is a condition code register that can only be
accessed implicitly or by pushing or popping it to the stack.
Arithmetic such as add/siub/and/or/xor can only be done with the A
register. Increment and decrement can only be done with the X and Y
registers, not the A register. The (zp,reg) addressing mode can only
be used with the X register. The (zp),reg addressing mode can only use
the Y register.

Yes, the 65C02 and 65816 relaxed some of these things a little, but
not all that much.

Is it not the job of the backend to figure out how to use the registers
effectively? Or am I deeply misunderstanding how a backend is supposed to
transform IR?

That is indeed the job of the backend, and it's an order of magnitude
harder when you have few registers and those registers have
specialized uses compared to when you have lots of registers and can
use any register for anything.