changeset 17275:846c059e3ecf

Truffle: allow interface types in child fields
author Andreas Woess <andreas.woess@jku.at>
date Tue, 30 Sep 2014 23:42:08 +0200
parents 5c55441b4c62
children ffb974bef674
files CHANGELOG.md graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/InterfaceChildFieldTest.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java
diffstat 3 files changed, 220 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGELOG.md	Tue Sep 30 21:35:36 2014 +0200
+++ b/CHANGELOG.md	Tue Sep 30 23:42:08 2014 +0200
@@ -6,6 +6,7 @@
 * ...
 
 ### Truffle
+* Relaxed declared type restriction on child fields to allow for interface types in addition to Node subclasses.
 * ...
 
 ## Version 0.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/InterfaceChildFieldTest.java	Tue Sep 30 23:42:08 2014 +0200
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2014, 2014, 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.api.test;
+
+import java.util.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.*;
+import com.oracle.truffle.api.frame.*;
+import com.oracle.truffle.api.nodes.*;
+
+/**
+ * Test child fields declared with interface types instead of {@link Node} subclasses.
+ */
+public class InterfaceChildFieldTest {
+
+    @Test
+    public void testChild() {
+        TruffleRuntime runtime = Truffle.getRuntime();
+        TestChildInterface leftChild = new TestLeafNode();
+        TestChildInterface rightChild = new TestLeafNode();
+        TestChildNode parent = new TestChildNode(leftChild, rightChild);
+        TestRootNode rootNode = new TestRootNode(parent);
+        CallTarget target = runtime.createCallTarget(rootNode);
+        Iterator<Node> iterator = parent.getChildren().iterator();
+        Assert.assertEquals(leftChild, iterator.next());
+        Assert.assertEquals(rightChild, iterator.next());
+        Assert.assertFalse(iterator.hasNext());
+        Object result = target.call();
+        Assert.assertEquals(42, result);
+
+        Assert.assertEquals(4, NodeUtil.countNodes(rootNode));
+        Assert.assertEquals(4, NodeUtil.countNodes(NodeUtil.cloneNode(rootNode)));
+    }
+
+    @Test
+    public void testChildren() {
+        TruffleRuntime runtime = Truffle.getRuntime();
+        TestChildInterface[] children = new TestChildInterface[5];
+        for (int i = 0; i < children.length; i++) {
+            children[i] = new TestLeafNode();
+        }
+        TestChildrenNode parent = new TestChildrenNode(children);
+        TestRootNode rootNode = new TestRootNode(parent);
+        CallTarget target = runtime.createCallTarget(rootNode);
+        Iterator<Node> iterator = parent.getChildren().iterator();
+        for (int i = 0; i < children.length; i++) {
+            Assert.assertEquals(children[i], iterator.next());
+        }
+        Assert.assertFalse(iterator.hasNext());
+        Object result = target.call();
+        Assert.assertEquals(105, result);
+
+        Assert.assertEquals(2 + children.length, NodeUtil.countNodes(rootNode));
+        Assert.assertEquals(2 + children.length, NodeUtil.countNodes(NodeUtil.cloneNode(rootNode)));
+    }
+
+    class TestRootNode extends RootNode {
+
+        @Child private TestChildInterface child;
+
+        public TestRootNode(TestChildInterface child) {
+            super(null);
+            this.child = child;
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            return child.executeIntf();
+        }
+    }
+
+    interface TestChildInterface {
+        public int executeIntf();
+    }
+
+    class TestLeafNode extends Node implements TestChildInterface {
+        public TestLeafNode() {
+            super(null);
+        }
+
+        public int executeIntf() {
+            return this.replace(new TestLeaf2Node()).executeIntf();
+        }
+    }
+
+    class TestLeaf2Node extends Node implements TestChildInterface {
+        public TestLeaf2Node() {
+            super(null);
+        }
+
+        public int executeIntf() {
+            return 21;
+        }
+    }
+
+    class TestChildNode extends Node implements TestChildInterface {
+
+        @Child private TestChildInterface left;
+        @Child private TestChildInterface right;
+
+        public TestChildNode(TestChildInterface left, TestChildInterface right) {
+            super(null);
+            this.left = left;
+            this.right = right;
+        }
+
+        @Override
+        public int executeIntf() {
+            return left.executeIntf() + right.executeIntf();
+        }
+    }
+
+    class TestChildrenNode extends Node implements TestChildInterface {
+
+        @Children private final TestChildInterface[] children;
+
+        public TestChildrenNode(TestChildInterface[] children) {
+            super(null);
+            this.children = children;
+        }
+
+        @Override
+        public int executeIntf() {
+            int sum = 0;
+            for (int i = 0; i < children.length; ++i) {
+                sum += children[i].executeIntf();
+            }
+            return sum;
+        }
+    }
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java	Tue Sep 30 21:35:36 2014 +0200
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/nodes/NodeUtil.java	Tue Sep 30 23:42:08 2014 +0200
@@ -185,7 +185,7 @@
 
         public NodeClass(Class<? extends Node> clazz, FieldOffsetProvider fieldOffsetProvider) {
             List<NodeField> fieldsList = new ArrayList<>();
-            List<Long> parentOffsetsList = new ArrayList<>();
+            long parentFieldOffset = -1;
             List<Long> childOffsetsList = new ArrayList<>();
             List<Long> childrenOffsetsList = new ArrayList<>();
 
@@ -195,30 +195,53 @@
                 }
 
                 NodeFieldKind kind;
-                if (Node.class.isAssignableFrom(field.getType()) && field.getName().equals("parent") && field.getDeclaringClass() == Node.class) {
+                if (field.getDeclaringClass() == Node.class && field.getName().equals("parent")) {
+                    assert Node.class.isAssignableFrom(field.getType());
                     kind = NodeFieldKind.PARENT;
-                    parentOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field));
-                } else if (Node.class.isAssignableFrom(field.getType()) && field.getAnnotation(Child.class) != null) {
+                    parentFieldOffset = fieldOffsetProvider.objectFieldOffset(field);
+                } else if (field.getAnnotation(Child.class) != null) {
+                    checkChildField(field);
                     kind = NodeFieldKind.CHILD;
                     childOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field));
