Skip to content

Add PolicyProcessEdges #570

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

Closed
wants to merge 5 commits into from
Closed

Conversation

qinsoon
Copy link
Member

@qinsoon qinsoon commented Apr 7, 2022

This PR adds another general process edges work packet, PolicyProcessEdges. For policies that may have different trace kinds and cannot use SFTProcessEdges, they can use PolicyProcessEdges. This further removes policy-specific code from plans (#258).

  • adds struct PolicyProcessEdges, which is similar to SFTProcessEdges. However, it allows multiple traces for the policy (specified by TraceKind). To use PolicyProcessEdges,
    • the policy needs to provide an implementation of SupportPolicyProcessEdges. and
    • the plan needs to provide an implementation of UsePolicyProcessEdges.
  • Immix, generational immix and mark compact now use PolicyProcessEdges.

@qinsoon qinsoon added the PR-testing Run binding tests for the pull request (deprecated: use PR-extended-testing instead) label Apr 7, 2022
@qinsoon qinsoon changed the title Add ImmixProcessEdgesWork Add PolicyProcessEdges Apr 11, 2022
@qinsoon qinsoon marked this pull request as ready for review April 11, 2022 23:14
@qinsoon qinsoon requested a review from wks April 11, 2022 23:14
Copy link
Collaborator

@wks wks left a comment

Choose a reason for hiding this comment

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

The significance of this PR is that it eliminated plan-specific ProcessEdgesWork implementations. The crux of the problem is still dynamically dispatching the trace_object(obj) call into the appropriate space. With "policy process edges", the Plan implements get_target_space and fallback_trace, expressing the dispatching of trace_object in a more declarative way.

However, I think the current implementation on the plan side is not concise enough. Personally, I think get_target_space and fallback_trace is no better than the old if-elseif-elseif-... statement:

if obj is in space1
    space1.trace_object(obj)
else if obj is in space2
    space2.trace_object(obj)
else if obj is in space3
...

We may consider a subsequent refactoring to make it even more declarative.

Comment on lines +56 to +63
fn create_scan_work<E: ProcessEdgesWork<VM = VM>>(
&'static self,
nodes: Vec<ObjectReference>,
) -> Box<dyn GCWork<VM>> {
Box::new(crate::scheduler::gc_work::ScanObjects::<E>::new(
nodes, false,
))
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This method can be lifted to the ProcessEdgesWork trait because ProcessEdgesWork::flush() contains an equivalent code sequence:

// ProcessEdgesWork::flush()
        let scan_objects_work = ScanObjects::<Self>::new(self.pop_nodes(), false);
        self.new_scan_work(Box::new(scan_objects_work));

It can use this create_scan_work, too:

// ProcessEdgesWork::flush()
        let scan_objects_work = Self::create_scan_work::<Self>(self.pop_nodes, false);
        self.new_scan_work(scan_objects_work);

A more concise way is to let the space provide an associated type

type ScanObjectsWorkType: GCWork = ScanObjects;

which can be overridden. But there seem to be a problem doing this in stable Rust.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have made some changes to this:

  • add create_scan_work() in ProcessEdgesWork.
  • rename new_scan_work() in ProcessEdgesWork to start_scan_work() to avoid confusion.
  • remove the override flush() impl in PolicyProcessEdges, and instead override create_scan_work() and forward the call to SupportPolicyProcessEdges.

@@ -113,6 +113,9 @@ impl<VM: VMBinding> WorkBucket<VM> {
pub fn add<W: GCWork<VM>>(&self, work: W) {
self.add_with_priority(Self::DEFAULT_PRIORITY, Box::new(work));
}
pub fn add_boxed(&self, work: Box<dyn GCWork<VM>>) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If convenient, I would prefer that add always takes a Box<dyn GCWork<VM>> as its parameter. That is, requiring the user to box the work first before giving to the scheduler.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is an easy refactoring. But it may not be worth it. If we only keep the 'boxed' version, we need to add Box::new() to ~27 callsites. In that case, you probably would like to extract the common pattern and have the unboxed version.

@wks
Copy link
Collaborator

wks commented Apr 12, 2022

Actually I am thinking about something like this:

trait TraceObject {
  fn trace_object(obj: ObjectReference, ...) -> ObjectReference;
}

#[derive(TraceObject)]
struct SemiSpace {
    #[space_field] from_space: CopySpace,
    #[space_field] to_space: CopySpace,
    #[fallback_trace] common: CommonSpace,
    ...
}

So that we can use a procedural macro to automatically generate (i.e. "derive") an implementation of trace_object for the plan.

@qinsoon
Copy link
Member Author

qinsoon commented Apr 12, 2022

The crux of the problem is still dynamically dispatching the trace_object(obj) call into the appropriate space.

The problem is not 'dynamic dispatch'. Using the if-else pattern to dispatch trace_object() is okay, and with this PR, we are still using if-else pattern for these plans.

The problem before this PR is that there are policy-specific code in the plan. For example, how Immix traces objects in different traces, what scan packet Immix uses, etc. Now those code are moved to the policy, and the code that remains in the plan is actually plan-specific.

@qinsoon
Copy link
Member Author

qinsoon commented Apr 13, 2022

Actually I am thinking about something like this:

trait TraceObject {
  fn trace_object(obj: ObjectReference, ...) -> ObjectReference;
}

#[derive(TraceObject)]
struct SemiSpace {
    #[space_field] from_space: CopySpace,
    #[space_field] to_space: CopySpace,
    #[fallback_trace] common: CommonSpace,
    ...
}

So that we can use a procedural macro to automatically generate (i.e. "derive") an implementation of trace_object for the plan.

This looks like a good idea. I have no experience with procedural macros, but I am interested to give it a try (now or at some point).

In this PR, different plans now use different ways to dispatch trace_object():

  • SFTProcessEdges, which is simplest for a plan. It requires the policy only has one trace, and use the default scan object work.
  • PolicyProcessEdges. It allows multiple traces for a policy, and allows policy-specific scan object work. The policy need to implement SupportPolicyProcessEdges, and the plan needs to implement UsePolicyProcessEdges.
  • Plan-specific ProcessEdgesWork impl. A plan can always implement their own process edges work.

Currently I am not seeing many merits from SFTProcessEdges, except that it is simple. And I think our goal was not about using SFT for trace object, but was about removing policy-specific code from plans. We could consider using what you propose to replace both SFTProcessEdges and PolicyProcessEdges. In that case, we should think about whether we go directly to the macro-based approach (if it is feasible), or we merge this PR first.

@qinsoon
Copy link
Member Author

qinsoon commented Apr 27, 2022

I am closing this PR. Following Kunshan's suggestion in #570 (comment), I have submitted another PR #575 to solve the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR-testing Run binding tests for the pull request (deprecated: use PR-extended-testing instead)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants