Skip to content

Commit b80ede1

Browse files
JakobDegenfacebook-github-bot
authored andcommitted
module: Hand write virtualization for NativeAttribute
Summary: So Rust's type system has a, uhh, limitation: ``` fn foo<T>(){} fn call_static<U: Fn() + 'static>(u: U) { u(); } pub fn check<'x>() { // Doesn't work: lifetime may not live long enough // call_static(foo::<&'x ()>); call_static(foo::<&'x ()> as fn()); } ``` Function ZSTs, when instantiated with some non-static generics, are not `'static`, while function pointers are. I'm looking to add generics support to `#[starlark_module]`, so think of the `foo` function as being the implementation for the function/attribute that the user wrote. This causes problems. `NativeAttribute` values need to be `'static`-ish, but they also need access to generic parameters that might be very non-static as a result of being `StarlarkValue`s. Working around the limitation above would require me to convert to a function pointer - which I could do, but that would introduce a double virtual call. So to avoid introducing the double virtual call, we explicit split out the `&dyn NativeAttr` into a data value and function pointer. That way, we can use the function pointer that we must explicitly create above to do the single virtual function invocation Hopefully that made sense Reviewed By: Nero5023 Differential Revision: D73726014 fbshipit-source-id: 4ee8f71cda1e0cc7bd10e60d1f270b766825efa5
1 parent f9bdf4c commit b80ede1

File tree

4 files changed

+39
-54
lines changed

4 files changed

+39
-54
lines changed

starlark/src/environment/methods.rs

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ use crate::values::FrozenValue;
3333
use crate::values::FrozenValueTyped;
3434
use crate::values::Heap;
3535
use crate::values::Value;
36-
use crate::values::function::NativeAttr;
3736
use crate::values::function::NativeAttribute;
3837
use crate::values::function::NativeMeth;
3938
use crate::values::function::NativeMethod;
@@ -75,7 +74,7 @@ impl Methods {
7574
/// of a method, this is the `TyCallable`
7675
pub(crate) fn get_ty(&self, name: &str) -> Option<Ty> {
7776
match self.members.get_str(name)? {
78-
UnboundValue::Attr(attr, _) => Some(attr.typ.dupe()),
77+
UnboundValue::Attr(attr) => Some(attr.typ.dupe()),
7978
UnboundValue::Method(method, _) => Some(method.ty.dupe()),
8079
}
8180
}
@@ -171,40 +170,39 @@ impl MethodsBuilder {
171170
) {
172171
// We want to build an attribute, that ignores its self argument, and does no subsequent allocation.
173172
let value = self.heap.alloc(value);
174-
self.set_attribute_fn(
173+
self.members.insert(
175174
name,
176-
true,
177-
docstring,
178-
V::starlark_type_repr(),
179-
move |_, _| Ok(value.to_value()),
175+
UnboundValue::Attr(self.heap.alloc_simple_typed(NativeAttribute {
176+
speculative_exec_safe: true,
177+
docstring,
178+
typ: V::starlark_type_repr(),
179+
data: Some(value),
180+
// SAFETY: Set to `Some` immediately above
181+
callable: |value, _, _| Ok(unsafe { value.unwrap_unchecked() }.to_value()),
182+
})),
180183
);
181184
}
182185

183-
/// Set an attribute. This function is usually called from code
184-
/// generated by `starlark_derive` and rarely needs to be called manually.
185-
pub fn set_attribute_fn<F>(
186+
/// Set an attribute. Only used by `starlark_module` macro
187+
#[doc(hidden)]
188+
pub fn set_attribute_fn(
186189
&mut self,
187190
name: &str,
188191
speculative_exec_safe: bool,
189192
docstring: Option<String>,
190193
typ: Ty,
191-
f: F,
192-
) where
193-
F: for<'v> Fn(Value<'v>, &'v Heap) -> crate::Result<Value<'v>> + Send + Sync + 'static,
194-
{
194+
// The first argument is always `None`
195+
f: for<'v> fn(Option<FrozenValue>, Value<'v>, &'v Heap) -> crate::Result<Value<'v>>,
196+
) {
195197
self.members.insert(
196198
name,
197-
UnboundValue::Attr(
198-
FrozenValueTyped::new(self.heap.alloc(NativeAttribute {
199-
speculative_exec_safe,
200-
docstring,
201-
typ,
202-
}))
203-
.unwrap(),
204-
FrozenRef::<dyn NativeAttr + 'static>::new(
205-
self.heap.alloc_any_debug_type_name(f).as_ref(),
206-
),
207-
),
199+
UnboundValue::Attr(self.heap.alloc_simple_typed(NativeAttribute {
200+
speculative_exec_safe,
201+
docstring,
202+
typ,
203+
data: None,
204+
callable: f,
205+
})),
208206
);
209207
}
210208

starlark/src/values/types/function.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,6 @@ pub trait NativeMeth: Send + Sync + 'static {
114114
) -> crate::Result<Value<'v>>;
115115
}
116116

