oxc
oxc copied to clipboard
Re-design `ScopeFlags`
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?