Code Monkey home page Code Monkey logo

csharpto2600's Introduction

CSharpTo2600

Iteration 3

A compiler and framework for creating Atari 2600 games using C#. It uses the .NET Compiler Platform (Roslyn) to compile C# files, and Mono.Cecil to compile the resulting CIL into 6502 assembler macros.

Current Status

The first iteration of this project compiled C# directly to 6502 assembly.
The second iteration of this project compiled CIL directly to 6502 assembly.
The third (and current) iteration of this project instead compiles CIL to custom macros for the 6502 assembler. This offers a higher level of abstraction to compile to, and makes it easier to optimize the results. We're also now using 6502.Net as the assembler instead of DASM, so every part of the compiler is running on .NET.

Current Goal

The ultimate goal is to add all the features needed for me to port my attempt at a 2600 game to C#. Along the way, I'd like to support as many useful C# features as possible.

Performance won't be as good as programs written in 6502 assembly. But my hope is that the convenience of being able to use C# will be a worthwhile tradeoff for simpler games.

Progress will likely be slow since I have other personal projects I'm also working on.

Example

The samples that currently exist are largely just for developing and testing specific features. The compiler's feature set is not stable yet, so any major samples would likely become obsolete. Below is an example of how you could, at the time of writing, write a program using the StandardTemplate to draw ascending then descending color values.

using VCSFramework;
using VCSFramework.Templates.Standard;
using static VCSFramework.Registers;

namespace Samples
{
    [TemplatedProgram(typeof(StandardTemplate))]
    public static class StandardTemplateSample
    {
        private static byte BackgroundColor;

        [VBlank]
        public static void ResetBackgroundColor() => BackgroundColor = 0;

        [Kernel(KernelType.EveryScanline)]
        [KernelScanlineRange(192, 96)]
        public static void KernelAscend()
        {
            ColuBk = BackgroundColor;
            BackgroundColor++;
        }

        [Kernel(KernelType.EveryScanline)]
        [KernelScanlineRange(96, 0)]
        public static void KernelDescend()
        {
            ColuBk = BackgroundColor;
            BackgroundColor--;
        }
    }
}

This produces the following output:

Features

An incomplete list of supported features in no particular order. Instructions may have various limitations, which I'll try to note.

  • ⭕ Primitive Types
    • ✔️ bool
    • ✔️ byte
    • sbyte
    • ushort
    • short
  • ❌ Array types
  • ✔️ Pointer Types
  • ⭕ Custom Types
    • ✔️ Value Types
      • ✔️ Single-byte types
      • ⭕ Multi-byte types (Not supported by all instructions yet)
      • ✔️ Composite types (struct-in-struct)
      • ✔️ Generic types
    • ⭕ Reference Types
      • ✔️ Static types
      • ❌ Instance types (Probably never)
    • ⭕ Static Members
      • ✔️ Fields
      • ❌ Properties
      • ✔️ Methods
    • ⭕ Instance Members
      • ✔️ Fields
      • ❌ Properties
      • ✔️ Methods
  • ⭕ Ease of Development
    • ⭕ Inline Assembly
      • ✔️ Static field aliasing
    • ⭕ ROM Data Access
      • RomData<> struct
        • ✔️ Constant indexing of types >8-bit in size
        • ❌ Non-constant indexing of types >8-bit in size
        • foreach support
    • ⭕ Program Templates
      • ✔️ RawTemplate
      • StandardTemplate
        • ✔️ VBlank callback
        • ✔️ Overscan callback
        • ⭕ Kernel callback
          • ✔️ Manual
          • ✔️ EveryScanline
          • EveryEvenNumberScanline / EveryOddNumberScanline
        • ✔️ Kernel scanline range
        • ❌ Screen switching support
  • ⭕ CIL OpCodes
    • ⭕ Arithmetic
      • ⭕ Addition (add, 8-bit only)
      • ⭕ Subtraction (sub, 8-bit only)
      • ❌ Division
      • ❌ Multiplication
    • ⭕ Bitwise
      • ⭕ Or (or) (Operands must be same type and 8-bit)
      • ⭕ Negate (neg) (Operand must be 8-bit)
    • ⭕ Branching
      • ✔️ Branch if true (brtrue, brtrue.s)
      • ✔️ Branch if false (brfalse, brfalse.s)
      • ✔️ Unconditional branch (br, br.s)
      • ✔️ Branch if less than (blt, blt.s)
    • ⭕ Comparison
      • ⭕ Equal (ceq) (Operands must be same type and 8-bit)
      • ⭕ Less than (clt) (Operands must be same type and 8-bit)
      • ❌ Greater than (cgt.un)
    • ⭕ Load
      • ✔️ Argument (ldarg, ldarg.s, ldarg.0, ldarg.1, ldarg.2, ldarg.3)
      • ✔️ Constant (ldc.i4, ldc.i4.s, ldc.i4.0, ldc.i4.1,ldc.i4.2,ldc.i4.3,ldc.i4.4,ldc.i4.5,ldc.i4.6,ldc.i4.7,ldc.i4.8) (up to 16-bit)
      • ❌ Element
      • ✔️ Field (static) (ldsfld)
        • ✔️ Address (ldsflda)
      • ⭕ Field (instance) (ldfld) (8-bit only)
        • ⭕ Address (ldflda) (Zero-page pointers only)
      • ⭕ Indirect (ldind.u1)
      • ✔️ Local (ldloc, ldloc.s, ldloc.0, ldloc.1, ldloc.2, ldloc.3)
        • ✔️ Address (ldloca)
      • ✔️ Object (ldobj)
    • ⭕ Store
      • ❌ Argument (starg, starg.s)
      • ❌ Element
      • ✔️ Field (static) (stsfld)
      • ✔️ Field (instance) (stfld)
      • ⭕ Indirect (stind.u1)
      • ✔️ Local (stloc, stloc.s, stloc.0, stloc.1, stloc.2, stloc.3)
      • ❌ Object (stobj)
    • ⭕ Miscellaneous
      • ✔️ Call Method (call)
      • ⭕ Convert (conv.i, conv.u, conv.u1) (treated as NOPs, no extension to int32)
      • ⭕ Duplicate (dup) (8-bit only)
      • ✔️ Initialize value type (initobj)
      • ⭕ Load String (ldstr) (Only supported for very specific scenarios, not general usage)
      • ✔️ NOP (nop) (Nothing emitted)
      • ✔️ Pop Stack (pop)
      • ✔️ Return (ret) (Pending function rework)

Building

Load the solution into Visual Studio Community 2019 and it should build and run fine.

Usage

Invoke VCSCompiler.exe --help for current usage documentation. Documentation at the time of writing:

VCSCompilerCLI:
  A compiler that compiles C# source code into a VCS (Atari 2600) binary.

Usage:
  VCSCompilerCLI [options] [<arguments>...]

Arguments:
  <arguments>    A list of C# source files to compile.

Options:
  --output-path <output-path>                    The path to save the compiled binary to. The same path with a different extension will be used for related files. If a path is not provided, temp files will be used. [default: ]
  --emulator-path <emulator-path>                Path of the emulator executable. If provided, it will be launched with the path to the output binary passed as an argument. [default: ]
  --text-editor-path <text-editor-path>          Path of the text editor executable. If provided, it will be launched with the path to the output ASM file passed as an argument. [default: ]
  --disable-optimizations                        True to disable optimizations. Main use is to observe output of primitive VIL macros and stack operations. Unoptimized code generally will not run correctly due to excessive cycles consumed. [default: False]
  --source-annotations <Both|CIL|CSharp|None>    Whether to include C#, CIL, neither, or both source lines as comments above the VIL macros that they were compiled to. [default: CSharp]
  --version                                      Show version information
  -?, -h, --help                                 Show help and usage information

For running the binary, I recommend using Stella.

License

This project is licensed under the MIT License.

csharpto2600's People

Contributors

yttrmin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

csharpto2600's Issues

Replace DASM dependency

DASM is the only non-.NET dependency in the project. Replacing it with a .NET-compatible assembler will be necessary for full portability.

6502.Net looks to be the best candidate to replace DASM with, though it currently targets .NET Framework.

Given the maturity of DASM, it might be best to abstract out how assembly code is emitted. That way we can easily toggle between DASM and an alternative implementation to gauge if everything works right.

