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