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

WIP: Extract mutable builder from ModuleScope #9914

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from

Conversation

hubertp
Copy link
Contributor

@hubertp hubertp commented May 10, 2024

Pull Request Description

Refactored mutable parts of ModuleScope into builder to make it easier to reduce unnecessary locks.

Important Notes

Checklist

Please ensure that the following checklist has been satisfied before submitting the PR:

  • The documentation has been updated, if necessary.
  • Screenshots/screencasts have been attached, if there are any visual changes. For interactive or animated visual changes, a screencast is preferred.
  • All code follows the
    Scala,
    Java,
    TypeScript,
    and
    Rust
    style guides. In case you are using a language not listed above, follow the Rust style guide.
  • Unit tests have been written where possible.

@hubertp hubertp added the CI: No changelog needed Do not require a changelog entry for this PR. label May 13, 2024
Copy link
Member

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

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

Ideally, I'd like the Builder to be more hidden, not flying around being accessed by everybody. But even the current state that makes ModuleScope immutable is a step forward and deserves to be integrated. Thank you!

private Map<Type, Map<Type, Function>> conversions;
private Set<ModuleScope> imports;
private Set<ModuleScope> exports;
private final Map<String, Object> polyglotSymbols;
Copy link
Member

Choose a reason for hiding this comment

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

Yup, this is great. Having all fields in ModuleScope final, is the goal.

public void addExport(ModuleScope scope) {
exports.add(scope);
}

