-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Modified abstract domain in global initialization checker #23138
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
base: main
Are you sure you want to change the base?
Conversation
5591de9
to
dbdfe1a
Compare
dbdfe1a
to
7582cf9
Compare
protected val vars: mutable.Map[Symbol, Heap.Addr] = varsMap | ||
protected val outers: mutable.Map[ClassSymbol, Value] = outersMap | ||
sealed abstract class Scope(using trace: Trace): | ||
// protected val vals: mutable.Map[Symbol, Value] = valsMap |
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.
Actually remove this instead of just commenting out.
|
||
def outerValue(sym: Symbol)(using Heap.MutableData): ScopeSet = Heap.readOuter(this, sym) | ||
|
||
def outer(using Heap.MutableData): ScopeSet = this.outerValue(meth) |
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.
Could we move outerValue
and outer
down to the subclasses where they make sense (presumably Ref
for outerValue
and LocalEnv
for outer
)? I worry about calling the wrong one by accident.
val owner = klass | ||
case class ObjectRef( | ||
klass: ClassSymbol | ||
)(using context: Context, @constructorOnly heap: Heap.MutableData, trace: Trace) extends Ref: |
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.
Could we avoid these using
parameters if we had an apply
method like for OfClass
?
It feels dangerous for the ObjectRef
instance to implicitly be capturing references to these inherently local things.
def meth = defn.ArrayConstructor | ||
def klass: ClassSymbol = defn.ArrayClass | ||
|
||
val contentSymbol = defn.ArrayConstructor |
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.
Is this unused? You use meth
below.
* UnknownValue is not ValueElement since RefSet containing UnknownValue | ||
* is equivalent to UnknownValue | ||
*/ | ||
case object UnknownValue extends ValueElement: |
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 extends clause contradicts the comment.
def show(using Context) = "OfArray(owner = " + owner.show + ")" | ||
def content(using Heap.MutableData) = valValue(meth) |
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.
Consider element
instead of content
.
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.
Also readElement
and writeElement
to match readVal
and writeVal
.
@@ -1128,7 +1151,7 @@ class Objects(using Context @constructorOnly): | |||
* @param ctor The symbol of the target constructor. | |||
* @param args The arguments passsed to the constructor. | |||
*/ | |||
def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo]): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) { | |||
def instantiate(outer: Value, klass: ClassSymbol, ctor: Symbol, args: List[ArgInfo], typeArgs: List[Type]): Contextual[Value] = log("instantiating " + klass.show + ", outer = " + outer + ", args = " + args.map(_.value.show), printer, (_: Value).show) { |
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.
What are the typeArgs
used for?
if classSym.hasSource then | ||
State.checkObjectAccess(classSym) | ||
else | ||
ObjectRef(classSym) | ||
} | ||
|
||
|
||
def checkClasses(classes: List[ClassSymbol])(using Context): Unit = | ||
def checkClasses(classes: List[ClassSymbol])(using Context) = |
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 remove the return type?
@@ -2032,33 +2033,35 @@ class Objects(using Context @constructorOnly): | |||
resolveThis(enclosing, thisV, klass, elideObjectAccess = cls.isStatic) | |||
else | |||
if cls.isAllOf(Flags.JavaInterface) then Bottom | |||
else evalType(tref.prefix, thisV, klass, elideObjectAccess = cls.isStatic) | |||
else |
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.
This change doesn't change the meaning. Undo to preserve history.
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 like this redesign, great work @EnzeXing 🎉
I left some comments, but I see no issue block the merge given that we expect further PRs coming up soon.
@EnzeXing You can decide which comments to address in this PR and which are left to later PRs.
We can discuss the comment related to Cartesian Product Algorithm in next meeting.
* ThisValue ::= Ref | vs // possible values for 'this' | ||
* LocalEnv(meth, ownerObject) // represents environments for methods or functions | ||
* Scope ::= Ref | LocalEnv | ||
* ScopeSet ::= Set(Scope) |
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.
Minor: The name Scope
, ScopeSet
, and LocalEnv
are actually keys to the heap. ScopeBody
is not intuitive enough. Better names will help understanding and maintenance.
It's a minor issue (the same for other naming issues mentioned below), we can delay it to a later PR if it's not easy to come up with better names.
* | fieldVarAddr(regions, valsym, owner) // independent of OfClass/ObjectRef | ||
* | arrayAddr(regions, owner) // independent of array element type | ||
* valsMap = sym -> val // maps variables to their values | ||
* outersMap = classSym -> ScopeSEt // maps the possible outer scopes for a corresponding (parent) class |
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.
Typo: ScopeSEt
-> ScopeSet
|
||
def varAddr(sym: Symbol): Heap.Addr = vars(sym) | ||
def meth: 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.
It seems meth
have different meaning depending on whether the underlying type is Ref
or LocalEnv
.
It might be better to have easier cast .asRef/.asEnvAddr
and then access the respective members instead of unifying them with unclear semantics. It aligns with Ondrej's comment about outer
.
instance.initOuter(klass, outer) | ||
val owner = State.currentObject | ||
val instance = new OfClass(klass, owner, ctor, outerScope.level + 1, summon[Regions.Data]) | ||
instance.initOuter(klass, outerScope) | ||
instance |
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.
Minor: I was thinking, given that now we simplied OfClass
, maybe we can remove this helper method.
Advantage: the initialization code for the outer will be at the place where we initialize the object, which will be more regular than before.
|
||
def widen(height: Int)(using Context): Data | ||
|
||
sealed abstract class Data(using Trace) extends Scope: |
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.
Can we remove Data
as discussed in the meeting?
given Join[Value] with | ||
extension (a: Value) | ||
def join(b: Value): Value = | ||
assert(!a.isInstanceOf[Package] && !b.isInstanceOf[Package], "Unexpected join between " + a + " and " + b) |
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.
For future: If Package
causes troubles and is not essential, we could simply remove it from the abstract domain.
if sym.is(Flags.Lazy) then | ||
val rhs = sym.defTree.asInstanceOf[ValDef].rhs | ||
eval(rhs, thisV, sym.enclosingClass.asClass, cacheResult = true) | ||
else | ||
// Assume forward reference check is doing a good job | ||
val value = Env.valValue(sym) | ||
val value = scopeSet.scopes.map(_.varValue(sym)).join |
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.
For future: Might be good to define a helper method in scopeSet
to hide the abstract domain details from the semantic logic. It will be more robust to refactoring changes.
given State.Data = new State.Data | ||
given Trace = Trace.empty | ||
given Heap.MutableData = Heap.empty |
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.
Given that now the heap is shared globally, maybe add a TODO to do garbage collection on the heap.
res match { | ||
case ref: Ref => thisV.initOuter(cls, ScopeSet(Set(ref))) | ||
case ValueSet(values) if values.forall(_.isInstanceOf[Ref]) => | ||
thisV.initOuter(cls, ScopeSet(values.map(_.asInstanceOf[Ref]))) |
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 map
can be avoided by doing the cast directly: values.asInstanceOf[..]
recur(scopeSet.scopes.map(_.outer).join) | ||
else | ||
recur(scopeSet.scopes.map(_.outer).join) | ||
end recur |
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.
For future: if possible, try to hide abstract domain details from the semantic logic. That would make the code more robust against refactoring and easier to understand.
This PR modifies the abstract domain of the global object initialization checker. It closely follows the abstract definitional interpreter framework, but changes the heap to map from scopes (including abstract objects or local environments) to a pair of
valsMap
andoutersMap
, both of which over-approximates the information of scope bodies of the concrete scopes they represent.[test_scala2_library_tasty]