/* * 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.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.Consumer; public class MacroCodeBuilder> extends CodeBuilder { JumpMode jumpMode = JumpMode.NARROW; Map labels = new HashMap<>(); List pendingJumps = new LinkedList<>(); class PendingJump { CharSequence label; int pc; PendingJump(CharSequence label, int pc) { this.label = label; this.pc = pc; } boolean resolve(CharSequence label, int offset) { if (this.label.equals(label)) { //patch offset code.withOffset(pc + 1, buf -> emitOffset(buf, jumpMode, offset - pc)); return true; } else { return false; } } } public enum InvocationKind { INVOKESTATIC, INVOKEVIRTUAL, INVOKESPECIAL, INVOKEINTERFACE; } public enum FieldAccessKind { STATIC, INSTANCE; } public enum CondKind { EQ(0), NE(1), LT(2), GE(3), GT(4), LE(5); int offset; CondKind(int offset) { this.offset = offset; } public CondKind negate() { switch (this) { case EQ: return NE; case NE: return EQ; case LT: return GE; case GE: return LT; case GT: return LE; case LE: return GT; default: throw new IllegalStateException("Unknown cond"); } } } static class WideJumpException extends RuntimeException { static final long serialVersionUID = 42L; } public MacroCodeBuilder(MethodBuilder methodBuilder) { super(methodBuilder); } public C load(TypeTag type, int n) { if (type == TypeTag.Q) { return vload(n); } else { switch (n) { case 0: return emitOp(Opcode.ILOAD_0.at(type, 4)); case 1: return emitOp(Opcode.ILOAD_1.at(type, 4)); case 2: return emitOp(Opcode.ILOAD_2.at(type, 4)); case 3: return emitOp(Opcode.ILOAD_3.at(type, 4)); default: return emitWideIfNeeded(Opcode.ILOAD.at(type), n); } } } public C store(TypeTag type, int n) { if (type == TypeTag.Q) { return vstore(n); } else { switch (n) { case 0: return emitOp(Opcode.ISTORE_0.at(type, 4)); case 1: return emitOp(Opcode.ISTORE_1.at(type, 4)); case 2: return emitOp(Opcode.ISTORE_2.at(type, 4)); case 3: return emitOp(Opcode.ISTORE_3.at(type, 4)); default: return emitWideIfNeeded(Opcode.ISTORE.at(type), n); } } } public C arrayload(TypeTag type) { return emitOp(Opcode.IALOAD.at(type)); } public C arraystore(TypeTag type, int n) { return emitOp(Opcode.IASTORE.at(type)); } public C const_(int i) { switch (i) { case -1: return iconst_m1(); case 0: return iconst_0(); case 1: return iconst_1(); case 2: return iconst_2(); case 3: return iconst_3(); case 4: return iconst_4(); case 5: return iconst_5(); default: if (i > 0 && i <= Byte.MAX_VALUE) { return bipush(i); } else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) { return sipush(i); } else { return ldc(i); } } } public C const_(long l) { if (l == 0) { return lconst_0(); } else if (l == 1) { return lconst_1(); } else { return ldc(l); } } public C const_(float f) { if (f == 0) { return fconst_0(); } else if (f == 1) { return fconst_1(); } else if (f == 2) { return fconst_2(); } else { return ldc(f); } } public C const_(double d) { if (d == 0) { return dconst_0(); } else if (d == 1) { return dconst_1(); } else { return ldc(d); } } public C getfield(FieldAccessKind fak, S owner, CharSequence name, T type) { switch (fak) { case INSTANCE: return getfield(owner, name, type); case STATIC: return getstatic(owner, name, type); default: throw new IllegalStateException(); } } public C putfield(FieldAccessKind fak, S owner, CharSequence name, T type) { switch (fak) { case INSTANCE: return putfield(owner, name, type); case STATIC: return putstatic(owner, name, type); default: throw new IllegalStateException(); } } public C invoke(InvocationKind ik, S owner, CharSequence name, T type, boolean isInterface) { switch (ik) { case INVOKESTATIC: return invokestatic(owner, name, type, isInterface); case INVOKEVIRTUAL: return invokevirtual(owner, name, type, isInterface); case INVOKESPECIAL: return invokespecial(owner, name, type, isInterface); case INVOKEINTERFACE: if (!isInterface) throw new AssertionError(); return invokeinterface(owner, name, type); default: throw new IllegalStateException(); } } public C add(TypeTag type) { return emitOp(Opcode.IADD.at(type)); } public C sub(TypeTag type) { return emitOp(Opcode.ISUB.at(type)); } public C mul(TypeTag type) { return emitOp(Opcode.IMUL.at(type)); } public C div(TypeTag type) { return emitOp(Opcode.IDIV.at(type)); } public C rem(TypeTag type) { return emitOp(Opcode.IREM.at(type)); } public C neg(TypeTag type) { return emitOp(Opcode.INEG.at(type)); } public C shl(TypeTag type) { return emitOp(Opcode.ISHL.at(type)); } public C shr(TypeTag type) { return emitOp(Opcode.ISHR.at(type)); } public C ushr(TypeTag type) { return emitOp(Opcode.ISHR.at(type)); } public C and(TypeTag type) { return emitOp(Opcode.IAND.at(type)); } public C or(TypeTag type) { return emitOp(Opcode.IOR.at(type)); } public C xor(TypeTag type) { return emitOp(Opcode.IXOR.at(type)); } public C return_(TypeTag type) { switch (type) { case V: return return_(); case Q: return vreturn(); default: return emitOp(Opcode.IRETURN.at(type)); } } @Override public LabelledTypedBuilder typed(TypeTag typeTag) { return super.typed(typeTag, _unused -> new LabelledTypedBuilder()); } public class LabelledTypedBuilder extends TypedBuilder { public C if_acmpeq(CharSequence target) { return ifcmp(TypeTag.A, CondKind.EQ, target); } public C if_acmpne(CharSequence target) { return ifcmp(TypeTag.A, CondKind.NE, target); } } public C conv(TypeTag from, TypeTag to) { switch (from) { case B: case C: case S: switch (to) { case J: return i2l(); case F: return i2f(); case D: return i2d(); } break; case I: switch (to) { case J: return i2l(); case F: return i2f(); case D: return i2d(); case B: return i2b(); case C: return i2c(); case S: return i2s(); } break; case J: switch (to) { case I: return l2i(); case F: return l2f(); case D: return l2d(); } break; case F: switch (to) { case I: return f2i(); case J: return f2l(); case D: return f2d(); } break; case D: switch (to) { case I: return d2i(); case J: return d2l(); case F: return d2f(); } break; } //no conversion is necessary - do nothing! return thisBuilder(); } public C if_null(CharSequence label) { return emitCondJump(Opcode.IF_NULL, Opcode.IF_NONNULL, label); } public C if_nonnull(CharSequence label) { return emitCondJump(Opcode.IF_NONNULL, Opcode.IF_NULL, label); } public C ifcmp(TypeTag type, CondKind cond, CharSequence label) { switch (type) { case I: return emitCondJump(Opcode.IF_ICMPEQ, cond, label); case A: return emitCondJump(Opcode.IF_ACMPEQ, cond, label); case J: return lcmp().emitCondJump(Opcode.IFEQ, cond, label); case D: return dcmpg().emitCondJump(Opcode.IFEQ, cond, label); case F: return fcmpg().emitCondJump(Opcode.IFEQ, cond, label); default: throw new IllegalArgumentException("Bad cmp type"); } } public C goto_(CharSequence label) { emitOp(jumpMode == JumpMode.NARROW ? Opcode.GOTO_ : Opcode.GOTO_W); emitOffset(code, jumpMode, labelOffset(label)); return thisBuilder(); } protected int labelOffset(CharSequence label) { int pc = code.offset - 1; Integer labelPc = labels.get(label); if (labelPc == null) { addPendingJump(label, pc); } return labelPc == null ? 0 : (labelPc - pc); } public C label(CharSequence s) { int pc = code.offset; Object old = labels.put(s, pc); if (old != null) { throw new IllegalStateException("label already exists"); } resolveJumps(s, pc); return thisBuilder(); } //FIXME: address this jumpy mess - i.e. offset and state update work against each other! public C emitCondJump(Opcode opcode, CondKind ck, CharSequence label) { return emitCondJump(opcode.at(ck), opcode.at(ck.negate()), label); } public C emitCondJump(Opcode pos, Opcode neg, CharSequence label) { if (jumpMode == JumpMode.NARROW) { emitOp(pos); emitOffset(code, jumpMode, labelOffset(label)); } else { emitOp(neg); emitOffset(code, JumpMode.NARROW, 8); goto_w(labelOffset(label)); } return thisBuilder(); } void addPendingJump(CharSequence label, int pc) { pendingJumps.add(new PendingJump(label, pc)); } void resolveJumps(CharSequence label, int pc) { Iterator jumpsIt = pendingJumps.iterator(); while (jumpsIt.hasNext()) { PendingJump jump = jumpsIt.next(); if (jump.resolve(label, pc)) { jumpsIt.remove(); } } } @Override protected void emitOffset(GrowableByteBuffer buf, JumpMode jumpMode, int offset) { if (jumpMode == JumpMode.NARROW && (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE)) { throw new WideJumpException(); } super.emitOffset(buf, jumpMode, offset); } public C jsr(CharSequence label) { emitOp(jumpMode == JumpMode.NARROW ? Opcode.JSR : Opcode.JSR_W); emitOffset(code, jumpMode, labelOffset(label)); return thisBuilder(); } @SuppressWarnings("unchecked") public C withTry(Consumer tryBlock, Consumer catchBlocks) { int start = code.offset; tryBlock.accept((C) this); int end = code.offset; CatchBuilder catchBuilder = makeCatchBuilder(start, end); catchBlocks.accept(catchBuilder); catchBuilder.build(); return thisBuilder(); } void clear() { code.offset = 0; catchers.offset = 0; ncatchers = 0; labels.clear(); pendingJumps = null; } protected CatchBuilder makeCatchBuilder(int start, int end) { return new CatchBuilder(start, end); } public class CatchBuilder { int start, end; String endLabel = labelName(); Map> catchers = new LinkedHashMap<>(); public Consumer finalizer; List pendingGaps = new ArrayList<>(); public CatchBuilder(int start, int end) { this.start = start; this.end = end; } public CatchBuilder withCatch(S exc, Consumer catcher) { catchers.put(exc, catcher); return this; } public CatchBuilder withFinally(Consumer finalizer) { this.finalizer = finalizer; return this; } @SuppressWarnings("unchecked") void build() { if (finalizer != null) { finalizer.accept((C) MacroCodeBuilder.this); } goto_(endLabel); for (Map.Entry> catcher_entry : catchers.entrySet()) { emitCatch(catcher_entry.getKey(), catcher_entry.getValue()); } if (finalizer != null) { emitFinalizer(); } resolveJumps(endLabel, code.offset); } @SuppressWarnings("unchecked") protected void emitCatch(S exc, Consumer catcher) { int offset = code.offset; MacroCodeBuilder.this.withCatch(exc, start, end, offset); catcher.accept((C) MacroCodeBuilder.this); if (finalizer != null) { int startFinalizer = code.offset; finalizer.accept((C) MacroCodeBuilder.this); pendingGaps.add(startFinalizer); pendingGaps.add(code.offset); } goto_(endLabel); } @SuppressWarnings("unchecked") protected void emitFinalizer() { int offset = code.offset; pop(); for (int i = 0; i < pendingGaps.size(); i += 2) { MacroCodeBuilder.this.withCatch(null, pendingGaps.get(i), pendingGaps.get(i + 1), offset); } MacroCodeBuilder.this.withCatch(null, start, end, offset); finalizer.accept((C) MacroCodeBuilder.this); } // @SuppressWarnings("unchecked") // CatchBuilder withCatch(S exc, Consumer catcher) { // int offset = code.offset; // MacroCodeBuilder.this.withCatch(exc, start, end, offset); // catcher.accept((C)MacroCodeBuilder.this); // return this; // } // // @SuppressWarnings("unchecked") // CatchBuilder withFinally(Consumer catcher) { // int offset = code.offset; // MacroCodeBuilder.this.withCatch(null, start, end, offset); // catcher.accept((C)MacroCodeBuilder.this); // return this; // } } @SuppressWarnings("unchecked") public C switch_(Consumer consumer) { int start = code.offset; SwitchBuilder sb = makeSwitchBuilder(); consumer.accept(sb); int nlabels = sb.cases.size(); switch (sb.switchCode()) { case LOOKUPSWITCH: { int[] lookupOffsets = new int[nlabels * 2]; int i = 0; for (Integer v : sb.cases.keySet()) { lookupOffsets[i] = v; i += 2; } lookupswitch(0, lookupOffsets); //backpatch lookup int curr = code.offset - (8 * nlabels) - 8; int defaultOffset = code.offset - start; code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, defaultOffset)); sb.defaultCase.accept((C) this); curr += 12; for (Consumer case_ : sb.cases.values()) { int offset = code.offset; code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, offset - start)); case_.accept((C) this); curr += 8; } break; } case TABLESWITCH: { int[] tableOffsets = new int[sb.hi - sb.lo + 1]; tableswitch(sb.lo, sb.hi, 0, tableOffsets); //backpatch table int curr = code.offset - (4 * tableOffsets.length) - 12; int defaultOffset = code.offset - start; code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, defaultOffset)); sb.defaultCase.accept((C) this); curr += 12; int lastCasePc = -1; for (int i = sb.lo; i <= sb.hi; i++) { Consumer case_ = sb.cases.get(i); if (case_ != null) { lastCasePc = code.offset; case_.accept((C) this); } int offset = lastCasePc - start; code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, offset)); curr += 4; } } } resolveJumps(sb.endLabel, code.offset); return thisBuilder(); } private static int labelCount = 0; String labelName() { return "label" + labelCount++; } protected SwitchBuilder makeSwitchBuilder() { return new SwitchBuilder(); } public class SwitchBuilder { Map> cases = new TreeMap<>(); int lo = Integer.MAX_VALUE; int hi = Integer.MIN_VALUE; String endLabel = labelName(); public Consumer defaultCase; @SuppressWarnings("unchecked") public SwitchBuilder withCase(int value, Consumer case_, boolean fallthrough) { if (value > hi) { hi = value; } if (value < lo) { lo = value; } if (!fallthrough) { Consumer prevCase = case_; case_ = C -> { prevCase.accept(C); C.goto_(endLabel); }; } cases.put(value, case_); return this; } @SuppressWarnings("unchecked") public SwitchBuilder withDefault(Consumer defaultCase) { if (this.defaultCase != null) { throw new IllegalStateException("default already set"); } this.defaultCase = defaultCase; return this; } Opcode switchCode() { int nlabels = cases.size(); // Determine whether to issue a tableswitch or a lookupswitch // instruction. long table_space_cost = 4 + ((long) hi - lo + 1); // words long lookup_space_cost = 3 + 2 * (long) nlabels; return nlabels > 0 && table_space_cost <= lookup_space_cost ? Opcode.TABLESWITCH : Opcode.LOOKUPSWITCH; } } }