public Map<String, Type> getTypes() {
return types;
Copy link
Member

Choose a reason for hiding this comment

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

We should make sure the returned Map is immutable. E.g. wrap it by Collections.unmodifiableMap either here or in the constructor.


public ModuleScope build() {
if (moduleScope == null) {
synchronized (this) {
Copy link
Member

Choose a reason for hiding this comment

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

synchronized? Only useful if we encourage the Builder to be used from multiple threads. But it shouldn't be used that way. A Builder should only be used from a single thread, am I right?

If so, we should assert Thread.currentThread() == builderCreatorThread at few places to ensure Builder is always used from a single thread.

Copy link
Member

Choose a reason for hiding this comment

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

AFAIK, ModuleScope is constructed only from IrToTruffle and that is single-threaded. Moreover, only one module is processed at single time. So I believe that usage of synchronized does not make sense as well. Moreover, I believe that it does not make sense to use ConcurrentHashMap as fields.

Copy link
Member

Choose a reason for hiding this comment

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

Note that I have removed all the ConcurrentHashMap stuff in my recently merged PR.

Copy link
Contributor Author

@hubertp hubertp May 24, 2024

Choose a reason for hiding this comment

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

If we enable multi-threading for visualizations, which can as a side-effect trigger compilation, then we can have multiple threads accessing/adding to the builder. That was also the motivation for this PR, to make it more explicit.

conversions = new ConcurrentHashMap<>();
imports = new HashSet<>();
exports = new HashSet<>();
moduleScope = null;
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 inconsistent with the synchronized block in build - make whole reset synchronized? Remove synchronization and assert only single threaded usage of the builder?

@@ -131,7 +131,7 @@ import scala.jdk.OptionConverters._
class IrToTruffle(
val context: EnsoContext,
val source: Source,
val moduleScope: ModuleScope,
val moduleScope: ModuleScope.Builder,
Copy link
Member

Choose a reason for hiding this comment

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

That looks promising!

@@ -145,10 +143,18 @@ public LocalScope getLocalScope() {
*
* @return the module scope for this node
*/
public ModuleScope getModuleScope() {
public ModuleScope.Builder getModuleScope() {
Copy link
Member

Choose a reason for hiding this comment

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

Ideally such a public getter of the builder should be avoided. The Builder should be private to the creator of it, not floating around being accessed by random parties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I was hoping that would be the case as well, except that we are currently dealing with a situation when the builder's state is also inspected while stuff is added to it.

@@ -55,7 +55,7 @@ public LocalScope getLocalScope() {
return localScope;
}

public ModuleScope getModuleScope() {
public ModuleScope.Builder getModuleScope() {
Copy link
Member

Choose a reason for hiding this comment

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

Why does ScopeInfo needs a Builder? Shouldn't all the scope already be built before execution even gets here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I know this one is not obvious but this one is needed because of EvalNode's parseExpression which builds ClosureRootNode, which in turn requires the builder because that's the only thing that IrToTruffle has when it creates those as well. catch-22.

Copy link
Member

@Akirathan Akirathan left a comment

Choose a reason for hiding this comment

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

Altough draft PR, I am still submitting "Request changes" here. In the current form, I don't see a lot of improvements over the previous state. I would like to see most, if not all, the runtime nodes and classes to receive immutable ModuleScope as parameters and fields, not ModuleScope.Builder.

@@ -56,7 +57,7 @@ public static Object[][] allPossibleEnsoInterpreterValues() throws Exception {
data.add(new Object[] {raw, n});
}
}
data.add(new Object[] {UnresolvedSymbol.build("unknown_name", null), "Function"});
data.add(new Object[] {UnresolvedSymbol.build("unknown_name", (ModuleScope) null), "Function"});
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change necessary? Casting null to ModuleScope?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

build is currently overloaded.

Set<ModuleScope.Builder> imports = new HashSet<>(this.imports);
Set<ModuleScope.Builder> exports = new HashSet<>(this.exports);
this.types
.entrySet()
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
.entrySet()
.forEach((key, value) -> {
if (typeNames.contains(key)) {
requestedTypes.put(key, value);
}
});

Copy link
Member

Choose a reason for hiding this comment

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

for (var en : types.entrySet() is easier to step thru in debugger.

Copy link
Member

Choose a reason for hiding this comment

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

When you apply my suggestion, you don't have to call entry.getKey() and entry.getValue() in the subsequent code. That was the main reason for the suggestion.


public ModuleScope build() {
if (moduleScope == null) {
synchronized (this) {
Copy link
Member

Choose a reason for hiding this comment

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

AFAIK, ModuleScope is constructed only from IrToTruffle and that is single-threaded. Moreover, only one module is processed at single time. So I believe that usage of synchronized does not make sense as well. Moreover, I believe that it does not make sense to use ConcurrentHashMap as fields.

@@ -54,7 +54,7 @@ public class ClosureRootNode extends EnsoRootNode {
public static ClosureRootNode build(
EnsoLanguage language,
LocalScope localScope,
ModuleScope moduleScope,
ModuleScope.Builder moduleScope,
Copy link
Member

Choose a reason for hiding this comment

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

I believe only IrToTruffle should use ModuleScope.Builder for its purposes. All the runtime nodes should have only immutable ModuleScope. Is that possible? Having ModuleScope.Builder as fields in some truffle nodes diminishes the whole idea of this PR, since we will still be able to mutate the underlying builder at runtime, which should be restricted. No?

private final String name;
private @CompilerDirectives.CompilationFinal ModuleScope definitionScope;
private @CompilerDirectives.CompilationFinal ModuleScope.Builder definitionScope;
Copy link
Member

Choose a reason for hiding this comment

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

DOes it make sense to declare fields of data as @CompilationFinal?

Copy link
Member

Choose a reason for hiding this comment

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

It might make sense. Instances of Type are directly referenced from various Enso Node - e.g. they are part of the AST. Remember we have the EXCLUSIVE sharing policy - there is no sharing of ASTs.

@hubertp
Copy link
Contributor Author

hubertp commented May 24, 2024

@Akirathan @JaroslavTulach
To cut the long story short, your requirement of having only ModuleScope is satisfied in runtime because you won't be able to mutate builder once something calls build(). Statically, yes, you still see ModuleScope.Builder in various places.

I understand the sentiment that builder shouldn't be visible outside of IrToTruffle but take into account that since Truffle nodes are built during IrToTruffle the only thing that I can provide reference to is ModuleScope.Builder not ModuleScope.
In fact that is pretty much the only reason why you still see ModuleScope.Builder in various nodes.
I wanted to have only ModuleScope in Truffle nodes but I have yet to figure out, if possible at all, how to make typechecker happy with that requirement.

@@ -740,6 +724,7 @@ class IrToTruffle(
moduleScope.registerConversionMethod(toType, fromType, function)
}
})
moduleScope.build()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe this line got lost in the amount of changes but it is probably worth putting it in bold. This ensures that ModuleScope.Builder can't be modified.

Added `built` method, apart from the existing `build` method, to return
ModuleScope associated with the builder.
This way it becomes clear when builder transforms into `ModuleScope`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: No changelog needed Do not require a changelog entry for this PR.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants