Skip to content
15 changes: 6 additions & 9 deletions src/ast/b.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,12 @@ impl Default for B {
}

// ── Layout guards ─────────────────────────────────────────────────────────
// Three `StoreRef<T>` variants (`#[repr(transparent)] NonNull<T>`, 8-byte
// payload) + one ZST → 1-byte discriminant + 8-byte payload = 9, align(8)
// rounds to 16. `Binding` = `B` (16, align 8) + `Loc` (i32) → 20 → 24.
// Matches `expr::Data`/`stmt::Data`: every pointer payload is non-nullable,
// so `Option<B>` packs into the same 16 bytes via the NonNull niche (and
// would continue to even under a future `#[repr(u8)]`, unlike the prior
// `*mut T` form which relied solely on spare-tag-value niche).
const _: () = assert!(core::mem::size_of::<B>() == 16);
const _: () = assert!(core::mem::size_of::<super::binding::Binding>() == 24);
// Three `StoreRef<T>` variants (8 B align 4) + one ZST → 1-byte discriminant
// + 8-byte payload = 9, align(4) rounds to 12. `Binding` = `B` (12, align 4)
// + `Loc` (i32) → 16. `Option<B>` niche-packs via spare discriminant values.
const _: () = assert!(core::mem::size_of::<B>() == 12);
const _: () = assert!(core::mem::align_of::<B>() == 4);
const _: () = assert!(core::mem::size_of::<super::binding::Binding>() == 16);
Comment thread
claude[bot] marked this conversation as resolved.
const _: () = assert!(
core::mem::size_of::<Option<B>>() == core::mem::size_of::<B>(),
"B lost its niche — check for #[repr] or oversized inline payload"
Expand Down
49 changes: 35 additions & 14 deletions src/ast/e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Default for NameOfSymbol {

pub struct Array {
pub items: ExprNodeList,
pub comma_after_spread: Option<crate::Loc>,
pub comma_after_spread: crate::Loc,
pub is_single_line: bool,
pub is_parenthesized: bool,
pub was_originally_macro: bool,
Expand All @@ -51,7 +51,7 @@ impl Default for Array {
fn default() -> Self {
Self {
items: bun_alloc::AstAlloc::vec(),
comma_after_spread: None,
comma_after_spread: crate::Loc::EMPTY,
is_single_line: false,
is_parenthesized: false,
was_originally_macro: false,
Expand All @@ -63,7 +63,7 @@ impl Default for Array {
impl Array {
pub const EMPTY: Array = Array {
items: bun_alloc::AstAlloc::vec(),
comma_after_spread: None,
comma_after_spread: crate::Loc::EMPTY,
is_single_line: false,
is_parenthesized: false,
was_originally_macro: false,
Expand Down Expand Up @@ -688,9 +688,35 @@ impl JSXSpecialProp {
}
}

/// `packed(4)` lowers the alignment to 4 so `expr::Data` (and therefore `Expr`)
/// drop to align 4 and pack into 16 bytes; the `f64` stays a single scalar so
/// `.value()` is one `movsd`/`ldr`. The field is private because comparisons on
/// a packed `f64` field would require `&self.value` (an unaligned reference);
/// callers go through `value()` / `new()` which copy by value.
#[derive(Clone, Copy)]
#[repr(C, packed(4))]
pub struct Number {
pub value: f64,
value: f64,
}

const _: () = assert!(core::mem::size_of::<Number>() == 8);
const _: () = assert!(core::mem::align_of::<Number>() == 4);

impl Number {
#[inline(always)]
pub const fn new(value: f64) -> Self {
Number { value }
}
#[inline(always)]
pub const fn value(self) -> f64 {
self.value
}
}
impl From<f64> for Number {
#[inline]
fn from(v: f64) -> Self {
Number::new(v)
}
}

const DOUBLE_DIGIT: [&[u8]; 101] = [
Expand Down Expand Up @@ -722,7 +748,7 @@ impl Number {
///
/// This can return `None` in wasm builds to avoid linking JSC
pub fn to_string(self, bump: &Bump) -> Option<Str> {
Self::to_string_from_f64(self.value, bump)
Self::to_string_from_f64(self.value(), bump)
}

pub fn to_string_from_f64(value: f64, bump: &Bump) -> Option<Str> {
Expand Down Expand Up @@ -794,7 +820,7 @@ impl Number {
}

pub fn to<T: NumberCast>(self) -> T {
let clamped = self.value.trunc().max(0.0).min(T::MAX_AS_F64);
let clamped = self.value().trunc().max(0.0).min(T::MAX_AS_F64);
T::from_f64(clamped)
}

Expand Down Expand Up @@ -832,7 +858,7 @@ impl BigInt {

pub struct Object {
pub properties: G::PropertyList,
pub comma_after_spread: Option<crate::Loc>,
pub comma_after_spread: crate::Loc,
pub is_single_line: bool,
pub is_parenthesized: bool,
pub was_originally_macro: bool,
Expand All @@ -843,7 +869,7 @@ impl Default for Object {
fn default() -> Self {
Self {
properties: bun_alloc::AstAlloc::vec(),
comma_after_spread: None,
comma_after_spread: crate::Loc::EMPTY,
is_single_line: false,
is_parenthesized: false,
was_originally_macro: false,
Expand Down Expand Up @@ -922,7 +948,7 @@ pub struct RopeQuery<'a> {
impl Object {
pub const EMPTY: Object = Object {
properties: bun_alloc::AstAlloc::vec(),
comma_after_spread: None,
comma_after_spread: crate::Loc::EMPTY,
is_single_line: false,
is_parenthesized: false,
was_originally_macro: false,
Expand Down Expand Up @@ -1585,12 +1611,7 @@ impl EString {
// Ordering / equality / const-literal / rope-mutation helpers.
// `string_z`/`to_zig_string` remain gated on `bun_core::ZStr` arena constructors.
impl EString {
pub const CLASS: EString = EString::from_static(b"class");
pub const EMPTY: EString = EString::from_static(b"");
pub const TRUE: EString = EString::from_static(b"true");
pub const FALSE: EString = EString::from_static(b"false");
pub const NULL: EString = EString::from_static(b"null");
pub const UNDEFINED: EString = EString::from_static(b"undefined");

pub fn is_identifier(&mut self, bump: &Bump) -> bool {
if !self.is_utf8() {
Expand Down
67 changes: 35 additions & 32 deletions src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ impl Expr {

pub fn as_number(&self) -> Option<f64> {
match self.data {
Data::ENumber(n) => Some(n.value),
Data::ENumber(n) => Some(n.value()),
_ => None,
}
}
Expand Down Expand Up @@ -1504,7 +1504,7 @@ impl Expr {
}
Data::ENumber(n) => {
return Some(expr.at(E::Boolean {
value: n.value == 0.0 || n.value.is_nan(),
value: n.value() == 0.0 || n.value().is_nan(),
}));
}
Data::EBigInt(b) => {
Expand Down Expand Up @@ -1738,18 +1738,14 @@ pub enum Data {
}

// ── Layout guards ─────────────────────────────────────────────────────────
// The identifier-family flags are packed into `Ref`'s spare bits (see
// `E::Identifier` doc), so every inline payload is ≤ 8 bytes; with the
// repr(Rust) discriminant that rounds to 16. `Expr` = `Data` (16, align 8) +
// `Loc` (i32) → 20 → 24 after tail padding.
//
// The `Option<Data>` assert proves Rust's niche optimization fires: the enum
// has spare discriminant values (47 variants < 256, and every pointer variant
// contributes a NonNull niche), so `None` packs into an unused bit-pattern
// rather than adding a word. If a future variant adds `#[repr(C)]`/`#[repr(u32)]`
// or a nullable `*mut T` payload, this assert catches the size regression.
const _: () = assert!(core::mem::size_of::<Data>() == 16); // Do not increase the size of Expr
const _: () = assert!(core::mem::size_of::<Expr>() == 24);
// Every payload — `StoreRef<T>` and the inline identifier/`Number`/etc.
// structs — is ≤ 8 bytes at align 4, so `Data` = 1-byte discriminant + 8-byte
// payload → 12 at align 4. `Expr` = `Data` (12, align 4) + `Loc` (i32) → 16.
// `Option<Data>`/`Option<Expr>` niche-pack via spare discriminant values
// (47 variants < 256); a `#[repr(C)]`/`#[repr(u32)]` on `Data` would break it.
const _: () = assert!(core::mem::size_of::<Data>() == 12); // Do not increase the size of Expr
const _: () = assert!(core::mem::align_of::<Data>() == 4);
const _: () = assert!(core::mem::size_of::<Expr>() == 16);
const _: () = assert!(
core::mem::size_of::<Option<Data>>() == core::mem::size_of::<Data>(),
"expr::Data lost its niche — check for #[repr] or nullable-ptr payload"
Expand All @@ -1758,16 +1754,23 @@ const _: () = assert!(
core::mem::size_of::<Option<Expr>>() == core::mem::size_of::<Expr>(),
"Expr lost its niche — Option<Expr> is used in G::Property/B::Property/etc."
);
// Inline-payload ceilings (regress any of these and `Data` grows past 16):
// Inline-payload ceilings (regress any of these and `Data` grows past 12).
// `align_of <= 4` is what keeps `Data` (and therefore `Expr`) at align 4.
const _: () = assert!(core::mem::size_of::<E::Identifier>() == 8);
const _: () = assert!(core::mem::align_of::<E::Identifier>() == 4);
const _: () = assert!(core::mem::size_of::<E::ImportIdentifier>() == 8);
const _: () = assert!(core::mem::size_of::<E::CommonJSExportIdentifier>() == 8);
const _: () = assert!(core::mem::size_of::<E::PrivateIdentifier>() == 8);
const _: () = assert!(core::mem::size_of::<E::Number>() <= 8);
const _: () = assert!(core::mem::align_of::<E::Number>() == 4);
const _: () = assert!(core::mem::size_of::<E::Special>() <= 8);
const _: () = assert!(core::mem::size_of::<E::RequireString>() <= 8);
const _: () = assert!(core::mem::size_of::<E::NewTarget>() <= 8);
const _: () = assert!(core::mem::size_of::<StoreRef<E::Binary>>() == core::mem::size_of::<usize>());
// Heap-payload shrinks unlocked by the 12-byte StoreSlice<T>:
const _: () = assert!(core::mem::size_of::<crate::G::FnBody>() == 16);
const _: () = assert!(core::mem::size_of::<E::Arrow>() <= 32);
const _: () = assert!(core::mem::size_of::<crate::S::Block>() == 16);

// Field-style accessors (`data.e_string()`, `data.e_object()`). The match arms
// in this file use these heavily; keeping them as inherent methods avoids
Expand Down Expand Up @@ -2685,7 +2688,7 @@ impl Data {
raw(hasher, e.value);
}
Data::ENumber(e) => {
raw(hasher, e.value);
raw(hasher, e.value());
}
Data::EBigInt(e) => {
hasher.update(&e.value);
Expand Down Expand Up @@ -2990,9 +2993,9 @@ impl Data {
Some(string_to_equivalent_number_value(str.slice8()))
}
Data::EBoolean(b) | Data::EBranchBoolean(b) => Some(if b.value { 1.0 } else { 0.0 }),
Data::ENumber(n) => Some(n.value),
Data::ENumber(n) => Some(n.value()),
Data::EInlinedEnum(inlined) => match &inlined.value.data {
Data::ENumber(num) => Some(num.value),
Data::ENumber(num) => Some(num.value()),
Data::EString(str) => {
if str.next.is_some() {
return None;
Expand All @@ -3013,16 +3016,16 @@ impl Data {
match self {
Data::EBoolean(b) | Data::EBranchBoolean(b) => Some(if b.value { 1.0 } else { 0.0 }),
Data::ENumber(n) => {
if n.value.is_finite() {
Some(n.value)
if n.value().is_finite() {
Some(n.value())
} else {
None
}
}
Data::EInlinedEnum(inlined) => match &inlined.value.data {
Data::ENumber(num) => {
if num.value.is_finite() {
Some(num.value)
if num.value().is_finite() {
Some(num.value())
} else {
None
}
Expand All @@ -3035,9 +3038,9 @@ impl Data {

pub fn extract_numeric_value(&self) -> Option<f64> {
match self {
Data::ENumber(n) => Some(n.value),
Data::ENumber(n) => Some(n.value()),
Data::EInlinedEnum(inlined) => match &inlined.value.data {
Data::ENumber(num) => Some(num.value),
Data::ENumber(num) => Some(num.value()),
_ => None,
},
_ => None,
Expand Down Expand Up @@ -3173,9 +3176,9 @@ impl Data {
return Equality {
ok: true,
equal: if l.value {
num.value == 1.0
num.value() == 1.0
} else {
num.value == 0.0
num.value() == 0.0
},
..Default::default()
};
Expand All @@ -3189,15 +3192,15 @@ impl Data {
Data::ENumber(r) => {
return Equality {
ok: true,
equal: l.value == r.value,
equal: l.value() == r.value(),
..Default::default()
};
}
Data::EInlinedEnum(r) => {
if let Data::ENumber(rn) = &r.value.data {
return Equality {
ok: true,
equal: l.value == rn.value,
equal: l.value() == rn.value(),
..Default::default()
};
}
Expand All @@ -3209,9 +3212,9 @@ impl Data {
// "1 == true" is true
// "0 == false" is true
equal: if r.value {
l.value == 1.0
l.value() == 1.0
} else {
l.value == 0.0
l.value() == 0.0
},
..Default::default()
};
Expand Down Expand Up @@ -3277,10 +3280,10 @@ impl Data {
Data::ENumber(r) => {
if !K::STRICT {
l.resolve_rope_if_needed(p.arena());
if r.value == 0.0 && (l.is_blank() || l.eql_comptime(b"0")) {
if r.value() == 0.0 && (l.is_blank() || l.eql_comptime(b"0")) {
return Equality::TRUE;
}
if r.value == 1.0 && l.eql_comptime(b"1") {
if r.value() == 1.0 && l.eql_comptime(b"1") {
return Equality::TRUE;
}
// the string could still equal 0 or 1 but it could be hex, binary, octal, ...
Expand Down
4 changes: 2 additions & 2 deletions src/ast/g.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ pub struct Fn {
// This was originally nullable, but doing so I believe caused a miscompilation
// Specifically, the body was always null.
pub body: FnBody,
pub arguments_ref: Option<Ref>,
pub arguments_ref: Ref,

pub flags: flags::FunctionSet,

Expand All @@ -297,7 +297,7 @@ impl Default for Fn {
loc: crate::Loc::EMPTY,
stmts: StmtNodeList::EMPTY,
},
arguments_ref: None,
arguments_ref: Ref::NONE,
flags: flags::FUNCTION_NONE,
return_ts_metadata: TypeScript::Metadata::MNone,
}
Expand Down
5 changes: 0 additions & 5 deletions src/ast/import_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ pub struct ImportRecord {

pub source_index: Index,

/// Dead field: the only reader (`js_printer`'s `print_bundled_import`)
/// has been deleted. Always 0. Remove together with the `module_id: 0` initializers
/// in the parser/bundler/css constructors when those files are next touched.
pub module_id: u32,

/// The original import specifier as written in source code (e.g., "./foo.js").
/// This is preserved before resolution overwrites `path` with the resolved path.
/// Used for metafile generation.
Expand Down
2 changes: 1 addition & 1 deletion src/ast/known_global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl KnownGlobal {
}
js_ast::expr::PrimitiveType::Number => {
let val = match arg.data {
js_ast::ExprData::ENumber(num) => num.value,
js_ast::ExprData::ENumber(num) => num.value(),
_ => return Some(Self::call_from_new(e, loc)),
};
if
Expand Down
Loading
Loading