Skip to content

Commit

Permalink
refactor(transformer): introduce TransformCtx::duplicate_expression
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Dec 9, 2024
1 parent 3c1b2bf commit b23adea
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 82 deletions.
132 changes: 132 additions & 0 deletions crates/oxc_transformer/src/common/duplicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
//! Utilities to duplicate expressions.
use std::{
mem::{ManuallyDrop, MaybeUninit},
ptr,
};

use oxc_ast::ast::{AssignmentOperator, Expression};
use oxc_span::SPAN;
use oxc_syntax::reference::ReferenceFlags;
use oxc_traverse::{BoundIdentifier, TraverseCtx};

use crate::TransformCtx;

impl<'a> TransformCtx<'a> {
/// Duplicate expression to be used twice.
///
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
///
/// * `this` -> `this`, `this`
/// * Bound identifier `foo` -> `foo`, `foo`
/// * Unbound identifier `foo` -> `_foo = foo`, `_foo`
/// * Anything else `foo()` -> `_foo = foo()`, `_foo`
///
/// Returns 2 `Expression`s. The first may be an `AssignmentExpression`,
/// and must be inserted into output first.
pub(crate) fn duplicate_expression(
&self,
expr: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>) {
let (maybe_assignment, references) = self.duplicate_expression_multiple::<1>(expr, ctx);
let [reference] = references;
(maybe_assignment, reference)
}

/// Duplicate expression to be used 3 times.
///
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
///
/// * `this` -> `this`, `this`, `this`
/// * Bound identifier `foo` -> `foo`, `foo`, `foo`
/// * Unbound identifier `foo` -> `_foo = foo`, `_foo`, `_foo`
/// * Anything else `foo()` -> `_foo = foo()`, `_foo`, `_foo`
///
/// Returns 3 `Expression`s. The first may be an `AssignmentExpression`,
/// and must be inserted into output first.
#[expect(clippy::similar_names)]
pub(crate) fn duplicate_expression_twice(
&self,
expr: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>, Expression<'a>) {
let (maybe_assignment, references) = self.duplicate_expression_multiple::<2>(expr, ctx);
let [reference1, reference2] = references;
(maybe_assignment, reference1, reference2)
}

/// Duplicate expression `N + 1` times.
///
/// If `expr` may have side effects, create a temp var `_expr` and assign to it.
///
/// * `this` -> `this`, [`this`; N]
/// * Bound identifier `foo` -> `foo`, [`foo`; N]
/// * Unbound identifier `foo` -> `_foo = foo`, [`_foo`; N]
/// * Anything else `foo()` -> `_foo = foo()`, [`_foo`; N]
///
/// Returns `N + 1` x `Expression`s. The first may be an `AssignmentExpression`,
/// and must be inserted into output first.
pub(crate) fn duplicate_expression_multiple<const N: usize>(
&self,
expr: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, [Expression<'a>; N]) {
// TODO: Handle if in a function's params
let temp_var_binding = match &expr {
Expression::Identifier(ident) => {
let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id());
if let Some(symbol_id) = reference.symbol_id() {
// Reading bound identifier cannot have side effects, so no need for temp var
let binding = BoundIdentifier::new(ident.name.clone(), symbol_id);
let references =
create_array(|| binding.create_spanned_read_expression(ident.span, ctx));
return (expr, references);
}

// Previously `x += 1` (`x` read + write), but moving to `_x = x` (`x` read only)
*reference.flags_mut() = ReferenceFlags::Read;

self.var_declarations.create_uid_var(&ident.name, ctx)
}
Expression::ThisExpression(this) => {
// Reading `this` cannot have side effects, so no need for temp var
let references = create_array(|| ctx.ast.expression_this(this.span));
return (expr, references);
}
_ => self.var_declarations.create_uid_var_based_on_node(&expr, ctx),
};

let assignment = ctx.ast.expression_assignment(
SPAN,
AssignmentOperator::Assign,
temp_var_binding.create_target(ReferenceFlags::Write, ctx),
expr,
);

let references = create_array(|| temp_var_binding.create_read_expression(ctx));

(assignment, references)
}
}

/// Create array of length `N`, with each item initialized with provided function `init`.
///
/// Implementation based on:
/// * <https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029>
/// * <https://github.com/rust-lang/rust/issues/61956>
//
// `#[inline]` so compiler can inline `init()`, and it may unroll the loop if `init` is simple enough.
#[inline]
fn create_array<const N: usize, T, I: FnMut() -> T>(mut init: I) -> [T; N] {
let mut array: [MaybeUninit<T>; N] = [const { MaybeUninit::uninit() }; N];
for elem in &mut array {
elem.write(init());
}
// Wrapping in `ManuallyDrop` should not be necessary because `MaybeUninit` does not impl `Drop`,
// but do it anyway just to make sure, as it's mentioned in issues above.
let mut array = ManuallyDrop::new(array);
// SAFETY: All elements of array are initialized.
// `[MaybeUninit<T>; N]` and `[T; N]` have same layout.
unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() }
}
1 change: 1 addition & 0 deletions crates/oxc_transformer/src/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use oxc_traverse::{Traverse, TraverseCtx};
use crate::{EnvOptions, TransformCtx};

