package com.oracle.truffle.js.test.instrumentation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.Set;
import org.graalvm.polyglot.Context;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.StandardTags.StatementTag;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.GlobalConstantNode;
import com.oracle.truffle.js.nodes.access.GlobalPropertyNode;
import com.oracle.truffle.js.nodes.access.JSConstantNode;
import com.oracle.truffle.js.nodes.access.JSReadFrameSlotNode;
import com.oracle.truffle.js.nodes.access.JSTargetableNode;
import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode;
import com.oracle.truffle.js.nodes.access.PropertyNode;
import com.oracle.truffle.js.nodes.access.ReadElementNode;
import com.oracle.truffle.js.nodes.access.WriteElementNode;
import com.oracle.truffle.js.nodes.access.WritePropertyNode;
import com.oracle.truffle.js.nodes.binary.JSAddConstantLeftNumberNode;
import com.oracle.truffle.js.nodes.binary.JSAddConstantRightNumberNode;
import com.oracle.truffle.js.nodes.binary.JSAddNode;
import com.oracle.truffle.js.nodes.binary.JSBitwiseAndConstantNode;
import com.oracle.truffle.js.nodes.binary.JSBitwiseOrConstantNode;
import com.oracle.truffle.js.nodes.binary.JSBitwiseXorConstantNode;
import com.oracle.truffle.js.nodes.binary.JSLeftShiftConstantNode;
import com.oracle.truffle.js.nodes.binary.JSRightShiftConstantNode;
import com.oracle.truffle.js.nodes.binary.JSUnsignedRightShiftConstantNode;
import com.oracle.truffle.js.nodes.control.ForNode;
import com.oracle.truffle.js.nodes.control.IfNode;
import com.oracle.truffle.js.nodes.control.WhileNode;
import com.oracle.truffle.js.nodes.function.IterationScopeNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode.InvokeNode;
import com.oracle.truffle.js.nodes.function.JSNewNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.BinaryOperationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ControlFlowRootTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.FunctionCallTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ObjectAllocationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadPropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.UnaryOperationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WriteElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WritePropertyTag;
import com.oracle.truffle.js.nodes.unary.JSNotNode;
import com.oracle.truffle.js.nodes.unary.VoidNode;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.objects.Undefined;
public class MaterializedNodes {
private JSContext jsContext;
private Context polyContext;
@BeforeClass
public static void initClass() {
dummyWithSourceSection.setSourceSection(dummySourceSection);
}
@Before
public void init() {
polyContext = TestUtil.newContextBuilder().build();
polyContext.enter();
JSRealm jsRealm = JavaScriptLanguage.getJSRealm(polyContext);
this.jsContext = jsRealm.getContext();
}
@After
public void dispose() {
polyContext.leave();
polyContext.close();
}
@Test
public void functionNode() {
JavaScriptNode[] args = new JavaScriptNode[]{};
JSFunctionCallNode c = JSFunctionCallNode.createCall(JSConstantNode.createUndefined(), null, args, false, false);
c.setSourceSection(Source.newBuilder(JavaScriptLanguage.ID, "", "").build().createUnavailableSection());
Set<Class<? extends Tag>> s = new HashSet<>();
s.add(FunctionCallTag.class);
c.addStatementTag();
JSFunctionCallNode m = (JSFunctionCallNode) c.materializeInstrumentableNodes(s);
assertTrue(m.hasTag(StatementTag.class));
assertTrue(!((InstrumentableNode) m.getTarget()).hasTag(StatementTag.class));
}
@Test
public void materializeMulti() {
JSTargetableNode undef = GlobalConstantNode.createGlobalConstant(jsContext, "test", Undefined.instance);
JavaScriptNode[] args = new JavaScriptNode[]{};
JSFunctionCallNode c = JSFunctionCallNode.createInvoke(undef, args, false, false);
c.setSourceSection(Source.newBuilder(JavaScriptLanguage.ID, "", "").build().createUnavailableSection());
undef.setSourceSection(Source.newBuilder(JavaScriptLanguage.ID, "", "").build().createUnavailableSection());
Set<Class<? extends Tag>> s = new HashSet<>();
s.add(FunctionCallTag.class);
c.addStatementTag();
JSFunctionCallNode m = (JSFunctionCallNode) c.materializeInstrumentableNodes(s);
m = (JSFunctionCallNode) m.materializeInstrumentableNodes(s);
InvokeNode i = (InvokeNode) m;
assertTrue(i.getFunctionTargetNode().getSourceSection() != null);
assertTrue(m.hasTag(StatementTag.class));
assertTrue(!((InstrumentableNode) m.getTarget()).hasTag(StatementTag.class));
assertTrue(m.hasSourceSection());
}
@Test
public void desugaredAddNode() {
JavaScriptNode optimized = JSAddNode.create(dummyDouble, dummyInt);
optimized.setSourceSection(Source.newBuilder(JavaScriptLanguage.ID, "", "").build().createUnavailableSection());
Set<Class<? extends Tag>> s = new HashSet<>();
s.add(BinaryOperationTag.class);
optimized.addStatementTag();
JavaScriptNode desugared = (JavaScriptNode) optimized.materializeInstrumentableNodes(s);
JavaScriptNode cloned = JavaScriptNode.cloneUninitialized(desugared, null);
assertTrue(cloned.getClass() == desugared.getClass());
}
private static class DummyConstantNode extends JavaScriptNode {
private final Object value;
DummyConstantNode(Object value) {
this.value = value;
}
@Override
public Object execute(VirtualFrame frame) {
return value;
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return new DummyConstantNode(value);
}
}
private static final JSConstantNode dummy = JSConstantNode.createUndefined();
private static final JSConstantNode dummyWithSourceSection = JSConstantNode.createUndefined();
private static final JavaScriptNode dummyInt = JSConstantNode.createInt(42);
private static final JavaScriptNode dummyDouble = JSConstantNode.createDouble(42.42);
private static final JavaScriptNode dummyJSNode = new DummyConstantNode(42);
private static final SourceSection dummySourceSection = Source.newBuilder(JavaScriptLanguage.ID, "", "").build().createUnavailableSection();
private JSContext getDummyCx() {
assert jsContext != null;
return jsContext;
}
@Test
public void materializeTwiceGlobalProperty() {
JavaScriptNode prop = GlobalPropertyNode.createPropertyNode(getDummyCx(), "foo");
assertNotMaterializedTwice(prop, ReadPropertyTag.class);
}
@Test
public void materializeTwicePropertyRead() {
PropertyNode prop = PropertyNode.createProperty(getDummyCx(), dummy, "foo");
assertNotMaterializedTwice(prop, ReadPropertyTag.class);
}
@Test
public void materializeTwiceElementRead() {
ReadElementNode elem = ReadElementNode.create(dummy, dummy, getDummyCx());
assertNotMaterializedTwice(elem, ReadElementTag.class);
}
@Test
public void materializeTwiceElementReadIndexAndTargetWithSourceSection() {
ReadElementNode elem = ReadElementNode.create(dummyWithSourceSection, dummyWithSourceSection, getDummyCx());
assertNotMaterializedTwice(elem, ReadElementTag.class);
}
@Test
public void materializeTwiceElementWrite() {
WriteElementNode elem = WriteElementNode.create(dummy, dummy, dummy, getDummyCx(), false);
assertNotMaterializedTwice(elem, WriteElementTag.class);
}
@Test
public void materializeTwicePropertyWrite() {
WritePropertyNode prop = WritePropertyNode.create(dummy, "foo", dummy, getDummyCx(), false);
assertNotMaterializedTwice(prop, WritePropertyTag.class);
}
@Test
public void materializeTwiceAddLeft() {
JavaScriptNode add = JSAddNode.create(dummyJSNode, dummyInt);
assert add instanceof JSAddConstantRightNumberNode;
assertNotMaterializedTwice(add, BinaryOperationTag.class);
}
@Test
public void materializeTwiceAddRight() {
JavaScriptNode add = JSAddNode.create(dummyDouble, dummyJSNode);
assert add instanceof JSAddConstantLeftNumberNode;
assertNotMaterializedTwice(add, BinaryOperationTag.class);
}
@Test
public void materializeTwiceAnd() {
JavaScriptNode node = JSBitwiseAndConstantNode.create(dummy, 42);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceOr() {
JavaScriptNode node = JSBitwiseOrConstantNode.create(dummy, 42);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceXor() {
JavaScriptNode node = JSBitwiseXorConstantNode.create(dummy, 42);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceLeftShift() {
JavaScriptNode node = JSLeftShiftConstantNode.create(dummy, dummyInt);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceRightShift() {
JavaScriptNode node = JSRightShiftConstantNode.create(dummy, dummyInt);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceUnsignedRightShift() {
JavaScriptNode node = JSUnsignedRightShiftConstantNode.create(dummy, dummyInt);
assertNotMaterializedTwice(node, BinaryOperationTag.class);
}
@Test
public void materializeTwiceNot() {
JSNotNode node = (JSNotNode) JSNotNode.create(dummyJSNode);
assertNotMaterializedTwice(node, UnaryOperationTag.class);
}
@Test
public void materializeTwiceFor() {
JavaScriptNode condition = JSConstantNode.createBoolean(true);
JavaScriptNode body = VoidNode.create(dummy);
JavaScriptNode modify = VoidNode.create(dummy);
JavaScriptNode first = JSConstantNode.createBoolean(true);
JavaScriptNode setNotFirst = VoidNode.create(dummy);
JSReadFrameSlotNode[] reads = new JSReadFrameSlotNode[]{};
JSWriteFrameSlotNode[] writes = new JSWriteFrameSlotNode[]{};
IterationScopeNode dummyScope = IterationScopeNode.create(null, reads, writes);
ForNode node = ForNode.createFor(condition, body, modify, dummyScope, first, setNotFirst);
assertNotMaterializedTwice((JavaScriptNode) node.getLoopNode().getRepeatingNode(), ControlFlowRootTag.class);
}
@Test
public void materializeMultiIf() {
IfNode ifNode = IfNode.create(dummy, dummy, dummy);
assertNotMaterializedTwice(ifNode, ControlFlowRootTag.class);
}
@Test
public void materializeMultiWhile() {
JavaScriptNode node = WhileNode.createDoWhile(dummyJSNode, dummyJSNode);
assertNotMaterializedTwice(node, ControlFlowRootTag.class);
}
@Test
public void materializeMultiCall() {
JavaScriptNode[] args = new JavaScriptNode[]{};
JSFunctionCallNode c = JSFunctionCallNode.createCall(JSConstantNode.createUndefined(), null, args, false, false);
assertNotMaterializedTwice(c, FunctionCallTag.class);
}
@Test
public void materializeMultiInvoke() {
JSTargetableNode prop = PropertyNode.createProperty(getDummyCx(), dummy, "foo");
prop.setSourceSection(dummySourceSection);
JavaScriptNode[] args = new JavaScriptNode[]{};
JSFunctionCallNode c = JSFunctionCallNode.createInvoke(prop, args, false, false);
assertNotMaterializedTwice(c, FunctionCallTag.class);
}
@Test
public void materializeNew() {
JSTargetableNode prop = GlobalPropertyNode.createPropertyNode(getDummyCx(), "foo");
JavaScriptNode[] args = new JavaScriptNode[]{};
JSNewNode newnode = JSNewNode.create(getDummyCx(), prop, args);
assertNotMaterializedTwice(newnode, ObjectAllocationTag.class);
}
private static void assertNotMaterializedTwice(JavaScriptNode node, Class<? extends Tag> tag) {
node.setSourceSection(dummySourceSection);
Set<Class<? extends Tag>> s = new HashSet<>();
s.add(tag);
node.addStatementTag();
InstrumentableNode m1 = node.materializeInstrumentableNodes(s);
InstrumentableNode m2 = m1.materializeInstrumentableNodes(s);
assertEquals(m1, m2);
}
}