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_ARM64_H_
18 #define BERBERIS_CALLING_CONVENTIONS_CALLING_CONVENTIONS_ARM64_H_
19 
20 #include "berberis/base/bit_util.h"
21 #include "berberis/base/logging.h"
22 
23 namespace berberis::arm64 {
24 
25 enum ArgLocationKind {
26   kArgLocationNone = 0,
27   kArgLocationStack,
28   kArgLocationInt,   // x0 - x7
29   kArgLocationSimd,  // v0 - v7
30 };
31 
32 struct ArgLocation {
33   ArgLocationKind kind;
34   unsigned offset;  // meaning of offset depends on kind!
35 };
36 
37 class CallingConventions {
38  public:
39   static constexpr unsigned kStackAlignmentBeforeCall = 16;
40 
41   CallingConventions() = default;
42   CallingConventions(const CallingConventions&) = default;
43   CallingConventions(CallingConventions&&) = default;
CallingConventions(unsigned int_offset,unsigned simd_offset)44   CallingConventions(unsigned int_offset, unsigned simd_offset)
45       : int_offset_(int_offset), simd_offset_(simd_offset) {}
46 
GetNextIntArgLoc(unsigned size,unsigned alignment)47   constexpr ArgLocation GetNextIntArgLoc(unsigned size, unsigned alignment) {
48     // Fundamental integer or pointer type - 1/1, 2/2, 3/3, 4/4, 8/8, 16/16.
49     CHECK_LE(size, 16u);
50     CHECK_EQ(size, alignment);
51 
52     unsigned size_in_regs = size > 8 ? 2 : 1;
53     unsigned alignment_in_regs = size_in_regs;
54     unsigned aligned_int_offset = AlignUp(int_offset_, alignment_in_regs);
55 
56     if (aligned_int_offset + size_in_regs <= kMaxIntOffset) {
57       // Use 1 or 2 int regs.
58       ArgLocation loc{kArgLocationInt, aligned_int_offset};
59       int_offset_ = aligned_int_offset + size_in_regs;
60       return loc;
61     }
62 
63     // Ensure no more integer registers are used for arguments.
64     // For size_in_regs == 2, x7 might remain unused.
65     int_offset_ = kMaxIntOffset;
66 
67     return GetNextStackArgLoc(size, alignment);
68   }
69 
GetNextFpArgLoc(unsigned size,unsigned alignment)70   constexpr ArgLocation GetNextFpArgLoc(unsigned size, unsigned alignment) {
71     // Fundamental floating-point type - 2/2, 4/4, 8/8, 16/16.
72     CHECK_LE(size, 16u);
73     CHECK_EQ(size, alignment);
74 
75     if (simd_offset_ < kMaxSimdOffset) {
76       // Use 1 simd reg.
77       ArgLocation loc{kArgLocationSimd, simd_offset_};
78       ++simd_offset_;
79       return loc;
80     }
81 
82     return GetNextStackArgLoc(size, alignment);
83   }
84 
GetIntResLoc(unsigned size)85   constexpr ArgLocation GetIntResLoc(unsigned size) {
86     // Fundamental integer or pointer type - 1/1, 2/2, 3/3, 4/4, 8/8, 16/16.
87     CHECK_LE(size, 16u);
88 
89     // Use x0.
90     return {kArgLocationInt, 0u};
91   }
92 
GetFpResLoc(unsigned size)93   constexpr ArgLocation GetFpResLoc(unsigned size) {
94     // Fundamental floating-point type - 2/2, 4/4, 8/8, 16/16.
95     CHECK_LE(size, 16u);
96 
97     // Use v0.
98     return {kArgLocationSimd, 0u};
99   }
100 
101  private:
GetNextStackArgLoc(unsigned size,unsigned alignment)102   constexpr ArgLocation GetNextStackArgLoc(unsigned size, unsigned alignment) {
103     unsigned alignment_in_stack = alignment > 8 ? alignment : 8;
104     unsigned size_in_stack = AlignUp(size, alignment_in_stack);
105 
106     unsigned aligned_stack_offset = AlignUp(stack_offset_, alignment_in_stack);
107 
108     ArgLocation loc{kArgLocationStack, aligned_stack_offset};
109     stack_offset_ = aligned_stack_offset + size_in_stack;
110     return loc;
111   }
112 
113   static constexpr unsigned kMaxIntOffset = 8u;
114   static constexpr unsigned kMaxSimdOffset = 8u;
115 
116   unsigned int_offset_ = 0;
117   unsigned simd_offset_ = 0;
118   unsigned stack_offset_ = 0;
119 };
120 
121 }  // namespace berberis::arm64
122 
123 #endif  // BERBERIS_CALLING_CONVENTIONS_CALLING_CONVENTIONS_ARM64_H_
124