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.word; 24 25 import static org.graalvm.compiler.nodes.ConstantNode.forInt; 26 import static org.graalvm.compiler.nodes.ConstantNode.forIntegerKind; 27 import static org.graalvm.word.LocationIdentity.any; 28 29 import java.lang.reflect.Constructor; 30 import java.util.Arrays; 31 32 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider; 33 import org.graalvm.compiler.bytecode.BridgeMethodUtils; 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.AbstractCompareAndSwapNode; 60 import org.graalvm.compiler.nodes.java.LoadFieldNode; 61 import org.graalvm.compiler.nodes.java.LoadIndexedNode; 62 import org.graalvm.compiler.nodes.java.LogicCompareAndSwapNode; 63 import org.graalvm.compiler.nodes.java.StoreIndexedNode; 64 import org.graalvm.compiler.nodes.java.ValueCompareAndSwapNode; 65 import org.graalvm.compiler.nodes.memory.HeapAccess.BarrierType; 66 import org.graalvm.compiler.nodes.memory.address.AddressNode; 67 import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode; 68 import org.graalvm.compiler.nodes.type.StampTool; 69 import org.graalvm.compiler.word.Word.Opcode; 70 import org.graalvm.compiler.word.Word.Operation; 71 import org.graalvm.word.LocationIdentity; 72 import org.graalvm.word.WordFactory; 73 74 import jdk.vm.ci.code.BailoutException; 75 import jdk.vm.ci.meta.JavaKind; 76 import jdk.vm.ci.meta.JavaType; 77 import jdk.vm.ci.meta.JavaTypeProfile; 78 import jdk.vm.ci.meta.ResolvedJavaField; 79 import jdk.vm.ci.meta.ResolvedJavaMethod; 80 import jdk.vm.ci.meta.ResolvedJavaType; 81 82 /** 83 * A plugin for calls to {@linkplain Operation word operations}, as well as all other nodes that 84 * need special handling for {@link Word} types. 85 */ 86 public class WordOperationPlugin extends WordFactory implements NodePlugin, TypePlugin, InlineInvokePlugin { 87 protected final WordTypes wordTypes; 88 protected final JavaKind wordKind; 89 protected final SnippetReflectionProvider snippetReflection; 90 91 public WordOperationPlugin(SnippetReflectionProvider snippetReflection, WordTypes wordTypes) { 92 this.snippetReflection = snippetReflection; 93 this.wordTypes = wordTypes; 94 this.wordKind = wordTypes.getWordKind(); 95 } 96 97 @Override 98 public boolean canChangeStackKind(GraphBuilderContext b) { 99 return true; 100 } 101 102 /** 103 * Processes a call to a method if it is annotated as a word operation by adding nodes to the 104 * graph being built that implement the denoted operation. 105 * 106 * @return {@code true} iff {@code method} is annotated with {@link Operation} (and was thus 107 * processed by this method) 108 */ 109 @Override 110 public boolean handleInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) { 111 if (!wordTypes.isWordOperation(method)) { 112 return false; 113 } 114 processWordOperation(b, args, wordTypes.getWordOperation(method, b.getMethod().getDeclaringClass())); 115 return true; 116 } 117 118 @Override 119 public StampPair interceptType(GraphBuilderTool b, JavaType declaredType, boolean nonNull) { 120 Stamp wordStamp = null; 121 if (declaredType instanceof ResolvedJavaType) { 122 ResolvedJavaType resolved = (ResolvedJavaType) declaredType; 123 if (wordTypes.isWord(resolved)) { 124 wordStamp = wordTypes.getWordStamp(resolved); 125 } else if (resolved.isArray() && wordTypes.isWord(resolved.getElementalType())) { 126 TypeReference trusted = TypeReference.createTrustedWithoutAssumptions(resolved); 127 wordStamp = StampFactory.object(trusted, nonNull); 128 } 129 } 130 if (wordStamp != null) { 131 return StampPair.createSingle(wordStamp); 132 } else { 133 return null; 134 } 135 } 136 137 @Override 138 public void notifyNotInlined(GraphBuilderContext b, ResolvedJavaMethod method, Invoke invoke) { 139 if (wordTypes.isWord(invoke.asNode())) { 140 invoke.asNode().setStamp(wordTypes.getWordStamp(StampTool.typeOrNull(invoke.asNode()))); 141 } 142 } 143 144 @Override 145 public boolean handleLoadField(GraphBuilderContext b, ValueNode receiver, ResolvedJavaField field) { 146 StampPair wordStamp = interceptType(b, field.getType(), false); 147 if (wordStamp != null) { 148 LoadFieldNode loadFieldNode = LoadFieldNode.createOverrideStamp(wordStamp, receiver, field); 149 b.addPush(field.getJavaKind(), loadFieldNode); 150 return true; 151 } 152 return false; 153 } 154 155 @Override 156 public boolean handleLoadStaticField(GraphBuilderContext b, ResolvedJavaField staticField) { 157 return handleLoadField(b, null, staticField); 158 } 159 160 @Override 161 public boolean handleLoadIndexed(GraphBuilderContext b, ValueNode array, ValueNode index, JavaKind elementKind) { 162 ResolvedJavaType arrayType = StampTool.typeOrNull(array); 163 /* 164 * There are cases where the array does not have a known type yet, i.e., the type is null. 165 * In that case we assume it is not a word type. 166 */ 167 if (arrayType != null && wordTypes.isWord(arrayType.getComponentType())) { 168 assert elementKind == JavaKind.Object; 169 b.addPush(elementKind, createLoadIndexedNode(array, index)); 170 return true; 171 } 172 return false; 173 } 174 175 protected LoadIndexedNode createLoadIndexedNode(ValueNode array, ValueNode index) { 176 return new LoadIndexedNode(null, array, index, wordTypes.getWordKind()); 177 } 178 179 @Override 180 public boolean handleStoreField(GraphBuilderContext b, ValueNode object, ResolvedJavaField field, ValueNode value) { 181 if (field.getJavaKind() == JavaKind.Object) { 182 boolean isWordField = wordTypes.isWord(field.getType()); 183 boolean isWordValue = value.getStackKind() == wordTypes.getWordKind(); 184 185 if (isWordField && !isWordValue) { 186 throw bailout(b, "Cannot store a non-word value into a word field: " + field.format("%H.%n")); 187 } else if (!isWordField && isWordValue) { 188 throw bailout(b, "Cannot store a word value into a non-word field: " + field.format("%H.%n")); 189 } 190 } 191 192 /* We never need to intercept the field store. */ 193 return false; 194 } 195 196 @Override 197 public boolean handleStoreStaticField(GraphBuilderContext b, ResolvedJavaField field, ValueNode value) { 198 return handleStoreField(b, null, field, value); 199 } 200 201 @Override 202 public boolean handleStoreIndexed(GraphBuilderContext b, ValueNode array, ValueNode index, JavaKind elementKind, ValueNode value) { 203 ResolvedJavaType arrayType = StampTool.typeOrNull(array); 204 if (arrayType != null && wordTypes.isWord(arrayType.getComponentType())) { 205 assert elementKind == JavaKind.Object; 206 if (value.getStackKind() != wordTypes.getWordKind()) { 207 throw bailout(b, "Cannot store a non-word value into a word array: " + arrayType.toJavaName(true)); 208 } 209 b.add(createStoreIndexedNode(array, index, value)); 210 return true; 211 } 212 if (elementKind == JavaKind.Object && value.getStackKind() == wordTypes.getWordKind()) { 213 throw bailout(b, "Cannot store a word value into a non-word array: " + arrayType.toJavaName(true)); 214 } 215 return false; 216 } 217 218 protected StoreIndexedNode createStoreIndexedNode(ValueNode array, ValueNode index, ValueNode value) { 219 return new StoreIndexedNode(array, index, wordTypes.getWordKind(), value); 220 } 221 222 @Override 223 public boolean handleCheckCast(GraphBuilderContext b, ValueNode object, ResolvedJavaType type, JavaTypeProfile profile) { 224 if (!wordTypes.isWord(type)) { 225 if (object.getStackKind() != JavaKind.Object) { 226 throw bailout(b, "Cannot cast a word value to a non-word type: " + type.toJavaName(true)); 227 } 228 return false; 229 } 230 231 if (object.getStackKind() != wordTypes.getWordKind()) { 232 throw bailout(b, "Cannot cast a non-word value to a word type: " + type.toJavaName(true)); 233 } 234 b.push(JavaKind.Object, object); 235 return true; 236 } 237 238 @Override 239 public boolean handleInstanceOf(GraphBuilderContext b, ValueNode object, ResolvedJavaType type, JavaTypeProfile profile) { 240 if (wordTypes.isWord(type)) { 241 throw bailout(b, "Cannot use instanceof for word a type: " + type.toJavaName(true)); 242 } else if (object.getStackKind() != JavaKind.Object) { 243 throw bailout(b, "Cannot use instanceof on a word value: " + type.toJavaName(true)); 244 } 245 return false; 246 } 247 248 protected void processWordOperation(GraphBuilderContext b, ValueNode[] args, ResolvedJavaMethod wordMethod) throws GraalError { 249 JavaKind returnKind = wordMethod.getSignature().getReturnKind(); 250 WordFactory.FactoryOperation factoryOperation = BridgeMethodUtils.getAnnotation(WordFactory.FactoryOperation.class, wordMethod); 251 if (factoryOperation != null) { 252 switch (factoryOperation.opcode()) { 253 case ZERO: 254 assert args.length == 0; 255 b.addPush(returnKind, forIntegerKind(wordKind, 0L)); 256 return; 257 258 case FROM_UNSIGNED: 259 assert args.length == 1; 260 b.push(returnKind, fromUnsigned(b, args[0])); 261 return; 262 263 case FROM_SIGNED: 264 assert args.length == 1; 265 b.push(returnKind, fromSigned(b, args[0])); 266 return; 267 } 268 } 269 270 Word.Operation operation = BridgeMethodUtils.getAnnotation(Word.Operation.class, wordMethod); 271 switch (operation.opcode()) { 272 case NODE_CLASS: 273 assert args.length == 2; 274 ValueNode left = args[0]; 275 ValueNode right = operation.rightOperandIsInt() ? toUnsigned(b, args[1], JavaKind.Int) : fromSigned(b, args[1]); 276 277 b.addPush(returnKind, createBinaryNodeInstance(operation.node(), left, right)); 278 break; 279 280 case COMPARISON: 281 assert args.length == 2; 282 b.push(returnKind, comparisonOp(b, operation.condition(), args[0], fromSigned(b, args[1]))); 283 break; 284 285 case IS_NULL: 286 assert args.length == 1; 287 b.push(returnKind, comparisonOp(b, Condition.EQ, args[0], ConstantNode.forIntegerKind(wordKind, 0L))); 288 break; 289 290 case IS_NON_NULL: 291 assert args.length == 1; 292 b.push(returnKind, comparisonOp(b, Condition.NE, args[0], ConstantNode.forIntegerKind(wordKind, 0L))); 293 break; 294 295 case NOT: 296 assert args.length == 1; 297 b.addPush(returnKind, new XorNode(args[0], b.add(forIntegerKind(wordKind, -1)))); 298 break; 299 300 case READ_POINTER: 301 case READ_OBJECT: 302 case READ_BARRIERED: { 303 assert args.length == 2 || args.length == 3; 304 JavaKind readKind = wordTypes.asKind(wordMethod.getSignature().getReturnType(wordMethod.getDeclaringClass())); 305 AddressNode address = makeAddress(b, args[0], args[1]); 306 LocationIdentity location; 307 if (args.length == 2) { 308 location = any(); 309 } else { 310 assert args[2].isConstant(); 311 location = snippetReflection.asObject(LocationIdentity.class, args[2].asJavaConstant()); 312 } 313 b.push(returnKind, readOp(b, readKind, address, location, operation.opcode())); 314 break; 315 } 316 case READ_HEAP: { 317 assert args.length == 3; 318 JavaKind readKind = wordTypes.asKind(wordMethod.getSignature().getReturnType(wordMethod.getDeclaringClass())); 319 AddressNode address = makeAddress(b, args[0], args[1]); 320 BarrierType barrierType = snippetReflection.asObject(BarrierType.class, args[2].asJavaConstant()); 321 b.push(returnKind, readOp(b, readKind, address, any(), barrierType, true)); 322 break; 323 } 324 case WRITE_POINTER: 325 case WRITE_OBJECT: 326 case WRITE_BARRIERED: 327 case INITIALIZE: { 328 assert args.length == 3 || args.length == 4; 329 JavaKind writeKind = wordTypes.asKind(wordMethod.getSignature().getParameterType(wordMethod.isStatic() ? 2 : 1, wordMethod.getDeclaringClass())); 330 AddressNode address = makeAddress(b, args[0], args[1]); 331 LocationIdentity location; 332 if (args.length == 3) { 333 location = any(); 334 } else { 335 assert args[3].isConstant(); 336 location = snippetReflection.asObject(LocationIdentity.class, args[3].asJavaConstant()); 337 } 338 writeOp(b, writeKind, address, location, args[2], operation.opcode()); 339 break; 340 } 341 342 case TO_RAW_VALUE: 343 assert args.length == 1; 344 b.push(returnKind, toUnsigned(b, args[0], JavaKind.Long)); 345 break; 346 347 case OBJECT_TO_TRACKED: 348 assert args.length == 1; 349 WordCastNode objectToTracked = b.add(WordCastNode.objectToTrackedPointer(args[0], wordKind)); 350 b.push(returnKind, objectToTracked); 351 break; 352 353 case OBJECT_TO_UNTRACKED: 354 assert args.length == 1; 355 WordCastNode objectToUntracked = b.add(WordCastNode.objectToUntrackedPointer(args[0], wordKind)); 356 b.push(returnKind, objectToUntracked); 357 break; 358 359 case FROM_ADDRESS: 360 assert args.length == 1; 361 WordCastNode addressToWord = b.add(WordCastNode.addressToWord(args[0], wordKind)); 362 b.push(returnKind, addressToWord); 363 break; 364 365 case TO_OBJECT: 366 assert args.length == 1; 367 WordCastNode wordToObject = b.add(WordCastNode.wordToObject(args[0], wordKind)); 368 b.push(returnKind, wordToObject); 369 break; 370 371 case TO_OBJECT_NON_NULL: 372 assert args.length == 1; 373 WordCastNode wordToObjectNonNull = b.add(WordCastNode.wordToObjectNonNull(args[0], wordKind)); 374 b.push(returnKind, wordToObjectNonNull); 375 break; 376 377 case CAS_POINTER: 378 assert args.length == 5; 379 AddressNode address = makeAddress(b, args[0], args[1]); 380 JavaKind valueKind = wordTypes.asKind(wordMethod.getSignature().getParameterType(1, wordMethod.getDeclaringClass())); 381 assert valueKind.equals(wordTypes.asKind(wordMethod.getSignature().getParameterType(2, wordMethod.getDeclaringClass()))) : wordMethod.getSignature(); 382 assert args[4].isConstant() : Arrays.toString(args); 383 LocationIdentity location = snippetReflection.asObject(LocationIdentity.class, args[4].asJavaConstant()); 384 JavaType returnType = wordMethod.getSignature().getReturnType(wordMethod.getDeclaringClass()); 385 b.addPush(returnKind, casOp(valueKind, wordTypes.asKind(returnType), address, location, args[2], args[3])); 386 break; 387 default: 388 throw new GraalError("Unknown opcode: %s", operation.opcode()); 389 } 390 } 391 392 /** 393 * Create an instance of a binary node which is used to lower {@link Word} operations. This 394 * method is called for all {@link Word} operations which are annotated with @Operation(node = 395 * ...) and encapsulates the reflective allocation of the node. 396 */ 397 private static ValueNode createBinaryNodeInstance(Class<? extends ValueNode> nodeClass, ValueNode left, ValueNode right) { 398 try { 399 Constructor<?> cons = nodeClass.getDeclaredConstructor(ValueNode.class, ValueNode.class); 400 return (ValueNode) cons.newInstance(left, right); 401 } catch (Throwable ex) { 402 throw new GraalError(ex).addContext(nodeClass.getName()); 403 } 404 } 405 406 private ValueNode comparisonOp(GraphBuilderContext graph, Condition condition, ValueNode left, ValueNode right) { 407 assert left.getStackKind() == wordKind && right.getStackKind() == wordKind; 408 409 // mirroring gets the condition into canonical form 410 boolean mirror = condition.canonicalMirror(); 411 412 ValueNode a = mirror ? right : left; 413 ValueNode b = mirror ? left : right; 414 415 CompareNode comparison; 416 if (condition == Condition.EQ || condition == Condition.NE) { 417 comparison = new IntegerEqualsNode(a, b); 418 } else if (condition.isUnsigned()) { 419 comparison = new IntegerBelowNode(a, b); 420 } else { 421 comparison = new IntegerLessThanNode(a, b); 422 } 423 424 ConstantNode trueValue = graph.add(forInt(1)); 425 ConstantNode falseValue = graph.add(forInt(0)); 426 427 if (condition.canonicalNegate()) { 428 ConstantNode temp = trueValue; 429 trueValue = falseValue; 430 falseValue = temp; 431 } 432 ConditionalNode materialize = graph.add(new ConditionalNode(graph.add(comparison), trueValue, falseValue)); 433 return materialize; 434 } 435 436 protected ValueNode readOp(GraphBuilderContext b, JavaKind readKind, AddressNode address, LocationIdentity location, Opcode op) { 437 assert op == Opcode.READ_POINTER || op == Opcode.READ_OBJECT || op == Opcode.READ_BARRIERED; 438 final BarrierType barrier = (op == Opcode.READ_BARRIERED ? BarrierType.PRECISE : BarrierType.NONE); 439 final boolean compressible = (op == Opcode.READ_OBJECT || op == Opcode.READ_BARRIERED); 440 441 return readOp(b, readKind, address, location, barrier, compressible); 442 } 443 444 public static ValueNode readOp(GraphBuilderContext b, JavaKind readKind, AddressNode address, LocationIdentity location, BarrierType barrierType, boolean compressible) { 445 /* 446 * A JavaReadNode lowered to a ReadNode that will not float. This means it cannot float 447 * above an explicit zero check on its base address or any other test that ensures the read 448 * is safe. 449 */ 450 JavaReadNode read = b.add(new JavaReadNode(readKind, address, location, barrierType, compressible)); 451 return read; 452 } 453 454 protected void writeOp(GraphBuilderContext b, JavaKind writeKind, AddressNode address, LocationIdentity location, ValueNode value, Opcode op) { 455 assert op == Opcode.WRITE_POINTER || op == Opcode.WRITE_OBJECT || op == Opcode.WRITE_BARRIERED || op == Opcode.INITIALIZE; 456 final BarrierType barrier = (op == Opcode.WRITE_BARRIERED ? BarrierType.PRECISE : BarrierType.NONE); 457 final boolean compressible = (op == Opcode.WRITE_OBJECT || op == Opcode.WRITE_BARRIERED); 458 assert op != Opcode.INITIALIZE || location.isInit() : "must use init location for initializing"; 459 b.add(new JavaWriteNode(writeKind, address, location, value, barrier, compressible)); 460 } 461 462 protected AbstractCompareAndSwapNode casOp(JavaKind writeKind, JavaKind returnKind, AddressNode address, LocationIdentity location, ValueNode expectedValue, ValueNode newValue) { 463 boolean isLogic = returnKind == JavaKind.Boolean; 464 assert isLogic || writeKind == returnKind : writeKind + " != " + returnKind; 465 AbstractCompareAndSwapNode cas; 466 if (isLogic) { 467 cas = new LogicCompareAndSwapNode(address, expectedValue, newValue, location); 468 } else { 469 cas = new ValueCompareAndSwapNode(address, expectedValue, newValue, location); 470 } 471 return cas; 472 } 473 474 public AddressNode makeAddress(GraphBuilderContext b, ValueNode base, ValueNode offset) { 475 return b.add(new OffsetAddressNode(base, fromSigned(b, offset))); 476 } 477 478 public ValueNode fromUnsigned(GraphBuilderContext b, ValueNode value) { 479 return convert(b, value, wordKind, true); 480 } 481 482 public ValueNode fromSigned(GraphBuilderContext b, ValueNode value) { 483 return convert(b, value, wordKind, false); 484 } 485 486 public ValueNode toUnsigned(GraphBuilderContext b, ValueNode value, JavaKind toKind) { 487 return convert(b, value, toKind, true); 488 } 489 490 public ValueNode convert(GraphBuilderContext b, ValueNode value, JavaKind toKind, boolean unsigned) { 491 if (value.getStackKind() == toKind) { 492 return value; 493 } 494 495 if (toKind == JavaKind.Int) { 496 assert value.getStackKind() == JavaKind.Long; 497 return b.add(new NarrowNode(value, 32)); 498 } else { 499 assert toKind == JavaKind.Long; 500 assert value.getStackKind() == JavaKind.Int; 501 if (unsigned) { 502 return b.add(new ZeroExtendNode(value, 64)); 503 } else { 504 return b.add(new SignExtendNode(value, 64)); 505 } 506 } 507 } 508 509 public WordTypes getWordTypes() { 510 return wordTypes; 511 } 512 513 private static BailoutException bailout(GraphBuilderContext b, String msg) { 514 throw b.bailout(msg + "\nat " + b.getCode().asStackTraceElement(b.bci())); 515 } 516 }