Code.java revision b3b96215f3dcbacb3f0d86780ac635cfc14ae9cf
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.dexmaker;
18
19import com.android.dx.rop.code.BasicBlockList;
20import com.android.dx.rop.code.Insn;
21import com.android.dx.rop.code.PlainCstInsn;
22import com.android.dx.rop.code.PlainInsn;
23import com.android.dx.rop.code.RegisterSpecList;
24import com.android.dx.rop.code.Rop;
25import static com.android.dx.rop.code.Rop.BRANCH_GOTO;
26import static com.android.dx.rop.code.Rop.BRANCH_NONE;
27import static com.android.dx.rop.code.Rop.BRANCH_RETURN;
28import com.android.dx.rop.code.Rops;
29import com.android.dx.rop.code.SourcePosition;
30import com.android.dx.rop.code.ThrowingCstInsn;
31import com.android.dx.rop.code.ThrowingInsn;
32import com.android.dx.rop.cst.CstInteger;
33import com.android.dx.rop.type.StdTypeList;
34import static com.android.dx.rop.type.Type.BT_BYTE;
35import static com.android.dx.rop.type.Type.BT_CHAR;
36import static com.android.dx.rop.type.Type.BT_INT;
37import static com.android.dx.rop.type.Type.BT_SHORT;
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.Iterator;
41import java.util.List;
42
43/**
44 * Builds a sequence of instructions.
45 */
46public final class Code {
47    private final MethodId<?, ?> method;
48    /**
49     * All allocated labels. Although the order of the labels in this list
50     * shouldn't impact behavior, it is used to determine basic block indices.
51     */
52    private final List<Label> labels = new ArrayList<Label>();
53
54    /**
55     * The label currently receiving instructions. This is null if the most
56     * recent instruction was a return or goto.
57     */
58    private Label currentLabel;
59
60    /** true once we've fixed the positions of the parameter registers */
61    private boolean localsInitialized;
62
63    private final Local<?> thisLocal;
64
65    /**
66     * The parameters on this method. If this is non-static, the first parameter
67     * is 'thisLocal' and we have to offset the user's indices by one.
68     */
69    private final List<Local<?>> parameters = new ArrayList<Local<?>>();
70    private final List<Local<?>> locals = new ArrayList<Local<?>>();
71    private SourcePosition sourcePosition = SourcePosition.NO_INFO;
72    private final List<Type<?>> catchTypes = new ArrayList<Type<?>>();
73    private final List<Label> catchLabels = new ArrayList<Label>();
74    private StdTypeList catches = StdTypeList.EMPTY;
75
76    Code(DexGenerator.MethodDeclaration methodDeclaration) {
77        this.method = methodDeclaration.method;
78        if (methodDeclaration.isStatic()) {
79            thisLocal = null;
80        } else {
81            thisLocal = Local.get(this, method.declaringType);
82            parameters.add(thisLocal);
83        }
84        for (Type<?> parameter : method.parameters.types) {
85            parameters.add(Local.get(this, parameter));
86        }
87        this.currentLabel = newLabel();
88        this.currentLabel.marked = true;
89    }
90
91    public <T> Local<T> newLocal(Type<T> type) {
92        if (localsInitialized) {
93            throw new IllegalStateException("Cannot allocate locals after adding instructions");
94        }
95        Local<T> result = Local.get(this, type);
96        locals.add(result);
97        return result;
98    }
99
100    public <T> Local<T> getParameter(int index, Type<T> type) {
101        if (thisLocal != null) {
102            index++; // adjust for the hidden 'this' parameter
103        }
104        return coerce(parameters.get(index), type);
105    }
106
107    public <T> Local<T> getThis(Type<T> type) {
108        if (thisLocal == null) {
109            throw new IllegalStateException("static methods cannot access 'this'");
110        }
111        return coerce(thisLocal, type);
112    }
113
114    @SuppressWarnings("unchecked") // guarded by an equals check
115    private <T> Local<T> coerce(Local<?> local, Type<T> expectedType) {
116        if (!local.type.equals(expectedType)) {
117            throw new IllegalArgumentException(
118                    "requested " + expectedType + " but was " + local.type);
119        }
120        return (Local<T>) local;
121    }
122
123    /**
124     * Assigns registers to locals. From the spec:
125     *  "the N arguments to a method land in the last N registers of the
126     *   method's invocation frame, in order. Wide arguments consume two
127     *   registers. Instance methods are passed a this reference as their
128     *   first argument."
129     *
130     * In addition to assigning registers to each of the locals, this creates
131     * instructions to move parameters into their initial registers. These
132     * instructions are inserted before the code's first real instruction.
133     */
134    void initializeLocals() {
135        if (localsInitialized) {
136            throw new AssertionError();
137        }
138        localsInitialized = true;
139
140        int reg = 0;
141        for (Local<?> local : locals) {
142            reg += local.initialize(reg);
143        }
144        int firstParamReg = reg;
145        List<Insn> moveParameterInstructions = new ArrayList<Insn>();
146        for (Local<?> local : parameters) {
147            CstInteger paramConstant = CstInteger.make(reg - firstParamReg);
148            reg += local.initialize(reg);
149            moveParameterInstructions.add(new PlainCstInsn(Rops.opMoveParam(local.type.ropType),
150                    sourcePosition, local.spec(), RegisterSpecList.EMPTY, paramConstant));
151        }
152        labels.get(0).instructions.addAll(0, moveParameterInstructions);
153    }
154
155    /**
156     * Returns the number of registers to hold the parameters. This includes the
157     * 'this' parameter if it exists.
158     */
159    int paramSize() {
160        int result = 0;
161        for (Local<?> local : parameters) {
162            result += local.size();
163        }
164        return result;
165    }
166
167    // labels
168
169    /**
170     * Creates a new label for use as a branch target. The new label must have
171     * code attached to it later by calling {@link #mark(Label)}.
172     */
173    public Label newLabel() {
174        Label result = new Label();
175        labels.add(result);
176        return result;
177    }
178
179    /**
180     * Start defining instructions for the named label.
181     */
182    public void mark(Label label) {
183        if (label.marked) {
184            throw new IllegalStateException("already marked");
185        }
186        label.marked = true;
187        if (currentLabel != null) {
188            jump(label); // blocks must end with a branch, return or throw
189        }
190        currentLabel = label;
191    }
192
193    public void jump(Label target) {
194        addInstruction(new PlainInsn(Rops.GOTO, sourcePosition, null, RegisterSpecList.EMPTY),
195                target);
196    }
197
198    public void addCatchClause(Type<?> throwable, Label catchClause) {
199        if (catchTypes.contains(throwable)) {
200            throw new IllegalArgumentException("Already caught: " + throwable);
201        }
202        catchTypes.add(throwable);
203        catches = toTypeList(catchTypes);
204        catchLabels.add(catchClause);
205    }
206
207    public Label removeCatchClause(Type<?> throwable) {
208        int index = catchTypes.indexOf(throwable);
209        if (index == -1) {
210            throw new IllegalArgumentException("No catch clause: " + throwable);
211        }
212        catchTypes.remove(index);
213        catches = toTypeList(catchTypes);
214        return catchLabels.remove(index);
215    }
216
217    public void throwValue(Local<?> throwable) {
218        addInstruction(new ThrowingInsn(Rops.THROW, sourcePosition,
219                RegisterSpecList.make(throwable.spec()), catches));
220    }
221
222    private StdTypeList toTypeList(List<Type<?>> types) {
223        StdTypeList result = new StdTypeList(types.size());
224        for (int i = 0; i < types.size(); i++) {
225            result.set(i, types.get(i).ropType);
226        }
227        return result;
228    }
229
230    private void addInstruction(Insn insn) {
231        addInstruction(insn, null);
232    }
233
234    /**
235     * @param branch the branches to follow; interpretation depends on the
236     *     instruction's branchingness.
237     */
238    private void addInstruction(Insn insn, Label branch) {
239        if (currentLabel == null || !currentLabel.marked) {
240            throw new IllegalStateException("no current label");
241        }
242        currentLabel.instructions.add(insn);
243
244        switch (insn.getOpcode().getBranchingness()) {
245        case BRANCH_NONE:
246            if (branch != null) {
247                throw new IllegalArgumentException("unexpected branch: " + branch);
248            }
249            return;
250
251        case BRANCH_RETURN:
252            if (branch != null) {
253                throw new IllegalArgumentException("unexpected branch: " + branch);
254            }
255            currentLabel = null;
256            break;
257
258        case BRANCH_GOTO:
259            if (branch == null) {
260                throw new IllegalArgumentException("branch == null");
261            }
262            currentLabel.primarySuccessor = branch;
263            currentLabel = null;
264            break;
265
266        case Rop.BRANCH_IF:
267            if (branch == null) {
268                throw new IllegalArgumentException("branch == null");
269            }
270            splitCurrentLabel(branch, Collections.<Label>emptyList());
271            break;
272
273        case Rop.BRANCH_THROW:
274            if (branch != null) {
275                throw new IllegalArgumentException("unexpected branch: " + branch);
276            }
277            splitCurrentLabel(null, new ArrayList<Label>(catchLabels));
278            break;
279
280        default:
281            throw new IllegalArgumentException();
282        }
283    }
284
285    /**
286     * Closes the current label and starts a new one.
287     *
288     * @param catchLabels an immutable list of catch labels
289     */
290    private void splitCurrentLabel(Label alternateSuccessor, List<Label> catchLabels) {
291        Label newLabel = newLabel();
292        currentLabel.primarySuccessor = newLabel;
293        currentLabel.alternateSuccessor = alternateSuccessor;
294        currentLabel.catchLabels = catchLabels;
295        currentLabel = newLabel;
296        currentLabel.marked = true;
297    }
298
299    // instructions: constants
300
301    public <T> void loadConstant(Local<T> target, T value) {
302        Rop rop = value == null
303                ? Rops.CONST_OBJECT_NOTHROW
304                : Rops.opConst(target.type.ropType);
305        if (rop.getBranchingness() == BRANCH_NONE) {
306            addInstruction(new PlainCstInsn(rop, sourcePosition, target.spec(),
307                    RegisterSpecList.EMPTY, Constants.getConstant(value)));
308        } else {
309            addInstruction(new ThrowingCstInsn(rop, sourcePosition,
310                    RegisterSpecList.EMPTY, catches, Constants.getConstant(value)));
311            moveResult(target, true);
312        }
313    }
314
315    // instructions: unary
316
317    public <T> void negate(Local<T> source, Local<T> target) {
318        unary(Rops.opNeg(source.type.ropType), source, target);
319    }
320
321    public <T> void not(Local<T> source, Local<T> target) {
322        unary(Rops.opNot(source.type.ropType), source, target);
323    }
324
325    public void numericCast(Local<?> source, Local<?> target) {
326        unary(getCastRop(source.type.ropType, target.type.ropType), source, target);
327    }
328
329    private Rop getCastRop(com.android.dx.rop.type.Type sourceType,
330            com.android.dx.rop.type.Type targetType) {
331        if (sourceType.getBasicType() == BT_INT) {
332            switch (targetType.getBasicType()) {
333            case BT_SHORT:
334                return Rops.TO_SHORT;
335            case BT_CHAR:
336                return Rops.TO_CHAR;
337            case BT_BYTE:
338                return Rops.TO_BYTE;
339            }
340        }
341        return Rops.opConv(targetType, sourceType);
342    }
343
344    private void unary(Rop rop, Local<?> source, Local<?> target) {
345        addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), source.spec()));
346    }
347
348    // instructions: binary
349
350    public <T> void op(BinaryOp op, Local<T> target, Local<T> a, Local<T> b) {
351        Rop rop = op.rop(StdTypeList.make(a.type.ropType, b.type.ropType));
352        RegisterSpecList sources = RegisterSpecList.make(a.spec(), b.spec());
353
354        if (rop.getBranchingness() == BRANCH_NONE) {
355            addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), sources));
356        } else {
357            addInstruction(new ThrowingInsn(rop, sourcePosition, sources, catches));
358            moveResult(target, true);
359        }
360    }
361
362    // instructions: branches
363
364    /**
365     * Compare ints. If the comparison is true, execution jumps to {@code
366     * trueLabel}. If it is false, execution continues to the next instruction.
367     */
368    public <T> void compare(Comparison comparison, Local<T> a, Local<T> b, Label trueLabel) {
369        if (trueLabel == null) {
370            throw new IllegalArgumentException();
371        }
372        Rop rop = comparison.rop(StdTypeList.make(a.type.ropType, b.type.ropType));
373        addInstruction(new PlainInsn(rop, sourcePosition, null,
374                RegisterSpecList.make(a.spec(), b.spec())), trueLabel);
375    }
376
377    /**
378     * Compare floats or doubles.
379     */
380    public <T extends Number> void compare(Local<T> a, Local<T> b, Local<Integer> target,
381            int nanValue) {
382        Rop rop;
383        if (nanValue == 1) {
384            rop = Rops.opCmpg(a.type.ropType);
385        } else if (nanValue == -1) {
386            rop = Rops.opCmpl(a.type.ropType);
387        } else {
388            throw new IllegalArgumentException("expected 1 or -1 but was " + nanValue);
389        }
390        addInstruction(new PlainInsn(rop, sourcePosition, target.spec(),
391                RegisterSpecList.make(a.spec(), b.spec())));
392    }
393
394    /**
395     * Compare longs.
396     */
397    public <T> void compare(Local<T> a, Local<T> b, Local<?> target) {
398        addInstruction(new PlainInsn(Rops.CMPL_LONG, sourcePosition, target.spec(),
399                RegisterSpecList.make(a.spec(), b.spec())));
400    }
401
402    // instructions: fields
403
404    public <D, V> void iget(FieldId<D, V> fieldId, Local<D> instance, Local<V> target) {
405        addInstruction(new ThrowingCstInsn(Rops.opGetField(target.type.ropType), sourcePosition,
406                RegisterSpecList.make(instance.spec()), catches, fieldId.constant));
407        moveResult(target, true);
408    }
409
410    public <D, V> void iput(FieldId<D, V> fieldId, Local<D> instance, Local<V> source) {
411        addInstruction(new ThrowingCstInsn(Rops.opPutField(source.type.ropType), sourcePosition,
412                RegisterSpecList.make(source.spec(), instance.spec()), catches, fieldId.constant));
413    }
414
415    public <V> void sget(FieldId<?, V> fieldId, Local<V> target) {
416        addInstruction(new ThrowingCstInsn(Rops.opGetStatic(target.type.ropType), sourcePosition,
417                RegisterSpecList.EMPTY, catches, fieldId.constant));
418        moveResult(target, true);
419    }
420
421    public <V> void sput(FieldId<?, V> fieldId, Local<V> source) {
422        addInstruction(new ThrowingCstInsn(Rops.opPutStatic(source.type.ropType), sourcePosition,
423                RegisterSpecList.make(source.spec()), catches, fieldId.constant));
424    }
425
426    // instructions: invoke
427
428    public <T> void newInstance(Local<T> target, MethodId<T, Void> constructor, Local<?>... args) {
429        if (target == null) {
430            throw new IllegalArgumentException();
431        }
432        addInstruction(new ThrowingCstInsn(Rops.NEW_INSTANCE, sourcePosition,
433                RegisterSpecList.EMPTY, catches, constructor.declaringType.constant));
434        moveResult(target, true);
435        invokeDirect(constructor, null, target, args);
436    }
437
438    public <R> void invokeStatic(MethodId<?, R> method, Local<? super R> target, Local<?>... args) {
439        invoke(Rops.opInvokeStatic(method.prototype(true)), method, target, null, args);
440    }
441
442    public <D, R> void invokeVirtual(MethodId<D, R> method, Local<? super R> target,
443            Local<? extends D> object, Local<?>... args) {
444        invoke(Rops.opInvokeVirtual(method.prototype(true)), method, target, object, args);
445    }
446
447    public <D, R> void invokeDirect(MethodId<D, R> method, Local<? super R> target,
448            Local<? extends D> object, Local<?>... args) {
449        invoke(Rops.opInvokeDirect(method.prototype(true)), method, target, object, args);
450    }
451
452    public <D, R> void invokeSuper(MethodId<D, R> method, Local<? super R> target,
453            Local<? extends D> object, Local<?>... args) {
454        invoke(Rops.opInvokeSuper(method.prototype(true)), method, target, object, args);
455    }
456
457    public <D, R> void invokeInterface(MethodId<D, R> method, Local<? super R> target,
458            Local<? extends D> object, Local<?>... args) {
459        invoke(Rops.opInvokeInterface(method.prototype(true)), method, target, object, args);
460    }
461
462    private <D, R> void invoke(Rop rop, MethodId<D, R> method, Local<? super R> target,
463            Local<? extends D> object, Local<?>... args) {
464        addInstruction(new ThrowingCstInsn(rop, sourcePosition, concatenate(object, args),
465                catches, method.constant));
466        if (target != null) {
467            moveResult(target, false);
468        }
469    }
470
471    // instructions: types
472
473    public void instanceOfType(Local<?> target, Local<?> source, Type<?> type) {
474        addInstruction(new ThrowingCstInsn(Rops.INSTANCE_OF, sourcePosition,
475                RegisterSpecList.make(source.spec()), catches, type.constant));
476        moveResult(target, true);
477    }
478
479    public void typeCast(Local<?> source, Local<?> target) {
480        addInstruction(new ThrowingCstInsn(Rops.CHECK_CAST, sourcePosition,
481                RegisterSpecList.make(source.spec()), catches, target.type.constant));
482        moveResult(target, true);
483    }
484
485    // instructions: arrays
486
487    public <T> void arrayLength(Local<T> array, Local<Integer> target) {
488        addInstruction(new ThrowingInsn(Rops.ARRAY_LENGTH, sourcePosition,
489                RegisterSpecList.make(array.spec()), catches));
490        moveResult(target, true);
491    }
492
493    public <T> void newArray(Local<Integer> length, Local<T> target) {
494        addInstruction(new ThrowingCstInsn(Rops.opNewArray(target.type.ropType), sourcePosition,
495                RegisterSpecList.make(length.spec()), catches, target.type.constant));
496        moveResult(target, true);
497    }
498
499    public void aget(Local<?> array, Local<Integer> index, Local<?> target) {
500        addInstruction(new ThrowingInsn(Rops.opAget(target.type.ropType), sourcePosition,
501                RegisterSpecList.make(array.spec(), index.spec()), catches));
502        moveResult(target, true);
503    }
504
505    public void aput(Local<?> array, Local<Integer> index, Local<?> source) {
506        addInstruction(new ThrowingInsn(Rops.opAput(source.type.ropType), sourcePosition,
507                RegisterSpecList.make(source.spec(), array.spec(), index.spec()), catches));
508    }
509
510    // instructions: return
511
512    public void returnVoid() {
513        if (!method.returnType.equals(Type.VOID)) {
514            throw new IllegalArgumentException("declared " + method.returnType
515                    + " but returned void");
516        }
517        addInstruction(new PlainInsn(Rops.RETURN_VOID, sourcePosition, null,
518                RegisterSpecList.EMPTY));
519    }
520
521    public void returnValue(Local<?> result) {
522        if (!result.type.equals(method.returnType)) {
523            // TODO: this is probably too strict.
524            throw new IllegalArgumentException("declared " + method.returnType
525                    + " but returned " + result.type);
526        }
527        addInstruction(new PlainInsn(Rops.opReturn(result.type.ropType), sourcePosition,
528                null, RegisterSpecList.make(result.spec())));
529    }
530
531    private void moveResult(Local<?> target, boolean afterNonInvokeThrowingInsn) {
532        Rop rop = afterNonInvokeThrowingInsn
533                ? Rops.opMoveResultPseudo(target.type.ropType)
534                : Rops.opMoveResult(target.type.ropType);
535        addInstruction(new PlainInsn(rop, sourcePosition, target.spec(), RegisterSpecList.EMPTY));
536    }
537
538    // produce BasicBlocks for dex
539
540    BasicBlockList toBasicBlocks() {
541        if (!localsInitialized) {
542            initializeLocals();
543        }
544
545        cleanUpLabels();
546
547        BasicBlockList result = new BasicBlockList(labels.size());
548        for (int i = 0; i < labels.size(); i++) {
549            result.set(i, labels.get(i).toBasicBlock());
550        }
551        return result;
552    }
553
554    /**
555     * Removes empty labels and assigns IDs to non-empty labels.
556     */
557    private void cleanUpLabels() {
558        int id = 0;
559        for (Iterator<Label> i = labels.iterator(); i.hasNext();) {
560            Label label = i.next();
561            if (label.isEmpty()) {
562                i.remove();
563            } else {
564                label.compact();
565                label.id = id++;
566            }
567        }
568    }
569
570    private static RegisterSpecList concatenate(Local<?> first, Local<?>[] rest) {
571        int offset = (first != null) ? 1 : 0;
572        RegisterSpecList result = new RegisterSpecList(offset + rest.length);
573        if (first != null) {
574            result.set(0, first.spec());
575        }
576        for (int i = 0; i < rest.length; i++) {
577            result.set(i + offset, rest[i].spec());
578        }
579        return result;
580    }
581}
582