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 pub mod dump_expr_tree;
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;
20 use bytecode_printer::Context;
21 use decl_provider::DeclProvider;
22 use emit_unit::emit_unit;
23 use env::emitter::Emitter;
28 use ocamlrep::rc::RcOc;
29 use options::HhbcFlags;
32 use options::ParserOptions;
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;
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,
53 pub hhbc_flags: HhbcFlags,
57 impl Default for NativeEnv {
58 fn default() -> Self {
60 filepath: RelativePath::make(Prefix::Dummy, Default::default()),
61 hhvm: Default::default(),
62 hhbc_flags: HhbcFlags::default(),
63 flags: EnvFlags::default(),
68 #[derive(Debug, Default, Clone, clap::Parser, Serialize, Deserialize)]
70 /// Enable features only allowed in systemlib
72 pub is_systemlib: bool,
74 /// Mutate the program as if we're in the debuger REPL
76 pub for_debugger_eval: bool,
78 /// Disable namespace elaboration for toplevel definitions
80 pub disable_toplevel_elaboration: bool,
82 /// Dump IR instead of HHAS
86 /// Compile files using the IR pass
92 fn to_options(&self) -> Options {
95 parser_options: ParserOptions {
96 po_disable_legacy_soft_typehints: false,
97 ..self.hhvm.parser_options.clone()
101 hhbc: self.hhbc_flags.clone(),
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;
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,
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)]
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,
154 pub fn fold(a: Self, b: Self) -> 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,
172 pub fn total_t(&self) -> Duration {
173 self.parser_profile.total_t
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,
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();
206 unit_to_string(native_env, writer, &unit, profile)?;
207 profile.codegen_bytes = alloc.allocated_bytes() as u64;
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 {
224 // Rewrite ok, now emit.
225 emit_unit_from_ast(emitter, namespace_env, ast)
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)
231 ErrorKind::Unrecoverable(x) => Err(Error::unrecoverable(x)),
234 profile.emitter_peak = stack_limit::peak() as u64;
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,
253 profile: &mut Profile,
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)
264 (print_result, profile.printing_t) = profile_rust::time(|| {
266 ir::print_unit(&mut FmtFromIo(writer), &ir, verbose)
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()),
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 {
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);
311 rewrite_and_emit(emitter, namespace_env, ast, profile)
314 fn emit_unit_from_text<'arena, 'decl>(
315 emitter: &mut Emitter<'arena, 'decl>,
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 */
330 .po_disable_xhp_element_mangling,
333 let parse_result = parse_file(
336 !flags.disable_toplevel_elaboration,
337 RcOc::clone(&namespace_env),
343 let ((unit, profile), codegen_t) = match parse_result {
345 elaborate_namespaces_visitor::elaborate_program(RcOc::clone(&namespace_env), &mut ast);
346 profile_rust::time(move || {
348 check_readonly_and_emit(emitter, namespace_env, &mut ast, profile),
353 Err(ParseError(pos, msg, fatal_op)) => {
354 profile_rust::time(move || (emit_fatal(emitter.alloc, fatal_op, pos, msg), profile))
357 profile.codegen_t = codegen_t;
359 Ok(unit) => Ok(unit),
360 Err(e) => Err(anyhow!("Unhandled Emitter error: {}", e)),
364 fn emit_fatal<'arena>(
365 alloc: &'arena bumpalo::Bump,
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>(
375 native_env: &NativeEnv,
376 decl_provider: Option<Arc<dyn DeclProvider<'decl> + 'decl>>,
377 alloc: &'arena bumpalo::Bump,
378 ) -> Emitter<'arena, 'decl> {
380 NativeEnv::to_options(native_env),
382 flags.for_debugger_eval,
388 fn create_parser_options(opts: &Options, type_directed: bool) -> ParserOptions {
391 po_disallow_silence: false,
392 tco_no_parser_readonly_check: type_directed,
393 ..opts.hhvm.parser_options.clone()
397 #[derive(Error, Debug)]
399 pub(crate) struct ParseError(Pos, String, FatalOp);
403 source_text: SourceText<'_>,
404 elaborate_namespaces: bool,
405 namespace_env: RcOc<NamespaceEnv>,
408 profile: &mut Profile,
409 ) -> Result<ast::Program, ParseError> {
410 let aast_env = AastEnv {
412 php5_compat_mode: !opts.hhbc.uvs,
415 elaborate_namespaces,
416 parser_options: create_parser_options(opts, type_directed),
420 let indexed_source_text = IndexedSourceText::new(source_text);
422 AastParser::from_text_with_namespace_env(&aast_env, namespace_env, &indexed_source_text);
424 Err(AastError::Other(msg)) => Err(ParseError(Pos::make_none(), msg, FatalOp::Parse)),
425 Err(AastError::NotAHackFile()) => Err(ParseError(
427 "Not a Hack file".to_string(),
430 Err(AastError::ParserFatal(syntax_error, pos)) => Err(ParseError(
432 syntax_error.message.to_string(),
435 Ok(ast) => match ast {
436 ParserResult { syntax_errors, .. } if !syntax_errors.is_empty() => {
437 let syntax_error = syntax_errors
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);
445 syntax_error.message.to_string(),
446 match syntax_error.error_type {
447 ErrorType::ParseError => FatalOp::Parse,
448 ErrorType::RuntimeError => FatalOp::Runtime,
453 lowerer_parsing_errors,
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))
462 profile: parser_profile,
465 profile.parser_profile = parser_profile;
466 let mut errors = errors.iter().filter(|e| {
468 /* Naming.MethodNeedsVisibility */
470 /* Naming.UnsupportedTraitUseAs */
473 match errors.next() {
474 Some(e) => Err(ParseError(
476 String::from(e.msg()),
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(
494 flags.for_debugger_eval,
498 let ctx = Context::new(&emitter);
500 print_expr::expr_to_string_lossy(ctx, expr)