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

Align the start pointer in ink allocator #2100

Merged
merged 13 commits into from
Feb 24, 2024
Merged

Align the start pointer in ink allocator #2100

merged 13 commits into from
Feb 24, 2024

Conversation

SkymanOne
Copy link
Contributor

@SkymanOne SkymanOne commented Feb 6, 2024

Summary

Closes #1535

  • y/n | Does it introduce breaking changes?
  • y/n | Is it dependant on the specific version of cargo-contract or pallet-contracts?

Fixes alignment rules on the ink bump allocator.

Description

Before this PR, the allocator allocated types of different order in sequential order after each other without respecting the alignment and padding rules. While this saved up tiny bits of memory usage, it misaligned the types in memory which can potentially increase direct access.

This PR ensure the valid alignment of datatypes, by correctly aligning the end pointer of the linear memory buffer.

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have added an entry to CHANGELOG.md
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • Any dependent changes have been merged and published in downstream modules

crates/allocator/src/bump.rs Outdated Show resolved Hide resolved
crates/allocator/src/bump.rs Outdated Show resolved Hide resolved
@SkymanOne SkymanOne marked this pull request as ready for review February 21, 2024 15:06
@SkymanOne SkymanOne requested a review from athei February 21, 2024 15:08

/// Aligns the start pointer of the next allocation.
fn align_ptr(&self, layout: &Layout) -> usize {
(self.next + layout.align() - 1) & !(layout.align() - 1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a comment with the reasoning behind the calculation and the AND here.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe some concrete example will help here

Copy link

github-actions bot commented Feb 21, 2024

🦑 📈 ink! Example Contracts ‒ Changes Report 📉 🦑

These are the results when building the integration-tests/* contracts from this branch with cargo-contract and comparing them to ink! master:

Contract Upstream Size (kB) PR Size (kB) Diff (kB) Diff (%) Change
call-builder-return-value 9.237 9.237 0 0
call-runtime 2.061 2.061 0 0
combined-extension 2.12 2.12 0 0
conditional-compilation 1.49 1.49 0 0
contract-storage 7.568 7.568 0 0
contract-terminate 1.329 1.329 0 0
contract-transfer 1.689 1.689 0 0
cross-contract-calls 5.835 5.835 0 0
cross-contract-calls/other-contract 1.583 1.583 0 0
custom-allocator 7.775 7.775 0 0
custom-environment 2.146 2.146 0 0
dns 7.318 7.318 0 0
e2e-call-runtime 1.296 1.296 0 0
e2e-runtime-only-backend 1.881 1.881 0 0
erc1155 14.308 14.308 0 0
erc20 6.918 6.918 0 0
erc721 10.007 10.007 0 0
events 5.258 5.258 0 0
flipper 1.639 1.639 0 0
incrementer 1.504 1.504 0 0
lang-err-integration-tests/call-builder-delegate 2.638 2.638 0 0
lang-err-integration-tests/call-builder 5.354 5.354 0 0
lang-err-integration-tests/constructors-return-value 1.985 1.985 0 0
lang-err-integration-tests/contract-ref 4.753 4.753 0 0
lang-err-integration-tests/integration-flipper 1.815 1.815 0 0
lazyvec-integration-test 4.616 4.616 0 0
mapping-integration-tests 7.999 7.999 0 0
mother 12.741 12.741 0 0
multi-contract-caller 6.313 6.313 0 0
multi-contract-caller/accumulator 1.378 1.378 0 0
multi-contract-caller/adder 1.912 1.912 0 0
multi-contract-caller/subber 1.932 1.932 0 0
multisig 21.821 21.821 0 0
payment-channel 5.659 5.659 0 0
psp22-extension 7.071 7.071 0 0
rand-extension 2.965 2.965 0 0
sr25519-verification 1.142 1.142 0 0
static-buffer 1.654 2.536 0.882 53.3253 📈
trait-dyn-cross-contract-calls 2.887 2.887 0 0
trait-dyn-cross-contract-calls/contracts/incrementer 1.545 1.545 0 0
trait-erc20 7.294 7.294 0 0
trait-flipper 1.49 1.49 0 0
trait-incrementer 1.614 1.614 0 0
upgradeable-contracts/delegator 3.928 3.151 -0.777 -19.7811 📉
upgradeable-contracts/delegator/delegatee 1.609 1.609 0 0
upgradeable-contracts/set-code-hash-migration 1.743 1.743 0 0
upgradeable-contracts/set-code-hash-migration/migration 1.45 1.45 0 0
upgradeable-contracts/set-code-hash-migration/updated-incrementer 1.897 1.897 0 0
upgradeable-contracts/set-code-hash 1.743 1.743 0 0
upgradeable-contracts/set-code-hash/updated-incrementer 1.721 1.721 0 0
wildcard-selector 2.846 2.846 0 0

Link to the run | Last update: Fri Feb 23 23:35:18 CET 2024

Copy link
Collaborator

@ascjones ascjones left a comment

Choose a reason for hiding this comment

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

As a reviewer coming to this for the first time, I'd like to see at least a brief description in the PR of the problem and how this solves it.

/// - Initially `self.next` is `0`` and aligned
/// - `layout.align() - 1` accounts for `0` as the first index.
/// - the binary with the inverse of the align ensures
/// that the next allocated pointer address is of the power of 2.
Copy link
Collaborator

@smiasojed smiasojed Feb 22, 2024

Choose a reason for hiding this comment

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

The inverse of the expression align - 1 creates a bitmask that is used to zero out bits, ensuring alignment according to type requirements.

Copy link
Contributor

Choose a reason for hiding this comment

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

  • The expression self.next + layout.align() - 1 ensures that the pointer is at least as large as the next alignment boundary.
  • The bitwise AND with !(layout.align() - 1) clears the lower bits of the result to ensure that the address is a multiple of the alignment. The ! operator creates a mask that zeroes out the lower bits that should not contribute to the final aligned address.


let aligned_size = layout.pad_to_align().size();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just out of curiosity, how is padding performed for fields in a struct, considering they field types have their own alignment requirements?

Copy link
Contributor

Choose a reason for hiding this comment

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

That is not really the concern of the allocator. The allocator will just create an allocation that adheres to the Layout passed. For a struct the alignment will be set to the alignment for the whole struct (AFAIK the alignment of the largest type in the struct). The padding within the struct is given implicitly by the size of the Layout. Rust will just assume the padding when accessing the struct.

@codecov-commenter
Copy link

codecov-commenter commented Feb 22, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 53.63%. Comparing base (5e70f38) to head (72b2467).
Report is 1 commits behind head on master.

❗ Current head 72b2467 differs from pull request most recent head 850566a. Consider uploading reports for the commit 850566a to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2100      +/-   ##
==========================================
- Coverage   53.69%   53.63%   -0.06%     
==========================================
  Files         224      224              
  Lines        7046     7048       +2     
  Branches     3118     3118              
==========================================
- Hits         3783     3780       -3     
- Misses       3263     3268       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


let aligned_size = layout.pad_to_align().size();
Copy link
Contributor

Choose a reason for hiding this comment

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

That is not really the concern of the allocator. The allocator will just create an allocation that adheres to the Layout passed. For a struct the alignment will be set to the alignment for the whole struct (AFAIK the alignment of the largest type in the struct). The padding within the struct is given implicitly by the size of the Layout. Rust will just assume the padding when accessing the struct.

/// - Initially `self.next` is `0`` and aligned
/// - `layout.align() - 1` accounts for `0` as the first index.
/// - the binary with the inverse of the align ensures
/// that the next allocated pointer address is of the power of 2.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • The expression self.next + layout.align() - 1 ensures that the pointer is at least as large as the next alignment boundary.
  • The bitwise AND with !(layout.align() - 1) clears the lower bits of the result to ensure that the address is a multiple of the alignment. The ! operator creates a mask that zeroes out the lower bits that should not contribute to the final aligned address.


/// Aligns the start pointer of the next allocation.
fn align_ptr(&self, layout: &Layout) -> usize {
(self.next + layout.align() - 1) & !(layout.align() - 1)
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe some concrete example will help here

Comment on lines 107 to 112
let debug_msg = get_res.debug_message();
let msgs: Vec<&str> = debug_msg.split('\n').collect();
let ptr1 = u64::from_str_radix(msgs[0].trim_start_matches("0x"), 16).unwrap();
let ptr2 = u64::from_str_radix(msgs[1].trim_start_matches("0x"), 16).unwrap();
let align = u64::from_str_radix(msgs[2], 10).unwrap();

Copy link
Contributor

Choose a reason for hiding this comment

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

This seems a bit hacky to rely on the debug_message here.
Can't we just do that in the ink! message itself and assert it does not panic, or return the values and assert here?

@pgherveou
Copy link
Contributor

stumbled upon this
https://github.com/paritytech/ink/blob/da598d3bb04acd145b246f01061165a636494018/crates/allocator/src/bump.rs#L17-L18

maybe we can remove the reference to wee_alloc in this PR since you are touching this file

@SkymanOne SkymanOne merged commit 835775c into master Feb 24, 2024
24 checks passed
@SkymanOne SkymanOne deleted the gn/fix-alloc branch February 24, 2024 10:50
This was referenced Feb 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Mis-aligned allocation in ink
7 participants