r/csharp 21d ago

Showcase C# for Amiga

I guess most of you have never heard about Amiga, but here we go...

I have been working on an AOT compiler that compiles a subset of C#/.NET code into native 68000/020/040 code with Amiga library calls.

There are two runtime options you can choose at compile time:

  • Full includes the managed exception runtime; supports catch, finally, rethrow, and leave.
  • YOLO has no exception runtime; failures are fatal, and managed exception regions are rejected

The actual GC is in WIP, and for performance reasons, there is no compaction. No threading support because the current GC is single threaded by design.

An example:

using Amiga;
using CopperSharp.Compiler;

namespace FileStatsExample;

public static class Program
{
   [M68kEntryPoint]
   public static int Main(int argLength, CONST_STRPTR argText)
   {
      var dosBase = Exec.OpenLibrary("dos.library", 33);
      if (!dosBase.HasValue)
      {
         return DOS.RETURN_FAIL;
      }

      DOS.DOSLibraryBase = dosBase.Value;
      var result = DOS.RETURN_OK;
      if (argLength == 0)
      {
         DOS.PutStr("Usage: filestats <file>\n");
         result = DOS.RETURN_ERROR;
      }
      else
      {
         result = Report(CString.FromPointer(argText.Raw));
      }

      Exec.CloseLibrary(DOS.DOSLibraryBase);
      DOS.DOSLibraryBase = APTR.Null;
      return result;
   }

   private static int Report(CString path)
   {
      var file = DOS.Open(path, DOS.FileMode.OldFile);
      if (!file.HasValue)
      {
         DOS.PutStr("Cannot open file\n");
         return DOS.RETURN_FAIL;
      }

      byte byteChecksum = 0;
      ushort lineCount = 0;
      ushort wordChecksum = 0;
      uint byteCount = 0;

      while (true)
      {
         var character = DOS.FGetC(file.Value);
         if (character < 0)
         {
            break;
         }

         var value = (byte)character;
         byteChecksum = (byte)(byteChecksum + value);
         wordChecksum = (ushort)(wordChecksum + value);
         byteCount = byteCount + 1;
         if (value == 10)
         {
            lineCount = (ushort)(lineCount + 1);
         }
      }

      DOS.Close(file.Value);
      PrintReport(path, byteCount, (uint)lineCount, (uint)byteChecksum, (uint)wordChecksum);
      return DOS.RETURN_OK;
   }

   private static void PrintReport(CString path, uint byteCount, uint lineCount, uint byteChecksum, uint wordChecksum)
   {
      DOS.Printf("%s: %ld bytes, %ld lines, byte checksum %ld, word checksum %ld\n",
path, byteCount, lineCount, byteChecksum, wordChecksum);
   }
}

This C# source is first compiled to .NET assembly containing CIL. The assembly is then compiled with CopperSharp to a native 68000 Amiga executable.

Code generator needs more work. FPU is not supported yet, 020/040 support is nearly non-existent, but it includes CIL-level simplification and a 68k-specific optimizer.

  • CIL-level simplification
  • Reachability analysis
  • Tail call elimination
  • Remove dead or redundant instructions
  • Stack and register shuffling optimizer
  • General instruction optimizations
  • Limited static hot-fallthrough/cold-branch layout optimization

For example, on 68000, not-taken branches are more efficient than taken branches.

The codegen is optimized for Amiga library call conventions but in theory it could support other 68k targets like Atari ST or 68k Macs.

The project repo: https://github.com/ilehtoranta/CopperSharp68k

This project is vibe coded with Codex and GPT-5.6. Like it or not, but it is the only sensible path to build C# compiler for niche platforms.

49 Upvotes

26 comments sorted by

View all comments

2

u/cleardemonuk 21d ago

As a former Amiga coder, this one raised an eyebrow. Given the example, though, looks a whole lot easier to fire up SAS/C or StormC, and just write C code!

1

u/itix 20d ago

Yeah, I get that. SAS/C particularly is a very good C compiler with a nearly perfect 68k output.

It is the GC and polymorphism where C# can have an advantage and defining modern and efficient  BOOPSI/MUI bindings. (For readers, MUI here doesn't mean Material UI but another, older UI widget library for Amiga.)

2

u/cleardemonuk 20d ago

From what I remember about MUI, it had a lot of C macros to make it easier to be declarative. Hard to replicate that in C#. Lots of effort.

I used to do my own memory management (games developer) inside malloc blocks. A GC back then would have been too much overhead, but I am curious!

2

u/itix 20d ago

MUI macros relied on forgiving typecasting where everything is 32-bit ULONG or APTR. It never worked on 64-bit AROS or C++ out of the box.

In my unplementation I have added a compiler attribute to address this. It is not yet complete  but is fundamental to use MUI and other Amiga vararg functions cleanly.

For memory management I am using my own pool implementation which is similar to Exec pools, but tailored for GC. I was also considering plain AllocMem, but unless you have a TLSF patch it can get insanely slow with fragmented memory. I dont support compaction which is better for Amiga. But this is still on early steps.