Pass through DeclParserOptions created from NativeEnv
[hiphop-php.git] / hphp / hack / src / hackc / compile / compile.rs
blobbb2e5e323e2eef64916edf10d1f813d8bdef36d9
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.
6 pub mod dump_expr_tree;
8 use std::fmt;
9 use std::sync::Arc;
10 use std::time::Duration;
11 use std::time::Instant;
13 use aast_parser::rust_aast_parser_types::Env as AastEnv;
14 use aast_parser::rust_aast_parser_types::ParserProfile;
15 use aast_parser::rust_aast_parser_types::ParserResult;
16 use aast_parser::AastParser;
17 use aast_parser::Error as AastError;
18 use anyhow::anyhow;
19 use anyhow::Result;
20 use bytecode_printer::Context;
21 use decl_provider::DeclProvider;
22 use emit_unit::emit_unit;
23 use env::emitter::Emitter;
24 use error::Error;
25 use error::ErrorKind;
26 use hhbc::FatalOp;
27 use hhbc::Unit;
28 use ocamlrep::rc::RcOc;
29 use options::HhbcFlags;
30 use options::Hhvm;
31 use options::Options;
32 use options::ParserOptions;
33 use oxidized::ast;
34 use oxidized::decl_parser_options::DeclParserOptions;
35 use oxidized::namespace_env::Env as NamespaceEnv;
36 use oxidized::pos::Pos;
37 use oxidized::relative_path::Prefix;
38 use oxidized::relative_path::RelativePath;
39 use parser_core_types::indexed_source_text::IndexedSourceText;
40 use parser_core_types::source_text::SourceText;
41 use parser_core_types::syntax_error::ErrorType;
42 use serde::Deserialize;
43 use serde::Serialize;
44 use thiserror::Error;
45 use types::readonly_check;
46 use types::readonly_nonlocal_infer;
48 /// Common input needed for compilation.
49 #[derive(Debug, Clone, Serialize, Deserialize)]
50 pub struct NativeEnv {
51     pub filepath: RelativePath,
52     pub hhvm: Hhvm,
53     pub hhbc_flags: HhbcFlags,
54     pub flags: EnvFlags,
57 impl Default for NativeEnv {
58     fn default() -> Self {
59         Self {
60             filepath: RelativePath::make(Prefix::Dummy, Default::default()),
61             hhvm: Default::default(),
62             hhbc_flags: HhbcFlags::default(),
63             flags: EnvFlags::default(),
64         }
65     }
68 #[derive(Debug, Default, Clone, clap::Parser, Serialize, Deserialize)]
69 pub struct EnvFlags {
70     /// Enable features only allowed in systemlib
71     #[clap(long)]
72     pub is_systemlib: bool,
74     /// Mutate the program as if we're in the debuger REPL
75     #[clap(long)]
76     pub for_debugger_eval: bool,
78     /// Disable namespace elaboration for toplevel definitions
79     #[clap(long)]
80     pub disable_toplevel_elaboration: bool,
82     /// Dump IR instead of HHAS
83     #[clap(long)]
84     pub dump_ir: bool,
86     /// Compile files using the IR pass
87     #[clap(long)]
88     pub enable_ir: bool,
91 impl NativeEnv {
92     fn to_options(&self) -> Options {
93         Options {
94             hhvm: Hhvm {
95                 parser_options: ParserOptions {
96                     po_disable_legacy_soft_typehints: false,
97                     ..self.hhvm.parser_options.clone()
98                 },
99                 ..self.hhvm.clone()
100             },
101             hhbc: self.hhbc_flags.clone(),
102             ..Default::default()
103         }
104     }
106     pub fn to_decl_parser_options(&self) -> DeclParserOptions {
107         let auto_namespace_map = self.hhvm.aliased_namespaces_cloned().collect();
108         // Keep in sync with getDeclFlags in runtime-option.cpp
109         let lang_flags = &self.hhvm.parser_options;
110         DeclParserOptions {
111             auto_namespace_map,
112             disable_xhp_element_mangling: lang_flags.po_disable_xhp_element_mangling,
113             interpret_soft_types_as_like_types: true,
114             allow_new_attribute_syntax: lang_flags.po_allow_new_attribute_syntax,
115             enable_xhp_class_modifier: lang_flags.po_enable_xhp_class_modifier,
116             php5_compat_mode: true,
117             hhvm_compat_mode: true,
118             ..Default::default()
119         }
120     }
123 /// Compilation profile. All times are in seconds,
124 /// except when they are ignored and should not be reported,
125 /// such as in the case hhvm.log_extern_compiler_perf is false.
126 #[derive(Debug, Default)]
127 pub struct Profile {
128     pub parser_profile: ParserProfile,
130     /// Time in seconds spent in emitter.
131     pub codegen_t: Duration,
133     /// Time in seconds spent in bytecode_printer.
134     pub printing_t: Duration,
136     /// Time taken by bc_to_ir
137     pub bc_to_ir_t: Duration,
139     /// Time taken by ir_to_bc
140     pub ir_to_bc_t: Duration,
142     /// Emitter arena allocation volume in bytes.
143     pub codegen_bytes: u64,
145     /// Peak stack size during codegen
146     pub rewrite_peak: u64,
147     pub emitter_peak: u64,
149     /// Was the log_extern_compiler_perf flag set?
150     pub log_enabled: bool,
153 impl Profile {
154     pub fn fold(a: Self, b: Self) -> Profile {
155         Profile {
156             parser_profile: a.parser_profile.fold(b.parser_profile),
158             codegen_t: a.codegen_t + b.codegen_t,
159             printing_t: a.printing_t + b.printing_t,
160             codegen_bytes: a.codegen_bytes + b.codegen_bytes,
162             bc_to_ir_t: a.bc_to_ir_t + b.bc_to_ir_t,
163             ir_to_bc_t: a.ir_to_bc_t + b.ir_to_bc_t,
165             rewrite_peak: std::cmp::max(a.rewrite_peak, b.rewrite_peak),
166             emitter_peak: std::cmp::max(a.emitter_peak, b.emitter_peak),
168             log_enabled: a.log_enabled | b.log_enabled,
169         }
170     }
172     pub fn total_t(&self) -> Duration {
173         self.parser_profile.total_t
174             + self.codegen_t
175             + self.bc_to_ir_t
176             + self.ir_to_bc_t
177             + self.printing_t
178     }
181 /// Compile Hack source code, write HHAS text to `writer`.
182 /// Update `profile` with stats from any passes that run,
183 /// even if the compiler ultimately returns Err.
184 pub fn from_text<'decl>(
185     writer: &mut dyn std::io::Write,
186     source_text: SourceText<'_>,
187     native_env: &NativeEnv,
188     decl_provider: Option<Arc<dyn DeclProvider<'decl> + 'decl>>,
189     profile: &mut Profile,
190 ) -> Result<()> {
191     let alloc = bumpalo::Bump::new();
192     let path = source_text.file_path().path().to_path_buf();
193     let mut emitter = create_emitter(&native_env.flags, native_env, decl_provider, &alloc);
194     let mut unit = emit_unit_from_text(&mut emitter, &native_env.flags, source_text, profile)?;
196     if native_env.flags.enable_ir {
197         let bc_to_ir_t = Instant::now();
198         let ir = bc_to_ir::bc_to_ir(&unit, &path);
199         profile.bc_to_ir_t = bc_to_ir_t.elapsed();
201         let ir_to_bc_t = Instant::now();
202         unit = ir_to_bc::ir_to_bc(&alloc, ir);
203         profile.ir_to_bc_t = ir_to_bc_t.elapsed();
204     }
206     unit_to_string(native_env, writer, &unit, profile)?;
207     profile.codegen_bytes = alloc.allocated_bytes() as u64;
208     Ok(())
211 fn rewrite_and_emit<'p, 'arena, 'decl>(
212     emitter: &mut Emitter<'arena, 'decl>,
213     namespace_env: RcOc<NamespaceEnv>,
214     ast: &'p mut ast::Program,
215     profile: &'p mut Profile,
216 ) -> Result<Unit<'arena>, Error> {
217     // First rewrite and modify `ast` in place.
218     stack_limit::reset();
219     let result = rewrite_program::rewrite_program(emitter, ast, RcOc::clone(&namespace_env));
220     profile.rewrite_peak = stack_limit::peak() as u64;
221     stack_limit::reset();
222     let unit = match result {
223         Ok(()) => {
224             // Rewrite ok, now emit.
225             emit_unit_from_ast(emitter, namespace_env, ast)
226         }
227         Err(e) => match e.into_kind() {
228             ErrorKind::IncludeTimeFatalException(fatal_op, pos, msg) => {
229                 emit_unit::emit_fatal_unit(emitter.alloc, fatal_op, pos, msg)
230             }
231             ErrorKind::Unrecoverable(x) => Err(Error::unrecoverable(x)),
232         },
233     };
234     profile.emitter_peak = stack_limit::peak() as u64;
235     unit
238 pub fn unit_from_text<'arena, 'decl>(
239     alloc: &'arena bumpalo::Bump,
240     source_text: SourceText<'_>,
241     native_env: &NativeEnv,
242     decl_provider: Option<Arc<dyn DeclProvider<'decl> + 'decl>>,
243     profile: &mut Profile,
244 ) -> Result<Unit<'arena>> {
245     let mut emitter = create_emitter(&native_env.flags, native_env, decl_provider, alloc);
246     emit_unit_from_text(&mut emitter, &native_env.flags, source_text, profile)
249 pub fn unit_to_string(
250     native_env: &NativeEnv,
251     writer: &mut dyn std::io::Write,
252     program: &Unit<'_>,
253     profile: &mut Profile,
254 ) -> Result<()> {
255     if native_env.flags.dump_ir {
256         let ir = bc_to_ir::bc_to_ir(program, native_env.filepath.path());
257         struct FmtFromIo<'a>(&'a mut dyn std::io::Write);
258         impl fmt::Write for FmtFromIo<'_> {
259             fn write_str(&mut self, s: &str) -> fmt::Result {
260                 self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)
261             }
262         }
263         let print_result;
264         (print_result, profile.printing_t) = profile_rust::time(|| {
265             let verbose = false;
266             ir::print_unit(&mut FmtFromIo(writer), &ir, verbose)
267         });
268         print_result?;
269     } else {
270         let print_result;
271         (print_result, profile.printing_t) = profile_rust::time(|| {
272             let opts = NativeEnv::to_options(native_env);
273             bytecode_printer::print_unit(
274                 &Context::new(&opts, Some(&native_env.filepath), opts.array_provenance()),
275                 writer,
276                 program,
277             )
278         });
279         print_result?;
280     }
281     Ok(())
284 fn emit_unit_from_ast<'arena, 'decl>(
285     emitter: &mut Emitter<'arena, 'decl>,
286     namespace: RcOc<NamespaceEnv>,
287     ast: &mut ast::Program,
288 ) -> Result<Unit<'arena>, Error> {
289     emit_unit(emitter, namespace, ast)
292 fn check_readonly_and_emit<'arena, 'decl>(
293     emitter: &mut Emitter<'arena, 'decl>,
294     namespace_env: RcOc<NamespaceEnv>,
295     ast: &mut ast::Program,
296     profile: &mut Profile,
297 ) -> Result<Unit<'arena>, Error> {
298     // TODO: T128303794 Experimental. Add more gating before emitter.decl_provider available in prod.
299     match &emitter.decl_provider {
300         None => (),
301         Some(decl_provider) => {
302             let mut new_ast = readonly_nonlocal_infer::infer(ast, decl_provider.clone());
303             let res = readonly_check::check_program(&mut new_ast, false);
304             // Ignores all errors after the first...
305             if let Some(readonly_check::ReadOnlyError(pos, msg)) = res.into_iter().next() {
306                 return emit_fatal(emitter.alloc, FatalOp::Parse, pos, msg);
307             }
308             *ast = new_ast;
309         }
310     }
311     rewrite_and_emit(emitter, namespace_env, ast, profile)
314 fn emit_unit_from_text<'arena, 'decl>(
315     emitter: &mut Emitter<'arena, 'decl>,
316     flags: &EnvFlags,
317     source_text: SourceText<'_>,
318     profile: &mut Profile,
319 ) -> Result<Unit<'arena>> {
320     profile.log_enabled = emitter.options().log_extern_compiler_perf();
321     let type_directed = emitter.decl_provider.is_some();
323     let namespace_env = RcOc::new(NamespaceEnv::empty(
324         emitter.options().hhvm.aliased_namespaces_cloned().collect(),
325         true, /* is_codegen */
326         emitter
327             .options()
328             .hhvm
329             .parser_options
330             .po_disable_xhp_element_mangling,
331     ));
333     let parse_result = parse_file(
334         emitter.options(),
335         source_text,
336         !flags.disable_toplevel_elaboration,
337         RcOc::clone(&namespace_env),
338         flags.is_systemlib,
339         type_directed,
340         profile,
341     );
343     let ((unit, profile), codegen_t) = match parse_result {
344         Ok(mut ast) => {
345             elaborate_namespaces_visitor::elaborate_program(RcOc::clone(&namespace_env), &mut ast);
346             profile_rust::time(move || {
347                 (
348                     check_readonly_and_emit(emitter, namespace_env, &mut ast, profile),
349                     profile,
350                 )
351             })
352         }
353         Err(ParseError(pos, msg, fatal_op)) => {
354             profile_rust::time(move || (emit_fatal(emitter.alloc, fatal_op, pos, msg), profile))
355         }
356     };
357     profile.codegen_t = codegen_t;
358     match unit {
359         Ok(unit) => Ok(unit),
360         Err(e) => Err(anyhow!("Unhandled Emitter error: {}", e)),
361     }
364 fn emit_fatal<'arena>(
365     alloc: &'arena bumpalo::Bump,
366     fatal_op: FatalOp,
367     pos: Pos,
368     msg: impl AsRef<str> + 'arena,
369 ) -> Result<Unit<'arena>, Error> {
370     emit_unit::emit_fatal_unit(alloc, fatal_op, pos, msg)
373 fn create_emitter<'arena, 'decl>(
374     flags: &EnvFlags,
375     native_env: &NativeEnv,
376     decl_provider: Option<Arc<dyn DeclProvider<'decl> + 'decl>>,
377     alloc: &'arena bumpalo::Bump,
378 ) -> Emitter<'arena, 'decl> {
379     Emitter::new(
380         NativeEnv::to_options(native_env),
381         flags.is_systemlib,
382         flags.for_debugger_eval,
383         alloc,
384         decl_provider,
385     )
388 fn create_parser_options(opts: &Options, type_directed: bool) -> ParserOptions {
389     ParserOptions {
390         po_codegen: true,
391         po_disallow_silence: false,
392         tco_no_parser_readonly_check: type_directed,
393         ..opts.hhvm.parser_options.clone()
394     }
397 #[derive(Error, Debug)]
398 #[error("{0}: {1}")]
399 pub(crate) struct ParseError(Pos, String, FatalOp);
401 fn parse_file(
402     opts: &Options,
403     source_text: SourceText<'_>,
404     elaborate_namespaces: bool,
405     namespace_env: RcOc<NamespaceEnv>,
406     is_systemlib: bool,
407     type_directed: bool,
408     profile: &mut Profile,
409 ) -> Result<ast::Program, ParseError> {
410     let aast_env = AastEnv {
411         codegen: true,
412         php5_compat_mode: !opts.hhbc.uvs,
413         keep_errors: false,
414         is_systemlib,
415         elaborate_namespaces,
416         parser_options: create_parser_options(opts, type_directed),
417         ..AastEnv::default()
418     };
420     let indexed_source_text = IndexedSourceText::new(source_text);
421     let ast_result =
422         AastParser::from_text_with_namespace_env(&aast_env, namespace_env, &indexed_source_text);
423     match ast_result {
424         Err(AastError::Other(msg)) => Err(ParseError(Pos::make_none(), msg, FatalOp::Parse)),
425         Err(AastError::NotAHackFile()) => Err(ParseError(
426             Pos::make_none(),
427             "Not a Hack file".to_string(),
428             FatalOp::Parse,
429         )),
430         Err(AastError::ParserFatal(syntax_error, pos)) => Err(ParseError(
431             pos,
432             syntax_error.message.to_string(),
433             FatalOp::Parse,
434         )),
435         Ok(ast) => match ast {
436             ParserResult { syntax_errors, .. } if !syntax_errors.is_empty() => {
437                 let syntax_error = syntax_errors
438                     .iter()
439                     .find(|e| e.error_type == ErrorType::RuntimeError)
440                     .unwrap_or(&syntax_errors[0]);
441                 let pos = indexed_source_text
442                     .relative_pos(syntax_error.start_offset, syntax_error.end_offset);
443                 Err(ParseError(
444                     pos,
445                     syntax_error.message.to_string(),
446                     match syntax_error.error_type {
447                         ErrorType::ParseError => FatalOp::Parse,
448                         ErrorType::RuntimeError => FatalOp::Runtime,
449                     },
450                 ))
451             }
452             ParserResult {
453                 lowerer_parsing_errors,
454                 ..
455             } if !lowerer_parsing_errors.is_empty() => {
456                 let (pos, msg) = lowerer_parsing_errors.into_iter().next().unwrap();
457                 Err(ParseError(pos, msg, FatalOp::Parse))
458             }
459             ParserResult {
460                 errors,
461                 aast,
462                 profile: parser_profile,
463                 ..
464             } => {
465                 profile.parser_profile = parser_profile;
466                 let mut errors = errors.iter().filter(|e| {
467                     e.code() != 2086
468                         /* Naming.MethodNeedsVisibility */
469                         && e.code() != 2102
470                         /* Naming.UnsupportedTraitUseAs */
471                         && e.code() != 2103
472                 });
473                 match errors.next() {
474                     Some(e) => Err(ParseError(
475                         e.pos().clone(),
476                         String::from(e.msg()),
477                         FatalOp::Parse,
478                     )),
479                     None => Ok(aast),
480                 }
481             }
482         },
483     }
486 pub fn expr_to_string_lossy(flags: &EnvFlags, expr: &ast::Expr) -> String {
487     use print_expr::Context;
489     let opts = Options::default();
490     let alloc = bumpalo::Bump::new();
491     let emitter = Emitter::new(
492         opts,
493         flags.is_systemlib,
494         flags.for_debugger_eval,
495         &alloc,
496         None,
497     );
498     let ctx = Context::new(&emitter);
500     print_expr::expr_to_string_lossy(ctx, expr)