Revisit Testing

Automated tests have been completely neglected and I don't think even compile anymore. Given all the features and optimizations that have been added, it would be very nice to have some tests.

Optimizations

In no particular order:

  • negateFromStack with a constant should be done at compile time.

Support foreach over RomData

A conventional for loop over a RomData<> works great if its type argument has a size of 1 byte. Larger sizes run into a significant issue with indexing. The address of the data goes from a simple byteData[i] to byteData[i * stride]. The 6502 has no multiplication instructions. Which means we'd have to either resort to very slow ways of calculating it, or add lookup tables to the ROM to aid in calculating the index. Neither are particularly great.

We could also add a method to fetch data by byte offset, and try to encourage the user to use something like i += sizeof(MyType), but that's pretty unfriendly.

Other compilers seem to get around this using a "strength reduction" optimizations that change the loop variable to use addition instead of multiplication, basically what's suggested above. That's nothing like our existing peephole optimizations though, so I imagine it'll be very difficult to add it.

A much more idiomatic option though is to support foreach loops over RomData<> types. A naive implementation would probably be 5 bytes in size (pointer to data, length, index, stride), which is pretty expensive memory and CPU-wise. What we might be able to do though, is use code generation to create specialized enumerables for each specific RomData<>.

For example, take a [RomDataGenerator("FooGenerator")]RomData<UserStruct> foo field, where UserStruct is 3 bytes in size. A code generator could see that and generate a RomData_FooGenerator_Iterator struct. RomData_FooGenerator_Iterator could be generated with a RomData<> field marked with [RomDataGenerator("FooGenerator")]RomData<UserStruct> data. This would give it access to the address, length, and stride as compile-time constants (after optimization). All the struct would need to store then is a single byte for the current index.

Given the complexities of strength reduction, and the potential optimizability of the foreach loop, the latter seems like the much more realistic option right now.

Support reading from ROM

It's essential to be able to read from the cartridge ROM, and there's currently no way to do that.

There are a few options to consider for how to implement this:

  • Pointer types (e.g. byte*)
  • Array types (e.g. byte[])
  • Custom type (e.g. a RomData type)

Actually instructing the compiler to insert data into ROM will most likely be done with a RomDataAttribute. It can be placed on a field of one of the above types, and can accept a byte[] as a constructor parameter. The compiler will then place that data in ROM and set the value of the field to refer to that address. It will be best to enforce that such a variable can never be reassigned, so we can always know what address it points to at compile time.

Performance is critical here given that the ROM will often be accessed inside kernels. A simple LDA with absolute addressing will be best when we can statically determine the address and offset. If we can statically determine the address but not the offset, LDA with absolute indexed is the next best option. If we can't statically determine address or offset, it's probably going to be a mess.

RomData Checks

  • Throw if a single generator emits more than 256 bytes.
  • Throw if a single generator emits more than 256 elements.
  • Throw if type argument for RomData<> is non-public (prevents dynamic from working).
  • Throw it type containing generator method is non-public (same reason as above, probably have to traverse whole hierarchy tree).
  • Throw on 0 elements emitted?
  • Throw on RomData<> type being used for local variable or argument.

Redo logging

Logging is a mess and currently consists of a ton of unrelated code just spamming the console, making it very difficult to pick out only the things you care about. We should pull in a proper logging library and redo all of it.

Support Errors

It could be useful to have some rudimentary support for throwing errors at runtime. The errors could be represented by a single color that fills the screen, or an audio tone.

It would be nice to support the throws syntax but it seems unlikely, since you have to throw objects.

Instead, may just want to provide a regular function that has this effect, while leaving memory as untouched as possible (for better debugging).

VCSCompiler VCSCompiler.RomDataUtilities

I'm attempting to build but am getting:

VCSCompiler\Optimizations.cs(7,26,7,42): error CS0234: The type or namespace name 'RomDataUtilities' does not exist in the namespace 'VCSCompiler' (are you missing an assembly reference?)

I've checked the repo and it doesn't seem to exist.

Write an Analyzer

The compiler supports only a subset of the C#, so there are many language features that will never be fully supported. It would be helpful to have an analyzer point out in the IDE if a user tries to use an unsupported feature.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.