// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "lua.h" #include "lualib.h" #include "Luau/CodeGen.h" #include "Luau/Compiler.h" #include "Luau/BytecodeBuilder.h" #include "Luau/Parser.h" #include "Luau/TimeTrace.h" #include "FileUtils.h" #include "Flags.h" #include #ifdef _WIN32 #include #include #endif LUAU_FASTFLAG(DebugLuauTimeTracing) enum class CompileFormat { Text, Binary, Remarks, Codegen, // Prints annotated native code including IR and assembly CodegenAsm, // Prints annotated native code assembly CodegenIr, // Prints annotated native code IR CodegenVerbose, // Prints annotated native code including IR, assembly and outlined code CodegenNull, Null }; enum class RecordStats { None, Total, Split }; struct GlobalOptions { int optimizationLevel = 1; int debugLevel = 1; } globalOptions; static Luau::CompileOptions copts() { Luau::CompileOptions result = {}; result.optimizationLevel = globalOptions.optimizationLevel; result.debugLevel = globalOptions.debugLevel; return result; } static std::optional getCompileFormat(const char* name) { if (strcmp(name, "text") == 0) return CompileFormat::Text; else if (strcmp(name, "binary") == 0) return CompileFormat::Binary; else if (strcmp(name, "text") == 0) return CompileFormat::Text; else if (strcmp(name, "remarks") == 0) return CompileFormat::Remarks; else if (strcmp(name, "codegen") == 0) return CompileFormat::Codegen; else if (strcmp(name, "codegenasm") == 0) return CompileFormat::CodegenAsm; else if (strcmp(name, "codegenir") == 0) return CompileFormat::CodegenIr; else if (strcmp(name, "codegenverbose") == 0) return CompileFormat::CodegenVerbose; else if (strcmp(name, "codegennull") == 0) return CompileFormat::CodegenNull; else if (strcmp(name, "null") == 0) return CompileFormat::Null; else return std::nullopt; } static void report(const char* name, const Luau::Location& location, const char* type, const char* message) { fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message); } static void reportError(const char* name, const Luau::ParseError& error) { report(name, error.getLocation(), "SyntaxError", error.what()); } static void reportError(const char* name, const Luau::CompileError& error) { report(name, error.getLocation(), "CompileError", error.what()); } static std::string getCodegenAssembly( const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options, Luau::CodeGen::LoweringStats* stats) { std::unique_ptr globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0) return Luau::CodeGen::getAssembly(L, -1, options, stats); fprintf(stderr, "Error loading bytecode %s\n", name); return ""; } static void annotateInstruction(void* context, std::string& text, int fid, int instpos) { Luau::BytecodeBuilder& bcb = *(Luau::BytecodeBuilder*)context; bcb.annotateInstruction(text, fid, instpos); } struct CompileStats { size_t lines; size_t bytecode; size_t bytecodeInstructionCount; size_t codegen; double readTime; double miscTime; double parseTime; double compileTime; double codegenTime; Luau::CodeGen::LoweringStats lowerStats; void serializeToJson(FILE* fp) { // use compact one-line formatting to reduce file length fprintf(fp, "{\ \"lines\": %zu, \ \"bytecode\": %zu, \ \"bytecodeInstructionCount\": %zu, \ \"codegen\": %zu, \ \"readTime\": %f, \ \"miscTime\": %f, \ \"parseTime\": %f, \ \"compileTime\": %f, \ \"codegenTime\": %f, \ \"lowerStats\": {\ \"totalFunctions\": %u, \ \"skippedFunctions\": %u, \ \"spillsToSlot\": %d, \ \"spillsToRestore\": %d, \ \"maxSpillSlotsUsed\": %u, \ \"blocksPreOpt\": %u, \ \"blocksPostOpt\": %u, \ \"maxBlockInstructions\": %u, \ \"regAllocErrors\": %d, \ \"loweringErrors\": %d\ }, \ \"blockLinearizationStats\": {\ \"constPropInstructionCount\": %u, \ \"timeSeconds\": %f\ }}", lines, bytecode, bytecodeInstructionCount, codegen, readTime, miscTime, parseTime, compileTime, codegenTime, lowerStats.totalFunctions, lowerStats.skippedFunctions, lowerStats.spillsToSlot, lowerStats.spillsToRestore, lowerStats.maxSpillSlotsUsed, lowerStats.blocksPreOpt, lowerStats.blocksPostOpt, lowerStats.maxBlockInstructions, lowerStats.regAllocErrors, lowerStats.loweringErrors, lowerStats.blockLinearizationStats.constPropInstructionCount, lowerStats.blockLinearizationStats.timeSeconds); } CompileStats& operator+=(const CompileStats& that) { this->lines += that.lines; this->bytecode += that.bytecode; this->bytecodeInstructionCount += that.bytecodeInstructionCount; this->codegen += that.codegen; this->readTime += that.readTime; this->miscTime += that.miscTime; this->parseTime += that.parseTime; this->compileTime += that.compileTime; this->codegenTime += that.codegenTime; this->lowerStats += that.lowerStats; return *this; } CompileStats operator+(const CompileStats& other) const { CompileStats result(*this); result += other; return result; } }; static double recordDeltaTime(double& timer) { double now = Luau::TimeTrace::getClock(); double delta = now - timer; timer = now; return delta; } static bool compileFile(const char* name, CompileFormat format, Luau::CodeGen::AssemblyOptions::Target assemblyTarget, CompileStats& stats) { double currts = Luau::TimeTrace::getClock(); std::optional source = readFile(name); if (!source) { fprintf(stderr, "Error opening %s\n", name); return false; } stats.readTime += recordDeltaTime(currts); // NOTE: Normally, you should use Luau::compile or luau_compile (see lua_require as an example) // This function is much more complicated because it supports many output human-readable formats through internal interfaces try { Luau::BytecodeBuilder bcb; Luau::CodeGen::AssemblyOptions options; options.target = assemblyTarget; options.outputBinary = format == CompileFormat::CodegenNull; if (!options.outputBinary) { options.includeAssembly = format != CompileFormat::CodegenIr; options.includeIr = format != CompileFormat::CodegenAsm; options.includeOutlinedCode = format == CompileFormat::CodegenVerbose; } options.annotator = annotateInstruction; options.annotatorContext = &bcb; if (format == CompileFormat::Text) { bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | Luau::BytecodeBuilder::Dump_Remarks); bcb.setDumpSource(*source); } else if (format == CompileFormat::Remarks) { bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks); bcb.setDumpSource(*source); } else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr || format == CompileFormat::CodegenVerbose) { bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals | Luau::BytecodeBuilder::Dump_Remarks); bcb.setDumpSource(*source); } stats.miscTime += recordDeltaTime(currts); Luau::Allocator allocator; Luau::AstNameTable names(allocator); Luau::ParseResult result = Luau::Parser::parse(source->c_str(), source->size(), names, allocator); if (!result.errors.empty()) throw Luau::ParseErrors(result.errors); stats.lines += result.lines; stats.parseTime += recordDeltaTime(currts); Luau::compileOrThrow(bcb, result, names, copts()); stats.bytecode += bcb.getBytecode().size(); stats.bytecodeInstructionCount = bcb.getTotalInstructionCount(); stats.compileTime += recordDeltaTime(currts); switch (format) { case CompileFormat::Text: printf("%s", bcb.dumpEverything().c_str()); break; case CompileFormat::Remarks: printf("%s", bcb.dumpSourceRemarks().c_str()); break; case CompileFormat::Binary: fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout); break; case CompileFormat::Codegen: case CompileFormat::CodegenAsm: case CompileFormat::CodegenIr: case CompileFormat::CodegenVerbose: printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).c_str()); break; case CompileFormat::CodegenNull: stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options, &stats.lowerStats).size(); stats.codegenTime += recordDeltaTime(currts); break; case CompileFormat::Null: break; } return true; } catch (Luau::ParseErrors& e) { for (auto& error : e.getErrors()) reportError(name, error); return false; } catch (Luau::CompileError& e) { reportError(name, e); return false; } } static void displayHelp(const char* argv0) { printf("Usage: %s [--mode] [options] [file list]\n", argv0); printf("\n"); printf("Available modes:\n"); printf(" binary, text, remarks, codegen\n"); printf("\n"); printf("Available options:\n"); printf(" -h, --help: Display this usage message.\n"); printf(" -O: compile with optimization level n (default 1, n should be between 0 and 2).\n"); printf(" -g: compile with debug level n (default 1, n should be between 0 and 2).\n"); printf(" --target=: compile code for specific architecture (a64, x64, a64_nf, x64_ms).\n"); printf(" --timetrace: record compiler time tracing information into trace.json\n"); printf(" --record-stats=