1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.replacements;
  24 
  25 import static org.graalvm.compiler.core.common.LocationIdentity.any;
  26 import static org.graalvm.compiler.nodes.ConstantNode.forInt;
  27 import static org.graalvm.compiler.nodes.ConstantNode.forIntegerKind;
  28 
  29 import java.lang.reflect.Constructor;
  30 
  31 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
  32 import org.graalvm.compiler.bytecode.BridgeMethodUtils;
  33 import org.graalvm.compiler.core.common.LocationIdentity;
  34 import org.graalvm.compiler.core.common.calc.Condition;
  35 import org.graalvm.compiler.core.common.type.Stamp;
  36 import org.graalvm.compiler.core.common.type.StampFactory;
  37 import org.graalvm.compiler.core.common.type.StampPair;
  38 import org.graalvm.compiler.core.common.type.TypeReference;
  39 import org.graalvm.compiler.debug.GraalError;
  40 import org.graalvm.compiler.nodes.ConstantNode;
  41 import org.graalvm.compiler.nodes.Invoke;
  42 import org.graalvm.compiler.nodes.ValueNode;
  43 import org.graalvm.compiler.nodes.calc.CompareNode;
  44 import org.graalvm.compiler.nodes.calc.ConditionalNode;
  45 import org.graalvm.compiler.nodes.calc.IntegerBelowNode;
  46 import org.graalvm.compiler.nodes.calc.IntegerEqualsNode;
  47 import org.graalvm.compiler.nodes.calc.IntegerLessThanNode;
  48 import org.graalvm.compiler.nodes.calc.NarrowNode;
  49 import org.graalvm.compiler.nodes.calc.SignExtendNode;
  50 import org.graalvm.compiler.nodes.calc.XorNode;
  51 import org.graalvm.compiler.nodes.calc.ZeroExtendNode;
  52 import org.graalvm.compiler.nodes.extended.JavaReadNode;
  53 import org.graalvm.compiler.nodes.extended.JavaWriteNode;
  54 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
  55 import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderTool;
  56 import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin;
  57 import org.graalvm.compiler.nodes.graphbuilderconf.NodePlugin;
  58 import org.graalvm.compiler.nodes.graphbuilderconf.TypePlugin;
  59 import org.graalvm.compiler.nodes.java.LoadFieldNode;
  60 import org.graalvm.compiler.nodes.java.LoadIndexedNode;
  61 import org.graalvm.compiler.nodes.java.StoreIndexedNode;
  62 import org.graalvm.compiler.nodes.memory.HeapAccess.BarrierType;
  63 import org.graalvm.compiler.nodes.memory.address.AddressNode;
  64 import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode;
  65 import org.graalvm.compiler.nodes.type.StampTool;
  66 import org.graalvm.compiler.word.Word;
  67 import org.graalvm.compiler.word.Word.Opcode;
  68 import org.graalvm.compiler.word.Word.Operation;
  69 import org.graalvm.compiler.word.WordTypes;
  70 import org.graalvm.compiler.word.nodes.WordCastNode;
  71 
  72 import jdk.vm.ci.code.BailoutException;
  73 import jdk.vm.ci.meta.JavaKind;
  74 import jdk.vm.ci.meta.JavaType;
  75 import jdk.vm.ci.meta.JavaTypeProfile;
  76 import jdk.vm.ci.meta.ResolvedJavaField;
  77 import jdk.vm.ci.meta.ResolvedJavaMethod;
  78 import jdk.vm.ci.meta.ResolvedJavaType;
  79 
  80 /**
  81  * A plugin for calls to {@linkplain Operation word operations}, as well as all other nodes that
  82  * need special handling for {@link Word} types.
  83  */
  84 public class WordOperationPlugin implements NodePlugin, TypePlugin, InlineInvokePlugin {
  85     protected final WordTypes wordTypes;
  86     protected final JavaKind wordKind;
  87     protected final SnippetReflectionProvider snippetReflection;
  88 
  89     public WordOperationPlugin(SnippetReflectionProvider snippetReflection, WordTypes wordTypes) {
  90         this.snippetReflection = snippetReflection;
  91         this.wordTypes = wordTypes;
  92         this.wordKind = wordTypes.getWordKind();
  93     }
  94 
  95     @Override
  96     public boolean canChangeStackKind(GraphBuilderContext b) {
  97         return true;
  98     }
  99 
 100     /**
 101      * Processes a call to a method if it is annotated with {@link Operation} by adding nodes to the
 102      * graph being built that implement the denoted operation.
 103      *
 104      * @return {@code true} iff {@code method} is annotated with {@link Operation} (and was thus
 105      *         processed by this method)
 106      */
 107     @Override
 108     public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) {
 109         if (!wordTypes.isWordOperation(method)) {
 110             return false;
 111         }
 112         processWordOperation(b, args, wordTypes.getWordOperation(method, b.getMethod().getDeclaringClass()));
 113         return true;
 114     }
 115 
 116     @Override
 117     public StampPair interceptType(GraphBuilderTool b, JavaType declaredType, boolean nonNull) {
 118         Stamp wordStamp = null;
 119         if (declaredType instanceof ResolvedJavaType) {
 120             ResolvedJavaType resolved = (ResolvedJavaType) declaredType;
 121             if (wordTypes.isWord(resolved)) {
 122                 wordStamp = wordTypes.getWordStamp(resolved);
 123             } else if (resolved.isArray() && wordTypes.isWord(resolved.getElementalType())) {
 124                 TypeReference trusted = TypeReference.createTrustedWithoutAssumptions(resolved);
 125                 wordStamp = StampFactory.object(trusted, nonNull);
 126             }
 127         }
 128         if (wordStamp != null) {
 129             return StampPair.createSingle(wordStamp);
 130         } else {
 131             return null;
 132         }
 133     }
 134 
 135     @Override
 136     public void notifyNotInlined(GraphBuilderContext b, ResolvedJavaMethod method, Invoke invoke) {
 137         if (wordTypes.isWord(invoke.asNode())) {
 138             invoke.asNode().setStamp(wordTypes.getWordStamp(StampTool.typeOrNull(invoke.asNode())));
 139         }
 140     }
 141 
 142     @Override
 143     public boolean handleLoadField(GraphBuilderContext b, ValueNode receiver, ResolvedJavaField field) {
 144         StampPair wordStamp = interceptType(b, field.getType(), false);
 145         if (wordStamp != null) {
 146             LoadFieldNode loadFieldNode = LoadFieldNode.createOverrideStamp(wordStamp, receiver, field);
 147             b.addPush(field.getJavaKind(), loadFieldNode);
 148             return true;
 149         }
 150         return false;
 151     }
 152 
 153     @Override
 154     public boolean handleLoadStaticField(GraphBuilderContext b, ResolvedJavaField staticField) {
 155         return handleLoadField(b, null, staticField);
 156     }
 157 
 158     @Override
 159     public boolean handleLoadIndexed(GraphBuilderContext b, ValueNode array, ValueNode index, JavaKind elementKind) {
 160         ResolvedJavaType arrayType = StampTool.typeOrNull(array);
 161         /*
 162          * There are cases where the array does not have a known type yet, i.e., the type is null.
 163          * In that case we assume it is not a word type.
 164          */
 165         if (arrayType != null && wordTypes.isWord(arrayType.getComponentType())) {
 166             assert elementKind == JavaKind.Object;
 167             b.addPush(elementKind, createLoadIndexedNode(array, index));
 168             return true;
 169         }
 170         return false;
 171     }
 172 
 173     protected LoadIndexedNode createLoadIndexedNode(ValueNode array, ValueNode index) {
 174         return new LoadIndexedNode(null, array, index, wordTypes.getWordKind());
 175     }
 176 
 177     @Override
 178     public boolean handleStoreField(GraphBuilderContext b, ValueNode object, ResolvedJavaField field, ValueNode value) {
 179         if (field.getJavaKind() == JavaKind.Object) {
 180             boolean isWordField = wordTypes.isWord(field.getType());
 181             boolean isWordValue = value.getStackKind() == wordTypes.getWordKind();
 182 
 183             if (isWordField && !isWordValue) {
 184                 throw bailout(b, "Cannot store a non-word value into a word field: " + field.format("%H.%n"));
 185             } else if (!isWordField && isWordValue) {
 186                 throw bailout(b, "Cannot store a word value into a non-word field: " + field.format("%H.%n"));
 187             }
 188         }
 189 
 190         /* We never need to intercept the field store. */
 191         return false;
 192     }
 193 
 194     @Override
 195     public boolean handleStoreStaticField(GraphBuilderContext b, ResolvedJavaField field, ValueNode value) {
 196         return handleStoreField(b, null, field, value);
 197     }
 198 
 199     @Override
 200     public boolean handleStoreIndexed(GraphBuilderContext b, ValueNode array, ValueNode index, JavaKind elementKind, ValueNode value) {
 201         ResolvedJavaType arrayType = StampTool.typeOrNull(array);
 202         if (arrayType != null && wordTypes.isWord(arrayType.getComponentType())) {
 203             assert elementKind == JavaKind.Object;
 204             if (value.getStackKind() != wordTypes.getWordKind()) {
 205                 throw bailout(b, "Cannot store a non-word value into a word array: " + arrayType.toJavaName(true));
 206             }
 207             b.add(createStoreIndexedNode(array, index, value));
 208             return true;
 209         }
 210         if (elementKind == JavaKind.Object && value.getStackKind() == wordTypes.getWordKind()) {
 211             throw bailout(b, "Cannot store a word value into a non-word array: " + arrayType.toJavaName(true));
 212         }
 213         return false;
 214     }
 215 
 216     protected StoreIndexedNode createStoreIndexedNode(ValueNode array, ValueNode index, ValueNode value) {
 217         return new StoreIndexedNode(array, index, wordTypes.getWordKind(), value);
 218     }
 219 
 220     @Override
 221     public boolean handleCheckCast(GraphBuilderContext b, ValueNode object, ResolvedJavaType type, JavaTypeProfile profile) {
 222         if (!wordTypes.isWord(type)) {
 223             if (object.getStackKind() != JavaKind.Object) {
 224                 throw bailout(b, "Cannot cast a word value to a non-word type: " + type.toJavaName(true));
 225             }
 226             return false;
 227         }
 228 
 229         if (object.getStackKind() != wordTypes.getWordKind()) {
 230             throw bailout(b, "Cannot cast a non-word value to a word type: " + type.toJavaName(true));
 231         }
 232         b.push(JavaKind.Object, object);
 233         return true;
 234     }
 235 
 236     @Override
 237     public boolean handleInstanceOf(GraphBuilderContext b, ValueNode object, ResolvedJavaType type, JavaTypeProfile profile) {
 238         if (wordTypes.isWord(type)) {
 239             throw bailout(b, "Cannot use instanceof for word a type: " + type.toJavaName(true));
 240         } else if (object.getStackKind() != JavaKind.Object) {
 241             throw bailout(b, "Cannot use instanceof on a word value: " + type.toJavaName(true));
 242         }
 243         return false;
 244     }
 245 
 246     protected void processWordOperation(GraphBuilderContext b, ValueNode[] args, ResolvedJavaMethod wordMethod) throws GraalError {
 247         Operation operation = BridgeMethodUtils.getAnnotation(Word.Operation.class, wordMethod);
 248         JavaKind returnKind = wordMethod.getSignature().getReturnKind();
 249         switch (operation.opcode()) {
 250             case NODE_CLASS:
 251                 assert args.length == 2;
 252                 ValueNode left = args[0];
 253                 ValueNode right = operation.rightOperandIsInt() ? toUnsigned(b, args[1], JavaKind.Int) : fromSigned(b, args[1]);
 254 
 255                 b.addPush(returnKind, createBinaryNodeInstance(operation.node(), left, right));
 256                 break;
 257 
 258             case COMPARISON:
 259                 assert args.length == 2;
 260                 b.push(returnKind, comparisonOp(b, operation.condition(), args[0], fromSigned(b, args[1])));
 261                 break;
 262 
 263             case NOT:
 264                 assert args.length == 1;
 265                 b.addPush(returnKind, new XorNode(args[0], b.add(forIntegerKind(wordKind, -1))));
 266                 break;
 267 
 268             case READ_POINTER:
 269             case READ_OBJECT:
 270             case READ_BARRIERED: {
 271                 assert args.length == 2 || args.length == 3;
 272                 JavaKind readKind = wordTypes.asKind(wordMethod.getSignature().getReturnType(wordMethod.getDeclaringClass()));
 273                 AddressNode address = makeAddress(b, args[0], args[1]);
 274                 LocationIdentity location;
 275                 if (args.length == 2) {
 276                     location = any();
 277                 } else {
 278                     assert args[2].isConstant();
 279                     location = snippetReflection.asObject(LocationIdentity.class, args[2].asJavaConstant());
 280                 }
 281                 b.push(returnKind, readOp(b, readKind, address, location, operation.opcode()));
 282                 break;
 283             }
 284             case READ_HEAP: {
 285                 assert args.length == 3;
 286                 JavaKind readKind = wordTypes.asKind(wordMethod.getSignature().getReturnType(wordMethod.getDeclaringClass()));
 287                 AddressNode address = makeAddress(b, args[0], args[1]);
 288                 BarrierType barrierType = snippetReflection.asObject(BarrierType.class, args[2].asJavaConstant());
 289                 b.push(returnKind, readOp(b, readKind, address, any(), barrierType, true));
 290                 break;
 291             }
 292             case WRITE_POINTER:
 293             case WRITE_OBJECT:
 294             case WRITE_BARRIERED:
 295             case INITIALIZE: {
 296                 assert args.length == 3 || args.length == 4;
 297                 JavaKind writeKind = wordTypes.asKind(wordMethod.getSignature().getParameterType(wordMethod.isStatic() ? 2 : 1, wordMethod.getDeclaringClass()));
 298                 AddressNode address = makeAddress(b, args[0], args[1]);
 299                 LocationIdentity location;
 300                 if (args.length == 3) {
 301                     location = any();
 302                 } else {
 303                     assert args[3].isConstant();
 304                     location = snippetReflection.asObject(LocationIdentity.class, args[3].asJavaConstant());
 305                 }
 306                 writeOp(b, writeKind, address, location, args[2], operation.opcode());
 307                 break;
 308             }
 309             case ZERO:
 310                 assert args.length == 0;
 311                 b.addPush(returnKind, forIntegerKind(wordKind, 0L));
 312                 break;
 313 
 314             case FROM_UNSIGNED:
 315                 assert args.length == 1;
 316                 b.push(returnKind, fromUnsigned(b, args[0]));
 317                 break;
 318 
 319             case FROM_SIGNED:
 320                 assert args.length == 1;
 321                 b.push(returnKind, fromSigned(b, args[0]));
 322                 break;
 323 
 324             case TO_RAW_VALUE:
 325                 assert args.length == 1;
 326                 b.push(returnKind, toUnsigned(b, args[0], JavaKind.Long));
 327                 break;
 328 
 329             case OBJECT_TO_TRACKED:
 330                 assert args.length == 1;
 331                 WordCastNode objectToTracked = b.add(WordCastNode.objectToTrackedPointer(args[0], wordKind));
 332                 b.push(returnKind, objectToTracked);
 333                 break;
 334 
 335             case OBJECT_TO_UNTRACKED:
 336                 assert args.length == 1;
 337                 WordCastNode objectToUntracked = b.add(WordCastNode.objectToUntrackedPointer(args[0], wordKind));
 338                 b.push(returnKind, objectToUntracked);
 339                 break;
 340 
 341             case FROM_ADDRESS:
 342                 assert args.length == 1;
 343                 WordCastNode addressToWord = b.add(WordCastNode.addressToWord(args[0], wordKind));
 344                 b.push(returnKind, addressToWord);
 345                 break;
 346 
 347             case TO_OBJECT:
 348                 assert args.length == 1;
 349                 WordCastNode wordToObject = b.add(WordCastNode.wordToObject(args[0], wordKind));
 350                 b.push(returnKind, wordToObject);
 351                 break;
 352 
 353             default:
 354                 throw new GraalError("Unknown opcode: %s", operation.opcode());
 355         }
 356     }
 357 
 358     /**
 359      * Create an instance of a binary node which is used to lower {@link Word} operations. This
 360      * method is called for all {@link Word} operations which are annotated with @Operation(node =
 361      * ...) and encapsulates the reflective allocation of the node.
 362      */
 363     private static ValueNode createBinaryNodeInstance(Class<? extends ValueNode> nodeClass, ValueNode left, ValueNode right) {
 364         try {
 365             Constructor<?> cons = nodeClass.getDeclaredConstructor(ValueNode.class, ValueNode.class);
 366             return (ValueNode) cons.newInstance(left, right);
 367         } catch (Throwable ex) {
 368             throw new GraalError(ex).addContext(nodeClass.getName());
 369         }
 370     }
 371 
 372     private ValueNode comparisonOp(GraphBuilderContext graph, Condition condition, ValueNode left, ValueNode right) {
 373         assert left.getStackKind() == wordKind && right.getStackKind() == wordKind;
 374 
 375         // mirroring gets the condition into canonical form
 376         boolean mirror = condition.canonicalMirror();
 377 
 378         ValueNode a = mirror ? right : left;
 379         ValueNode b = mirror ? left : right;
 380 
 381         CompareNode comparison;
 382         if (condition == Condition.EQ || condition == Condition.NE) {
 383             comparison = new IntegerEqualsNode(a, b);
 384         } else if (condition.isUnsigned()) {
 385             comparison = new IntegerBelowNode(a, b);
 386         } else {
 387             comparison = new IntegerLessThanNode(a, b);
 388         }
 389 
 390         ConstantNode trueValue = graph.add(forInt(1));
 391         ConstantNode falseValue = graph.add(forInt(0));
 392 
 393         if (condition.canonicalNegate()) {
 394             ConstantNode temp = trueValue;
 395             trueValue = falseValue;
 396             falseValue = temp;
 397         }
 398         ConditionalNode materialize = graph.add(new ConditionalNode(graph.add(comparison), trueValue, falseValue));
 399         return materialize;
 400     }
 401 
 402     protected ValueNode readOp(GraphBuilderContext b, JavaKind readKind, AddressNode address, LocationIdentity location, Opcode op) {
 403         assert op == Opcode.READ_POINTER || op == Opcode.READ_OBJECT || op == Opcode.READ_BARRIERED;
 404         final BarrierType barrier = (op == Opcode.READ_BARRIERED ? BarrierType.PRECISE : BarrierType.NONE);
 405         final boolean compressible = (op == Opcode.READ_OBJECT || op == Opcode.READ_BARRIERED);
 406 
 407         return readOp(b, readKind, address, location, barrier, compressible);
 408     }
 409 
 410     public static ValueNode readOp(GraphBuilderContext b, JavaKind readKind, AddressNode address, LocationIdentity location, BarrierType barrierType, boolean compressible) {
 411         /*
 412          * A JavaReadNode lowered to a ReadNode that will not float. This means it cannot float
 413          * above an explicit zero check on its base address or any other test that ensures the read
 414          * is safe.
 415          */
 416         JavaReadNode read = b.add(new JavaReadNode(readKind, address, location, barrierType, compressible));
 417         return read;
 418     }
 419 
 420     protected void writeOp(GraphBuilderContext b, JavaKind writeKind, AddressNode address, LocationIdentity location, ValueNode value, Opcode op) {
 421         assert op == Opcode.WRITE_POINTER || op == Opcode.WRITE_OBJECT || op == Opcode.WRITE_BARRIERED || op == Opcode.INITIALIZE;
 422         final BarrierType barrier = (op == Opcode.WRITE_BARRIERED ? BarrierType.PRECISE : BarrierType.NONE);
 423         final boolean compressible = (op == Opcode.WRITE_OBJECT || op == Opcode.WRITE_BARRIERED);
 424         final boolean initialize = (op == Opcode.INITIALIZE);
 425         b.add(new JavaWriteNode(writeKind, address, location, value, barrier, compressible, initialize));
 426     }
 427 
 428     public AddressNode makeAddress(GraphBuilderContext b, ValueNode base, ValueNode offset) {
 429         return b.add(new OffsetAddressNode(base, fromSigned(b, offset)));
 430     }
 431 
 432     public ValueNode fromUnsigned(GraphBuilderContext b, ValueNode value) {
 433         return convert(b, value, wordKind, true);
 434     }
 435 
 436     public ValueNode fromSigned(GraphBuilderContext b, ValueNode value) {
 437         return convert(b, value, wordKind, false);
 438     }
 439 
 440     public ValueNode toUnsigned(GraphBuilderContext b, ValueNode value, JavaKind toKind) {
 441         return convert(b, value, toKind, true);
 442     }
 443 
 444     public ValueNode convert(GraphBuilderContext b, ValueNode value, JavaKind toKind, boolean unsigned) {
 445         if (value.getStackKind() == toKind) {
 446             return value;
 447         }
 448 
 449         if (toKind == JavaKind.Int) {
 450             assert value.getStackKind() == JavaKind.Long;
 451             return b.add(new NarrowNode(value, 32));
 452         } else {
 453             assert toKind == JavaKind.Long;
 454             assert value.getStackKind() == JavaKind.Int;
 455             if (unsigned) {
 456                 return b.add(new ZeroExtendNode(value, 64));
 457             } else {
 458                 return b.add(new SignExtendNode(value, 64));
 459             }
 460         }
 461     }
 462 
 463     public WordTypes getWordTypes() {
 464         return wordTypes;
 465     }
 466 
 467     private static BailoutException bailout(GraphBuilderContext b, String msg) {
 468         throw b.bailout(msg + "\nat " + b.getCode().asStackTraceElement(b.bci()));
 469     }
 470 }