Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CodeGen/src/IrCallWrapperX64.cpp
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#include "Luau/IrCallWrapperX64.h"
3
4
#include "Luau/AssemblyBuilderX64.h"
5
#include "Luau/IrRegAllocX64.h"
6
7
#include "EmitCommonX64.h"
8
9
namespace Luau
10
{
11
namespace CodeGen
12
{
13
namespace X64
14
{
15
16
static const std::array<OperandX64, 6> kWindowsGprOrder = {rcx, rdx, r8, r9, addr[rsp + kStackRegHomeStorage], addr[rsp + kStackRegHomeStorage + 8]};
17
static const std::array<OperandX64, 6> kSystemvGprOrder = {rdi, rsi, rdx, rcx, r8, r9};
18
static const std::array<OperandX64, 4> kXmmOrder = {xmm0, xmm1, xmm2, xmm3}; // Common order for first 4 fp arguments on Windows/SystemV
19
20
static bool sameUnderlyingRegister(RegisterX64 a, RegisterX64 b)
21
{
22
SizeX64 underlyingSizeA = a.size == SizeX64::xmmword ? SizeX64::xmmword : SizeX64::qword;
23
SizeX64 underlyingSizeB = b.size == SizeX64::xmmword ? SizeX64::xmmword : SizeX64::qword;
24
25
return underlyingSizeA == underlyingSizeB && a.index == b.index;
26
}
27
28
IrCallWrapperX64::IrCallWrapperX64(IrRegAllocX64& regs, AssemblyBuilderX64& build, uint32_t instIdx)
29
: regs(regs)
30
, build(build)
31
, instIdx(instIdx)
32
, funcOp(noreg)
33
{
34
gprUses.fill(0);
35
xmmUses.fill(0);
36
}
37
38
void IrCallWrapperX64::addArgument(SizeX64 targetSize, OperandX64 source, IrOp sourceOp)
39
{
40
// Instruction operands rely on current instruction index for lifetime tracking
41
CODEGEN_ASSERT(instIdx != kInvalidInstIdx || sourceOp.kind == IrOpKind::None);
42
43
CODEGEN_ASSERT(argCount < kMaxCallArguments);
44
CallArgument& arg = args[argCount++];
45
arg = {targetSize, source, sourceOp};
46
47
arg.target = getNextArgumentTarget(targetSize);
48
49
if (build.abi == ABIX64::Windows)
50
{
51
// On Windows, gpr/xmm register positions move in sync
52
gprPos++;
53
xmmPos++;
54
}
55
else
56
{
57
if (targetSize == SizeX64::xmmword)
58
xmmPos++;
59
else
60
gprPos++;
61
}
62
}
63
64
void IrCallWrapperX64::addArgument(SizeX64 targetSize, ScopedRegX64& scopedReg)
65
{
66
addArgument(targetSize, scopedReg.release(), {});
67
}
68
69
void IrCallWrapperX64::call(const OperandX64& func)
70
{
71
funcOp = func;
72
73
countRegisterUses();
74
75
for (int i = 0; i < argCount; ++i)
76
{
77
CallArgument& arg = args[i];
78
79
if (arg.sourceOp.kind != IrOpKind::None)
80
{
81
if (IrInst* inst = regs.function.asInstOp(arg.sourceOp))
82
{
83
// Source registers are recorded separately from source operands in CallArgument
84
// If source is the last use of IrInst, clear the register from the operand
85
if (regs.isLastUseReg(*inst, instIdx))
86
inst->regX64 = noreg;
87
// If it's not the last use and register is volatile, register ownership is taken, which also spills the operand
88
else if (inst->regX64.size == SizeX64::xmmword || regs.shouldFreeGpr(inst->regX64))
89
regs.takeReg(inst->regX64, kInvalidInstIdx);
90
}
91
}
92
93
// Immediate values are stored at the end since they are not interfering and target register can still be used temporarily
94
if (arg.source.cat == CategoryX64::imm)
95
{
96
arg.candidate = false;
97
}
98
// Arguments passed through stack can be handled immediately
99
else if (arg.target.cat == CategoryX64::mem)
100
{
101
if (arg.source.cat == CategoryX64::mem)
102
{
103
ScopedRegX64 tmp{regs, arg.target.memSize};
104
105
freeSourceRegisters(arg);
106
107
if (arg.source.memSize == SizeX64::none)
108
build.lea(tmp.reg, arg.source);
109
else
110
build.mov(tmp.reg, arg.source);
111
112
build.mov(arg.target, tmp.reg);
113
}
114
else
115
{
116
freeSourceRegisters(arg);
117
118
build.mov(arg.target, arg.source);
119
}
120
121
arg.candidate = false;
122
}
123
// Skip arguments that are already in their place
124
else if (arg.source.cat == CategoryX64::reg && sameUnderlyingRegister(arg.target.base, arg.source.base))
125
{
126
freeSourceRegisters(arg);
127
128
// If target is not used as source in other arguments, prevent register allocator from giving it out
129
if (getRegisterUses(arg.target.base) == 0)
130
regs.takeReg(arg.target.base, kInvalidInstIdx);
131
else // Otherwise, make sure we won't free it when last source use is completed
132
addRegisterUse(arg.target.base);
133
134
arg.candidate = false;
135
}
136
}
137
138
// Repeat until we run out of arguments to pass
139
while (true)
140
{
141
// Find target argument register that is not an active source
142
if (CallArgument* candidate = findNonInterferingArgument())
143
{
144
// This section is only for handling register targets
145
CODEGEN_ASSERT(candidate->target.cat == CategoryX64::reg);
146
147
freeSourceRegisters(*candidate);
148
149
CODEGEN_ASSERT(getRegisterUses(candidate->target.base) == 0);
150
regs.takeReg(candidate->target.base, kInvalidInstIdx);
151
152
moveToTarget(*candidate);
153
154
candidate->candidate = false;
155
}
156
// If all registers cross-interfere (rcx <- rdx, rdx <- rcx), one has to be renamed
157
else if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg)
158
{
159
renameConflictingRegister(conflict);
160
}
161
else
162
{
163
for (int i = 0; i < argCount; ++i)
164
CODEGEN_ASSERT(!args[i].candidate);
165
break;
166
}
167
}
168
169
// Handle immediate arguments last
170
for (int i = 0; i < argCount; ++i)
171
{
172
CallArgument& arg = args[i];
173
174
if (arg.source.cat == CategoryX64::imm)
175
{
176
// There could be a conflict with the function source register, make this argument a candidate to find it
177
arg.candidate = true;
178
179
if (RegisterX64 conflict = findConflictingTarget(); conflict != noreg)
180
renameConflictingRegister(conflict);
181
182
if (arg.target.cat == CategoryX64::reg)
183
regs.takeReg(arg.target.base, kInvalidInstIdx);
184
185
moveToTarget(arg);
186
187
arg.candidate = false;
188
}
189
}
190
191
// Free registers used in the function call
192
removeRegisterUse(funcOp.base);
193
removeRegisterUse(funcOp.index);
194
195
// Just before the call is made, argument registers are all marked as free in register allocator
196
for (int i = 0; i < argCount; ++i)
197
{
198
CallArgument& arg = args[i];
199
200
if (arg.target.cat == CategoryX64::reg)
201
regs.freeReg(arg.target.base);
202
}
203
204
regs.preserveAndFreeInstValues();
205
206
regs.assertAllFree();
207
208
build.call(funcOp);
209
}
210
211
RegisterX64 IrCallWrapperX64::suggestNextArgumentRegister(SizeX64 size) const
212
{
213
OperandX64 target = getNextArgumentTarget(size);
214
215
if (target.cat != CategoryX64::reg)
216
return regs.allocReg(size, kInvalidInstIdx);
217
218
if (!regs.canTakeReg(target.base))
219
return regs.allocReg(size, kInvalidInstIdx);
220
221
return regs.takeReg(target.base, kInvalidInstIdx);
222
}
223
224
OperandX64 IrCallWrapperX64::getNextArgumentTarget(SizeX64 size) const
225
{
226
if (size == SizeX64::xmmword)
227
{
228
CODEGEN_ASSERT(size_t(xmmPos) < kXmmOrder.size());
229
return kXmmOrder[xmmPos];
230
}
231
232
const std::array<OperandX64, 6>& gprOrder = build.abi == ABIX64::Windows ? kWindowsGprOrder : kSystemvGprOrder;
233
234
CODEGEN_ASSERT(size_t(gprPos) < gprOrder.size());
235
OperandX64 target = gprOrder[gprPos];
236
237
// Keep requested argument size
238
if (target.cat == CategoryX64::reg)
239
target.base.size = size;
240
else if (target.cat == CategoryX64::mem)
241
target.memSize = size;
242
243
return target;
244
}
245
246
void IrCallWrapperX64::countRegisterUses()
247
{
248
for (int i = 0; i < argCount; ++i)
249
{
250
addRegisterUse(args[i].source.base);
251
addRegisterUse(args[i].source.index);
252
}
253
254
addRegisterUse(funcOp.base);
255
addRegisterUse(funcOp.index);
256
}
257
258
CallArgument* IrCallWrapperX64::findNonInterferingArgument()
259
{
260
for (int i = 0; i < argCount; ++i)
261
{
262
CallArgument& arg = args[i];
263
264
if (arg.candidate && !interferesWithActiveSources(arg, i) && !interferesWithOperand(funcOp, arg.target.base))
265
return &arg;
266
}
267
268
return nullptr;
269
}
270
271
bool IrCallWrapperX64::interferesWithOperand(const OperandX64& op, RegisterX64 reg) const
272
{
273
return sameUnderlyingRegister(op.base, reg) || sameUnderlyingRegister(op.index, reg);
274
}
275
276
bool IrCallWrapperX64::interferesWithActiveSources(const CallArgument& targetArg, int targetArgIndex) const
277
{
278
for (int i = 0; i < argCount; ++i)
279
{
280
const CallArgument& arg = args[i];
281
282
if (arg.candidate && i != targetArgIndex && interferesWithOperand(arg.source, targetArg.target.base))
283
return true;
284
}
285
286
return false;
287
}
288
289
bool IrCallWrapperX64::interferesWithActiveTarget(RegisterX64 sourceReg) const
290
{
291
for (int i = 0; i < argCount; ++i)
292
{
293
const CallArgument& arg = args[i];
294
295
if (arg.candidate && sameUnderlyingRegister(arg.target.base, sourceReg))
296
return true;
297
}
298
299
return false;
300
}
301
302
void IrCallWrapperX64::moveToTarget(CallArgument& arg)
303
{
304
if (arg.source.cat == CategoryX64::reg)
305
{
306
RegisterX64 source = arg.source.base;
307
308
if (source.size == SizeX64::xmmword)
309
build.vmovsd(arg.target, source, source);
310
else
311
build.mov(arg.target, source);
312
}
313
else if (arg.source.cat == CategoryX64::imm)
314
{
315
build.mov(arg.target, arg.source);
316
}
317
else
318
{
319
if (arg.source.memSize == SizeX64::none)
320
build.lea(arg.target, arg.source);
321
else if (arg.target.base.size == SizeX64::xmmword && arg.source.memSize == SizeX64::xmmword)
322
build.vmovups(arg.target, arg.source);
323
else if (arg.target.base.size == SizeX64::xmmword)
324
build.vmovsd(arg.target, arg.source);
325
else
326
build.mov(arg.target, arg.source);
327
}
328
}
329
330
void IrCallWrapperX64::freeSourceRegisters(CallArgument& arg)
331
{
332
removeRegisterUse(arg.source.base);
333
removeRegisterUse(arg.source.index);
334
}
335
336
void IrCallWrapperX64::renameRegister(RegisterX64& target, RegisterX64 reg, RegisterX64 replacement)
337
{
338
if (sameUnderlyingRegister(target, reg))
339
{
340
addRegisterUse(replacement);
341
removeRegisterUse(target);
342
343
target.index = replacement.index; // Only change index, size is preserved
344
}
345
}
346
347
void IrCallWrapperX64::renameSourceRegisters(RegisterX64 reg, RegisterX64 replacement)
348
{
349
for (int i = 0; i < argCount; ++i)
350
{
351
CallArgument& arg = args[i];
352
353
if (arg.candidate)
354
{
355
renameRegister(arg.source.base, reg, replacement);
356
renameRegister(arg.source.index, reg, replacement);
357
}
358
}
359
360
renameRegister(funcOp.base, reg, replacement);
361
renameRegister(funcOp.index, reg, replacement);
362
}
363
364
RegisterX64 IrCallWrapperX64::findConflictingTarget() const
365
{
366
for (int i = 0; i < argCount; ++i)
367
{
368
const CallArgument& arg = args[i];
369
370
if (arg.candidate)
371
{
372
if (interferesWithActiveTarget(arg.source.base))
373
return arg.source.base;
374
375
if (interferesWithActiveTarget(arg.source.index))
376
return arg.source.index;
377
}
378
}
379
380
if (interferesWithActiveTarget(funcOp.base))
381
return funcOp.base;
382
383
if (interferesWithActiveTarget(funcOp.index))
384
return funcOp.index;
385
386
return noreg;
387
}
388
389
void IrCallWrapperX64::renameConflictingRegister(RegisterX64 conflict)
390
{
391
// Get a fresh register
392
RegisterX64 freshReg = regs.allocReg(conflict.size, kInvalidInstIdx);
393
394
if (conflict.size == SizeX64::xmmword)
395
build.vmovsd(freshReg, conflict, conflict);
396
else
397
build.mov(freshReg, conflict);
398
399
renameSourceRegisters(conflict, freshReg);
400
}
401
402
int IrCallWrapperX64::getRegisterUses(RegisterX64 reg) const
403
{
404
return reg.size == SizeX64::xmmword ? xmmUses[reg.index] : (reg.size != SizeX64::none ? gprUses[reg.index] : 0);
405
}
406
407
void IrCallWrapperX64::addRegisterUse(RegisterX64 reg)
408
{
409
if (reg.size == SizeX64::xmmword)
410
xmmUses[reg.index]++;
411
else if (reg.size != SizeX64::none)
412
gprUses[reg.index]++;
413
}
414
415
void IrCallWrapperX64::removeRegisterUse(RegisterX64 reg)
416
{
417
if (reg.size == SizeX64::xmmword)
418
{
419
CODEGEN_ASSERT(xmmUses[reg.index] != 0);
420
xmmUses[reg.index]--;
421
422
if (xmmUses[reg.index] == 0) // we don't use persistent xmm regs so no need to call shouldFreeRegister
423
regs.freeReg(reg);
424
}
425
else if (reg.size != SizeX64::none)
426
{
427
CODEGEN_ASSERT(gprUses[reg.index] != 0);
428
gprUses[reg.index]--;
429
430
if (gprUses[reg.index] == 0 && regs.shouldFreeGpr(reg))
431
regs.freeReg(reg);
432
}
433
}
434
435
} // namespace X64
436
} // namespace CodeGen
437
} // namespace Luau
438
439