Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CodeGen/src/CodeGenLower.h
2725 views
1
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
2
#pragma once
3
4
#include "Luau/AssemblyBuilderA64.h"
5
#include "Luau/AssemblyBuilderX64.h"
6
#include "Luau/CodeGen.h"
7
#include "Luau/IrBuilder.h"
8
#include "Luau/IrDump.h"
9
#include "Luau/IrUtils.h"
10
#include "Luau/LoweringStats.h"
11
#include "Luau/OptimizeConstProp.h"
12
#include "Luau/OptimizeDeadStore.h"
13
#include "Luau/OptimizeFinalX64.h"
14
15
#include "EmitCommon.h"
16
#include "IrLoweringA64.h"
17
#include "IrLoweringX64.h"
18
19
#include "lobject.h"
20
#include "lstate.h"
21
22
#include <algorithm>
23
#include <vector>
24
25
LUAU_FASTFLAG(DebugCodegenOptSize)
26
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
27
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
28
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
29
LUAU_FASTFLAG(LuauCodegenBlockSafeEnv)
30
31
namespace Luau
32
{
33
namespace CodeGen
34
{
35
36
inline void gatherFunctionsHelper(
37
std::vector<Proto*>& results,
38
Proto* proto,
39
const unsigned int flags,
40
const bool hasNativeFunctions,
41
const bool root
42
)
43
{
44
if (results.size() <= size_t(proto->bytecodeid))
45
results.resize(proto->bytecodeid + 1);
46
47
// Skip protos that we've already compiled in this run: this happens because at -O2, inlined functions get their protos reused
48
if (results[proto->bytecodeid])
49
return;
50
51
// if native module, compile cold functions if requested
52
// if not native module, compile function if it has native attribute and is not root
53
bool shouldGather = hasNativeFunctions ? (!root && (proto->flags & LPF_NATIVE_FUNCTION) != 0)
54
: ((proto->flags & LPF_NATIVE_COLD) == 0 || (flags & CodeGen_ColdFunctions) != 0);
55
56
if (shouldGather)
57
results[proto->bytecodeid] = proto;
58
59
// Recursively traverse child protos even if we aren't compiling this one
60
for (int i = 0; i < proto->sizep; i++)
61
gatherFunctionsHelper(results, proto->p[i], flags, hasNativeFunctions, false);
62
}
63
64
inline void gatherFunctions(std::vector<Proto*>& results, Proto* root, const unsigned int flags, const bool hasNativeFunctions = false)
65
{
66
gatherFunctionsHelper(results, root, flags, hasNativeFunctions, true);
67
}
68
69
inline unsigned getInstructionCount(const std::vector<IrInst>& instructions, IrCmd cmd)
70
{
71
return unsigned(std::count_if(
72
instructions.begin(),
73
instructions.end(),
74
[&cmd](const IrInst& inst)
75
{
76
return inst.cmd == cmd;
77
}
78
));
79
}
80
81
template<typename AssemblyBuilder, typename IrLowering>
82
inline bool lowerImpl(
83
AssemblyBuilder& build,
84
IrLowering& lowering,
85
IrFunction& function,
86
const std::vector<uint32_t>& sortedBlocks,
87
int bytecodeid,
88
AssemblyOptions options
89
)
90
{
91
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
92
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
93
94
for (size_t i = 0; i < function.bcMapping.size(); ++i)
95
{
96
uint32_t irLocation = function.bcMapping[i].irLocation;
97
98
if (irLocation != ~0u)
99
bcLocations[irLocation] = uint32_t(i);
100
}
101
102
bool outputEnabled = options.includeAssembly || options.includeIr;
103
104
IrToStringContext ctx{build.text, function.blocks, function.constants, function.cfg, function.proto};
105
106
// We use this to skip outlined fallback blocks from IR/asm text output
107
size_t textSize = build.text.length();
108
uint32_t codeSize = build.getCodeSize();
109
bool seenFallback = false;
110
111
IrBlock dummy;
112
dummy.start = ~0u;
113
114
// Make sure entry block is first
115
CODEGEN_ASSERT(sortedBlocks[0] == 0);
116
CODEGEN_ASSERT(function.entryBlock == 0);
117
118
for (size_t i = 0; i < sortedBlocks.size(); ++i)
119
{
120
uint32_t blockIndex = sortedBlocks[i];
121
IrBlock& block = function.blocks[blockIndex];
122
123
if (block.kind == IrBlockKind::Dead)
124
continue;
125
126
CODEGEN_ASSERT(block.start != ~0u);
127
CODEGEN_ASSERT(block.finish != ~0u);
128
CODEGEN_ASSERT(!seenFallback || block.kind == IrBlockKind::Fallback);
129
130
// If we want to skip fallback code IR/asm, we'll record when those blocks start once we see them
131
if (block.kind == IrBlockKind::Fallback && !seenFallback)
132
{
133
textSize = build.text.length();
134
codeSize = build.getCodeSize();
135
seenFallback = true;
136
}
137
138
if (options.includeIr)
139
{
140
if (options.includeIrPrefix == IncludeIrPrefix::Yes)
141
build.logAppend("# ");
142
143
toStringDetailed(ctx, block, blockIndex, options.includeUseInfo, options.includeCfgInfo, options.includeRegFlowInfo);
144
}
145
146
// Values can only reference restore operands in the current block chain
147
function.validRestoreOpBlocks.push_back(blockIndex);
148
149
build.setLabel(block.label);
150
151
if (blockIndex == function.entryBlock)
152
{
153
function.entryLocation = build.getLabelOffset(block.label);
154
}
155
156
lowering.startBlock(block);
157
158
IrBlock& nextBlock = getNextBlock(function, sortedBlocks, dummy, i);
159
160
// Optimizations often propagate information between blocks
161
// To make sure the register and spill state is correct when blocks are lowered, we check that sorted block order matches the expected one
162
if (block.expectedNextBlock != ~0u)
163
CODEGEN_ASSERT(function.getBlockIndex(nextBlock) == block.expectedNextBlock);
164
165
// Block might establish a safe environment right at the start
166
if (FFlag::LuauCodegenBlockSafeEnv && (block.flags & kBlockFlagSafeEnvCheck) != 0)
167
{
168
if (options.includeIr)
169
{
170
if (options.includeIrPrefix == IncludeIrPrefix::Yes)
171
build.logAppend("# ");
172
173
build.logAppend(" implicit CHECK_SAFE_ENV exit(%u)\n", block.startpc);
174
}
175
176
CODEGEN_ASSERT(block.startpc != kBlockNoStartPc);
177
lowering.checkSafeEnv(IrOp{IrOpKind::VmExit, block.startpc}, nextBlock);
178
}
179
180
for (uint32_t index = block.start; index <= block.finish; index++)
181
{
182
CODEGEN_ASSERT(index < function.instructions.size());
183
184
uint32_t bcLocation = bcLocations[index];
185
186
// If IR instruction is the first one for the original bytecode, we can annotate it with source code text
187
if (outputEnabled && options.annotator && bcLocation != ~0u)
188
{
189
options.annotator(options.annotatorContext, build.text, bytecodeid, bcLocation);
190
191
// If available, report inferred register tags
192
BytecodeTypes bcTypes = function.getBytecodeTypesAt(bcLocation);
193
194
if (bcTypes.result != LBC_TYPE_ANY || bcTypes.a != LBC_TYPE_ANY || bcTypes.b != LBC_TYPE_ANY || bcTypes.c != LBC_TYPE_ANY)
195
{
196
toString(ctx.result, bcTypes, options.compilationOptions.userdataTypes);
197
198
build.logAppend("\n");
199
}
200
}
201
202
// If bytecode needs the location of this instruction for jumps, record it
203
if (bcLocation != ~0u)
204
{
205
Label label = (index == block.start) ? block.label : build.setLabel();
206
function.bcMapping[bcLocation].asmLocation = build.getLabelOffset(label);
207
}
208
209
IrInst& inst = function.instructions[index];
210
211
// Skip pseudo instructions, but make sure they are not used at this stage
212
// This also prevents them from getting into text output when that's enabled
213
if (isPseudo(inst.cmd))
214
{
215
CODEGEN_ASSERT(inst.useCount == 0);
216
continue;
217
}
218
219
// Either instruction result value is not referenced or the use count is not zero
220
CODEGEN_ASSERT(inst.lastUse == 0 || inst.useCount != 0);
221
222
if (options.includeIr)
223
{
224
if (options.includeIrPrefix == IncludeIrPrefix::Yes)
225
build.logAppend("# ");
226
227
toStringDetailed(ctx, block, blockIndex, inst, index, options.includeUseInfo);
228
}
229
230
lowering.lowerInst(inst, index, nextBlock);
231
232
if (lowering.hasError())
233
{
234
// Place labels for all blocks that we're skipping
235
// This is needed to avoid AssemblyBuilder assertions about jumps in earlier blocks with unplaced labels
236
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
237
{
238
IrBlock& abandoned = function.blocks[sortedBlocks[j]];
239
240
build.setLabel(abandoned.label);
241
}
242
243
lowering.finishFunction();
244
245
return false;
246
}
247
}
248
249
lowering.finishBlock(block, nextBlock);
250
251
if (options.includeIr && options.includeIrPrefix == IncludeIrPrefix::Yes)
252
build.logAppend("#\n");
253
254
if (block.expectedNextBlock == ~0u)
255
function.validRestoreOpBlocks.clear();
256
}
257
258
if (!seenFallback)
259
{
260
textSize = build.text.length();
261
codeSize = build.getCodeSize();
262
}
263
264
lowering.finishFunction();
265
266
if (outputEnabled && !options.includeOutlinedCode && textSize < build.text.size())
267
{
268
build.text.resize(textSize);
269
270
if (options.includeAssembly)
271
build.logAppend("; skipping %u bytes of outlined code\n", unsigned((build.getCodeSize() - codeSize) * sizeof(build.code[0])));
272
}
273
274
return true;
275
}
276
277
inline bool lowerIr(
278
X64::AssemblyBuilderX64& build,
279
IrBuilder& ir,
280
const std::vector<uint32_t>& sortedBlocks,
281
ModuleHelpers& helpers,
282
Proto* proto,
283
AssemblyOptions options,
284
LoweringStats* stats
285
)
286
{
287
optimizeMemoryOperandsX64(ir.function);
288
289
X64::IrLoweringX64 lowering(build, helpers, ir.function, stats);
290
291
return lowerImpl(build, lowering, ir.function, sortedBlocks, proto->bytecodeid, options);
292
}
293
294
inline bool lowerIr(
295
A64::AssemblyBuilderA64& build,
296
IrBuilder& ir,
297
const std::vector<uint32_t>& sortedBlocks,
298
ModuleHelpers& helpers,
299
Proto* proto,
300
AssemblyOptions options,
301
LoweringStats* stats
302
)
303
{
304
A64::IrLoweringA64 lowering(build, helpers, ir.function, stats);
305
306
return lowerImpl(build, lowering, ir.function, sortedBlocks, proto->bytecodeid, options);
307
}
308
309
template<typename AssemblyBuilder>
310
inline bool lowerFunction(
311
IrBuilder& ir,
312
AssemblyBuilder& build,
313
ModuleHelpers& helpers,
314
Proto* proto,
315
AssemblyOptions options,
316
LoweringStats* stats,
317
CodeGenCompilationResult& codeGenCompilationResult
318
)
319
{
320
ir.function.stats = stats;
321
ir.function.recordCounters = options.compilationOptions.recordCounters;
322
323
killUnusedBlocks(ir.function);
324
325
unsigned preOptBlockCount = 0;
326
unsigned maxBlockInstructions = 0;
327
328
for (const IrBlock& block : ir.function.blocks)
329
{
330
preOptBlockCount += (block.kind != IrBlockKind::Dead);
331
unsigned blockInstructions = block.finish - block.start;
332
maxBlockInstructions = std::max(maxBlockInstructions, blockInstructions);
333
}
334
335
// we update stats before checking the heuristic so that even if we bail out
336
// our stats include information about the limit that was exceeded.
337
if (stats)
338
{
339
stats->blocksPreOpt += preOptBlockCount;
340
stats->maxBlockInstructions = maxBlockInstructions;
341
}
342
343
if (preOptBlockCount >= unsigned(FInt::CodegenHeuristicsBlockLimit.value))
344
{
345
codeGenCompilationResult = CodeGenCompilationResult::CodeGenOverflowBlockLimit;
346
return false;
347
}
348
349
if (maxBlockInstructions >= unsigned(FInt::CodegenHeuristicsBlockInstructionLimit.value))
350
{
351
codeGenCompilationResult = CodeGenCompilationResult::CodeGenOverflowBlockInstructionLimit;
352
return false;
353
}
354
355
computeCfgInfo(ir.function);
356
357
constPropInBlockChains(ir);
358
359
if (!FFlag::DebugCodegenOptSize)
360
{
361
double startTime = 0.0;
362
unsigned constPropInstructionCount = 0;
363
364
if (stats)
365
{
366
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE);
367
startTime = lua_clock();
368
}
369
370
createLinearBlocks(ir);
371
372
if (stats)
373
{
374
stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime;
375
constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount;
376
stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount;
377
}
378
}
379
380
markDeadStoresInBlockChains(ir);
381
382
// Recompute the CFG predecessors/successors to match block uses after optimizations
383
computeCfgBlockEdges(ir.function);
384
385
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(ir.function);
386
387
// In order to allocate registers during lowering, we need to know where instruction results are last used
388
updateLastUseLocations(ir.function, sortedBlocks);
389
390
if (stats)
391
{
392
for (const IrBlock& block : ir.function.blocks)
393
{
394
if (block.kind != IrBlockKind::Dead)
395
++stats->blocksPostOpt;
396
}
397
}
398
399
bool result = lowerIr(build, ir, sortedBlocks, helpers, proto, options, stats);
400
401
if (!result)
402
codeGenCompilationResult = CodeGenCompilationResult::CodeGenLoweringFailure;
403
404
return result;
405
}
406
407
} // namespace CodeGen
408
} // namespace Luau
409
410