Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Roblox
GitHub Repository: Roblox/luau
Path: blob/master/CLI/src/Bytecode.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 "lua.h"
3
#include "lualib.h"
4
5
#include "Luau/CodeGen.h"
6
#include "Luau/Compiler.h"
7
#include "Luau/BytecodeBuilder.h"
8
#include "Luau/Parser.h"
9
#include "Luau/BytecodeSummary.h"
10
#include "Luau/FileUtils.h"
11
#include "Luau/Flags.h"
12
13
#include <memory>
14
15
using Luau::CodeGen::FunctionBytecodeSummary;
16
17
struct GlobalOptions
18
{
19
int optimizationLevel = 1;
20
int debugLevel = 1;
21
} globalOptions;
22
23
static Luau::CompileOptions copts()
24
{
25
Luau::CompileOptions result = {};
26
result.optimizationLevel = globalOptions.optimizationLevel;
27
result.debugLevel = globalOptions.debugLevel;
28
result.typeInfoLevel = 1;
29
30
return result;
31
}
32
33
static void displayHelp(const char* argv0)
34
{
35
printf("Usage: %s [options] [file list]\n", argv0);
36
printf("\n");
37
printf("Available options:\n");
38
printf(" -h, --help: Display this usage message.\n");
39
printf(" -O<n>: compile with optimization level n (default 1, n should be between 0 and 2).\n");
40
printf(" -g<n>: compile with debug level n (default 1, n should be between 0 and 2).\n");
41
printf(" --fflags=<flags>: comma-separated list of fast flags to enable/disable (--fflags=true,false,LuauFlag1=true,LuauFlag2=false).\n");
42
printf(" --summary-file=<filename>: file in which bytecode analysis summary will be recorded (default 'bytecode-summary.json').\n");
43
44
exit(0);
45
}
46
47
static bool parseArgs(int argc, char** argv, std::string& summaryFile)
48
{
49
for (int i = 1; i < argc; i++)
50
{
51
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
52
{
53
displayHelp(argv[0]);
54
}
55
else if (strncmp(argv[i], "-O", 2) == 0)
56
{
57
int level = atoi(argv[i] + 2);
58
if (level < 0 || level > 2)
59
{
60
fprintf(stderr, "Error: Optimization level must be between 0 and 2 inclusive.\n");
61
return false;
62
}
63
globalOptions.optimizationLevel = level;
64
}
65
else if (strncmp(argv[i], "-g", 2) == 0)
66
{
67
int level = atoi(argv[i] + 2);
68
if (level < 0 || level > 2)
69
{
70
fprintf(stderr, "Error: Debug level must be between 0 and 2 inclusive.\n");
71
return false;
72
}
73
globalOptions.debugLevel = level;
74
}
75
else if (strncmp(argv[i], "--summary-file=", 15) == 0)
76
{
77
summaryFile = argv[i] + 15;
78
79
if (summaryFile.size() == 0)
80
{
81
fprintf(stderr, "Error: filename missing for '--summary-file'.\n\n");
82
return false;
83
}
84
}
85
else if (strncmp(argv[i], "--fflags=", 9) == 0)
86
{
87
setLuauFlags(argv[i] + 9);
88
}
89
else if (argv[i][0] == '-')
90
{
91
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
92
displayHelp(argv[0]);
93
}
94
}
95
96
return true;
97
}
98
99
static void report(const char* name, const Luau::Location& location, const char* type, const char* message)
100
{
101
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
102
}
103
104
static void reportError(const char* name, const Luau::ParseError& error)
105
{
106
report(name, error.getLocation(), "SyntaxError", error.what());
107
}
108
109
static void reportError(const char* name, const Luau::CompileError& error)
110
{
111
report(name, error.getLocation(), "CompileError", error.what());
112
}
113
114
static bool analyzeFile(const char* name, const unsigned nestingLimit, std::vector<FunctionBytecodeSummary>& summaries)
115
{
116
std::optional<std::string> source = readFile(name);
117
118
if (!source)
119
{
120
fprintf(stderr, "Error opening %s\n", name);
121
return false;
122
}
123
124
try
125
{
126
Luau::BytecodeBuilder bcb;
127
128
compileOrThrow(bcb, *source, copts());
129
130
const std::string& bytecode = bcb.getBytecode();
131
132
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
133
lua_State* L = globalState.get();
134
135
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
136
{
137
summaries = Luau::CodeGen::summarizeBytecode(L, -1, nestingLimit);
138
return true;
139
}
140
else
141
{
142
fprintf(stderr, "Error loading bytecode %s\n", name);
143
return false;
144
}
145
}
146
catch (Luau::ParseErrors& e)
147
{
148
for (auto& error : e.getErrors())
149
reportError(name, error);
150
return false;
151
}
152
catch (Luau::CompileError& e)
153
{
154
reportError(name, e);
155
return false;
156
}
157
158
return true;
159
}
160
161
static std::string escapeFilename(const std::string& filename)
162
{
163
std::string escaped;
164
escaped.reserve(filename.size());
165
166
for (const char ch : filename)
167
{
168
switch (ch)
169
{
170
case '\\':
171
escaped.push_back('/');
172
break;
173
case '"':
174
escaped.push_back('\\');
175
escaped.push_back(ch);
176
break;
177
default:
178
escaped.push_back(ch);
179
}
180
}
181
182
return escaped;
183
}
184
185
static void serializeFunctionSummary(const FunctionBytecodeSummary& summary, FILE* fp)
186
{
187
const unsigned nestingLimit = summary.getNestingLimit();
188
const unsigned opLimit = summary.getOpLimit();
189
190
fprintf(fp, " {\n");
191
fprintf(fp, " \"source\": \"%s\",\n", summary.getSource().c_str());
192
fprintf(fp, " \"name\": \"%s\",\n", summary.getName().c_str());
193
fprintf(fp, " \"line\": %d,\n", summary.getLine());
194
fprintf(fp, " \"nestingLimit\": %u,\n", nestingLimit);
195
fprintf(fp, " \"counts\": [");
196
197
for (unsigned nesting = 0; nesting <= nestingLimit; ++nesting)
198
{
199
fprintf(fp, "\n [");
200
201
for (unsigned i = 0; i < opLimit; ++i)
202
{
203
fprintf(fp, "%d", summary.getCount(nesting, uint8_t(i)));
204
if (i < opLimit - 1)
205
fprintf(fp, ", ");
206
}
207
208
fprintf(fp, "]");
209
if (nesting < nestingLimit)
210
fprintf(fp, ",");
211
}
212
213
fprintf(fp, "\n ]");
214
fprintf(fp, "\n }");
215
}
216
217
static void serializeScriptSummary(const std::string& file, const std::vector<FunctionBytecodeSummary>& scriptSummary, FILE* fp)
218
{
219
std::string escaped(escapeFilename(file));
220
const size_t functionCount = scriptSummary.size();
221
222
fprintf(fp, " \"%s\": [\n", escaped.c_str());
223
224
for (size_t i = 0; i < functionCount; ++i)
225
{
226
serializeFunctionSummary(scriptSummary[i], fp);
227
fprintf(fp, i == (functionCount - 1) ? "\n" : ",\n");
228
}
229
230
fprintf(fp, " ]");
231
}
232
233
static bool serializeSummaries(
234
const std::vector<std::string>& files,
235
const std::vector<std::vector<FunctionBytecodeSummary>>& scriptSummaries,
236
const std::string& summaryFile
237
)
238
{
239
240
FILE* fp = fopen(summaryFile.c_str(), "w");
241
const size_t fileCount = files.size();
242
243
if (!fp)
244
{
245
fprintf(stderr, "Unable to open '%s'.\n", summaryFile.c_str());
246
return false;
247
}
248
249
fprintf(fp, "{\n");
250
251
for (size_t i = 0; i < fileCount; ++i)
252
{
253
serializeScriptSummary(files[i], scriptSummaries[i], fp);
254
fprintf(fp, i < (fileCount - 1) ? ",\n" : "\n");
255
}
256
257
fprintf(fp, "}");
258
fclose(fp);
259
260
return true;
261
}
262
263
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
264
{
265
printf("%s(%d): ASSERTION FAILED: %s\n", file, line, expr);
266
return 1;
267
}
268
269
int main(int argc, char** argv)
270
{
271
Luau::assertHandler() = assertionHandler;
272
273
setLuauFlagsDefault();
274
275
std::string summaryFile("bytecode-summary.json");
276
unsigned nestingLimit = 0;
277
278
if (!parseArgs(argc, argv, summaryFile))
279
return 1;
280
281
const std::vector<std::string> files = getSourceFiles(argc, argv);
282
size_t fileCount = files.size();
283
284
std::vector<std::vector<FunctionBytecodeSummary>> scriptSummaries;
285
scriptSummaries.reserve(fileCount);
286
287
for (size_t i = 0; i < fileCount; ++i)
288
{
289
if (!analyzeFile(files[i].c_str(), nestingLimit, scriptSummaries[i]))
290
return 1;
291
}
292
293
if (!serializeSummaries(files, scriptSummaries, summaryFile))
294
return 1;
295
296
fprintf(stdout, "Bytecode summary written to '%s'\n", summaryFile.c_str());
297
298
return 0;
299
}
300
301