enables arena based instruction sequence in hackc
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_method.rs
blob926e0c41bc3a72d364bbd1124d784a53bfed3d8a
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 hhbc_by_ref_ast_scope::{self as ast_scope, Lambda, Scope, ScopeItem};
6 use hhbc_by_ref_emit_attribute as emit_attribute;
7 use hhbc_by_ref_emit_body as emit_body;
8 use hhbc_by_ref_emit_fatal::{emit_fatal_runtimeomitframe, raise_fatal_parse};
9 use hhbc_by_ref_emit_memoize_helpers as emit_memoize_helpers;
10 use hhbc_by_ref_emit_native_opcode as emit_native_opcode;
11 use hhbc_by_ref_env::emitter::Emitter;
12 use hhbc_by_ref_hhas_attribute as hhas_attribute;
13 use hhbc_by_ref_hhas_coeffects::HhasCoeffects;
14 use hhbc_by_ref_hhas_method::{HhasMethod, HhasMethodFlags};
15 use hhbc_by_ref_hhas_pos::Span;
16 use hhbc_by_ref_hhbc_id::{method, Id};
17 use hhbc_by_ref_hhbc_string_utils as string_utils;
18 use hhbc_by_ref_instruction_sequence::{instr, Result};
19 use hhbc_by_ref_options::{HhvmFlags, Options};
20 use naming_special_names_rust::{classes, special_idents, user_attributes};
21 use ocamlrep::rc::RcOc;
22 use oxidized::{ast as T, ast_defs};
24 use itertools::Either;
25 use std::borrow::Cow;
27 pub fn from_asts<'a, 'arena>(
28     alloc: &'arena bumpalo::Bump,
29     emitter: &mut Emitter<'arena>,
30     class: &'a T::Class_,
31     methods: &'a [T::Method_],
32 ) -> Result<Vec<HhasMethod<'arena>>> {
33     methods
34         .iter()
35         .map(|m| from_ast(alloc, emitter, class, m))
36         .collect::<Result<Vec<_>>>()
39 pub fn from_ast<'a, 'arena>(
40     alloc: &'arena bumpalo::Bump,
41     emitter: &mut Emitter<'arena>,
42     class: &'a T::Class_,
43     method_: impl Into<Cow<'a, T::Method_>>,
44 ) -> Result<HhasMethod<'arena>> {
45     let method_: Cow<'a, T::Method_> = method_.into();
46     let method = method_.as_ref();
47     let is_memoize = method
48         .user_attributes
49         .iter()
50         .any(|ua| user_attributes::is_memoized(&ua.name.1));
51     let class_name = string_utils::mangle(string_utils::strip_global_ns(&class.name.1).into());
52     let is_closure_body = &method.name.1 == "__invoke" && (class.name.1).starts_with("Closure$");
53     let mut attributes =
54         emit_attribute::from_asts(alloc, emitter, &class.namespace, &method.user_attributes)?;
55     if !is_closure_body {
56         attributes.extend(emit_attribute::add_reified_attribute(&method.tparams[..]));
57     };
58     let call_context = if is_closure_body {
59         match &method.user_attributes[..] {
60             [T::UserAttribute {
61                 name: ast_defs::Id(_, ref s),
62                 params: _,
63             }] if s.eq_ignore_ascii_case("__DynamicMethCallerForce") => {
64                 Some("__SystemLib\\DynamicContextOverrideUnsafe".to_string())
65             }
66             _ => None,
67         }
68     } else {
69         None
70     };
72     let is_native = attributes
73         .iter()
74         .any(|attr| attr.is(user_attributes::is_native));
75     let is_native_opcode_impl = hhas_attribute::is_native_opcode_impl(&attributes);
76     let is_abstract = class.kind.is_cinterface() || method.abstract_;
77     let is_async = method.fun_kind.is_fasync() || method.fun_kind.is_fasync_generator();
78     let is_no_injection = hhas_attribute::is_no_injection(&attributes);
80     if !(method.static_ || is_closure_body) {
81         for p in method.params.iter() {
82             if p.name == special_idents::THIS {
83                 return Err(raise_fatal_parse(&p.pos, "Cannot re-assign $this"));
84             }
85         }
86     };
87     if class.kind.is_cinterface() && !method.body.ast.is_empty() {
88         return Err(raise_fatal_parse(
89             &method.name.0,
90             format!(
91                 "Interface method {}::{} cannot contain body",
92                 class_name, &method.name.1
93             ),
94         ));
95     };
96     if !method.static_ && class.final_ && class.kind.is_cabstract() {
97         return Err(raise_fatal_parse(
98             &method.name.0,
99             format!(
100                 "Class {} contains non-static method {} and therefore cannot be declared 'abstract final'",
101                 class_name, &method.name.1
102             ),
103         ));
104     };
106     let visibility = if is_native_opcode_impl {
107         T::Visibility::Public
108     } else if is_memoize {
109         T::Visibility::Private
110     } else {
111         method.visibility
112     };
113     let deprecation_info = if is_memoize {
114         None
115     } else {
116         hhas_attribute::deprecation_info(attributes.iter())
117     };
118     let default_dropthrough = if method.abstract_ {
119         Some(emit_fatal_runtimeomitframe(
120             alloc,
121             &&method.name.0,
122             format!(
123                 "Cannot call abstract method {}::{}()",
124                 class_name, &method.name.1
125             ),
126         ))
127     } else {
128         None
129     };
130     let mut scope = Scope {
131         items: vec![
132             ScopeItem::Class(ast_scope::Class::new_ref(class)),
133             ScopeItem::Method(match &method_ {
134                 Cow::Borrowed(m) => ast_scope::Method::new_ref(&m),
135                 Cow::Owned(m) => ast_scope::Method::new_rc(&m),
136             }),
137         ],
138     };
139     if is_closure_body {
140         scope.items.push(ScopeItem::Lambda(Lambda {
141             is_async,
142             coeffects: HhasCoeffects::default(),
143         }))
144     };
145     let namespace = RcOc::clone(
146         emitter
147             .emit_global_state()
148             .closure_namespaces
149             .get(&class_name)
150             .unwrap_or(&class.namespace),
151     );
152     let mut coeffects = HhasCoeffects::from_ast(&method.ctxs, &method.params);
153     if method.ctxs == None && is_closure_body {
154         let parent_coeffects = emitter
155             .emit_global_state()
156             .get_lambda_coeffects_of_scope(&class.name.1, &method.name.1);
157         coeffects = parent_coeffects.inherit_to_child_closure()
158     }
159     if is_native_opcode_impl
160         && (class.name.1.as_str() == classes::GENERATOR
161             || class.name.1.as_str() == classes::ASYNC_GENERATOR)
162     {
163         match method.name.1.as_str() {
164             "send" | "raise" | "throw" | "next" | "rewind" => {
165                 coeffects = coeffects.with_gen_coeffect()
166             }
167             _ => {}
168         }
169     }
170     let (ast_body_block, is_rx_body, rx_disabled) = if !coeffects.is_any_rx() {
171         (&method.body.ast, false, false)
172     } else {
173         match hhbc_by_ref_hhas_coeffects::halves_of_is_enabled_body(&method.body) {
174             None => (&method.body.ast, true, false),
175             Some((enabled_body, disabled_body)) => {
176                 if emitter
177                     .options()
178                     .hhvm
179                     .flags
180                     .contains(hhbc_by_ref_options::HhvmFlags::RX_IS_ENABLED)
181                 {
182                     (enabled_body, true, false)
183                 } else {
184                     (disabled_body, false, true)
185                 }
186             }
187         }
188     };
189     let (body, is_generator, is_pair_generator) = if is_native_opcode_impl {
190         (
191             emit_native_opcode::emit_body(
192                 alloc,
193                 emitter,
194                 &scope,
195                 namespace.as_ref(),
196                 &class.user_attributes,
197                 &method.name,
198                 &method.params,
199                 method.ret.1.as_ref(),
200             )?,
201             false,
202             false,
203         )
204     } else {
205         let class_tparam_names = class
206             .tparams
207             .iter()
208             .map(|tp| (tp.name).1.as_str())
209             .collect::<Vec<_>>();
210         let mut flags = emit_body::Flags::empty();
211         flags.set(
212             emit_body::Flags::SKIP_AWAITABLE,
213             method.fun_kind.is_fasync(),
214         );
215         flags.set(emit_body::Flags::MEMOIZE, is_memoize);
216         flags.set(emit_body::Flags::CLOSURE_BODY, is_closure_body);
217         flags.set(emit_body::Flags::NATIVE, is_native);
218         flags.set(emit_body::Flags::RX_BODY, is_rx_body);
219         flags.set(emit_body::Flags::ASYNC, is_async);
220         flags.set(
221             emit_body::Flags::HAS_COEFFECTS_LOCAL,
222             coeffects.has_coeffects_local(),
223         );
224         emit_body::emit_body(
225             alloc,
226             emitter,
227             namespace,
228             Either::Right(ast_body_block),
229             instr::null(alloc),
230             scope,
231             emit_body::Args {
232                 immediate_tparams: &method.tparams,
233                 class_tparam_names: class_tparam_names.as_slice(),
234                 ast_params: &method.params,
235                 ret: method.ret.1.as_ref(),
236                 pos: &method.span,
237                 deprecation_info: &deprecation_info,
238                 doc_comment: method.doc_comment.clone(),
239                 default_dropthrough,
240                 call_context,
241                 flags,
242             },
243         )?
244     };
245     let name = {
246         if is_memoize {
247             method::Type::from_ast_name_and_suffix(
248                 alloc,
249                 &method.name.1,
250                 emit_memoize_helpers::MEMOIZE_SUFFIX,
251             )
252         } else {
253             method::Type::from_ast_name(alloc, &method.name.1)
254         }
255     };
256     let is_interceptable = is_method_interceptable(emitter.options());
257     let span = if is_native_opcode_impl {
258         Span(0, 0)
259     } else {
260         Span::from_pos(&method.span)
261     };
262     let mut flags = HhasMethodFlags::empty();
263     flags.set(HhasMethodFlags::IS_STATIC, method.static_);
264     flags.set(HhasMethodFlags::IS_FINAL, method.final_);
265     flags.set(HhasMethodFlags::IS_ABSTRACT, is_abstract);
266     flags.set(HhasMethodFlags::IS_ASYNC, is_async);
267     flags.set(HhasMethodFlags::IS_GENERATOR, is_generator);
268     flags.set(HhasMethodFlags::IS_PAIR_GENERATOR, is_pair_generator);
269     flags.set(HhasMethodFlags::IS_CLOSURE_BODY, is_closure_body);
270     flags.set(HhasMethodFlags::IS_INTERCEPTABLE, is_interceptable);
271     flags.set(HhasMethodFlags::IS_MEMOIZE_IMPL, is_memoize);
272     flags.set(HhasMethodFlags::RX_DISABLED, rx_disabled);
273     flags.set(HhasMethodFlags::NO_INJECTION, is_no_injection);
274     Ok(HhasMethod {
275         attributes,
276         visibility,
277         name,
278         body,
279         span,
280         coeffects,
281         flags,
282     })
285 fn is_method_interceptable(opts: &Options) -> bool {
286     opts.hhvm
287         .flags
288         .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION)