-
Notifications
You must be signed in to change notification settings - Fork 1.6k
RFC: Add an attribute for raising the alignment of various items #3806
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
3a4f5ac
4badbc9
2b647a8
13cc4f2
bbe09ee
81748b6
51b8069
855a766
1c7a402
dee1e70
4701919
59b1230
16f3bee
5229770
96350ee
0c21fb3
959a94c
75f615a
8b1fa67
95f3972
fefe945
a478717
32b2a93
f140b23
60f23d2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,377 @@ | ||
- Feature Name: `align_attr` | ||
- Start Date: 2025-05-01 | ||
- RFC PR: [rust-lang/rfcs#3806](https://github.com/rust-lang/rfcs/pull/3806) | ||
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) | ||
|
||
# Summary | ||
[summary]: #summary | ||
|
||
Add an `#[align(…)]` attribute to set the minimum alignment of `struct` and `enum` | ||
fields, `static`s, functions, and local variables. | ||
|
||
# Motivation | ||
[motivation]: #motivation | ||
|
||
## Bindings to C and C++ | ||
|
||
[C](https://en.cppreference.com/w/c/language/_Alignas) and [C++](https://en.cppreference.com/w/cpp/language/alignas) | ||
provide an `alignas` modifier to set the alignment of specific struct fields. To | ||
represent such structures in Rust, `bindgen` is sometimes forced to add explicit | ||
padding fields: | ||
|
||
```c | ||
// C code | ||
#include <stdint.h> | ||
struct foo { | ||
uint8_t x; | ||
_Alignas(128) uint8_t y; | ||
uint8_t z; | ||
}; | ||
``` | ||
|
||
```rust | ||
// Rust bindings generated by `bindgen` | ||
#[repr(C, align(128))] | ||
pub struct foo { | ||
pub x: u8, | ||
pub __bindgen_padding_0: [u8; 127usize], | ||
pub y: u8, | ||
pub z: u8, | ||
} | ||
``` | ||
|
||
The `__bindgen_padding_0` field makes the generated bindings more confusing and | ||
less ergonomic. Also, it is unsound: the padding should be using `MaybeUninit`. | ||
And even then, there is no guarantee of ABI compatibility on all potential | ||
platforms. | ||
|
||
## Packing values into fewer cache lines | ||
|
||
When working with large values (lookup tables, for example), it is often | ||
desirable, for optimal performance, to pack them into as few cache lines as | ||
possible. One way of doing this is to force the alignment of the value to be at | ||
least the size of the cache line, or perhaps the greatest common denominator of | ||
the value and cache line sizes. | ||
|
||
The simplest way of accomplishing this in Rust today is to use a wrapper struct | ||
with a `#[repr(align(…))]` attribute: | ||
|
||
```rust | ||
type SomeLargeType = [[u8; 64]; 21]; | ||
|
||
#[repr(align(128))] | ||
struct CacheAligned<T>(T); | ||
|
||
static LOOKUP_TABLE: CacheAligned<SomeLargeType> = CacheAligned(SomeLargeType { | ||
data: todo!(), | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example is confusing since There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed, sorry. The contents of the type are immaterial, only the size matters |
||
``` | ||
|
||
However, this approach has several downsides: | ||
|
||
- It requires defining a separate wrapper type. | ||
- It changes the type of the item, which may not be allowed if it is part of the | ||
crate's public API. | ||
- It may add padding to the value, which might not be necessary or desirable. | ||
|
||
In some cases, it can also improve performance to align function items in the | ||
same way. | ||
|
||
## Required alignment for low-level use cases | ||
|
||
Some low-level use-cases (for example, the [RISC-V `mtvec` | ||
register](https://five-embeddev.com/riscv-priv-isa-manual/Priv-v1.12/machine.html#machine-trap-vector-base-address-register-mtvec)) | ||
require functions or statics to have a certain minimum alignment. | ||
|
||
## Interoperating with systems that have types where size is not a multiple of alignment | ||
|
||
In Rust, a type’s size is always a multiple of its alignment. However, there are | ||
other languages that can interoperate with Rust, where this is not the case | ||
(WGSL, for example). It’s important for Rust to be able to represent such | ||
structures. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me how this would work while keeping Rust's "size is multiple of align" rule intact. I guess if it's about individual fields in a larger aggregate that maintains the rule in total? I don't know anything about WGSL so an example would be appreciated. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That’s exactly it. The WSGL example was taken from this comment on Internals: https://internals.rust-lang.org/t/pre-rfc-align-attribute/21004/20 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding a worked example would indeed help readers of the RFC on this point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here is a concrete example of implementing Rust-WGSL compatibility using the // WGSL
struct Example { // size = 32, alignment = 16
foo: vec3<f32>, // offset = 0, size = 12
bar: vec3<f32>, // offset = 16, size = 12
baz: f32, // offset = 28, size = 4
} // Rust
#[repr(linear)] // as defined in this RFC; repr(C) in current Rust
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub(crate) struct Example {
#[align(16)]
foo: [f32; 3],
// #[align] below causes 4 bytes of padding to be inserted here to satisfy it.
#[align(16)]
bar: [f32; 3],
baz: f32, // If we used a wrapper for bar, this field would be at offset 32, wrongly
} It is often possible to order structure fields to fill gaps so that no inter-field padding is needed — such as if the fields in this example were declared in the order (Please feel free to use any of the above text in the RFC.) |
||
|
||
# Explanation | ||
[explanation]: #explanation | ||
|
||
The `align` attribute is a new inert, built-in attribute that can be applied to | ||
ADT fields, `static` items, function items, and local variable declarations. The | ||
attribute accepts a single required parameter, which must be a power-of-2 | ||
integer literal from 1 up to 2<sup>29</sup>. (This is the same as | ||
`#[repr(align(…))]`.) | ||
Comment on lines
+101
to
+105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 2^29 limit is way too high. The consistency with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For a struct field, both GCC and clang supported There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The bug with local variables (rust-lang/rust#70143) seems to have been fixed everywhere except Windows, and just waiting on someone to fix it there as well in LLVM. (And even on Windows where the issue is not fixed, the only effect is to break the stack overflow protection, bringing it down to the same level as many Tier 2 targets.) So the only remaining issue is with statics, where it looks like a target-specific max alignment might be necessary. Once implemented, that solution can be used to address Overall, I don't think any of this is sufficient motivation to impose a stricter maximum on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that fixing the soundness issue for locals just means that putting a local with huge alignment in a stack frame is very likely to trigger the stack overflow check and abort the program. There is no use case for such massively over-aligned locals or statics, which is why those soundness issues been mostly theoretical problems and why the only progress toward fixing them over many years has been side effects of unrelated improvements (inline stack checks). The only reason why the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
one use case I can think of is having a massive array that is faster because it's properly aligned so the OS can use huge pages (on x86_64, those require alignment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To use huge pages for static data, you'd want to align the ELF segment containing the relevant sections (or equivalent in other formats), so the right tool there is a linker script or similar platform-specific mechanism. Over-aligning individual
In any case, I'm sure I'm technically wrong to claim that nobody could ever come up with a use case for massively over-aligned statics. But there's a reason why Linux and glibc have only started supporting it at all in the last few years, and other environments like musl-based Linux and Windows apparently doesn't support it at all (see discussion in aforementioned issues). |
||
|
||
Multiple `align` attributes may be present on the same item; the highest | ||
alignment among them will be used. The compiler may signal this case with a | ||
warn-by-default lint. | ||
|
||
## On ADT fields | ||
|
||
The `align` attribute may be applied to any field of any `struct`, `enum`, or | ||
`union` that is not `#[repr(transparent)]`. | ||
|
||
```rust | ||
#[repr(C)] | ||
struct Foo { | ||
#[align(8)] | ||
a: u32, | ||
} | ||
|
||
enum Bar { | ||
Variant(#[align(16)] u128), | ||
} | ||
|
||
union Baz { | ||
#[align(16)] | ||
a: u32, | ||
} | ||
``` | ||
|
||
The effect of the attribute is to force the address of the field to have at | ||
least the specified alignment. If the field already has at least that | ||
alignment, due to the required alignment of its type or to a `repr` attribute on | ||
the containing type, the attribute has no effect. | ||
|
||
In contrast to a `repr(align(…))` wrapper struct, an `align` annotation does *not* | ||
necessarily add extra padding to force the field to have a size that is a | ||
multiple of its alignment. (The size of the containing ADT must still be a | ||
multiple of its alignment; that hasn't changed.) | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`align` attributes for fields of a `#[repr(packed(n))]` ADT may not specify an | ||
alignment higher than `n`. | ||
|
||
```rust | ||
#[repr(packed(4))] | ||
struct Sardines { | ||
#[align(2)] // OK | ||
a: u8, | ||
#[align(4)] // OK | ||
b: u16, | ||
#[align(8)] //~ ERROR | ||
c: u32, | ||
} | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
`align()` attributes on ADT fields are shown in `rustdoc`-generated documentation. | ||
|
||
## Interaction with `repr(C)` | ||
|
||
`repr(C)` currently has two contradictory meanings: “a simple, linear layout | ||
algorithm that works the same everywhere” and “an ABI matching that of the | ||
target’s standard C compiler”. This RFC does not aim to reslove that conflict; | ||
that is being discussed as part of [RFC | ||
3718](https://github.com/rust-lang/rfcs/pull/3718). Henceforth, we will use | ||
`repr(C_for_real)` to denote “match the system C compiler”, and `repr(linear)` | ||
to denote “simple, portable layout algorithm”; but those names are not | ||
normative. | ||
|
||
### `repr(C_for_real)` | ||
|
||
The layout of a `repr(C_for_real)` ADT with `align` attributes on its fields is | ||
identical to that of the corresponding C ADT declared with `alignas` | ||
annotations. For example, the struct below is equivalent to the C `struct foo` | ||
from the motivation section: | ||
|
||
```rust | ||
#[repr(C_for_real)] | ||
pub struct foo { | ||
pub x: u8, | ||
#[align(128)] | ||
pub y: u8, | ||
pub z: u8, | ||
} | ||
``` | ||
|
||
### `repr(linear)` | ||
|
||
In a `repr(linear)` ADT, a field with an `align` attribute has its alignment, as | ||
well as the alignment of the containing ADT, increased to at least what the | ||
attribute specifies. | ||
|
||
For example, the following two structs have the same layout in memory (though | ||
not necessarily the same ABI): | ||
|
||
```rust | ||
#[repr(linear)] | ||
pub struct foo { | ||
pub x: u8, | ||
#[align(128)] | ||
pub y: u8, | ||
pub z: u8, | ||
} | ||
``` | ||
|
||
```rust | ||
#[repr(linear, align(128))] | ||
pub struct foo2 { | ||
pub x: u8, | ||
pub _padding: [MaybeUninit<u8>; 127usize], | ||
pub y: u8, | ||
pub z: u8, | ||
} | ||
``` | ||
|
||
## On `static`s | ||
|
||
Any `static` item (including `static`s inside `extern` blocks) may have an | ||
`align` attribute applied: | ||
|
||
```rust | ||
#[align(32)] | ||
static BAZ: [u32; 12] = [0xDEADBEEF; 12]; | ||
|
||
unsafe extern "C" { | ||
#[align(2)] | ||
safe static BOZZLE: u8; | ||
} | ||
``` | ||
|
||
The effect of the attribute is to force the `static` to be stored with at least | ||
the specified alignment. The attribute does not force padding bytes to be added | ||
after the `static`. For `static`s inside `unsafe extern` blocks, if the `static` | ||
does not meet the specified alignment, the behavior is undefined. (For | ||
misaligned `static` items declared inside old-style `extern` blocks, UB occurs | ||
only if the item is used.) | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The `align` attribute may also be applied to thread-local `static`s created with | ||
the `thread_local!` macro; the attribute affects the alignment of the underlying | ||
value, not that of the outer `std::thread::LocalKey`. | ||
|
||
```rust | ||
thread_local! { | ||
#[align(64)] | ||
static FOO: u8 = 42; | ||
} | ||
|
||
fn main() { | ||
FOO.with(|r| { | ||
let p: *const u8 = r; | ||
assert_eq!(p.align_offset(64), 0); | ||
}); | ||
} | ||
``` | ||
|
||
`align()` attributes on `static`s are shown in `rustdoc`-generated documentation. | ||
|
||
## On function items | ||
|
||
On function items, `#[align(…)]` sets the alignment of the function’s code. This | ||
replaces `#[repr(align(…))]` on function items from `#![feature(fn_align)]`. | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
`align` attributes on function items are shown in `rustdoc`-generated documentation. | ||
|
||
## On local variables | ||
|
||
The `align` attribute may also be applied to local variable declarations inside | ||
`let` bindings. The attribute forces the local to have at least the alignment | ||
specified: | ||
|
||
```rust | ||
fn main() { | ||
let (a, #[align(4)] b, #[align(2)] mut c) = (4u8, 2u8, 1u8); | ||
c *= 2; | ||
dbg!(a, b, c); | ||
|
||
if let Some(#[align(4)] x @ 1..) = Some(42u8) { | ||
dbg!(x); | ||
let p: *const u8 = x; | ||
assert_eq!(p.align_offset(4), 0); | ||
} | ||
} | ||
``` | ||
|
||
`align` attributes may not be applied to function parameters. | ||
|
||
```rust | ||
fn foo(#[align(8)] _a: u32) {} //~ ERROR | ||
``` | ||
Jules-Bertholet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
They also may not be applied to `_` bindings. | ||
|
||
```rust | ||
let #[align(4)] _ = true; //~ ERROR | ||
``` | ||
|
||
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
- This feature adds additional complexity to the languge. | ||
- The distinction between `align` and `repr(align)` may be confusing for users. | ||
|
||
# Rationale and alternatives | ||
[rationale-and-alternatives]: #rationale-and-alternatives | ||
|
||
Compared to the wrapper type approach, the `align` attribute adds additional | ||
flexibility, because it does not force the insertion of padding. If we don't | ||
adopt this feature, `bindgen` will continue to generate suboptimal bindings, and | ||
users will continue to be forced to choose between suboptimal alignment and | ||
additional padding. | ||
|
||
## `#[align(…)]` vs `#[repr(align(…))]` | ||
|
||
One potential alternative would be to use `#[repr(align(…))]` everywhere, | ||
instead of introducing a new attribute. | ||
|
||
Benefits of this alternative: | ||
|
||
- No new attribute polluting the namespace. | ||
- Requesting a certain alignment is spelled the same everywhere. | ||
- `#[repr(…)]` on fields might accept additional options in the future. | ||
|
||
Drawbacks: | ||
|
||
- `#[repr(align(…))]` is a longer and noisier syntax. | ||
- `#[repr(…)]` on non-ADTs, with the possible exception of field definitions, will | ||
probably only ever accept `align(…)` as an argument. It would not be consistent | ||
with the existing `#[repr(…)]` on ADTs. | ||
- `#[align(…)]` *only* aligns, while `#[repr(align(…))]` also pads to a multiple | ||
of the alignment. Having different syntax makes that distinction more clear. | ||
|
||
## `#[align(n)]` vs `#[align = n]` | ||
|
||
`align = n` might be misinterpreted as requesting an alignment of *exactly* `n`, | ||
instead of *at least* `n`. | ||
|
||
# Prior art | ||
[prior-art]: #prior-art | ||
|
||
This proposal is the Rust equivalent of [C | ||
`alignas`](https://en.cppreference.com/w/c/language/_Alignas_). | ||
|
||
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
1. What should the syntax be for applying the `align` attribute to `ref`/`ref | ||
mut` bindings? | ||
|
||
- Option A: the attribute goes inside the `ref`/`ref mut`. | ||
|
||
```rust | ||
fn foo(x: &u8) { | ||
let ref #[align(4)] _a = *x; | ||
} | ||
``` | ||
|
||
- Option B: the attribute goes outside the `ref`/`ref mut`. | ||
|
||
```rust | ||
fn foo(x: &u8) { | ||
let #[align(4)] ref _a = *x; | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whatever we do, I'd expect it to be the same as for As for where to put it, it seems like a bit of a coin toss. Anyone have a good argument for which way it should go? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’m comfortable deferring it because I see no use-case for it, and I don’t want to hold up the RFC on something with no use case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, but still, I repeat my question, as we need to answer it for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, wait, I think there may be a misunderstanding here. By “the same as for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. This RFC specifies this is allowed (quoting from an example in the RFC): let (a, #[align(4)] b, #[align(2)] mut c) = (4u8, 2u8, 1u8); My question is whether there are good arguments about whether we should prefer that, or should instead prefer: let (a, #[align(4)] b, mut #[align(2)] c) = (4u8, 2u8, 1u8); The RFC should discuss any reasons we might want to prefer one over the other. Separately, and secondarily, my feeling is that if we chose let #[align(..)] mut a = ..; then we would also choose: let #[align(..)] ref a = ..; And if we instead chose let mut #[align(..)] a = ..; then we would choose: let ref #[align(..)] a = ..; So my feeling is that in settling the question of how to syntactically combine There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don’t agree that we would necessarily want to make the same choice in both cases. I actually think it depends on how If the combination looks like let ref (mut x) = …;
let ref mut (mut x) = …; Then we should also do let ref (#[align(…)] x) = …;
let ref mut (#[align(…)] x) = …; But if it looks like let mut ref x = …;
let mut ref mut x = …; Then we should do let #[align(…)] ref x = …;
let #[align(…)] ref mut x = …; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that event, and in your model, that would still leave us deciding between: let ref (mut #[align(..)] x) = ..; // 1
// vs
let ref (#[align(..)] mut x) = ..; // 2 And between: let #[align(..)] mut ref x = ..; // 3
// vs
let mut #[align(..)] ref x = ..; // 4 I would estimate that we'd comfortably favor 1, 3 over 2, 4. There are also, of course, these possibilities: let #[align(..)] ref (mut x) = ..; // 5
let mut ref #[align(..)] x = ..; // 6 If in this RFC we pick That is, even in this future possibility, I'm going to want to keep all of the binding mode tokens either to the left or to the right of the attribute. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ll elaborate in the RFC, but my preference is for 2 or 3. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ve added a section on this to the RFC. |
||
|
||
(I believe the simplest option is to forbid this combination entirely for now.) | ||
|
||
2. Does MSVC do something weird with `alignas`? In other words, is the concern | ||
about `repr(C)` vs `repr(linear)` purely theoretical at this point, or does | ||
it matter in practice today? | ||
|
||
# Future possibilities | ||
[future-possibilities]: #future-possibilities | ||
|
||
- The `align(…)` and `repr(align(…))` attributes currently accept only integer | ||
literals as parameters. In the future, they could support `const` expressions | ||
as well. | ||
- We could provide additional facilities for controlling the layout of ADTs; for | ||
example, a way to specify exact field offsets or arbitrary padding. | ||
- We could add type-safe APIs for over-aligned pointers; for example, | ||
over-aligned reference types that are subtypes of `&`/`&mut`. | ||
- We could also add similar APIs for over-aligned function pointers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very confused by this paragraph, and don't understand what it tries to say. By raising the alignment, the items will be further apart from each other (since each one must be properly aligned), and therefore it will take more cache lines, not fewer?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that you don't want a field to span across 2 cache lines if it could fit into just one. An easy way to achieve that is to make the field start at the start of a new cache line.
Aligning a field also means that a reference to it is aligned, which can improve performance in certain cases.
edit: i agree the text should explain this in more detail
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve reworded this paragraph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so you want to avoid false sharing, that makes sense.
The new text helps, thanks. The section title still says "Packing values into fewer cache lines" though, which doesn't seem to match the text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not about false sharing -- if that was a problem, trailing padding until the next cache line boundary would be a plus. It's about possibly bringing fewer cache lines into the cache for common access patterns. This is a minor micro-optimization at best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exactly it.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it pertains to the header, the ambiguity is the difference between packing the collection of all values into (maybe) fewer cache lines, or packing the bits of each value into (maybe) fewer cache lines.
The word "packing" tends to imply arranging many individual discrete things to fit in less space, and so is perhaps a misleading word for this when combined with "values".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the paragraph again to clarify further.