oxc icon indicating copy to clipboard operation
oxc copied to clipboard

Re-design `ScopeFlags`

Open overlookmotel opened this issue 9 months ago • 4 comments

Personally I find the design of ScopeFlags confusing. It's also a bit bloated - we can pack more information into less bits.

For example, presently we use separate bits for Constructor and GetAccessor, but the two are mutually exclusive - a function can't be both a constructor and a getter. So we can compress this info.

New design

I propose redesigning it as an 8-bit bitfield with the following parts:

  • Bits 0-2: "Full" function type i.e. function or method (not arrow)
  • Bits 3-4: "Special" block i.e. ClassStaticBlock / TsModuleBlock
  • Bit 5: Strict mode
  • Bit 6: Arrow function
  • Bit 7: Statement block

Why?

Encode more info

This allows us to distinguish between many different states which we can't presently without searching up the scopes stack. e.g.:

  • At program top level (ScopeFlags::Top).
  • In a nested statement block at program top level (ScopeFlags::Block).
  • At top level within a function (ScopeFlags::Function).
  • In a nested statement block within a function (ScopeFlags::Function | ScopeFlags::Block).
  • In an arrow function within a class constructor (ScopeFlags::Constructor | ScopeFlags::Arrow).

Reduce type size

Reducing ScopeFlags from 2 bytes to 1 byte is not such a big wow in itself.

But getting it down to 8 bits would also allow packing it into ScopeId. ScopeId can be a u32, but 24 bits should suffice for the actual ID (16 million scopes is probably enough for any JS file). So the top 8 bits could contain the scope flags. Then we can have scope type info embedded inline, reducing memory accesses.

Implementation

Implementation something like this:

const FUNCTION_TYPE_MASK:  u8 = 0b00000111;
const BLOCK_TYPE_MASK:     u8 = 0b00011000;
const BLOCK_TYPE_SHIFT:    u8 = 3;
const STRICT_MODE:         u8 = 0b00100000;
const ARROW:               u8 = 0b01000000;
const BLOCK:               u8 = 0b10000000;

#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ScopeFlags(u8);

#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum FunctionType {
  None = 0,
  Function = 1,
  ObjectMethod = 2,
  ClassMethod = 3,
  GetAccessor = 4,
  SetAccessor = 5,
  Constructor = 6,
  // 7 is unused
}

#[derive(Clone, Copy, Debug, PartialEq)]
#[repr(u8)]
pub enum SpecialBlockType {
  None = 0,
  ClassStaticBlock = 1 << BLOCK_TYPE_SHIFT,
  TsModuleBlock = 2 << BLOCK_TYPE_SHIFT,
  // 3 is unused
}

#[allow(non_upper_case_globals)]
impl ScopeFlags {
  pub const Top: Self = Self(FunctionType::None as u8);
  pub const Function: Self = Self(FunctionType::Function as u8);
  pub const Constructor: Self = Self(FunctionType::Constructor as u8);
  pub const GetAccessor: Self = Self(FunctionType::GetAccessor as u8);
  pub const SetAccessor: Self = Self(FunctionType::SetAccessor as u8);

  pub const ClassStaticBlock: Self = Self(SpecialBlockType::ClassStaticBlock as u8);
  pub const TsModuleBlock: Self = Self(SpecialBlockType::TsModuleBlock as u8);

  pub const StrictMode: Self = Self(STRICT_MODE);
  pub const Arrow: Self = Self(ARROW);
  pub const Block: Self = Self(BLOCK);
}

impl ScopeFlags {
  pub const fn at_top(self) -> bool {
    self.function_type() == FunctionType::None
  }

  pub const fn function_type(self) -> FunctionType {
    unsafe { std::mem::transmute(self.0 & FUNCTION_TYPE_MASK) }
  }

  pub const fn in_full_function(&self) -> bool {
    self.function_type() != FunctionType::None
  }

  pub const fn special_block_type(self) -> SpecialBlockType {
    unsafe { std::mem::transmute(self.0 & BLOCK_TYPE_MASK) }
  }

  pub const fn in_special_block(self) -> bool {
    self.special_block_type() != SpecialBlockType::None
  }

  pub const fn is_strict_mode(self) -> bool {
    (self.0 & STRICT_MODE) != 0
  }

  pub const fn in_arrow(self) -> bool {
    (self.0 & ARROW) != 0
  }

  pub const fn in_block(self) -> bool {
    (self.0 & BLOCK) != 0
  }

  pub const fn with_function_type(self, fn_type: FunctionType) -> Self {
    Self((self.0 & !FUNCTION_TYPE_MASK) | fn_type as u8)
  }

  pub const fn with_special_block_type(self, block_type: SpecialBlockType) -> Self {
    Self((self.0 & !BLOCK_TYPE_MASK) | block_type as u8)
  }

  pub const fn with_strict_mode(self, is_strict: bool) -> Self {
    Self((self.0 & !STRICT_MODE) | (is_strict as u8 * STRICT_MODE))
  }

  pub const fn with_arrow(self, is_arrow: bool) -> Self {
    Self((self.0 & !ARROW) | (is_arrow as u8 * ARROW))
  }

  pub const fn with_block(self, is_block: bool) -> Self {
    Self((self.0 & !BLOCK) | (is_block as u8 * BLOCK))
  }
}

Any thoughts?

overlookmotel avatar May 14 '24 14:05 overlookmotel