com.oracle.truffle.sl.nodes.local.SLScopedNode Maven / Gradle / Ivy
Show all versions of truffle-sl Show documentation
/*
* Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.sl.nodes.local;
import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.NodeLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.sl.SLLanguage;
import com.oracle.truffle.sl.nodes.SLExpressionNode;
import com.oracle.truffle.sl.nodes.SLRootNode;
import com.oracle.truffle.sl.nodes.controlflow.SLBlockNode;
import com.oracle.truffle.sl.runtime.SLContext;
import com.oracle.truffle.sl.runtime.SLNull;
/**
* The SL implementation of {@link NodeLibrary} provides fast access to local variables. It's used
* by tools like debugger, profiler, tracer, etc. To provide good performance, we cache write nodes
* that declare variables and use them in the interop contract.
*/
@ExportLibrary(value = NodeLibrary.class)
public abstract class SLScopedNode extends Node {
/**
* Index to the the {@link SLBlockNode#getDeclaredLocalVariables() block's variables} that
* determine variables belonging into this scope (excluding parent scopes) on node enter. The
* scope variables are in the interval <0, visibleVariablesIndexOnEnter).
*/
@CompilationFinal private volatile int visibleVariablesIndexOnEnter = -1;
/**
* Similar to {@link #visibleVariablesIndexOnEnter}, but determines variables on node exit. The
* scope variables are in the interval <0, visibleVariablesIndexOnExit).
*/
@CompilationFinal private volatile int visibleVariablesIndexOnExit = -1;
/**
* For performance reasons, fix the library implementation for the particular node.
*/
@ExportMessage
boolean accepts(@Shared("node") @Cached(value = "this", adopt = false) SLScopedNode cachedNode) {
return this == cachedNode;
}
/**
* We do provide a scope.
*/
@ExportMessage
@SuppressWarnings("static-method")
public boolean hasScope(@SuppressWarnings("unused") Frame frame) {
return true;
}
/**
* The scope depends on the current node and the node's block. Cache the node and its block for
* fast access. Depending on the block node, we create either block variables, or function
* arguments (in the RootNode, but outside of a block).
*/
@ExportMessage
@SuppressWarnings("static-method")
final Object getScope(Frame frame, boolean nodeEnter, @Shared("node") @Cached(value = "this", adopt = false) SLScopedNode cachedNode,
@Cached(value = "this.findBlock()", adopt = false, allowUncached = true) Node blockNode) {
if (blockNode instanceof SLBlockNode) {
return new VariablesObject(frame, cachedNode, nodeEnter, (SLBlockNode) blockNode);
} else {
return new ArgumentsObject(frame, (SLRootNode) blockNode);
}
}
/**
* Test if a function of that name exists. The functions are context-dependent, therefore do a
* context lookup via {@link SLContext#getCurrent(Node)}.
*/
@ExportMessage
@TruffleBoundary
final boolean hasRootInstance(@SuppressWarnings("unused") Frame frame) {
String functionName = getRootNode().getName();
// The instance of the current RootNode is a function of the same name.
return SLContext.get(this).getFunctionRegistry().getFunction(functionName) != null;
}
/**
* Provide function instance of that name. The function is context-dependent, therefore do a
* context lookup via {@link SLContext#getCurrent(Node)}.
*/
@ExportMessage
@TruffleBoundary
final Object getRootInstance(@SuppressWarnings("unused") Frame frame) throws UnsupportedMessageException {
String functionName = getRootNode().getName();
// The instance of the current RootNode is a function of the same name.
Object function = SLContext.get(this).getFunctionRegistry().getFunction(functionName);
if (function != null) {
return function;
} else {
throw UnsupportedMessageException.create();
}
}
/**
* Find block of this node. Traverse the parent chain and find the first {@link SLBlockNode}. If
* none is found, {@link RootNode} is returned.
*
* @return the block node, always non-null. Either SLBlockNode, or SLRootNode.
*/
public final Node findBlock() {
Node parent = getParent();
while (parent != null) {
if (parent instanceof SLBlockNode) {
break;
}
Node p = parent.getParent();
if (p == null) {
assert parent instanceof RootNode : String.format("Not adopted node under %s.", parent);
return parent;
}
parent = p;
}
return parent;
}
/**
* Set the index to the the {@link SLBlockNode#getDeclaredLocalVariables() block's variables}
* that determine variables belonging into this scope (excluding parent scopes) on node enter.
*/
public final void setVisibleVariablesIndexOnEnter(int index) {
assert visibleVariablesIndexOnEnter == -1 : "The index is set just once";
assert 0 <= index;
visibleVariablesIndexOnEnter = index;
}
/**
* Similar to {@link #setVisibleVariablesIndexOnEnter(int)}, but determines variables on node
* exit.
*/
public final void setVisibleVariablesIndexOnExit(int index) {
assert visibleVariablesIndexOnExit == -1 : "The index is set just once";
assert 0 <= index;
visibleVariablesIndexOnExit = index;
}
/**
* Provide the index that determines variables on node enter.
*/
protected final int getVisibleVariablesIndexOnEnter() {
return visibleVariablesIndexOnEnter;
}
/**
* Scope of function arguments. This scope is provided by nodes just under a {@link SLRootNode},
* outside of a {@link SLBlockNode block}.
*
* The arguments declared by {@link SLRootNode#getDeclaredArguments() root node} are provided as
* scope members.
*/
@ExportLibrary(InteropLibrary.class)
static final class ArgumentsObject implements TruffleObject {
/**
* The member caching limit.
*/
static int LIMIT = 3;
private final Frame frame;
protected final SLRootNode root;
/**
* The arguments depend on the current frame and root node.
*/
ArgumentsObject(Frame frame, SLRootNode root) {
this.frame = frame;
this.root = root;
}
/**
* For performance reasons, fix the library implementation for the particular root node.
*/
@ExportMessage
boolean accepts(@Cached(value = "this.root", adopt = false) SLRootNode cachedRoot) {
return this.root == cachedRoot;
}
/**
* This is a scope object, providing arguments as members.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean isScope() {
return true;
}
/**
* The scope must provide an associated language.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasLanguage() {
return true;
}
@ExportMessage
@SuppressWarnings("static-method")
Class extends TruffleLanguage>> getLanguage() {
return SLLanguage.class;
}
/**
* Provide the function name as the scope's display string.
*/
@ExportMessage
Object toDisplayString(@SuppressWarnings("unused") boolean allowSideEffects) {
return root.getName();
}
/**
* We provide a source section of this scope.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasSourceLocation() {
return true;
}
/**
* @return Source section of the function.
*/
@ExportMessage
SourceSection getSourceLocation() {
return root.getSourceSection();
}
/**
* Scope must provide scope members.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasMembers() {
return true;
}
/**
* We return an array of argument objects as scope members.
*/
@ExportMessage
Object getMembers(@SuppressWarnings("unused") boolean includeInternal) {
SLWriteLocalVariableNode[] writeNodes = root.getDeclaredArguments();
return new KeysArray(writeNodes, writeNodes.length, writeNodes.length);
}
/**
* Test if a member exists. We cache the result for fast access.
*/
@ExportMessage(name = "isMemberReadable")
static final class ExistsMember {
/**
* If the member is cached, provide the cached result. Call
* {@link #doGeneric(ArgumentsObject, String)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static boolean doCached(ArgumentsObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member existence for fast-path access
@Cached("doGeneric(receiver, member)") boolean cachedResult) {
assert cachedResult == doGeneric(receiver, member);
return cachedResult;
}
/**
* Test if an argument with that name exists.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static boolean doGeneric(ArgumentsObject receiver, String member) {
return receiver.hasArgumentIndex(member);
}
}
/**
* Test if a member is modifiable. We cache the result for fast access.
*/
@ExportMessage(name = "isMemberModifiable")
static final class ModifiableMember {
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static boolean doCached(ArgumentsObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member existence for fast-path access
@Cached("receiver.hasArgumentIndex(member)") boolean cachedResult) {
return cachedResult && receiver.frame != null;
}
/**
* Test if an argument can be modified (it must exist and we must have a frame).
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static boolean doGeneric(ArgumentsObject receiver, String member) {
return receiver.findArgumentIndex(member) >= 0 && receiver.frame != null;
}
}
/**
* We can not insert new arguments.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
return false;
}
/**
* Read an argument value. Cache the argument's index for fast access.
*/
@ExportMessage(name = "readMember")
static final class ReadMember {
/**
* If the member is cached, use the cached index and read the value at that index. Call
* {@link #doGeneric(ArgumentsObject, String)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static Object doCached(ArgumentsObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member's index for fast-path access
@Cached("receiver.findArgumentIndex(member)") int index) throws UnknownIdentifierException {
return doRead(receiver, cachedMember, index);
}
/**
* Find the member's index and read the value at that index.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static Object doGeneric(ArgumentsObject receiver, String member) throws UnknownIdentifierException {
int index = receiver.findArgumentIndex(member);
return doRead(receiver, member, index);
}
/**
* Read the argument at the provided index from {@link Frame#getArguments()} array.
*/
private static Object doRead(ArgumentsObject receiver, String member, int index) throws UnknownIdentifierException {
if (index < 0) {
throw UnknownIdentifierException.create(member);
}
if (receiver.frame != null) {
return receiver.frame.getArguments()[index];
} else {
return SLNull.SINGLETON;
}
}
}
/**
* Write an argument value. Cache the argument's index for fast access.
*/
@ExportMessage(name = "writeMember")
static final class WriteMember {
/**
* If the member is cached, use the cached index and write the value at that index. Call
* {@link #doGeneric(ArgumentsObject, String, Object)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static void doCached(ArgumentsObject receiver, String member, Object value,
@Cached("member") String cachedMember,
// We cache the member's index for fast-path access
@Cached("receiver.findArgumentIndex(member)") int index) throws UnknownIdentifierException, UnsupportedMessageException {
doWrite(receiver, member, index, value);
}
/**
* Find the member's index and write the value at that index.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static void doGeneric(ArgumentsObject receiver, String member, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
int index = receiver.findArgumentIndex(member);
doWrite(receiver, member, index, value);
}
/**
* Write the argument value at the provided index into {@link Frame#getArguments()}
* array.
*/
private static void doWrite(ArgumentsObject receiver, String member, int index, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
if (index < 0) {
throw UnknownIdentifierException.create(member);
}
if (receiver.frame != null) {
receiver.frame.getArguments()[index] = value;
} else {
throw UnsupportedMessageException.create();
}
}
}
boolean hasArgumentIndex(String member) {
return findArgumentIndex(member) >= 0;
}
int findArgumentIndex(String member) {
SLWriteLocalVariableNode[] writeNodes = root.getDeclaredArguments();
for (int i = 0; i < writeNodes.length; i++) {
SLWriteLocalVariableNode writeNode = writeNodes[i];
if (member.equals(writeNode.getSlot().getIdentifier())) {
return i;
}
}
return -1;
}
}
/**
* Scope of all variables accessible in the scope from the entered or exited node.
*/
@ExportLibrary(InteropLibrary.class)
static final class VariablesObject implements TruffleObject {
/**
* The member caching limit.
*/
static int LIMIT = 4;
private final Frame frame; // the current frame
protected final SLScopedNode node; // the current node
final boolean nodeEnter; // whether the node was entered or is about to be exited
protected final SLBlockNode block; // the inner-most block of the current node
VariablesObject(Frame frame, SLScopedNode node, boolean nodeEnter, SLBlockNode blockNode) {
this.frame = frame;
this.node = node;
this.nodeEnter = nodeEnter;
this.block = blockNode;
}
/**
* For performance reasons, fix the library implementation for the current node and enter
* flag.
*/
@ExportMessage
boolean accepts(@Cached(value = "this.node", adopt = false) SLScopedNode cachedNode,
@Cached(value = "this.nodeEnter") boolean cachedNodeEnter) {
return this.node == cachedNode && this.nodeEnter == cachedNodeEnter;
}
/**
* This is a scope object, providing variables as members.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean isScope() {
return true;
}
/**
* The scope must provide an associated language.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasLanguage() {
return true;
}
@ExportMessage
@SuppressWarnings("static-method")
Class extends TruffleLanguage>> getLanguage() {
return SLLanguage.class;
}
/**
* Provide either "block", or the function name as the scope's display string.
*/
@ExportMessage
@SuppressWarnings({"static-method", "unused"})
Object toDisplayString(boolean allowSideEffects, @Shared("block") @Cached(value = "this.block", adopt = false) SLBlockNode cachedBlock,
@Shared("parentBlock") @Cached(value = "this.block.findBlock()", adopt = false, allowUncached = true) Node parentBlock) {
// Cache the parent block for the fast-path access
if (parentBlock instanceof SLBlockNode) {
return "block";
} else {
return ((SLRootNode) parentBlock).getName();
}
}
/**
* There is a parent scope if we're in a block.
*/
@ExportMessage
@SuppressWarnings({"static-method", "unused"})
boolean hasScopeParent(
@Shared("block") @Cached(value = "this.block", adopt = false) SLBlockNode cachedBlock,
@Shared("parentBlock") @Cached(value = "this.block.findBlock()", adopt = false, allowUncached = true) Node parentBlock) {
// Cache the parent block for the fast-path access
return parentBlock instanceof SLBlockNode;
}
/**
* The parent scope object is based on the parent block node.
*/
@ExportMessage
@SuppressWarnings("unused")
Object getScopeParent(
@Shared("block") @Cached(value = "this.block", adopt = false) SLBlockNode cachedBlock,
@Shared("parentBlock") @Cached(value = "this.block.findBlock()", adopt = false, allowUncached = true) Node parentBlock) throws UnsupportedMessageException {
// Cache the parent block for the fast-path access
if (!(parentBlock instanceof SLBlockNode)) {
throw UnsupportedMessageException.create();
}
return new VariablesObject(frame, cachedBlock, true, (SLBlockNode) parentBlock);
}
/**
* We provide a source section of this scope.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasSourceLocation() {
return true;
}
/**
* The source section of this scope is either the block, or the function.
*/
@ExportMessage
@TruffleBoundary
SourceSection getSourceLocation() {
Node parentBlock = block.findBlock();
if (parentBlock instanceof RootNode) {
// We're in the function block
assert parentBlock instanceof SLRootNode : String.format("In SLLanguage we expect SLRootNode, not %s", parentBlock.getClass());
return ((SLRootNode) parentBlock).getSourceSection();
} else {
return block.getSourceSection();
}
}
/**
* Scope must provide scope members.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean hasMembers() {
return true;
}
/**
* Test if a member exists. We cache the result for fast access.
*/
@ExportMessage(name = "isMemberReadable")
static final class ExistsMember {
/**
* If the member is cached, provide the cached result. Call
* {@link #doGeneric(VariablesObject, String)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static boolean doCached(VariablesObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member existence for fast-path access
@Cached("doGeneric(receiver, member)") boolean cachedResult) {
assert cachedResult == doGeneric(receiver, member);
return cachedResult;
}
/**
* Test if a variable with that name exists. It exists if we have a corresponding write
* node.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static boolean doGeneric(VariablesObject receiver, String member) {
return receiver.hasWriteNode(member);
}
}
/**
* Test if a member is modifiable. We cache the result for fast access.
*/
@ExportMessage(name = "isMemberModifiable")
static final class ModifiableMember {
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static boolean doCached(VariablesObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member existence for fast-path access
@Cached("receiver.hasWriteNode(member)") boolean cachedResult) {
return cachedResult && receiver.frame != null;
}
/**
* Test if a variable with that name exists and we have a frame.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static boolean doGeneric(VariablesObject receiver, String member) {
return receiver.hasWriteNode(member) && receiver.frame != null;
}
}
/**
* We do not support insertion of new variables.
*/
@ExportMessage
@SuppressWarnings("static-method")
boolean isMemberInsertable(@SuppressWarnings("unused") String member) {
return false;
}
/**
* Read a variable value. Cache the frame slot by variable name for fast access.
*/
@ExportMessage(name = "readMember")
static final class ReadMember {
/**
* If the member is cached, use the cached frame slot and read the value from it. Call
* {@link #doGeneric(VariablesObject, String)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static Object doCached(VariablesObject receiver, String member,
@Cached("member") String cachedMember,
// We cache the member's frame slot for fast-path access
@Cached("receiver.findSlot(member)") FrameSlot slot) throws UnknownIdentifierException {
return doRead(receiver, cachedMember, slot);
}
/**
* The uncached version finds the member's slot and read the value from it.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static Object doGeneric(VariablesObject receiver, String member) throws UnknownIdentifierException {
FrameSlot slot = receiver.findSlot(member);
return doRead(receiver, member, slot);
}
private static Object doRead(VariablesObject receiver, String member, FrameSlot slot) throws UnknownIdentifierException {
if (slot == null) {
throw UnknownIdentifierException.create(member);
}
if (receiver.frame != null) {
return receiver.frame.getValue(slot);
} else {
return SLNull.SINGLETON;
}
}
}
/**
* Write a variable value. Cache the write node by variable name for fast access.
*/
@ExportMessage(name = "writeMember")
static final class WriteMember {
/*
* If the member is cached, use the cached write node and use it to write the value.
* Call {@link #doGeneric(VariablesObject, String, Object)} otherwise.
*/
@Specialization(limit = "LIMIT", guards = {"cachedMember.equals(member)"})
@SuppressWarnings("unused")
static void doCached(VariablesObject receiver, String member, Object value,
@Cached("member") String cachedMember,
// We cache the member's write node for fast-path access
@Cached(value = "receiver.findWriteNode(member)", adopt = false) SLWriteLocalVariableNode writeNode) throws UnknownIdentifierException, UnsupportedMessageException {
doWrite(receiver, cachedMember, writeNode, value);
}
/**
* The uncached version finds the write node and use it to write the value.
*/
@Specialization(replaces = "doCached")
@TruffleBoundary
static void doGeneric(VariablesObject receiver, String member, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
SLWriteLocalVariableNode writeNode = receiver.findWriteNode(member);
doWrite(receiver, member, writeNode, value);
}
private static void doWrite(VariablesObject receiver, String member, SLWriteLocalVariableNode writeNode, Object value) throws UnknownIdentifierException, UnsupportedMessageException {
if (writeNode == null) {
throw UnknownIdentifierException.create(member);
}
if (receiver.frame == null) {
throw UnsupportedMessageException.create();
}
writeNode.executeWrite((VirtualFrame) receiver.frame, value);
}
}
/**
* Get the variables. Cache the array of write nodes that declare variables in the scope(s)
* and the indexes which determine visible variables.
*/
@ExportMessage
@SuppressWarnings("static-method")
Object getMembers(@SuppressWarnings("unused") boolean includeInternal,
@Cached(value = "this.block.getDeclaredLocalVariables()", adopt = false, dimensions = 1, allowUncached = true) SLWriteLocalVariableNode[] writeNodes,
@Cached(value = "this.getVisibleVariablesIndex()", allowUncached = true) int visibleVariablesIndex,
@Cached(value = "this.block.getParentBlockIndex()", allowUncached = true) int parentBlockIndex) {
return new KeysArray(writeNodes, visibleVariablesIndex, parentBlockIndex);
}
int getVisibleVariablesIndex() {
assert node.visibleVariablesIndexOnEnter >= 0;
assert node.visibleVariablesIndexOnExit >= 0;
return nodeEnter ? node.visibleVariablesIndexOnEnter : node.visibleVariablesIndexOnExit;
}
boolean hasWriteNode(String member) {
return findWriteNode(member) != null;
}
FrameSlot findSlot(String member) {
SLWriteLocalVariableNode writeNode = findWriteNode(member);
if (writeNode != null) {
return writeNode.getSlot();
} else {
return null;
}
}
/**
* Find write node, which declares variable of the given name. Search through the variables
* declared in the block and its parents and return the first one that matches.
*
* @param member the variable name
*/
SLWriteLocalVariableNode findWriteNode(String member) {
SLWriteLocalVariableNode[] writeNodes = block.getDeclaredLocalVariables();
int parentBlockIndex = block.getParentBlockIndex();
int index = getVisibleVariablesIndex();
for (int i = 0; i < index; i++) {
SLWriteLocalVariableNode writeNode = writeNodes[i];
if (member.equals(writeNode.getSlot().getIdentifier())) {
return writeNode;
}
}
for (int i = parentBlockIndex; i < writeNodes.length; i++) {
SLWriteLocalVariableNode writeNode = writeNodes[i];
if (member.equals(writeNode.getSlot().getIdentifier())) {
return writeNode;
}
}
return null;
}
}
/**
* Array of visible variables. The variables are based on their declaration write nodes and are
* represented as {@link Key} objects.
*/
@ExportLibrary(InteropLibrary.class)
static final class KeysArray implements TruffleObject {
@CompilationFinal(dimensions = 1) private final SLWriteLocalVariableNode[] writeNodes;
private final int variableIndex;
private final int parentBlockIndex;
/**
* Creates a new array of visible variables.
*
* @param writeNodes all variables declarations in the scope, including parent scopes.
* @param variableIndex index to the variables array, determining variables in the
* inner-most scope (from zero index up to the variableIndex
,
* exclusive).
* @param parentBlockIndex index to the variables array, determining variables in the parent
* block's scope (from parentBlockIndex
to the end of the
* writeNodes
array).
*/
KeysArray(SLWriteLocalVariableNode[] writeNodes, int variableIndex, int parentBlockIndex) {
this.writeNodes = writeNodes;
this.variableIndex = variableIndex;
this.parentBlockIndex = parentBlockIndex;
}
@SuppressWarnings("static-method")
@ExportMessage
boolean hasArrayElements() {
return true;
}
@ExportMessage
long getArraySize() {
// We see all parent's variables (writeNodes.length - parentBlockIndex) plus the
// variables in the inner-most scope visible by the current node (variableIndex).
return writeNodes.length - parentBlockIndex + variableIndex;
}
@ExportMessage
boolean isArrayElementReadable(long index) {
return index >= 0 && index < (writeNodes.length - parentBlockIndex + variableIndex);
}
@ExportMessage
Object readArrayElement(long index) throws InvalidArrayIndexException {
if (!isArrayElementReadable(index)) {
throw InvalidArrayIndexException.create(index);
}
if (index < variableIndex) {
// if we're in the inner-most scope, it's simply the variable on the index
return new Key(writeNodes[(int) index]);
} else {
// else it's a variable declared in the parent's scope, we start at parentBlockIndex
return new Key(writeNodes[(int) index - variableIndex + parentBlockIndex]);
}
}
}
/**
* Representation of a variable based on a {@link SLWriteLocalVariableNode write node} that
* declares the variable. It provides the variable name as a {@link Key#asString() string} and
* the name node associated with the variable's write node as a {@link Key#getSourceLocation()
* source location}.
*/
@ExportLibrary(InteropLibrary.class)
static final class Key implements TruffleObject {
private final SLWriteLocalVariableNode writeNode;
Key(SLWriteLocalVariableNode writeNode) {
this.writeNode = writeNode;
}
@ExportMessage
@SuppressWarnings("static-method")
boolean isString() {
return true;
}
@ExportMessage
@TruffleBoundary
String asString() {
// FrameSlot's identifier object is not safe to convert to String on fast-path.
return writeNode.getSlot().getIdentifier().toString();
}
@ExportMessage
boolean hasSourceLocation() {
return writeNode.getNameNode().getSourceCharIndex() >= 0;
}
@ExportMessage
@TruffleBoundary
SourceSection getSourceLocation() throws UnsupportedMessageException {
if (!hasSourceLocation()) {
throw UnsupportedMessageException.create();
}
SLExpressionNode nameNode = writeNode.getNameNode();
return writeNode.getRootNode().getSourceSection().getSource().createSection(nameNode.getSourceCharIndex(), nameNode.getSourceLength());
}
}
}