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

Annotate mmap ranges using PR_SET_VMA #1236

Merged
merged 24 commits into from
Nov 29, 2024
Merged

Annotate mmap ranges using PR_SET_VMA #1236

merged 24 commits into from
Nov 29, 2024

Conversation

wks
Copy link
Collaborator

@wks wks commented Nov 15, 2024

We demand that every invocation of mmap within mmtk-core to be accompanied with an "annotation" for the purpose of the mmap. On Linux, we will use PR_SET_VMA_ANON_NAME to set the attribute after mmap so that it can be seen in /proc/pid/maps. This will greatly improve the debugging experience.

We demand that every invocation of `mmap` within mmtk-core to be
accompanied with an "annotation" for the purpose of the mmap.  On Linux,
we will use `PR_SET_VMA` to set the annotation after `mmap` so that it
can be seen in `/proc/pid/maps`.  This will greatly improve the
debugging experience.
@k-sareen
Copy link
Collaborator

k-sareen commented Nov 16, 2024

Why not just use the space/metadata spec name instead? Also there's a 80-byte limit for the string (including the NUL byte).

However, for Map32, spaces may interleave at chunk granularity in the shared discontiguous memory range.

Why is this a problem? If a chunk is recycled, then we should just rename it.

@wks
Copy link
Collaborator Author

wks commented Nov 16, 2024

Why not just use the space/metadata spec name instead?

  • For space, I just use its name.
  • For side metadata, I use both space name and metadata spec name to distinguish between metadata for different spaces.
  • And there are mmap invocations for other purposes, too, such as SFTMap.

Also there's a 80-byte limit for the string (including the NUL byte).

Good point. Although I don't think any space name + metadata name would exceed this limit, it's safe to perform the check when formatting.

Why is this a problem? If a chunk is recycled, then we should just rename it.

The number of mmap entries is limited at the OS level. So whenever possible, we should merge adjacent entries. If no chunk in the discontiguous space has name (annotated with PR_SET_VMA_ANON_NAME), the OS may merge adjacent entries; but if adjacent entries have different names, they can't be merged.

@k-sareen
Copy link
Collaborator

k-sareen commented Nov 16, 2024

And there are mmap invocations for other purposes, too, such as SFTMap.

Yes that is a good point. Though in my branch I just use an Option<&str> because it's not always clear what name to give to an mmap region such as the side metadata implementation for 32-bits and for panic_if_unmapped.

The number of mmap entries is limited at the OS level. So whenever possible, we should merge adjacent entries. If no chunk in the discontiguous space has name (annotated with PR_SET_VMA_ANON_NAME), the OS may merge adjacent entries; but if adjacent entries have different names, they can't be merged.

I'm not sure this is a real concern? I don't imagine we'll ever be hitting the limit even for 32-bit machines. Even Android always has the annotations enabled. I strongly suggest having these on by default. If we ever hit the limit then we can disable it.

@wks wks marked this pull request as ready for review November 25, 2024 12:20
@wks wks added the PR-extended-testing Run extended tests for the pull request label Nov 25, 2024
@wks
Copy link
Collaborator Author

wks commented Nov 25, 2024

The number of mmap entries is limited at the OS level. So whenever possible, we should merge adjacent entries. If no chunk in the discontiguous space has name (annotated with PR_SET_VMA_ANON_NAME), the OS may merge adjacent entries; but if adjacent entries have different names, they can't be merged.

I'm not sure this is a real concern? I don't imagine we'll ever be hitting the limit even for 32-bit machines. Even Android always has the annotations enabled. I strongly suggest having these on by default. If we ever hit the limit then we can disable it.

Well, perhaps it's not a problem. I tried a few benchmarks. We do mmap at chunk granularity. This means we don't call mmap very often. It is proportional to the heap size, but even memory-hungry benchmarks like h2 run just fine, with only a few hundred invocations of mmap in the entire execution. And there are less than 20 entries in /proc/pid/mmap The following is captured while running the h2 benchmark on OpenJDK with 1GB heap size. They are the only mmaps created by mmtk-core (besides those created by malloc and pthread_create).