-                    assert !Modifier.isFinal(field.getModifiers()) : "child field must not be final (\"" + field.getName() + "\", " + clazz + ")";
-                } else if (field.getType().isArray() && Node.class.isAssignableFrom(field.getType().getComponentType()) && field.getAnnotation(Children.class) != null) {
+                } else if (field.getAnnotation(Children.class) != null) {
+                    checkChildrenField(field);
                     kind = NodeFieldKind.CHILDREN;
                     childrenOffsetsList.add(fieldOffsetProvider.objectFieldOffset(field));
-                    assert Modifier.isFinal(field.getModifiers()) : "children array field must be final (\"" + field.getName() + "\", " + clazz + ")";
                 } else {
                     kind = NodeFieldKind.DATA;
                 }
                 fieldsList.add(new NodeField(kind, field.getType(), field.getName(), fieldOffsetProvider.objectFieldOffset(field)));
             }
+
+            if (parentFieldOffset < 0) {
+                throw new AssertionError("parent field not found");
+            }
+
             this.fields = fieldsList.toArray(new NodeField[fieldsList.size()]);
-            assert parentOffsetsList.size() == 1 : "must have exactly one parent field";
-            this.parentOffset = parentOffsetsList.get(0);
+            this.parentOffset = parentFieldOffset;
             this.childOffsets = toLongArray(childOffsetsList);
             this.childrenOffsets = toLongArray(childrenOffsetsList);
             this.clazz = clazz;
         }
 
+        private static void checkChildField(Field field) {
+            if (!(Node.class.isAssignableFrom(field.getType()) || field.getType().isInterface())) {
+                throw new AssertionError("@Child field type must be a subclass of Node or an interface (" + field + ")");
+            }
+            if (Modifier.isFinal(field.getModifiers())) {
+                throw new AssertionError("@Child field must not be final (" + field + ")");
+            }
+        }
+
+        private static void checkChildrenField(Field field) {
+            if (!(field.getType().isArray() && (Node.class.isAssignableFrom(field.getType().getComponentType()) || field.getType().getComponentType().isInterface()))) {
+                throw new AssertionError("@Children field type must be an array of a subclass of Node or an interface (" + field + ")");
+            }
+            if (!Modifier.isFinal(field.getModifiers())) {
+                throw new AssertionError("@Children field must be final (" + field + ")");
+            }
+        }
+
         public NodeField[] getFields() {
             return fields;
         }
@@ -269,7 +292,7 @@
             private int childrenCount() {
                 int nodeCount = childOffsets.length;
                 for (long fieldOffset : childrenOffsets) {
-                    Node[] children = ((Node[]) unsafe.getObject(node, fieldOffset));
+                    Object[] children = ((Object[]) unsafe.getObject(node, fieldOffset));
                     if (children != null) {
                         nodeCount += children.length;
                     }
@@ -283,9 +306,9 @@
                     return (Node) unsafe.getObject(node, childOffsets[idx]);
                 } else {
                     for (long fieldOffset : childrenOffsets) {
-                        Node[] nodeArray = (Node[]) unsafe.getObject(node, fieldOffset);
+                        Object[] nodeArray = (Object[]) unsafe.getObject(node, fieldOffset);
                         if (idx < nodeCount + nodeArray.length) {
-                            return nodeArray[idx - nodeCount];
+                            return (Node) nodeArray[idx - nodeCount];
                         }
                         nodeCount += nodeArray.length;
                     }
@@ -361,12 +384,12 @@
             }
         }
         for (long fieldOffset : nodeClass.childrenOffsets) {
-            Node[] children = (Node[]) unsafe.getObject(orig, fieldOffset);
+            Object[] children = (Object[]) unsafe.getObject(orig, fieldOffset);
             if (children != null) {
-                Node[] clonedChildren = (Node[]) Array.newInstance(children.getClass().getComponentType(), children.length);
+                Object[] clonedChildren = (Object[]) Array.newInstance(children.getClass().getComponentType(), children.length);
                 for (int i = 0; i < children.length; i++) {
                     if (children[i] != null) {
-                        Node clonedChild = cloneNode(children[i]);
+                        Node clonedChild = cloneNode((Node) children[i]);
                         clonedChildren[i] = clonedChild;
                         unsafe.putObject(clonedChild, nodeClass.parentOffset, clone);
                     }
@@ -388,11 +411,11 @@
             }
         }
         for (long fieldOffset : nodeClass.childrenOffsets) {
-            Node[] children = (Node[]) unsafe.getObject(node, fieldOffset);
+            Object[] children = (Object[]) unsafe.getObject(node, fieldOffset);
             if (children != null) {
-                for (Node child : children) {
+                for (Object child : children) {
                     if (child != null) {
-                        nodes.add(child);
+                        nodes.add((Node) child);
                     }
                 }
             }
@@ -415,8 +438,7 @@
         for (long fieldOffset : nodeClass.getChildrenOffsets()) {
             Object arrayObject = unsafe.getObject(parent, fieldOffset);
             if (arrayObject != null) {
-                assert arrayObject instanceof Node[] : "Children array must be instanceof Node[] ";
-                Node[] array = (Node[]) arrayObject;
+                Object[] array = (Object[]) arrayObject;
                 for (int i = 0; i < array.length; i++) {
                     if (array[i] == oldChild) {
                         assert assertAssignable(nodeClass, fieldOffset, newChild);
@@ -655,25 +677,7 @@
         if (parent == null) {
             p.println(nodeName(node));
         } else {
-            String fieldName = "unknownField";
-            NodeField[] fields = NodeClass.get(parent.getClass()).fields;
-            for (NodeField field : fields) {
-                Object value = field.loadValue(parent);
-                if (value == node) {
-                    fieldName = field.getName();
-                    break;
-                } else if (value instanceof Node[]) {
-                    int index = 0;
-                    for (Node arrayNode : (Node[]) value) {
-                        if (arrayNode == node) {
-                            fieldName = field.getName() + "[" + index + "]";
-                            break;
-                        }
-                        index++;
-                    }
-                }
-            }
-            p.print(fieldName);
+            p.print(getNodeFieldName(parent, node, "unknownField"));
             p.print(" = ");
             p.println(nodeName(node));
         }
@@ -713,25 +717,7 @@
         }
 
         if (parent != null) {
-            String childName = "";
-            NodeField[] fields = NodeClass.get(parent.getClass()).fields;
-            for (NodeField field : fields) {
-                Object value = field.loadValue(parent);
-                if (value == node) {
-                    childName = field.getName();
-                    break;
-                } else if (value instanceof Node[]) {
-                    int index = 0;
-                    for (Node arrayNode : (Node[]) value) {
-                        if (arrayNode == node) {
-                            childName = field.getName() + "[" + index + "]";
-                            break;
-                        }
-                        index++;
-                    }
-                }
-            }
-            sb.append(childName);
+            sb.append(getNodeFieldName(parent, node, ""));
         }
 
         sb.append("  (" + node.getClass().getSimpleName() + ")  ");
@@ -744,6 +730,25 @@
         p.flush();
     }
 
+    private static String getNodeFieldName(Node parent, Node node, String defaultName) {
+        NodeField[] fields = NodeClass.get(parent.getClass()).fields;
+        for (NodeField field : fields) {
+            Object value = field.loadValue(parent);
+            if (field.getKind() == NodeFieldKind.CHILD && value == node) {
+                return field.getName();
+            } else if (field.getKind() == NodeFieldKind.CHILDREN) {
+                int index = 0;
+                for (Object arrayNode : (Object[]) value) {
+                    if (arrayNode == node) {
+                        return field.getName() + "[" + index + "]";
+                    }
+                    index++;
+                }
+            }
+        }
+        return defaultName;
+    }
+
     /**
      * Prints a human readable form of a {@link Node} AST to the given {@link PrintStream}. This
      * print method does not check for cycles in the node structure.
@@ -815,13 +820,13 @@
 
     private static void printChildren(PrintWriter p, int level, Object value) {
         String sep;
-        Node[] children = (Node[]) value;
+        Object[] children = (Object[]) value;
         p.print(" = [");
         sep = "";
-        for (Node child : children) {
+        for (Object child : children) {
             p.print(sep);
             sep = ", ";
-            printTree(p, child, level + 1);
+            printTree(p, (Node) child, level + 1);
         }
         p.print("]");
     }