From 1e95b0202cf50c4f5dbec16bdca2007cb45379d2 Mon Sep 17 00:00:00 2001 From: stepan <stepan.sindelar@oracle.com> Date: Tue, 25 Oct 2016 15:33:44 +0200 Subject: [PATCH] Replacement executes the target first to specialize The ReplacementBlockNode executes RHS and the target and then executes ReplacementNode, which can specialize on target value. In the default case ReplacementNode creates nodes that handle replacement in the same way as before: SpecialReplacementNode and GenericReplacementNode. --- .../r/nodes/control/ReplacementBlockNode.java | 244 +------------ .../r/nodes/control/ReplacementNode.java | 325 ++++++++++++++++++ 2 files changed, 342 insertions(+), 227 deletions(-) create mode 100644 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java 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 49ca3331fd..cab106e8d6 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 0000000000..5175306e09 --- /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; + } + } +} -- GitLab