40000000-40400000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:nursery]
40400000-40800000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:los]
40800000-41800000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:nursery]
41800000-41c00000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:los]
41c00000-5f400000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:nursery]
5f400000-6f400000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:immix_mature]
6f400000-8f000000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:nursery]
8f000000-90800000 rw-p 00000000 00:00 0                                  [anon:mmtk:space:immix_mature]
100000000-1001e0000 rw-p 00000000 00:00 0 
1001e0000-140000000 ---p 00000000 00:00 0 
e0005000000-e0005800000 rw-p 00000000 00:00 0                            [anon:mmtk:sidemeta:nursery:VMGlobalLogBitSpec]
e0005800000-e0005c00000 rw-p 00000000 00:00 0                            [anon:mmtk:sidemeta:immix_mature:VMGlobalLogBitSpec]
e0005c00000-e0006400000 rw-p 00000000 00:00 0                            [anon:mmtk:sidemeta:nursery:VMGlobalLogBitSpec]
e0006400000-e0006800000 rw-p 00000000 00:00 0                            [anon:mmtk:sidemeta:immix_mature:VMGlobalLogBitSpec]
4e0800400000-4e0800c00000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:immix_mature:IX_LINE_MARK]
4e8800000000-4e8800400000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:immix_mature:IX_BLOCK_DEFRAG]
4e8900000000-4e8900400000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:immix_mature:IX_BLOCK_MARK]
4e8a00000000-4e8a00400000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:immix_mature:CHUNK_MARK]
4eaa82000000-4eaa82400000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:los:VMLocalLOSMarkNurserySpec]
4eac83000000-4eac84800000 rw-p 00000000 00:00 0                          [anon:mmtk:sidemeta:immix_mature:VMLocalMarkBitSpec]

So I think it's OK to leave it enabled by default. The user can still set MMTK_MMAP_ANNOTATION=false to disable it if it ever becomes problematic.

@k-sareen
Copy link
Collaborator

I think you need to update some of the tests to use the new annotation API as well.

Re: mmap chunks. Like I mentioned in a meeting, we can improve this further by allocating spaces from different ends of the address space

@k-sareen
Copy link
Collaborator

Err.. Why is sysinfo broken 🤔

@wks
Copy link
Collaborator Author

wks commented Nov 26, 2024

Err.. Why is sysinfo broken 🤔

There is an upstream bug report: GuillaumeGomez/sysinfo#1392

@k-sareen
Copy link
Collaborator

Should we pin version of sysinfo/libc until this is resolved then?

@wks
Copy link
Collaborator Author

wks commented Nov 26, 2024

Should we pin version of sysinfo/libc until this is resolved then?

I pinned the version of libc in this PR so that we can test and review this PR.

There is already a PR for the sysinfo crate that will fix this problem.

Copy link
Collaborator

@k-sareen k-sareen left a comment

Choose a reason for hiding this comment

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

LGTM except some minor stuff in review.

src/mmtk.rs Outdated Show resolved Hide resolved
src/policy/space.rs Outdated Show resolved Hide resolved
src/policy/space.rs Outdated Show resolved Hide resolved
src/util/heap/layout/mmapper.rs Show resolved Hide resolved
src/util/heap/layout/mmapper.rs Show resolved Hide resolved
src/util/heap/layout/mmapper.rs Show resolved Hide resolved
src/util/memory.rs Outdated Show resolved Hide resolved
src/util/metadata/side_metadata/global.rs Show resolved Hide resolved
src/util/memory.rs Show resolved Hide resolved
/// FIXME: Since it is set via `Options`, it is in theory a decision per MMTk instance. However, we
/// currently don't have a good design for multiple MMTk instances, so we use static variable for
/// now.
pub(crate) static MMAP_ANNOTATION: AtomicBool = AtomicBool::new(true);
Copy link
Member

Choose a reason for hiding this comment

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

This looks bad. Though there is a FIXME above, we should avoid introducing this at first place.

Both Plan and Space have references to the options. You can let plan/space to check options and create a MmapAnnotation. If the runtime option is not enabled, we can use Option<MmapAnnotation> or MmapAnnotation::Omit/None.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. That looks bad.

Although it is possible to let plans and spaces check options, and optionally pass MmapAnnotation::Omit or a None for Option<MmapAnnotation>, it is not ideal.

One important part of my intention is that the annotation cannot be omitted. I'd like the function signatures to enforce that an annotation must be provided for every call to mmap. If we give the caller the option to pass None or Omit, developers will do it if they think it is not worth annotating. Instead, the decision of whether to actually annotate the mmap should be postponed until mmap is actually called and is successful, and should be controlled by the user via an option rather than at call sites.

And checking at every call site is repetitive and error prone. Developers may forget to do the check or erroneously omit the annotation.

Ideally, functions related to memory mapping should be wrapped into a struct, maybe named MmapSupport or CommonMapper. It will be created per MMTK instance, like Mmapper and VMMap (which are currently both global singletons), and referenced by plans, spaces, SFT, etc., whatever needs to use mmap. The mmap_annotation: bool should be an instance variable of CommonMapper, and map_fixed should be an instance method of MmapSupport. That may involve much refactoring, including moving dzmmap, dzmmap_noreplace and mmap_noreserve into MmapSupport. For now, creating a global boolean variable seems to be the easiest solution.

In the short term, we may introduce MmapSupport as a global singleton, like Mmapper and VMMap. In the long term, we can make them specific to the MMTK instance.

Copy link
Member

Choose a reason for hiding this comment

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

One important part of my intention is that the annotation cannot be omitted. I'd like the function signatures to enforce that an annotation must be provided for every call to mmap.

We could enforce the users to supply MmapAnnotation, and turn it into Option<MmapAnnotation> before we send it to mmap functions. Plus the problem of having a global var outweighs the benefits of 'enforcing annotations'.

Alternatively, we can have an instance for memory which holds the boolean value from the option. I think they are both worth trying before adopting the current implementation. We have arg structs for creating plans and spaces. Creating a Memory instance and letting plan/space to hold it shouldn't be too hard.

pub struct CreateGeneralPlanArgs<'a, VM: VMBinding> {

It will be created per MMTK instance, like Mmapper and VMMap (which are currently both global singletons), and referenced by plans, spaces, SFT, etc., whatever needs to use mmap.

I think it is still arguable whether Mmapper and VMMap should be global or per instance. We do have a hacky vm_layout() that should be per instance, and is global at the moment. But we should avoid introducing more globals like this. The more we have, the more difficult and the more resistance we will face when we try to support multiple instances.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Alternatively, we can have an instance for memory which holds the boolean value from the option. I think they are both worth trying before adopting the current implementation. We have arg structs for creating plans and spaces. Creating a Memory instance and letting plan/space to hold it shouldn't be too hard.

pub struct CreateGeneralPlanArgs<'a, VM: VMBinding> {

Yes. This is my intended way to do it. But this may involve too many changes and I think it is better to do the refactoring separately.

The introduced instance (let's call it MmapSupport for now) should contain dzmap, dzmap_noreplace, mmap_noreserve and mmap_fixed. I grepped the code and found that the following places will be affected.

  • MapState: MapState::transition_to_* will call those functions directly, as if the MapState can transition its own state without the help of Mmapper or the MmapSupport instance. This needs to be refactored so that MapState will become a state holder, and Mmapper implementations (FragmentedMapper and ByteMapMmapper) can call the mmap functions in MmapSupport to do the transition.
  • LockFreeImmortalSpace: It skipped the Mmapper and directly calls dzmmap_noreplace. I think this is a bug because Mmapper should be aware of the mapping of spaces.
  • RawMemoryFreeList: It calls dzmmap_noreplace to map its memory. Not sure if it should go through Mmapper, but probably it should because a RawMemoryFreeList is part of a space using it.

In the end, we may have all mmap invocations going through Mmapper, and an Mmapper holds a reference to MmapSupport.

We could enforce the users to supply MmapAnnotation, and turn it into Option<MmapAnnotation> before we send it to mmap functions.

It is good to enforce the users to supply MmapAnnotation. The difficulty is drawing a border between the "user" and the "mmap functions". And another difficulty is that we lost access to Options before reaching "mmap functions".

Take the call chain SideMetadataContext::map_metadata_internal -> try_mmap_contiguous_metadata_space -> MMAPPER.ensure_mapped for example.

  • In SideMetadataContext::map_metadata_internal, we have enough information (space name and side metadata name) to construct the annotation. We construct MmapAnnotation here. But since SideMetadataContext does not reference anySpace or Plan, it cannot access Options.
  • try_mmap_contiguous_metadata_space is a top-level function. It has access to neither space name nor Option.
  • MMAPER.ensure_mapped is a function that is more related to mmap. It may be refactored to hold a boolean variable MMAP_ANNOTATION, or refer to MmapSupport I mentioned above.

So the availability of Options or the MMAP_ANNOTATION variable does not match the distinction between user and mmap. I think it's easier to refactor the code and just let "mmap functions" decide whether to annotate.

Plus the problem of having a global var outweighs the benefits of 'enforcing annotations'.

Having global var is bad, but I think it is worse to let the users check Options::mmap_annotation before constructing the Option<MmapAnnotation> because it is repetitive and error-prone. Having a global variable is the easiest workaround before we refactor Mmapper, MapState, etc.

If you think having a global variable is unacceptible, I can remove Options::mmap_annotation and make the annotation controlled by a Cargo feature instead, or leave it always on. I think it is OK to leave it always on because there will be strictly fewer prctl than mmap, and we don't call mmap very often, either.

Copy link
Member

Choose a reason for hiding this comment

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

If you think having a global variable is unacceptible, I can remove Options::mmap_annotation and make the annotation controlled by a Cargo feature instead, or leave it always on.

Right. We dont really need the option anyway. We can always annotate the mmaps.

The reason that I am against having a global var is that we know it's wrong and this kind of small issues will accumulate over time and eventually make it hard to get rid of them. We should try to avoid introducing them in first place.

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 removed the option, and added a Cargo feature no_mmap_annotation just in case anyone needs to disable it for any reason.

Copy link
Member

Choose a reason for hiding this comment

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

This global var can be removed now. I am suprised that we did not get any warning from the compiler for this, this seems to be an unused variable.

Copy link
Member

@qinsoon qinsoon left a comment

Choose a reason for hiding this comment

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

Generally looks good. The global var MMAP_ANNOTATION can be removed.

src/policy/lockfreeimmortalspace.rs Show resolved Hide resolved
"failed to mmap meta memory"
);
CHUNK_METADATA
.try_map_metadata_space(start, size, space_name)
Copy link
Member

Choose a reason for hiding this comment

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

This is good refactoring with clear improvement. The old code put the actual action performed in the assert! which is bad.

/// FIXME: Since it is set via `Options`, it is in theory a decision per MMTk instance. However, we
/// currently don't have a good design for multiple MMTk instances, so we use static variable for
/// now.
pub(crate) static MMAP_ANNOTATION: AtomicBool = AtomicBool::new(true);
Copy link
Member

Choose a reason for hiding this comment

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

This global var can be removed now. I am suprised that we did not get any warning from the compiler for this, this seems to be an unused variable.

Copy link
Member

@qinsoon qinsoon left a comment

Choose a reason for hiding this comment

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

Looks good

@wks
Copy link
Collaborator Author

wks commented Nov 29, 2024

V8 failed for HTTP 404. I think it might be a transient failure if the Ubuntu server is updating itself and happens to be in an inconsistent state when we ran the test. See: https://github.com/mmtk/mmtk-core/actions/runs/12078239098/job/33686682978?pr=1236

JikesRVM failed for the classic "instruction ... not in RVM space" error. Interestingly, the dumped maps file showed that our annotation is working. See: https://github.com/mmtk/mmtk-core/actions/runs/12078239098/job/33686683158?pr=1236

I'll rerun those tests.

@wks wks added this pull request to the merge queue Nov 29, 2024
Merged via the queue into mmtk:master with commit cd2fe83 Nov 29, 2024
29 of 34 checks passed
@wks wks deleted the feature/mmap-anno branch November 29, 2024 10:18
github-merge-queue bot pushed a commit that referenced this pull request Dec 2, 2024
#1242 fixed most similar issues in
the repo, but #1236 introduced
`MmapAnnotation` and introduced a new warning.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR-extended-testing Run extended tests for the pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants