1 /*
2  * Copyright (C) 2023 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 
17 #ifndef BERBERIS_CALLING_CONVENTIONS_CALLING_CONVENTIONS_ARM_H_
18 #define BERBERIS_CALLING_CONVENTIONS_CALLING_CONVENTIONS_ARM_H_
19 
20 #include "berberis/base/bit_util.h"
21 #include "berberis/base/logging.h"
22 
23 namespace berberis::arm {
24 
25 enum ArgLocationKind {
26   kArgLocationNone = 0,
27   kArgLocationStack,
28   kArgLocationInt,
29   kArgLocationIntAndStack,
30   kArgLocationSimd,
31 };
32 
33 struct ArgLocation {
34   ArgLocationKind kind;
35   unsigned offset;  // meaning of offset depends on kind!
36 };
37 
38 class CallingConventions {
39  public:
40   static constexpr unsigned kStackAlignmentBeforeCall = 8;
41 
42   CallingConventions() = default;
43   CallingConventions(const CallingConventions&) = default;
44   CallingConventions(CallingConventions&&) = default;
45   // These are used for va_list initialization, where stack may not be aligned.
CallingConventions(const CallingConventions & base,unsigned stack)46   CallingConventions(const CallingConventions& base, unsigned stack)
47       : int_byte_offset_(base.int_byte_offset_),
48         simd_mask_(base.simd_mask_),
49         init_stack_offset_(base.init_stack_offset_ + stack),
50         stack_offset_(base.stack_offset_ + stack) {}
CallingConventions(unsigned stack)51   CallingConventions(unsigned stack) : init_stack_offset_(stack), stack_offset_(stack) {}
52   static constexpr struct StackOnly {
53   } kStackOnly;
CallingConventions(StackOnly,unsigned stack)54   CallingConventions(StackOnly, unsigned stack)
55       : int_byte_offset_(kMaxIntByteOffset),
56         simd_mask_(0),
57         init_stack_offset_(stack),
58         stack_offset_(stack) {}
59 
GetNextIntArgLoc(unsigned size,unsigned alignment)60   constexpr ArgLocation GetNextIntArgLoc(unsigned size, unsigned alignment) {
61     unsigned param_alignment = 0;
62     unsigned param_size = 0;
63 
64     // Handle under- and over-aligned parameters.
65     if (alignment < 4) {
66       param_alignment = 4;
67       param_size = AlignUp(size, 4);
68     } else if (alignment > 8) {
69       param_alignment = 8;
70       param_size = size;
71     } else {
72       param_alignment = alignment;
73       param_size = size;
74     }
75 
76     unsigned param_offset = AlignUp(int_byte_offset_, param_alignment);
77 
78     if (param_offset + param_size <= kMaxIntByteOffset) {
79       // Parameter on int register.
80       int_byte_offset_ = param_offset + param_size;
81 
82       return {kArgLocationInt, param_offset / 4};
83     }
84 
85     if (param_offset < kMaxIntByteOffset && stack_offset_ == init_stack_offset_) {
86       // Parameter on int register and stack.
87       int_byte_offset_ = kMaxIntByteOffset;
88       stack_offset_ = param_offset + param_size - kMaxIntByteOffset;
89 
90       return {kArgLocationIntAndStack, param_offset / 4};
91     }
92 
93     // Parameter on stack.
94     int_byte_offset_ = kMaxIntByteOffset;
95 
96     param_offset = AlignUp(stack_offset_, param_alignment);
97     stack_offset_ = param_offset + param_size;
98 
99     return {kArgLocationStack, param_offset};
100   }
101 
GetNextFpArgLoc(unsigned size,unsigned alignment)102   constexpr ArgLocation GetNextFpArgLoc(unsigned size, unsigned alignment) {
103     if (simd_mask_) {
104       unsigned param_size_mask = (1u << (size / 4)) - 1;
105       for (unsigned index = 0; index < kMaxSimdOffset; index += alignment / 4) {
106         unsigned param_mask = (param_size_mask << index);
107         if ((simd_mask_ & param_mask) == param_mask) {
108           // Parameter is on simd registers.
109           simd_mask_ &= ~param_mask;
110 
111           return {kArgLocationSimd, index};
112         }
113       }
114 
115       // No available simd registers, this and next params are on stack.
116       simd_mask_ = 0;
117     }
118 
119     // Parameter is on stack.
120     unsigned param_offset = AlignUp(stack_offset_, alignment);
121     stack_offset_ = param_offset + size;
122 
123     return {kArgLocationStack, param_offset};
124   }
125 
GetIntResLoc(unsigned size)126   constexpr ArgLocation GetIntResLoc(unsigned size) {
127     // Fundamental integer or pointer type - 1/1, 2/2, 3/3, 4/4, 8/8, 16/16.
128     CHECK_LE(size, 16u);
129 
130     // Use r0.
131     return {kArgLocationInt, 0u};
132   }
133 
GetFpResLoc(unsigned size)134   constexpr ArgLocation GetFpResLoc(unsigned size) {
135     // Fundamental floating-point type - 2/2, 4/4, 8/8, 16/16.
136     CHECK_LE(size, 16u);
137 
138     // Use v0.
139     return {kArgLocationSimd, 0u};
140   }
141 
142  private:
143   static constexpr unsigned kMaxIntByteOffset = 16u;
144   static constexpr unsigned kMaxSimdOffset = 16u;
145 
146   unsigned int_byte_offset_ = 0;
147   unsigned simd_mask_ = (1u << kMaxSimdOffset) - 1;
148   unsigned init_stack_offset_ = 0;
149   unsigned stack_offset_ = 0;
150 };
151 
152 }  // namespace berberis::arm
153 
154 #endif  // BERBERIS_CALLING_CONVENTIONS_CALLING_CONVENTIONS_ARM_H_
155