1.1 --- a/src/jdk/nashorn/internal/codegen/Label.java Wed Aug 20 10:25:28 2014 +0200 1.2 +++ b/src/jdk/nashorn/internal/codegen/Label.java Wed Aug 20 10:26:01 2014 +0200 1.3 @@ -24,8 +24,13 @@ 1.4 */ 1.5 package jdk.nashorn.internal.codegen; 1.6 1.7 +import java.util.ArrayList; 1.8 +import java.util.Arrays; 1.9 +import java.util.BitSet; 1.10 +import java.util.Iterator; 1.11 +import java.util.List; 1.12 +import java.util.ListIterator; 1.13 import jdk.nashorn.internal.codegen.types.Type; 1.14 -import jdk.nashorn.internal.runtime.Debug; 1.15 1.16 /** 1.17 * Abstraction for labels, separating a label from the underlying 1.18 @@ -38,20 +43,23 @@ 1.19 //byte code generation evaluation type stack for consistency check 1.20 //and correct opcode selection. one per label as a label may be a 1.21 //join point 1.22 - static final class Stack { 1.23 - Type[] data = new Type[8]; 1.24 - int sp = 0; 1.25 + static final class Stack implements Cloneable { 1.26 + static final int NON_LOAD = -1; 1.27 + 1.28 + Type[] data; 1.29 + int[] localLoads; 1.30 + int sp; 1.31 + 1.32 + List<Type> localVariableTypes; 1.33 + int firstTemp; // index of the first temporary local variable 1.34 + // Bitmap marking last slot belonging to a single symbol. 1.35 + BitSet symbolBoundary; 1.36 1.37 Stack() { 1.38 - } 1.39 - 1.40 - private Stack(final Type[] type, final int sp) { 1.41 - this(); 1.42 - this.data = new Type[type.length]; 1.43 - this.sp = sp; 1.44 - for (int i = 0; i < sp; i++) { 1.45 - data[i] = type[i]; 1.46 - } 1.47 + data = new Type[8]; 1.48 + localLoads = new int[8]; 1.49 + localVariableTypes = new ArrayList<>(8); 1.50 + symbolBoundary = new BitSet(); 1.51 } 1.52 1.53 boolean isEmpty() { 1.54 @@ -62,7 +70,147 @@ 1.55 return sp; 1.56 } 1.57 1.58 - boolean isEquivalentTo(final Stack other) { 1.59 + void clear() { 1.60 + sp = 0; 1.61 + } 1.62 + 1.63 + void push(final Type type) { 1.64 + if (data.length == sp) { 1.65 + final Type[] newData = new Type[sp * 2]; 1.66 + final int[] newLocalLoad = new int[sp * 2]; 1.67 + System.arraycopy(data, 0, newData, 0, sp); 1.68 + System.arraycopy(localLoads, 0, newLocalLoad, 0, sp); 1.69 + data = newData; 1.70 + localLoads = newLocalLoad; 1.71 + } 1.72 + data[sp] = type; 1.73 + localLoads[sp] = NON_LOAD; 1.74 + sp++; 1.75 + } 1.76 + 1.77 + Type peek() { 1.78 + return peek(0); 1.79 + } 1.80 + 1.81 + Type peek(final int n) { 1.82 + final int pos = sp - 1 - n; 1.83 + return pos < 0 ? null : data[pos]; 1.84 + } 1.85 + 1.86 + /** 1.87 + * Retrieve the top <tt>count</tt> types on the stack without modifying it. 1.88 + * 1.89 + * @param count number of types to return 1.90 + * @return array of Types 1.91 + */ 1.92 + Type[] getTopTypes(final int count) { 1.93 + final Type[] topTypes = new Type[count]; 1.94 + System.arraycopy(data, sp - count, topTypes, 0, count); 1.95 + return topTypes; 1.96 + } 1.97 + 1.98 + int[] getLocalLoads(final int from, final int to) { 1.99 + final int count = to - from; 1.100 + final int[] topLocalLoads = new int[count]; 1.101 + System.arraycopy(localLoads, from, topLocalLoads, 0, count); 1.102 + return topLocalLoads; 1.103 + } 1.104 + 1.105 + /** 1.106 + * Returns the number of used local variable slots, including all live stack-store temporaries. 1.107 + * @return the number of used local variable slots, including all live stack-store temporaries. 1.108 + */ 1.109 + int getUsedSlotsWithLiveTemporaries() { 1.110 + // There are at least as many as are declared by the current blocks. 1.111 + int usedSlots = firstTemp; 1.112 + // Look at every load on the stack, and bump the number of used slots up by the temporaries seen there. 1.113 + for(int i = sp; i-->0;) { 1.114 + final int slot = localLoads[i]; 1.115 + if(slot != Label.Stack.NON_LOAD) { 1.116 + final int afterSlot = slot + localVariableTypes.get(slot).getSlots(); 1.117 + if(afterSlot > usedSlots) { 1.118 + usedSlots = afterSlot; 1.119 + } 1.120 + } 1.121 + } 1.122 + return usedSlots; 1.123 + } 1.124 + 1.125 + /** 1.126 + * 1.127 + * @param joinOrigin the stack from the other branch. 1.128 + */ 1.129 + void joinFrom(final Stack joinOrigin, final boolean breakTarget) { 1.130 + assert isStackCompatible(joinOrigin); 1.131 + if(breakTarget) { 1.132 + // As we're joining labels that can jump across block boundaries, the number of local variables can 1.133 + // differ, and we should always respect the one having less variables. 1.134 + firstTemp = Math.min(firstTemp, joinOrigin.firstTemp); 1.135 + } else { 1.136 + assert firstTemp == joinOrigin.firstTemp; 1.137 + } 1.138 + final int[] otherLoads = joinOrigin.localLoads; 1.139 + int firstDeadTemp = firstTemp; 1.140 + for(int i = 0; i < sp; ++i) { 1.141 + final int localLoad = localLoads[i]; 1.142 + if(localLoad != otherLoads[i]) { 1.143 + localLoads[i] = NON_LOAD; 1.144 + } else if(localLoad >= firstDeadTemp) { 1.145 + firstDeadTemp = localLoad + localVariableTypes.get(localLoad).getSlots(); 1.146 + } 1.147 + } 1.148 + // Eliminate dead temporaries 1.149 + undefineLocalVariables(firstDeadTemp, false); 1.150 + assert isVariablePartitioningEqual(joinOrigin, firstDeadTemp); 1.151 + mergeVariableTypes(joinOrigin, firstDeadTemp); 1.152 + } 1.153 + 1.154 + private void mergeVariableTypes(final Stack joinOrigin, final int toSlot) { 1.155 + final ListIterator<Type> it1 = localVariableTypes.listIterator(); 1.156 + final Iterator<Type> it2 = joinOrigin.localVariableTypes.iterator(); 1.157 + 1.158 + for(int i = 0; i < toSlot; ++i) { 1.159 + final Type thisType = it1.next(); 1.160 + final Type otherType = it2.next(); 1.161 + if(otherType == Type.UNKNOWN) { 1.162 + // Variables that are <unknown> on the other branch will become <unknown> here too. 1.163 + it1.set(Type.UNKNOWN); 1.164 + } else if (thisType != otherType) { 1.165 + if(thisType.isObject() && otherType.isObject()) { 1.166 + // different object types are merged into Object. 1.167 + // TODO: maybe find most common superclass? 1.168 + it1.set(Type.OBJECT); 1.169 + } else { 1.170 + assert thisType == Type.UNKNOWN; 1.171 + } 1.172 + } 1.173 + } 1.174 + } 1.175 + 1.176 + void joinFromTry(final Stack joinOrigin) { 1.177 + // As we're joining labels that can jump across block boundaries, the number of local variables can 1.178 + // differ, and we should always respect the one having less variables. 1.179 + firstTemp = Math.min(firstTemp, joinOrigin.firstTemp); 1.180 + assert isVariablePartitioningEqual(joinOrigin, firstTemp); 1.181 + mergeVariableTypes(joinOrigin, firstTemp); 1.182 + } 1.183 + 1.184 + private int getFirstDeadLocal(final List<Type> types) { 1.185 + int i = types.size(); 1.186 + for(final ListIterator<Type> it = types.listIterator(i); 1.187 + it.hasPrevious() && it.previous() == Type.UNKNOWN; 1.188 + --i) { 1.189 + // no body 1.190 + } 1.191 + 1.192 + // Respect symbol boundaries; we never chop off half a symbol's storage 1.193 + while(!symbolBoundary.get(i - 1)) { 1.194 + ++i; 1.195 + } 1.196 + return i; 1.197 + } 1.198 + 1.199 + private boolean isStackCompatible(final Stack other) { 1.200 if (sp != other.sp) { 1.201 return false; 1.202 } 1.203 @@ -74,51 +222,271 @@ 1.204 return true; 1.205 } 1.206 1.207 - void clear() { 1.208 - sp = 0; 1.209 + private boolean isVariablePartitioningEqual(final Stack other, final int toSlot) { 1.210 + // No difference in the symbol boundaries before the toSlot 1.211 + final BitSet diff = other.getSymbolBoundaryCopy(); 1.212 + diff.xor(symbolBoundary); 1.213 + return diff.previousSetBit(toSlot - 1) == -1; 1.214 } 1.215 1.216 - void push(final Type type) { 1.217 - if (data.length == sp) { 1.218 - final Type[] newData = new Type[sp * 2]; 1.219 - for (int i = 0; i < sp; i++) { 1.220 - newData[i] = data[i]; 1.221 - } 1.222 - data = newData; 1.223 + void markDeadLocalVariables(final int fromSlot, final int slotCount) { 1.224 + final int localCount = localVariableTypes.size(); 1.225 + if(fromSlot >= localCount) { 1.226 + return; 1.227 } 1.228 - data[sp++] = type; 1.229 + final int toSlot = Math.min(fromSlot + slotCount, localCount); 1.230 + invalidateLocalLoadsOnStack(fromSlot, toSlot); 1.231 + for(int i = fromSlot; i < toSlot; ++i) { 1.232 + localVariableTypes.set(i, Type.UNKNOWN); 1.233 + } 1.234 } 1.235 1.236 - Type peek() { 1.237 - return peek(0); 1.238 + @SuppressWarnings("unchecked") 1.239 + List<Type> getLocalVariableTypesCopy() { 1.240 + return (List<Type>)((ArrayList<Type>)localVariableTypes).clone(); 1.241 } 1.242 1.243 - Type peek(final int n) { 1.244 - final int pos = sp - 1 - n; 1.245 - return pos < 0 ? null : data[pos]; 1.246 + BitSet getSymbolBoundaryCopy() { 1.247 + return (BitSet)symbolBoundary.clone(); 1.248 + } 1.249 + 1.250 + /** 1.251 + * Returns a list of local variable slot types, but for those symbols that have multiple values, only the slot 1.252 + * holding the widest type is marked as live. 1.253 + * @return a list of widest local variable slot types. 1.254 + */ 1.255 + List<Type> getWidestLiveLocals(final List<Type> lvarTypes) { 1.256 + final List<Type> widestLiveLocals = new ArrayList<>(lvarTypes); 1.257 + boolean keepNextValue = true; 1.258 + final int size = widestLiveLocals.size(); 1.259 + for(int i = size - 1; i-- > 0;) { 1.260 + if(symbolBoundary.get(i)) { 1.261 + keepNextValue = true; 1.262 + } 1.263 + final Type t = widestLiveLocals.get(i); 1.264 + if(t != Type.UNKNOWN) { 1.265 + if(keepNextValue) { 1.266 + if(t != Type.SLOT_2) { 1.267 + keepNextValue = false; 1.268 + } 1.269 + } else { 1.270 + widestLiveLocals.set(i, Type.UNKNOWN); 1.271 + } 1.272 + } 1.273 + } 1.274 + widestLiveLocals.subList(Math.max(getFirstDeadLocal(widestLiveLocals), firstTemp), widestLiveLocals.size()).clear(); 1.275 + return widestLiveLocals; 1.276 + } 1.277 + 1.278 + String markSymbolBoundariesInLvarTypesDescriptor(final String lvarDescriptor) { 1.279 + final char[] chars = lvarDescriptor.toCharArray(); 1.280 + int j = 0; 1.281 + for(int i = 0; i < chars.length; ++i) { 1.282 + final char c = chars[i]; 1.283 + final int nextj = j + CodeGeneratorLexicalContext.getTypeForSlotDescriptor(c).getSlots(); 1.284 + if(!symbolBoundary.get(nextj - 1)) { 1.285 + chars[i] = Character.toLowerCase(c); 1.286 + } 1.287 + j = nextj; 1.288 + } 1.289 + return new String(chars); 1.290 } 1.291 1.292 Type pop() { 1.293 + assert sp > 0; 1.294 return data[--sp]; 1.295 } 1.296 1.297 - Stack copy() { 1.298 - return new Stack(data, sp); 1.299 + @Override 1.300 + public Stack clone() { 1.301 + try { 1.302 + final Stack clone = (Stack)super.clone(); 1.303 + clone.data = data.clone(); 1.304 + clone.localLoads = localLoads.clone(); 1.305 + clone.symbolBoundary = getSymbolBoundaryCopy(); 1.306 + clone.localVariableTypes = getLocalVariableTypesCopy(); 1.307 + return clone; 1.308 + } catch(final CloneNotSupportedException e) { 1.309 + throw new AssertionError("", e); 1.310 + } 1.311 + } 1.312 + 1.313 + private Stack cloneWithEmptyStack() { 1.314 + final Stack stack = clone(); 1.315 + stack.sp = 0; 1.316 + return stack; 1.317 + } 1.318 + 1.319 + int getTopLocalLoad() { 1.320 + return localLoads[sp - 1]; 1.321 + } 1.322 + 1.323 + void markLocalLoad(final int slot) { 1.324 + localLoads[sp - 1] = slot; 1.325 + } 1.326 + 1.327 + /** 1.328 + * Performs various bookeeping when a value is stored in a local variable slot. 1.329 + * @param slot the slot written to 1.330 + * @param onlySymbolLiveValue if true, this is the symbol's only live value, and other values of the symbol 1.331 + * should be marked dead 1.332 + * @param Type the type written to the slot 1.333 + */ 1.334 + void onLocalStore(final Type type, final int slot, final boolean onlySymbolLiveValue) { 1.335 + if(onlySymbolLiveValue) { 1.336 + final int fromSlot = slot == 0 ? 0 : (symbolBoundary.previousSetBit(slot - 1) + 1); 1.337 + final int toSlot = symbolBoundary.nextSetBit(slot) + 1; 1.338 + for(int i = fromSlot; i < toSlot; ++i) { 1.339 + localVariableTypes.set(i, Type.UNKNOWN); 1.340 + } 1.341 + invalidateLocalLoadsOnStack(fromSlot, toSlot); 1.342 + } else { 1.343 + invalidateLocalLoadsOnStack(slot, slot + type.getSlots()); 1.344 + } 1.345 + 1.346 + localVariableTypes.set(slot, type); 1.347 + if(type.isCategory2()) { 1.348 + localVariableTypes.set(slot + 1, Type.SLOT_2); 1.349 + } 1.350 + } 1.351 + 1.352 + /** 1.353 + * Given a slot range, invalidate knowledge about local loads on stack from these slots (because they're being 1.354 + * killed). 1.355 + * @param fromSlot first slot, inclusive. 1.356 + * @param toSlot last slot, exclusive. 1.357 + */ 1.358 + private void invalidateLocalLoadsOnStack(final int fromSlot, final int toSlot) { 1.359 + for(int i = 0; i < sp; ++i) { 1.360 + final int localLoad = localLoads[i]; 1.361 + if(localLoad >= fromSlot && localLoad < toSlot) { 1.362 + localLoads[i] = NON_LOAD; 1.363 + } 1.364 + } 1.365 + } 1.366 + 1.367 + /** 1.368 + * Marks a range of slots as belonging to a defined local variable. The slots will start out with no live value 1.369 + * in them. 1.370 + * @param fromSlot first slot, inclusive. 1.371 + * @param toSlot last slot, exclusive. 1.372 + */ 1.373 + void defineBlockLocalVariable(final int fromSlot, final int toSlot) { 1.374 + defineLocalVariable(fromSlot, toSlot); 1.375 + assert firstTemp < toSlot; 1.376 + firstTemp = toSlot; 1.377 + } 1.378 + 1.379 + /** 1.380 + * Defines a new temporary local variable and returns its allocated index. 1.381 + * @param width the required width (in slots) for the new variable. 1.382 + * @return the bytecode slot index where the newly allocated local begins. 1.383 + */ 1.384 + int defineTemporaryLocalVariable(final int width) { 1.385 + final int fromSlot = getUsedSlotsWithLiveTemporaries(); 1.386 + defineLocalVariable(fromSlot, fromSlot + width); 1.387 + return fromSlot; 1.388 + } 1.389 + 1.390 + /** 1.391 + * Marks a range of slots as belonging to a defined temporary local variable. The slots will start out with no 1.392 + * live value in them. 1.393 + * @param fromSlot first slot, inclusive. 1.394 + * @param toSlot last slot, exclusive. 1.395 + */ 1.396 + void defineTemporaryLocalVariable(final int fromSlot, final int toSlot) { 1.397 + defineLocalVariable(fromSlot, toSlot); 1.398 + } 1.399 + 1.400 + private void defineLocalVariable(final int fromSlot, final int toSlot) { 1.401 + assert !hasLoadsOnStack(fromSlot, toSlot); 1.402 + assert fromSlot < toSlot; 1.403 + symbolBoundary.clear(fromSlot, toSlot - 1); 1.404 + symbolBoundary.set(toSlot - 1); 1.405 + final int lastExisting = Math.min(toSlot, localVariableTypes.size()); 1.406 + for(int i = fromSlot; i < lastExisting; ++i) { 1.407 + localVariableTypes.set(i, Type.UNKNOWN); 1.408 + } 1.409 + for(int i = lastExisting; i < toSlot; ++i) { 1.410 + localVariableTypes.add(i, Type.UNKNOWN); 1.411 + } 1.412 + } 1.413 + 1.414 + /** 1.415 + * Undefines all local variables past the specified slot. 1.416 + * @param fromSlot the first slot to be undefined 1.417 + * @param canTruncateSymbol if false, the fromSlot must be either the first slot of a symbol, or the first slot 1.418 + * after the last symbol. If true, the fromSlot can be in the middle of the storage area for a symbol. This 1.419 + * should be used with care - it is only meant for use in optimism exception handlers. 1.420 + */ 1.421 + void undefineLocalVariables(final int fromSlot, final boolean canTruncateSymbol) { 1.422 + final int lvarCount = localVariableTypes.size(); 1.423 + assert lvarCount == symbolBoundary.length(); 1.424 + assert !hasLoadsOnStack(fromSlot, lvarCount); 1.425 + if(canTruncateSymbol) { 1.426 + if(fromSlot > 0) { 1.427 + symbolBoundary.set(fromSlot - 1); 1.428 + } 1.429 + } else { 1.430 + assert fromSlot == 0 || symbolBoundary.get(fromSlot - 1); 1.431 + } 1.432 + if(fromSlot < lvarCount) { 1.433 + symbolBoundary.clear(fromSlot, lvarCount); 1.434 + localVariableTypes.subList(fromSlot, lvarCount).clear(); 1.435 + } 1.436 + firstTemp = Math.min(fromSlot, firstTemp); 1.437 + assert symbolBoundary.length() == localVariableTypes.size(); 1.438 + assert symbolBoundary.length() == fromSlot; 1.439 + } 1.440 + 1.441 + private void markAsOptimisticCatchHandler(final int liveLocalCount) { 1.442 + // Live temporaries that are no longer on stack are undefined 1.443 + undefineLocalVariables(liveLocalCount, true); 1.444 + // Temporaries are promoted 1.445 + firstTemp = liveLocalCount; 1.446 + // No trailing undefineds 1.447 + localVariableTypes.subList(firstTemp, localVariableTypes.size()).clear(); 1.448 + assert symbolBoundary.length() == firstTemp; 1.449 + // Generalize all reference types to Object, and promote boolean to int 1.450 + for(final ListIterator<Type> it = localVariableTypes.listIterator(); it.hasNext();) { 1.451 + final Type type = it.next(); 1.452 + if(type == Type.BOOLEAN) { 1.453 + it.set(Type.INT); 1.454 + } else if(type.isObject() && type != Type.OBJECT) { 1.455 + it.set(Type.OBJECT); 1.456 + } 1.457 + } 1.458 + } 1.459 + 1.460 + /** 1.461 + * Returns true if any loads on the stack come from the specified slot range. 1.462 + * @param fromSlot start of the range (inclusive) 1.463 + * @param toSlot end of the range (exclusive) 1.464 + * @return true if any loads on the stack come from the specified slot range. 1.465 + */ 1.466 + boolean hasLoadsOnStack(final int fromSlot, final int toSlot) { 1.467 + for(int i = 0; i < sp; ++i) { 1.468 + final int load = localLoads[i]; 1.469 + if(load >= fromSlot && load < toSlot) { 1.470 + return true; 1.471 + } 1.472 + } 1.473 + return false; 1.474 } 1.475 1.476 @Override 1.477 public String toString() { 1.478 - final StringBuilder builder = new StringBuilder("["); 1.479 - for (int i = 0; i < sp; i++) { 1.480 - builder.append(data[i]); 1.481 - if (i < sp - 1) { 1.482 - builder.append(", "); 1.483 - } 1.484 - } 1.485 - return builder.append("]").toString(); 1.486 + return "stack=" + Arrays.toString(Arrays.copyOf(data, sp)) 1.487 + + ", symbolBoundaries=" + String.valueOf(symbolBoundary) 1.488 + + ", firstTemp=" + firstTemp 1.489 + + ", localTypes=" + String.valueOf(localVariableTypes) 1.490 + ; 1.491 } 1.492 } 1.493 1.494 + /** Next id for debugging purposes, remove if footprint becomes unmanageable */ 1.495 + private static int nextId = 0; 1.496 + 1.497 /** Name of this label */ 1.498 private final String name; 1.499 1.500 @@ -128,6 +496,14 @@ 1.501 /** ASM representation of this label */ 1.502 private jdk.internal.org.objectweb.asm.Label label; 1.503 1.504 + /** Id for debugging purposes, remove if footprint becomes unmanageable */ 1.505 + private final int id; 1.506 + 1.507 + /** Is this label reachable (anything ever jumped to it)? */ 1.508 + private boolean reachable; 1.509 + 1.510 + private boolean breakTarget; 1.511 + 1.512 /** 1.513 * Constructor 1.514 * 1.515 @@ -136,6 +512,7 @@ 1.516 public Label(final String name) { 1.517 super(); 1.518 this.name = name; 1.519 + this.id = nextId++; 1.520 } 1.521 1.522 /** 1.523 @@ -146,9 +523,9 @@ 1.524 public Label(final Label label) { 1.525 super(); 1.526 this.name = label.name; 1.527 + this.id = label.id; 1.528 } 1.529 1.530 - 1.531 jdk.internal.org.objectweb.asm.Label getLabel() { 1.532 if (this.label == null) { 1.533 this.label = new jdk.internal.org.objectweb.asm.Label(); 1.534 @@ -160,12 +537,61 @@ 1.535 return stack; 1.536 } 1.537 1.538 - void setStack(final Label.Stack stack) { 1.539 - this.stack = stack; 1.540 + void joinFrom(final Label.Stack joinOrigin) { 1.541 + this.reachable = true; 1.542 + if(stack == null) { 1.543 + stack = joinOrigin.clone(); 1.544 + } else { 1.545 + stack.joinFrom(joinOrigin, breakTarget); 1.546 + } 1.547 + } 1.548 + 1.549 + void joinFromTry(final Label.Stack joinOrigin, final boolean isOptimismHandler) { 1.550 + this.reachable = true; 1.551 + if (stack == null) { 1.552 + if(!isOptimismHandler) { 1.553 + stack = joinOrigin.cloneWithEmptyStack(); 1.554 + // Optimism handler needs temporaries to remain live, others don't. 1.555 + stack.undefineLocalVariables(stack.firstTemp, false); 1.556 + } 1.557 + } else { 1.558 + assert !isOptimismHandler; 1.559 + stack.joinFromTry(joinOrigin); 1.560 + } 1.561 + } 1.562 + 1.563 + void markAsBreakTarget() { 1.564 + breakTarget = true; 1.565 + } 1.566 + 1.567 + boolean isBreakTarget() { 1.568 + return breakTarget; 1.569 + } 1.570 + 1.571 + void onCatch() { 1.572 + if(stack != null) { 1.573 + stack = stack.cloneWithEmptyStack(); 1.574 + } 1.575 + } 1.576 + void markAsOptimisticCatchHandler(final Label.Stack currentStack, final int liveLocalCount) { 1.577 + stack = currentStack.cloneWithEmptyStack(); 1.578 + stack.markAsOptimisticCatchHandler(liveLocalCount); 1.579 + } 1.580 + 1.581 + void markAsOptimisticContinuationHandlerFor(final Label afterConsumeStackLabel) { 1.582 + stack = afterConsumeStackLabel.stack.cloneWithEmptyStack(); 1.583 + } 1.584 + 1.585 + boolean isReachable() { 1.586 + return reachable; 1.587 + } 1.588 + 1.589 + boolean isAfter(final Label other) { 1.590 + return label.getOffset() > other.label.getOffset(); 1.591 } 1.592 1.593 @Override 1.594 public String toString() { 1.595 - return name + '_' + Debug.id(this); 1.596 + return name + '_' + id; 1.597 } 1.598 }