Skip to content

Commit 0920e98

Browse files
committed
feat(codegen): keep arrow function PIFEs (#12353)
PIFE is the abbreviation of "Possibly-Invoked Function Expressions". It is a function expression wrapped with a parenthesized expression. PIFEs annotate functions that are likely to be invoked eagerly. When v8 encounters such expressions, it compiles them eagerly (rather than compiling it later). See [v8's blog post](https://v8.dev/blog/preparser#pife) for more details. The blog post only mentions regular FunctionExpressions, but ArrowFunctions are also supported. The cases that will be eagerly compiled are: - when `!` comes before function literals ([code](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L3730-L3733)) (e.g. `!function(){}`) - when a function expression is wrapped with parenthesis ([code](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L2243-L2248)) (e.g. `(function () {})`, `(async function () {})`) - when an arrow function is wrapped with parenthesis ([code1](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L2173-L2174), [code2](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L2232-L2259), [code3](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L3297-L3298)) (e.g. `console.log((() => {}))`) - when `()` or ``` `` ``` (tagged templates) comes after function literals (only in some cases) ([code1](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L3987-L3992), [code2](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L4344-L4348)) (e.g. `~function(){}()`, ```~function(){}`` ```) - when [explicit compile hints](https://v8.dev/blog/explicit-compile-hints) are used ([code1](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser.cc#L2717-L2720), [code2](https://github.com/v8/v8/blob/328f6c467b940f322544567740c9c871064d045c/src/parsing/parser-base.h#L5070-L5073)) (e.g. `//# allFunctionsCalledOnLoad`) Keeping PIFEs as-is in the code generation will unlock optimizations like rolldown/rolldown#5319. _Note: this PR only implements arrow function PIFEs for now._
1 parent 998c67b commit 0920e98

File tree

20 files changed

+101
-28
lines changed

20 files changed

+101
-28
lines changed

crates/oxc_ast/src/ast/js.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,6 +1935,14 @@ pub struct ArrowFunctionExpression<'a> {
19351935
#[builder(default)]
19361936
#[estree(skip)]
19371937
pub pure: bool,
1938+
#[builder(default)]
1939+
#[estree(skip)]
1940+
/// `true` if the function should be marked as "Possibly-Invoked Function Expression" (PIFE).
1941+
///
1942+
/// References:
1943+
/// - v8 blog post about PIFEs: <https://v8.dev/blog/preparser#pife>
1944+
/// - introduced PR: <https://github.com/oxc-project/oxc/pull/12353>
1945+
pub pife: bool,
19381946
}
19391947

19401948
/// Generator Function Definitions

crates/oxc_ast/src/generated/assert_layouts.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ const _: () = {
617617
assert!(offset_of!(FunctionBody, directives) == 8);
618618
assert!(offset_of!(FunctionBody, statements) == 32);
619619

620-
// Padding: 1 bytes
620+
// Padding: 0 bytes
621621
assert!(size_of::<ArrowFunctionExpression>() == 48);
622622
assert!(align_of::<ArrowFunctionExpression>() == 8);
623623
assert!(offset_of!(ArrowFunctionExpression, span) == 0);
@@ -629,6 +629,7 @@ const _: () = {
629629
assert!(offset_of!(ArrowFunctionExpression, body) == 32);
630630
assert!(offset_of!(ArrowFunctionExpression, scope_id) == 40);
631631
assert!(offset_of!(ArrowFunctionExpression, pure) == 46);
632+
assert!(offset_of!(ArrowFunctionExpression, pife) == 47);
632633

633634
// Padding: 7 bytes
634635
assert!(size_of::<YieldExpression>() == 32);
@@ -2207,7 +2208,7 @@ const _: () = {
22072208
assert!(offset_of!(FunctionBody, directives) == 8);
22082209
assert!(offset_of!(FunctionBody, statements) == 24);
22092210

2210-
// Padding: 1 bytes
2211+
// Padding: 0 bytes
22112212
assert!(size_of::<ArrowFunctionExpression>() == 32);
22122213
assert!(align_of::<ArrowFunctionExpression>() == 4);
22132214
assert!(offset_of!(ArrowFunctionExpression, span) == 0);
@@ -2219,6 +2220,7 @@ const _: () = {
22192220
assert!(offset_of!(ArrowFunctionExpression, body) == 20);
22202221
assert!(offset_of!(ArrowFunctionExpression, scope_id) == 24);
22212222
assert!(offset_of!(ArrowFunctionExpression, pure) == 30);
2223+
assert!(offset_of!(ArrowFunctionExpression, pife) == 31);
22222224

22232225
// Padding: 3 bytes
22242226
assert!(size_of::<YieldExpression>() == 20);

crates/oxc_ast/src/generated/ast_builder.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ impl<'a> AstBuilder<'a> {
364364
))
365365
}
366366

367-
/// Build an [`Expression::ArrowFunctionExpression`] with `scope_id` and `pure`.
367+
/// Build an [`Expression::ArrowFunctionExpression`] with `scope_id` and `pure` and `pife`.
368368
///
369369
/// This node contains an [`ArrowFunctionExpression`] that will be stored in the memory arena.
370370
///
@@ -378,8 +378,9 @@ impl<'a> AstBuilder<'a> {
378378
/// * `body`: See `expression` for whether this arrow expression returns an expression.
379379
/// * `scope_id`
380380
/// * `pure`: `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
381+
/// * `pife`: `true` if the function should be marked as "Possibly-Invoked Function Expression" (PIFE).
381382
#[inline]
382-
pub fn expression_arrow_function_with_scope_id_and_pure<T1, T2, T3, T4>(
383+
pub fn expression_arrow_function_with_scope_id_and_pure_and_pife<T1, T2, T3, T4>(
383384
self,
384385
span: Span,
385386
expression: bool,
@@ -390,6 +391,7 @@ impl<'a> AstBuilder<'a> {
390391
body: T4,
391392
scope_id: ScopeId,
392393
pure: bool,
394+
pife: bool,
393395
) -> Expression<'a>
394396
where
395397
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterDeclaration<'a>>>>,
@@ -398,7 +400,7 @@ impl<'a> AstBuilder<'a> {
398400
T4: IntoIn<'a, Box<'a, FunctionBody<'a>>>,
399401
{
400402
Expression::ArrowFunctionExpression(
401-
self.alloc_arrow_function_expression_with_scope_id_and_pure(
403+
self.alloc_arrow_function_expression_with_scope_id_and_pure_and_pife(
402404
span,
403405
expression,
404406
r#async,
@@ -408,6 +410,7 @@ impl<'a> AstBuilder<'a> {
408410
body,
409411
scope_id,
410412
pure,
413+
pife,
411414
),
412415
)
413416
}
@@ -6067,6 +6070,7 @@ impl<'a> AstBuilder<'a> {
60676070
body: body.into_in(self.allocator),
60686071
scope_id: Default::default(),
60696072
pure: Default::default(),
6073+
pife: Default::default(),
60706074
}
60716075
}
60726076

@@ -6114,10 +6118,10 @@ impl<'a> AstBuilder<'a> {
61146118
)
61156119
}
61166120

6117-
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure`.
6121+
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure` and `pife`.
61186122
///
61196123
/// If you want the built node to be allocated in the memory arena,
6120-
/// use [`AstBuilder::alloc_arrow_function_expression_with_scope_id_and_pure`] instead.
6124+
/// use [`AstBuilder::alloc_arrow_function_expression_with_scope_id_and_pure_and_pife`] instead.
61216125
///
61226126
/// ## Parameters
61236127
/// * `span`: The [`Span`] covering this node
@@ -6129,8 +6133,9 @@ impl<'a> AstBuilder<'a> {
61296133
/// * `body`: See `expression` for whether this arrow expression returns an expression.
61306134
/// * `scope_id`
61316135
/// * `pure`: `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
6136+
/// * `pife`: `true` if the function should be marked as "Possibly-Invoked Function Expression" (PIFE).
61326137
#[inline]
6133-
pub fn arrow_function_expression_with_scope_id_and_pure<T1, T2, T3, T4>(
6138+
pub fn arrow_function_expression_with_scope_id_and_pure_and_pife<T1, T2, T3, T4>(
61346139
self,
61356140
span: Span,
61366141
expression: bool,
@@ -6141,6 +6146,7 @@ impl<'a> AstBuilder<'a> {
61416146
body: T4,
61426147
scope_id: ScopeId,
61436148
pure: bool,
6149+
pife: bool,
61446150
) -> ArrowFunctionExpression<'a>
61456151
where
61466152
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterDeclaration<'a>>>>,
@@ -6158,13 +6164,14 @@ impl<'a> AstBuilder<'a> {
61586164
body: body.into_in(self.allocator),
61596165
scope_id: Cell::new(Some(scope_id)),
61606166
pure,
6167+
pife,
61616168
}
61626169
}
61636170

6164-
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure`, and store it in the memory arena.
6171+
/// Build an [`ArrowFunctionExpression`] with `scope_id` and `pure` and `pife`, and store it in the memory arena.
61656172
///
61666173
/// Returns a [`Box`] containing the newly-allocated node.
6167-
/// If you want a stack-allocated node, use [`AstBuilder::arrow_function_expression_with_scope_id_and_pure`] instead.
6174+
/// If you want a stack-allocated node, use [`AstBuilder::arrow_function_expression_with_scope_id_and_pure_and_pife`] instead.
61686175
///
61696176
/// ## Parameters
61706177
/// * `span`: The [`Span`] covering this node
@@ -6176,8 +6183,9 @@ impl<'a> AstBuilder<'a> {
61766183
/// * `body`: See `expression` for whether this arrow expression returns an expression.
61776184
/// * `scope_id`
61786185
/// * `pure`: `true` if the function is marked with a `/*#__NO_SIDE_EFFECTS__*/` comment
6186+
/// * `pife`: `true` if the function should be marked as "Possibly-Invoked Function Expression" (PIFE).
61796187
#[inline]
6180-
pub fn alloc_arrow_function_expression_with_scope_id_and_pure<T1, T2, T3, T4>(
6188+
pub fn alloc_arrow_function_expression_with_scope_id_and_pure_and_pife<T1, T2, T3, T4>(
61816189
self,
61826190
span: Span,
61836191
expression: bool,
@@ -6188,6 +6196,7 @@ impl<'a> AstBuilder<'a> {
61886196
body: T4,
61896197
scope_id: ScopeId,
61906198
pure: bool,
6199+
pife: bool,
61916200
) -> Box<'a, ArrowFunctionExpression<'a>>
61926201
where
61936202
T1: IntoIn<'a, Option<Box<'a, TSTypeParameterDeclaration<'a>>>>,
@@ -6196,7 +6205,7 @@ impl<'a> AstBuilder<'a> {
61966205
T4: IntoIn<'a, Box<'a, FunctionBody<'a>>>,
61976206
{
61986207
Box::new_in(
6199-
self.arrow_function_expression_with_scope_id_and_pure(
6208+
self.arrow_function_expression_with_scope_id_and_pure_and_pife(
62006209
span,
62016210
expression,
62026211
r#async,
@@ -6206,6 +6215,7 @@ impl<'a> AstBuilder<'a> {
62066215
body,
62076216
scope_id,
62086217
pure,
6218+
pife,
62096219
),
62106220
self.allocator,
62116221
)

crates/oxc_ast/src/generated/derive_clone_in.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3677,6 +3677,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionExpression<'_> {
36773677
body: CloneIn::clone_in(&self.body, allocator),
36783678
scope_id: Default::default(),
36793679
pure: CloneIn::clone_in(&self.pure, allocator),
3680+
pife: CloneIn::clone_in(&self.pife, allocator),
36803681
}
36813682
}
36823683

@@ -3691,6 +3692,7 @@ impl<'new_alloc> CloneIn<'new_alloc> for ArrowFunctionExpression<'_> {
36913692
body: CloneIn::clone_in_with_semantic_ids(&self.body, allocator),
36923693
scope_id: CloneIn::clone_in_with_semantic_ids(&self.scope_id, allocator),
36933694
pure: CloneIn::clone_in_with_semantic_ids(&self.pure, allocator),
3695+
pife: CloneIn::clone_in_with_semantic_ids(&self.pife, allocator),
36943696
}
36953697
}
36963698
}

crates/oxc_ast/src/generated/derive_content_eq.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,7 @@ impl ContentEq for ArrowFunctionExpression<'_> {
11071107
&& ContentEq::content_eq(&self.return_type, &other.return_type)
11081108
&& ContentEq::content_eq(&self.body, &other.body)
11091109
&& ContentEq::content_eq(&self.pure, &other.pure)
1110+
&& ContentEq::content_eq(&self.pife, &other.pife)
11101111
}
11111112
}
11121113

crates/oxc_ast/src/generated/derive_dummy.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,7 @@ impl<'a> Dummy<'a> for ArrowFunctionExpression<'a> {
11401140
body: Dummy::dummy(allocator),
11411141
scope_id: Dummy::dummy(allocator),
11421142
pure: Dummy::dummy(allocator),
1143+
pife: Dummy::dummy(allocator),
11431144
}
11441145
}
11451146
}

crates/oxc_ast_macros/src/generated/structs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ pub static STRUCTS: phf::Map<&'static str, StructDetails> = ::phf::Map {
278278
("TemplateElement", StructDetails { field_order: None }),
279279
(
280280
"ArrowFunctionExpression",
281-
StructDetails { field_order: Some(&[0, 6, 7, 1, 2, 3, 4, 5, 8]) },
281+
StructDetails { field_order: Some(&[0, 6, 7, 1, 2, 3, 4, 5, 8, 9]) },
282282
),
283283
("NumericLiteral", StructDetails { field_order: None }),
284284
("TSTupleType", StructDetails { field_order: None }),

crates/oxc_codegen/src/gen.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,7 @@ impl Gen for PropertyKey<'_> {
16331633

16341634
impl GenExpr for ArrowFunctionExpression<'_> {
16351635
fn gen_expr(&self, p: &mut Codegen, precedence: Precedence, ctx: Context) {
1636-
p.wrap(precedence >= Precedence::Assign, |p| {
1636+
p.wrap(precedence >= Precedence::Assign || self.pife, |p| {
16371637
if self.r#async {
16381638
p.print_space_before_identifier();
16391639
p.add_source_mapping(self.span);

crates/oxc_codegen/tests/integration/esbuild.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ fn test_for() {
498498
test("for (x(a in b);;);", "for (x(a in b);;);\n");
499499
test("for (x[a in b];;);", "for (x[a in b];;);\n");
500500
test("for (x?.[a in b];;);", "for (x?.[a in b];;);\n");
501-
test("for ((x => a in b);;);", "for ((x) => (a in b);;);\n");
501+
test("for ((x => a in b);;);", "for (((x) => (a in b));;);\n");
502502

503503
// Make sure for-of loops with commas are wrapped in parentheses
504504
test("for (let a in b, c);", "for (let a in b, c);\n");

crates/oxc_codegen/tests/integration/js.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ fn r#yield() {
251251
fn arrow() {
252252
test_minify("x => a, b", "x=>a,b;");
253253
test_minify("x => (a, b)", "x=>(a,b);");
254-
test_minify("x => (a => b)", "x=>a=>b;");
254+
test_minify("x => (a => b)", "x=>(a=>b);");
255255
test_minify("x => y => a, b", "x=>y=>a,b;");
256256
test_minify("x => y => (a = b)", "x=>y=>a=b;");
257257
test_minify("x => y => z => a = b, c", "x=>y=>z=>a=b,c;");
@@ -415,6 +415,14 @@ fn pure_comment() {
415415
test_same("false || /* @__PURE__ */ noEffect();\n");
416416
}
417417

418+
#[test]
419+
fn pife() {
420+
test_same("foo((() => 0));\n");
421+
test_minify_same("foo((()=>0));");
422+
test_same("(() => 0)();\n");
423+
test_minify_same("(()=>0)();");
424+
}
425+
418426
// followup from https://github.com/oxc-project/oxc/pull/6422
419427
#[test]
420428
fn in_expr_in_sequence_in_for_loop_init() {

0 commit comments

Comments
 (0)