Tracking Issue for FnStatic and FnPtr::addr/from_ptr/as_ptr and Code rust-lang/rust#148768

Open

21 comments loaded in 0.84s

programmerjake Avatar

View all comments

Feature gate: #![feature(fn_static)]

This is a tracking issue for FnStatic and FnPtr::addr/from_ptr/as_ptr and Code.

Public API

// core::ops

pub extern type Code;

// Implemented only for function pointers.
pub unsafe trait FnPtr {
    fn addr(self) -> usize;
    fn as_ptr(self) -> NonNull<Code>;
    unsafe fn from_ptr(NonNull<Code>) -> Self;
}

// Implemented for all types that coerce to function pointers.
pub unsafe trait FnStatic<Args>: Fn<Args> {
    type FnPtr: FnPtr;
    fn as_fn() -> Self::FnPtr;
    fn addr() -> usize;
    fn as_ptr() -> NonNull<Code>;
    extern "rust-call" fn call_static(args: Args) -> Self::Output;
}

Steps / History

(Remember to update the S-tracking-* label when checking boxes.)

  • ACP: rust-lang/libs-team#589
  • t-lang approval because FnStatic is a new builtin trait that interacts with the language, as well as discussing how to deal with new targets where code pointers are bigger than data pointers.
  • Implementation: #...
  • Final comment period (FCP)1
  • Stabilization PR

Unresolved Questions

  • Is it sufficient to just add type aliases whenever adding a target where code pointers are bigger than data pointers? It was proposed that NonNull<Code> is used for pointing to the code of a function, and that whenever a target where code pointers are larger than data pointers is added, then type aliases are added and APIs are changed to use those type aliases instead of directly using NonNull<Code> or usize when representing code addresses.

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html

programmerjake Avatar
programmerjake Avatar

for reference, some examples of targets we might add where code pointers are bigger than data pointers: rust-lang/libs-team#589 (comment)

joshtriplett Avatar

@programmerjake On those targets, what is the size of size_t? The size of a code pointer or the size of a data pointer?

joshtriplett Avatar

Also, I would expect these traits to be sealed. And if they're sealed, we can always add more to them later.

In which case, we might want to just have addr, while we're working on the rest.

We still have to make some lang decisions about usize and the size of code pointers, data pointers, etc.

programmerjake Avatar

@programmerjake On those targets, what is the size of size_t? The size of a code pointer or the size of a data pointer?

on AVR (MPLAB XC8 here, since that's what the manufacturer has on their website) size_t is 16-bits. a code pointer is 16-bits, a data pointer is either 16 or 24 bits depending on which MCU you're targetting and if the pointer is const. They support more than 64k words of code by essentially making you deal with it yourself by switching out the register that holds the top part of the address in inline asm or something.

on 16-bit x86 in medium mode size_t is 16-bits, a code pointer is 32-bits, and a data pointer is 16-bits.

on MSP430 (I used the docs of MSP430 Optimizing C/C++ Compiler v21.6.0.LTS) it seems the code and data memory models are independently selectable:

code:

model used bits size in bits align in bits
small 16 16 16
large 20 32 16

data:

model data pointer used size data pointer size size_t/ptrdiff_t size
small 16 16 16
restricted 20 32 16
large 20 32 32
programmerjake Avatar

Also, I would expect these traits to be sealed. And if they're sealed, we can always add more to them later.

In which case, we might want to just have addr, while we're working on the rest.

I think we should have FnStatic::as_fn() and FnStatic::call_static() at the very least, they have no design issues afaik.

Having addr without as_ptr seems likely to just encourage creating more technical debt by reinforcing the assumption that data pointers are identical to code pointers. Also, we may want to use extern type Code in #146948 (comment)

tgross35 Avatar

The size_t bit is interesting. The C standard says it is "the unsigned integer type of the result of the sizeof operator", so really it just means you can't have a type that wouldn't fit into a 16-bit address space, even if the space is actually larger. I wonder how this actually plays out, and how it might carries over to Rust. If T is huge then is sizeof(T[2]) UB in C?

Is uintptr_t the size of a data pointer on all these platforms? It sounds like it should be based on the spec.

The following type designates an unsigned integer type, other than a bit-precise integer type, with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer.

And then an integer type the same size as a data pointer is a concept that C doesn't account for. Something like ufnptr may be worth considering as we figure out these types (cc #t-lang > Compat with size_t, ptrdiff_t, intptr_t, fnptr-sized int for some discussion there).

tmandry Avatar

It was proposed that NonNull<Code> is used for pointing to the code of a function, and that whenever a target where code pointers are larger than data pointers is added, then type aliases are added and APIs are changed to use those type aliases instead of directly using NonNull<Code> or usize when representing code addresses.

This seems like a sensible half-measure for making code easy to port to these platforms in the future without increasing the API surface much. What will the docs on Code say? Should we just include a type alias now so it's easy to explain that we might change it on future platforms?

FnStatic is a new builtin trait that interacts with the language

Is there a proposal for what should the interaction with the language should be? If I have F: FnStatic() how do I call it?

traviscross Avatar

We talked about this in the lang call today. It seems to us to call for a lang experiment, and @tmandry volunteered to champion it.

jackh726 Avatar

Adding T-types label - it's not clear to me what types are going to be implementing these traits (I expect there to be builtin impls?). Certainly the "Implemented for all types that coerce to function pointers." comment is pretty hand-wavy.

Not nominating or anything for the team yet, but I expect that when more of the implementation details (or a sketch of implementation) gets ironed out, t-types should get a ping for vibe checks.

programmerjake Avatar

Is there a proposal for what should the interaction with the language should be? If I have F: FnStatic() how do I call it?

how about something like <F>(a, b, c)? if there are no values in scope named F, we could also have F(a, b, c)

tgross35 Avatar

Would (F)(a, b, c) work? To be similar to (struct.field)(a, b, c)

programmerjake Avatar

Would (F)(a, b, c) work? To be similar to (struct.field)(a, b, c)

afaict that wouldn't work any better than F(a, b, c) since the errors you might run into are having some other F in the value namespace since F lives in the type namespace, which is what the <F>(a, b, c) syntax I proposed will handle. You don't have to worry about disambiguating with method syntax since there is no self value, so wrapping the function in () doesn't help. That said, having F act like a function item for calling the FnStatic would be nice, since you can also just pass it into other constructs that need a function, e.g. some_option.map(F), or have it silently coerce to a function pointer.

hanna-kruppe Avatar

<F>(a, b, c) would be new syntax, though, since paths currently can’t end with a <type> segment. Adding new syntax seems overkill for this feature, when F::bikeshed_call(a, b, c) and F::as_fn()(a, b, c) would also work for disambiguating.

It would be nice to be able to use F(a, b, c) when it’s unambiguous. But I wonder how commonly that will be needed, even. I expect that many APIs will require the caller to pass in a value of said type (i.e., call_me_maybe(foo) instead of hypothetical call_me_maybe::<typeof(foo)>(). And since it’s a zero-sized value by definition, there’s little harm in just keeping that value around instead of storing only the type.

programmerjake Avatar

one of the major motivations for FnStatic that isn't prominently mentioned in the ACP is being able to call it without a value, because for some code patterns (e.g. trampolines in the link below) you can't capture anything and can't have extra arguments, see one of the lang team design notes on it: https://github.com/rust-lang/lang-team/blob/98a2a1b432f219bf49e7ab07b68f5ab6657d4771/src/design_notes/fn_type_trait_impls.md

so I think having syntax to do that without having to use perma-unstable extern "rust-call" functions or casting to a function pointer would be useful.

hanna-kruppe Avatar

I don’t see any problem with casting to a function pointer, but if there is one that I’m missing, there’s the option F::bikeshed_call(a, b, c). By that I mean a compiler-generated inherent method (name open to bikeshedding) that has the exact same signature as the function behind the impl FnStatic (i.e., without extern "rust-call" shenanigans). It’s essentially a way to spell F(a, b, c) without the namespacing issues and without inventing new qualified path syntax.

Amanieu Avatar

I think the best solution here would actually be to remove call_static from the trait and instead provide a way to create a new instance of Self via an associated constant. Then all the other methods can be changed to take self which allows them to be used with the dot method call syntax. Calling the function directly is handled by the Fn trait.

lolbinarycat Avatar

I don't see the utility of having addr and as_ptr on the trait itself when they are already accessible on FnPtr. Also seconding the concern of size_of::<fn()>() != size_of::<usize>().

Amanieu Avatar

FnPtr is only implemented on function pointers. FnStatic is implemented on stateless callable types (function definitions, tuple struct constructors, etc).

zesterer Avatar

Thanks so much to everybody working on this! I suggested an extremely similar feature a while back, along with a few motivating needs. Perhaps it's useful to somebody here?

It would also make the implementation of my ffd crate much less scary!

Really very excited to see this introduced, no more assert_eq!(size_of::<F>(), 0)!

carbotaniuman Avatar