Skip to content

Re-design ScopeFlags #16

@overlookmotel

Description

@overlookmotel

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?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions