Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf(ast, ast_codegen): optimize AST structs' memory layouts #4404

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

rzvxa
Copy link
Collaborator

@rzvxa rzvxa commented Jul 22, 2024

No description provided.

Copy link

graphite-app bot commented Jul 22, 2024

Your org has enabled the Graphite merge queue for merging into main

Add the label “merge” to the PR and Graphite will automatically add it to the merge queue when it’s ready to merge. Or use the label “hotfix” to add to the merge queue as a hot fix.

You must have a Graphite account and log in to Graphite in order to use the merge queue. Sign up using this link.

Copy link
Collaborator Author

rzvxa commented Jul 22, 2024

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

Join @rzvxa and the rest of your teammates on Graphite Graphite

Copy link

codspeed-hq bot commented Jul 22, 2024

CodSpeed Performance Report

Merging #4404 will not alter performance

Comparing 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema (80e3195) with main (0c52c0d)

Summary

✅ 32 untouched benchmarks

tasks/ast_codegen/src/layout.rs Outdated Show resolved Hide resolved
tasks/ast_codegen/src/layout.rs Outdated Show resolved Hide resolved
// well known types
// TODO: generate const assertions for these in the ast crate
Span => { 64: Layout::of::<oxc_span::Span>(), 32: Layout::of::<oxc_span::Span>() },
Atom => { 64: Layout::of::<oxc_span::Atom>(), 32: Layout::of::<oxc_span::Atom>() },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Span and Atom, can we mark them #[ast] and have codegen parse the files in oxc_span, same as it does for oxc_ast? We need both types to be made #[repr(C)] anyway.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly believe we shouldn't extend the ast marker to types outside of the AST crate. Those one-off types can always be made repr(C) without all this infra. Span is already in the ideal order, And Vec type doesn't expose its fields so it can be reordered manually. Atom is a transparent type so no need for this stuff whatsoever.

But that's just my preference, I still haven't seen any real need to process those types.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With that said, I have nothing against hard coding them as opposed to using a layout of method. I just did it for the sake of consistency if we regenerate on multiple architectures.

Copy link
Collaborator

@overlookmotel overlookmotel Jul 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Span and Atom we can mark them #[repr(C)] and hard-code them if you prefer. But:

  1. AST transfer needs to know the field order for Span (so we'd have to hard-code that too).
  2. We can't avoid extending outside of oxc_ast crate, because there are various types in oxc_syntax crate which are part of the AST e.g. AssignmentOperator. These types need #[repr(C)] and explicit discriminants. So it seems to me that simplest way to do that is #[ast] + codegen.

What's the reason for your opposition to codegen reaching into other crates?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mulled over this, I guess you are right it would be easier if we do so.
I wanted to keep the code generation's input limited to AST types, Vec, Box, Span, and Atom obviously aren't AST types. But your argument for types defined in oxc_syntax is valid.

I'm not familiar with all AST-related stuff in the oxc_syntax crate, But All I can remember is unit enums. So they should be easy to make repr(C).

Initially, I was slightly biased against doing this but now I'm natural. It sure helps not to have to worry about those types. Even if they all are simple enums.
I'll do it in a follow-up PR.

tasks/ast_codegen/src/layout.rs Outdated Show resolved Hide resolved
@github-actions github-actions bot added the A-ast Area - AST label Jul 26, 2024
@rzvxa
Copy link
Collaborator Author

rzvxa commented Jul 28, 2024

@overlookmotel I have a problem understanding something with repr C and option types. Can you help me with it?

Take a look at this struct definition:

pub struct TSCallSignatureDeclaration<'a> {
#[serde(flatten)]
pub span: Span,
pub this_param: Option<TSThisParameter<'a>>,
pub params: Box<'a, FormalParameters<'a>>,
pub return_type: Option<Box<'a, TSTypeAnnotation<'a>>>,
pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
}

If you get the size of the this_param field you'll see the option flag is folded into the TSThisParameter type which is 40 bytes. After investigating I found out that the option flag is getting folded onto the TSThisParameter::this field, That field contains an Atom which is a string slice and that type has 1 bit of free space in its pointer to accept the option.

Is it correct in the first place? If my assumptions are right; How does it work on FFI? I guess option doesn't mean nullable for that pointer types are a better fit, But packing this information in structures wouldn't break the C compatibility? Does it only happen on pointers because there is free space in the address? Right now I only fold the option if there is room at the end of type(either the last field of struct or if all enum variants have 1 bit headroom). Should I also consider all the padding in between fields as viable space for folding options?


Edit:

I went with my intuition but it might very well be wrong so please let me know if I'm doing it incorrectly.

@rzvxa rzvxa force-pushed the 07-22-chore_cleanup_schema branch from 34cacf6 to 38b4fc8 Compare July 28, 2024 18:02
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 671d536 to 5112832 Compare July 28, 2024 18:02
@overlookmotel
Copy link
Collaborator

overlookmotel commented Jul 29, 2024

Option type itself is not repr(C) so it's unaffected. However its layout is specified: https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html#discriminant-elision-on-option-like-enums

To clarify one thing:

Option<T> will be same size as T if T has a "niche". This isn't quite the same thing as "1 bit of free space". A NonNull pointer doesn't have a spare bit - the memory address uses all 64 bits (not 63). But it does still have a "niche".

"Niche" means a pattern of bits which is illegal for the type to use. In the case of NonNull, that pattern is all 64 bits = zero, because a NonNull can't be null (aka 0).

I think the idea for FFI is that if you're getting a pointer from C, that C pointer can be either null or non-null. That maps cleanly onto Rust's Option<NonNull> - a null C pointer has same bit pattern as None, and a non-null C pointer has same bit pattern as Some(valid_ptr).

The rules around niches as far as I understand them are:

Box and Vec use a NonNull for their pointer, so they have a niche. &T must always point to a valid T, which can't be stored at null address. So &T is basically same as a NonNull, and has a niche (that includes &str).

Same thing for all the NonZero* integer types - NonZeroU32, NonZeroUsize etc. They all have a niche value of zero.

Some types have multiple niches. e.g. bool has 254 niches - 0 and 1 are the only legal values, and 2-255 are available as niches. So Option<bool> is 1 byte.

enum Foo { A: u32, B: u32, C: u32, D: u32 } has 252 niches because the discriminant only has 4 legal values, which leaves 252 spare values - even though the u32 "payloads" for all the variants have no niches.

(By the way, these complications around niches in enums is what motivated the "squashed enums" PR #3115. It was intractable to figure out what niches and discriminants Rust would produce when you have multiple enums, each with niches, nested enum-in-enum-in-enum).

If a struct has a field which has a niche, then the struct inherits that niche too (as you've found). The hard part is when a struct has more than 1 field with a niche - which niche does the struct use? I don't know the answer to that one - but we don't need an answer anyway right now - if T has any niches, Option<T> will be same size as T.

Padding cannot be used as a niche - I don't entirely understand that one, but pretty sure it's true.

NB: One oddity is that Cell<T> does NOT have a niche, even if T does (ditto UnsafeCell and RefCell).

One last thing: #[rustc_layout(debug)] can be helpful for figuring out what Rust is doing with type layouts. See https://www.ralfj.de/blog/2020/04/04/layout-debugging.html

@rzvxa
Copy link
Collaborator Author

rzvxa commented Jul 29, 2024

Thank you, I'll rework it to consider niches correctly.

@rzvxa rzvxa force-pushed the 07-22-chore_cleanup_schema branch from 38b4fc8 to af18a31 Compare July 29, 2024 18:21
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 0ebd4eb to d8867e2 Compare July 29, 2024 18:21
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 7d25dc6 to 719efba Compare July 30, 2024 19:34
@rzvxa rzvxa force-pushed the 07-22-chore_cleanup_schema branch from af18a31 to fbaa37a Compare July 30, 2024 20:34
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 719efba to b23e99f Compare July 30, 2024 20:34
@rzvxa rzvxa changed the base branch from 07-22-chore_cleanup_schema to 07-30-refactor_ast_codegen_abstract_passes_and_generators_behind_runner_trait July 30, 2024 20:34
@rzvxa rzvxa force-pushed the 07-30-refactor_ast_codegen_abstract_passes_and_generators_behind_runner_trait branch from e822ba6 to 425f59c Compare July 30, 2024 21:14
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 07ae601 to d84a2db Compare July 30, 2024 21:14
@rzvxa rzvxa changed the base branch from 08-02-refactor_ast_make_ast_structs_repr_c_ to 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema August 2, 2024 15:14
@rzvxa rzvxa changed the title feat(ast_codegen): add alignment and size data to the schema. perf(ast, ast_codegen): optimize AST structs memory layouts Aug 2, 2024
@rzvxa rzvxa changed the title perf(ast, ast_codegen): optimize AST structs memory layouts perf(ast, ast_codegen): optimize AST structs' memory layouts Aug 2, 2024
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch 3 times, most recently from edf5eb8 to ed8675c Compare August 2, 2024 17:15
@rzvxa rzvxa force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 31a5a1c to 42740c6 Compare August 2, 2024 17:25
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from ed8675c to 7118f04 Compare August 2, 2024 17:25
@Boshen Boshen force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 42740c6 to 8cdd9d9 Compare August 2, 2024 22:43
@Boshen Boshen force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 7118f04 to 4cc58bf Compare August 2, 2024 22:44
@overlookmotel overlookmotel force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 8cdd9d9 to d0297db Compare August 3, 2024 12:15
@overlookmotel overlookmotel force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 4cc58bf to 9579449 Compare August 3, 2024 12:16
@overlookmotel overlookmotel force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from d0297db to 717cd87 Compare August 3, 2024 12:26
@rzvxa rzvxa force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 717cd87 to 01338e2 Compare August 3, 2024 12:35
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 9579449 to 97c92f9 Compare August 3, 2024 12:35
@rzvxa rzvxa force-pushed the 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from d07ce36 to 0c52c0d Compare August 3, 2024 12:43
@rzvxa rzvxa changed the base branch from 08-02-feat_ast_codegen_add_alignment_and_size_data_to_the_schema to main August 3, 2024 12:46
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch 4 times, most recently from 18f1433 to 67e0203 Compare August 3, 2024 12:58
@rzvxa rzvxa changed the base branch from main to 08-03-chore_fix_mistake_in_configuring_deny.toml_v2 August 3, 2024 12:58
@rzvxa rzvxa force-pushed the 08-03-chore_fix_mistake_in_configuring_deny.toml_v2 branch from 375b347 to 297401d Compare August 3, 2024 13:03
@rzvxa rzvxa changed the base branch from 08-03-chore_fix_mistake_in_configuring_deny.toml_v2 to main August 3, 2024 13:06
@rzvxa rzvxa force-pushed the 07-22-feat_ast_codegen_add_alignment_and_size_data_to_the_schema branch from 67e0203 to 80e3195 Compare August 3, 2024 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ast Area - AST A-parser Area - Parser
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants