1/*
2 * Copyright (C) 2007 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.android.dexgen.rop.code;
18
19import com.android.dexgen.rop.type.TypeBearer;
20import com.android.dexgen.util.MutabilityControl;
21
22import java.util.HashMap;
23
24/**
25 * Container for local variable information for a particular {@link
26 * RopMethod}.
27 */
28public final class LocalVariableInfo
29        extends MutabilityControl {
30    /** {@code >= 0;} the register count for the method */
31    private final int regCount;
32
33    /**
34     * {@code non-null;} {@link RegisterSpecSet} to use when indicating a block
35     * that has no locals; it is empty and immutable but has an appropriate
36     * max size for the method
37     */
38    private final RegisterSpecSet emptySet;
39
40    /**
41     * {@code non-null;} array consisting of register sets representing the
42     * sets of variables already assigned upon entry to each block,
43     * where array indices correspond to block labels
44     */
45    private final RegisterSpecSet[] blockStarts;
46
47    /** {@code non-null;} map from instructions to the variable each assigns */
48    private final HashMap<Insn, RegisterSpec> insnAssignments;
49
50    /**
51     * Constructs an instance.
52     *
53     * @param method {@code non-null;} the method being represented by this instance
54     */
55    public LocalVariableInfo(RopMethod method) {
56        if (method == null) {
57            throw new NullPointerException("method == null");
58        }
59
60        BasicBlockList blocks = method.getBlocks();
61        int maxLabel = blocks.getMaxLabel();
62
63        this.regCount = blocks.getRegCount();
64        this.emptySet = new RegisterSpecSet(regCount);
65        this.blockStarts = new RegisterSpecSet[maxLabel];
66        this.insnAssignments =
67            new HashMap<Insn, RegisterSpec>(blocks.getInstructionCount());
68
69        emptySet.setImmutable();
70    }
71
72    /**
73     * Sets the register set associated with the start of the block with
74     * the given label.
75     *
76     * @param label {@code >= 0;} the block label
77     * @param specs {@code non-null;} the register set to associate with the block
78     */
79    public void setStarts(int label, RegisterSpecSet specs) {
80        throwIfImmutable();
81
82        if (specs == null) {
83            throw new NullPointerException("specs == null");
84        }
85
86        try {
87            blockStarts[label] = specs;
88        } catch (ArrayIndexOutOfBoundsException ex) {
89            // Translate the exception.
90            throw new IllegalArgumentException("bogus label");
91        }
92    }
93
94    /**
95     * Merges the given register set into the set for the block with the
96     * given label. If there was not already an associated set, then this
97     * is the same as calling {@link #setStarts}. Otherwise, this will
98     * merge the two sets and call {@link #setStarts} on the result of the
99     * merge.
100     *
101     * @param label {@code >= 0;} the block label
102     * @param specs {@code non-null;} the register set to merge into the start set
103     * for the block
104     * @return {@code true} if the merge resulted in an actual change
105     * to the associated set (including storing one for the first time) or
106     * {@code false} if there was no change
107     */
108    public boolean mergeStarts(int label, RegisterSpecSet specs) {
109        RegisterSpecSet start = getStarts0(label);
110        boolean changed = false;
111
112        if (start == null) {
113            setStarts(label, specs);
114            return true;
115        }
116
117        RegisterSpecSet newStart = start.mutableCopy();
118        newStart.intersect(specs, true);
119
120        if (start.equals(newStart)) {
121            return false;
122        }
123
124        newStart.setImmutable();
125        setStarts(label, newStart);
126
127        return true;
128    }
129
130    /**
131     * Gets the register set associated with the start of the block
132     * with the given label. This returns an empty set with the appropriate
133     * max size if no set was associated with the block in question.
134     *
135     * @param label {@code >= 0;} the block label
136     * @return {@code non-null;} the associated register set
137     */
138    public RegisterSpecSet getStarts(int label) {
139        RegisterSpecSet result = getStarts0(label);
140
141        return (result != null) ? result : emptySet;
142    }
143
144    /**
145     * Gets the register set associated with the start of the given
146     * block. This is just convenient shorthand for
147     * {@code getStarts(block.getLabel())}.
148     *
149     * @param block {@code non-null;} the block in question
150     * @return {@code non-null;} the associated register set
151     */
152    public RegisterSpecSet getStarts(BasicBlock block) {
153        return getStarts(block.getLabel());
154    }
155
156    /**
157     * Gets a mutable copy of the register set associated with the
158     * start of the block with the given label. This returns a
159     * newly-allocated empty {@link RegisterSpecSet} of appropriate
160     * max size if there is not yet any set associated with the block.
161     *
162     * @param label {@code >= 0;} the block label
163     * @return {@code non-null;} the associated register set
164     */
165    public RegisterSpecSet mutableCopyOfStarts(int label) {
166        RegisterSpecSet result = getStarts0(label);
167
168        return (result != null) ?
169            result.mutableCopy() : new RegisterSpecSet(regCount);
170    }
171
172    /**
173     * Adds an assignment association for the given instruction and
174     * register spec. This throws an exception if the instruction
175     * doesn't actually perform a named variable assignment.
176     *
177     * <b>Note:</b> Although the instruction contains its own spec for
178     * the result, it still needs to be passed in explicitly to this
179     * method, since the spec that is stored here should always have a
180     * simple type and the one in the instruction can be an arbitrary
181     * {@link TypeBearer} (such as a constant value).
182     *
183     * @param insn {@code non-null;} the instruction in question
184     * @param spec {@code non-null;} the associated register spec
185     */
186    public void addAssignment(Insn insn, RegisterSpec spec) {
187        throwIfImmutable();
188
189        if (insn == null) {
190            throw new NullPointerException("insn == null");
191        }
192
193        if (spec == null) {
194            throw new NullPointerException("spec == null");
195        }
196
197        insnAssignments.put(insn, spec);
198    }
199
200    /**
201     * Gets the named register being assigned by the given instruction, if
202     * previously stored in this instance.
203     *
204     * @param insn {@code non-null;} instruction in question
205     * @return {@code null-ok;} the named register being assigned, if any
206     */
207    public RegisterSpec getAssignment(Insn insn) {
208        return insnAssignments.get(insn);
209    }
210
211    /**
212     * Gets the number of assignments recorded by this instance.
213     *
214     * @return {@code >= 0;} the number of assignments
215     */
216    public int getAssignmentCount() {
217        return insnAssignments.size();
218    }
219
220    public void debugDump() {
221        for (int label = 0 ; label < blockStarts.length; label++) {
222            if (blockStarts[label] == null) {
223                continue;
224            }
225
226            if (blockStarts[label] == emptySet) {
227                System.out.printf("%04x: empty set\n", label);
228            } else {
229                System.out.printf("%04x: %s\n", label, blockStarts[label]);
230            }
231        }
232    }
233
234    /**
235     * Helper method, to get the starts for a label, throwing the
236     * right exception for range problems.
237     *
238     * @param label {@code >= 0;} the block label
239     * @return {@code null-ok;} associated register set or {@code null} if there
240     * is none
241     */
242    private RegisterSpecSet getStarts0(int label) {
243        try {
244            return blockStarts[label];
245        } catch (ArrayIndexOutOfBoundsException ex) {
246            // Translate the exception.
247            throw new IllegalArgumentException("bogus label");
248        }
249    }
250}
251