Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/bun_core/string/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,16 @@ impl String {
core::ptr::null_mut()
}
}
/// Address of the JSC atom behind `self`: equal for equal text of live atoms on one thread.
#[inline]
pub fn atom_address(&self) -> Option<core::num::NonZeroUsize> {
if self.tag != Tag::WTFStringImpl {
return None;
}
let atom = self.as_wtf();
debug_assert!(atom.is_atom());
core::num::NonZeroUsize::new(core::ptr::from_ref(atom).addr())
}
/// An isolated copy of a WTF-backed impl (+1, `clone()` for other tags),
/// for handing the value to one other thread; not for sharing one impl
/// between VMs.
Expand Down
79 changes: 67 additions & 12 deletions src/jsc/ConsoleObject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,51 @@ struct Column {
width: u32,
}

/// The column of a property name, by [`column_key`].
type ColumnIndex = HashMap<usize, usize>;

/// Comparing this many keys costs about as much as one index lookup.
const MAX_SCANNED_COLUMNS: usize = 8;

/// The identity of a name from `JSPropertyIterator`: its atom's address, 0 for the empty name.
#[inline(always)]
fn column_key(name: &BunString) -> usize {
match name.atom_address() {
Some(address) => address.get(),
None => {
debug_assert!(name.is_empty());
0
}
}
}

/// Finds or adds the column of `name` through the index, which the first call builds.
#[inline(never)]
fn indexed_column(
columns: &mut Vec<Column>,
index: &mut Option<ColumnIndex>,
name: &BunString,
) -> usize {
let index = index.get_or_insert_with(|| {
let mut index = ColumnIndex::with_capacity(columns.len());
for (i, column) in columns.iter().enumerate().skip(1) {
index
.put_no_clobber(column_key(&column.name), i)
.expect("unreachable");
}
index
});
let entry = index.get_or_put(column_key(name)).expect("unreachable");
if !entry.found_existing {
*entry.value_ptr = columns.len();
columns.push(Column {
name: name.clone(),
width: 1,
});
}
*entry.value_ptr
}

enum RowKey {
/// Property-name UTF-8 bytes + visible width (plain-object tabular data).
Str {
Expand Down Expand Up @@ -714,6 +759,7 @@ impl<'a> TablePrinter<'a> {
&mut self,
cell_text: &mut Vec<u8>,
columns: &mut Vec<Column>,
column_index: &mut Option<ColumnIndex>,
row_key: RowKey,
row_value: JSValue,
) -> JsResult<CollectedRow> {
Expand Down Expand Up @@ -768,21 +814,25 @@ impl<'a> TablePrinter<'a> {
},
)?;

// One object's names are distinct: a cell can only match an earlier row's column.
let row_start = columns.len();
while let Some((col_key, value)) = cols_iter.next()? {
// find or create the column for the property
let col_idx: usize = 'brk: {
// reshaped for borrowck — split find/append.
if let Some(idx) =
columns[1..].iter().position(|col| col.name.eql(&col_key))
{
break 'brk 1 + idx;
let col_idx: usize = if row_start > 1 + MAX_SCANNED_COLUMNS {
indexed_column(columns, column_index, &col_key)
} else {
let key = column_key(&col_key);
let earlier = &columns[1..row_start];
match earlier.iter().position(|col| column_key(&col.name) == key) {
Some(idx) => 1 + idx,
None => {
columns.push(Column {
name: (*col_key).clone(),
width: 1,
});
columns.len() - 1
}
}

columns.push(Column {
name: (*col_key).clone(),
width: 1,
});
break 'brk columns.len() - 1;
};

let cell = self.format_cell::<ENABLE_ANSI_COLORS>(cell_text, value)?;
Expand Down Expand Up @@ -906,11 +956,13 @@ impl<'a> TablePrinter<'a> {
let mut cell_text: Vec<u8> = Vec::new();
let mut rows: Vec<CollectedRow> = Vec::new();
{
let mut column_index: Option<ColumnIndex> = None;
if self.is_iterable {
struct Ctx<'c, 'a> {
this: &'c mut TablePrinter<'a>,
cell_text: &'c mut Vec<u8>,
columns: &'c mut Vec<Column>,
column_index: &'c mut Option<ColumnIndex>,
rows: &'c mut Vec<CollectedRow>,
idx: u32,
err: Option<jsc::JsError>,
Expand All @@ -921,6 +973,7 @@ impl<'a> TablePrinter<'a> {
this: self,
cell_text: &mut cell_text,
columns: &mut columns,
column_index: &mut column_index,
rows: &mut rows,
idx: 0,
err: None,
Expand All @@ -942,6 +995,7 @@ impl<'a> TablePrinter<'a> {
match ctx.this.collect_row::<C>(
ctx.cell_text,
ctx.columns,
ctx.column_index,
RowKey::Num(ctx.idx),
value,
) {
Expand Down Expand Up @@ -974,6 +1028,7 @@ impl<'a> TablePrinter<'a> {
let row = self.collect_row::<ENABLE_ANSI_COLORS>(
&mut cell_text,
&mut columns,
&mut column_index,
key,
value,
)?;
Expand Down
4 changes: 4 additions & 0 deletions src/jsc/JSPropertyIterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ impl<'a> JSPropertyIterator<'a> {
/// The name borrows the C++ iterator's storage (no ref) and is valid until
/// `self` drops, hence `&self`: a yielded name may be held across later
/// calls. The value is `JSValue::ZERO` unless `options.include_value`.
/// Without `options.include_symbols` a name is empty or a JSC atom (`String::atom_address`).
pub fn next(&self) -> JsResult<Option<(bun_core::StringView<'_>, JSValue)>> {
loop {
let i: usize = self.iter_i.get() as usize;
Expand Down Expand Up @@ -222,6 +223,9 @@ impl<'a> JSPropertyIterator<'a> {
if name.is_dead() {
continue;
}
debug_assert!(
self.options.include_symbols || name.is_empty() || name.atom_address().is_some()
);

if self.options.skip_empty_name && name.is_empty() {
continue;
Expand Down
Loading
Loading