1 /*
2  * Copyright (C) 2019 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 #include "berberis/code_gen_lib/gen_wrapper.h"
18 
19 #include "berberis/assembler/machine_code.h"
20 #include "berberis/assembler/x86_64.h"
21 #include "berberis/base/bit_util.h"
22 #include "berberis/base/logging.h"
23 #include "berberis/guest_abi/guest_arguments.h"
24 #include "berberis/guest_state/guest_addr.h"
25 #include "berberis/runtime_primitives/host_code.h"
26 
27 namespace berberis {
28 
29 using x86_64::Assembler;
30 
GenWrapGuestFunction(MachineCode * mc,GuestAddr pc,const char * signature,HostCode guest_runner,const char * name)31 void GenWrapGuestFunction(MachineCode* mc,
32                           GuestAddr pc,
33                           const char* signature,
34                           HostCode guest_runner,
35                           const char* name) {
36   UNUSED(name);
37 
38   Assembler as(mc);
39 
40   // On function entry, rsp + 8 is a multiple of 16.
41   // Right before next function call, rsp is a multiple of 16.
42 
43   // Default prologue.
44   as.Push(Assembler::rbp);
45   as.Movq(Assembler::rbp, Assembler::rsp);
46 
47   static_assert(alignof(GuestArgumentBuffer) <= 16, "unexpected GuestArgumentBuffer alignment");
48 
49   // Estimate guest argument buffer size.
50   // Each argument can be 2 8-bytes at most. Result can be 2 8-bytes at most.
51   // At least 8 arguments go to registers in GuestArgumentBuffer.
52   // First 8-byte of stack is in GuestArgumentBuffer.
53   // Result is return on registers in GuestArgumentBuffer.
54   // TODO(eaeltsin): maybe run parameter passing to calculate exactly?
55   size_t num_args = strlen(signature) - 1;
56   size_t max_stack_argv_size = (num_args > 8 ? num_args - 8 : 0) * 16;
57   size_t guest_argument_buffer_size = sizeof(GuestArgumentBuffer) - 8 + max_stack_argv_size;
58 
59   size_t aligned_frame_size = AlignUp(guest_argument_buffer_size, 16);
60 
61   // Allocate stack frame.
62   as.Subq(Assembler::rsp, static_cast<int32_t>(aligned_frame_size));
63 
64   // rsp is 16-bytes aligned and points to GuestArgumentBuffer.
65 
66   constexpr int kArgcOffset = offsetof(GuestArgumentBuffer, argc);
67   constexpr int kRescOffset = offsetof(GuestArgumentBuffer, resc);
68   constexpr int kArgvOffset = offsetof(GuestArgumentBuffer, argv);
69   constexpr int kSimdArgcOffset = offsetof(GuestArgumentBuffer, simd_argc);
70   constexpr int kSimdRescOffset = offsetof(GuestArgumentBuffer, simd_resc);
71   constexpr int kSimdArgvOffset = offsetof(GuestArgumentBuffer, simd_argv);
72   constexpr int kStackArgcOffset = offsetof(GuestArgumentBuffer, stack_argc);
73   constexpr int kStackArgvOffset = offsetof(GuestArgumentBuffer, stack_argv);
74 
75   const int params_offset = aligned_frame_size + 16;
76 
77   // Convert parameters and set argc.
78   int argc = 0;
79   int simd_argc = 0;
80   int stack_argc = 0;
81   int host_stack_argc = 0;
82   for (size_t i = 1; signature[i] != '\0'; ++i) {
83     if (signature[i] == 'z' || signature[i] == 'b' || signature[i] == 's' || signature[i] == 'c' ||
84         signature[i] == 'i' || signature[i] == 'p' || signature[i] == 'l') {
85       static constexpr Assembler::Register kParamRegs[] = {
86           Assembler::rdi,
87           Assembler::rsi,
88           Assembler::rdx,
89           Assembler::rcx,
90           Assembler::r8,
91           Assembler::r9,
92       };
93       if (argc < static_cast<int>(std::size(kParamRegs))) {
94         as.Movq({.base = Assembler::rsp, .disp = kArgvOffset + argc * 8}, kParamRegs[argc]);
95       } else if (argc < 8) {
96         as.Movq(Assembler::rax,
97                 {.base = Assembler::rsp, .disp = params_offset + host_stack_argc * 8});
98         ++host_stack_argc;
99         as.Movq({.base = Assembler::rsp, .disp = kArgvOffset + argc * 8}, Assembler::rax);
100       } else {
101         as.Movq(Assembler::rax,
102                 {.base = Assembler::rsp, .disp = params_offset + host_stack_argc * 8});
103         ++host_stack_argc;
104         as.Movq({.base = Assembler::rsp, .disp = kStackArgvOffset + stack_argc * 8},
105                 Assembler::rax);
106         ++stack_argc;
107       }
108       ++argc;
109     } else if (signature[i] == 'f' || signature[i] == 'd') {
110       static constexpr Assembler::XMMRegister kParamRegs[] = {
111           Assembler::xmm0,
112           Assembler::xmm1,
113           Assembler::xmm2,
114           Assembler::xmm3,
115           Assembler::xmm4,
116           Assembler::xmm5,
117           Assembler::xmm6,
118           Assembler::xmm7,
119       };
120       if (simd_argc < static_cast<int>(std::size(kParamRegs))) {
121         as.Movq({.base = Assembler::rsp, .disp = kSimdArgvOffset + simd_argc * 16},
122                 kParamRegs[simd_argc]);
123       } else {
124         as.Movq(Assembler::rax,
125                 {.base = Assembler::rsp, .disp = params_offset + host_stack_argc * 8});
126         ++host_stack_argc;
127         as.Movq({.base = Assembler::rsp, .disp = kStackArgvOffset + stack_argc * 8},
128                 Assembler::rax);
129         ++stack_argc;
130       }
131       ++simd_argc;
132     } else {
133       FATAL("signature char '%c' not supported", signature[i]);
134     }
135   }
136   as.Movl({.base = Assembler::rsp, .disp = kArgcOffset}, std::min(argc, 8));
137   as.Movl({.base = Assembler::rsp, .disp = kSimdArgcOffset}, std::min(simd_argc, 8));
138   // ATTENTION: GuestArgumentBuffer::stack_argc is in bytes!
139   as.Movl({.base = Assembler::rsp, .disp = kStackArgcOffset}, stack_argc * 8);
140 
141   // Set resc.
142   if (signature[0] == 'z' || signature[0] == 'b' || signature[0] == 's' ||
143       signature[0] == 'c' | signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') {
144     as.Movl({.base = Assembler::rsp, .disp = kRescOffset}, 1);
145     as.Movl({.base = Assembler::rsp, .disp = kSimdRescOffset}, 0);
146   } else if (signature[0] == 'f' || signature[0] == 'd') {
147     as.Movl({.base = Assembler::rsp, .disp = kRescOffset}, 0);
148     as.Movl({.base = Assembler::rsp, .disp = kSimdRescOffset}, 1);
149   } else {
150     CHECK_EQ('v', signature[0]);
151     as.Movl({.base = Assembler::rsp, .disp = kRescOffset}, 0);
152     as.Movl({.base = Assembler::rsp, .disp = kSimdRescOffset}, 0);
153   }
154 
155   // Call guest runner.
156   as.Movq(Assembler::rdi, pc);
157   as.Movq(Assembler::rsi, Assembler::rsp);
158   as.Call(guest_runner);
159 
160   // Get the result.
161   if (signature[0] == 'z' || signature[0] == 'b' || signature[0] == 's' ||
162       signature[0] == 'c' | signature[0] == 'i' || signature[0] == 'p' || signature[0] == 'l') {
163     as.Movq(Assembler::rax, {.base = Assembler::rsp, .disp = kArgvOffset});
164   } else if (signature[0] == 'f' || signature[0] == 'd') {
165     as.Movq(Assembler::xmm0, {.base = Assembler::rsp, .disp = kSimdArgvOffset});
166   } else {
167     CHECK_EQ('v', signature[0]);
168   }
169 
170   // Free stack frame.
171   as.Addq(Assembler::rsp, static_cast<int32_t>(aligned_frame_size));
172 
173   // Default epilogue.
174   as.Pop(Assembler::rbp);
175   as.Ret();
176 
177   as.Finalize();
178 }
179 
180 }  // namespace berberis
181