pub mod arrow_function_converter;
mod duplicate;
pub mod helper_loader;
pub mod module_imports;
pub mod statement_injector;
Expand Down
67 changes: 7 additions & 60 deletions crates/oxc_transformer/src/es2022/class_properties/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ use std::mem;
use oxc_allocator::Box as ArenaBox;
use oxc_ast::{ast::*, NONE};
use oxc_span::SPAN;
use oxc_syntax::{
reference::{ReferenceFlags, ReferenceId},
symbol::SymbolId,
};
use oxc_syntax::{reference::ReferenceId, symbol::SymbolId};
use oxc_traverse::{
ast_operations::get_var_name_from_node, Ancestor, BoundIdentifier, TraverseCtx,
};
Expand All @@ -19,7 +16,7 @@ use crate::common::helper_loader::Helper;
use super::{
private_props::ResolvedPrivateProp,
utils::{
assert_expr_neither_parenthesis_nor_typescript_syntax, create_array, create_assignment,
assert_expr_neither_parenthesis_nor_typescript_syntax, create_assignment,
create_underscore_ident_name,
},
ClassProperties,
Expand Down Expand Up @@ -1461,13 +1458,12 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
///
/// Returns 2 `Expression`s. The first must be inserted into output first.
fn duplicate_object(
&mut self,
&self,
object: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>) {
let (object1, duplicates) = self.duplicate_object_multiple::<1>(object, ctx);
let [object2] = duplicates;
(object1, object2)
assert_expr_neither_parenthesis_nor_typescript_syntax(&object);
self.ctx.duplicate_expression(object, ctx)
}

/// Duplicate object to be used in triple.
Expand All @@ -1481,61 +1477,12 @@ impl<'a, 'ctx> ClassProperties<'a, 'ctx> {
///
/// Returns 3 `Expression`s. The first must be inserted into output first.
fn duplicate_object_twice(
&mut self,
&self,
object: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, Expression<'a>, Expression<'a>) {
let (object1, duplicates) = self.duplicate_object_multiple::<2>(object, ctx);
let [object2, object3] = duplicates;
(object1, object2, object3)
}

/// Duplicate object `N + 1` times.
///
/// If `object` may have side effects, create a temp var `_object` and assign to it.
///
/// * `this` -> `this`, [`this`; N]
/// * Bound identifier `object` -> `object`, [`object`; N]
/// * Unbound identifier `object` -> `_object = object`, [`_object`; N]
/// * Anything else `foo()` -> `_foo = foo()`, [`_foo`; N]
///
/// Returns `N + 1` `Expression`s. The first must be inserted into output first.
fn duplicate_object_multiple<const N: usize>(
&mut self,
object: Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> (Expression<'a>, [Expression<'a>; N]) {
assert_expr_neither_parenthesis_nor_typescript_syntax(&object);

// TODO: Handle if in a function's params
let temp_var_binding = match &object {
Expression::Identifier(ident) => {
let reference = ctx.symbols_mut().get_reference_mut(ident.reference_id());
if let Some(symbol_id) = reference.symbol_id() {
// Reading bound identifier cannot have side effects, so no need for temp var
let binding = BoundIdentifier::new(ident.name.clone(), symbol_id);
let duplicates =
create_array(|| binding.create_spanned_read_expression(ident.span, ctx));
return (object, duplicates);
}

// Previously `x += 1` (`x` read + write), but moving to `_x = x` (`x` read only)
*reference.flags_mut() = ReferenceFlags::Read;

self.ctx.var_declarations.create_uid_var(&ident.name, ctx)
}
Expression::ThisExpression(this) => {
// Reading `this` cannot have side effects, so no need for temp var
let duplicates = create_array(|| ctx.ast.expression_this(this.span));
return (object, duplicates);
}
_ => self.ctx.var_declarations.create_uid_var_based_on_node(&object, ctx),
};

let object1 = create_assignment(&temp_var_binding, object, ctx);
let references = create_array(|| temp_var_binding.create_read_expression(ctx));

(object1, references)
self.ctx.duplicate_expression_twice(object, ctx)
}

/// `_classPrivateFieldGet2(_prop, object)`
Expand Down
22 changes: 0 additions & 22 deletions crates/oxc_transformer/src/es2022/class_properties/utils.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
//! ES2022: Class Properties
//! Utility functions.
use std::{
mem::{ManuallyDrop, MaybeUninit},
ptr,
};

use oxc_ast::ast::*;
use oxc_span::SPAN;
use oxc_syntax::reference::ReferenceFlags;
Expand Down Expand Up @@ -66,20 +61,3 @@ pub(super) fn assert_expr_neither_parenthesis_nor_typescript_syntax(expr: &Expre
"Should not be: {expr:?}",
);
}

/// Create array of length `N`, with each item initialized with provided function `init`.
#[inline]
pub(super) fn create_array<const N: usize, T, I: FnMut() -> T>(mut init: I) -> [T; N] {
// https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029
// https://github.com/rust-lang/rust/issues/61956
let mut array: [MaybeUninit<T>; N] = [const { MaybeUninit::uninit() }; N];
for elem in &mut array {
elem.write(init());
}
// Wrapping in `ManuallyDrop` should not be necessary because `MaybeUninit` does not impl `Drop`,
// but do it anyway just to make sure, as it's mentioned in issues above.
let mut array = ManuallyDrop::new(array);
// SAFETY: All elements of array are initialized.
// `[MaybeUninit<T>; N]` and `[T; N]` have equivalent layout.
unsafe { ptr::from_mut(&mut array).cast::<[T; N]>().read() }
}

0 comments on commit b23adea

Please sign in to comment.