-
Notifications
You must be signed in to change notification settings - Fork 323
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
Extract mutable builder from ModuleScope
#9914
Conversation
There was a problem hiding this 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; |
There was a problem hiding this comment.
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.
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
|
||
public ModuleScope build() { | ||
if (moduleScope == null) { | ||
synchronized (this) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
@@ -131,7 +131,7 @@ import scala.jdk.OptionConverters._ | |||
class IrToTruffle( | |||
val context: EnsoContext, | |||
val source: Source, | |||
val moduleScope: ModuleScope, | |||
val moduleScope: ModuleScope.Builder, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks promising!
engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/node/callable/CaptureCallerInfoNode.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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
.
...on-tests/src/test/java/org/enso/interpreter/node/expression/builtin/meta/TypeOfNodeTest.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
|
||
public ModuleScope build() { | ||
if (moduleScope == null) { | ||
synchronized (this) { |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, please don't reference builder, reference Module
.
@Akirathan @JaroslavTulach I understand the sentiment that builder shouldn't be visible outside of |
engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala
Outdated
Show resolved
Hide resolved
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`.
Don't we have to support some form of mutability to allow recompilation of
... provide reference to uninitialized |
engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
`ImportExportScope` serves as a proxy to the underlying module's scope. Can't reference the scope directly at the point when it is built because building of some modules might still be in progress. This also allowed to get rid off the horrible `withTypes` method that would create a temporary module scope.
It's unused and untested anyway.
Simplified the whole approach by avoiding calls to `built` and `build` to determine the state of `ModuleScope.Builder`. Instead, `ModuleScope.Builder` is only passed whenever a registration is necessary. Since builder is accessed in various places, while it may still, in theory, be constructor, this change introduces a simple, short-lived, proxy that delegates to accessors and satisfies the `ModuleScope` API.
e4e2f20
to
1f347d0
Compare
New builder means new builder (minus types).
ModuleScope
ModuleScope
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the PR gets us to a state where I'd like to be, but it is certainly making the system better. Ideally I want:
var b = module.newScopeBuilder(reuseYesOrNo);
b.register(...)
b.build() // andInstall();
where buildAndInstall()
actually verifies the current module scope is the same as the scope when the builder was created and otherwise yields an inconsistency error.
@@ -348,7 +340,7 @@ get_polyglot_language value = @Builtin_Method "Meta.get_polyglot_language" | |||
|
|||
Arguments: | |||
- name: The name of the unresolved symbol. | |||
- scope: The scope in which the symbol name is unresolved. | |||
- symbol: The unresolved symbol from which to get the scope. | |||
create_unresolved_symbol : Text -> Any -> Unresolved_Symbol | |||
create_unresolved_symbol name scope = @Builtin_Method "Meta.create_unresolved_symbol" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument is still named scope
.
} else { | ||
return scope.withTypes(types); | ||
} | ||
public ModuleScope.Builder getScopeBuilder() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exposing current builder (created by whoever else), isn't really safe.
return scopeBuilder; | ||
} | ||
|
||
public ModuleScope.Builder newScopeBuilder() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating new builder is OK. But storing it in this.scopeBuilder
variable isn't correct.
return builder; | ||
} | ||
|
||
public void resetScope() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer newScopeBuilder(boolean inheritTypes)
slightly...
private ModuleSources sources; | ||
private QualifiedName name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't QualifiedName
final
? I don't see any write to it...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Three is a rename
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. Thank you. However, in the name of exorcising mutability away from Module
it may be reasonable to ask whether name shouldn't be a temporary property - e.g. be held by ModuleScope
?
private final String name; | ||
private @CompilerDirectives.CompilationFinal ModuleScope definitionScope; | ||
private @CompilerDirectives.CompilationFinal ModuleScope.Builder definitionScope; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, please don't reference builder, reference Module
.
* A proxy scope delegating to the underlying module's scope. Additionally, `ImportExportScope` may | ||
* limit the number of types that are imported/exported. | ||
*/ | ||
public class ImportExportScope implements EnsoObject { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't this object extend EnsoObject
? It shouldn't flow thru user data right?
Make final
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
? EnsoObject
is an interface, I don't understand the request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, EnsoObject
is an interface with a purpose (described in its javadoc) of:
/** All non-primitive Enso types extends from {@code EnsoObject}. */
why do you believe ImportExportScope
should be a non-primitive Enso type? Do you believe this object shall flow thru user program? Unless I am mistaken you have removed exposure of ModuleScope
from Meta.enso
claiming "it is dangerous" - why do you believe ImportExportScope
should be exposed (and manipulated) by Enso programs?
public class ImportExportScope implements EnsoObject { | |
public final class ImportExportScope { |
|
||
import org.enso.compiler.context.CompilerContext; | ||
|
||
public class TruffleCompilerModuleScopeBuilder extends CompilerContext.ModuleScopeBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
final
@@ -723,8 +734,7 @@ public void close() { | |||
module.module.setLoadedFromCache(loadedFromCache); | |||
} | |||
if (resetScope) { | |||
module.module.ensureScopeExists(); | |||
module.module.getScope().reset(); | |||
module.module.resetScope(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect this to call newBuilder()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resetScope
does call newBuilder
@@ -86,7 +86,7 @@ RootCallTarget parseExpression(LocalScope scope, ModuleScope moduleScope, String | |||
var sco = newInlineContext.localScope().getOrElse(LocalScope::root); | |||
var mod = newInlineContext.getModule(); | |||
var m = org.enso.interpreter.runtime.Module.fromCompilerModule(mod); | |||
var toTruffle = new IrToTruffle(context, src, m.getScope(), compiler.getConfig()); | |||
var toTruffle = new IrToTruffle(context, src, m.getScopeBuilder(), compiler.getConfig()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can imagine that this is a place where EvalNode
wants to incrementally add something to the scope. Still having some sequence like:
var b = m.newScopeBuilder(reuseEverything=true)
b.registerXyz(...)
b.build() // and install into `Module`
is a way better than having a getter to existing scope builder.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks like a better design than the one we currently have. There might be some follow-up issues.
...ne/runtime/src/main/java/org/enso/interpreter/node/callable/dispatch/InvokeFunctionNode.java
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/data/atom/AtomConstructor.java
Outdated
Show resolved
Hide resolved
engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java
Outdated
Show resolved
Hide resolved
...ument-common/src/main/scala/org/enso/interpreter/instrument/job/UpsertVisualizationJob.scala
Show resolved
Hide resolved
|
||
public static class Builder { | ||
|
||
@CompilerDirectives.CompilationFinal private ModuleScope moduleScope = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt this is correct. How is a code using this moduleScope
field in PE going to be invalidated when the field changes?
Anyway, I believe Builder
shouldn't be used on fast path. Rather put CompilerAsserts.neverPartOfCompilation();
to every method of the Builder
!
There are few minor issues that I'd like to address separately, as this PR has already been dragging on. Most importantly remove the |
Pull Request Description
Refactored mutable parts of
ModuleScope
into builder to make it easier to reduce unnecessary locks.Important Notes
Elements of ModuleScope (types, imports etc) are used while building of it may still be in progress. In order to make static typing happy, every
ModuleScope.Builder
can be exposed as (unmodifiable)ModuleScope
.Checklist
Please ensure that the following checklist has been satisfied before submitting the PR:
Scala,
Java,
TypeScript,
and
Rust
style guides. In case you are using a language not listed above, follow the Rust style guide.