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

feat(ecmascript): Map.groupBy #559

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

yossydev
Copy link
Contributor

@yossydev yossydev commented Feb 5, 2025

There are still a few more parts that need to be fixed.

ref: #146
doc: https://tc39.es/ecma262/multipage/keyed-collections.html#sec-map.groupby

Comment on lines 173 to 174
// c. Append entry to map.[[MapData]].
todo!()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

“I don’t know how to implement this part, so I left it as a TODO. 😱😭”

Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be basically equivalent to step 6 of Map.prototype.set, so you can find the roughly correct code over in map_prototype.rs:455.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! Honestly, I’m not confident, but I tried making it while using your reference.
Some tests are still failing, so I think something is probably missing. e507955

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, from a quick read, this all looks good. I guess figuring out why the tests are failing would need proper investigation :(

@yossydev yossydev changed the title WIP feat(ecmascript): Map.groupBy feat(ecmascript): Map.groupBy Feb 6, 2025
@yossydev yossydev marked this pull request as ready for review February 6, 2025 12:29
Comment on lines 173 to 174
// c. Append entry to map.[[MapData]].
todo!()
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be basically equivalent to step 6 of Map.prototype.set, so you can find the roughly correct code over in map_prototype.rs:455.

let mut groups = group_by_property(agent, items, callback_fn, gc.reborrow())?;
// 2. Let map be ! Construct(%Map%).
let map = agent.current_realm().intrinsics().map();
let map = construct(agent, map.into_function(), None, None, gc.reborrow())?
Copy link
Collaborator

Choose a reason for hiding this comment

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

suggestion: Because of the ! Construct we know that this will never call into user code (because it is synchronous code and has been marked as never throwing); here it might be a good idea to instead use agent.heap.create(MapHeapData { ... }) directly. Another good idea with that would be to implement a reserve method on MapHeapData (if we don't have that already?) that would be called here with groups.len() so as to ensure we have the proper amount of memory reserved with a minimal amount of allocations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you! I tried fixing it, but what do you think??
95c8c22

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks good to me! <3

@yossydev yossydev requested a review from aapoalas February 8, 2025 07:55
Copy link
Collaborator

@aapoalas aapoalas left a comment

Choose a reason for hiding this comment

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

Aside from one minor issue related to the reserve method, this all looks good to me! Nice work!

If you want to investigate the test failures, that would probably be a good idea but if you don't we can leave it for a followup :)

map_data.reserve(groups.len());
let map = agent.heap.create(map_data);

let gc = gc.nogc();
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitpick: In this sort of case, better to use gc.into_nogc() for lifetime related reasons.

let mut groups = group_by_property(agent, items, callback_fn, gc.reborrow())?;
// 2. Let map be ! Construct(%Map%).
let map = agent.current_realm().intrinsics().map();
let map = construct(agent, map.into_function(), None, None, gc.reborrow())?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks good to me! <3

@@ -84,6 +84,19 @@ impl MapHeapData {
self.map_data.rehash_if_needed_mut(arena);
&mut self.map_data
}

pub fn reserve(&mut self, new_len: usize) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

issue: Take arena: &impl PrimitiveHeapIndexable here to use the value.hash() function; read the key value from map_data.keys[key_index as usize] like elsewhere.

Copy link
Contributor Author

@yossydev yossydev Feb 10, 2025

Choose a reason for hiding this comment

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

Thank you! I’d like to confirm something regarding this.

If we add arena as an argument, I think we need to define primitive_heap before calling map_data.reserve(), so would the code look like this?

let groups = group_by_property(agent, items, callback_fn, gc.reborrow())?;
// 2. Let map be ! Construct(%Map%).
let Heap {
    maps,
    bigints,
    numbers,
    strings,
    ..
} = &mut agent.heap;
let primitive_heap = PrimitiveHeap::new(bigints, numbers, strings);
let mut map_data = MapHeapData::default();
map_data.reserve(groups.len(), &primitive_heap);
let map = agent.heap.create(map_data);

However, this results in the error:
“cannot borrow agent.heap as mutable more than once at a time second mutable borrow occurs here.”
Does this mean my implementation approach is different from what you had in mind?

Could you explain the correct approach? 🙇‍♂️

Comment on lines 173 to 174
// c. Append entry to map.[[MapData]].
todo!()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm, from a quick read, this all looks good. I guess figuring out why the tests are failing would need proper investigation :(

@aapoalas
Copy link
Collaborator

aapoalas commented Feb 9, 2025

Oh, I figured out the test failures: I assume your system somehow ensures that uninitialized memory is always zeroed out. We weren't properly doing zeroing out new data in an ArrayBuffer data when resizing. On your system the tests testing for that work because your system does it automatically. On CI and my machine, it does not happen automatically so the tests fail there. #563 fixes the UB there.

@yossydev
Copy link
Contributor Author

@aapoalas
Thank you for the review! Sorry for the delayed response!

0924398 ← Everything has been fixed except for the issue mentioned in this discussion!

@yossydev yossydev requested a review from aapoalas February 10, 2025 15:53
@yossydev
Copy link
Contributor Author

Oh, I figured out the test failures: I assume your system somehow ensures that uninitialized memory is always zeroed out. We weren't properly doing zeroing out new data in an ArrayBuffer data when resizing. On your system the tests testing for that work because your system does it automatically. On CI and my machine, it does not happen automatically so the tests fail there. #563 fixes the UB there.

Amazing, you found it so well!! Haha

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.

2 participants