[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)