1 /*
  2  * Copyright (c) 2017, 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 
 24 package jdk.experimental.bytecode;
 25 
 26 import java.util.ArrayList;
 27 import java.util.HashMap;
 28 import java.util.Iterator;
 29 import java.util.LinkedHashMap;
 30 import java.util.LinkedList;
 31 import java.util.List;
 32 import java.util.Map;
 33 import java.util.TreeMap;
 34 import java.util.function.Consumer;
 35 
 36 public class MacroCodeBuilder<S, T, E, C extends MacroCodeBuilder<S, T, E, C>> extends CodeBuilder<S, T, E, C> {
 37 
 38     JumpMode jumpMode = JumpMode.NARROW;
 39 
 40     Map<CharSequence, Integer> labels = new HashMap<>();
 41     List<PendingJump> pendingJumps = new LinkedList<>();
 42 
 43     class PendingJump {
 44         CharSequence label;
 45         int pc;
 46 
 47         PendingJump(CharSequence label, int pc) {
 48             this.label = label;
 49             this.pc = pc;
 50         }
 51 
 52         boolean resolve(CharSequence label, int offset) {
 53             if (this.label.equals(label)) {
 54                 //patch offset
 55                 code.withOffset(pc + 1, buf -> emitOffset(buf, jumpMode, offset - pc));
 56                 return true;
 57             } else {
 58                 return false;
 59             }
 60         }
 61     }
 62 
 63     public enum InvocationKind {
 64         INVOKESTATIC,
 65         INVOKEVIRTUAL,
 66         INVOKESPECIAL,
 67         INVOKEINTERFACE;
 68     }
 69 
 70     public enum FieldAccessKind {
 71         STATIC,
 72         INSTANCE;
 73     }
 74 
 75     public enum CondKind {
 76         EQ(0),
 77         NE(1),
 78         LT(2),
 79         GE(3),
 80         GT(4),
 81         LE(5);
 82 
 83         int offset;
 84 
 85         CondKind(int offset) {
 86             this.offset = offset;
 87         }
 88 
 89         public CondKind negate() {
 90             switch (this) {
 91                 case EQ:
 92                     return NE;
 93                 case NE:
 94                     return EQ;
 95                 case LT:
 96                     return GE;
 97                 case GE:
 98                     return LT;
 99                 case GT:
100                     return LE;
101                 case LE:
102                     return GT;
103                 default:
104                     throw new IllegalStateException("Unknown cond");
105             }
106         }
107     }
108 
109     static class WideJumpException extends RuntimeException {
110         static final long serialVersionUID = 42L;
111     }
112 
113     public MacroCodeBuilder(MethodBuilder<S, T, E> methodBuilder) {
114         super(methodBuilder);
115     }
116 
117     public C load(TypeTag type, int n) {
118         switch (n) {
119             case 0:
120                 return emitOp(Opcode.ILOAD_0.at(type, 4));
121             case 1:
122                 return emitOp(Opcode.ILOAD_1.at(type, 4));
123             case 2:
124                 return emitOp(Opcode.ILOAD_2.at(type, 4));
125             case 3:
126                 return emitOp(Opcode.ILOAD_3.at(type, 4));
127             default:
128                 return emitWideIfNeeded(Opcode.ILOAD.at(type), n);
129         }
130     }
131 
132     public C store(TypeTag type, int n) {
133         switch (n) {
134             case 0:
135                 return emitOp(Opcode.ISTORE_0.at(type, 4));
136             case 1:
137                 return emitOp(Opcode.ISTORE_1.at(type, 4));
138             case 2:
139                 return emitOp(Opcode.ISTORE_2.at(type, 4));
140             case 3:
141                 return emitOp(Opcode.ISTORE_3.at(type, 4));
142             default:
143                 return emitWideIfNeeded(Opcode.ISTORE.at(type), n);
144         }
145     }
146 
147     public C arrayload(TypeTag type) {
148         return emitOp(Opcode.IALOAD.at(type));
149     }
150 
151     public C arraystore(TypeTag type, int n) {
152         return emitOp(Opcode.IASTORE.at(type));
153     }
154 
155     public C const_(int i) {
156         switch (i) {
157             case -1:
158                 return iconst_m1();
159             case 0:
160                 return iconst_0();
161             case 1:
162                 return iconst_1();
163             case 2:
164                 return iconst_2();
165             case 3:
166                 return iconst_3();
167             case 4:
168                 return iconst_4();
169             case 5:
170                 return iconst_5();
171             default:
172                 if (i > 0 && i <= Byte.MAX_VALUE) {
173                     return bipush(i);
174                 } else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
175                     return sipush(i);
176                 } else {
177                     return ldc(i);
178                 }
179         }
180     }
181 
182     public C const_(long l) {
183         if (l == 0) {
184             return lconst_0();
185         } else if (l == 1) {
186             return lconst_1();
187         } else {
188             return ldc(l);
189         }
190     }
191 
192     public C const_(float f) {
193         if (f == 0) {
194             return fconst_0();
195         } else if (f == 1) {
196             return fconst_1();
197         } else if (f == 2) {
198             return fconst_2();
199         } else {
200             return ldc(f);
201         }
202     }
203 
204     public C const_(double d) {
205         if (d == 0) {
206             return dconst_0();
207         } else if (d == 1) {
208             return dconst_1();
209         } else {
210             return ldc(d);
211         }
212     }
213 
214     public C getfield(FieldAccessKind fak, S owner, CharSequence name, T type) {
215         switch (fak) {
216             case INSTANCE:
217                 return getfield(owner, name, type);
218             case STATIC:
219                 return getstatic(owner, name, type);
220             default:
221                 throw new IllegalStateException();
222         }
223     }
224 
225     public C putfield(FieldAccessKind fak, S owner, CharSequence name, T type) {
226         switch (fak) {
227             case INSTANCE:
228                 return putfield(owner, name, type);
229             case STATIC:
230                 return putstatic(owner, name, type);
231             default:
232                 throw new IllegalStateException();
233         }
234     }
235 
236     public C invoke(InvocationKind ik, S owner, CharSequence name, T type, boolean isInterface) {
237         switch (ik) {
238             case INVOKESTATIC:
239                 return invokestatic(owner, name, type, isInterface);
240             case INVOKEVIRTUAL:
241                 return invokevirtual(owner, name, type, isInterface);
242             case INVOKESPECIAL:
243                 return invokespecial(owner, name, type, isInterface);
244             case INVOKEINTERFACE:
245                 if (!isInterface) throw new AssertionError();
246                 return invokeinterface(owner, name, type);
247             default:
248                 throw new IllegalStateException();
249         }
250     }
251 
252     public C add(TypeTag type) {
253         return emitOp(Opcode.IADD.at(type));
254     }
255 
256     public C sub(TypeTag type) {
257         return emitOp(Opcode.ISUB.at(type));
258     }
259 
260     public C mul(TypeTag type) {
261         return emitOp(Opcode.IMUL.at(type));
262     }
263 
264     public C div(TypeTag type) {
265         return emitOp(Opcode.IDIV.at(type));
266     }
267 
268     public C rem(TypeTag type) {
269         return emitOp(Opcode.IREM.at(type));
270     }
271 
272     public C neg(TypeTag type) {
273         return emitOp(Opcode.INEG.at(type));
274     }
275 
276     public C shl(TypeTag type) {
277         return emitOp(Opcode.ISHL.at(type));
278     }
279 
280     public C shr(TypeTag type) {
281         return emitOp(Opcode.ISHR.at(type));
282     }
283 
284     public C ushr(TypeTag type) {
285         return emitOp(Opcode.ISHR.at(type));
286     }
287 
288     public C and(TypeTag type) {
289         return emitOp(Opcode.IAND.at(type));
290     }
291 
292     public C or(TypeTag type) {
293         return emitOp(Opcode.IOR.at(type));
294     }
295 
296     public C xor(TypeTag type) {
297         return emitOp(Opcode.IXOR.at(type));
298     }
299 
300     public C return_(TypeTag type) {
301         switch (type) {
302             case V:
303             case Q:
304                 return return_();
305             default:
306                 return emitOp(Opcode.IRETURN.at(type));
307         }
308     }
309 
310     public C conv(TypeTag from, TypeTag to) {
311         switch (from) {
312             case B:
313             case C:
314             case S:
315                 switch (to) {
316                     case J:
317                         return i2l();
318                     case F:
319                         return i2f();
320                     case D:
321                         return i2d();
322                 }
323                 break;
324             case I:
325                 switch (to) {
326                     case J:
327                         return i2l();
328                     case F:
329                         return i2f();
330                     case D:
331                         return i2d();
332                     case B:
333                         return i2b();
334                     case C:
335                         return i2c();
336                     case S:
337                         return i2s();
338                 }
339                 break;
340             case J:
341                 switch (to) {
342                     case I:
343                         return l2i();
344                     case F:
345                         return l2f();
346                     case D:
347                         return l2d();
348                 }
349                 break;
350             case F:
351                 switch (to) {
352                     case I:
353                         return f2i();
354                     case J:
355                         return f2l();
356                     case D:
357                         return f2d();
358                 }
359                 break;
360             case D:
361                 switch (to) {
362                     case I:
363                         return d2i();
364                     case J:
365                         return d2l();
366                     case F:
367                         return d2f();
368                 }
369                 break;
370         }
371         //no conversion is necessary - do nothing!
372         return thisBuilder();
373     }
374 
375     public C if_null(CharSequence label) {
376         return emitCondJump(Opcode.IF_NULL, Opcode.IF_NONNULL, label);
377     }
378 
379     public C if_nonnull(CharSequence label) {
380         return emitCondJump(Opcode.IF_NONNULL, Opcode.IF_NULL, label);
381     }
382 
383     public C ifcmp(TypeTag type, CondKind cond, CharSequence label) {
384         switch (type) {
385             case I:
386                 return emitCondJump(Opcode.IF_ICMPEQ, cond, label);
387             case A:
388                 return emitCondJump(Opcode.IF_ACMPEQ, cond, label);
389             case J:
390                 return lcmp().emitCondJump(Opcode.IFEQ, cond, label);
391             case D:
392                 return dcmpg().emitCondJump(Opcode.IFEQ, cond, label);
393             case F:
394                 return fcmpg().emitCondJump(Opcode.IFEQ, cond, label);
395             default:
396                 throw new IllegalArgumentException("Bad cmp type");
397         }
398     }
399 
400     public C goto_(CharSequence label) {
401         emitOp(jumpMode == JumpMode.NARROW ? Opcode.GOTO_ : Opcode.GOTO_W);
402         emitOffset(code, jumpMode, labelOffset(label));
403         return thisBuilder();
404     }
405 
406     protected int labelOffset(CharSequence label) {
407         int pc = code.offset - 1;
408         Integer labelPc = labels.get(label);
409         if (labelPc == null) {
410             addPendingJump(label, pc);
411         }
412         return labelPc == null ? 0 : (labelPc - pc);
413     }
414 
415     public C label(CharSequence s) {
416         int pc = code.offset;
417         Object old = labels.put(s, pc);
418         if (old != null) {
419             throw new IllegalStateException("label already exists");
420         }
421         resolveJumps(s, pc);
422         return thisBuilder();
423     }
424 
425     //FIXME: address this jumpy mess - i.e. offset and state update work against each other!
426     public C emitCondJump(Opcode opcode, CondKind ck, CharSequence label) {
427         return emitCondJump(opcode.at(ck), opcode.at(ck.negate()), label);
428     }
429 
430     public C emitCondJump(Opcode pos, Opcode neg, CharSequence label) {
431         if (jumpMode == JumpMode.NARROW) {
432             emitOp(pos);
433             emitOffset(code, jumpMode, labelOffset(label));
434         } else {
435             emitOp(neg);
436             emitOffset(code, JumpMode.NARROW, 8);
437             goto_w(labelOffset(label));
438         }
439         return thisBuilder();
440     }
441 
442     void addPendingJump(CharSequence label, int pc) {
443         pendingJumps.add(new PendingJump(label, pc));
444     }
445 
446     void resolveJumps(CharSequence label, int pc) {
447         Iterator<PendingJump> jumpsIt = pendingJumps.iterator();
448         while (jumpsIt.hasNext()) {
449             PendingJump jump = jumpsIt.next();
450             if (jump.resolve(label, pc)) {
451                 jumpsIt.remove();
452             }
453         }
454     }
455 
456     @Override
457     protected void emitOffset(GrowableByteBuffer buf, JumpMode jumpMode, int offset) {
458         if (jumpMode == JumpMode.NARROW && (offset < Short.MIN_VALUE || offset > Short.MAX_VALUE)) {
459             throw new WideJumpException();
460         }
461         super.emitOffset(buf, jumpMode, offset);
462     }
463 
464     public C jsr(CharSequence label) {
465         emitOp(jumpMode == JumpMode.NARROW ? Opcode.JSR : Opcode.JSR_W);
466         emitOffset(code, jumpMode, labelOffset(label));
467         return thisBuilder();
468     }
469 
470     @SuppressWarnings("unchecked")
471     public C withTry(Consumer<? super C> tryBlock, Consumer<? super CatchBuilder> catchBlocks) {
472         int start = code.offset;
473         tryBlock.accept((C) this);
474         int end = code.offset;
475         CatchBuilder catchBuilder = makeCatchBuilder(start, end);
476         catchBlocks.accept(catchBuilder);
477         catchBuilder.build();
478         return thisBuilder();
479     }
480 
481     void clear() {
482         code.offset = 0;
483         catchers.offset = 0;
484         ncatchers = 0;
485         labels.clear();
486         pendingJumps = null;
487     }
488 
489     protected CatchBuilder makeCatchBuilder(int start, int end) {
490         return new CatchBuilder(start, end);
491     }
492 
493     public class CatchBuilder {
494         int start, end;
495 
496         String endLabel = labelName();
497 
498         Map<S, Consumer<? super C>> catchers = new LinkedHashMap<>();
499         public Consumer<? super C> finalizer;
500         List<Integer> pendingGaps = new ArrayList<>();
501 
502         public CatchBuilder(int start, int end) {
503             this.start = start;
504             this.end = end;
505         }
506 
507         public CatchBuilder withCatch(S exc, Consumer<? super C> catcher) {
508             catchers.put(exc, catcher);
509             return this;
510         }
511 
512         public CatchBuilder withFinally(Consumer<? super C> finalizer) {
513             this.finalizer = finalizer;
514             return this;
515         }
516 
517         @SuppressWarnings("unchecked")
518         void build() {
519             if (finalizer != null) {
520                 finalizer.accept((C) MacroCodeBuilder.this);
521             }
522             goto_(endLabel);
523             for (Map.Entry<S, Consumer<? super C>> catcher_entry : catchers.entrySet()) {
524                 emitCatch(catcher_entry.getKey(), catcher_entry.getValue());
525             }
526             if (finalizer != null) {
527                 emitFinalizer();
528             }
529             resolveJumps(endLabel, code.offset);
530         }
531 
532         @SuppressWarnings("unchecked")
533         protected void emitCatch(S exc, Consumer<? super C> catcher) {
534             int offset = code.offset;
535             MacroCodeBuilder.this.withCatch(exc, start, end, offset);
536             catcher.accept((C) MacroCodeBuilder.this);
537             if (finalizer != null) {
538                 int startFinalizer = code.offset;
539                 finalizer.accept((C) MacroCodeBuilder.this);
540                 pendingGaps.add(startFinalizer);
541                 pendingGaps.add(code.offset);
542             }
543             goto_(endLabel);
544         }
545 
546         @SuppressWarnings("unchecked")
547         protected void emitFinalizer() {
548             int offset = code.offset;
549             pop();
550             for (int i = 0; i < pendingGaps.size(); i += 2) {
551                 MacroCodeBuilder.this.withCatch(null, pendingGaps.get(i), pendingGaps.get(i + 1), offset);
552             }
553             MacroCodeBuilder.this.withCatch(null, start, end, offset);
554             finalizer.accept((C) MacroCodeBuilder.this);
555         }
556 
557 //        @SuppressWarnings("unchecked")
558 //        CatchBuilder withCatch(S exc, Consumer<? super C> catcher) {
559 //            int offset = code.offset;
560 //            MacroCodeBuilder.this.withCatch(exc, start, end, offset);
561 //            catcher.accept((C)MacroCodeBuilder.this);
562 //            return this;
563 //        }
564 //
565 //        @SuppressWarnings("unchecked")
566 //        CatchBuilder withFinally(Consumer<? super C> catcher) {
567 //            int offset = code.offset;
568 //            MacroCodeBuilder.this.withCatch(null, start, end, offset);
569 //            catcher.accept((C)MacroCodeBuilder.this);
570 //            return this;
571 //        }
572     }
573 
574     @SuppressWarnings("unchecked")
575     public C switch_(Consumer<? super SwitchBuilder> consumer) {
576         int start = code.offset;
577         SwitchBuilder sb = makeSwitchBuilder();
578         consumer.accept(sb);
579         int nlabels = sb.cases.size();
580         switch (sb.switchCode()) {
581             case LOOKUPSWITCH: {
582                 int[] lookupOffsets = new int[nlabels * 2];
583                 int i = 0;
584                 for (Integer v : sb.cases.keySet()) {
585                     lookupOffsets[i] = v;
586                     i += 2;
587                 }
588                 lookupswitch(0, lookupOffsets);
589                 //backpatch lookup
590                 int curr = code.offset - (8 * nlabels) - 8;
591                 int defaultOffset = code.offset - start;
592                 code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, defaultOffset));
593                 sb.defaultCase.accept((C) this);
594                 curr += 12;
595                 for (Consumer<? super C> case_ : sb.cases.values()) {
596                     int offset = code.offset;
597                     code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, offset - start));
598                     case_.accept((C) this);
599                     curr += 8;
600                 }
601                 break;
602             }
603             case TABLESWITCH: {
604                 int[] tableOffsets = new int[sb.hi - sb.lo + 1];
605                 tableswitch(sb.lo, sb.hi, 0, tableOffsets);
606                 //backpatch table
607                 int curr = code.offset - (4 * tableOffsets.length) - 12;
608                 int defaultOffset = code.offset - start;
609                 code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, defaultOffset));
610                 sb.defaultCase.accept((C) this);
611                 curr += 12;
612                 int lastCasePc = -1;
613                 for (int i = sb.lo; i <= sb.hi; i++) {
614                     Consumer<? super C> case_ = sb.cases.get(i);
615                     if (case_ != null) {
616                         lastCasePc = code.offset;
617                         case_.accept((C) this);
618                     }
619                     int offset = lastCasePc - start;
620                     code.withOffset(curr, buf -> emitOffset(buf, JumpMode.WIDE, offset));
621                     curr += 4;
622                 }
623             }
624         }
625         resolveJumps(sb.endLabel, code.offset);
626         return thisBuilder();
627     }
628 
629     private static int labelCount = 0;
630 
631     String labelName() {
632         return "label" + labelCount++;
633     }
634 
635     protected SwitchBuilder makeSwitchBuilder() {
636         return new SwitchBuilder();
637     }
638 
639     public class SwitchBuilder {
640         Map<Integer, Consumer<? super C>> cases = new TreeMap<>();
641         int lo = Integer.MAX_VALUE;
642         int hi = Integer.MIN_VALUE;
643         String endLabel = labelName();
644 
645         public Consumer<? super C> defaultCase;
646 
647         @SuppressWarnings("unchecked")
648         public SwitchBuilder withCase(int value, Consumer<? super C> case_, boolean fallthrough) {
649             if (value > hi) {
650                 hi = value;
651             }
652             if (value < lo) {
653                 lo = value;
654             }
655             if (!fallthrough) {
656                 Consumer<? super C> prevCase = case_;
657                 case_ = C -> {
658                     prevCase.accept(C);
659                     C.goto_(endLabel);
660                 };
661             }
662             cases.put(value, case_);
663             return this;
664         }
665 
666         @SuppressWarnings("unchecked")
667         public SwitchBuilder withDefault(Consumer<? super C> defaultCase) {
668             if (this.defaultCase != null) {
669                 throw new IllegalStateException("default already set");
670             }
671             this.defaultCase = defaultCase;
672             return this;
673         }
674 
675         Opcode switchCode() {
676             int nlabels = cases.size();
677             // Determine whether to issue a tableswitch or a lookupswitch
678             // instruction.
679             long table_space_cost = 4 + ((long) hi - lo + 1); // words
680             long lookup_space_cost = 3 + 2 * (long) nlabels;
681             return
682                     nlabels > 0 &&
683                             table_space_cost <= lookup_space_cost
684                             ?
685                             Opcode.TABLESWITCH : Opcode.LOOKUPSWITCH;
686         }
687     }
688 }