Rip out legacy reactivity from the typechecker and HackC
[hiphop-php.git] / hphp / hack / src / hhbc / emit_memoize_function.rs
blob254d35107bc3e52090c15aef535ecb30be0361be
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
5 use ast_scope_rust::{self as ast_scope, Scope, ScopeItem};
6 use emit_attribute_rust as emit_attribute;
7 use emit_body_rust as emit_body;
8 use emit_memoize_helpers_rust as emit_memoize_helpers;
9 use emit_param_rust as emit_param;
10 use emit_pos_rust::emit_pos_then;
11 use env::{emitter::Emitter, local, Env};
12 use hhas_body_rust::HhasBody;
13 use hhas_coeffects::HhasCoeffects;
14 use hhas_function_rust::{Flags as HhasFunctionFlags, HhasFunction};
15 use hhas_param_rust::HhasParam;
16 use hhas_pos_rust::Span;
17 use hhas_type::Info as HhasTypeInfo;
18 use hhbc_ast_rust::{FcallArgs, FcallFlags};
19 use hhbc_id_rust::{function::Type as FunId, Id};
20 use hhbc_string_utils_rust::reified;
21 use instruction_sequence_rust::{instr, InstrSeq, Result};
22 use ocamlrep::rc::RcOc;
23 use options::{HhvmFlags, Options, RepoFlags};
24 use oxidized::{ast as T, pos::Pos};
25 use runtime::TypedValue;
27 pub(crate) fn is_interceptable(opts: &Options) -> bool {
28     opts.hhvm
29         .flags
30         .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
31         && !opts.repo_flags.contains(RepoFlags::AUTHORITATIVE)
34 pub(crate) fn emit_wrapper_function<'a>(
35     emitter: &mut Emitter,
36     original_id: FunId<'a>,
37     renamed_id: &FunId,
38     deprecation_info: &Option<&[TypedValue]>,
39     f: &'a T::Fun_,
40 ) -> Result<HhasFunction<'a>> {
41     emit_memoize_helpers::check_memoize_possible(&(f.name).0, &f.params, false)?;
42     let scope = Scope {
43         items: vec![ScopeItem::Function(ast_scope::Fun::new_ref(f))],
44     };
45     let mut tparams = scope
46         .get_tparams()
47         .iter()
48         .map(|tp| tp.name.1.as_str())
49         .collect::<Vec<_>>();
50     let params =
51         emit_param::from_asts(emitter, &mut tparams, &f.namespace, true, &scope, &f.params)?;
52     let mut attributes = emit_attribute::from_asts(emitter, &f.namespace, &f.user_attributes)?;
53     attributes.extend(emit_attribute::add_reified_attribute(&f.tparams));
54     let return_type_info = emit_body::emit_return_type_info(
55         &tparams,
56         f.fun_kind.is_fasync(), /* skip_awaitable */
57         f.ret.1.as_ref(),
58     )?;
59     let is_reified = f
60         .tparams
61         .iter()
62         .any(|tp| tp.reified.is_reified() || tp.reified.is_soft_reified());
63     let mut env = Env::default(RcOc::clone(&f.namespace)).with_scope(scope);
64     // TODO(hrust): avoid cloning
65     let renamed_id_owned: FunId<'static> = renamed_id.to_raw_string().to_string().into();
66     let body_instrs = make_memoize_function_code(
67         emitter,
68         &mut env,
69         &f.span,
70         *deprecation_info,
71         &params,
72         &f.params,
73         renamed_id_owned,
74         f.fun_kind.is_fasync(),
75         is_reified,
76     )?;
77     let coeffects = HhasCoeffects::from_ast(&f.ctxs, &f.params);
78     env.with_rx_body(coeffects.is_any_rx_or_pure());
79     let body = make_wrapper_body(
80         emitter,
81         env,
82         return_type_info,
83         params,
84         body_instrs,
85         is_reified,
86     )?;
88     let mut flags = HhasFunctionFlags::empty();
89     flags.set(HhasFunctionFlags::ASYNC, f.fun_kind.is_fasync());
90     let is_interceptable = is_interceptable(emitter.options());
91     flags.set(HhasFunctionFlags::INTERCEPTABLE, is_interceptable);
93     Ok(HhasFunction {
94         attributes,
95         name: original_id,
96         body,
97         span: Span::from_pos(&f.span),
98         coeffects,
99         flags,
100     })
103 fn make_memoize_function_code(
104     e: &mut Emitter,
105     env: &mut Env,
106     pos: &Pos,
107     deprecation_info: Option<&[TypedValue]>,
108     hhas_params: &[HhasParam],
109     ast_params: &[T::FunParam],
110     renamed_id: FunId<'static>,
111     is_async: bool,
112     is_reified: bool,
113 ) -> Result {
114     let fun = if hhas_params.is_empty() && !is_reified {
115         make_memoize_function_no_params_code(e, env, deprecation_info, renamed_id, is_async)
116     } else {
117         make_memoize_function_with_params_code(
118             e,
119             env,
120             pos,
121             deprecation_info,
122             hhas_params,
123             ast_params,
124             renamed_id,
125             is_async,
126             is_reified,
127         )
128     }?;
129     Ok(emit_pos_then(pos, fun))
132 fn make_memoize_function_with_params_code(
133     e: &mut Emitter,
134     env: &mut Env,
135     pos: &Pos,
136     deprecation_info: Option<&[TypedValue]>,
137     hhas_params: &[HhasParam],
138     ast_params: &[T::FunParam],
139     renamed_id: FunId<'static>,
140     is_async: bool,
141     is_reified: bool,
142 ) -> Result {
143     let param_count = hhas_params.len();
144     let notfound = e.label_gen_mut().next_regular();
145     let suspended_get = e.label_gen_mut().next_regular();
146     let eager_set = e.label_gen_mut().next_regular();
147     // The local that contains the reified generics is the first non parameter local,
148     // so the first local is parameter count + 1 when there are reified = generics
149     let add_refied = usize::from(is_reified);
150     let first_local = local::Type::Unnamed(param_count + add_refied);
151     let deprecation_body =
152         emit_body::emit_deprecation_info(&env.scope, deprecation_info, e.systemlib())?;
153     let (begin_label, default_value_setters) =
154         // Default value setters belong in the wrapper method not in the original method
155         emit_param::emit_param_default_value_setter(e, env, pos, hhas_params)?;
156     let fcall_args = {
157         let mut fcall_flags = FcallFlags::default();
158         fcall_flags.set(FcallFlags::HAS_GENERICS, is_reified);
159         FcallArgs::new(
160             fcall_flags,
161             1,
162             vec![],
163             if is_async {
164                 Some(eager_set.clone())
165             } else {
166                 None
167             },
168             param_count,
169             None,
170         )
171     };
172     let (reified_get, reified_memokeym) = if !is_reified {
173         (instr::empty(), instr::empty())
174     } else {
175         (
176             instr::cgetl(local::Type::Named(reified::GENERICS_LOCAL_NAME.into())),
177             InstrSeq::gather(emit_memoize_helpers::get_memo_key_list(
178                 param_count,
179                 param_count + add_refied,
180                 reified::GENERICS_LOCAL_NAME.into(),
181             )),
182         )
183     };
184     let param_count = (param_count + add_refied) as isize;
185     Ok(InstrSeq::gather(vec![
186         begin_label,
187         emit_body::emit_method_prolog(e, env, pos, hhas_params, ast_params, &[])?,
188         deprecation_body,
189         emit_memoize_helpers::param_code_sets(hhas_params, param_count as usize),
190         reified_memokeym,
191         if is_async {
192             InstrSeq::gather(vec![
193                 instr::memoget_eager(
194                     notfound.clone(),
195                     suspended_get.clone(),
196                     Some((first_local.clone(), param_count)),
197                 ),
198                 instr::retc(),
199                 instr::label(suspended_get),
200                 instr::retc_suspended(),
201             ])
202         } else {
203             InstrSeq::gather(vec![
204                 instr::memoget(notfound.clone(), Some((first_local.clone(), param_count))),
205                 instr::retc(),
206             ])
207         },
208         instr::label(notfound),
209         instr::nulluninit(),
210         instr::nulluninit(),
211         emit_memoize_helpers::param_code_gets(hhas_params),
212         reified_get,
213         instr::fcallfuncd(fcall_args, renamed_id),
214         instr::memoset(Some((first_local.clone(), param_count))),
215         if is_async {
216             InstrSeq::gather(vec![
217                 instr::retc_suspended(),
218                 instr::label(eager_set),
219                 instr::memoset_eager(Some((first_local, param_count))),
220                 instr::retc(),
221             ])
222         } else {
223             InstrSeq::gather(vec![instr::retc()])
224         },
225         default_value_setters,
226     ]))
229 fn make_memoize_function_no_params_code(
230     e: &mut Emitter,
231     env: &mut Env,
232     deprecation_info: Option<&[TypedValue]>,
233     renamed_id: FunId<'static>,
234     is_async: bool,
235 ) -> Result {
236     let notfound = e.label_gen_mut().next_regular();
237     let suspended_get = e.label_gen_mut().next_regular();
238     let eager_set = e.label_gen_mut().next_regular();
239     let deprecation_body =
240         emit_body::emit_deprecation_info(&env.scope, deprecation_info, e.systemlib())?;
241     let fcall_args = FcallArgs::new(
242         FcallFlags::default(),
243         1,
244         vec![],
245         if is_async {
246             Some(eager_set.clone())
247         } else {
248             None
249         },
250         0,
251         None,
252     );
253     Ok(InstrSeq::gather(vec![
254         deprecation_body,
255         if is_async {
256             InstrSeq::gather(vec![
257                 instr::memoget_eager(notfound.clone(), suspended_get.clone(), None),
258                 instr::retc(),
259                 instr::label(suspended_get),
260                 instr::retc_suspended(),
261             ])
262         } else {
263             InstrSeq::gather(vec![instr::memoget(notfound.clone(), None), instr::retc()])
264         },
265         instr::label(notfound),
266         instr::nulluninit(),
267         instr::nulluninit(),
268         instr::fcallfuncd(fcall_args, renamed_id),
269         instr::memoset(None),
270         if is_async {
271             InstrSeq::gather(vec![
272                 instr::retc_suspended(),
273                 instr::label(eager_set),
274                 instr::memoset_eager(None),
275                 instr::retc(),
276             ])
277         } else {
278             instr::retc()
279         },
280     ]))
283 fn make_wrapper_body<'a>(
284     emitter: &mut Emitter,
285     env: Env<'a>,
286     return_type_info: HhasTypeInfo,
287     params: Vec<HhasParam>,
288     body_instrs: InstrSeq,
289     is_reified: bool,
290 ) -> Result<HhasBody<'a>> {
291     emit_body::make_body(
292         emitter,
293         body_instrs,
294         if is_reified {
295             vec![reified::GENERICS_LOCAL_NAME.into()]
296         } else {
297             vec![]
298         },
299         true,   /* is_memoize_wrapper */
300         false,  /* is_memoize_wrapper_lsb */
301         vec![], /* upper_bounds */
302         vec![], /* shadowed_tparams */
303         params,
304         Some(return_type_info),
305         None, /* doc comment */
306         Some(env),
307     )