Closed karashiiro closed 1 year ago
Looks OK. Quick note: there may be no need to constrain slices to only managed types. The restrictions on pointer types have been greatly loosened in C#11, and they seem to allow for things such as pointers to a local variable holding a reference to a managed type. If I understand the changes correctly, something like this is now legal.
public unsafe void Test() {
object a = new object();
object b = new object();
if(a != b){
Console.WriteLine("References not equal!");
}
object* aptr = &a;
*aptr = b;
if(a == b){
Console.WriteLine("Reference changed using pointers!");
}
}
Not restricting slices would enable us to hold slices of stack-allocated Rust arrays of managed objects:
fn test(){
let a:[MString;4] = ["A".into(),"B".into(),"C".into(),"D".into()];
print_mstrings(&a)
}
fn print_mstrings(slice:&[MString]){
for mstring in slice{
system::console::Console::wrtienln_mstring(mstring);
}
}
Allowing for such uses could be quite benefitial.
That's right, I only blindly added the constraint because Rider gave me a warning for taking the pointer of a managed object. I can see the value in this for sure, though we'd need to be sure everything is pinned before calling a method like that (we'd need to do that either way actually).
To be honest, however, I'm not sure if the unmanaged
generic constraint exists at runtime, given that it just compiles down to an attribute. It might only exist for IDE warnings and be otherwise treated as struct
.
At any rate, agreed - it's worth considering not constraining it at all.
Correct me if I am wrong, but are not managed object pointers equivalent to ref
? They use the same instructions, and I have had some object pointer operations decompile back to references. I believe the official documentation even sometimes refers to ref
as managed pointers. Since I plan to forbid using raw managed references outside the stack, there will not be any need for pinning, since GC will be aware of all pointers and handle them appropriately.
As far as I'm aware, they're not necessarily the same, although they can be. For one thing, pointers to managed objects can be cast arbitrarily and have the actual value of their pointers stored, at which point there can't possibly be any GC-safe way of using them. I've personally written code that abused ref
/pointer conversions before passing things into unmanaged code and gotten burned when generation 2 GC happened.
With that said, I wouldn't be surprised if pointers to managed objects could be replaced with ref
automatically in certain cases (and ref
is GC-safe), but I certainly wouldn't rely on that. If there is compiler magic there, it probably requires that the pointer is known statically to never be cast to another type or be moved to the heap. As a trivial case in which a pointer to a managed object would not be GC-safe, consider an unmanaged function that takes a managed pointer and returns that exact same pointer (sort of like black_box
). Given that the compiler can't know what the unmanaged function did with that pointer, it wouldn't be able to confirm that those conditions are met under any circumstances.
As a somewhat more relevant example (that doesn't reference unmanaged code), a simple unsafe cast to nint
and back can't be handled by the runtime, as far as I know.
Transmutes to and from managed types will be forbidden by the backend, it is not something that is implemented yet, but it should not be that hard (most of the questions are around the issue of error messages).
In the case of the backend, we won't be dealing with unmanaged code for the most part (one of the objectives of the project is to have managed rust code). Additionally, you already need to be quite careful when dealing with FFI in Rust (you should not, for example, pass a Box<T>
trough FFI), so we will be only extending the preexisting safety rules of rust to include more types.
Since using managed types outside the stack will be already forbidden in Rust code, we can guarantee that a managed pointer points only to the stack. If in projection.rs
we forbid getting addresses of fields belonging to managed types (by always getting it by-value), it becomes impossible to have a managed pointer pointing to the managed heap.
I think as long as we can guarantee that managed pointers point only to variables living on the stack, no issues should arise.
There is a bug with the current slice get_Item and set_Item implementation. They generate
ldobj valuetype !0
// And
stobj valuetype !0
instead of
ldobj !0
// And
stobj !0
I am working on a fix.
Fixed in 6956a5d75e2f1cb6436d160ba39e8ff108451b42. The bug turned out to be quite easy to fix: LDObj and STObj only took DotnetTypeRef
as the object to load/Store. Changed to Type, now all edge cases should work out fine.
Closes #13.
This adds support for getting/setting values at indices in slices and arrays. Some of the
stdlib
syntax is a bit awkward right now because of some instructions expectingTypeDef
and others expectingDotnetTypeDef
, but I figured that could be handled in a future PR.For the slice methods, I just wrote a reference implementation in C# (without auto-properties or
StructLayout
, for simplicity) and copied over the .NET output.Codegen comparisons
Reference:
.NET 7.0.401:
rustc_codegen_clr: