/* * Copyright (c) 2017, 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 jdk.experimental.bytecode; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.function.ToIntFunction; public class TypedCodeBuilder> extends MacroCodeBuilder { State lastStackMapState; int lastStackMapPc = -1; Map lvarOffsets = new HashMap<>(); protected State state; int depth = 0; int currLocalOffset = 0; class StatefulPendingJump extends PendingJump { State state; StatefulPendingJump(CharSequence label, int pc, State state) { super(label, pc); this.state = state; } @Override boolean resolve(CharSequence label, int pc) { boolean b = super.resolve(label, pc); if (b) { TypedCodeBuilder.this.state = TypedCodeBuilder.this.state.merge(state); } return b; } } class LocalVarInfo { CharSequence name; int offset; int depth; TypeTag type; LocalVarInfo(CharSequence name, int offset, int depth, TypeTag type) { this.name = name; this.offset = offset; this.depth = depth; this.type = type; } } public TypedCodeBuilder(MethodBuilder methodBuilder) { super(methodBuilder); T t = methodBuilder.desc; state = new State(); if ((methodBuilder.flags & Flag.ACC_STATIC.flag) == 0) { T clazz = typeHelper.type(methodBuilder.thisClass); state.load(clazz, currLocalOffset++); //TODO: uninit?? } Iterator paramsIt = typeHelper.parameterTypes(t); while (paramsIt.hasNext()) { T p = paramsIt.next(); state.load(p, currLocalOffset); currLocalOffset += typeHelper.tag(p).width; } lastStackMapState = state.dup(); stacksize = state.stack.size(); localsize = state.locals.size(); } @Override protected C emitOp(Opcode opcode, Object optPoolValue) { updateState(opcode, optPoolValue); return super.emitOp(opcode, optPoolValue); } @Override protected SwitchBuilder makeSwitchBuilder() { return new TypedSwitchBuilder(); } class TypedSwitchBuilder extends SwitchBuilder { @Override public SwitchBuilder withCase(int value, Consumer case_, boolean fallthrough) { super.withCase(value, c -> { withLocalScope(() -> { State prevState = state; state = prevState.dup(); emitStackMap(c.offset()); case_.accept(c); state = prevState; }); }, fallthrough); return this; } @Override public SwitchBuilder withDefault(Consumer defaultCase) { super.withDefault(c -> { withLocalScope(() -> { State prevState = state; state = prevState.dup(); emitStackMap(c.offset()); defaultCase.accept(c); state = prevState; }); }); return this; } } @Override public StatefulTypedBuilder typed(TypeTag tag) { return super.typed(tag, StatefulTypedBuilder::new); } public class StatefulTypedBuilder extends LabelledTypedBuilder { TypeTag tag; StatefulTypedBuilder(TypeTag tag) { this.tag = tag; } @Override public C astore_0() { return storeAndUpdate(super::astore_0); } @Override public C astore_1() { return storeAndUpdate(super::astore_1); } @Override public C astore_2() { return storeAndUpdate(super::astore_2); } @Override public C astore_3() { return storeAndUpdate(super::astore_3); } @Override public C astore(int n) { return storeAndUpdate(() -> super.astore(n)); } @Override public C aastore() { return storeAndUpdate(super::aastore); } @Override public C areturn() { state.pop(tag); state.push(typeHelper.nullType()); return super.areturn(); } @Override public C anewarray(S s) { super.anewarray(s); state.pop(); state.push(typeHelper.arrayOf(typeHelper.type(s))); return thisBuilder(); } @Override public C aconst_null() { super.aconst_null(); state.pop(); state.push(tag); return thisBuilder(); } public C if_acmpeq(CharSequence label) { return jumpAndUpdate(() -> super.if_acmpeq(label)); } public C if_acmpne(CharSequence label) { return jumpAndUpdate(() -> super.if_acmpne(label)); } private C storeAndUpdate(Supplier op) { state.pop(tag); state.push(typeHelper.nullType()); return op.get(); } private C jumpAndUpdate(Supplier op) { state.pop(tag); state.pop(tag); state.push(typeHelper.nullType()); state.push(typeHelper.nullType()); return op.get(); } } public class State { public final ArrayList stack; public final Vector locals; boolean alive; State(ArrayList stack, Vector locals) { this.stack = stack; this.locals = locals; } State() { this(new ArrayList<>(), new Vector<>()); } void push(TypeTag tag) { switch (tag) { case A: case V: throw new IllegalStateException("Bad type tag"); default: push(typeHelper.fromTag(tag)); } } void push(T t) { stack.add(t); if (width(t) == 2) { stack.add(null); } if (stack.size() > stacksize) { stacksize = stack.size(); } } T peek() { return stack.get(stack.size() - 1); } T tosType() { T tos = peek(); if (tos == null) { //double slot tos = stack.get(stack.size() - 2); } return tos; } T popInternal() { return stack.remove(stack.size() - 1); } @SuppressWarnings("unchecked") T pop() { if (stack.size() == 0 || peek() == null) throw new IllegalStateException(); return popInternal(); } T pop2() { T o = stack.get(stack.size() - 2); TypeTag t = typeHelper.tag(o); if (t.width != 2) throw new IllegalStateException(); popInternal(); popInternal(); return o; } T pop(TypeTag t) { return (t.width() == 2) ? pop2() : pop(); } void load(TypeTag tag, int index) { if (tag == TypeTag.A) throw new IllegalStateException("Bad type tag"); load(typeHelper.fromTag(tag), index); } void load(T t, int index) { ensureDefined(index); locals.set(index, t); if (width(t) == 2) { locals.add(null); } if (locals.size() > localsize) { localsize = locals.size(); } } void ensureDefined(int index) { if (index >= locals.size()) { locals.setSize(index + 1); } } State dup() { State newState = new State(new ArrayList<>(stack), new Vector<>(locals)); return newState; } State merge(State that) { if (!alive) { return that; } if (that.stack.size() != stack.size()) { throw new IllegalStateException("Bad stack size at merge point"); } for (int i = 0; i < stack.size(); i++) { T t1 = stack.get(i); T t2 = that.stack.get(i); stack.set(i, merge(t1, t2, "Bad stack type at merge point")); } int nlocals = locals.size() > that.locals.size() ? that.locals.size() : locals.size(); for (int i = 0; i < nlocals; i++) { T t1 = locals.get(i); T t2 = that.locals.get(i); locals.set(i, merge(t1, t2, "Bad local type at merge point")); } if (locals.size() > nlocals) { for (int i = nlocals; i < locals.size(); i++) { locals.remove(i); } } return this; } T merge(T t1, T t2, String msg) { if (t1 == null && t2 == null) { return t1; } T res; TypeTag tag1 = typeHelper.tag(t1); TypeTag tag2 = typeHelper.tag(t2); if (tag1 != TypeTag.A && tag2 != TypeTag.A && tag1 != TypeTag.Q && tag2 != TypeTag.Q) { res = typeHelper.fromTag(TypeTag.commonSupertype(tag1, tag2)); } else if (t1 == typeHelper.nullType()) { res = t2; } else if (t2 == typeHelper.nullType()) { res = t1; } else { res = typeHelper.commonSupertype(t1, t2); } if (res == null) { throw new IllegalStateException(msg); } return res; } @Override public String toString() { return String.format("[locals = %s, stack = %s]", locals, stack); } } int width(T o) { return o == typeHelper.nullType() ? TypeTag.A.width() : typeHelper.tag(o).width; } @SuppressWarnings("unchecked") public void updateState(Opcode op, Object optValue) { switch (op) { case VALOAD: case AALOAD: state.pop(); state.push(typeHelper.elemtype(state.pop())); break; case GOTO_: state.alive = false; break; case NOP: case INEG: case LNEG: case FNEG: case DNEG: break; case ACONST_NULL: state.push(typeHelper.nullType()); break; case ICONST_M1: case ICONST_0: case ICONST_1: case ICONST_2: case ICONST_3: case ICONST_4: case ICONST_5: state.push(TypeTag.I); break; case LCONST_0: case LCONST_1: state.push(TypeTag.J); break; case FCONST_0: case FCONST_1: case FCONST_2: state.push(TypeTag.F); break; case DCONST_0: case DCONST_1: state.push(TypeTag.D); break; case ILOAD_0: case FLOAD_0: case ALOAD_0: case LLOAD_0: case DLOAD_0: state.push(state.locals.get(0)); break; case ILOAD_1: case FLOAD_1: case ALOAD_1: case LLOAD_1: case DLOAD_1: state.push(state.locals.get(1)); break; case ILOAD_2: case FLOAD_2: case ALOAD_2: case LLOAD_2: case DLOAD_2: state.push(state.locals.get(2)); break; case ILOAD_3: case FLOAD_3: case ALOAD_3: case LLOAD_3: case DLOAD_3: state.push(state.locals.get(3)); break; case ILOAD: case FLOAD: case ALOAD: case LLOAD: case DLOAD: case VLOAD: state.push(state.locals.get((Integer) optValue)); break; case IALOAD: case BALOAD: case CALOAD: case SALOAD: state.pop(); state.pop(); state.push(TypeTag.I); break; case LALOAD: state.pop(); state.pop(); state.push(TypeTag.J); break; case FALOAD: state.pop(); state.pop(); state.push(TypeTag.F); break; case DALOAD: state.pop(); state.pop(); state.push(TypeTag.D); break; case ISTORE_0: case FSTORE_0: case ASTORE_0: state.load(state.pop(), 0); break; case ISTORE_1: case FSTORE_1: case ASTORE_1: state.load(state.pop(), 1); break; case ISTORE_2: case FSTORE_2: case ASTORE_2: state.load(state.pop(), 2); break; case ISTORE_3: case FSTORE_3: case ASTORE_3: state.load(state.pop(), 3); break; case ISTORE: case FSTORE: case ASTORE: case VSTORE: state.load(state.pop(), (int) optValue); break; case LSTORE_0: case DSTORE_0: state.load(state.pop2(), 0); break; case LSTORE_1: case DSTORE_1: state.load(state.pop2(), 1); break; case LSTORE_2: case DSTORE_2: state.load(state.pop2(), 2); break; case LSTORE_3: case DSTORE_3: state.load(state.pop2(), 3); break; case LSTORE: case DSTORE: state.load(state.pop2(), (int) optValue); break; case POP: case LSHR: case LSHL: case LUSHR: state.pop(); break; case VRETURN: case ARETURN: case IRETURN: case FRETURN: state.pop(); break; case ATHROW: state.pop(); break; case POP2: state.pop2(); break; case LRETURN: case DRETURN: state.pop2(); break; case DUP: state.push(state.peek()); break; case RETURN: break; case ARRAYLENGTH: state.pop(); state.push(TypeTag.I); break; case ISUB: case IADD: case IMUL: case IDIV: case IREM: case ISHL: case ISHR: case IUSHR: case IAND: case IOR: case IXOR: state.pop(); state.pop(); state.push(TypeTag.I); break; case VASTORE: case AASTORE: state.pop(); state.pop(); state.pop(); break; case LAND: case LOR: case LXOR: case LREM: case LDIV: case LMUL: case LSUB: case LADD: state.pop2(); state.pop2(); state.push(TypeTag.J); break; case LCMP: state.pop2(); state.pop2(); state.push(TypeTag.I); break; case L2I: state.pop2(); state.push(TypeTag.I); break; case I2L: state.pop(); state.push(TypeTag.J); break; case I2F: state.pop(); state.push(TypeTag.F); break; case I2D: state.pop(); state.push(TypeTag.D); break; case L2F: state.pop2(); state.push(TypeTag.F); break; case L2D: state.pop2(); state.push(TypeTag.D); break; case F2I: state.pop(); state.push(TypeTag.I); break; case F2L: state.pop(); state.push(TypeTag.J); break; case F2D: state.pop(); state.push(TypeTag.D); break; case D2I: state.pop2(); state.push(TypeTag.I); break; case D2L: state.pop2(); state.push(TypeTag.J); break; case D2F: state.pop2(); state.push(TypeTag.F); break; case TABLESWITCH: case LOOKUPSWITCH: state.pop(); break; case DUP_X1: { T val1 = state.pop(); T val2 = state.pop(); state.push(val1); state.push(val2); state.push(val1); break; } case BASTORE: state.pop(); state.pop(); state.pop(); break; case I2B: case I2C: case I2S: break; case FMUL: case FADD: case FSUB: case FDIV: case FREM: state.pop(); state.pop(); state.push(TypeTag.F); break; case CASTORE: case IASTORE: case FASTORE: case SASTORE: state.pop(); state.pop(); state.pop(); break; case LASTORE: case DASTORE: state.pop2(); state.pop(); state.pop(); break; case DUP2: if (state.peek() != null) { //form 1 T value1 = state.pop(); T value2 = state.pop(); state.push(value2); state.push(value1); state.push(value2); state.push(value1); } else { //form 2 T value = state.pop2(); state.push(value); state.push(value); } break; case DUP2_X1: if (state.peek() != null) { T value1 = state.pop(); T value2 = state.pop(); T value3 = state.pop(); state.push(value2); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { T value1 = state.pop2(); T value2 = state.pop(); state.push(value1); state.push(value2); state.push(value1); } break; case DUP2_X2: if (state.peek() != null) { T value1 = state.pop(); T value2 = state.pop(); if (state.peek() != null) { // form 1 T value3 = state.pop(); T value4 = state.pop(); state.push(value2); state.push(value1); state.push(value4); state.push(value3); state.push(value2); state.push(value1); } else { // form 3 T value3 = state.pop2(); state.push(value2); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } } else { T value1 = state.pop2(); if (state.peek() != null) { // form 2 T value2 = state.pop(); T value3 = state.pop(); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { // form 4 T value2 = state.pop2(); state.push(value1); state.push(value2); state.push(value1); } } break; case DUP_X2: { T value1 = state.pop(); if (state.peek() != null) { // form 1 T value2 = state.pop(); T value3 = state.pop(); state.push(value1); state.push(value3); state.push(value2); state.push(value1); } else { // form 2 T value2 = state.pop2(); state.push(value1); state.push(value2); state.push(value1); } } break; case FCMPL: case FCMPG: state.pop(); state.pop(); state.push(TypeTag.I); break; case DCMPL: case DCMPG: state.pop2(); state.pop2(); state.push(TypeTag.I); break; case SWAP: { T value1 = state.pop(); T value2 = state.pop(); state.push(value1); state.push(value2); break; } case DADD: case DSUB: case DMUL: case DDIV: case DREM: state.pop2(); state.pop2(); state.push(TypeTag.D); break; case RET: break; case WIDE: // must be handled by the caller. return; case MONITORENTER: case MONITOREXIT: state.pop(); break; case VNEW: case NEW: state.push(typeHelper.type((S) optValue)); break; case NEWARRAY: state.pop(); state.push(typeHelper.arrayOf(typeHelper.fromTag((TypeTag) optValue))); break; case ANEWARRAY: state.pop(); state.push(typeHelper.arrayOf(typeHelper.arrayOf(typeHelper.type((S)optValue)))); break; case VNEWARRAY: case VBOX: case VUNBOX: state.pop(); state.push(typeHelper.type((S) optValue)); break; case MULTIVNEWARRAY: case MULTIANEWARRAY: for (int i = 0; i < (byte) ((Object[]) optValue)[1]; i++) { state.pop(); } state.push(typeHelper.type((S) ((Object[]) optValue)[0])); break; case INVOKEINTERFACE: case INVOKEVIRTUAL: case INVOKESPECIAL: case INVOKESTATIC: case INVOKEDYNAMIC: processInvoke(op, (T) optValue); break; case GETSTATIC: state.push((T) optValue); break; case VGETFIELD: case GETFIELD: state.pop(); state.push((T) optValue); break; case PUTSTATIC: { TypeTag tag = typeHelper.tag((T) optValue); if (tag.width == 1) { state.pop(); } else { state.pop2(); } break; } case PUTFIELD: { TypeTag tag = typeHelper.tag((T) optValue); if (tag.width == 1) { state.pop(); } else { state.pop2(); } state.pop(); break; } case BIPUSH: case SIPUSH: state.push(TypeTag.I); break; case LDC: case LDC_W: case LDC2_W: state.push((T)optValue); break; case IF_ACMPEQ: case IF_ICMPEQ: case IF_ACMPNE: case IF_ICMPGE: case IF_ICMPGT: case IF_ICMPLE: case IF_ICMPLT: case IF_ICMPNE: state.pop(); state.pop(); break; case IF_NONNULL: case IF_NULL: case IFEQ: case IFGE: case IFGT: case IFLE: case IFLT: case IFNE: state.pop(); break; case INSTANCEOF: state.pop(); state.push(TypeTag.Z); break; case TYPED: case CHECKCAST: break; default: throw new UnsupportedOperationException("Unsupported opcode: " + op); } } void processInvoke(Opcode opcode, T invokedType) { Iterator paramsIt = typeHelper.parameterTypes(invokedType); while (paramsIt.hasNext()) { T t = paramsIt.next(); TypeTag tag = typeHelper.tag(t); if (tag.width == 2) { state.popInternal(); state.popInternal(); } else { state.popInternal(); } } if (opcode != Opcode.INVOKESTATIC && opcode != Opcode.INVOKEDYNAMIC) { state.pop(); //receiver } T retType = typeHelper.returnType(invokedType); TypeTag retTag = typeHelper.tag(retType); if (retTag != TypeTag.V) state.push(retType); } @Override protected C ldc(ToIntFunction> indexFunc, boolean fat) { LdcPoolHelper ldcPoolHelper = new LdcPoolHelper(); int index = indexFunc.applyAsInt(ldcPoolHelper); fat = typeHelper.tag(ldcPoolHelper.type).width() == 2; return super.ldc(index, ldcPoolHelper.type, fat); } //where class LdcPoolHelper implements PoolHelper { T type; @Override public int putClass(S symbol) { type = typeHelper.type(symbol); return poolHelper.putClass(symbol); } @Override public int putInt(int i) { type = typeHelper.fromTag(TypeTag.I); return poolHelper.putInt(i); } @Override public int putFloat(float f) { type = typeHelper.fromTag(TypeTag.F); return poolHelper.putFloat(f); } @Override public int putLong(long l) { type = typeHelper.fromTag(TypeTag.J); return poolHelper.putLong(l); } @Override public int putDouble(double d) { type = typeHelper.fromTag(TypeTag.D); return poolHelper.putDouble(d); } @Override public int putString(String s) { type = typeHelper.type(typeHelper.symbolFrom("java/lang/String")); return poolHelper.putString(s); } @Override public int putDynamicConstant(CharSequence constName, T constType, S bsmClass, CharSequence bsmName, T bsmType, Consumer> staticArgs) { type = constType; return poolHelper.putDynamicConstant(constName, constType, bsmClass, bsmName, bsmType, staticArgs); } @Override public int putFieldRef(S owner, CharSequence name, T type) { throw new IllegalStateException(); } @Override public int putMethodRef(S owner, CharSequence name, T type, boolean isInterface) { throw new IllegalStateException(); } @Override public int putUtf8(CharSequence s) { throw new IllegalStateException(); } @Override public int putType(T t) { throw new IllegalStateException(); } @Override public int putMethodType(T t) { type = typeHelper.type(typeHelper.symbolFrom("java/lang/invoke/MethodType")); return poolHelper.putMethodType(t); } @Override public int putHandle(int refKind, S owner, CharSequence name, T t) { type = typeHelper.type(typeHelper.symbolFrom("java/lang/invoke/MethodHandle")); return poolHelper.putHandle(refKind, owner, name, t); } @Override public int putHandle(int refKind, S owner, CharSequence name, T t, boolean isInterface) { type = typeHelper.type(typeHelper.symbolFrom("java/lang/invoke/MethodHandle")); return poolHelper.putHandle(refKind, owner, name, t, isInterface); } @Override public int putInvokeDynamic(CharSequence invokedName, T invokedType, S bsmClass, CharSequence bsmName, T bsmType, Consumer> staticArgs) { throw new IllegalStateException(); } @Override public int size() { throw new IllegalStateException(); } @Override public E entries() { throw new IllegalStateException(); } } public C load(int index) { return load(typeHelper.tag(state.locals.get(index)), index); } public C store(int index) { return store(typeHelper.tag(state.tosType()), index); } @Override public C withLocalSize(int localsize) { throw new IllegalStateException("Local size automatically computed"); } @Override public C withStackSize(int stacksize) { throw new IllegalStateException("Stack size automatically computed"); } public C withLocal(CharSequence name, T type) { int offset = currLocalOffset; TypeTag tag = typeHelper.tag(type); lvarOffsets.put(name, new LocalVarInfo(name, offset, depth, tag)); state.load(type, offset); currLocalOffset += tag.width; return thisBuilder(); } public C load(CharSequence local) { return load(lvarOffsets.get(local).offset); } public C store(CharSequence local) { return store(lvarOffsets.get(local).offset); } @Override public C withTry(Consumer tryBlock, Consumer catchBlocks) { return super.withTry(c -> { withLocalScope(() -> { tryBlock.accept(c); }); }, catchBlocks); } @Override protected CatchBuilder makeCatchBuilder(int start, int end) { return new TypedCatchBuilder(start, end); } class TypedCatchBuilder extends CatchBuilder { State initialState = state.dup(); TypedCatchBuilder(int start, int end) { super(start, end); } @Override protected void emitCatch(S exc, Consumer catcher) { withLocalScope(() -> { state.push(typeHelper.type(exc)); emitStackMap(code.offset); super.emitCatch(exc, catcher); state = initialState; }); } @Override protected void emitFinalizer() { withLocalScope(() -> { state.push(typeHelper.type(typeHelper.symbolFrom("java/lang/Throwable"))); emitStackMap(code.offset); super.emitFinalizer(); }); } } protected void withLocalScope(Runnable runnable) { int prevDepth = depth; try { depth++; runnable.run(); } finally { Iterator> lvarIt = lvarOffsets.entrySet().iterator(); while (lvarIt.hasNext()) { LocalVarInfo lvi = lvarIt.next().getValue(); if (lvi.depth == depth) { int width = lvi.type.width; currLocalOffset -= width; lvarIt.remove(); } } depth = prevDepth; } } @Override void addPendingJump(CharSequence label, int pc) { pendingJumps.add(new StatefulPendingJump(label, pc, state.dup())); } @Override void resolveJumps(CharSequence label, int pc) { super.resolveJumps(label, pc); emitStackMap(pc); } //TODO: optimize stackmap generation by avoiding intermediate classes protected void emitStackMap(int pc) { //stack map generation if (pc > lastStackMapPc) { writeStackMapFrame(pc); lastStackMapState = state.dup(); lastStackMapPc = pc; nstackmaps++; } } @Override void build(GrowableByteBuffer buf) { if (stacksize == -1) { throw new IllegalStateException("Bad stack size"); } if (localsize == -1) { throw new IllegalStateException("Bad locals size"); } if (nstackmaps > 0) { GrowableByteBuffer stackmapsAttr = new GrowableByteBuffer(); stackmapsAttr.writeChar(nstackmaps); stackmapsAttr.writeBytes(stackmaps); withAttribute("StackMapTable", stackmapsAttr.bytes()); } super.build(buf); } /** * Compare this frame with the previous frame and produce * an entry of compressed stack map frame. */ void writeStackMapFrame(int pc) { List locals = state.locals; List stack = state.stack; List prev_locals = lastStackMapState.locals; int offset_delta = lastStackMapPc == -1 ? pc : pc - lastStackMapPc - 1; if (stack.size() == 1) { if (locals.size() == prev_locals.size() && prev_locals.equals(locals)) { sameLocals1StackItemFrame(offset_delta, stack.get(stack.size() - 1)); return; } } else if (stack.size() == 0) { int diff_length = prev_locals.size() - locals.size(); if (diff_length == 0) { sameFrame(offset_delta); return; } else if (-MAX_LOCAL_LENGTH_DIFF < diff_length && diff_length < 0) { appendFrame(offset_delta, prev_locals.size(), locals); return; } else if (0 < diff_length && diff_length < MAX_LOCAL_LENGTH_DIFF) { chopFrame(offset_delta, diff_length); return; } } fullFrame(offset_delta, locals, stack); } }