From d458d240cdc07765cfe6bd81417faa3f642d0d75 Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 16 Jun 2023 10:35:18 -0700 Subject: [PATCH] Sync to upstream/release/581 (#958) * Definition files can now ascribe indexers to class types. (https://github.com/Roblox/luau/pull/949) * Remove --compile support from the REPL. You can just use luau-compile instead. * When an exception is thrown during parallel typechecking (usually an ICE), we now gracefully stop typechecking and drain active workers before rethrowing the exception. New solver * Include more source location information when we hit an internal compiler error * Improve the logic that simplifies intersections of tables JIT * Save testable type annotations to bytecode * Improve block placement for linearized blocks * Add support for lea reg, [rip+offset] for labels * Unify X64 and A64 codegen for RETURN * Outline interrupt handlers for X64 * Remove global rArgN in favor of build.abi * Change A64 INTERRUPT lowering to match X64 --------- Co-authored-by: Arseny Kapoulkine Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintSolver.h | 8 +- Analysis/src/ConstraintSolver.cpp | 140 +++++------ Analysis/src/Frontend.cpp | 49 +++- Analysis/src/Normalize.cpp | 8 +- Analysis/src/Simplify.cpp | 12 + Analysis/src/Substitution.cpp | 7 +- CLI/Repl.cpp | 293 +--------------------- CodeGen/include/Luau/AssemblyBuilderX64.h | 3 + CodeGen/include/Luau/IrData.h | 2 + CodeGen/include/Luau/IrDump.h | 2 + CodeGen/src/AssemblyBuilderX64.cpp | 24 +- CodeGen/src/CodeAllocator.cpp | 2 + CodeGen/src/CodeGen.cpp | 20 +- CodeGen/src/CodeGenA64.cpp | 10 +- CodeGen/src/CodeGenX64.cpp | 18 +- CodeGen/src/EmitCommon.h | 2 +- CodeGen/src/EmitCommonX64.cpp | 52 ++-- CodeGen/src/EmitCommonX64.h | 27 +- CodeGen/src/EmitInstructionX64.cpp | 46 +++- CodeGen/src/EmitInstructionX64.h | 2 +- CodeGen/src/IrBuilder.cpp | 1 + CodeGen/src/IrDump.cpp | 151 +++++++++-- CodeGen/src/IrLoweringA64.cpp | 36 +-- CodeGen/src/IrLoweringA64.h | 10 + CodeGen/src/IrLoweringX64.cpp | 38 ++- CodeGen/src/IrLoweringX64.h | 10 + CodeGen/src/OptimizeConstProp.cpp | 9 +- Common/include/Luau/Bytecode.h | 23 +- Compiler/include/Luau/BytecodeBuilder.h | 5 + Compiler/src/BytecodeBuilder.cpp | 100 ++++++++ Compiler/src/Compiler.cpp | 79 ++---- Compiler/src/Types.cpp | 106 ++++++++ Compiler/src/Types.h | 9 + Sources.cmake | 2 + VM/src/lvm.h | 1 - VM/src/lvmexecute.cpp | 18 +- VM/src/lvmload.cpp | 73 +++--- bench/bench.py | 61 ++++- bench/bench_support.lua | 42 ++++ tests/AssemblyBuilderX64.test.cpp | 16 ++ tests/Compiler.test.cpp | 63 ++++- tests/Simplify.test.cpp | 2 +- 42 files changed, 969 insertions(+), 613 deletions(-) create mode 100644 Compiler/src/Types.cpp create mode 100644 Compiler/src/Types.h diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index ef87175..b13bb21 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -174,10 +174,10 @@ struct ConstraintSolver bool blockOnPendingTypes(TypePackId target, NotNull constraint); void unblock(NotNull progressed); - void unblock(TypeId progressed); - void unblock(TypePackId progressed); - void unblock(const std::vector& types); - void unblock(const std::vector& packs); + void unblock(TypeId progressed, Location location); + void unblock(TypePackId progressed, Location location); + void unblock(const std::vector& types, Location location); + void unblock(const std::vector& packs, Location location); /** * @returns true if the TypeId is in a blocked state. diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index c9ac8cc..b85d2c5 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -539,8 +539,8 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullty.emplace(builtinTypes->errorRecoveryType()); } - unblock(c.generalizedType); - unblock(c.sourceType); + unblock(c.generalizedType, constraint->location); + unblock(c.sourceType, constraint->location); return true; } @@ -564,7 +564,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNulllocation); asMutable(c.subType)->ty.emplace(errorRecoveryType()); - unblock(c.subType); + unblock(c.subType, constraint->location); return true; } @@ -574,7 +574,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNullscope, constraint->location, this}; queuer.traverse(c.subType); - unblock(c.subType); + unblock(c.subType, constraint->location); return true; } @@ -597,7 +597,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNullty.emplace(builtinTypes->booleanType); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } case AstExprUnary::Len: @@ -605,7 +605,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNullty.emplace(builtinTypes->numberType); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } case AstExprUnary::Minus: @@ -635,7 +635,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNullty.emplace(builtinTypes->errorRecoveryType()); } - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } } @@ -684,7 +684,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull(leftType) && !isLogical)) { asMutable(resultType)->ty.emplace(errorRecoveryType()); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -697,7 +697,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullty.emplace(builtinTypes->booleanType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -760,7 +760,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullty.emplace(mmResult); - unblock(resultType); + unblock(resultType, constraint->location); (*c.astOriginalCallTypes)[c.astFragment] = *mm; (*c.astOverloadResolvedTypes)[c.astFragment] = *instantiatedMm; @@ -790,14 +790,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullscope); asMutable(resultType)->ty.emplace(anyPresent ? builtinTypes->anyType : leftType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } else if (get(leftType) || get(rightType)) { unify(leftType, rightType, constraint->scope); asMutable(resultType)->ty.emplace(builtinTypes->neverType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -814,14 +814,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullscope); asMutable(resultType)->ty.emplace(anyPresent ? builtinTypes->anyType : leftType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } else if (get(leftType) || get(rightType)) { unify(leftType, rightType, constraint->scope); asMutable(resultType)->ty.emplace(builtinTypes->neverType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -840,14 +840,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullisExactlyNumber() || get(lt->tops)) && rt->isExactlyNumber()) { asMutable(resultType)->ty.emplace(builtinTypes->booleanType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } if (lt && rt && (lt->isSubtypeOfString() || get(lt->tops)) && rt->isSubtypeOfString()) { asMutable(resultType)->ty.emplace(builtinTypes->booleanType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -855,7 +855,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull(leftType) || get(rightType)) { asMutable(resultType)->ty.emplace(builtinTypes->booleanType); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -867,7 +867,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullty.emplace(builtinTypes->booleanType); - unblock(resultType); + unblock(resultType, constraint->location); return true; // And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is // truthy. @@ -876,7 +876,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullfalsyType).result; asMutable(resultType)->ty.emplace(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result); - unblock(resultType); + unblock(resultType, constraint->location); return true; } // Or evaluates to the LHS type if the LHS is truthy, and the RHS type if @@ -886,7 +886,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNulltruthyType).result; asMutable(resultType)->ty.emplace(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result); - unblock(resultType); + unblock(resultType, constraint->location); return true; } default: @@ -898,7 +898,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNullscope); unify(rightType, errorRecoveryType(), constraint->scope); asMutable(resultType)->ty.emplace(errorRecoveryType()); - unblock(resultType); + unblock(resultType, constraint->location); return true; } @@ -1065,14 +1065,14 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul const PendingExpansionType* petv = get(follow(c.target)); if (!petv) { - unblock(c.target); + unblock(c.target, constraint->location); return true; } - auto bindResult = [this, &c](TypeId result) { + auto bindResult = [this, &c, constraint](TypeId result) { LUAU_ASSERT(get(c.target)); asMutable(c.target)->ty.emplace(result); - unblock(c.target); + unblock(c.target, constraint->location); }; std::optional tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value) @@ -1400,9 +1400,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullgetChanges(); bestOverloadLog->commit(); - unblock(changedTypes); - unblock(changedPacks); - unblock(c.result); + unblock(changedTypes, constraint->location); + unblock(changedPacks, constraint->location); + unblock(c.result, constraint->location); InstantiationQueuer queuer{constraint->scope, constraint->location, this}; queuer.traverse(fn); @@ -1421,7 +1421,7 @@ bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNullty.emplace(bindTo); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1440,7 +1440,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNullty.emplace(TableState::Free, TypeLevel{}, constraint->scope); ttv.props[c.prop] = Property{c.resultType}; asMutable(c.resultType)->ty.emplace(constraint->scope); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1454,7 +1454,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNullty.emplace(result.value_or(builtinTypes->anyType)); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1568,7 +1568,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNullscope); bind(c.resultType, c.subjectType); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1593,8 +1593,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNulllocation); + unblock(c.resultType, constraint->location); return true; } else if (auto ttv = getMutable(subjectType)) @@ -1605,7 +1605,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNullprops[c.path[0]] = Property{c.propType}; bind(c.resultType, c.subjectType); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } else if (ttv->state == TableState::Unsealed) @@ -1614,14 +1614,14 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNulllocation); + unblock(c.resultType, constraint->location); return true; } else { bind(c.resultType, subjectType); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } } @@ -1630,7 +1630,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNulllocation); return true; } } @@ -1649,8 +1649,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullty.emplace(subjectType); asMutable(c.propType)->ty.emplace(scope); - unblock(c.propType); - unblock(c.resultType); + unblock(c.propType, constraint->location); + unblock(c.resultType, constraint->location); return true; } @@ -1662,8 +1662,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullindexer->indexType, constraint->scope); asMutable(c.propType)->ty.emplace(tt->indexer->indexResultType); asMutable(c.resultType)->ty.emplace(subjectType); - unblock(c.propType); - unblock(c.resultType); + unblock(c.propType, constraint->location); + unblock(c.resultType, constraint->location); return true; } else if (tt->state == TableState::Free || tt->state == TableState::Unsealed) @@ -1675,8 +1675,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullindexer = TableIndexer{promotedIndexTy, c.propType}; asMutable(c.propType)->ty.emplace(tt->scope); asMutable(c.resultType)->ty.emplace(subjectType); - unblock(c.propType); - unblock(c.resultType); + unblock(c.propType, constraint->location); + unblock(c.resultType, constraint->location); return true; } // Do not augment sealed or generic tables that lack indexers @@ -1684,8 +1684,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullty.emplace(builtinTypes->errorRecoveryType()); asMutable(c.resultType)->ty.emplace(builtinTypes->errorRecoveryType()); - unblock(c.propType); - unblock(c.resultType); + unblock(c.propType, constraint->location); + unblock(c.resultType, constraint->location); return true; } @@ -1704,7 +1704,7 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul else *asMutable(c.resultType) = BoundType{builtinTypes->anyType}; - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1720,7 +1720,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullty.emplace(sourcePack); - unblock(resultPack); + unblock(resultPack, constraint->location); return true; } @@ -1745,7 +1745,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullty.emplace(srcTy); - unblock(*destIter); + unblock(*destIter, constraint->location); } else unify(*destIter, srcTy, constraint->scope); @@ -1763,7 +1763,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullty.emplace(builtinTypes->errorRecoveryType()); - unblock(*destIter); + unblock(*destIter, constraint->location); } ++destIter; @@ -1852,7 +1852,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNullty.emplace(c.type); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1880,7 +1880,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNullty.emplace(c.discriminant); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1892,7 +1892,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNullty.emplace(result); - unblock(c.resultType); + unblock(c.resultType, constraint->location); return true; } @@ -1904,10 +1904,10 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNulllocation, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force); for (TypeId r : result.reducedTypes) - unblock(r); + unblock(r, constraint->location); for (TypePackId r : result.reducedPacks) - unblock(r); + unblock(r, constraint->location); if (force) return true; @@ -1928,10 +1928,10 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNulllocation, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force); for (TypeId r : result.reducedTypes) - unblock(r); + unblock(r, constraint->location); for (TypePackId r : result.reducedPacks) - unblock(r); + unblock(r, constraint->location); if (force) return true; @@ -2374,8 +2374,8 @@ bool ConstraintSolver::tryUnify(NotNull constraint, TID subTy, u.log.commit(); - unblock(changedTypes); - unblock(changedPacks); + unblock(changedTypes, constraint->location); + unblock(changedPacks, constraint->location); return true; } @@ -2509,7 +2509,7 @@ void ConstraintSolver::unblock(NotNull progressed) return unblock_(progressed.get()); } -void ConstraintSolver::unblock(TypeId ty) +void ConstraintSolver::unblock(TypeId ty, Location location) { DenseHashSet seen{nullptr}; @@ -2517,7 +2517,7 @@ void ConstraintSolver::unblock(TypeId ty) while (true) { if (seen.find(progressed)) - iceReporter.ice("ConstraintSolver::unblock encountered a self-bound type!"); + iceReporter.ice("ConstraintSolver::unblock encountered a self-bound type!", location); seen.insert(progressed); if (logger) @@ -2532,7 +2532,7 @@ void ConstraintSolver::unblock(TypeId ty) } } -void ConstraintSolver::unblock(TypePackId progressed) +void ConstraintSolver::unblock(TypePackId progressed, Location) { if (logger) logger->popBlock(progressed); @@ -2540,16 +2540,16 @@ void ConstraintSolver::unblock(TypePackId progressed) return unblock_(progressed); } -void ConstraintSolver::unblock(const std::vector& types) +void ConstraintSolver::unblock(const std::vector& types, Location location) { for (TypeId t : types) - unblock(t); + unblock(t, location); } -void ConstraintSolver::unblock(const std::vector& packs) +void ConstraintSolver::unblock(const std::vector& packs, Location location) { for (TypePackId t : packs) - unblock(t); + unblock(t, location); } bool ConstraintSolver::isBlocked(TypeId ty) @@ -2586,8 +2586,8 @@ ErrorVec ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull Frontend::checkQueuedModules(std::optional nextItems; + std::optional itemWithException; while (remaining != 0) { @@ -603,17 +605,25 @@ std::vector Frontend::checkQueuedModules(std::optional Frontend::checkQueuedModules(std::optional checkedModules; @@ -1104,6 +1123,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vectorname = sourceModule.name; result->humanReadableName = sourceModule.humanReadableName; + iceHandler->moduleName = sourceModule.name; + std::unique_ptr logger; if (recordJsonLog) { @@ -1189,9 +1210,19 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect prepareModuleScope(name, scope, forAutocomplete); }; - return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler}, - NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver}, - environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, recordJsonLog); + try + { + return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler}, + NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver}, + environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, recordJsonLog); + } + catch (const InternalCompilerError& err) + { + InternalCompilerError augmented = err.location.has_value() + ? InternalCompilerError{err.message, sourceModule.humanReadableName, *err.location} + : InternalCompilerError{err.message, sourceModule.humanReadableName}; + throw augmented; + } } else { diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 3af7e85..e4f22f3 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -2117,15 +2117,15 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there TypeId hmtable = nullptr; if (const MetatableType* hmtv = get(here)) { - htable = hmtv->table; - hmtable = hmtv->metatable; + htable = follow(hmtv->table); + hmtable = follow(hmtv->metatable); } TypeId ttable = there; TypeId tmtable = nullptr; if (const MetatableType* tmtv = get(there)) { - ttable = tmtv->table; - tmtable = tmtv->metatable; + ttable = follow(tmtv->table); + tmtable = follow(tmtv->metatable); } const TableType* httv = get(htable); diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 8e9424a..e17df38 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -6,6 +6,7 @@ #include "Luau/ToString.h" #include "Luau/TypeArena.h" #include "Luau/Normalize.h" // TypeIds +#include LUAU_FASTINT(LuauTypeReductionRecursionLimit) @@ -236,6 +237,17 @@ Relation relateTables(TypeId left, TypeId right) NotNull leftTable{get(left)}; NotNull rightTable{get(right)}; LUAU_ASSERT(1 == rightTable->props.size()); + // Disjoint props have nothing in common + // t1 with props p1's cannot appear in t2 and t2 with props p2's cannot appear in t1 + bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props), [&](auto prop) { + return rightTable->props.find(prop.first) != end(rightTable->props); + }); + bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) { + return leftTable->props.find(prop.first) != end(leftTable->props); + }); + + if (!(foundPropFromLeftInRight || foundPropFromRightInLeft) && leftTable->props.size() >= 1 && rightTable->props.size() >= 1) + return Relation::Disjoint; const auto [propName, rightProp] = *begin(rightTable->props); diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 26cbdc6..655881a 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -111,11 +111,14 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a else if constexpr (std::is_same_v) return dest.addType(a); else if constexpr (std::is_same_v) - return ty; + return dest.addType(a); else if constexpr (std::is_same_v) return ty; else if constexpr (std::is_same_v) - return ty; + { + PendingExpansionType clone = PendingExpansionType{a.prefix, a.name, a.typeArguments, a.packArguments}; + return dest.addType(std::move(clone)); + } else if constexpr (std::is_same_v) return ty; else if constexpr (std::is_same_v) diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp index a585a73..87ce271 100644 --- a/CLI/Repl.cpp +++ b/CLI/Repl.cpp @@ -6,7 +6,6 @@ #include "Luau/CodeGen.h" #include "Luau/Compiler.h" -#include "Luau/BytecodeBuilder.h" #include "Luau/Parser.h" #include "Luau/TimeTrace.h" @@ -40,27 +39,6 @@ LUAU_FASTFLAG(DebugLuauTimeTracing) -enum class CliMode -{ - Unknown, - Repl, - Compile, - RunSourceFiles -}; - -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 -}; - constexpr int MaxTraversalLimit = 50; static bool codegen = false; @@ -668,178 +646,11 @@ static bool runFile(const char* name, lua_State* GL, bool repl) return status == 0; } -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) -{ - 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); - - 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 codegen; - - double readTime; - double miscTime; - double parseTime; - double compileTime; - double codegenTime; -}; - -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, 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.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.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).c_str()); - break; - case CompileFormat::CodegenNull: - stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).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("Usage: %s [options] [file list]\n", argv0); printf("\n"); - printf("When mode and file list are omitted, an interactive REPL is started instead.\n"); - printf("\n"); - printf("Available modes:\n"); - printf(" omitted: compile and run input files one by one\n"); - printf(" --compile[=format]: compile input files and output resulting bytecode/assembly (binary, text, remarks, codegen)\n"); + printf("When file list is omitted, an interactive REPL is started instead.\n"); printf("\n"); printf("Available options:\n"); printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n"); @@ -864,67 +675,12 @@ int replMain(int argc, char** argv) setLuauFlagsDefault(); - CliMode mode = CliMode::Unknown; - CompileFormat compileFormat{}; int profile = 0; bool coverage = false; bool interactive = false; bool codegenPerf = false; - // Set the mode if the user has explicitly specified one. - int argStart = 1; - if (argc >= 2 && strncmp(argv[1], "--compile", strlen("--compile")) == 0) - { - argStart++; - mode = CliMode::Compile; - if (strcmp(argv[1], "--compile") == 0) - { - compileFormat = CompileFormat::Text; - } - else if (strcmp(argv[1], "--compile=binary") == 0) - { - compileFormat = CompileFormat::Binary; - } - else if (strcmp(argv[1], "--compile=text") == 0) - { - compileFormat = CompileFormat::Text; - } - else if (strcmp(argv[1], "--compile=remarks") == 0) - { - compileFormat = CompileFormat::Remarks; - } - else if (strcmp(argv[1], "--compile=codegen") == 0) - { - compileFormat = CompileFormat::Codegen; - } - else if (strcmp(argv[1], "--compile=codegenasm") == 0) - { - compileFormat = CompileFormat::CodegenAsm; - } - else if (strcmp(argv[1], "--compile=codegenir") == 0) - { - compileFormat = CompileFormat::CodegenIr; - } - else if (strcmp(argv[1], "--compile=codegenverbose") == 0) - { - compileFormat = CompileFormat::CodegenVerbose; - } - else if (strcmp(argv[1], "--compile=codegennull") == 0) - { - compileFormat = CompileFormat::CodegenNull; - } - else if (strcmp(argv[1], "--compile=null") == 0) - { - compileFormat = CompileFormat::Null; - } - else - { - fprintf(stderr, "Error: Unrecognized value for '--compile' specified.\n"); - return 1; - } - } - - for (int i = argStart; i < argc; i++) + for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { @@ -1026,50 +782,20 @@ int replMain(int argc, char** argv) #endif } - const std::vector files = getSourceFiles(argc, argv); - if (mode == CliMode::Unknown) - { - mode = files.empty() ? CliMode::Repl : CliMode::RunSourceFiles; - } - - if (mode != CliMode::Compile && codegen && !Luau::CodeGen::isSupported()) + if (codegen && !Luau::CodeGen::isSupported()) { fprintf(stderr, "Cannot enable --codegen, native code generation is not supported in current configuration\n"); return 1; } - switch (mode) - { - case CliMode::Compile: - { -#ifdef _WIN32 - if (compileFormat == CompileFormat::Binary) - _setmode(_fileno(stdout), _O_BINARY); -#endif + const std::vector files = getSourceFiles(argc, argv); - CompileStats stats = {}; - int failed = 0; - - for (const std::string& path : files) - failed += !compileFile(path.c_str(), compileFormat, stats); - - if (compileFormat == CompileFormat::Null) - printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000), - int(stats.bytecode / 1024), stats.readTime, stats.parseTime, stats.compileTime); - else if (compileFormat == CompileFormat::CodegenNull) - printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n", - int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024), - stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime, - stats.codegenTime); - - return failed ? 1 : 0; - } - case CliMode::Repl: + if (files.empty()) { runRepl(); return 0; } - case CliMode::RunSourceFiles: + else { std::unique_ptr globalState(luaL_newstate(), lua_close); lua_State* L = globalState.get(); @@ -1101,9 +827,4 @@ int replMain(int argc, char** argv) return failed ? 1 : 0; } - case CliMode::Unknown: - default: - LUAU_ASSERT(!"Unhandled cli mode."); - return 1; - } } diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 9e7d501..aea01ee 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -98,6 +98,8 @@ public: void call(Label& label); void call(OperandX64 op); + void lea(RegisterX64 lhs, Label& label); + void int3(); void ud2(); @@ -243,6 +245,7 @@ private: LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3, OperandX64 op4); LUAU_NOINLINE void log(Label label); LUAU_NOINLINE void log(const char* opcode, Label label); + LUAU_NOINLINE void log(const char* opcode, RegisterX64 reg, Label label); void log(OperandX64 op); const char* getSizeName(SizeX64 size) const; diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 4a3fa42..1c79ccb 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -801,6 +801,8 @@ struct IrBlock uint32_t start = ~0u; uint32_t finish = ~0u; + uint32_t sortkey = ~0u; + Label label; }; diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index 179edd0..2f86ebf 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -38,6 +38,8 @@ std::string toString(const IrFunction& function, bool includeUseInfo); std::string dump(const IrFunction& function); std::string toDot(const IrFunction& function, bool includeInst); +std::string toDotCfg(const IrFunction& function); +std::string toDotDjGraph(const IrFunction& function); std::string dumpDot(const IrFunction& function, bool includeInst); diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index c7644a8..2a8bc92 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -463,6 +463,20 @@ void AssemblyBuilderX64::call(OperandX64 op) commit(); } +void AssemblyBuilderX64::lea(RegisterX64 lhs, Label& label) +{ + LUAU_ASSERT(lhs.size == SizeX64::qword); + + placeBinaryRegAndRegMem(lhs, OperandX64(SizeX64::qword, noreg, 1, rip, 0), 0x8d, 0x8d); + + codePos -= 4; + placeLabel(label); + commit(); + + if (logText) + log("lea", lhs, label); +} + void AssemblyBuilderX64::int3() { if (logText) @@ -1415,7 +1429,7 @@ void AssemblyBuilderX64::commit() { LUAU_ASSERT(codePos <= codeEnd); - if (codeEnd - codePos < kMaxInstructionLength) + if (unsigned(codeEnd - codePos) < kMaxInstructionLength) extend(); } @@ -1501,6 +1515,14 @@ void AssemblyBuilderX64::log(const char* opcode, Label label) logAppend(" %-12s.L%d\n", opcode, label.id); } +void AssemblyBuilderX64::log(const char* opcode, RegisterX64 reg, Label label) +{ + logAppend(" %-12s", opcode); + log(reg); + text.append(","); + logAppend(".L%d\n", label.id); +} + void AssemblyBuilderX64::log(OperandX64 op) { switch (op.cat) diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index 09e1bb7..880a324 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -56,8 +56,10 @@ static void makePagesExecutable(uint8_t* mem, size_t size) static void flushInstructionCache(uint8_t* mem, size_t size) { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) if (FlushInstructionCache(GetCurrentProcess(), mem, size) == 0) LUAU_ASSERT(!"Failed to flush instruction cache"); +#endif } #else static uint8_t* allocatePages(size_t size) diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 89399cb..d7283b4 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -125,7 +125,7 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback); // Try to order by instruction order - return a.start < b.start; + return a.sortkey < b.sortkey; }); // For each IR instruction that begins a bytecode instruction, which bytecode instruction is it? @@ -234,6 +234,8 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& build.setLabel(abandoned.label); } + lowering.finishFunction(); + return false; } } @@ -244,7 +246,15 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& build.logAppend("#\n"); } - if (outputEnabled && !options.includeOutlinedCode && seenFallback) + if (!seenFallback) + { + textSize = build.text.length(); + codeSize = build.getCodeSize(); + } + + lowering.finishFunction(); + + if (outputEnabled && !options.includeOutlinedCode && textSize < build.text.size()) { build.text.resize(textSize); @@ -594,6 +604,12 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options) X64::assembleHelpers(build, helpers); #endif + if (!options.includeOutlinedCode && options.includeAssembly) + { + build.text.clear(); + build.logAppend("; skipping %u bytes of outlined helpers\n", unsigned(build.getCodeSize() * sizeof(build.code[0]))); + } + for (Proto* p : protos) if (p) if (std::optional np = assembleFunction(build, data, helpers, p, options)) diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 355e29c..cc01318 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -288,27 +288,27 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers) { if (build.logText) build.logAppend("; exitContinueVm\n"); - helpers.exitContinueVm = build.setLabel(); + build.setLabel(helpers.exitContinueVm); emitExit(build, /* continueInVm */ true); if (build.logText) build.logAppend("; exitNoContinueVm\n"); - helpers.exitNoContinueVm = build.setLabel(); + build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); if (build.logText) build.logAppend("; reentry\n"); - helpers.reentry = build.setLabel(); + build.setLabel(helpers.reentry); emitReentry(build, helpers); if (build.logText) build.logAppend("; interrupt\n"); - helpers.interrupt = build.setLabel(); + build.setLabel(helpers.interrupt); emitInterrupt(build); if (build.logText) build.logAppend("; return\n"); - helpers.return_ = build.setLabel(); + build.setLabel(helpers.return_); emitReturn(build, helpers); } diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 4100e66..41c3dbd 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -56,6 +56,11 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde locations.start = build.setLabel(); unwind.startFunction(); + RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; + RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi; + RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx; + RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx; + // Save common non-volatile registers if (build.abi == ABIX64::SystemV) { @@ -177,22 +182,27 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers) { if (build.logText) build.logAppend("; exitContinueVm\n"); - helpers.exitContinueVm = build.setLabel(); + build.setLabel(helpers.exitContinueVm); emitExit(build, /* continueInVm */ true); if (build.logText) build.logAppend("; exitNoContinueVm\n"); - helpers.exitNoContinueVm = build.setLabel(); + build.setLabel(helpers.exitNoContinueVm); emitExit(build, /* continueInVm */ false); if (build.logText) build.logAppend("; continueCallInVm\n"); - helpers.continueCallInVm = build.setLabel(); + build.setLabel(helpers.continueCallInVm); emitContinueCallInVm(build); + if (build.logText) + build.logAppend("; interrupt\n"); + build.setLabel(helpers.interrupt); + emitInterrupt(build); + if (build.logText) build.logAppend("; return\n"); - helpers.return_ = build.setLabel(); + build.setLabel(helpers.return_); emitReturn(build, helpers); } diff --git a/CodeGen/src/EmitCommon.h b/CodeGen/src/EmitCommon.h index bfdde16..f912ffb 100644 --- a/CodeGen/src/EmitCommon.h +++ b/CodeGen/src/EmitCommon.h @@ -25,13 +25,13 @@ struct ModuleHelpers Label exitContinueVm; Label exitNoContinueVm; Label return_; + Label interrupt; // X64 Label continueCallInVm; // A64 Label reentry; // x0: closure - Label interrupt; // x0: pc offset, x1: return address, x2: interrupt }; } // namespace CodeGen diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 4ad4efe..f240d26 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -278,39 +278,34 @@ void emitUpdateBase(AssemblyBuilderX64& build) build.mov(rBase, qword[rState + offsetof(lua_State, base)]); } -static void emitSetSavedPc(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos) +void emitInterrupt(AssemblyBuilderX64& build) { - ScopedRegX64 tmp1{regs, SizeX64::qword}; - ScopedRegX64 tmp2{regs, SizeX64::qword}; + // rax = pcpos + 1 + // rbx = return address in native code - build.mov(tmp1.reg, sCode); - build.add(tmp1.reg, pcpos * sizeof(Instruction)); - build.mov(tmp2.reg, qword[rState + offsetof(lua_State, ci)]); - build.mov(qword[tmp2.reg + offsetof(CallInfo, savedpc)], tmp1.reg); -} + // note: rbx is non-volatile so it will be saved across interrupt call automatically + + RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; + RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi; -void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos) -{ Label skip; - ScopedRegX64 tmp{regs, SizeX64::qword}; + // Update L->ci->savedpc; required in case interrupt errors + build.mov(rcx, sCode); + build.lea(rcx, addr[rcx + rax * sizeof(Instruction)]); + build.mov(rax, qword[rState + offsetof(lua_State, ci)]); + build.mov(qword[rax + offsetof(CallInfo, savedpc)], rcx); - // Skip if there is no interrupt set - build.mov(tmp.reg, qword[rState + offsetof(lua_State, global)]); - build.mov(tmp.reg, qword[tmp.reg + offsetof(global_State, cb.interrupt)]); - build.test(tmp.reg, tmp.reg); + // Load interrupt handler; it may be nullptr in case the update raced with the check before we got here + build.mov(rax, qword[rState + offsetof(lua_State, global)]); + build.mov(rax, qword[rax + offsetof(global_State, cb.interrupt)]); + build.test(rax, rax); build.jcc(ConditionX64::Zero, skip); - emitSetSavedPc(regs, build, pcpos + 1); - // Call interrupt - // TODO: This code should move to the end of the function, or even be outlined so that it can be shared by multiple interruptible instructions - IrCallWrapperX64 callWrap(regs, build); - callWrap.addArgument(SizeX64::qword, rState); - callWrap.addArgument(SizeX64::dword, -1); - callWrap.call(tmp.release()); - - emitUpdateBase(build); // interrupt may have reallocated stack + build.mov(rArg1, rState); + build.mov(dwordReg(rArg2), -1); + build.call(rax); // Check if we need to exit build.mov(al, byte[rState + offsetof(lua_State, status)]); @@ -322,6 +317,10 @@ void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos) emitExit(build, /* continueInVm */ false); build.setLabel(skip); + + emitUpdateBase(build); // interrupt may have reallocated stack + + build.jmp(rbx); } void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos) @@ -354,14 +353,15 @@ void emitContinueCallInVm(AssemblyBuilderX64& build) void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers) { - // input: ci in r8, res in rdi, number of written values in ecx - RegisterX64 ci = r8; + // input: res in rdi, number of written values in ecx RegisterX64 res = rdi; RegisterX64 written = ecx; + RegisterX64 ci = r8; RegisterX64 cip = r9; RegisterX64 nresults = esi; + build.mov(ci, qword[rState + offsetof(lua_State, ci)]); build.lea(cip, addr[ci - sizeof(CallInfo)]); // nresults = ci->nresults diff --git a/CodeGen/src/EmitCommonX64.h b/CodeGen/src/EmitCommonX64.h index eb4532a..37be73f 100644 --- a/CodeGen/src/EmitCommonX64.h +++ b/CodeGen/src/EmitCommonX64.h @@ -53,31 +53,6 @@ constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* cod constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16]; constexpr OperandX64 sSpillArea = addr[rsp + kStackSize + 24]; -// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts -#if defined(_WIN32) - -constexpr RegisterX64 rArg1 = rcx; -constexpr RegisterX64 rArg2 = rdx; -constexpr RegisterX64 rArg3 = r8; -constexpr RegisterX64 rArg4 = r9; -constexpr RegisterX64 rArg5 = noreg; -constexpr RegisterX64 rArg6 = noreg; -constexpr OperandX64 sArg5 = qword[rsp + 32]; -constexpr OperandX64 sArg6 = qword[rsp + 40]; - -#else - -constexpr RegisterX64 rArg1 = rdi; -constexpr RegisterX64 rArg2 = rsi; -constexpr RegisterX64 rArg3 = rdx; -constexpr RegisterX64 rArg4 = rcx; -constexpr RegisterX64 rArg5 = r8; -constexpr RegisterX64 rArg6 = r9; -constexpr OperandX64 sArg5 = noreg; -constexpr OperandX64 sArg6 = noreg; - -#endif - inline OperandX64 luauReg(int ri) { return xmmword[rBase + ri * sizeof(TValue)]; @@ -202,7 +177,7 @@ void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build); void emitExit(AssemblyBuilderX64& build, bool continueInVm); void emitUpdateBase(AssemblyBuilderX64& build); -void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos); +void emitInterrupt(AssemblyBuilderX64& build); void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos); void emitContinueCallInVm(AssemblyBuilderX64& build); diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index 5d1c642..61d5ac6 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -18,6 +18,12 @@ namespace X64 void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults) { + // TODO: This should use IrCallWrapperX64 + RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; + RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi; + RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx; + RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx; + build.mov(rArg1, rState); build.lea(rArg2, luauRegAddress(ra)); @@ -163,20 +169,34 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int } } -void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults) +void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults, bool functionVariadic) { - RegisterX64 ci = r8; RegisterX64 res = rdi; RegisterX64 written = ecx; - build.mov(ci, qword[rState + offsetof(lua_State, ci)]); - build.mov(res, qword[ci + offsetof(CallInfo, func)]); + if (functionVariadic) + { + build.mov(res, qword[rState + offsetof(lua_State, ci)]); + build.mov(res, qword[res + offsetof(CallInfo, func)]); + } + else if (actualResults != 1) + build.lea(res, addr[rBase - sizeof(TValue)]); // invariant: ci->func + 1 == ci->base for non-variadic frames if (actualResults == 0) { build.xor_(written, written); build.jmp(helpers.return_); } + else if (actualResults == 1 && !functionVariadic) + { + // fast path: minimizes res adjustments + // note that we skipped res computation for this specific case above + build.vmovups(xmm0, luauReg(ra)); + build.vmovups(xmmword[rBase - sizeof(TValue)], xmm0); + build.mov(res, rBase); + build.mov(written, 1); + build.jmp(helpers.return_); + } else if (actualResults >= 1 && actualResults <= 3) { for (int r = 0; r < actualResults; ++r) @@ -206,8 +226,11 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i Label repeatValueLoop, exitValueLoop; - build.cmp(vali, valend); - build.jcc(ConditionX64::NotBelow, exitValueLoop); + if (actualResults == LUA_MULTRET) + { + build.cmp(vali, valend); + build.jcc(ConditionX64::NotBelow, exitValueLoop); + } build.setLabel(repeatValueLoop); build.vmovups(xmm0, xmmword[vali]); @@ -225,6 +248,11 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index) { + // TODO: This should use IrCallWrapperX64 + RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; + RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi; + RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx; + OperandX64 last = index + count - 1; // Using non-volatile 'rbx' for dynamic 'count' value (for LUA_MULTRET) to skip later recomputation @@ -327,6 +355,12 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // ipairs-style traversal is handled in IR LUAU_ASSERT(aux >= 0); + // TODO: This should use IrCallWrapperX64 + RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi; + RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi; + RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx; + RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx; + // This is a fast-path for builtin table iteration, tag check for 'ra' has to be performed before emitting this instruction // Registers are chosen in this way to simplify fallback code for the node part diff --git a/CodeGen/src/EmitInstructionX64.h b/CodeGen/src/EmitInstructionX64.h index 84fe113..b248b7e 100644 --- a/CodeGen/src/EmitInstructionX64.h +++ b/CodeGen/src/EmitInstructionX64.h @@ -18,7 +18,7 @@ class AssemblyBuilderX64; struct IrRegAllocX64; void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults); -void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults); +void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults, bool functionVariadic); void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index); void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 6ab5e24..98db297 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -429,6 +429,7 @@ void IrBuilder::beginBlock(IrOp block) LUAU_ASSERT(target.start == ~0u || target.start == uint32_t(function.instructions.size())); target.start = uint32_t(function.instructions.size()); + target.sortkey = target.start; inTerminatedBlock = false; } diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 7ea9b79..09cafba 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -656,28 +656,23 @@ std::string dump(const IrFunction& function) return result; } -std::string toDot(const IrFunction& function, bool includeInst) +static void appendLabelRegset(IrToStringContext& ctx, const std::vector& regSets, size_t blockIdx, const char* name) { - std::string result; - IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + if (blockIdx < regSets.size()) + { + const RegisterSet& rs = regSets[blockIdx]; - auto appendLabelRegset = [&ctx](const std::vector& regSets, size_t blockIdx, const char* name) { - if (blockIdx < regSets.size()) + if (rs.regs.any() || rs.varargSeq) { - const RegisterSet& rs = regSets[blockIdx]; - - if (rs.regs.any() || rs.varargSeq) - { - append(ctx.result, "|{%s|", name); - appendRegisterSet(ctx, rs, "|"); - append(ctx.result, "}"); - } + append(ctx.result, "|{%s|", name); + appendRegisterSet(ctx, rs, "|"); + append(ctx.result, "}"); } - }; - - append(ctx.result, "digraph CFG {\n"); - append(ctx.result, "node[shape=record]\n"); + } +} +static void appendBlocks(IrToStringContext& ctx, const IrFunction& function, bool includeInst, bool includeIn, bool includeOut, bool includeDef) +{ for (size_t i = 0; i < function.blocks.size(); i++) { const IrBlock& block = function.blocks[i]; @@ -692,7 +687,8 @@ std::string toDot(const IrFunction& function, bool includeInst) append(ctx.result, "label=\"{"); toString(ctx, block, uint32_t(i)); - appendLabelRegset(ctx.cfg.in, i, "in"); + if (includeIn) + appendLabelRegset(ctx, ctx.cfg.in, i, "in"); if (includeInst && block.start != ~0u) { @@ -709,11 +705,25 @@ std::string toDot(const IrFunction& function, bool includeInst) } } - appendLabelRegset(ctx.cfg.def, i, "def"); - appendLabelRegset(ctx.cfg.out, i, "out"); + if (includeDef) + appendLabelRegset(ctx, ctx.cfg.def, i, "def"); + + if (includeOut) + appendLabelRegset(ctx, ctx.cfg.out, i, "out"); append(ctx.result, "}\"];\n"); } +} + +std::string toDot(const IrFunction& function, bool includeInst) +{ + std::string result; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + + append(ctx.result, "digraph CFG {\n"); + append(ctx.result, "node[shape=record]\n"); + + appendBlocks(ctx, function, includeInst, /* includeIn */ true, /* includeOut */ true, /* includeDef */ true); for (size_t i = 0; i < function.blocks.size(); i++) { @@ -750,6 +760,107 @@ std::string toDot(const IrFunction& function, bool includeInst) return result; } +std::string toDotCfg(const IrFunction& function) +{ + std::string result; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + + append(ctx.result, "digraph CFG {\n"); + append(ctx.result, "node[shape=record]\n"); + + appendBlocks(ctx, function, /* includeInst */ false, /* includeIn */ false, /* includeOut */ false, /* includeDef */ true); + + for (size_t i = 0; i < function.blocks.size() && i < ctx.cfg.successorsOffsets.size(); i++) + { + BlockIteratorWrapper succ = successors(ctx.cfg, unsigned(i)); + + for (uint32_t target : succ) + append(ctx.result, "b%u -> b%u;\n", unsigned(i), target); + } + + append(ctx.result, "}\n"); + + return result; +} + +std::string toDotDjGraph(const IrFunction& function) +{ + std::string result; + IrToStringContext ctx{result, function.blocks, function.constants, function.cfg}; + + append(ctx.result, "digraph CFG {\n"); + + for (size_t i = 0; i < ctx.blocks.size(); i++) + { + const IrBlock& block = ctx.blocks[i]; + + append(ctx.result, "b%u [", unsigned(i)); + + if (block.kind == IrBlockKind::Fallback) + append(ctx.result, "style=filled;fillcolor=salmon;"); + else if (block.kind == IrBlockKind::Bytecode) + append(ctx.result, "style=filled;fillcolor=palegreen;"); + + append(ctx.result, "label=\""); + toString(ctx, block, uint32_t(i)); + append(ctx.result, "\"];\n"); + } + + // Layer by depth in tree + uint32_t depth = 0; + bool found = true; + + while (found) + { + found = false; + + append(ctx.result, "{rank = same;"); + for (size_t i = 0; i < ctx.cfg.domOrdering.size(); i++) + { + if (ctx.cfg.domOrdering[i].depth == depth) + { + append(ctx.result, "b%u;", unsigned(i)); + found = true; + } + } + append(ctx.result, "}\n"); + + depth++; + } + + for (size_t i = 0; i < ctx.cfg.domChildrenOffsets.size(); i++) + { + BlockIteratorWrapper dom = domChildren(ctx.cfg, unsigned(i)); + + for (uint32_t target : dom) + append(ctx.result, "b%u -> b%u;\n", unsigned(i), target); + + // Join edges are all successor edges that do not strongly dominate + BlockIteratorWrapper succ = successors(ctx.cfg, unsigned(i)); + + for (uint32_t successor : succ) + { + bool found = false; + + for (uint32_t target : dom) + { + if (target == successor) + { + found = true; + break; + } + } + + if (!found) + append(ctx.result, "b%u -> b%u [style=dotted];\n", unsigned(i), successor); + } + } + + append(ctx.result, "}\n"); + + return result; +} + std::string dumpDot(const IrFunction& function, bool includeInst) { std::string result = toDot(function, includeInst); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 5c29ad4..94c46db 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -1165,25 +1165,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::INTERRUPT: { - RegisterA64 temp = regs.allocTemp(KindA64::x); + regs.spill(build, index); - Label skip, next; - build.ldr(temp, mem(rState, offsetof(lua_State, global))); - build.ldr(temp, mem(temp, offsetof(global_State, cb.interrupt))); - build.cbz(temp, skip); + Label self; - size_t spills = regs.spill(build, index); + build.ldr(x0, mem(rState, offsetof(lua_State, global))); + build.ldr(x0, mem(x0, offsetof(global_State, cb.interrupt))); + build.cbnz(x0, self); - // Jump to outlined interrupt handler, it will give back control to x1 - build.mov(x0, (uintOp(inst.a) + 1) * sizeof(Instruction)); - build.adr(x1, next); - build.b(helpers.interrupt); + Label next = build.setLabel(); - build.setLabel(next); - - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state - - build.setLabel(skip); + interruptHandlers.push_back({self, uintOp(inst.a), next}); break; } case IrCmd::CHECK_GC: @@ -1733,6 +1725,20 @@ void IrLoweringA64::finishBlock() regs.assertNoSpills(); } +void IrLoweringA64::finishFunction() +{ + if (build.logText) + build.logAppend("; interrupt handlers\n"); + + for (InterruptHandler& handler : interruptHandlers) + { + build.setLabel(handler.self); + build.mov(x0, (handler.pcpos + 1) * sizeof(Instruction)); + build.adr(x1, handler.next); + build.b(helpers.interrupt); + } +} + bool IrLoweringA64::hasError() const { return error; diff --git a/CodeGen/src/IrLoweringA64.h b/CodeGen/src/IrLoweringA64.h index 1df09bd..fc228cf 100644 --- a/CodeGen/src/IrLoweringA64.h +++ b/CodeGen/src/IrLoweringA64.h @@ -27,6 +27,7 @@ struct IrLoweringA64 void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); void finishBlock(); + void finishFunction(); bool hasError() const; @@ -53,6 +54,13 @@ struct IrLoweringA64 IrBlock& blockOp(IrOp op) const; Label& labelOp(IrOp op) const; + struct InterruptHandler + { + Label self; + unsigned int pcpos; + Label next; + }; + AssemblyBuilderA64& build; ModuleHelpers& helpers; NativeState& data; @@ -63,6 +71,8 @@ struct IrLoweringA64 IrValueLocationTracking valueTracker; + std::vector interruptHandlers; + bool error = false; }; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index b9c35df..320cb07 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -958,8 +958,27 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) break; } case IrCmd::INTERRUPT: - emitInterrupt(regs, build, uintOp(inst.a)); + { + unsigned pcpos = uintOp(inst.a); + + // We unconditionally spill values here because that allows us to ignore register state when we synthesize interrupt handler + // This can be changed in the future if we can somehow record interrupt handler code separately + // Since interrupts are loop edges or call/ret, we don't have a significant opportunity for register reuse here anyway + regs.preserveAndFreeInstValues(); + + ScopedRegX64 tmp{regs, SizeX64::qword}; + + Label self; + + build.mov(tmp.reg, qword[rState + offsetof(lua_State, global)]); + build.cmp(qword[tmp.reg + offsetof(global_State, cb.interrupt)], 0); + build.jcc(ConditionX64::NotEqual, self); + + Label next = build.setLabel(); + + interruptHandlers.push_back({self, pcpos, next}); break; + } case IrCmd::CHECK_GC: callStepGc(regs, build); break; @@ -991,7 +1010,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) } case IrCmd::SET_SAVEDPC: { - // This is like emitSetSavedPc, but using register allocation instead of relying on rax/rdx ScopedRegX64 tmp1{regs, SizeX64::qword}; ScopedRegX64 tmp2{regs, SizeX64::qword}; @@ -1048,7 +1066,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next) case IrCmd::RETURN: regs.assertAllFree(); regs.assertNoSpills(); - emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b)); + emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b), function.variadic); break; case IrCmd::FORGLOOP: regs.assertAllFree(); @@ -1350,6 +1368,20 @@ void IrLoweringX64::finishBlock() regs.assertNoSpills(); } +void IrLoweringX64::finishFunction() +{ + if (build.logText) + build.logAppend("; interrupt handlers\n"); + + for (InterruptHandler& handler : interruptHandlers) + { + build.setLabel(handler.self); + build.mov(rax, handler.pcpos + 1); + build.lea(rbx, handler.next); + build.jmp(helpers.interrupt); + } +} + bool IrLoweringX64::hasError() const { // If register allocator had to use more stack slots than we have available, this function can't run natively diff --git a/CodeGen/src/IrLoweringX64.h b/CodeGen/src/IrLoweringX64.h index cab4a85..a375a33 100644 --- a/CodeGen/src/IrLoweringX64.h +++ b/CodeGen/src/IrLoweringX64.h @@ -29,6 +29,7 @@ struct IrLoweringX64 void lowerInst(IrInst& inst, uint32_t index, IrBlock& next); void finishBlock(); + void finishFunction(); bool hasError() const; @@ -53,6 +54,13 @@ struct IrLoweringX64 IrBlock& blockOp(IrOp op) const; Label& labelOp(IrOp op) const; + struct InterruptHandler + { + Label self; + unsigned int pcpos; + Label next; + }; + AssemblyBuilderX64& build; ModuleHelpers& helpers; NativeState& data; @@ -62,6 +70,8 @@ struct IrLoweringX64 IrRegAllocX64 regs; IrValueLocationTracking valueTracker; + + std::vector interruptHandlers; }; } // namespace X64 diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 338bb49..b779fb4 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -1059,16 +1059,21 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited // TODO: using values from the first block can cause 'live out' of the linear block predecessor to not have all required registers constPropInBlock(build, startingBlock, state); - // Veryfy that target hasn't changed + // Verify that target hasn't changed LUAU_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx); + // Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below + uint32_t startingInsn = startingBlock.start; + // Create new linearized block into which we are going to redirect starting block jump IrOp newBlock = build.block(IrBlockKind::Linearized); visited.push_back(false); - // TODO: placement of linear blocks in final lowering is sub-optimal, it should follow our predecessor build.beginBlock(newBlock); + // By default, blocks are ordered according to start instruction; we alter sort order to make sure linearized block is placed right after the starting block + function.blocks[newBlock.index].sortkey = startingInsn + 1; + replace(function, termInst.a, newBlock); // Clone the collected path into our fresh block diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 54086d5..eab57b1 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -413,8 +413,10 @@ enum LuauBytecodeTag { // Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled LBC_VERSION_MIN = 3, - LBC_VERSION_MAX = 3, + LBC_VERSION_MAX = 4, LBC_VERSION_TARGET = 3, + // Type encoding version + LBC_TYPE_VERSION = 1, // Types of constant table entries LBC_CONSTANT_NIL = 0, LBC_CONSTANT_BOOLEAN, @@ -425,6 +427,25 @@ enum LuauBytecodeTag LBC_CONSTANT_CLOSURE, }; +// Type table tags +enum LuauBytecodeEncodedType +{ + LBC_TYPE_NIL = 0, + LBC_TYPE_BOOLEAN, + LBC_TYPE_NUMBER, + LBC_TYPE_STRING, + LBC_TYPE_TABLE, + LBC_TYPE_FUNCTION, + LBC_TYPE_THREAD, + LBC_TYPE_USERDATA, + LBC_TYPE_VECTOR, + + LBC_TYPE_ANY = 15, + LBC_TYPE_OPTIONAL_BIT = 1 << 7, + + LBC_TYPE_INVALID = 256, +}; + // Builtin function ids, used in LOP_FASTCALL enum LuauBuiltinFunction { diff --git a/Compiler/include/Luau/BytecodeBuilder.h b/Compiler/include/Luau/BytecodeBuilder.h index ba4232a..f3c2f47 100644 --- a/Compiler/include/Luau/BytecodeBuilder.h +++ b/Compiler/include/Luau/BytecodeBuilder.h @@ -74,6 +74,8 @@ public: void foldJumps(); void expandJumps(); + void setFunctionTypeInfo(std::string value); + void setDebugFunctionName(StringRef name); void setDebugFunctionLineDefined(int line); void setDebugLine(int line); @@ -118,6 +120,7 @@ public: std::string dumpFunction(uint32_t id) const; std::string dumpEverything() const; std::string dumpSourceRemarks() const; + std::string dumpTypeInfo() const; void annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const; @@ -132,6 +135,7 @@ public: static std::string getError(const std::string& message); static uint8_t getVersion(); + static uint8_t getTypeEncodingVersion(); private: struct Constant @@ -186,6 +190,7 @@ private: std::string dump; std::string dumpname; std::vector dumpinstoffs; + std::string typeinfo; }; struct DebugLocal diff --git a/Compiler/src/BytecodeBuilder.cpp b/Compiler/src/BytecodeBuilder.cpp index e2b769e..9296519 100644 --- a/Compiler/src/BytecodeBuilder.cpp +++ b/Compiler/src/BytecodeBuilder.cpp @@ -6,6 +6,8 @@ #include #include +LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false) + namespace Luau { @@ -513,6 +515,11 @@ bool BytecodeBuilder::patchSkipC(size_t jumpLabel, size_t targetLabel) return true; } +void BytecodeBuilder::setFunctionTypeInfo(std::string value) +{ + functions[currentFunction].typeinfo = std::move(value); +} + void BytecodeBuilder::setDebugFunctionName(StringRef name) { unsigned int index = addStringTableEntry(name); @@ -606,6 +613,13 @@ void BytecodeBuilder::finalize() bytecode = char(version); + if (FFlag::BytecodeVersion4) + { + uint8_t typesversion = getTypeEncodingVersion(); + LUAU_ASSERT(typesversion == 1); + writeByte(bytecode, typesversion); + } + writeStringTable(bytecode); writeVarInt(bytecode, uint32_t(functions.size())); @@ -628,6 +642,14 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const writeByte(ss, func.numupvalues); writeByte(ss, func.isvararg); + if (FFlag::BytecodeVersion4) + { + writeByte(ss, 0); // Reserved for cgflags + + writeVarInt(ss, uint32_t(func.typeinfo.size())); + ss.append(func.typeinfo); + } + // instructions writeVarInt(ss, uint32_t(insns.size())); @@ -1092,9 +1114,18 @@ std::string BytecodeBuilder::getError(const std::string& message) uint8_t BytecodeBuilder::getVersion() { // This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags + + if (FFlag::BytecodeVersion4) + return 4; + return LBC_VERSION_TARGET; } +uint8_t BytecodeBuilder::getTypeEncodingVersion() +{ + return LBC_TYPE_VERSION; +} + #ifdef LUAU_ASSERTENABLED void BytecodeBuilder::validate() const { @@ -2269,6 +2300,75 @@ std::string BytecodeBuilder::dumpSourceRemarks() const return result; } +static const char* getBaseTypeString(uint8_t type) +{ + uint8_t tag = type & ~LBC_TYPE_OPTIONAL_BIT; + switch (tag) + { + case LBC_TYPE_NIL: + return "nil"; + case LBC_TYPE_BOOLEAN: + return "boolean"; + case LBC_TYPE_NUMBER: + return "number"; + case LBC_TYPE_STRING: + return "string"; + case LBC_TYPE_TABLE: + return "{ }"; + case LBC_TYPE_FUNCTION: + return "function( )"; + case LBC_TYPE_THREAD: + return "thread"; + case LBC_TYPE_USERDATA: + return "userdata"; + case LBC_TYPE_VECTOR: + return "vector"; + case LBC_TYPE_ANY: + return "any"; + } + + LUAU_ASSERT(!"Unhandled type in getBaseTypeString"); + return nullptr; +} + +std::string BytecodeBuilder::dumpTypeInfo() const +{ + std::string result; + + for (size_t i = 0; i < functions.size(); ++i) + { + const std::string& typeinfo = functions[i].typeinfo; + if (typeinfo.empty()) + continue; + + uint8_t encodedType = typeinfo[0]; + + LUAU_ASSERT(encodedType == LBC_TYPE_FUNCTION); + + formatAppend(result, "%zu: function(", i); + + LUAU_ASSERT(typeinfo.size() >= 2); + + uint8_t numparams = typeinfo[1]; + + LUAU_ASSERT(size_t(1 + numparams - 1) < typeinfo.size()); + + for (uint8_t i = 0; i < numparams; ++i) + { + uint8_t et = typeinfo[2 + i]; + const char* optional = (et & LBC_TYPE_OPTIONAL_BIT) ? "?" : ""; + formatAppend(result, "%s%s", getBaseTypeString(et), optional); + + if (i + 1 != numparams) + formatAppend(result, ", "); + } + + formatAppend(result, ")\n"); + } + + return result; +} + void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const { if ((dumpFlags & Dump_Code) == 0) diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 6466722..8dd9876 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -10,6 +10,7 @@ #include "ConstantFolding.h" #include "CostModel.h" #include "TableShape.h" +#include "Types.h" #include "ValueTracking.h" #include @@ -25,7 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauCompileInlineDefer, false) +LUAU_FASTFLAGVARIABLE(CompileFunctionType, false) +LUAU_FASTFLAG(BytecodeVersion4) namespace Luau { @@ -202,6 +204,13 @@ struct Compiler setDebugLine(func); + if (FFlag::BytecodeVersion4 && FFlag::CompileFunctionType) + { + std::string funcType = getFunctionType(func); + if (!funcType.empty()) + bytecode.setFunctionTypeInfo(std::move(funcType)); + } + if (func->vararg) bytecode.emitABC(LOP_PREPVARARGS, uint8_t(self + func->args.size), 0, 0); @@ -560,15 +569,7 @@ struct Compiler size_t oldLocals = localStack.size(); std::vector args; - if (FFlag::LuauCompileInlineDefer) - { - args.reserve(func->args.size); - } - else - { - // note that we push the frame early; this is needed to block recursive inline attempts - inlineFrames.push_back({func, oldLocals, target, targetCount}); - } + args.reserve(func->args.size); // evaluate all arguments; note that we don't emit code for constant arguments (relying on constant folding) // note that compiler state (variable registers/values) does not change here - we defer that to a separate loop below to handle nested calls @@ -590,16 +591,8 @@ struct Compiler else LUAU_ASSERT(!"Unexpected expression type"); - if (FFlag::LuauCompileInlineDefer) - { - for (size_t j = i; j < func->args.size; ++j) - args.push_back({func->args.data[j], uint8_t(reg + (j - i))}); - } - else - { - for (size_t j = i; j < func->args.size; ++j) - pushLocal(func->args.data[j], uint8_t(reg + (j - i))); - } + for (size_t j = i; j < func->args.size; ++j) + args.push_back({func->args.data[j], uint8_t(reg + (j - i))}); // all remaining function arguments have been allocated and assigned to break; @@ -614,26 +607,17 @@ struct Compiler else bytecode.emitABC(LOP_LOADNIL, reg, 0, 0); - if (FFlag::LuauCompileInlineDefer) - args.push_back({var, reg}); - else - pushLocal(var, reg); + args.push_back({var, reg}); } else if (arg == nullptr) { // since the argument is not mutated, we can simply fold the value into the expressions that need it - if (FFlag::LuauCompileInlineDefer) - args.push_back({var, kInvalidReg, {Constant::Type_Nil}}); - else - locstants[var] = {Constant::Type_Nil}; + args.push_back({var, kInvalidReg, {Constant::Type_Nil}}); } else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown) { // since the argument is not mutated, we can simply fold the value into the expressions that need it - if (FFlag::LuauCompileInlineDefer) - args.push_back({var, kInvalidReg, *cv}); - else - locstants[var] = *cv; + args.push_back({var, kInvalidReg, *cv}); } else { @@ -643,20 +627,14 @@ struct Compiler // if the argument is a local that isn't mutated, we will simply reuse the existing register if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written)) { - if (FFlag::LuauCompileInlineDefer) - args.push_back({var, uint8_t(reg)}); - else - pushLocal(var, uint8_t(reg)); + args.push_back({var, uint8_t(reg)}); } else { uint8_t temp = allocReg(arg, 1); compileExprTemp(arg, temp); - if (FFlag::LuauCompileInlineDefer) - args.push_back({var, temp}); - else - pushLocal(var, temp); + args.push_back({var, temp}); } } } @@ -668,19 +646,16 @@ struct Compiler compileExprAuto(expr->args.data[i], rsi); } - if (FFlag::LuauCompileInlineDefer) - { - // apply all evaluated arguments to the compiler state - // note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal - for (InlineArg& arg : args) - if (arg.value.type == Constant::Type_Unknown) - pushLocal(arg.local, arg.reg); - else - locstants[arg.local] = arg.value; + // apply all evaluated arguments to the compiler state + // note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal + for (InlineArg& arg : args) + if (arg.value.type == Constant::Type_Unknown) + pushLocal(arg.local, arg.reg); + else + locstants[arg.local] = arg.value; - // the inline frame will be used to compile return statements as well as to reject recursive inlining attempts - inlineFrames.push_back({func, oldLocals, target, targetCount}); - } + // the inline frame will be used to compile return statements as well as to reject recursive inlining attempts + inlineFrames.push_back({func, oldLocals, target, targetCount}); // fold constant values updated above into expressions in the function body foldConstants(constants, variables, locstants, builtinsFold, func->body); diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp new file mode 100644 index 0000000..0204198 --- /dev/null +++ b/Compiler/src/Types.cpp @@ -0,0 +1,106 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/BytecodeBuilder.h" + +#include "Types.h" + +namespace Luau +{ + +static LuauBytecodeEncodedType getType(AstType* ty) +{ + if (AstTypeReference* ref = ty->as()) + { + if (ref->name == "nil") + return LBC_TYPE_NIL; + else if (ref->name == "boolean") + return LBC_TYPE_BOOLEAN; + else if (ref->name == "number") + return LBC_TYPE_NUMBER; + else if (ref->name == "string") + return LBC_TYPE_STRING; + else if (ref->name == "thread") + return LBC_TYPE_THREAD; + else if (ref->name == "any" || ref->name == "unknown") + return LBC_TYPE_ANY; + } + else if (AstTypeTable* table = ty->as()) + { + return LBC_TYPE_TABLE; + } + else if (AstTypeFunction* func = ty->as()) + { + return LBC_TYPE_FUNCTION; + } + else if (AstTypeUnion* un = ty->as()) + { + bool optional = false; + LuauBytecodeEncodedType type = LBC_TYPE_INVALID; + + for (AstType* ty : un->types) + { + LuauBytecodeEncodedType et = getType(ty); + + if (et == LBC_TYPE_NIL) + { + optional = true; + continue; + } + + if (type == LBC_TYPE_INVALID) + { + type = et; + continue; + } + + if (type != et) + return LBC_TYPE_ANY; + } + + if (type == LBC_TYPE_INVALID) + return LBC_TYPE_ANY; + + return LuauBytecodeEncodedType(type | (optional && (type != LBC_TYPE_ANY) ? LBC_TYPE_OPTIONAL_BIT : 0)); + } + else if (AstTypeIntersection* inter = ty->as()) + { + return LBC_TYPE_ANY; + } + + return LBC_TYPE_ANY; +} + +std::string getFunctionType(const AstExprFunction* func) +{ + if (func->vararg || func->generics.size || func->genericPacks.size) + return {}; + + bool self = func->self != 0; + + std::string typeInfo; + typeInfo.reserve(func->args.size + self + 2); + + typeInfo.push_back(LBC_TYPE_FUNCTION); + typeInfo.push_back(uint8_t(self + func->args.size)); + + if (self) + typeInfo.push_back(LBC_TYPE_TABLE); + + bool haveNonAnyParam = false; + for (AstLocal* arg : func->args) + { + LuauBytecodeEncodedType ty = arg->annotation ? getType(arg->annotation) : LBC_TYPE_ANY; + + if (ty != LBC_TYPE_ANY) + haveNonAnyParam = true; + + typeInfo.push_back(ty); + } + + // If all parameters simplify to any, we can just omit type info for this function + if (!haveNonAnyParam) + return {}; + + return typeInfo; +} + +} // namespace Luau \ No newline at end of file diff --git a/Compiler/src/Types.h b/Compiler/src/Types.h new file mode 100644 index 0000000..1be9155 --- /dev/null +++ b/Compiler/src/Types.h @@ -0,0 +1,9 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Ast.h" + +namespace Luau +{ +std::string getFunctionType(const AstExprFunction* func); +} // namespace Luau diff --git a/Sources.cmake b/Sources.cmake index b1693c3..5b9bd61 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -43,6 +43,7 @@ target_sources(Luau.Compiler PRIVATE Compiler/src/ConstantFolding.cpp Compiler/src/CostModel.cpp Compiler/src/TableShape.cpp + Compiler/src/Types.cpp Compiler/src/ValueTracking.cpp Compiler/src/lcode.cpp Compiler/src/Builtins.h @@ -50,6 +51,7 @@ target_sources(Luau.Compiler PRIVATE Compiler/src/ConstantFolding.h Compiler/src/CostModel.h Compiler/src/TableShape.h + Compiler/src/Types.h Compiler/src/ValueTracking.h ) diff --git a/VM/src/lvm.h b/VM/src/lvm.h index cfb6456..5ec7bc1 100644 --- a/VM/src/lvm.h +++ b/VM/src/lvm.h @@ -24,7 +24,6 @@ LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId v LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val); LUAI_FUNC void luaV_concat(lua_State* L, int total, int last); LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil); -LUAI_FUNC void luaV_getimport_dep(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil); LUAI_FUNC void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit); LUAI_FUNC void luaV_callTM(lua_State* L, int nparams, int res); LUAI_FUNC void luaV_tryfuncTM(lua_State* L, StkId func); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 79bf807..90c5a7e 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -16,8 +16,6 @@ #include -LUAU_FASTFLAG(LuauGetImportDirect) - // Disable c99-designator to avoid the warning in CGOTO dispatch table #ifdef __clang__ #if __has_warning("-Wc99-designator") @@ -432,20 +430,8 @@ reentry: { uint32_t aux = *pc++; - if (FFlag::LuauGetImportDirect) - { - VM_PROTECT(luaV_getimport(L, cl->env, k, ra, aux, /* propagatenil= */ false)); - VM_NEXT(); - } - else - { - VM_PROTECT(luaV_getimport_dep(L, cl->env, k, aux, /* propagatenil= */ false)); - ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack - - setobj2s(L, ra, L->top - 1); - L->top--; - VM_NEXT(); - } + VM_PROTECT(luaV_getimport(L, cl->env, k, ra, aux, /* propagatenil= */ false)); + VM_NEXT(); } } diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index f26cc05..edbe503 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -13,8 +13,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauGetImportDirect, false) - // TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens template struct TempBuffer @@ -77,34 +75,6 @@ void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, luaV_gettable(L, res, &k[id2], res); } -void luaV_getimport_dep(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil) -{ - LUAU_ASSERT(!FFlag::LuauGetImportDirect); - - int count = id >> 30; - int id0 = count > 0 ? int(id >> 20) & 1023 : -1; - int id1 = count > 1 ? int(id >> 10) & 1023 : -1; - int id2 = count > 2 ? int(id) & 1023 : -1; - - // allocate a stack slot so that we can do table lookups - luaD_checkstack(L, 1); - setnilvalue(L->top); - L->top++; - - // global lookup into L->top-1 - TValue g; - sethvalue(L, &g, env); - luaV_gettable(L, &g, &k[id0], L->top - 1); - - // table lookup for id1 - if (id1 >= 0 && (!propagatenil || !ttisnil(L->top - 1))) - luaV_gettable(L, L->top - 1, &k[id1], L->top - 1); - - // table lookup for id2 - if (id2 >= 0 && (!propagatenil || !ttisnil(L->top - 1))) - luaV_gettable(L, L->top - 1, &k[id2], L->top - 1); -} - template static T read(const char* data, size_t size, size_t& offset) { @@ -153,17 +123,12 @@ static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id) // note: we call getimport with nil propagation which means that accesses to table chains like A.B.C will resolve in nil // this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global // injection - if (FFlag::LuauGetImportDirect) - { - // allocate a stack slot so that we can do table lookups - luaD_checkstack(L, 1); - setnilvalue(L->top); - L->top++; + // allocate a stack slot so that we can do table lookups + luaD_checkstack(L, 1); + setnilvalue(L->top); + L->top++; - luaV_getimport(L, L->gt, self->k, L->top - 1, self->id, /* propagatenil= */ true); - } - else - luaV_getimport_dep(L, L->gt, self->k, self->id, /* propagatenil= */ true); + luaV_getimport(L, L->gt, self->k, L->top - 1, self->id, /* propagatenil= */ true); } }; @@ -194,6 +159,8 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size uint8_t version = read(data, size, offset); + + // 0 means the rest of the bytecode is the error message if (version == 0) { @@ -221,6 +188,13 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size TString* source = luaS_new(L, chunkname); + + if (version >= 4) + { + uint8_t typesversion = read(data, size, offset); + LUAU_ASSERT(typesversion == 1); + } + // string table unsigned int stringCount = readVarInt(data, size, offset); TempBuffer strings(L, stringCount); @@ -248,6 +222,25 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size p->nups = read(data, size, offset); p->is_vararg = read(data, size, offset); + if (version >= 4) + { + uint8_t cgflags = read(data, size, offset); + LUAU_ASSERT(cgflags == 0); + + uint32_t typesize = readVarInt(data, size, offset); + + if (typesize) + { + uint8_t* types = (uint8_t*)data + offset; + + LUAU_ASSERT(typesize == unsigned(2 + p->numparams)); + LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION); + LUAU_ASSERT(types[1] == p->numparams); + + offset += typesize; + } + } + p->sizecode = readVarInt(data, size, offset); p->code = luaM_newarray(L, p->sizecode, Instruction, p->memcat); for (int j = 0; j < p->sizecode; ++j) diff --git a/bench/bench.py b/bench/bench.py index 547e0d3..002dfad 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -73,7 +73,7 @@ def arrayRangeOffset(count, offset): return result -def getCallgrindOutput(lines): +def getCallgrindOutput(stdout, lines): result = [] name = None @@ -86,12 +86,36 @@ def getCallgrindOutput(lines): result += "|><|" + name + "|><|" + str(insn / CALLGRIND_INSN_PER_SEC * 1000.0) + "||_||" name = None + # If no results were found above, this may indicate the native executable running + # the benchmark doesn't have support for callgrind builtin. In that case just + # report the "totals" from the output file. + if len(result) == 0: + elements = stdout.decode('utf8').split("|><|") + if len(elements) >= 2: + name = elements[1] + + for l in lines: + if l.startswith("totals: "): + insn = int(l[8:]) + # Note: we only run each bench once under callgrind so we only report a single time per run; callgrind instruction count variance is ~0.01% so it might as well be zero + result += "|><|" + name + "|><|" + str(insn / CALLGRIND_INSN_PER_SEC * 1000.0) + "||_||" + return "".join(result) def conditionallyShowCommand(cmd): if arguments.show_commands: print(f'{colored(Color.BLUE, "EXECUTING")}: {cmd}') +def checkValgrindExecutable(): + """Return true if valgrind can be successfully spawned""" + try: + subprocess.check_call("valgrind --version", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except: + print(f"{colored(Color.YELLOW, 'WARNING')}: Unable to spawn 'valgrind'. Please ensure valgrind is installed when using '--callgrind'.") + return False + + return True + def getVmOutput(cmd): if os.name == "nt": try: @@ -103,17 +127,24 @@ def getVmOutput(cmd): except: return "" elif arguments.callgrind: - try: - fullCmd = "valgrind --tool=callgrind --callgrind-out-file=callgrind.out --combine-dumps=yes --dump-line=no " + cmd - conditionallyShowCommand(fullCmd) - subprocess.check_call(fullCmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=scriptdir) - path = os.path.join(scriptdir, "callgrind.out") - with open(path, "r") as file: - lines = file.readlines() - os.unlink(path) - return getCallgrindOutput(lines) - except: + if not checkValgrindExecutable(): return "" + output_path = os.path.join(scriptdir, "callgrind.out") + try: + os.unlink(output_path) # Remove stale output + except: + pass + fullCmd = "valgrind --tool=callgrind --callgrind-out-file=callgrind.out --combine-dumps=yes --dump-line=no " + cmd + conditionallyShowCommand(fullCmd) + try: + output = subprocess.check_output(fullCmd, shell=True, stderr=subprocess.DEVNULL, cwd=scriptdir) + except subprocess.CalledProcessError as e: + print(f"{colored(Color.YELLOW, 'WARNING')}: Valgrind returned error code {e.returncode}") + output = e.output + with open(output_path, "r") as file: + lines = file.readlines() + os.unlink(output_path) + return getCallgrindOutput(output, lines) else: conditionallyShowCommand(cmd) with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=scriptdir) as p: @@ -352,7 +383,7 @@ def analyzeResult(subdir, main, comparisons): if influxReporter != None: influxReporter.report_result(subdir, main.name, main.filename, "SUCCESS", main.min, main.avg, main.max, main.sampleConfidenceInterval, main.shortVm, main.vm) - print(colored(Color.YELLOW, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(main.avg) + "ms +/- " + + print(colored(Color.GREEN, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(main.avg) + "ms +/- " + '{:6.3f}'.format(main.sampleConfidenceInterval / main.avg * 100) + "% on " + main.shortVm) plotLabels.append(main.name) @@ -449,7 +480,7 @@ def analyzeResult(subdir, main, comparisons): 'P(T<=t)': '---' if pValue < 0 else '{:.0f}%'.format(pValue * 100) }) - print(colored(Color.YELLOW, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(compare.avg) + "ms +/- " + + print(colored(Color.GREEN, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(compare.avg) + "ms +/- " + '{:6.3f}'.format(compare.sampleConfidenceInterval / compare.avg * 100) + "% on " + compare.shortVm + ' ({:+7.3f}%, '.format(speedup * 100) + verdict + ")") @@ -727,6 +758,10 @@ def run(args, argsubcb): arguments = args argumentSubstituionCallback = argsubcb + if os.name == "nt" and arguments.callgrind: + print(f"{colored(Color.RED, 'ERROR')}: --callgrind is not supported on Windows. Please consider using this option on another OS, or Linux using WSL.") + sys.exit(1) + if arguments.report_metrics or arguments.print_influx_debugging: import influxbench influxReporter = influxbench.InfluxReporter(arguments) diff --git a/bench/bench_support.lua b/bench/bench_support.lua index a9608ec..9e415fc 100644 --- a/bench/bench_support.lua +++ b/bench/bench_support.lua @@ -57,4 +57,46 @@ function bench.runCode(f, description) print(report) end +-- This function acts a bit like a Unix "fork" operation +-- When it is first called it clones `scriptInstance` and starts executing +-- the cloned script parented to an Actor. When the cloned script calls "runScriptCodeUnderActor" +-- it will run 'f' and print out the provided 'description'. +-- +-- The function returns 'true' if it was invoked from a script running under an Actor +-- and 'false' otherwise. +-- +-- Example usage: +-- local bench = script and require(script.Parent.bench_support) or require("bench_support") +-- function testFunc() +-- ... +-- end +-- bench.runScriptCodeUnderActor(script, testFunc, "test function") +function bench.runScriptCodeUnderActor(scriptInstance, f, description) + if scriptInstance:GetActor() then + -- If this function was called from an Actor script, just run the function provided using runCode + bench.runCode(f, description) + return true + else + -- If this function was not called from an Actor script, clone the script and place it under + -- Actor instance. + + -- Create an Actor to run the script under + local actor = Instance.new("Actor") + -- Clone this script (i.e. the bench_support module) and place it under the Actor where + -- the script script would expect it to be when using 'require'. + local benchModule = script:Clone() + benchModule.Parent = actor + -- Clone the scriptInstance + local actorScript = scriptInstance:Clone() + -- Enable the script since `scriptInstance` may be started by roblox-cli without ever being enabled. + actorScript.Disabled = false + actorScript.Parent = actor + -- Add the actor to the workspace which will start executing the cloned script. + -- Note: the script needs to be placed under a instance that implements 'IScriptFilter' + -- (which workspace does) or it will never start executing. + actor.Parent = workspace + return false + end +end + return bench diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 63e92f8..177e249 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -542,6 +542,20 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions") SINGLE_COMPARE(bsf(eax, edx), 0x0f, 0xbc, 0xc2); } +TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelLea") +{ + CHECK(check( + [](AssemblyBuilderX64& build) { + Label fn; + build.lea(rax, fn); + build.ret(); + + build.setLabel(fn); + build.ret(); + }, + {0x48, 0x8d, 0x05, 0x01, 0x00, 0x00, 0x00, 0xc3, 0xc3})); +} + TEST_CASE("LogTest") { AssemblyBuilderX64 build(/* logText= */ true); @@ -561,6 +575,7 @@ TEST_CASE("LogTest") Label start = build.setLabel(); build.cmp(rsi, rdi); build.jcc(ConditionX64::Equal, start); + build.lea(rcx, start); build.jmp(qword[rdx]); build.vaddps(ymm9, ymm12, ymmword[rbp + 0xc]); @@ -605,6 +620,7 @@ TEST_CASE("LogTest") .L1: cmp rsi,rdi je .L1 + lea rcx,.L1 jmp qword ptr [rdx] vaddps ymm9,ymm12,ymmword ptr [rbp+0Ch] vaddpd ymm2,ymm7,qword ptr [.start-8] diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 4885b31..97cc326 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -49,6 +49,15 @@ static std::string compileFunction0Coverage(const char* source, int level) return bcb.dumpFunction(0); } +static std::string compileFunction0TypeTable(const char* source) +{ + Luau::BytecodeBuilder bcb; + bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code); + Luau::compileOrThrow(bcb, source); + + return bcb.dumpTypeInfo(); +} + TEST_SUITE_BEGIN("Compiler"); TEST_CASE("CompileToBytecode") @@ -5796,8 +5805,6 @@ RETURN R3 1 TEST_CASE("InlineRecurseArguments") { - ScopedFastFlag sff("LuauCompileInlineDefer", true); - // the example looks silly but we preserve it verbatim as it was found by fuzzer for a previous version of the compiler CHECK_EQ("\n" + compileFunction(R"( local function foo(a, b) @@ -7071,4 +7078,56 @@ L1: RETURN R3 1 )"); } +TEST_CASE("EncodedTypeTable") +{ + ScopedFastFlag sffs[] = { + {"BytecodeVersion4", true}, + {"CompileFunctionType", true}, + }; + + CHECK_EQ("\n" + compileFunction0TypeTable(R"( +function myfunc(test: string, num: number) + print(test) +end + +function myfunc2(test: number?) +end + +function myfunc3(test: string, n: number) +end + +function myfunc4(test: string | number, n: number) +end + +-- Promoted to function(any, any) since general unions are not supported. +-- Functions with all `any` parameters will have omitted type info. +function myfunc5(test: string | number, n: number | boolean) +end + +myfunc('test') +)"), + R"( +0: function(string, number) +1: function(number?) +2: function(string, number) +3: function(any, number) +)"); + + CHECK_EQ("\n" + compileFunction0TypeTable(R"( +local Str = { + a = 1 +} + +-- Implicit `self` parameter is automatically assumed to be table type. +function Str:test(n: number) + print(self.a, n) +end + +Str:test(234) +)"), + R"( +0: function({ }, number) +)"); +} + TEST_SUITE_END(); diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index 2052019..1223152 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -341,7 +341,7 @@ TEST_CASE_FIXTURE(SimplifyFixture, "tables") CHECK(t2 == intersect(t2, t1)); TypeId t3 = mkTable({}); - + // {tag : string} intersect {{}} CHECK(t1 == intersect(t1, t3)); CHECK(t1 == intersect(t3, t1)); }