Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feedback: Simpler Float-to-Integer conversion #39

Open
neon-sunset opened this issue May 19, 2024 · 4 comments
Open

Feedback: Simpler Float-to-Integer conversion #39

neon-sunset opened this issue May 19, 2024 · 4 comments

Comments

@neon-sunset
Copy link

neon-sunset commented May 19, 2024

While reading https://fractalfir.github.io/generated_html/rustc_codegen_clr_v0_1_1.html I noticed a section on float to integer conversions.

As of .NET 8, all numeric types implement INumberBase<TSelf> interface.
It exposes the following members:

partial interface INumberBase<TSelf>
{
        static TSelf CreateChecked<TOther>(TOther value) where TOther : INumberBase<TOther>;
        static TSelf CreateSaturating<TOther>(TOther value) where TOther : INumberBase<TOther>;
        static TSelf CreateTruncating<TOther>(TOther value) where TOther : INumberBase<TOther>;
}

Making the following code valid:

var f32 = 257.45f;
var u8 = byte.CreateSaturating(f32);

These can be targeted for numeric conversions of desired behavior instead, and should they receive further optimizations, these improvements would carry over to this project.

p.s.: on dead code elimination - .NET has built-in trimmer/linker which can prune unreachable members, including statics. But if you're aware of it and want to implement the logic separately, then apologies for the spam. All in all very impressive project, keep up good work and thank you.

@FractalFir
Copy link
Owner

Hi!

But if you're aware of it and want to implement the logic separately, then apologies for the spam.
No worries! I always like to talk about this project :).

I am aware of INumberBase(somebody told me about it before), but I choose to not use it for compatibility purposes.

I try to keep my project compatible with Mono(an older .NET runtime), because it is better than CoreCLR for debugging.
Mono only supports .NET 4.6, so I try to avoid using new .NET APIs. This way, I am compatible with all .NET runtimes.

I did not know about the .NET trimmer, so thanks for letting me know. Dead code elimination in the project is currently WIP, so it may help me keep the assembly size down.

@neon-sunset
Copy link
Author

neon-sunset commented May 20, 2024

Mono is definitely compatible with the latest version. It is now part of dotnet/runtime here: https://github.com/dotnet/runtime/tree/main/src/mono (AFAIK it is tricker in terms of initial setup - .NET really doesn't want people using Mono over CoreCLR when it's an option heh, the gap in performance is just too big)

It's used for various, more exotic targets, like WASM and iOS (until NativeAOT becomes sufficiently good and non-experimental there).

I assume features like static abstract interface members* (enabling T.Parse(text)) and span overloads which can 1:1 handle Rust's slices are desirable to have for cleaner integration, which are only available on newer TFMs.

* (perhaps they could provide better lowering strategy for zero-sized types, lifting all members on such structs to static and making them just a part of generic signature, avoiding having to emit a field or, worst case, emitting it as property of Value => default(TZeroSized)? though it's probably best I stop shooting in the air and look at implementation first 😅)

@FractalFir
Copy link
Owner

FractalFir commented May 21, 2024

Huh. Do you have any links/sources about setting up mono for newer versions of .NET? That could be really helpful for debugging.

As for using spans to implement Rust slices, I have thought about that, it is possible, but not something I want to do right now. Due to the way Rust works, all fat pointers (slices and &dyn Trait) share their implementation. So, switching to spans would require decoupling them, and implementing certain things twice.

Besides that, Spans use managed references (as opposed to raw pointers used by the current implementation). That could cause some GC issues, so I want to avoid that for now. There is also the problem of using pointers as generic arguments being forbidden, which would force me to only use spans for slices of non-pointer types, and use the current implementation for pointer slices. That would further complicate things.

Currently, the plan is to only have the Rust side be "aware" of the interop layer. All interop code would be written in Rust, and people using Rust crates from .NET would not need to know about the implementation details.

So, I plan to add support for easily converting Rust slices to Spans, but I want them to still remain distinct types to prevent some common mistakes.

Issues with mixing spans and solution to those problems

In Rust, you can safely store a reference to slice on the unmanaged heap:

let sref:Box<&[u8]> = Box::new(slice_ref);

Doing the same with spans would cause GC issues.
So, a cast from a slice to a span would require pinning the underlying memory for the lifetime of that slice.
If slices and spans are separate, implementing this is easy:

fn accepts_span<'a>(span:Span<u8,'a>){
// Safely pins the GC managed memory, and keeps GC from collecting it while the slice reference exists
let slice:&[u8] = span.gc_pin_into_slice();
// Rust will now ensure the slice does not outlive the span. Span can't be moved off stack (enforced by the codegen), so 
// we can't cause GC issues.
// As soon as the slice is droped, we unpin the memory(using the `Drop` trait) to let GC do its thing
drop(slice);
}

This is safe, and makes people aware of the potential cost of gc_pin_into_slice.

though it's probably best I stop shooting in the air and look at implementation first 😅

Don't worry :). A shot in the air can still hit.

Besides, this was just a thing I never thought about too deeply. Explain stuff like this gives me the opportunity to better shape the future APIs, and get some valuable feedback early.

Let me know if you have any other questions about the project. I am currently working on improving the internal documentation (you should be able to see it using cargo doc --open).

Things like the reasoning behind my weird implementation of slices probably should be there, so your questions already helped me improve the project a bit :).

@hez2010
Copy link

hez2010 commented Nov 1, 2024

.NET doesn't ship prebuilt binary of mono for platforms other than Android, iOS, and WASM.
You need to build it from the source:

git clone https://github.com/dotnet/runtime
cd runtime
./build.sh -s clr+libs+mono -c Release
./src/tests/build.cs Release mono generatelayoutonly

Then it will generate a Core_Root at artifacts/tests/coreclr/<os>.<arch>.Release/Tests/Core_Root.
Then you can use corerun to run and debug the managed dll.
btw the generated folders and binaries are still named coreclr but the underlying is mono, because modern .NET unified the executable file name of both coreclr and mono to be coreclr.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants