mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
feat(ast_codegen): add alignment and size data to the schema. (#4615)
This PR generates the layouts in the schema but doesn't use it to reorder.
This commit is contained in:
parent
3f53b6f586
commit
0c52c0db02
11 changed files with 3038 additions and 9 deletions
2249
crates/oxc_ast/src/generated/assert_layouts.rs
Normal file
2249
crates/oxc_ast/src/generated/assert_layouts.rs
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -37,6 +37,8 @@ pub mod syntax_directed_operations;
|
|||
mod trivia;
|
||||
|
||||
mod generated {
|
||||
#[cfg(test)]
|
||||
pub mod assert_layouts;
|
||||
pub mod ast_builder;
|
||||
pub mod ast_kind;
|
||||
pub mod span;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::{REnum, RStruct, RType};
|
||||
use crate::{schema::Inherit, util::TypeExt, TypeName};
|
||||
use crate::{layout::KnownLayout, schema::Inherit, util::TypeExt, TypeName};
|
||||
use quote::ToTokens;
|
||||
use serde::Serialize;
|
||||
|
||||
|
|
@ -9,11 +9,26 @@ pub enum TypeDef {
|
|||
Enum(EnumDef),
|
||||
}
|
||||
|
||||
impl TypeDef {
|
||||
pub fn name(&self) -> &String {
|
||||
match self {
|
||||
Self::Struct(it) => &it.name,
|
||||
Self::Enum(it) => &it.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct StructDef {
|
||||
pub name: TypeName,
|
||||
pub fields: Vec<FieldDef>,
|
||||
pub has_lifetime: bool,
|
||||
pub size_64: usize,
|
||||
pub align_64: usize,
|
||||
pub offsets_64: Option<Vec<usize>>,
|
||||
pub size_32: usize,
|
||||
pub align_32: usize,
|
||||
pub offsets_32: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -23,6 +38,12 @@ pub struct EnumDef {
|
|||
/// For `@inherits` inherited enum variants
|
||||
pub inherits: Vec<EnumInheritDef>,
|
||||
pub has_lifetime: bool,
|
||||
pub size_64: usize,
|
||||
pub align_64: usize,
|
||||
pub offsets_64: Option<Vec<usize>>,
|
||||
pub size_32: usize,
|
||||
pub align_32: usize,
|
||||
pub offsets_32: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
@ -57,21 +78,55 @@ impl From<&RType> for Option<TypeDef> {
|
|||
|
||||
impl From<&REnum> for EnumDef {
|
||||
fn from(it @ REnum { item, meta }: &REnum) -> Self {
|
||||
let (size_64, align_64, offsets_64) = meta
|
||||
.layout_64
|
||||
.clone()
|
||||
.layout()
|
||||
.map_or_else(|| panic!("Uncalculated layout on {}!", item.ident), KnownLayout::unpack);
|
||||
let (size_32, align_32, offsets_32) = meta
|
||||
.layout_32
|
||||
.clone()
|
||||
.layout()
|
||||
.map_or_else(|| panic!("Uncalculated layout on {}!", item.ident), KnownLayout::unpack);
|
||||
Self {
|
||||
name: it.ident().to_string(),
|
||||
variants: item.variants.iter().map(Into::into).collect(),
|
||||
has_lifetime: item.generics.lifetimes().count() > 0,
|
||||
inherits: meta.inherits.iter().map(Into::into).collect(),
|
||||
has_lifetime: item.generics.lifetimes().count() > 0,
|
||||
|
||||
size_64,
|
||||
align_64,
|
||||
offsets_64,
|
||||
size_32,
|
||||
align_32,
|
||||
offsets_32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&RStruct> for StructDef {
|
||||
fn from(it @ RStruct { item, .. }: &RStruct) -> Self {
|
||||
fn from(it @ RStruct { item, meta }: &RStruct) -> Self {
|
||||
let (size_64, align_64, offsets_64) = meta
|
||||
.layout_64
|
||||
.clone()
|
||||
.layout()
|
||||
.map_or_else(|| panic!("Uncalculated layout on {}!", item.ident), KnownLayout::unpack);
|
||||
let (size_32, align_32, offsets_32) = meta
|
||||
.layout_32
|
||||
.clone()
|
||||
.layout()
|
||||
.map_or_else(|| panic!("Uncalculated layout on {}!", item.ident), KnownLayout::unpack);
|
||||
Self {
|
||||
name: it.ident().to_string(),
|
||||
fields: item.fields.iter().map(Into::into).collect(),
|
||||
has_lifetime: item.generics.lifetimes().count() > 0,
|
||||
|
||||
size_64,
|
||||
align_64,
|
||||
offsets_64,
|
||||
size_32,
|
||||
align_32,
|
||||
offsets_32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
118
tasks/ast_codegen/src/generators/assert_layouts.rs
Normal file
118
tasks/ast_codegen/src/generators/assert_layouts.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{PathArguments, Type};
|
||||
|
||||
use crate::{
|
||||
defs::{FieldDef, TypeDef},
|
||||
output, CodegenCtx, Generator, GeneratorOutput,
|
||||
};
|
||||
|
||||
use super::{define_generator, generated_header};
|
||||
|
||||
define_generator! {
|
||||
pub struct AssertLayouts;
|
||||
}
|
||||
|
||||
impl Generator for AssertLayouts {
|
||||
fn name(&self) -> &'static str {
|
||||
stringify!(AssertLayouts)
|
||||
}
|
||||
|
||||
fn generate(&mut self, ctx: &CodegenCtx) -> GeneratorOutput {
|
||||
let (assertions_64, assertions_32) = ctx
|
||||
.schema
|
||||
.borrow()
|
||||
.definitions
|
||||
.iter()
|
||||
.map(|def| {
|
||||
let typ =
|
||||
ctx.find(def.name()).and_then(|ty| ty.borrow().as_type()).map(|mut ty| {
|
||||
if let Type::Path(ty) = &mut ty {
|
||||
if let Some(seg) = ty.path.segments.first_mut() {
|
||||
seg.arguments = PathArguments::None;
|
||||
}
|
||||
}
|
||||
ty
|
||||
});
|
||||
let typ = typ.unwrap();
|
||||
assert_type(&typ, def)
|
||||
})
|
||||
.collect::<(Vec<TokenStream>, Vec<TokenStream>)>();
|
||||
|
||||
let header = generated_header!();
|
||||
|
||||
GeneratorOutput::Stream((
|
||||
output(crate::AST_CRATE, "assert_layouts.rs"),
|
||||
quote! {
|
||||
#header
|
||||
|
||||
use std::mem::{align_of, offset_of, size_of};
|
||||
|
||||
endl!();
|
||||
|
||||
use crate::ast::*;
|
||||
|
||||
endl!();
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
const _: () = { #(#assertions_64)* };
|
||||
endl!();
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
const _: () = { #(#assertions_32)* };
|
||||
endl!();
|
||||
|
||||
#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
|
||||
const _: () = panic!("Platforms with pointer width other than 64 or 32 bit are not supported");
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_type(ty: &Type, def: &TypeDef) -> (TokenStream, TokenStream) {
|
||||
match def {
|
||||
TypeDef::Struct(def) => (
|
||||
with_offsets_assertion(
|
||||
assert_size_align(ty, def.size_64, def.align_64),
|
||||
ty,
|
||||
&def.fields,
|
||||
def.offsets_64.as_deref(),
|
||||
),
|
||||
with_offsets_assertion(
|
||||
assert_size_align(ty, def.size_32, def.align_32),
|
||||
ty,
|
||||
&def.fields,
|
||||
def.offsets_64.as_deref(),
|
||||
),
|
||||
),
|
||||
TypeDef::Enum(def) => (
|
||||
assert_size_align(ty, def.size_64, def.align_64),
|
||||
assert_size_align(ty, def.size_32, def.align_32),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_size_align(ty: &Type, size: usize, align: usize) -> TokenStream {
|
||||
quote! {
|
||||
assert!(size_of::<#ty>() == #size);
|
||||
assert!(align_of::<#ty>() == #align);
|
||||
}
|
||||
}
|
||||
|
||||
fn with_offsets_assertion(
|
||||
mut tk: TokenStream,
|
||||
ty: &Type,
|
||||
fields: &[FieldDef],
|
||||
offsets: Option<&[usize]>,
|
||||
) -> TokenStream {
|
||||
let Some(offsets) = offsets else { return tk };
|
||||
|
||||
let assertions = fields.iter().zip(offsets).map(|(field, offset)| {
|
||||
let field = field.name.as_ref().map(|it| format_ident!("{it}"));
|
||||
quote! {
|
||||
assert!(offset_of!(#ty, #field) == #offset);
|
||||
}
|
||||
});
|
||||
tk.extend(assertions);
|
||||
tk
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
mod assert_layouts;
|
||||
mod ast_builder;
|
||||
mod ast_kind;
|
||||
mod impl_get_span;
|
||||
|
|
@ -39,6 +40,7 @@ macro_rules! generated_header {
|
|||
pub(crate) use generated_header;
|
||||
pub(crate) use insert;
|
||||
|
||||
pub use assert_layouts::AssertLayouts;
|
||||
pub use ast_builder::AstBuilderGenerator;
|
||||
pub use ast_kind::AstKindGenerator;
|
||||
pub use impl_get_span::ImplGetSpanGenerator;
|
||||
|
|
|
|||
181
tasks/ast_codegen/src/layout.rs
Normal file
181
tasks/ast_codegen/src/layout.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub enum Layout {
|
||||
#[default]
|
||||
Unknown,
|
||||
Layout(KnownLayout),
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub const fn known(size: usize, align: usize, niches: u128) -> Self {
|
||||
Self::Layout(KnownLayout { size, align, niches, offsets: None })
|
||||
}
|
||||
|
||||
pub fn layout(self) -> Option<KnownLayout> {
|
||||
if let Self::Layout(layout) = self {
|
||||
Some(layout)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KnownLayout> for Layout {
|
||||
fn from(layout: KnownLayout) -> Self {
|
||||
Self::Layout(layout)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct KnownLayout {
|
||||
size: usize,
|
||||
align: usize,
|
||||
/// number of available niches
|
||||
niches: u128,
|
||||
offsets: Option<Vec<usize>>,
|
||||
}
|
||||
|
||||
impl KnownLayout {
|
||||
pub const fn new(size: usize, align: usize, niches: u128) -> Self {
|
||||
Self { size, align, niches, offsets: None }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn align(&self) -> usize {
|
||||
self.align
|
||||
}
|
||||
|
||||
/// number of available niches
|
||||
#[inline]
|
||||
pub fn niches(&self) -> u128 {
|
||||
self.niches
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn offsets(&self) -> Option<&Vec<usize>> {
|
||||
self.offsets.as_ref()
|
||||
}
|
||||
|
||||
pub unsafe fn set_size_unchecked(&mut self, size: usize) {
|
||||
self.size = size;
|
||||
}
|
||||
|
||||
pub unsafe fn set_align_unchecked(&mut self, align: usize) {
|
||||
self.align = align;
|
||||
}
|
||||
|
||||
pub unsafe fn set_niches_unchecked(&mut self, niches: u128) {
|
||||
self.niches = niches;
|
||||
}
|
||||
|
||||
pub fn with_offsets(mut self, offsets: Vec<usize>) -> Self {
|
||||
self.offsets = Some(offsets);
|
||||
self
|
||||
}
|
||||
|
||||
/// Panics
|
||||
/// if doesn't have enough viable space and `can_resize` is false
|
||||
pub fn consume_niches(&mut self, n: u128, can_resize: bool) {
|
||||
if self.niches() >= n {
|
||||
self.niches -= n;
|
||||
} else if can_resize {
|
||||
let align = self.align();
|
||||
self.size += align;
|
||||
self.niches += max_val_of_bytes(align);
|
||||
self.consume_niches(n, can_resize);
|
||||
} else {
|
||||
panic!("`{}` called on a layout without enough space.", stringify!(consume_niches));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unpack(self) -> (/* size */ usize, /* align */ usize, /* offsets */ Option<Vec<usize>>) {
|
||||
let Self { size, align, offsets, .. } = self;
|
||||
(size, align, offsets)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
/// # Panics
|
||||
/// If `T` has more than 8 niches.
|
||||
pub const fn of<T>() -> Self {
|
||||
// TODO: find a better way of calculating this.
|
||||
struct N1<T>(Option<T>);
|
||||
struct N2<T>(N1<N1<T>>);
|
||||
struct N3<T>(N1<N2<T>>);
|
||||
struct N4<T>(N1<N3<T>>);
|
||||
struct N5<T>(N1<N4<T>>);
|
||||
struct N6<T>(N1<N5<T>>);
|
||||
struct N7<T>(N1<N6<T>>);
|
||||
struct N8<T>(N1<N7<T>>);
|
||||
|
||||
let size = size_of::<T>();
|
||||
let align = align_of::<T>();
|
||||
let niches = if size_of::<N1<T>>() > size {
|
||||
0
|
||||
} else if size_of::<N2<T>>() > size {
|
||||
1
|
||||
} else if size_of::<N3<T>>() > size {
|
||||
2
|
||||
} else if size_of::<N4<T>>() > size {
|
||||
3
|
||||
} else if size_of::<N5<T>>() > size {
|
||||
4
|
||||
} else if size_of::<N6<T>>() > size {
|
||||
5
|
||||
} else if size_of::<N7<T>>() > size {
|
||||
6
|
||||
} else if size_of::<N8<T>>() > size {
|
||||
7
|
||||
} else if size_of::<N8<T>>() == size {
|
||||
8
|
||||
} else {
|
||||
panic!("`T` has more niches than what we can infer automatically");
|
||||
};
|
||||
|
||||
Self::known(size, align, niches)
|
||||
}
|
||||
|
||||
pub const fn zero() -> Self {
|
||||
#[repr(C)]
|
||||
struct Empty;
|
||||
Self::of::<Empty>()
|
||||
}
|
||||
|
||||
pub const fn ptr_32() -> Self {
|
||||
Layout::known(4, 4, 0)
|
||||
}
|
||||
|
||||
pub const fn ptr_64() -> Self {
|
||||
Layout::known(8, 8, 0)
|
||||
}
|
||||
|
||||
pub const fn wide_ptr_32() -> Self {
|
||||
Layout::known(8, 4, 1)
|
||||
}
|
||||
|
||||
pub const fn wide_ptr_64() -> Self {
|
||||
Layout::of::<&str>()
|
||||
}
|
||||
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
matches!(self, Self::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the max valid number in a primitive with the size of `n` bytes.
|
||||
/// Panics
|
||||
/// For `n` bigger than `16`, Or if it's not a power of 2 number
|
||||
fn max_val_of_bytes(n: usize) -> u128 {
|
||||
match n {
|
||||
1 => u8::MAX as u128,
|
||||
2 => u16::MAX as u128,
|
||||
4 => u32::MAX as u128,
|
||||
8 => u64::MAX as u128,
|
||||
16 => u128::MAX,
|
||||
_ => panic!("We do not support `n` bigger than 16 bytes."),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
const AST_CRATE: &str = "crates/oxc_ast";
|
||||
#[allow(dead_code)]
|
||||
const AST_MACROS_CRATE: &str = "crates/oxc_ast_macros";
|
||||
|
||||
mod defs;
|
||||
mod fmt;
|
||||
mod generators;
|
||||
mod layout;
|
||||
mod markers;
|
||||
mod passes;
|
||||
mod schema;
|
||||
|
|
@ -13,13 +16,14 @@ use std::{cell::RefCell, collections::HashMap, io::Read, path::PathBuf, rc::Rc};
|
|||
use bpaf::{Bpaf, Parser};
|
||||
use fmt::{cargo_fmt, pprint};
|
||||
use itertools::Itertools;
|
||||
use passes::{BuildSchema, Linker, Pass};
|
||||
use passes::{BuildSchema, CalcLayout, Linker, Pass};
|
||||
use proc_macro2::TokenStream;
|
||||
use syn::parse_file;
|
||||
|
||||
use defs::TypeDef;
|
||||
use generators::{
|
||||
AstBuilderGenerator, AstKindGenerator, Generator, VisitGenerator, VisitMutGenerator,
|
||||
AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, VisitGenerator,
|
||||
VisitMutGenerator,
|
||||
};
|
||||
use schema::{Module, REnum, RStruct, RType, Schema};
|
||||
use util::{write_all_to, NormalizeError};
|
||||
|
|
@ -258,7 +262,9 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
|||
let CodegenResult { outputs, schema } = files()
|
||||
.fold(AstCodegen::default(), AstCodegen::add_file)
|
||||
.pass(Linker)
|
||||
.pass(CalcLayout)
|
||||
.pass(BuildSchema)
|
||||
.gen(AssertLayouts)
|
||||
.gen(AstKindGenerator)
|
||||
.gen(AstBuilderGenerator)
|
||||
.gen(ImplGetSpanGenerator)
|
||||
|
|
|
|||
370
tasks/ast_codegen/src/passes/calc_layout.rs
Normal file
370
tasks/ast_codegen/src/passes/calc_layout.rs
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
/// We use compiler to infer 64bit type layouts.
|
||||
#[cfg(not(target_pointer_width = "64"))]
|
||||
compile_error!("`oxc_ast_codegen::layout` only supports 64 architectures.");
|
||||
use std::collections::HashMap;
|
||||
|
||||
use itertools::Itertools;
|
||||
use lazy_static::lazy_static;
|
||||
use quote::ToTokens;
|
||||
use syn::Type;
|
||||
|
||||
use crate::{
|
||||
layout::{KnownLayout, Layout},
|
||||
schema::{REnum, RStruct, RType},
|
||||
util::{NormalizeError, TypeAnalyzeResult, TypeExt, TypeWrapper},
|
||||
CodegenCtx, Result, TypeRef,
|
||||
};
|
||||
|
||||
use super::{define_pass, Pass};
|
||||
|
||||
type WellKnown = HashMap<&'static str, PlatformLayout>;
|
||||
|
||||
define_pass! {
|
||||
pub struct CalcLayout;
|
||||
}
|
||||
|
||||
impl Pass for CalcLayout {
|
||||
fn name(&self) -> &'static str {
|
||||
stringify!(CalcLayout)
|
||||
}
|
||||
|
||||
fn each(&mut self, ty: &mut RType, ctx: &CodegenCtx) -> crate::Result<bool> {
|
||||
calc_layout(ty, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PlatformLayout(/* 64 */ Layout, /* 32 */ Layout);
|
||||
|
||||
impl PlatformLayout {
|
||||
const UNKNOWN: Self = Self(Layout::Unknown, Layout::Unknown);
|
||||
|
||||
const fn zero() -> Self {
|
||||
Self(Layout::zero(), Layout::zero())
|
||||
}
|
||||
|
||||
const fn ptr() -> Self {
|
||||
Self(Layout::ptr_64(), Layout::ptr_32())
|
||||
}
|
||||
|
||||
const fn wide_ptr() -> Self {
|
||||
Self(Layout::wide_ptr_64(), Layout::wide_ptr_32())
|
||||
}
|
||||
|
||||
/// Return `true` if either of platform layouts is unknown.
|
||||
fn is_unknown(&self) -> bool {
|
||||
self.0.is_unknown() || self.1.is_unknown()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Layout, Layout)> for PlatformLayout {
|
||||
fn from((x64, x32): (Layout, Layout)) -> Self {
|
||||
Self(x64, x32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the layout of `ty` by mutating it.
|
||||
/// Returns `false` if the layout is unknown at this point.
|
||||
pub fn calc_layout(ty: &mut RType, ctx: &CodegenCtx) -> Result<bool> {
|
||||
let unknown_layout = ty
|
||||
.layout_32()
|
||||
.and_then(|x32| ty.layout_64().map(|x64| PlatformLayout(x64, x32)))
|
||||
.is_ok_and(|pl| pl.is_unknown());
|
||||
let layout = match ty {
|
||||
RType::Enum(enum_) if unknown_layout => calc_enum_layout(enum_, ctx),
|
||||
RType::Struct(struct_) if unknown_layout => calc_struct_layout(struct_, ctx),
|
||||
_ => return Ok(true),
|
||||
}?;
|
||||
if layout.is_unknown() {
|
||||
Ok(false)
|
||||
} else {
|
||||
let PlatformLayout(x64, x32) = layout;
|
||||
ty.set_layout(x64, x32)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_enum_layout(ty: &mut REnum, ctx: &CodegenCtx) -> Result<PlatformLayout> {
|
||||
fn collect_variant_layouts(ty: &REnum, ctx: &CodegenCtx) -> Result<Vec<PlatformLayout>> {
|
||||
// all unit variants?
|
||||
if ty.item.variants.iter().all(|var| var.fields.is_empty()) {
|
||||
// all AST enums are `repr(u8)` so it would have a 1 byte layout/alignment,
|
||||
// if it holds no data
|
||||
let layout = KnownLayout::new(0, 1, 0);
|
||||
let layout = Layout::Layout(layout);
|
||||
Ok(vec![PlatformLayout(layout.clone(), layout)])
|
||||
} else {
|
||||
ty.item
|
||||
.variants
|
||||
.iter()
|
||||
.map(|var| {
|
||||
let typ =
|
||||
var.fields.iter().exactly_one().map(|f| f.ty.analyze(ctx)).normalize()?;
|
||||
calc_type_layout(&typ, ctx)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
fn fold_layout(mut acc: KnownLayout, layout: KnownLayout) -> KnownLayout {
|
||||
// SAFETY: we are folding valid layouts so it is safe.
|
||||
unsafe {
|
||||
// max alignment
|
||||
if layout.align() > acc.align() {
|
||||
acc.set_align_unchecked(layout.align());
|
||||
}
|
||||
// max size
|
||||
if layout.size() > acc.size() {
|
||||
acc.set_size_unchecked(layout.size());
|
||||
}
|
||||
// min niches
|
||||
if layout.niches() < acc.niches() {
|
||||
acc.set_niches_unchecked(layout.niches());
|
||||
}
|
||||
}
|
||||
acc
|
||||
}
|
||||
|
||||
let with_tag = |mut acc: KnownLayout| -> KnownLayout {
|
||||
acc.consume_niches(ty.item.variants.len() as u128, true);
|
||||
acc
|
||||
};
|
||||
|
||||
let layouts = collect_variant_layouts(ty, ctx)?;
|
||||
let (layouts_x64, layouts_x32): (Vec<KnownLayout>, Vec<KnownLayout>) = layouts
|
||||
.into_iter()
|
||||
.map(|PlatformLayout(x64, x32)| {
|
||||
x64.layout().and_then(|x64| x32.layout().map(|x32| (x64, x32)))
|
||||
})
|
||||
.collect::<Option<_>>()
|
||||
.expect("already checked.");
|
||||
|
||||
let x32 = with_tag(layouts_x32.into_iter().fold(KnownLayout::default(), fold_layout));
|
||||
let x64 = with_tag(layouts_x64.into_iter().fold(KnownLayout::default(), fold_layout));
|
||||
Ok(PlatformLayout(Layout::from(x64), Layout::from(x32)))
|
||||
}
|
||||
|
||||
fn calc_struct_layout(ty: &mut RStruct, ctx: &CodegenCtx) -> Result<PlatformLayout> {
|
||||
fn collect_field_layouts(ty: &RStruct, ctx: &CodegenCtx) -> Result<Vec<PlatformLayout>> {
|
||||
if ty.item.fields.is_empty() {
|
||||
Ok(vec![PlatformLayout::zero()])
|
||||
} else {
|
||||
ty.item
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let typ = field.ty.analyze(ctx);
|
||||
calc_type_layout(&typ, ctx)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn with_padding(
|
||||
layouts: &[KnownLayout],
|
||||
) -> std::result::Result<KnownLayout, std::alloc::LayoutError> {
|
||||
let layouts = layouts.iter().enumerate();
|
||||
let mut offsets = vec![0; layouts.len()];
|
||||
let mut output = std::alloc::Layout::from_size_align(0, 1)?;
|
||||
let mut niches = 0;
|
||||
for (ix, layout) in layouts {
|
||||
let (new_layout, offset) = output
|
||||
.extend(std::alloc::Layout::from_size_align(layout.size(), layout.align())?)?;
|
||||
output = new_layout;
|
||||
niches += layout.niches();
|
||||
offsets[ix] = offset;
|
||||
}
|
||||
let output = output.pad_to_align();
|
||||
Ok(KnownLayout::new(output.size(), output.align(), niches).with_offsets(offsets))
|
||||
}
|
||||
|
||||
let layouts = collect_field_layouts(ty, ctx)?;
|
||||
|
||||
if layouts.iter().any(PlatformLayout::is_unknown) {
|
||||
return Ok(PlatformLayout::UNKNOWN);
|
||||
}
|
||||
|
||||
let (layouts_x64, layouts_x32): (Vec<KnownLayout>, Vec<KnownLayout>) = layouts
|
||||
.into_iter()
|
||||
.map(|PlatformLayout(x64, x32)| {
|
||||
x64.layout().and_then(|x64| x32.layout().map(|x32| (x64, x32)))
|
||||
})
|
||||
.collect::<Option<_>>()
|
||||
.expect("already checked.");
|
||||
|
||||
let x32 = with_padding(&layouts_x32).normalize()?;
|
||||
let x64 = with_padding(&layouts_x64).normalize()?;
|
||||
|
||||
Ok(PlatformLayout(Layout::from(x64), Layout::from(x32)))
|
||||
}
|
||||
|
||||
fn calc_type_layout(ty: &TypeAnalyzeResult, ctx: &CodegenCtx) -> Result<PlatformLayout> {
|
||||
fn is_slice(ty: &TypeAnalyzeResult) -> bool {
|
||||
if let Type::Reference(typ) = &ty.typ {
|
||||
// TODO: support for &[T] slices.
|
||||
typ.elem.get_ident().as_ident().is_some_and(|id| id == "str")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_option(layout: Layout) -> Layout {
|
||||
let Layout::Layout(mut known) = layout else { return layout };
|
||||
// option needs only one niche, We allow resizing in case there isn't enough space.
|
||||
known.consume_niches(1, true);
|
||||
Layout::Layout(known)
|
||||
}
|
||||
|
||||
let get_layout = |type_ref: Option<&TypeRef>| -> Result<PlatformLayout> {
|
||||
let result = if let Some(type_ref) = &type_ref {
|
||||
if calc_layout(&mut type_ref.borrow_mut(), ctx)? {
|
||||
type_ref.borrow().layouts().map(PlatformLayout::from)?
|
||||
} else {
|
||||
PlatformLayout::UNKNOWN
|
||||
}
|
||||
} else if let Some(well_known) =
|
||||
WELL_KNOWN.get(ty.typ.get_ident().inner_ident().to_string().as_str())
|
||||
{
|
||||
well_known.clone()
|
||||
} else {
|
||||
let Type::Path(typ) = &ty.typ else {
|
||||
panic!();
|
||||
};
|
||||
|
||||
let typ = typ
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.map(|it| it.to_token_stream().to_string().replace(' ', ""))
|
||||
.expect("We only accept single segment types.");
|
||||
|
||||
if let Some(typ) = WELL_KNOWN.get(typ.as_str()) {
|
||||
typ.clone()
|
||||
} else {
|
||||
panic!("Unsupported type: {:#?}", ty.typ.to_token_stream().to_string())
|
||||
}
|
||||
};
|
||||
Ok(result)
|
||||
};
|
||||
|
||||
let layout = match ty.wrapper {
|
||||
TypeWrapper::Vec | TypeWrapper::VecBox | TypeWrapper::VecOpt => {
|
||||
WELL_KNOWN[stringify!(Vec)].clone()
|
||||
}
|
||||
TypeWrapper::OptVec => {
|
||||
let mut pl = WELL_KNOWN[stringify!(Vec)].clone();
|
||||
// preconsume one niche for option
|
||||
if let Layout::Layout(layout) = &mut pl.0 {
|
||||
layout.consume_niches(1, true);
|
||||
}
|
||||
if let Layout::Layout(layout) = &mut pl.1 {
|
||||
layout.consume_niches(1, true);
|
||||
}
|
||||
pl
|
||||
}
|
||||
TypeWrapper::Ref if is_slice(ty) => PlatformLayout::wide_ptr(),
|
||||
TypeWrapper::Ref | TypeWrapper::Box | TypeWrapper::OptBox => PlatformLayout::ptr(),
|
||||
TypeWrapper::None => get_layout(ty.type_ref.as_ref())?,
|
||||
TypeWrapper::Opt => {
|
||||
let PlatformLayout(x64, x32) = get_layout(ty.type_ref.as_ref())?;
|
||||
PlatformLayout(try_fold_option(x64), try_fold_option(x32))
|
||||
}
|
||||
TypeWrapper::Complex => {
|
||||
let PlatformLayout(x64, x32) = get_layout(ty.type_ref.as_ref())?;
|
||||
PlatformLayout(x64, x32)
|
||||
}
|
||||
};
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
macro_rules! well_known {
|
||||
($($typ:ty: { $($platform:tt => $layout:expr,)*},)*) => {
|
||||
WellKnown::from([
|
||||
$((
|
||||
stringify!($typ),
|
||||
well_known!(@ $( $platform => $layout,)*)
|
||||
)),*
|
||||
])
|
||||
};
|
||||
|
||||
// entries
|
||||
(@ _ => $layout:expr,) => {
|
||||
PlatformLayout($layout, $layout)
|
||||
};
|
||||
(@ 64 => $layout_64:expr, 32 => $layout_32:expr,) => {
|
||||
PlatformLayout($layout_64, $layout_32)
|
||||
};
|
||||
(@ 32 => $layout_32:expr, 64 => $layout_64:expr,) => {
|
||||
well_known!(@ 64 => $layout_64, 32 => $layout_32)
|
||||
};
|
||||
|
||||
// compile errors
|
||||
(@ 32 => $layout:expr,) => {
|
||||
::std::compile_error!("non_exhaustive well known type, `64` target isn't covered.")
|
||||
};
|
||||
(@ 64 => $layout:expr,) => {
|
||||
::std::compile_error!("non_exhaustive well known type, `32` target isn't covered.")
|
||||
};
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref WELL_KNOWN: WellKnown = well_known! {
|
||||
// builtins
|
||||
// types smaller or equal to 4bytes have the same layout on most platforms.
|
||||
char: { _ => Layout::of::<char>(), },
|
||||
bool: { _ => Layout::of::<bool>(), },
|
||||
u8: { _ => Layout::of::<u8>(), },
|
||||
i8: { _ => Layout::of::<i8>(), },
|
||||
u16: { _ => Layout::of::<u16>(), },
|
||||
i16: { _ => Layout::of::<i16>(), },
|
||||
u32: { _ => Layout::of::<u32>(), },
|
||||
i32: { _ => Layout::of::<i32>(), },
|
||||
f32: { _ => Layout::of::<f32>(), },
|
||||
// 32bit layouts are based on WASM
|
||||
u64: {
|
||||
64 => Layout::of::<u64>(),
|
||||
32 => Layout::known(8, 8, 0),
|
||||
},
|
||||
i64: {
|
||||
64 => Layout::of::<i64>(),
|
||||
32 => Layout::known(8, 8, 0),
|
||||
},
|
||||
f64: {
|
||||
64 => Layout::of::<f64>(),
|
||||
32 => Layout::known(8, 8, 0),
|
||||
},
|
||||
usize: {
|
||||
64 => Layout::of::<usize>(),
|
||||
32 => Layout::known(4, 4, 0),
|
||||
},
|
||||
isize: {
|
||||
64 => Layout::of::<isize>(),
|
||||
32 => Layout::known(4, 4, 0),
|
||||
},
|
||||
// well known types
|
||||
// TODO: generate const assertions for these in the ast crate
|
||||
Span: { _ => Layout::known(8, 4, 0), },
|
||||
Atom: {
|
||||
64 => Layout::wide_ptr_64(),
|
||||
32 => Layout::wide_ptr_32(),
|
||||
},
|
||||
Vec: {
|
||||
64 => Layout::known(32, 8, 1),
|
||||
32 => Layout::known(16, 4, 1),
|
||||
},
|
||||
Cell<Option<ScopeId>>: { _ => Layout::known(4, 4, 0), },
|
||||
Cell<Option<SymbolId>>: { _ => Layout::known(4, 4, 0), },
|
||||
Cell<Option<ReferenceId>>: { _ => Layout::known(4, 4, 0), },
|
||||
ReferenceFlag: { _ => Layout::known(1, 1, 0), },
|
||||
AssignmentOperator: { _ => Layout::known(1, 1, 1), },
|
||||
LogicalOperator: { _ => Layout::known(1, 1, 1), },
|
||||
UnaryOperator: { _ => Layout::known(1, 1, 1), },
|
||||
BinaryOperator: { _ => Layout::known(1, 1, 1), },
|
||||
UpdateOperator: { _ => Layout::known(1, 1, 1), },
|
||||
SourceType: { _ => Layout::known(4, 1, 1), },
|
||||
RegExpFlags: { _ => Layout::known(1, 1, 0), },
|
||||
BigintBase: { _ => Layout::known(1, 1, 1), },
|
||||
NumberBase: { _ => Layout::known(1, 1, 1), },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod build_schema;
|
||||
mod calc_layout;
|
||||
mod linker;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
|
@ -8,6 +9,7 @@ use itertools::Itertools;
|
|||
use crate::{schema::RType, CodegenCtx, Result};
|
||||
|
||||
pub use build_schema::BuildSchema;
|
||||
pub use calc_layout::CalcLayout;
|
||||
pub use linker::Linker;
|
||||
|
||||
pub trait Pass {
|
||||
|
|
|
|||
|
|
@ -9,13 +9,13 @@ use syn::{
|
|||
Path, Token, Type, Variant, Visibility,
|
||||
};
|
||||
|
||||
use crate::{util::NormalizeError, TypeName};
|
||||
use crate::{layout::Layout, util::NormalizeError, TypeName};
|
||||
|
||||
use super::{parse_file, Itertools, PathBuf, Rc, Read, RefCell, Result, TypeDef, TypeRef};
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize)]
|
||||
pub struct Schema {
|
||||
definitions: Vec<TypeDef>,
|
||||
pub definitions: Vec<TypeDef>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -33,6 +33,8 @@ impl From<Ident> for Inherit {
|
|||
#[derive(Debug, Default, Clone)]
|
||||
pub struct EnumMeta {
|
||||
pub inherits: Vec<Inherit>,
|
||||
pub layout_32: Layout,
|
||||
pub layout_64: Layout,
|
||||
pub visitable: bool,
|
||||
pub ast: bool,
|
||||
}
|
||||
|
|
@ -68,6 +70,8 @@ impl From<ItemEnum> for REnum {
|
|||
/// Placeholder for now!
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct StructMeta {
|
||||
pub layout_32: Layout,
|
||||
pub layout_64: Layout,
|
||||
pub visitable: bool,
|
||||
pub ast: bool,
|
||||
}
|
||||
|
|
@ -170,6 +174,41 @@ impl RType {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn layout_32(&self) -> Result<Layout> {
|
||||
match self {
|
||||
RType::Enum(it) => Ok(it.meta.layout_32.clone()),
|
||||
RType::Struct(it) => Ok(it.meta.layout_32.clone()),
|
||||
_ => Err("Unsupported type!".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layout_64(&self) -> Result<Layout> {
|
||||
match self {
|
||||
RType::Enum(it) => Ok(it.meta.layout_64.clone()),
|
||||
RType::Struct(it) => Ok(it.meta.layout_64.clone()),
|
||||
_ => Err("Unsupported type!".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn layouts(&self) -> Result<(/* 64 */ Layout, /* 32 */ Layout)> {
|
||||
self.layout_64().and_then(|x64| self.layout_32().map(|x32| (x64, x32)))
|
||||
}
|
||||
|
||||
pub fn set_layout(&mut self, layout_64: Layout, layout_32: Layout) -> Result<()> {
|
||||
macro_rules! assign {
|
||||
($it:ident) => {{
|
||||
$it.meta.layout_32 = layout_32;
|
||||
$it.meta.layout_64 = layout_64;
|
||||
}};
|
||||
}
|
||||
match self {
|
||||
RType::Enum(it) => assign!(it),
|
||||
RType::Struct(it) => assign!(it),
|
||||
_ => return Err("Unsupported type!".to_string()),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Item> for RType {
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ pub enum TypeWrapper {
|
|||
pub struct TypeAnalyzeResult {
|
||||
pub type_ref: Option<TypeRef>,
|
||||
pub wrapper: TypeWrapper,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
impl TypeExt for Type {
|
||||
|
|
@ -225,11 +226,15 @@ impl TypeExt for Type {
|
|||
}
|
||||
let type_ident = self.get_ident();
|
||||
let Some((type_ident, wrapper)) = analyze(&type_ident) else {
|
||||
return TypeAnalyzeResult { type_ref: None, wrapper: TypeWrapper::Ref };
|
||||
return TypeAnalyzeResult {
|
||||
type_ref: None,
|
||||
wrapper: TypeWrapper::Ref,
|
||||
typ: self.clone(),
|
||||
};
|
||||
};
|
||||
|
||||
let type_ref = ctx.find(&type_ident.to_string());
|
||||
TypeAnalyzeResult { type_ref, wrapper }
|
||||
TypeAnalyzeResult { type_ref, wrapper, typ: self.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue