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
Fix globbed read/import bugs #449
base: main
Are you sure you want to change the base?
Conversation
SharedMemberNode enables generating non-constant object members at run time without generating an unbounded number of Truffle root nodes.
Introduce VmObjectBuilder, a uniform way to build `VmObject`s whose `ObjectMember`s are determined at run time. Replace some manual object building code with VmObjectBuilder. Add some assertions to fix IntelliJ warnings.
- Leverage SharedMemberNode to have a single Truffle root node per globbed read/import expression instead of one root node per resolved glob element. - Remove caching in ReadGlobNode because it only works correctly if glob pattern is a string *constant*. - Remove caching in StaticReadNode (now ReadGlobElementNode/ImportGlobElementNode) because it seems unnecessary and the implementation doesn't look quite right.
Problem: The result of a globbed read depends not only on the glob pattern but also on the current module URI. However, ResourceManager does not take this into account when caching globbed reads, causing wrong results. Changes: - Cache globbed reads per read expression. This is also how globbed imports are cached, except that import URIs are constant. - Make ReadGlobNode and ImportGlobNode code as similar as possible. - Reduce code duplication by inheriting from AbstractReadNode. - Add some tests.
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, great work!!
I have some minor blocking comments, but overall this is a great improvement. Thanks so much for your PR.
currentModule.getOriginal(), | ||
currentModule.getUri(), | ||
globPattern); | ||
var builder = new VmObjectBuilder(); |
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.
var builder = new VmObjectBuilder(); | |
var builder = new VmObjectBuilder(resolvedElements.size()); |
private final String globPattern; | ||
private final SharedMemberNode importGlobElementNode; |
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.
Should declare this @Child
. Also, it's good to avoid allocating an extra node until it's needed.
private final SharedMemberNode importGlobElementNode; | |
@Child @LateInit private SharedMemberNode importGlobElementNode; |
Add:
private SharedMemberNode getMemberNode() {
if (importGlobElementNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
var language = VmLanguage.get(this);
importGlobElementNode = new SharedMemberNode(
sourceSection,
sourceSection,
"",
language,
new FrameDescriptor(),
new ImportGlobElementNode(sourceSection, language, currentModule));
}
return importGlobElementNode;
}
Then within executeGeneric
:
-builder.addEntry(entry.getKey(), importGlobElementNode);
+builder.addEntry(entry.getKey(), getMemberNode());
Same comment applies to ReadGlobNode
.
import org.pkl.core.util.GlobResolver.ResolvedGlobElement; | ||
|
||
/** Used by {@link ReadGlobNode}. */ | ||
public class ReadGlobElementNode extends ExpressionNode { |
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.
"Element" clashes with our element/entry/property lexicon.
Maybe:
public class ReadGlobElementNode extends ExpressionNode { | |
public class ReadGlobMemberBodyNode extends ExpressionNode { |
Same comment on the import side; let's rename ImportGlobElementNode
to maybe ImportGlobMemberBodyNode
@TruffleBoundary | ||
private Object readResource(VmObjectLike mapping, String path) { | ||
@SuppressWarnings("unchecked") | ||
var globElements = (Map<String, ResolvedGlobElement>) mapping.getExtraStorage(); |
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.
Ahh, great idea! I didn't think about this, but this is much better. And this mirrors how we do this in the for generators too.
@TruffleBoundary | ||
private Object readResource(VmObjectLike mapping, String path) { | ||
@SuppressWarnings("unchecked") | ||
var globElements = (Map<String, ResolvedGlobElement>) mapping.getExtraStorage(); | ||
var resourceUri = globElements.get(path).getUri(); | ||
var resource = VmContext.get(this).getResourceManager().read(resourceUri, this).orElse(null); | ||
if (resource == null) { | ||
throw exceptionBuilder().evalError("cannotFindResource", resourceUri).build(); | ||
} | ||
return resource; |
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 get rid of this truffle boundary:
@TruffleBoundary | |
private Object readResource(VmObjectLike mapping, String path) { | |
@SuppressWarnings("unchecked") | |
var globElements = (Map<String, ResolvedGlobElement>) mapping.getExtraStorage(); | |
var resourceUri = globElements.get(path).getUri(); | |
var resource = VmContext.get(this).getResourceManager().read(resourceUri, this).orElse(null); | |
if (resource == null) { | |
throw exceptionBuilder().evalError("cannotFindResource", resourceUri).build(); | |
} | |
return resource; | |
private Object readResource(VmObjectLike mapping, String path) { | |
@SuppressWarnings("unchecked") | |
var globElements = (Map<String, ResolvedGlobElement>) mapping.getExtraStorage(); | |
var resourceUri = VmUtils.getMapValue(globElements, path).getUri(); | |
var resource = VmContext.get(this).getResourceManager().read(resourceUri, this).orElse(null); | |
if (resource == null) { | |
CompilerDirectives.transferToInterpreter(); | |
throw exceptionBuilder().evalError("cannotFindResource", resourceUri).build(); | |
} | |
return resource; |
This change will require VmUtils.getMapValue()
, which should be as simple as:
@TruffleBoundary
public static <K, V> V getMapValue(Map<K, V> map, K key) {
return map.get(key);
}
currentModule, | ||
currentModule.getUri(), | ||
globPattern); | ||
var builder = new VmObjectBuilder(); |
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.
var builder = new VmObjectBuilder(); | |
var builder = new VmObjectBuilder(resolvedElements.size()); |
/** | ||
* Resolves the glob URI into a set of URIs. | ||
* | ||
* <p>The glob URI must be absolute. For example: {@code "file:///foo/bar/*.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.
Ah, this is an artifact of how globbing was first implemented (glob patterns expanded to their absolute path). Moving away from that yields the bug that you noticed. We should have added an assertion here.
@TruffleBoundary | ||
public VmMapping toMapping() { | ||
var builder = new VmObjectBuilder(keyOrder.size()); | ||
for (var key : keyOrder) { | ||
var value = map.get(key); | ||
assert value != null; | ||
builder.addEntry(key, value); | ||
} | ||
return builder.toMapping(); | ||
} | ||
|
||
@TruffleBoundary | ||
public VmDynamic toDynamic() { | ||
var builder = new VmObjectBuilder(keyOrder.size()); | ||
for (var key : keyOrder) { | ||
var value = map.get(key); | ||
assert value != null; | ||
if (key instanceof String) { | ||
builder.addProperty(Identifier.get((String) key), value); | ||
} else { | ||
builder.addEntry(key, value); | ||
} | ||
} | ||
return builder.toDynamic(); | ||
} | ||
|
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 is a replacement for code that was visible to Truffle inlining, so it shouldn't be then moved behind a TruffleBoundary
.
Here is an implementation of these two methods in a way that can be inlined:
@TruffleBoundary | |
public VmMapping toMapping() { | |
var builder = new VmObjectBuilder(keyOrder.size()); | |
for (var key : keyOrder) { | |
var value = map.get(key); | |
assert value != null; | |
builder.addEntry(key, value); | |
} | |
return builder.toMapping(); | |
} | |
@TruffleBoundary | |
public VmDynamic toDynamic() { | |
var builder = new VmObjectBuilder(keyOrder.size()); | |
for (var key : keyOrder) { | |
var value = map.get(key); | |
assert value != null; | |
if (key instanceof String) { | |
builder.addProperty(Identifier.get((String) key), value); | |
} else { | |
builder.addEntry(key, value); | |
} | |
} | |
return builder.toDynamic(); | |
} | |
public VmMapping toMapping() { | |
var builder = new VmObjectBuilder(getLength()); | |
for (var entry : this) { | |
builder.addEntry(VmUtils.getKey(entry), VmUtils.getValue(entry)); | |
} | |
return builder.toMapping(); | |
} | |
public VmDynamic toDynamic() { | |
var builder = new VmObjectBuilder(getLength()); | |
for (var entry : this) { | |
var key = VmUtils.getKey(entry); | |
var value = VmUtils.getValue(entry); | |
if (key instanceof String) { | |
builder.addProperty(Identifier.get((String) key), value); | |
} else { | |
builder.addEntry(key, value); | |
} | |
} | |
return builder.toDynamic(); | |
} | |
VmUtils.createEmptyMaterializedFrame(), | ||
BaseModule.getMappingClass().getPrototype(), | ||
members); | ||
return self.toMapping(); |
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.
Should change class toDynamic
to also call out to self.toDynamic()
yeah?
@@ -463,15 +465,15 @@ private static Pair<String, String[]> splitGlobPatternIntoBaseAndWildcards( | |||
* <p>Each pair is the expanded form of the glob pattern, paired with its resolved absolute URI. | |||
*/ | |||
@TruffleBoundary | |||
public static List<ResolvedGlobElement> resolveGlob( | |||
public static Map<String, ResolvedGlobElement> resolveGlob( |
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 did this change from List
to Map
?
This PR fixes several bugs related to globbed reads/imports and makes code improvements along the way.
Bug fixes:
ResolvedGlobElement.getPath()
depend on the current module URIClassCastException
when reflecting on globbed importSee commit messages for further details.