117-
/// A native function that can be evaluated.
118-
pub trait NativeAttr:
119-
for<'v> Fn(Value<'v>, &'v Heap) -> crate::Result<Value<'v>> + Send + Sync + 'static
120-
{
121-
}
122-
123-
impl<T> NativeAttr for T where
124-
T: for<'v> Fn(Value<'v>, &'v Heap) -> crate::Result<Value<'v>> + Send + Sync + 'static
125-
{
126-
}
127-
128117
/// Starlark representation of native (Rust) functions.
129118
///
130119
/// Almost always created with [`#[starlark_module]`](macro@crate::starlark_module).
@@ -288,20 +277,23 @@ pub(crate) struct NativeAttribute {
288277
pub(crate) speculative_exec_safe: bool,
289278
pub(crate) docstring: Option<String>,
290279
pub(crate) typ: Ty,
280+
/// Essentially a `&dyn Fn(Value, Heap) -> Result<Value>`, but expanded out by hand, and
281+
/// with the `Self` hardcoded to always be `Option<FrozenValue>`
282+
///
283+
/// We don't use a enum over whether the callable below requires a `data` argument as that
284+
/// would introduce an additional branch when calling the attribute
285+
pub(crate) data: Option<FrozenValue>,
286+
#[allocative(skip)] // "Not general enough"
287+
pub(crate) callable:
288+
for<'v> fn(Option<FrozenValue>, Value<'v>, &'v Heap) -> crate::Result<Value<'v>>,
291289
}
292290

293291
starlark_simple_value!(NativeAttribute);
294292

295293
impl NativeAttribute {
296294
#[inline]
297-
pub(crate) fn invoke_method_impl<'v>(
298-
function: &dyn NativeAttr,
299-
this: Value<'v>,
300-
args: &Arguments<'v, '_>,
301-
eval: &mut Evaluator<'v, '_, '_>,
302-
) -> crate::Result<Value<'v>> {
303-
let method = function(this, eval.heap())?;
304-
method.invoke(args, eval)
295+
pub(crate) fn invoke<'v>(&self, this: Value<'v>, heap: &'v Heap) -> crate::Result<Value<'v>> {
296+
(self.callable)(self.data, this, heap)
305297
}
306298
}
307299

starlark/src/values/types/unbound.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ use crate::values::Heap;
3131
use crate::values::Value;
3232
use crate::values::ValueLike;
3333
use crate::values::function::BoundMethodGen;
34-
use crate::values::function::NativeAttr;
3534
use crate::values::function::NativeAttribute;
3635
use crate::values::function::NativeMeth;
3736
use crate::values::function::NativeMethod;
@@ -45,10 +44,7 @@ pub(crate) enum UnboundValue {
4544
FrozenRef<'static, dyn NativeMeth>,
4645
),
4746
/// An attribute with `this` unbound.
48-
Attr(
49-
FrozenValueTyped<'static, NativeAttribute>,
50-
FrozenRef<'static, dyn NativeAttr>,
51-
),
47+
Attr(FrozenValueTyped<'static, NativeAttribute>),
5248
}
5349

5450
impl Debug for UnboundValue {
@@ -62,7 +58,7 @@ impl UnboundValue {
6258
pub(crate) fn to_frozen_value(&self) -> FrozenValue {
6359
match self {
6460
UnboundValue::Method(m, _) => m.to_frozen_value(),
65-
UnboundValue::Attr(a, _) => a.to_frozen_value(),
61+
UnboundValue::Attr(a) => a.to_frozen_value(),
6662
}
6763
}
6864

@@ -73,7 +69,7 @@ impl UnboundValue {
7369
UnboundValue::Method(m, _) => {
7470
Ok(heap.alloc_complex(BoundMethodGen::new(this.to_value(), *m)))
7571
}
76-
UnboundValue::Attr(_, a) => a(this, heap),
72+
UnboundValue::Attr(a) => a.invoke(this, heap),
7773
}
7874
}
7975

@@ -90,9 +86,7 @@ impl UnboundValue {
9086
Some(span),
9187
|eval| match self {
9288
UnboundValue::Method(_, m) => m.invoke(eval, this, args),
93-
UnboundValue::Attr(_, a) => {
94-
NativeAttribute::invoke_method_impl(&**a, this, args, eval)
95-
}
89+
UnboundValue::Attr(a) => a.invoke(this, eval.heap()),
9690
},
9791
)
9892
}

starlark_derive/src/module/render.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ fn render_attr(x: StarAttr) -> syn::Stmt {
144144
let outer: syn::ItemFn = syn::parse_quote! {
145145
#[allow(non_snake_case)]
146146
fn #name<'v>(
147+
_ignored: std::option::Option<starlark::values::FrozenValue>,
147148
#this_value: starlark::values::Value<'v>,
148149
heap: &'v starlark::values::Heap,
149150
) -> starlark::Result<starlark::values::Value<'v>> {

0 commit comments

Comments
 (0)