1/*
2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3  * Copyright (c) 2017, Red Hat Inc. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.
9 *
10 * This code is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13 * version 2 for more details (a copy is included in the LICENSE file that
14 * accompanied this code).
15 *
16 * You should have received a copy of the GNU General Public License version
17 * 2 along with this work; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
21 * or visit www.oracle.com if you need additional information or have any
22 * questions.
23 */
24
25package org.graalvm.compiler.core.aarch64;
26
27import jdk.vm.ci.aarch64.AArch64Kind;
28import jdk.vm.ci.meta.JavaConstant;
29import org.graalvm.compiler.asm.aarch64.AArch64Address;
30import org.graalvm.compiler.core.common.LIRKind;
31import org.graalvm.compiler.core.common.NumUtil;
32import org.graalvm.compiler.core.common.type.Stamp;
33import org.graalvm.compiler.nodes.ValueNode;
34import org.graalvm.compiler.nodes.calc.AddNode;
35import org.graalvm.compiler.nodes.memory.address.AddressNode;
36import org.graalvm.compiler.nodes.memory.address.OffsetAddressNode;
37import org.graalvm.compiler.nodes.memory.address.RawAddressNode;
38import org.graalvm.compiler.phases.common.AddressLoweringByUsePhase;
39
40public class AArch64AddressLoweringByUse extends AddressLoweringByUsePhase.AddressLoweringByUse {
41    private AArch64LIRKindTool kindtool;
42
43    public AArch64AddressLoweringByUse(AArch64LIRKindTool kindtool) {
44        this.kindtool = kindtool;
45    }
46
47    @Override
48    public AddressNode lower(ValueNode use, Stamp stamp, AddressNode address) {
49        if (address instanceof RawAddressNode) {
50            return doLower(stamp, address.getBase(), null);
51        } else if (address instanceof OffsetAddressNode) {
52            OffsetAddressNode offsetAddress = (OffsetAddressNode) address;
53            return doLower(stamp, offsetAddress.getBase(), offsetAddress.getOffset());
54        } else {
55            // must be an already transformed AArch64AddressNode
56            return address;
57        }
58    }
59
60    @Override
61    public AddressNode lower(AddressNode address) {
62        return lower(null, null, address);
63    }
64
65    private AddressNode doLower(Stamp stamp, ValueNode base, ValueNode index) {
66        AArch64AddressNode ret = new AArch64AddressNode(base, index);
67        AArch64Kind aarch64Kind = (stamp == null ? null : getAArch64Kind(stamp));
68
69        // improve the address as much as possible
70        boolean changed;
71        do {
72            changed = improve(aarch64Kind, ret);
73        } while (changed);
74
75        // avoid duplicates
76        return base.graph().unique(ret);
77    }
78
79    protected boolean improve(AArch64Kind kind, AArch64AddressNode ret) {
80        AArch64Address.AddressingMode mode = ret.getAddressingMode();
81        // if we have already set a displacement or set to base only mode then we are done
82        if (isDisplacementMode(mode) || isBaseOnlyMode(mode)) {
83            return false;
84        }
85        ValueNode base = ret.getBase();
86        ValueNode index = ret.getIndex();
87
88        // avoid a constant or null base if possible
89        if (base == null) {
90            ret.setBase(index);
91            ret.setIndex(base);
92            return true;
93        }
94        // make sure any integral JavaConstant
95        // is the index rather than the base
96        // strictly we don't need the conditions on index
97        // as we ought not to see two JavaConstant values
98        if (base.isJavaConstant() && base.asJavaConstant().getJavaKind().isNumericInteger() &&
99                        index != null && !index.isJavaConstant()) {
100            ret.setBase(index);
101            ret.setIndex(base);
102            return true;
103        }
104
105        // if the base is an add then move it up
106        if (index == null && base instanceof AddNode) {
107            AddNode add = (AddNode) base;
108            ret.setBase(add.getX());
109            ret.setIndex(add.getY());
110            return true;
111        }
112
113        // we can try to fold a JavaConstant index into a displacement
114        if (index != null && index.isJavaConstant()) {
115            JavaConstant javaConstant = index.asJavaConstant();
116            if (javaConstant.getJavaKind().isNumericInteger()) {
117                long disp = javaConstant.asLong();
118                mode = immediateMode(kind, disp);
119                if (isDisplacementMode(mode)) {
120                    index = null;
121                    // we can fold this in as a displacement
122                    // but first see if we can pull up any additional
123                    // constants added into the base
124                    boolean tryNextBase = (base instanceof AddNode);
125                    while (tryNextBase) {
126                        AddNode add = (AddNode) base;
127                        tryNextBase = false;
128                        ValueNode child = add.getX();
129                        if (child.isJavaConstant() && child.asJavaConstant().getJavaKind().isNumericInteger()) {
130                            long newDisp = disp + child.asJavaConstant().asLong();
131                            AArch64Address.AddressingMode newMode = immediateMode(kind, newDisp);
132                            if (newMode != AArch64Address.AddressingMode.REGISTER_OFFSET) {
133                                disp = newDisp;
134                                mode = newMode;
135                                base = add.getY();
136                                ret.setBase(base);
137                                tryNextBase = (base instanceof AddNode);
138                            }
139                        } else {
140                            child = add.getY();
141                            if (child.isJavaConstant() && child.asJavaConstant().getJavaKind().isNumericInteger()) {
142                                long newDisp = disp + child.asJavaConstant().asLong();
143                                AArch64Address.AddressingMode newMode = immediateMode(kind, newDisp);
144                                if (newMode != AArch64Address.AddressingMode.REGISTER_OFFSET) {
145                                    disp = newDisp;
146                                    mode = newMode;
147                                    base = add.getX();
148                                    ret.setBase(base);
149                                    tryNextBase = (base instanceof AddNode);
150                                }
151                            }
152                        }
153                    }
154                    if (disp != 0) {
155                        // ok now set the displacement in place of an index
156                        ret.setIndex(null);
157                        int scaleFactor = computeScaleFactor(kind, mode);
158                        ret.setDisplacement(disp, scaleFactor, mode);
159                    } else {
160                        // reset to base register only
161                        ret.setIndex(null);
162                        ret.setDisplacement(0, 1, AArch64Address.AddressingMode.BASE_REGISTER_ONLY);
163                    }
164                    return true;
165                }
166            }
167        }
168        // nope cannot improve this any more
169        return false;
170    }
171
172    private AArch64Kind getAArch64Kind(Stamp stamp) {
173        LIRKind lirKind = stamp.getLIRKind(kindtool);
174        if (!lirKind.isValue()) {
175            if (!lirKind.isReference(0) || lirKind.getReferenceCount() != 1) {
176                return null;
177            }
178        }
179
180        return (AArch64Kind) lirKind.getPlatformKind();
181    }
182
183    private static AArch64Address.AddressingMode immediateMode(AArch64Kind kind, long value) {
184        if (kind != null) {
185            int size = kind.getSizeInBytes();
186            // this next test should never really fail
187            if ((value & (size - 1)) == 0) {
188                long encodedValue = value / size;
189                // assert value % size == 0
190                // we can try for a 12 bit scaled offset
191                if (NumUtil.isUnsignedNbit(12, encodedValue)) {
192                    return AArch64Address.AddressingMode.IMMEDIATE_SCALED;
193                }
194            }
195        }
196
197        // we can try for a 9 bit unscaled offset
198        if (NumUtil.isSignedNbit(9, value)) {
199            return AArch64Address.AddressingMode.IMMEDIATE_UNSCALED;
200        }
201
202        // nope this index needs to be passed via offset register
203        return AArch64Address.AddressingMode.REGISTER_OFFSET;
204    }
205
206    private static int computeScaleFactor(AArch64Kind kind, AArch64Address.AddressingMode mode) {
207        if (mode == AArch64Address.AddressingMode.IMMEDIATE_SCALED) {
208            return kind.getSizeInBytes();
209        }
210        return 1;
211    }
212
213    boolean isBaseOnlyMode(AArch64Address.AddressingMode addressingMode) {
214        return addressingMode == AArch64Address.AddressingMode.BASE_REGISTER_ONLY;
215    }
216
217    private static boolean isDisplacementMode(AArch64Address.AddressingMode addressingMode) {
218        switch (addressingMode) {
219            case IMMEDIATE_POST_INDEXED:
220            case IMMEDIATE_PRE_INDEXED:
221            case IMMEDIATE_SCALED:
222            case IMMEDIATE_UNSCALED:
223                return true;
224        }
225        return false;
226    }
227}
228