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