-
Notifications
You must be signed in to change notification settings - Fork 281
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
Simplify lazy type checking of listings/mappings #789
Conversation
9d78e92
to
6a793b7
Compare
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 haven't dug deep yet, but I'm surprised this passes! Using the amends chain affects the semantics of super
. This feels somewhat risky, and perhaps there is a semantic that isn't yet covered by our language snippet tests.
I also don't think this is great for perf. Using the amends chain means that every child receives its own complete set of cached members every time, and also means computing the same value multiple times (which you've seen by the now deleted listing7.pkl
).
For large listings/mappings, I think this will get quite expensive.
} | ||
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); | ||
// `if (receiver instanceof VmListingOrMapping)` doesn't work | ||
// (only) because PropertiesRenderer amends a VmDynamic with a VmListing (hack?) |
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.
Umm... probably a bug
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.
To me this looks like a quick and efficient hack. But I think it would be better not to break the invariant that the entire amends chain below the prototype (which is VmTyped
) uses the same subclass of VmObject
.
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.
Yeah, I think you're right. And yeah, should remove that hack.
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 hack wasn't as easy to remove as I had thought, so I'll leave this for another PR.
@Specialization | ||
protected Object evalMapping(VirtualFrame frame, VmMapping receiver) { | ||
var result = executeBody(frame); | ||
return receiver.doTypeCast(result, VmUtils.getOwner(frame), callNode, null, 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.
Haven't verified anything yet, but, It doesn't seem correct to call doTypeCast
both here and in VmUtils
.
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.
VmUtils.doReadMember
only typecasts constant members, whereas ElementEntryNode
is only used for non-constant members. This now works the same for elements, entries, and properties.
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.
Gotcha! Okay, that makes sense.
Does it? Note that a typecast results in an object with no members.
I think this PR could implement the same optimization that exists in the current code.
I think that my proposed optimization "avoid creating an intermediate untyped listing/mapping", which isn't possible with the current data structure, is more likely to result in real-world gains. |
If we don't share cached members, every node on m1: Mapping = new {
["foo"] {
bar {
baz = doSomething()
}
}
}
m2: Mapping<String, Dynamic> = m1 Here's a "real-world" ish piece of code that ends up being much more costly (run with import "package://pkg.pkl-lang.org/pkl-k8s/[email protected]#/api/core/v1/Pod.pkl"
import "package://pkg.pkl-lang.org/pkl-k8s/[email protected]#/K8sResource.pkl"
local pods: Listing<Pod> = new {
new {
metadata {
name = trace("my-cool-pod")
namespace = "dev"
}
}
}
local allPodNames = gatherNames(pods)
function gatherNames(resources: Listing<K8sResource>) =
resources.toList().map((k) -> k.metadata.name)
output {
files {
for (p in pods) {
["\(p.metadata.name).yaml"] = p.output
}
["all-pods-names.yaml"] {
value = allPodNames
renderer = new YamlRenderer {}
}
}
}
I think the current approach probably makes the best trade-off here; we have extra fields for every |
BTW, quick note: your proposed changes introduces this regression; so we definitely have some corner cases not caught by our language snippet tests: Given: class Person { name: String }
people1: Listing<Person> = new {
new {
name = "Sandy"
}
}
people2: Listing<Person(name.endsWith("Smith"))> = (people1) {
[[true]] {
name = super.name + " Smith"
}
} The changes in this PR produces:
|
Fixed here: 724482d |
Your first example is a downcast caused by partial typing. Intuitively, it feels OK to me that a downcast, which shouldn't be the norm, incurs some overhead. Note that if Your second example is an upcast. This case feels more relevant to me because it cannot be easily avoided. Here, a better optimization is to recognize (and cache) the fact that no type cast is required because The number of root node calls made in a limited set of circumstances (*) shouldn't be the only metric an implementation is judged by. I see the following problems with the current implementation, some of which are easier to fix than others:
(*) direct assignment without amendment, type cast cannot be elided, many computed members, computations are expensive |
I think it might be fine if it was a little bit of overhead, but, in this case, it's twice the overhead (in both space and time). That's quite a lot, and can make it hard for users to reason through the performance of a program. This means that changing or adding a type annotation, which seems quite innocent, might double the cost of something. In large codebases, this can become quite problematic. Responding to the rest of the concerns:
Indeed--it would be good to reduce complexity, but I think we should be sharing cached members whenever possible.
Sorry; can you elaborate on "virtual method call"? Do you mean dynamic dispatch?
That's true, although, In the common case, I think this is pretty marginal. In the following code, all of the cached values are stored directly on the delegated (
As long as cached values are stored on the delegated, member lookups are just as cheap as they used to be.
We can probably optimize the case of
Might be unnecessary in the current implementation too; need to investigate this (thanks for pointing this out!)
It's indeed not necessarily; it's there as an optimization, although, it might not be saving that much.
Are you referring to #785? If so, this is fixed in #822.
Good point! We should switch to |
I think you may be focusing too much on this particular optimization instead of the bigger optimization picture. Anyway, I brought back the optimization and corresponding test here: 2ec4124 Another flaw of this optimization, or at least its current implementation, is that it only works if the member is, coincidentally, first evaluated for the parent/delegate. This can be verified by changing the order of
I think
Yes. Several
Yes, but not the amendment case.
What makes |
We're certainly on two sides here. You are right that this optimization is only relevant for certain cases of Pkl code. But running into it would otherwise be a significant perf penalty. Doing extra evaluation is expensive, and should be avoided if possible. But thanks for adding this optimization back! I will take a look.
Going through the existing code again, I realized that I introduced |
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.
Overall, these changes look good to me! This is an improvement over how the code is written today.
I have some comments, but they're all relatively minor.
Is this PR intended to still be a draft? What else is needed here?
@Specialization | ||
protected Object evalMapping(VirtualFrame frame, VmMapping receiver) { | ||
var result = executeBody(frame); | ||
return receiver.doTypeCast(result, VmUtils.getOwner(frame), callNode, null, 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.
Gotcha! Okay, that makes sense.
private final @Nullable ListingOrMappingTypeCastNode typeCastNode; | ||
private final MaterializedFrame typeNodeFrame; |
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 think we still need this, to address #823. This is an existing regression, that your PR doesn't solve either.
However, this comment isn't blocking because your PR isn't introducing a new regression.
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.
fixed in bc10eba
* of {@code typeNode}. (If {@code true}, it is redundant to check that elements/values have type | ||
* {@code typeNode}.) | ||
*/ | ||
public final boolean valueTypeIsSubtypeOf(TypeNode typeNode) { |
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.
[nit] "subtype" is kind of a misnomer; there's lots of subtype relationships that this would return false
for (e.g. "foo"
is a subtype of "foo"|"bar"
).
I'd prefer if this kept the original name (hasSameCheckAs
). Would also be open to other suggestions.
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 method name is shorthand for "value type is known to be subtype of" (see doc comment, happy to rename). The current implementation isn't much more than "has same checks as", which is a valid implementation of "is known to be subtype of". However, the implementation should be improved to handle as many subtype relationships as feasible and cache them, which should improve performance considerably.
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 also like hasSameCheckAs
more, or something of the sort. It's more of an equivalence check. It doesn't really care about subtyping relationships.
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 the value of aspirational naming, but then, ideally, we'd have a TODO comment and a linked Issue. If it stays aspirational beyond the point of us remembering, it becomes misleading.
@@ -261,17 +262,17 @@ public static Object doReadMember( | |||
|
|||
final var constantValue = member.getConstantValue(); | |||
if (constantValue != null) { | |||
var ret = constantValue; | |||
// for a property, do a type check | |||
Object result = constantValue; |
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.
Object result = constantValue; | |
var result = constantValue; |
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.
fixed
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.
Doesn't seem to be fixed.
} | ||
ret = vmListingOrMapping.typecastObjectMember(member, ret, callNode); | ||
// `if (receiver instanceof VmListingOrMapping)` doesn't work | ||
// (only) because PropertiesRenderer amends a VmDynamic with a VmListing (hack?) |
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.
Yeah, I think you're right. And yeah, should remove that hack.
|
||
return properties; | ||
} | ||
|
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.
Accidentally added this? This method isn't used anywhere.
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.
Removed. This was accidentally added by reverting your commit 753dd38.
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's still there.
local c = (b) { new Listing { 1 } } | ||
local d = c as Listing<Listing<Int>> | ||
|
||
result = d |
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 rename this to inputs/errors/listingTypeCheckError8.pkl
?
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.
done
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.
Still has the old name.
protected Object evalListing(VirtualFrame frame, VmListing receiver) { | ||
var result = executeBody(frame); | ||
return VmUtils.shouldRunTypeCheck(frame) | ||
? receiver.doTypeCast(result, VmUtils.getOwner(frame), callNode, null, null) | ||
: result; | ||
} | ||
|
||
@Specialization | ||
protected Object evalMapping(VirtualFrame frame, VmMapping receiver) { |
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.
Let's avoid creating IndirectCallNode
for evalDynamic
, and also defer until they are needed. We can use the @Cached
helper for this.
protected Object evalListing(VirtualFrame frame, VmListing receiver) { | |
var result = executeBody(frame); | |
return VmUtils.shouldRunTypeCheck(frame) | |
? receiver.doTypeCast(result, VmUtils.getOwner(frame), callNode, null, null) | |
: result; | |
} | |
@Specialization | |
protected Object evalMapping(VirtualFrame frame, VmMapping receiver) { | |
protected Object evalListing( | |
VirtualFrame frame, VmListing receiver, @Cached("create()") @Shared("callNode") IndirectCallNode callNode) { | |
var result = executeBody(frame); | |
return VmUtils.shouldRunTypeCheck(frame) | |
? receiver.doTypeCast(result, VmUtils.getOwner(frame), callNode, null, null) | |
: result; | |
} | |
@Specialization | |
protected Object evalMapping( | |
VirtualFrame frame, VmMapping receiver, @Cached("create()") @Shared("callNode") IndirectCallNode callNode) { |
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.
Another thought: there is another optimization that we can make to these specializations; see
pkl/pkl-core/src/main/java/org/pkl/core/ast/expression/member/ReadPropertyNode.java
Lines 64 to 68 in ad06a96
// This method effectively covers `VmObject receiver` but is implemented in a more | |
// efficient way. See: | |
// https://www.graalvm.org/22.0/graalvm-as-a-platform/language-implementation-framework/TruffleLibraries/#strategy-2-java-interfaces | |
@Specialization(guards = "receiver.getClass() == cachedClass", limit = "99") | |
protected Object evalObject( |
We should be able to mostly copy the code here, and specialize on the receiver being a VmListingOrMapping
.
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.
Let's avoid creating IndirectCallNode for evalDynamic
fixed
there is another optimization that we can make to these specializations
I think the optimization in ReadPropertyNode
is very specific to the needs of that class. ElementOrEntryNode
just needs two specialization methods that specialize on final types VmListing
and VmMapping
, so spelling them out is much simpler and at least as efficient to execute.
By the way, is it intentational that ReadProperty.checkConst()
runs once per node, not once per receiver.getVmClass()
?
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.
By the way, is it intentational that ReadProperty.checkConst() runs once per node, not once per receiver.getVmClass()?
Sorry, missed this when I was catching up on messages while I was out.
I think checking once per node is correct. needsConst
is only set to true in the case of an implicit this
receiver (see the constructor calls in ResolveVariableNode
).
And, in the case of the implicit receiver, we only need to check const in the case of class properties.
open class A {
a = 1
}
class B extends A {
const b = a // <-- implicit this lookup of `a`, which is not in a const scope.
}
In this case, getVmClass()
will always give you the same 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.
receiver.getVmClass()
could, in general, return a subclass. But I see that Pkl enforces that a property's const-ness doesn't change when subclassing, so this should nevertheless be safe (but also worth a comment).
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 do: #859
} | ||
|
||
@Specialization | ||
protected Object evalDynamic(VirtualFrame frame, @SuppressWarnings("unused") VmDynamic receiver) { |
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.
protected Object evalDynamic(VirtualFrame frame, @SuppressWarnings("unused") VmDynamic receiver) { | |
protected Object evalDynamic(VirtualFrame frame, VmDynamic ignored) { |
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.
fixed
@TruffleBoundary | ||
public static <K, V> UnmodifiableEconomicMap<K, V> emptyMap() { | ||
return EconomicMap.emptyMap(); | ||
} |
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.
We don't need a truffle boundary on EconomicMap.emptyMap()
. It just casts a singleton, and Truffle should have no trouble doing partial evaluation here.
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.
Removed. We also don't need EconomicMaps
. :-)
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's not removed.
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 only reason for EconomicMaps
was to put truffle boundaries in place, so yeah, no need.
By the way, heads up: I'm going to be on vacay for the next two weeks, so you won't hear from me for a bit! |
Additionally, whether it works at all depends on evaluation order, which users can't reason about. I'd love to see some memory/cpu benchmarks that prove or disprove its worth. Fortunately, the optimization turned out to be easy to integrate into my implementation of
I guess this means further delays for my pending PRs. Anyway, I hope that you are enjoying your vacation!
Removed draft status. |
getRootNode().getFrameDescriptor(), | ||
valueTypeNode, | ||
getRootNode().getName()); | ||
language, new FrameDescriptor(), valueTypeNode, getRootNode().getName()); |
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 the change to new FrameDescriptor()
instead of getting it from the root node?
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 correct frame descriptor here is an empty frame descriptor. I don't see a logical connection between this frame descriptor and getRootNode()
's frame descriptor.
* of {@code typeNode}. (If {@code true}, it is redundant to check that elements/values have type | ||
* {@code typeNode}.) | ||
*/ | ||
public final boolean valueTypeIsSubtypeOf(TypeNode typeNode) { |
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 also like hasSameCheckAs
more, or something of the sort. It's more of an equivalence check. It doesn't really care about subtyping relationships.
var value = getCachedValue(i); | ||
var cursor = cachedValues.getEntries(); | ||
while (cursor.advance()) { | ||
Object key = cursor.getKey(); |
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.
Doesn't seem to be fixed.
@@ -261,17 +262,17 @@ public static Object doReadMember( | |||
|
|||
final var constantValue = member.getConstantValue(); | |||
if (constantValue != null) { | |||
var ret = constantValue; | |||
// for a property, do a type check | |||
Object result = constantValue; |
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.
Doesn't seem to be fixed.
var sourceSection = member.getBodySection(); | ||
if (!sourceSection.isAvailable()) { | ||
sourceSection = member.getSourceSection(); | ||
if (typeCastNode != 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.
It's not fixed in the last commit (bc10eba).
|
||
return properties; | ||
} | ||
|
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's still there.
@TruffleBoundary | ||
public static <K, V> UnmodifiableEconomicMap<K, V> emptyMap() { | ||
return EconomicMap.emptyMap(); | ||
} |
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's not removed.
local c = (b) { new Listing { 1 } } | ||
local d = c as Listing<Listing<Int>> | ||
|
||
result = d |
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.
Still has the old name.
Looks good! Only some things that are still not addressed. Maybe you forgot to commit those changes? |
Sorry for that, forgot to push. Fixed. PS: Once the review is complete, I'd like to squash the commits and edit the commit message. |
My point is that this should care about subtyping relationships. "Is subtype of" is the desirable check here because listings/mappings are covariant in their element/value type. For example, assigning As long as this subtype check is only used to improve performance, it's acceptable to return |
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.
Looks good from my side. Will just wait for @holzensp to do a pass.
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.
A few comments / questions, but none of them blocking.
* of {@code typeNode}. (If {@code true}, it is redundant to check that elements/values have type | ||
* {@code typeNode}.) | ||
*/ | ||
public final boolean valueTypeIsSubtypeOf(TypeNode typeNode) { |
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 the value of aspirational naming, but then, ideally, we'd have a TODO comment and a linked Issue. If it stays aspirational beyond the point of us remembering, it becomes misleading.
// Note that mutating a frame's receiver and owner argument is very risky | ||
// because any VmObject instantiated within the same root node execution | ||
// holds a reference to (not immutable snapshot of) the frame | ||
// via VmObjectLike.enclosingFrame. | ||
// *Maybe* this works out for TypeAliasTypeNode because an object instantiated | ||
// within a type constraint doesn't escape the constraint expression. | ||
// If mutating receiver and owner can't be avoided, it would be safer | ||
// to have VmObjectLike store them directly instead of storing enclosingFrame. |
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 this danger; why not make a synthetic extra frame, on top of the given frame, with the new owner/receiver?
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 the value of aspirational naming, but then, ideally, we'd have a TODO comment and a linked Issue. If it stays aspirational beyond the point of us remembering, it becomes misleading.
I've now changed the method name to isValueTypeKnownSubtypeOf
, at which point it's no longer aspirational.
The method already handles ValueType
<: Any
and ValueType
<: Unknown
.
Improving this further is part of (6) on my planned list of improvements.
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 this danger; why not make a synthetic extra frame, on top of the given frame, with the new owner/receiver?
Inserting a frame means inserting a root node call, which I think means abandoning the current implementation strategy of inlining type aliases into the root node referencing them.
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 this danger; why not make a synthetic extra frame, on top of the given frame, with the new owner/receiver?
Turns out this may be possible:
var newArgs = frame.getArguments().clone();
newArgs[0] = newReceiver;
newArgs[1] = newOwner;
var newFrame = Truffle.getRuntime().createVirtualFrame(newArgs, frame.getDescriptor());
childNode.execute(newFrame);
@TruffleBoundary | ||
public static <K, V> UnmodifiableEconomicMap<K, V> emptyMap() { | ||
return EconomicMap.emptyMap(); | ||
} |
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 only reason for EconomicMaps
was to put truffle boundaries in place, so yeah, no need.
var cursor = cachedValues.getEntries(); | ||
while (cursor.advance()) { | ||
var key = cursor.getKey(); | ||
if (key instanceof Identifier) continue; |
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.
Doesn't this just create more work? I guess this trades lookup cost (of the previous by-index formulation) against instanceof
+ branching cost.
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 expect this branch to be rarely taken for a listing. The instanceof check is roughly key.getClass() == Identifier.class
), which should be faster than a map lookup. (I didn't write this code, just reverted the commit that made major changes here and elsewhere.)
cachedHash = result; | ||
return result; | ||
} | ||
|
||
// assumes mapping has been forced |
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.
Asserting is better than assuming; not against making forced
protected
.
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 didn't write this code, just reverted a commit. This method requires shallow forcing, which isn't currently tracked and hence can't be asserted. (I've added tracking of shallow forcing in one of my planned PRs.)
} else if (owner instanceof VmListingOrMapping) { | ||
result = | ||
((VmListingOrMapping) receiver) | ||
.doTypeCast(constantValue, owner, callNode, member, 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.
Agreed with "better to remove the hack" above, but this reads weird, even with the comment, maybe...
} else if (owner instanceof VmListingOrMapping) { | |
result = | |
((VmListingOrMapping) receiver) | |
.doTypeCast(constantValue, owner, callNode, member, null); | |
} else if (owner instanceof VmListingOrMapping && receiver instance VmListingOrMapping checkable) { | |
result = checkable.doTypeCast(constantValue, owner, callNode, member, 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.
changed to (almost) your code
Replied to all feedback. Let me know when it's time to squash and edit the commit message. It would also be great if you could test this PR internally. |
There seem to be still some rough edges: typealias Alias = String(endsWith("x"))
local function isValid(alias: Alias): Boolean = alias.startsWith("a")
foo: Listing<Alias(isValid(this))>(isDistinct)
amends "foo.pkl"
foo {
"abcdx"
"ax"
} Results in
If you remove the |
Fixed in 9e7b241. Anything else? |
Didn't find anything else, but CI doesn't seem to be happy about your
|
Fixed (forgot to commit the test output file) and added one more commit ("Rename method, add implementation comments"). |
This PR is good to go from my side. You can add your commit comment here or squash the commits yourself. We always squash as we merge anyway so every PR is one commit. |
Squashed and edited commit message. |
Motivation: - simplify implementation of lazy type checking - fix correctness issues of lazy type checking (apple#785) Changes: - implement listing/mapping type cast via amendment (`parent`) instead of delegation (`delegate`) - handle type checking of *computed* elements/entries in the same way as type checking of computed properties - ElementOrEntryNode is the equivalent of TypeCheckedPropertyNode - remove fields VmListingOrMapping.delegate/typeNodeFrame/cachedMembers/checkedMembers - fix apple#785 by executing all type casts between a member's owner and receiver - fix apple#823 by storing owner and receiver directly instead of storing the mutable frame containing them (typeNodeFrame) - remove overrides of VmObject methods that are no longer required - good for Truffle partial evaluation and JVM inlining - revert a85a173 except for added tests - move `VmUtils.setOwner` and `VmUtils.setReceiver` and make them private - these methods aren't generally safe to use Result: - simpler code with greater optimization potential - VmListingOrMapping can now have both a type node and new members - fewer changes to surrounding code - smaller memory footprint - better performance in some cases - fixes apple#785 - fixes apple#823 Potential future optimizations: - avoid lazy type checking overhead for untyped listings/mappings - improve efficiency of forcing a typed listing/mapping - currently, lazy type checking will traverse the parent chain once per member, reducing the performance benefit of shallow-forcing a listing/mapping over evaluating each member individually - avoid creating an intermediate untyped listing/mapping in the following cases: - `new Listing<X> {...}` - amendment of `property: Listing<X>`
Added two more tests based on #822 (comment):
|
Motivation: - simplify implementation of lazy type checking - fix correctness issues of lazy type checking (#785) Changes: - implement listing/mapping type cast via amendment (`parent`) instead of delegation (`delegate`) - handle type checking of *computed* elements/entries in the same way as type checking of computed properties - ElementOrEntryNode is the equivalent of TypeCheckedPropertyNode - remove fields VmListingOrMapping.delegate/typeNodeFrame/cachedMembers/checkedMembers - fix #785 by executing all type casts between a member's owner and receiver - fix #823 by storing owner and receiver directly instead of storing the mutable frame containing them (typeNodeFrame) - remove overrides of VmObject methods that are no longer required - good for Truffle partial evaluation and JVM inlining - revert a85a173 except for added tests - move `VmUtils.setOwner` and `VmUtils.setReceiver` and make them private - these methods aren't generally safe to use Result: - simpler code with greater optimization potential - VmListingOrMapping can now have both a type node and new members - fewer changes to surrounding code - smaller memory footprint - better performance in some cases - fixes #785 - fixes #823 Potential future optimizations: - avoid lazy type checking overhead for untyped listings/mappings - improve efficiency of forcing a typed listing/mapping - currently, lazy type checking will traverse the parent chain once per member, reducing the performance benefit of shallow-forcing a listing/mapping over evaluating each member individually - avoid creating an intermediate untyped listing/mapping in the following cases: - `new Listing<X> {...}` - amendment of `property: Listing<X>`
I marked this PR as a draft because my initial goal is to start a conversation. For details see commit messages.