Compiling Rust's `std` to C, emitting .NET debug info - `rustc_codegen_clr` update.
This is a small update on the progress of rustc_codegen_clr - a Rust to .NET compiler backend (which can produce C code too, more on that later).
Debug info
Preserving variable names
Since the last update, I have made some significant progress on emitting Rust debug information in a format that .NET understands. First of all, argument and local variable names are preserved when possible. This should make debugging far easier, but it also improves interop: argument names in signatures are visible in most C# IDEs. This should make using Rust code from C# far easier.
Source information
The codegen now also includes source information. Basically, it tells the .NET runtime which part of the source code any given ops come from.
.line 10:16 './add.rs’
ldc.i8 18446744073709551615
conv.u8
stloc.1
There are still some issues with the source information. Sadly, the issue is incredibly stupid, and, for once, not caused by me!
CIL Hell
According to the CIL spec(ECMA-335, 6th edition), the .line directive should take 3 optional arguments: line, column and file name.
The new versions of ILASM introduced new syntax: they support specifying the line and column as ranges. So you can write something like this:
.line 6,7:8,9 ‘add.rs’
This is a nice addition, but it is not part of the spec and often not available.
The new versions of ILASM do support the old syntax: they just specify the ranges to be empty. So this:
.line 6:8 ‘add.rs’
Is equivalent to this:
.line 6,6 : 8,8 ‘add.rs’
All seems fine, at least for now.
But, the new version also demands that if the line start is equal to line end, then the column start must be smaller than column end. Since 8 is not less than 8, this range:
.line 6,6 : 8,8 ‘add.rs’
// Or this
.line 6:8 ‘add.rs’
is invalid. In fact, any source information provided using the standard, spec-compliant format is rejected by new versions of ilasm.
So, I can either: Comply with the standard and break new ilasm Not comply with the standard, use a syntax extension, and break old (but still widely used) ilasm. Great.
Still, I have a solution in mind. I “just” need to assemble a small test file, and check which syntax works.
Better type names!
On a more positive note, I have greatly improved the Rust to .NET type translation.
I have revamped the code handling type definitions, and types it produces are far more readable.
Previously, I would just use the mangled name of a generic type (e.g. _ZN4core6option6Option17hffa294a4ed847d32E). Now, the types are automatically placed in correct namespaces, and generics are differentiated by a hash at the end of the class name(e.g. core.option.Option.hffa294a4ed847d32). This should make using Rust types in C# easier, since this new naming scheme is much more understandable.
Compiling Rust std to C
The project also supports emitting C source code - it can compile Rust to C. The code producing C is a bit less mature, but I have recently made some progress working on it.
It can now build and use the Rust standard library, with minimal intervention. The “minimal intervention” is just commenting out 3 constants, which get improperly loaded, before building the C source code.
After those fixes get manually applied, the resulting source code can be built using clang and gcc. While there still are some issues with more “advanced” features (such as acquiring anOnceLock), more “basic” things, such as allocating/modifying strings and vectors, already work.
Limitations
There are, of course, some limitations. The quality of generated C code is rather poor. The C_MODE of the backend works by pretending that C is just a really weird .NET runtime. So, the generated C code is a bit… weird(e.g. It calls System_Int128op_Addition to add i128s). The C_MODE is also not meant for anything serious - it is far less tested.
This whole thing started mostly because I jokingly looked into how hard would it be to make rustc_codegen_clr emit C code. Turns out - very easy, so I kind of… just did that. And now my Rust to .NET compiler can create C code, because - why not.
The whole C-specific part is currently under 1K LOC (947 lines in total), and I use it mostly for debugging. Tools for debugging unsafe .NET code are far poorer than tolls meant for testing C - so keeping this weird feature around is justified by this alone. It also helped me discover some more subtle bugs, which were harder to see in .NET.
I plan writing a longer form article on the exact specifics of the Rust to C conversion(e.g. enforcing proper type layout) in the near future.
NOTE: due to some fundamental differences between C and Rust\, it is not possible to covert all valid Rust into UB-free C.\clang and gcc can be configured to relax some requirements(e.g. strict aliasing), eliminating the UB, but not all C compilers support this.
GitHub sponsors
Some people have asked me if I considered using GitHub sponsors to support the project. I have now set that up. So, if this is something you are interested in, here is the link.
If you have any questions/suggestions regarding the project, please feel free to ask. I usually try to respond to all of them.