diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java index 49ca3331fd2a9d03770957d1190e14d9a9b980e8..cab106e8d6d968585878eadc7fc7d24ffc6cfe7b 100644 --- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java @@ -25,29 +25,22 @@ package com.oracle.truffle.r.nodes.control; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; -import com.oracle.truffle.api.nodes.ExplodeLoop; import com.oracle.truffle.api.nodes.Node; import com.oracle.truffle.api.source.SourceSection; import com.oracle.truffle.r.nodes.RASTUtils; import com.oracle.truffle.r.nodes.access.RemoveAndAnswerNode; import com.oracle.truffle.r.nodes.access.WriteVariableNode; import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode; -import com.oracle.truffle.r.nodes.function.RCallSpecialNode; -import com.oracle.truffle.r.nodes.function.RCallSpecialNode.RecursiveSpecialBailout; import com.oracle.truffle.r.nodes.function.visibility.SetVisibilityNode; -import com.oracle.truffle.r.nodes.unary.GetNonSharedNodeGen; import com.oracle.truffle.r.runtime.ArgumentsSignature; import com.oracle.truffle.r.runtime.RError; -import com.oracle.truffle.r.runtime.builtins.RSpecialFactory.FullCallNeededException; -import com.oracle.truffle.r.runtime.context.RContext; -import com.oracle.truffle.r.runtime.data.RDataFactory; import com.oracle.truffle.r.runtime.data.RLanguage; import com.oracle.truffle.r.runtime.data.RNull; -import com.oracle.truffle.r.runtime.nodes.RNode; import com.oracle.truffle.r.runtime.nodes.RSourceSectionNode; import com.oracle.truffle.r.runtime.nodes.RSyntaxCall; import com.oracle.truffle.r.runtime.nodes.RSyntaxConstant; @@ -62,17 +55,10 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode; * fallback to generic implementation. */ public final class ReplacementBlockNode extends RSourceSectionNode implements RSyntaxNode, RSyntaxCall { - /** - * Used to initialize {@link #tempNamesStartIndex}. When we are processing a replacement AST, we - * set this value so that newly created replacements within the original replacement have - * different temporary variable names. Example {@code x[x[1]<-2]<-3}. - */ - private static int tempNamesCount = 0; - /** - * Should be one of {@link ReplacementNode} or {@link ReplacementNodeSpecial}. - */ - @Child private ReplacementBase replacementNode; + private static AtomicInteger rhsNameIndex = new AtomicInteger(-1); + + @Child private ReplacementNode replacementNode; @Child private WriteVariableNode storeRhs; @Child private RemoveAndAnswerNode removeRhs; @@ -81,15 +67,15 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS private final String operator; private final RSyntaxNode lhs; private final boolean isSuper; - private final int tempNamesStartIndex; + private final String rhsName; public ReplacementBlockNode(SourceSection src, String operator, RSyntaxNode lhs, RSyntaxNode rhs, boolean isSuper) { super(src); assert "<-".equals(operator) || "<<-".equals(operator) || "=".equals(operator); assert lhs != null && rhs != null; - tempNamesStartIndex = tempNamesCount; - storeRhs = WriteVariableNode.createAnonymous(getRhsName(), rhs.asRNode(), WriteVariableNode.Mode.INVISIBLE); - removeRhs = RemoveAndAnswerNode.create(getRhsName()); + rhsName = "*rhs*" + rhsNameIndex.incrementAndGet(); + storeRhs = WriteVariableNode.createAnonymous(rhsName, rhs.asRNode(), WriteVariableNode.Mode.INVISIBLE); + removeRhs = RemoveAndAnswerNode.create(rhsName); this.operator = operator; this.lhs = lhs; this.isSuper = isSuper; @@ -100,7 +86,8 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS storeRhs.execute(frame); getReplacementNode().execute(frame); try { - return removeRhs.execute(frame); + Object result = removeRhs.execute(frame); + return result; } finally { visibility.execute(frame, false); } @@ -135,23 +122,15 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS return new RSyntaxElement[]{lhs, storeRhs.getRhs().asRSyntaxNode()}; } - private RNode getReplacementNode() { + private ReplacementNode getReplacementNode() { if (replacementNode == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); - replacementNode = insert(createReplacementNode(true)); + replacementNode = insert(createReplacementNode()); } return replacementNode; } - private String getRhsName() { - return "*tmpr*" + (tempNamesStartIndex - 1); - } - - private RNode createReplacementNodeWithoutSpecials() { - return createReplacementNode(false); - } - - private ReplacementBase createReplacementNode(boolean useSpecials) { + private ReplacementNode createReplacementNode() { CompilerAsserts.neverPartOfCompilation(); /* @@ -160,7 +139,6 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS */ List<RSyntaxCall> calls = new ArrayList<>(); RSyntaxElement current = lhs; - boolean onlySpecials = true; while (!(current instanceof RSyntaxLookup)) { if (!(current instanceof RSyntaxCall)) { if (current instanceof RSyntaxConstant && ((RSyntaxConstant) current).getValue() == RNull.instance) { @@ -176,69 +154,11 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS if (call.getSyntaxArguments().length == 0 || !(syntaxLHS instanceof RSyntaxLookup || isNamespaceLookupCall(syntaxLHS))) { throw RError.error(this, RError.Message.INVALID_NULL_LHS); } - onlySpecials &= call instanceof RCallSpecialNode; current = call.getSyntaxArguments()[0]; } RSyntaxLookup variable = (RSyntaxLookup) current; - SourceSection source = getSourceSection(); - - // Note: if specials are turned off in FastR, onlySpecials will never be true - if (onlySpecials && useSpecials && !isSuper) { - return createSpecialReplacement(source, calls, variable); - } - - return createGenericReplacement(source, calls, variable); - } - - /** - * Creates a replacement that consists only of {@link RCallSpecialNode} calls. - */ - private ReplacementBase createSpecialReplacement(SourceSection source, List<RSyntaxCall> calls, RSyntaxLookup variable) { - RNode extractFunc = createReplacementForVariableUsing(variable, isSuper); - for (int i = calls.size() - 1; i >= 1; i--) { - extractFunc = createSpecialFunctionQuery(extractFunc.asRSyntaxNode(), calls.get(i)); - } - RNode updateFunc = createFunctionUpdate(source, extractFunc.asRSyntaxNode(), ReadVariableNode.create(getRhsName()), calls.get(0)); - assert updateFunc instanceof RCallSpecialNode : "should be only specials"; - return new ReplacementNodeSpecial((RCallSpecialNode) updateFunc); - } - - /** - * When there are more than two function calls in LHS, then we save some function calls by - * saving the intermediate results into temporary variables and reusing them. - */ - private ReplacementBase createGenericReplacement(SourceSection source, List<RSyntaxCall> calls, RSyntaxLookup variable) { - List<RNode> instructions = new ArrayList<>(); - int tempNamesIndex = tempNamesStartIndex; - tempNamesCount += calls.size() + 1; - - /* - * Create the calls that extract inner components - only needed for complex replacements - * like "a(b(x)) <- z" (where we would extract "b(x)"). - */ - for (int i = calls.size() - 1; i >= 1; i--) { - ReadVariableNode newLhs = ReadVariableNode.create("*tmp*" + (tempNamesIndex + i + 1)); - RNode update = createSpecialFunctionQuery(newLhs, calls.get(i)); - instructions.add(WriteVariableNode.createAnonymous("*tmp*" + (tempNamesIndex + i), update, WriteVariableNode.Mode.INVISIBLE)); - } - /* - * Create the update calls, for "a(b(x)) <- z", this would be `a<-` and `b<-`. - */ - for (int i = 0; i < calls.size(); i++) { - RNode update = createFunctionUpdate(source, ReadVariableNode.create("*tmp*" + (tempNamesIndex + i + 1)), ReadVariableNode.create("*tmpr*" + (tempNamesIndex + i - 1)), - calls.get(i)); - if (i < calls.size() - 1) { - instructions.add(WriteVariableNode.createAnonymous("*tmpr*" + (tempNamesIndex + i), update, WriteVariableNode.Mode.INVISIBLE)); - } else { - instructions.add(WriteVariableNode.createAnonymous(variable.getIdentifier(), update, WriteVariableNode.Mode.REGULAR, isSuper)); - } - } - - ReadVariableNode variableValue = createReplacementForVariableUsing(variable, isSuper); - ReplacementNode newReplacementNode = new ReplacementNode(variableValue, "*tmp*" + (tempNamesIndex + calls.size()), instructions); - - tempNamesCount -= calls.size() + 1; - return newReplacementNode; + ReadVariableNode varRead = createReplacementForVariableUsing(variable, isSuper); + return ReplacementNodeGen.create(getSourceSection(), calls, rhsName, variable.getIdentifier(), isSuper, varRead); } private static ReadVariableNode createReplacementForVariableUsing(RSyntaxLookup var, boolean isSuper) { @@ -269,151 +189,21 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS return false; } - /** - * Creates a call that looks like {@code fun} but has the first argument replaced with - * {@code newLhs}. - */ - private static RNode createSpecialFunctionQuery(RSyntaxNode newLhs, RSyntaxCall fun) { - RSyntaxElement[] arguments = fun.getSyntaxArguments(); - - RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length]; - for (int i = 0; i < arguments.length; i++) { - argNodes[i] = i == 0 ? newLhs : process(arguments[i]); - } - - return RCallSpecialNode.createCallInReplace(fun.getSourceSection(), process(fun.getSyntaxLHS()).asRNode(), fun.getSyntaxSignature(), argNodes).asRNode(); - } - - /** - * Creates a call that looks like {@code fun}, but has its first argument replaced with - * {@code newLhs}, its target turned into an update function ("foo<-"), with the given value - * added to the arguments list. - */ - private static RNode createFunctionUpdate(SourceSection source, RSyntaxNode newLhs, RSyntaxNode rhs, RSyntaxCall fun) { - RSyntaxElement[] arguments = fun.getSyntaxArguments(); - - ArgumentsSignature signature = fun.getSyntaxSignature(); - RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length + 1]; - String[] names = new String[argNodes.length]; - for (int i = 0; i < arguments.length; i++) { - names[i] = signature.getName(i); - argNodes[i] = i == 0 ? newLhs : process(arguments[i]); - } - argNodes[argNodes.length - 1] = rhs; - names[argNodes.length - 1] = "value"; - - RSyntaxElement syntaxLHS = fun.getSyntaxLHS(); - RSyntaxNode newSyntaxLHS; - if (syntaxLHS instanceof RSyntaxLookup) { - RSyntaxLookup lookupLHS = (RSyntaxLookup) syntaxLHS; - String symbol = lookupLHS.getIdentifier(); - if ("slot".equals(symbol) || "@".equals(symbol)) { - // this is pretty gross, but at this point seems like the only way to get setClass - // to work properly - argNodes[0] = GetNonSharedNodeGen.create(argNodes[0].asRNode()); - } - newSyntaxLHS = lookup(lookupLHS.getSourceSection(), symbol + "<-", true); - } else { - // data types (and lengths) are verified in isNamespaceLookupCall - RSyntaxCall callLHS = (RSyntaxCall) syntaxLHS; - RSyntaxElement[] oldArgs = callLHS.getSyntaxArguments(); - RSyntaxNode[] newArgs = new RSyntaxNode[2]; - newArgs[0] = (RSyntaxNode) oldArgs[0]; - newArgs[1] = lookup(oldArgs[1].getSourceSection(), ((RSyntaxLookup) oldArgs[1]).getIdentifier() + "<-", true); - newSyntaxLHS = RCallSpecialNode.createCall(callLHS.getSourceSection(), ((RSyntaxNode) callLHS.getSyntaxLHS()).asRNode(), callLHS.getSyntaxSignature(), newArgs); - } - return RCallSpecialNode.createCall(source, newSyntaxLHS.asRNode(), ArgumentsSignature.get(names), argNodes).asRNode(); - } - - private static RSyntaxNode process(RSyntaxElement original) { - return RContext.getASTBuilder().process(original); - } - - private static RSyntaxNode lookup(SourceSection source, String symbol, boolean functionLookup) { - return RContext.getASTBuilder().lookup(source, symbol, functionLookup); - } - /* * Encapsulates check for the specific structure of replacements, to display the replacement * instead of the "internal" form (with *tmp*, etc.) of the update call. */ public static RLanguage getRLanguage(RLanguage language) { + // TODO: fix me! RSyntaxNode sn = (RSyntaxNode) language.getRep(); Node parent = RASTUtils.unwrapParent(sn.asNode()); if (parent instanceof WriteVariableNode) { WriteVariableNode wvn = (WriteVariableNode) parent; - if (wvn.getParent() instanceof ReplacementBase) { - // the parent of Replacement interface is always ReplacementBlockNode - return RDataFactory.createLanguage((RNode) wvn.getParent().getParent()); - } + return ReplacementNode.getLanguage(wvn); } - return null; } - /** - * Base class for nodes implementing the actual replacement. - */ - private abstract static class ReplacementBase extends RNode { - } - - /** - * Replacement that is made of only special calls, if one of the special calls falls back to - * full version, the replacement also falls back to {@link ReplacementNode}. - */ - private static final class ReplacementNodeSpecial extends ReplacementBase { - - @Child private RCallSpecialNode replaceCall; - - ReplacementNodeSpecial(RCallSpecialNode replaceCall) { - this.replaceCall = replaceCall; - this.replaceCall.setPropagateFullCallNeededException(true); - } - - @Override - public Object execute(VirtualFrame frame) { - try { - // Note: the very last call is the actual assignment, e.g. [[<-, if this call's - // argument is shared, it bails out. Moreover, if that call's argument is not - // shared, it could not be extracted from a shared container (list), so we should be - // OK with not calling any other update function and just update the value directly. - replaceCall.execute(frame); - } catch (FullCallNeededException | RecursiveSpecialBailout e) { - assert getParent() instanceof ReplacementBlockNode; - RNode newReplacement = ((ReplacementBlockNode) getParent()).createReplacementNodeWithoutSpecials(); - return replace(newReplacement).execute(frame); - } - return null; - } - } - - /** - * Holds the sequence of nodes created for R's replacement assignment. - */ - public static final class ReplacementNode extends ReplacementBase { - - @Child private WriteVariableNode storeValue; - @Children private final RNode[] updates; - @Child private RemoveAndAnswerNode removeTemp; - - public ReplacementNode(RNode variable, String tmpSymbol, List<RNode> updates) { - this.storeValue = WriteVariableNode.createAnonymous(tmpSymbol, variable, WriteVariableNode.Mode.INVISIBLE); - this.updates = updates.toArray(new RNode[updates.size()]); - this.removeTemp = RemoveAndAnswerNode.create(tmpSymbol); - } - - @Override - @ExplodeLoop - public Object execute(VirtualFrame frame) { - storeValue.execute(frame); - for (RNode update : updates) { - update.execute(frame); - } - removeTemp.execute(frame); - return null; - } - } - /** * Used by the parser for assignments that miss a left hand side. This node will raise an error * once executed. diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java new file mode 100644 index 0000000000000000000000000000000000000000..5175306e093570627ebb05a5004a4f0607f46953 --- /dev/null +++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.truffle.r.nodes.control; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.truffle.api.CompilerAsserts; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.NodeChild; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.dsl.TypeSystemReference; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.ExplodeLoop; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.source.SourceSection; +import com.oracle.truffle.r.nodes.EmptyTypeSystemFlatLayout; +import com.oracle.truffle.r.nodes.access.RemoveAndAnswerNode; +import com.oracle.truffle.r.nodes.access.WriteVariableNode; +import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode; +import com.oracle.truffle.r.nodes.function.RCallSpecialNode; +import com.oracle.truffle.r.nodes.function.RCallSpecialNode.RecursiveSpecialBailout; +import com.oracle.truffle.r.nodes.unary.GetNonSharedNodeGen; +import com.oracle.truffle.r.runtime.ArgumentsSignature; +import com.oracle.truffle.r.runtime.builtins.RSpecialFactory.FullCallNeededException; +import com.oracle.truffle.r.runtime.context.RContext; +import com.oracle.truffle.r.runtime.data.RDataFactory; +import com.oracle.truffle.r.runtime.data.RLanguage; +import com.oracle.truffle.r.runtime.nodes.RNode; +import com.oracle.truffle.r.runtime.nodes.RSyntaxCall; +import com.oracle.truffle.r.runtime.nodes.RSyntaxElement; +import com.oracle.truffle.r.runtime.nodes.RSyntaxLookup; +import com.oracle.truffle.r.runtime.nodes.RSyntaxNode; + +@NodeChild(value = "target") +@TypeSystemReference(EmptyTypeSystemFlatLayout.class) +abstract class ReplacementNode extends RNode { + + /** + * Used to initialize {@link #tempNamesStartIndex}. When we are processing a replacement AST, we + * set this value so that newly created replacements within the original replacement have + * different temporary variable names. Example {@code x[x[1]<-2]<-3}. + */ + public static int tempNamesCount = 0; + + private final int tempNamesStartIndex; + private final SourceSection source; + private final List<RSyntaxCall> calls; + private final String targetVarName; + private final String rhsVarName; + private final boolean isSuper; + + ReplacementNode(SourceSection source, List<RSyntaxCall> calls, String rhsVarName, String targetVarName, boolean isSuper) { + this.source = source; + this.calls = calls; + this.rhsVarName = rhsVarName; + this.targetVarName = targetVarName; + this.isSuper = isSuper; + tempNamesStartIndex = tempNamesCount; + } + + @Specialization + protected Object doRObject(VirtualFrame frame, Object target, + @Cached("createTargetTmpWrite()") WriteVariableNode targetTmpWrite, + @Cached("createTargetTmpRemove()") RemoveAndAnswerNode targetTmpRemove, + @Cached("createTargetWrite()") WriteVariableNode targetWrite, + @Cached("createReplacementNode()") ReplacementBase replacement) { + targetTmpWrite.execute(frame, target); + replacement.execute(frame); + targetWrite.execute(frame, targetTmpRemove.execute(frame)); + return null; + } + + protected final WriteVariableNode createTargetTmpWrite() { + return WriteVariableNode.createAnonymous(getTargetTmpName(), null, WriteVariableNode.Mode.INVISIBLE); + } + + protected final WriteVariableNode createTargetWrite() { + return WriteVariableNode.createAnonymous(targetVarName, null, WriteVariableNode.Mode.INVISIBLE, isSuper); + } + + protected final RemoveAndAnswerNode createTargetTmpRemove() { + return RemoveAndAnswerNode.create(getTargetTmpName()); + } + + protected final ReplacementBase createReplacementNode() { + return createReplacementNode(true); + } + + private RNode createReplacementNodeWithoutSpecials() { + return createReplacementNode(false); + } + + private ReplacementBase createReplacementNode(boolean useSpecials) { + CompilerAsserts.neverPartOfCompilation(); + // Note: if specials are turned off in FastR, onlySpecials will never be true + boolean createSpecial = hasOnlySpecialCalls() && useSpecials && !isSuper; + return createSpecial ? createSpecialReplacement(source, calls) : createGenericReplacement(source, calls); + } + + private String getTargetTmpName() { + return "*tmp*" + tempNamesStartIndex; + } + + private boolean hasOnlySpecialCalls() { + for (int i = 0; i < calls.size(); i++) { + if (!(calls.get(i) instanceof RCallSpecialNode)) { + return false; + } + } + return true; + } + + /** + * Creates a replacement that consists only of {@link RCallSpecialNode} calls. + */ + private SpecialReplacementNode createSpecialReplacement(SourceSection source, List<RSyntaxCall> calls) { + RNode extractFunc = ReadVariableNode.create(getTargetTmpName()); + for (int i = calls.size() - 1; i >= 1; i--) { + extractFunc = createSpecialFunctionQuery(extractFunc.asRSyntaxNode(), calls.get(i)); + } + RNode updateFunc = createFunctionUpdate(source, extractFunc.asRSyntaxNode(), ReadVariableNode.create(rhsVarName), calls.get(0)); + assert updateFunc instanceof RCallSpecialNode : "should be only specials"; + return new SpecialReplacementNode((RCallSpecialNode) updateFunc); + } + + /** + * When there are more than two function calls in LHS, then we save some function calls by + * saving the intermediate results into temporary variables and reusing them. + */ + private GenericReplacementNode createGenericReplacement(SourceSection source, List<RSyntaxCall> calls) { + List<RNode> instructions = new ArrayList<>(); + tempNamesCount += tempNamesStartIndex + calls.size() + 1; + + /* + * Create the calls that extract inner components - only needed for complex replacements + * like "a(b(x)) <- z" (where we would extract "b(x)"). The extracted values are saved into + * temporary variables *tmp*{index} indexed from tempNamesIndex to (tempNamesIndex + + * calls.size()-1), the first such temporary variable holds the "target" of the replacement, + * 'x' in our example (the assignment from 'x' is not done in this loop). + */ + for (int i = calls.size() - 1, tmpIndex = 0; i >= 1; i--, tmpIndex++) { + ReadVariableNode newLhs = ReadVariableNode.create("*tmp*" + (tempNamesStartIndex + tmpIndex)); + RNode update = createSpecialFunctionQuery(newLhs, calls.get(i)); + instructions.add(WriteVariableNode.createAnonymous("*tmp*" + (tempNamesStartIndex + tmpIndex + 1), update, WriteVariableNode.Mode.INVISIBLE)); + } + /* + * Create the update calls, for "a(b(x)) <- z", this would be `a<-` and `b<-`, the + * intermediate results are stored to temporary variables *tmpr*{index}. + */ + for (int i = 0; i < calls.size(); i++) { + int tmpIndex = tempNamesStartIndex + calls.size() - i - 1; + String tmprName = i == 0 ? rhsVarName : "*tmpr*" + (tempNamesStartIndex + i - 1); + RNode update = createFunctionUpdate(source, ReadVariableNode.create("*tmp*" + tmpIndex), ReadVariableNode.create(tmprName), calls.get(i)); + if (i < calls.size() - 1) { + instructions.add(WriteVariableNode.createAnonymous("*tmpr*" + (tempNamesStartIndex + i), update, WriteVariableNode.Mode.INVISIBLE)); + } else { + instructions.add(WriteVariableNode.createAnonymous(getTargetTmpName(), update, WriteVariableNode.Mode.REGULAR)); + } + } + + GenericReplacementNode newReplacementNode = new GenericReplacementNode(instructions); + tempNamesCount -= calls.size() + 1; + return newReplacementNode; + } + + /** + * Creates a call that looks like {@code fun} but has the first argument replaced with + * {@code newLhs}. + */ + private static RNode createSpecialFunctionQuery(RSyntaxNode newLhs, RSyntaxCall fun) { + RSyntaxElement[] arguments = fun.getSyntaxArguments(); + + RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length]; + for (int i = 0; i < arguments.length; i++) { + argNodes[i] = i == 0 ? newLhs : process(arguments[i]); + } + + return RCallSpecialNode.createCallInReplace(fun.getSourceSection(), process(fun.getSyntaxLHS()).asRNode(), fun.getSyntaxSignature(), argNodes).asRNode(); + } + + /** + * Creates a call that looks like {@code fun}, but has its first argument replaced with + * {@code newLhs}, its target turned into an update function ("foo<-"), with the given value + * added to the arguments list. + */ + private static RNode createFunctionUpdate(SourceSection source, RSyntaxNode newLhs, RSyntaxNode rhs, RSyntaxCall fun) { + RSyntaxElement[] arguments = fun.getSyntaxArguments(); + + ArgumentsSignature signature = fun.getSyntaxSignature(); + RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length + 1]; + String[] names = new String[argNodes.length]; + for (int i = 0; i < arguments.length; i++) { + names[i] = signature.getName(i); + argNodes[i] = i == 0 ? newLhs : process(arguments[i]); + } + argNodes[argNodes.length - 1] = rhs; + names[argNodes.length - 1] = "value"; + + RSyntaxElement syntaxLHS = fun.getSyntaxLHS(); + RSyntaxNode newSyntaxLHS; + if (syntaxLHS instanceof RSyntaxLookup) { + RSyntaxLookup lookupLHS = (RSyntaxLookup) syntaxLHS; + String symbol = lookupLHS.getIdentifier(); + if ("slot".equals(symbol) || "@".equals(symbol)) { + // this is pretty gross, but at this point seems like the only way to get setClass + // to work properly + argNodes[0] = GetNonSharedNodeGen.create(argNodes[0].asRNode()); + } + newSyntaxLHS = lookup(lookupLHS.getSourceSection(), symbol + "<-", true); + } else { + // data types (and lengths) are verified in isNamespaceLookupCall + RSyntaxCall callLHS = (RSyntaxCall) syntaxLHS; + RSyntaxElement[] oldArgs = callLHS.getSyntaxArguments(); + RSyntaxNode[] newArgs = new RSyntaxNode[2]; + newArgs[0] = (RSyntaxNode) oldArgs[0]; + newArgs[1] = lookup(oldArgs[1].getSourceSection(), ((RSyntaxLookup) oldArgs[1]).getIdentifier() + "<-", true); + newSyntaxLHS = RCallSpecialNode.createCall(callLHS.getSourceSection(), ((RSyntaxNode) callLHS.getSyntaxLHS()).asRNode(), callLHS.getSyntaxSignature(), newArgs); + } + return RCallSpecialNode.createCall(source, newSyntaxLHS.asRNode(), ArgumentsSignature.get(names), argNodes).asRNode(); + } + + private static RSyntaxNode process(RSyntaxElement original) { + return RContext.getASTBuilder().process(original); + } + + private static RSyntaxNode lookup(SourceSection source, String symbol, boolean functionLookup) { + return RContext.getASTBuilder().lookup(source, symbol, functionLookup); + } + + static RLanguage getLanguage(WriteVariableNode wvn) { + Node parent = wvn.getParent(); + if (parent instanceof ReplacementBase) { + Node replacementBlock = ((ReplacementBase) parent).getReplacementNodeParent().getParent(); + assert replacementBlock instanceof ReplacementBlockNode; + return RDataFactory.createLanguage((RNode) replacementBlock); + } + return null; + } + + /** + * Base class for nodes implementing the actual replacement. + */ + protected abstract static class ReplacementBase extends RNode { + protected final ReplacementNode getReplacementNodeParent() { + // Note: new DSL puts another node in between ReplacementBase instance and + // ReplacementNode, to be flexible we traverse the parents until we reach it + Node current = this; + do { + current = current.getParent(); + } while (!(current instanceof ReplacementNode)); + return (ReplacementNode) current; + } + } + + /** + * Replacement that is made of only special calls, if one of the special calls falls back to + * full version, the replacement also falls back to {@link ReplacementNode}. + */ + private static final class SpecialReplacementNode extends ReplacementBase { + + @Child private RCallSpecialNode replaceCall; + + SpecialReplacementNode(RCallSpecialNode replaceCall) { + this.replaceCall = replaceCall; + this.replaceCall.setPropagateFullCallNeededException(true); + } + + @Override + public Object execute(VirtualFrame frame) { + try { + // Note: the very last call is the actual assignment, e.g. [[<-, if this call's + // argument is shared, it bails out. Moreover, if that call's argument is not + // shared, it could not be extracted from a shared container (list), so we should be + // OK with not calling any other update function and just update the value directly. + replaceCall.execute(frame); + } catch (FullCallNeededException | RecursiveSpecialBailout e) { + RNode newReplacement = getReplacementNodeParent().createReplacementNodeWithoutSpecials(); + return replace(newReplacement).execute(frame); + } + return null; + } + } + + /** + * Holds the sequence of nodes created for R's replacement assignment. + */ + private static final class GenericReplacementNode extends ReplacementBase { + @Children private final RNode[] updates; + + GenericReplacementNode(List<RNode> updates) { + this.updates = updates.toArray(new RNode[updates.size()]); + } + + @Override + @ExplodeLoop + public Object execute(VirtualFrame frame) { + for (RNode update : updates) { + update.execute(frame); + } + return null; + } + } +}