[HN Gopher] DotnetSnes: Library allowing to use C# to create SNE...
___________________________________________________________________
DotnetSnes: Library allowing to use C# to create SNES ROMs
Author : ingve
Score : 60 points
Date : 2025-05-02 11:29 UTC (2 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| SideburnsOfDoom wrote:
| > No dynamic allocations are supported (thus no reference type
| support)
|
| No class instances. This is a severe limitation, to the point
| where it will change the character of the language entirely.
| KallDrexx wrote:
| In theory, allocation could be hacked in with an arena
| allocator. I just need to add in reference type support and
| custom allocation strategies into the transpiler.
|
| Then again, that's only viable if you don't use enough memory
| to require rom bank switching, or have better control over
| which class gets placed in which rom bank.
| SideburnsOfDoom wrote:
| Does "no reference type support" also imply "no strings" ?
| i.e. no instances of the common reference type System.String
| ?
| KallDrexx wrote:
| Strings become `char*` so they are supported as long as
| they are not dynamically generated (no concatenation for
| example, but some things like `printf()` templates do work.
|
| e.g. https://github.com/KallDrexx/DotnetSnes/blob/master/sr
| c/Dotn...
| KallDrexx wrote:
| Also, I would point out that there are cases where it doesn't
| change significant amount of the language or hamper things.
|
| The transpiler also works for Linux eBPF kernel side
| applications without unsafe pointers and other odd things I
| need to do for SNES.
|
| Since you can have methods on c# struct instances, things can
| still work pretty idiomatically.
|
| You also do have statically sized arrays, or heap allocated
| arrays (the latter is only supported if host C application
| passes it in before hand, and is responsible for free ing it).
| rawling wrote:
| https://news.ycombinator.com/item?id=43673011
|
| Some discussion 3 weeks ago
| chongli wrote:
| Someone asked on the previous discussion why there's no heap
| but no one answered and replies are locked out, so I'll answer
| here:
|
| There's no heap on the SNES because the system has only one
| program running at all times (the game). There's no point using
| dynamic memory allocation when all of the memory on the system
| is available to the program, so you can just start writing to
| any valid, writeable address you want. Some addresses are, of
| course, not writeable (such as ROM space), and some addresses
| are not memory (memory-mapped IO) but that's not a problem.
|
| The really nice thing about the SNES is that it uses a 24-bit
| address space instead of the 16-bit address space of the NES.
| Most NES games needed to use mapper chips to swap between
| different ROM banks since with 16 bits you can only address
| 64KB of memory at a time and many NES games were larger than
| that. Having 24 bits allows you to fit your game into far fewer
| ROM banks, greatly simplifying the programming model and making
| it much more realistic to use a high-level language like C#.
| kfuse wrote:
| Frankly, that doesn't explain much, because that sounds like
| how modern computing works: every program has its own
| continuos 32/64 bit address space. With 24 bits you can
| address 16MB which seems enough to be useful if you throw
| away reflection and such.
| chongli wrote:
| 16MB is larger than every single SNES game ever released.
|
| Modern programs have dynamic memory allocation. You can't
| just start writing to any address you want. You have to
| request memory from the operating system with malloc() and
| then free() it when you're done. Memory-managed programming
| languages handle this for you but it's still there under
| the covers.
|
| On the SNES, you simply have all memory available from the
| beginning. No malloc/free, just start reading and writing.
| ninkendo wrote:
| Malloc and free aren't handled by the operating system,
| they're handled in user space.
|
| Underneath malloc is mmap(2) (or in older unices,
| setbrk), which actually requests the memory. And with
| delayed/lazy allocation in the OS, you can just mmap a
| huge region up front, and it won't actually do anything
| until you write/read to the individual pages.
|
| Point is, you only need one up front call to mmap to
| write to any page you want.
| chongli wrote:
| The SNES doesn't have any concept of user space. Your
| program has full control of the hardware. You can do
| whatever you want. There is no operating system at all.
| ninkendo wrote:
| I was responding to your second paragraph, where you talk
| about modern programs having to request memory from the
| OS with malloc and free. This isn't true, malloc and free
| are not operating system concepts, they are ways for your
| program to divide up memory address space that is already
| mapped to you.
|
| To bring this back to the SNES, you could totally use
| malloc and free on the SNES, but it would be just vending
| pointers to the address space you can already use. But my
| point is that _this is no different from a modern OS_ ,
| because malloc and free are just managing the address
| space you already got from the OS using mmap. And plenty
| of malloc implementations avoid repeated calls to mmap by
| mapping a large amount of space up front.
|
| My point is, "having full access to the hardware" is
| completely orthogonal to whether malloc and free are a
| good idea. You can use malloc/free on a flat address
| space, just like you can use them on a big fat mmap()
| region. Instead, the reason you'd generally avoid
| malloc/free on SNES is that the amount of physical memory
| is so tiny that it's generally a bad idea to do any
| dynamic memory management. Instead you want fixed regions
| representing in-game entities and logic, and the memory
| addresses you use should be managed manually in fixed
| size buffers.
|
| (If you're still not convinced, consider that malloc and
| free work just fine in DOS, where there's also no virtual
| memory and you have total access to the physical memory
| space in your program. DOS doesn't have mmap, and malloc
| implementations on DOS just stick to managing the flat,
| physical address space. No MMU or virtual memory needed.)
| orphea wrote:
| > If you're still not convinced, consider that malloc and
| free work just fine in DOS, where there's also no virtual
| memory and you have total access to the physical memory
| space in your program.
|
| Also: any modern microcontroller.
| NobodyNada wrote:
| The point is that "the system has only one program
| running at all times" is not an explanation for why
| there's no dynamic memory allocation, because modern
| operating systems use virtual memory to give the illusion
| of a flat address space that the program is in full
| control over. You can use the .data/.bss sections of an
| executable exactly as you would use memory in a SNES
| game.
|
| And in fact, on many game consoles newer than the SNES
| (such as the PS1, N64, GC/Wii, DS/GBA, etc.) there's no
| operating system and the game is in full control of the
| hardware _and_ games frequently and extensively use
| dynamic memory allocation. Whether you manage memory
| statically or dynamically & whether you have an
| operating system or not below your program are almost
| completely orthogonal.
|
| Rather, the reason why SNES games don't use dynamic
| memory management is because it's impossible to do
| efficiently on the SNES's processor. Dynamic memory
| management requires working with pointers, and the 65816
| is really bad at handling pointers for several reasons:
|
| - Registers are 16 bits while (far) addresses are 24
| bits, so pointers to anything besides "data in a specific
| ROM bank" are awkward and slow.
|
| - There are only three general-purpose registers, so
| register pressure is extreme. You _can_ store pointers in
| the direct page to alleviate this, but addressing modes
| relative to direct-page pointers are slow and extremely
| limited.
|
| - There is no adder integrated into the address-
| generation unit. Instructions that access memory at an
| offset from a pointer have to spend an extra clock cycle
| or two going through the ALU.
|
| - Stack operations are slow and limited, so parameter
| passing is a pain and local variables are non-existent.
|
| All of these factors mean that idiomatic and efficient
| 65xx code uses static, global variables at fixed
| addresses for _everything_. When you need dynamism, you
| make an array and index into it instead of making
| general-purpose memory allocations.
|
| But as you get into more modern 32- or 64-bit processors,
| this changes. You have more registers and better
| addressing modes, so the slowness and awkwardness of
| working with pointers is gone; and addresses are so long
| that instructions operating on static memory addresses
| are actually _slower_ due to the increased code size. So,
| idiomatic code for modern processors is pointer-heavy and
| can benefit from dynamic memory allocation.
| reverseblade2 wrote:
| Just use F# to Rust via fable. https://github.com/ncave/fable-
| raytracer
| KallDrexx wrote:
| It's not clear that Fable would fit the constraints, especially
| since F# heavily relies on reference types for discriminated
| unions and lots of other features. It's possible that Fable
| wouldn't be able to compile down to lower end embedded
| platforms, or even things like Linux eBPF applications (like my
| transpiler can do).
___________________________________________________________________
(page generated 2025-05-04 23:02 UTC)