1 // Copyright (c) Facebook, Inc. and its affiliates.
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 use ast_scope::{self, Scope, ScopeItem};
7 use emit_pos::emit_pos_then;
8 use env::{emitter::Emitter, Env};
10 use ffi::{Slice, Str};
12 hhas_attribute::{self, HhasAttribute},
14 hhas_coeffects::HhasCoeffects,
15 hhas_function::{HhasFunction, HhasFunctionFlags},
16 hhas_param::HhasParam,
18 hhas_type::HhasTypeInfo,
19 FCallArgs, FCallArgsFlags, Label, Local, LocalRange, TypedValue,
21 use hhbc_string_utils::reified;
22 use hhvm_types_ffi::ffi::Attr;
23 use instruction_sequence::{instr, InstrSeq};
24 use ocamlrep::rc::RcOc;
25 use options::{HhvmFlags, Options, RepoFlags};
26 use oxidized::{ast as T, pos::Pos};
28 pub fn is_interceptable(opts: &Options) -> bool {
31 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)
32 && !opts.repo_flags.contains(RepoFlags::AUTHORITATIVE)
35 pub(crate) fn get_attrs_for_fun<'a, 'arena, 'decl>(
36 emitter: &mut Emitter<'arena, 'decl>,
38 user_attrs: &'a [HhasAttribute<'arena>],
39 is_memoize_impl: bool,
42 let is_systemlib = emitter.systemlib();
44 is_systemlib || (hhas_attribute::has_dynamically_callable(user_attrs) && !is_memoize_impl);
45 let is_prov_skip_frame = hhas_attribute::has_provenance_skip_frame(user_attrs);
46 let is_meth_caller = hhas_attribute::has_meth_caller(user_attrs);
48 let mut attrs = Attr::AttrNone;
49 attrs.set(Attr::AttrBuiltin, is_meth_caller | is_systemlib);
50 attrs.set(Attr::AttrDynamicallyCallable, is_dyn_call);
51 attrs.set(Attr::AttrInterceptable, is_interceptable(emitter.options()));
54 hhas_attribute::has_foldable(user_attrs),
56 attrs.set(Attr::AttrIsMethCaller, is_meth_caller);
58 Attr::AttrNoInjection,
59 hhas_attribute::is_no_injection(user_attrs),
61 attrs.set(Attr::AttrPersistent, is_systemlib);
62 attrs.set(Attr::AttrProvenanceSkipFrame, is_prov_skip_frame);
63 attrs.set(Attr::AttrReadonlyReturn, f.readonly_ret.is_some());
64 attrs.set(Attr::AttrUnique, is_systemlib);
65 attrs.set(Attr::AttrInternal, fd.internal);
69 pub(crate) fn emit_wrapper_function<'a, 'arena, 'decl>(
70 emitter: &mut Emitter<'arena, 'decl>,
71 original_id: hhbc::FunctionName<'arena>,
72 renamed_id: &hhbc::FunctionName<'arena>,
73 deprecation_info: Option<&[TypedValue<'arena>]>,
75 ) -> Result<HhasFunction<'arena>> {
76 let alloc = emitter.alloc;
78 emit_memoize_helpers::check_memoize_possible(&(f.name).0, &f.params, false)?;
80 items: vec![ScopeItem::Function(ast_scope::Fun::new_ref(fd))],
82 let mut tparams = scope
85 .map(|tp| tp.name.1.as_str())
87 let params = emit_param::from_asts(emitter, &mut tparams, true, &scope, &f.params)?;
88 let mut attributes = emit_attribute::from_asts(emitter, &f.user_attributes)?;
89 attributes.extend(emit_attribute::add_reified_attribute(alloc, &f.tparams));
90 let return_type_info = emit_body::emit_return_type_info(
93 f.fun_kind.is_fasync(), /* skip_awaitable */
99 .any(|tp| tp.reified.is_reified() || tp.reified.is_soft_reified());
100 let should_emit_implicit_context = emitter
104 .contains(HhvmFlags::ENABLE_IMPLICIT_CONTEXT)
105 && attributes.iter().any(|a| {
106 naming_special_names_rust::user_attributes::is_memoized_policy_sharded(
107 a.name.unsafe_as_str(),
110 let mut env = Env::default(alloc, RcOc::clone(&fd.namespace)).with_scope(scope);
111 let (body_instrs, decl_vars) = make_memoize_function_code(
119 f.fun_kind.is_fasync(),
121 should_emit_implicit_context,
123 let coeffects = HhasCoeffects::from_ast(alloc, f.ctxs.as_ref(), &f.params, &f.tparams, vec![]);
124 let body = make_wrapper_body(
133 let mut flags = HhasFunctionFlags::empty();
134 flags.set(HhasFunctionFlags::ASYNC, f.fun_kind.is_fasync());
135 let attrs = get_attrs_for_fun(emitter, fd, &attributes, false);
138 attributes: Slice::fill_iter(alloc, attributes.into_iter()),
141 span: HhasSpan::from_pos(&f.span),
148 fn make_memoize_function_code<'a, 'arena, 'decl>(
149 e: &mut Emitter<'arena, 'decl>,
150 env: &mut Env<'a, 'arena>,
152 deprecation_info: Option<&[TypedValue<'arena>]>,
153 hhas_params: &[(HhasParam<'arena>, Option<(Label, T::Expr)>)],
154 ast_params: &[T::FunParam],
155 renamed_id: hhbc::FunctionName<'arena>,
158 should_emit_implicit_context: bool,
159 ) -> Result<(InstrSeq<'arena>, Vec<Str<'arena>>)> {
160 let (fun, decl_vars) = if hhas_params.is_empty() && !is_reified && !should_emit_implicit_context
162 make_memoize_function_no_params_code(e, env, deprecation_info, renamed_id, is_async)
164 make_memoize_function_with_params_code(
174 should_emit_implicit_context,
177 Ok((emit_pos_then(pos, fun), decl_vars))
180 fn make_memoize_function_with_params_code<'a, 'arena, 'decl>(
181 e: &mut Emitter<'arena, 'decl>,
182 env: &mut Env<'a, 'arena>,
184 deprecation_info: Option<&[TypedValue<'arena>]>,
185 hhas_params: &[(HhasParam<'arena>, Option<(Label, T::Expr)>)],
186 ast_params: &[T::FunParam],
187 renamed_id: hhbc::FunctionName<'arena>,
190 should_emit_implicit_context: bool,
191 ) -> Result<(InstrSeq<'arena>, Vec<Str<'arena>>)> {
193 let param_count = hhas_params.len();
194 let notfound = e.label_gen_mut().next_regular();
195 let suspended_get = e.label_gen_mut().next_regular();
196 let eager_set = e.label_gen_mut().next_regular();
197 // The local that contains the reified generics is the first non parameter local,
198 // so the first unnamed local is parameter count + 1 when there are reified generics.
199 let add_reified = usize::from(is_reified);
200 let add_implicit_context = usize::from(should_emit_implicit_context);
201 let generics_local = Local::new(param_count); // only used if is_reified == true.
202 let decl_vars = match is_reified {
203 true => vec![reified::GENERICS_LOCAL_NAME.into()],
209 .map(|(param, _)| param.name)
210 .chain(decl_vars.iter().copied()),
212 let first_unnamed_idx = param_count + add_reified;
213 let deprecation_body =
214 emit_body::emit_deprecation_info(alloc, &env.scope, deprecation_info, e.systemlib())?;
215 let (begin_label, default_value_setters) =
216 // Default value setters belong in the wrapper method not in the original method
217 emit_param::emit_param_default_value_setter(e, env, pos, hhas_params)?;
219 let mut fcall_flags = FCallArgsFlags::default();
220 fcall_flags.set(FCallArgsFlags::HasGenerics, is_reified);
227 if is_async { Some(eager_set) } else { None },
231 let (reified_get, reified_memokeym) = if !is_reified {
232 (instr::empty(), instr::empty())
235 instr::c_get_l(generics_local),
236 InstrSeq::gather(emit_memoize_helpers::get_memo_key_list(
237 Local::new(param_count + first_unnamed_idx),
242 let ic_memokey = if !should_emit_implicit_context {
245 // Last unnamed local slot
246 let local = Local::new(first_unnamed_idx + param_count + add_reified);
247 emit_memoize_helpers::get_implicit_context_memo_key(alloc, local)
249 let first_unnamed_local = Local::new(first_unnamed_idx);
250 let key_count = (param_count + add_reified + add_implicit_context) as isize;
251 let local_range = LocalRange {
252 start: first_unnamed_local,
253 len: key_count.try_into().unwrap(),
255 let instrs = InstrSeq::gather(vec![
257 emit_body::emit_method_prolog(e, env, pos, hhas_params, ast_params, &[])?,
259 emit_memoize_helpers::param_code_sets(hhas_params.len(), Local::new(first_unnamed_idx)),
263 InstrSeq::gather(vec![
264 instr::memo_get_eager(notfound, suspended_get, local_range),
266 instr::label(suspended_get),
267 instr::ret_c_suspended(),
270 InstrSeq::gather(vec![instr::memo_get(notfound, local_range), instr::ret_c()])
272 instr::label(notfound),
273 instr::null_uninit(),
274 instr::null_uninit(),
275 emit_memoize_helpers::param_code_gets(hhas_params.len()),
277 instr::f_call_func_d(fcall_args, renamed_id),
278 instr::memo_set(local_range),
280 InstrSeq::gather(vec![
281 instr::ret_c_suspended(),
282 instr::label(eager_set),
283 instr::memo_set_eager(local_range),
289 default_value_setters,
291 Ok((instrs, decl_vars))
294 fn make_memoize_function_no_params_code<'a, 'arena, 'decl>(
295 e: &mut Emitter<'arena, 'decl>,
296 env: &mut Env<'a, 'arena>,
297 deprecation_info: Option<&[TypedValue<'arena>]>,
298 renamed_id: hhbc::FunctionName<'arena>,
300 ) -> Result<(InstrSeq<'arena>, Vec<Str<'arena>>)> {
302 let notfound = e.label_gen_mut().next_regular();
303 let suspended_get = e.label_gen_mut().next_regular();
304 let eager_set = e.label_gen_mut().next_regular();
305 let deprecation_body =
306 emit_body::emit_deprecation_info(alloc, &env.scope, deprecation_info, e.systemlib())?;
307 let fcall_args = FCallArgs::new(
308 FCallArgsFlags::default(),
313 if is_async { Some(eager_set) } else { None },
316 let instrs = InstrSeq::gather(vec![
319 InstrSeq::gather(vec![
320 instr::memo_get_eager(notfound, suspended_get, LocalRange::default()),
322 instr::label(suspended_get),
323 instr::ret_c_suspended(),
326 InstrSeq::gather(vec![
327 instr::memo_get(notfound, LocalRange::default()),
331 instr::label(notfound),
332 instr::null_uninit(),
333 instr::null_uninit(),
334 instr::f_call_func_d(fcall_args, renamed_id),
335 instr::memo_set(LocalRange::default()),
337 InstrSeq::gather(vec![
338 instr::ret_c_suspended(),
339 instr::label(eager_set),
340 instr::memo_set_eager(LocalRange::default()),
347 Ok((instrs, Vec::new()))
350 fn make_wrapper_body<'a, 'arena, 'decl>(
351 emitter: &mut Emitter<'arena, 'decl>,
352 env: Env<'a, 'arena>,
353 return_type_info: HhasTypeInfo<'arena>,
354 params: Vec<(HhasParam<'arena>, Option<(Label, T::Expr)>)>,
355 decl_vars: Vec<Str<'arena>>,
356 body_instrs: InstrSeq<'arena>,
357 ) -> Result<HhasBody<'arena>> {
358 emit_body::make_body(
363 true, /* is_memoize_wrapper */
364 false, /* is_memoize_wrapper_lsb */
365 vec![], /* upper_bounds */
366 vec![], /* shadowed_tparams */
368 Some(return_type_info),
369 None, /* doc comment */