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

Low-level/unsafe features? #120

Open
KirillAldashkin opened this issue Jun 21, 2023 · 6 comments
Open

Low-level/unsafe features? #120

KirillAldashkin opened this issue Jun 21, 2023 · 6 comments

Comments

@KirillAldashkin
Copy link

  1. C# is known to have a quite good combination of low-level performance features and a high-level OOP model
  2. Writing the Draco's core library will require some features that will be unsuitable for regular code

This will be a discussion of including (or not) perfomance-related, low-level and unsafe C# features such as:

  1. structs
  2. unsafe context in general
  3. Data pointers (*T) and refs
  4. Function pointers (delegate*<>)
  5. Fixed buffers
  6. stackalloc
@KirillAldashkin
Copy link
Author

KirillAldashkin commented Jun 21, 2023

Just my thoughts:

  1. struct is definitely a must-have feature, but all boxing casts (to object/interface) should be done explicilty (Conversion to object must be explicit #68)
  2. unsafe block is redundant, unsafe modifier for methods/types is ok
  3. C-style pointer manipulation syntax is awkward (* and & are used in arifmetic so some keywords instead will be better).
    Something like struct Ptr<T> should be introduced in core library to be used as a pointer type in Draco. This will unite pointers with rest of type system (in C# pointers are not object).
    No ideas about refs since I don't know their internal implementation very well, but something like ref struct Ref<T> to wrap them into a complete type is wanted too (but there's a problem)
  4. Some analog of struct Ptr<T> from above is also wanted to unite function pointers with rest of type system, but there is a problem too
  5. Good as is, but should use Span<T> instead of a pointer and not be unsafe
  6. Good as is, but should return Span<T> by default

@Binto86
Copy link
Contributor

Binto86 commented Jun 21, 2023

So im going to comment only on the points i know something about:

  • we want structs, probably they will have same definition as normal types, but they will have different keyword or be anotated by a attribute
  • we dont want to have C-like pointers at all and only want Pointer<T>

@WhiteBlackGoose
Copy link
Member

I vote for annotation with attribute for structs.

Something like struct Ptr should be introduced in core library to be used as a pointer type in Draco. This will unite pointers with rest of type system (in C# pointers are not object).

To consider: that's similar to how F# does it, but it may be worth a look

but something like ref struct Ref is wanted too

I say a type annotation as well, like [Struct] and [RefStruct] (could also go with [StackOnlyStruct]).

@LPeter1997
Copy link
Member

Thanks for your input! I generally agree with your points - and admittedly, haven't really thought too deeply on some of them. I definitely like the "leaving the C-heritage syntax behind" mentality,

@KirillAldashkin
Copy link
Author

KirillAldashkin commented Jun 21, 2023

but something like ref struct Ref is wanted too

I say a type annotation as well, like [Struct] and [RefStruct] (could also go with [StackOnlyStruct]).

Didn't talk about ref struct definition syntax here. I meant to introduce the type Ref<T> for wrapping ref T in the same way as Ptr<T> wraps T*. But, as I said earlier, there is a problem with this.

@KirillAldashkin
Copy link
Author

KirillAldashkin commented Jun 22, 2023

How about introducing ref lambdas?

Idea

ref func(a: int32) = a * a

is a ref lambda which type is:

unsafe ref struct RefFunc<TArg, TRet> // some kind of "Func<TArg, TRet>" delegate type
{
    private void* _context;
    private delegate* managed<void*, TArg, TRet> _function;

    public TRet Call(TArg arg) => _function(_context, arg);

    public RefFunc(void* context, delegate* managed<void*, TArg, TRet> function)
    {
        _context = context;
        _function = function;
    }
}

If ref lambda captures some context, then it have the same ref safety rules as scoped ref, otherwise as unscoped ref

Example

func foo(bar: ref (int32) -> unit): unit {
    bar(1)
    bar(2)
}
func main(): unit {
    var counter: int32 = 1
    var baz = ref func(add: int32): unit { counter += add; }
    foo(baz)
    Console.WriteLine(counter)
}

compiles to

static unit foo(RefFunc<int, unit> bar)
{
    bar.Call(1);
    bar.Call(2);
    return unit();
}

[CompilerGenerated]
ref struct _Context
{
    public int counter;
}

[CompilerGenerated]
unsafe static unit _lambda(void* context, int add)
{
    ((_Context*)context)->counter += add;
    return unit();
}

unsafe static unit main()
{
    _Context context = new() { counter = 1 };
    RefFunc<int, unit> baz = new(&context, &_lambda);
    foo(baz);
    Console.WriteLine(context.counter);
    return unit();
}

[CompilerGenerated]
static void _true_main() => main(); // method that returns neither "void" nor "int" can't be an entrypoint

Pros

  1. ref lambdas are fast and allocation-free at instantiating.
  2. Capturing context in ref lambdas is also allocation-free, since they can safely access the stack of the method in which they are declared.

Cons

  1. ref lambdas can't go on heap, so they can't be used in async code
  2. ref lambdas with context can't go outside of the method they are declared in

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

4 participants