Skip to content

Commit 9579449

Browse files
rzvxaoverlookmotel
authored andcommitted
feat(ast_codegen): add alignment and size data to the schema.
1 parent d0297db commit 9579449

File tree

12 files changed

+3552
-1071
lines changed

12 files changed

+3552
-1071
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_ast/src/generated/assert_layouts.rs

Lines changed: 1062 additions & 1062 deletions
Large diffs are not rendered by default.

crates/oxc_ast/src/generated/assert_repr_rust_layout.rs

Lines changed: 1899 additions & 0 deletions
Large diffs are not rendered by default.

crates/oxc_ast/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![allow(clippy::wildcard_imports)]
2+
#![allow(clippy::inconsistent_struct_constructor)]
23
// TODO: I'm not sure if it is a but or intentional but clippy needs this allowed both on this
34
// module and the generated one.
45
#![allow(clippy::self_named_module_files)]
@@ -24,7 +25,6 @@
2425
//! [`AssignmentTarget`]: ast::AssignmentTarget
2526
//! [`oxc_parser`]: <https://docs.rs/oxc_parser>
2627
//! [`Parser`]: <https://docs.rs/oxc_parser/latest/oxc_parser/struct.Parser.html>
27-
2828
#[cfg(feature = "serialize")]
2929
mod serialize;
3030

@@ -39,6 +39,8 @@ mod trivia;
3939
mod generated {
4040
#[cfg(test)]
4141
pub mod assert_layouts;
42+
// TODO: remove me; don't merge to upstream!!
43+
pub mod assert_repr_rust_layout;
4244
pub mod ast_builder;
4345
pub mod ast_kind;
4446
pub mod span;

crates/oxc_ast_macros/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ doctest = false
2424
quote = { workspace = true }
2525
syn = { workspace = true, features = ["full"] }
2626
proc-macro2 = { workspace = true }
27+
lazy_static = { workspace = true }
28+
rustc-hash = { workspace = true }

crates/oxc_ast_macros/src/generated/ast_field_order_data.rs

Lines changed: 405 additions & 0 deletions
Large diffs are not rendered by default.

crates/oxc_ast_macros/src/lib.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
mod reorder_fields;
2+
mod generated {
3+
pub mod ast_field_order_data;
4+
}
5+
16
use proc_macro::TokenStream;
27
use proc_macro2::TokenStream as TokenStream2;
38
use quote::quote;
9+
use reorder_fields::reorder_fields;
410

511
/// returns `#[repr(C, u8)]` if `enum_` has any non-unit variant,
612
/// Otherwise it would return `#[repr(u8)]`.
@@ -25,10 +31,16 @@ fn enum_repr(enum_: &syn::ItemEnum) -> TokenStream2 {
2531
#[allow(clippy::missing_panics_doc)]
2632
pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
2733
let input = syn::parse_macro_input!(input as syn::Item);
28-
29-
let repr = match input {
30-
syn::Item::Enum(ref enum_) => enum_repr(enum_),
31-
syn::Item::Struct(_) => quote!(#[repr(C)]),
34+
let (repr, item) = match input {
35+
syn::Item::Enum(enum_) => (enum_repr(&enum_), syn::Item::Enum(enum_)),
36+
syn::Item::Struct(mut struct_) => {
37+
let id = struct_.ident.to_string();
38+
// if we have field ordering data for this type use it to reorder.
39+
if let Some(data) = generated::ast_field_order_data::get(id.as_str()) {
40+
reorder_fields(&mut struct_, data);
41+
};
42+
(quote!(#[repr(C)]), syn::Item::Struct(struct_))
43+
}
3244

3345
_ => {
3446
unreachable!()
@@ -38,7 +50,7 @@ pub fn ast(_args: TokenStream, input: TokenStream) -> TokenStream {
3850
let expanded = quote! {
3951
#[derive(::oxc_ast_macros::Ast)]
4052
#repr
41-
#input
53+
#item
4254
};
4355
TokenStream::from(expanded)
4456
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use proc_macro2::TokenStream;
2+
use quote::format_ident;
3+
use syn::{Field, Fields, FieldsNamed, FieldsUnnamed, ItemStruct};
4+
5+
const DUMMY: &str = "DUMMY";
6+
7+
#[inline]
8+
pub fn reorder_fields(ty: &mut ItemStruct, data: &[u8]) {
9+
let (Fields::Named(FieldsNamed { named: fields, .. })
10+
| Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. })) = &mut ty.fields
11+
else {
12+
debug_assert!(false, "Entered unreachable code!");
13+
// SAFETY: We don't generate any ordering data for empty structs, And the debug assertions
14+
// are on in CI runs; The debug assertion above would ensure any possible mistake gets caught
15+
// by tests early on in the PR's life span. This allows us to avoid a branch here.
16+
unsafe { std::hint::unreachable_unchecked() }
17+
};
18+
19+
// TODO: We can replace this with uninitialized memory, It might be faster if we use one byte
20+
// to check if a field is placeholder or not and keep the rest of the bytes uninitialized as we
21+
// never read them. I'm not sure if it is safe with a mutable reference or not but I believe it
22+
// would be safe with exclusive ownership of the field.
23+
let mut pick = Field {
24+
attrs: Vec::default(),
25+
vis: syn::Visibility::Inherited,
26+
mutability: syn::FieldMutability::None,
27+
ident: Some(format_ident!("{DUMMY}")),
28+
colon_token: None,
29+
ty: syn::Type::Verbatim(TokenStream::default()),
30+
};
31+
// TODO: use bit array here?
32+
let mut is_ordered = vec![false; fields.len()];
33+
let mut target;
34+
// Best case O(n), Worst case O(2n)
35+
for i in 0..fields.len() {
36+
if is_ordered[i] {
37+
continue;
38+
}
39+
40+
let field = &mut fields[i];
41+
// `pick` the first unordered field
42+
pick = std::mem::replace(field, pick);
43+
// capture its ideal position
44+
target = data[i];
45+
46+
// while we have something in our `pick`
47+
while pick.ident.as_ref().is_some_and(|it| it != DUMMY) {
48+
// select the slot of target position
49+
let field = &mut fields[target as usize];
50+
// put the picked field in the target slot and pick the previous item
51+
pick = std::mem::replace(field, pick);
52+
// mark the field as ordered
53+
is_ordered[target as usize] = true;
54+
// capture the ideal position of our new pick
55+
target = data[target as usize];
56+
}
57+
}
58+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use itertools::Itertools;
2+
use proc_macro2::TokenStream;
3+
use quote::quote;
4+
use syn::ItemStruct;
5+
6+
use crate::{layout::Layout, output, schema::RType, CodegenCtx, Generator, GeneratorOutput};
7+
8+
use super::{define_generator, generated_header};
9+
10+
define_generator! {
11+
pub struct AstFieldOrder;
12+
}
13+
14+
impl Generator for AstFieldOrder {
15+
fn name(&self) -> &'static str {
16+
stringify!(AstFieldOrder)
17+
}
18+
19+
fn generate(&mut self, ctx: &CodegenCtx) -> GeneratorOutput {
20+
let orders_64 = ctx
21+
.ty_table
22+
.iter()
23+
.filter(|ty| matches!(&*ty.borrow(), RType::Struct(s) if !s.item.fields.is_empty()))
24+
.map(|ty| {
25+
let RType::Struct(ty) = &*ty.borrow() else { unreachable!() };
26+
generate_orders(&ty.item, &ty.meta.layout_64)
27+
});
28+
let orders_32 = ctx
29+
.ty_table
30+
.iter()
31+
.filter(|ty| matches!(&*ty.borrow(), RType::Struct(s) if !s.item.fields.is_empty()))
32+
.map(|ty| {
33+
let RType::Struct(ty) = &*ty.borrow() else { unreachable!() };
34+
generate_orders(&ty.item, &ty.meta.layout_64)
35+
});
36+
let header = generated_header!();
37+
GeneratorOutput::Stream((
38+
output(crate::AST_MACROS_CRATE, "ast_field_order_data.rs"),
39+
quote! {
40+
#header
41+
use lazy_static::lazy_static;
42+
use rustc_hash::FxHashMap;
43+
44+
endl!();
45+
46+
pub fn get(ident: &str) -> Option<&[u8]> {
47+
48+
#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
49+
std::compile_error!(
50+
"Platforms with pointer width other than 64 or 32 bit are not supported"
51+
);
52+
#[cfg(target_pointer_width = "64")]
53+
lazy_static! {
54+
static ref DATA: FxHashMap<&'static str, &'static [u8]> =
55+
FxHashMap::from_iter([#(#orders_64),*]);
56+
}
57+
#[cfg(target_pointer_width = "32")]
58+
lazy_static! {
59+
static ref DATA: FxHashMap<&'static str, &'static [u8]> =
60+
FxHashMap::from_iter([#(#orders_32),*]);
61+
}
62+
63+
64+
DATA.get(ident).copied()
65+
}
66+
},
67+
))
68+
}
69+
}
70+
71+
fn generate_orders(ty: &ItemStruct, layout: &Layout) -> Option<TokenStream> {
72+
let ident = &ty.ident.to_string();
73+
let Layout::Layout(layout) = layout else { panic!("Layout isn't determined yet!") };
74+
let offsets = layout.offsets();
75+
if let Some(offsets) = offsets {
76+
let orders = offsets
77+
.iter()
78+
.zip(ty.fields.iter().enumerate())
79+
.sorted_by(|a, b| Ord::cmp(a.0, b.0))
80+
.map(|(_, fi)| fi)
81+
.enumerate()
82+
.sorted_by(|a, b| Ord::cmp(&a.1 .0, &b.1 .0))
83+
.map(|it| {
84+
u8::try_from(it.0).expect("We have no AST type with enough fields to exhaust `u8`.")
85+
});
86+
Some(quote!((#ident, &[#(#orders),*][..])))
87+
} else {
88+
None
89+
}
90+
}

tasks/ast_codegen/src/generators/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod assert_layouts;
22
mod ast_builder;
3+
mod ast_field_order;
34
mod ast_kind;
45
mod impl_get_span;
56
mod visit;
@@ -42,6 +43,7 @@ pub(crate) use insert;
4243

4344
pub use assert_layouts::AssertLayouts;
4445
pub use ast_builder::AstBuilderGenerator;
46+
pub use ast_field_order::AstFieldOrder;
4547
pub use ast_kind::AstKindGenerator;
4648
pub use impl_get_span::ImplGetSpanGenerator;
4749
pub use visit::{VisitGenerator, VisitMutGenerator};

tasks/ast_codegen/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const AST_CRATE: &str = "crates/oxc_ast";
2-
#[allow(dead_code)]
32
const AST_MACROS_CRATE: &str = "crates/oxc_ast_macros";
43

54
mod defs;
@@ -22,7 +21,7 @@ use syn::parse_file;
2221

2322
use defs::TypeDef;
2423
use generators::{
25-
AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, VisitGenerator,
24+
AssertLayouts, AstBuilderGenerator, AstFieldOrder, AstKindGenerator, Generator, VisitGenerator,
2625
VisitMutGenerator,
2726
};
2827
use schema::{Module, REnum, RStruct, RType, Schema};
@@ -265,6 +264,7 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
265264
.pass(CalcLayout)
266265
.pass(BuildSchema)
267266
.gen(AssertLayouts)
267+
.gen(AstFieldOrder)
268268
.gen(AstKindGenerator)
269269
.gen(AstBuilderGenerator)
270270
.gen(ImplGetSpanGenerator)

tasks/ast_codegen/src/passes/calc_layout.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,16 @@ fn calc_struct_layout(ty: &mut RStruct, ctx: &CodegenCtx) -> Result<PlatformLayo
164164
fn with_padding(
165165
layouts: &[KnownLayout],
166166
) -> std::result::Result<KnownLayout, std::alloc::LayoutError> {
167-
let layouts = layouts.iter().enumerate();
167+
// reorder fields
168+
let layouts = layouts.iter().enumerate().sorted_by(|a, b| {
169+
let (ia, a) = a;
170+
let (ib, b) = b;
171+
if b.size() == a.size() {
172+
Ord::cmp(&ia, &ib)
173+
} else {
174+
Ord::cmp(&b.size(), &a.size())
175+
}
176+
});
168177
let mut offsets = vec![0; layouts.len()];
169178
let mut output = std::alloc::Layout::from_size_align(0, 1)?;
170179
let mut niches = 0;

0 commit comments

Comments
 (0)