From 29047504daa8f9bba574d86b8be8b6145ad86fd8 Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:55:39 -0800 Subject: [PATCH 1/3] Sync to upstream/release/657 (#1619) ## General - Fix a parsing bug related to the starting position of function names. - Rename Luau's `Table` struct to `LuaTable`. ## New Solver - Add support for generics in user-defined type functions ([RFC](https://rfcs.luau.org/support-for-generic-function-types-in-user-defined-type-functions.html)). - Provide a definition of `math.lerp` to the typechecker. - Implement error suppression in `string.format`. - Fixes #1587. - Ensure function call discriminant types are always filled when resolving `FunctionCallConstraint`. --- Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Talha Pathan Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/ConstraintSolver.h | 5 + Analysis/include/Luau/TypeFunctionRuntime.h | 23 +- Analysis/src/AstQuery.cpp | 36 +- Analysis/src/BuiltinDefinitions.cpp | 24 +- Analysis/src/ConstraintSolver.cpp | 100 ++- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 243 ++++++- Analysis/src/TypeFunctionRuntime.cpp | 673 ++++++++++++++++---- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 222 ++++++- Ast/include/Luau/Parser.h | 2 +- Ast/src/Parser.cpp | 11 +- CodeGen/include/Luau/CodeAllocator.h | 2 +- CodeGen/include/Luau/CodeGen.h | 268 +------- CodeGen/include/Luau/CodeGenCommon.h | 6 + CodeGen/include/Luau/CodeGenOptions.h | 188 ++++++ CodeGen/include/Luau/IrData.h | 26 +- CodeGen/include/Luau/IrDump.h | 2 +- CodeGen/include/Luau/LoweringStats.h | 103 +++ CodeGen/src/BytecodeAnalysis.cpp | 2 +- CodeGen/src/CodeBlockUnwind.cpp | 1 + CodeGen/src/CodeGen.cpp | 3 +- CodeGen/src/CodeGenAssembly.cpp | 1 - CodeGen/src/CodeGenContext.cpp | 1 + CodeGen/src/CodeGenLower.h | 5 + CodeGen/src/CodeGenUtils.cpp | 22 +- CodeGen/src/CodeGenUtils.h | 4 +- CodeGen/src/EmitCommonX64.cpp | 6 +- CodeGen/src/EmitInstructionX64.cpp | 8 +- CodeGen/src/IrLoweringA64.cpp | 25 +- CodeGen/src/IrLoweringX64.cpp | 23 +- CodeGen/src/IrRegAllocA64.cpp | 2 +- CodeGen/src/IrRegAllocX64.cpp | 2 +- CodeGen/src/IrTranslation.cpp | 2 +- CodeGen/src/IrUtils.cpp | 4 + CodeGen/src/NativeState.h | 22 +- Sources.cmake | 2 + VM/src/lapi.cpp | 24 +- VM/src/lbuflib.cpp | 4 +- VM/src/lbuiltins.cpp | 14 +- VM/src/lfunc.cpp | 4 +- VM/src/lfunc.h | 4 +- VM/src/lgc.cpp | 18 +- VM/src/lgc.h | 2 +- VM/src/lgcdebug.cpp | 12 +- VM/src/lmem.cpp | 2 +- VM/src/lobject.h | 10 +- VM/src/lstate.h | 8 +- VM/src/ltable.cpp | 70 +- VM/src/ltable.h | 30 +- VM/src/ltablib.cpp | 36 +- VM/src/ltm.cpp | 6 +- VM/src/ltm.h | 2 +- VM/src/lvm.h | 2 +- VM/src/lvmexecute.cpp | 30 +- VM/src/lvmload.cpp | 8 +- VM/src/lvmutils.cpp | 8 +- tests/AnyTypeSummary.test.cpp | 10 +- tests/AstQuery.test.cpp | 4 - tests/CodeAllocator.test.cpp | 1 + tests/Conformance.test.cpp | 8 +- tests/Parser.test.cpp | 23 + tests/TypeFunction.user.test.cpp | 477 ++++++++++++++ tests/TypeInfer.builtins.test.cpp | 16 + tests/main.cpp | 2 + tools/natvis/VM.natvis | 2 +- 64 files changed, 2220 insertions(+), 686 deletions(-) create mode 100644 CodeGen/include/Luau/CodeGenOptions.h create mode 100644 CodeGen/include/Luau/LoweringStats.h diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index ceb9cab4..bb358abb 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -420,6 +420,11 @@ public: void throwUserCancelError() const; ToStringOptions opts; + + void fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes + ); }; void dump(NotNull rootScope, struct ToStringOptions& opts); diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 356d34a5..d715ccd3 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -119,7 +119,14 @@ struct TypeFunctionVariadicTypePack TypeFunctionTypeId type; }; -using TypeFunctionTypePackVariant = Variant; +struct TypeFunctionGenericTypePack +{ + bool isNamed = false; + + std::string name; +}; + +using TypeFunctionTypePackVariant = Variant; struct TypeFunctionTypePackVar { @@ -135,6 +142,9 @@ struct TypeFunctionTypePackVar struct TypeFunctionFunctionType { + std::vector generics; + std::vector genericPacks; + TypeFunctionTypePackId argTypes; TypeFunctionTypePackId retTypes; }; @@ -210,6 +220,14 @@ struct TypeFunctionClassType std::string name; }; +struct TypeFunctionGenericType +{ + bool isNamed = false; + bool isPack = false; + + std::string name; +}; + using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionPrimitiveType, TypeFunctionAnyType, @@ -221,7 +239,8 @@ using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionNegationType, TypeFunctionFunctionType, TypeFunctionTableType, - TypeFunctionClassType>; + TypeFunctionClassType, + TypeFunctionGenericType>; struct TypeFunctionType { diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 93dabeae..96c4ea10 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -13,8 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauDocumentationAtPosition) - namespace Luau { @@ -518,7 +516,6 @@ static std::optional getMetatableDocumentation( const AstName& index ) { - LUAU_ASSERT(FFlag::LuauDocumentationAtPosition); auto indexIt = mtable->props.find("__index"); if (indexIt == mtable->props.end()) return std::nullopt; @@ -575,26 +572,7 @@ std::optional getDocumentationSymbolAtPosition(const Source } else if (const ClassType* ctv = get(parentTy)) { - if (FFlag::LuauDocumentationAtPosition) - { - while (ctv) - { - if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) - { - if (FFlag::LuauSolverV2) - { - if (auto ty = propIt->second.readTy) - return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol); - } - else - return checkOverloadedDocumentationSymbol( - module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol - ); - } - ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; - } - } - else + while (ctv) { if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) { @@ -608,17 +586,15 @@ std::optional getDocumentationSymbolAtPosition(const Source module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol ); } + ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; } } - else if (FFlag::LuauDocumentationAtPosition) + else if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) { - if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) + if (auto mtable = get(*ptv->metatable)) { - if (auto mtable = get(*ptv->metatable)) - { - if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) - return docSymbol; - } + if (std::optional docSymbol = getMetatableDocumentation(module, parentExpr, mtable, indexName->index)) + return docSymbol; } } } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 2db6d567..fba3c964 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTypestateBuiltins2) LUAU_FASTFLAGVARIABLE(LuauStringFormatArityFix) +LUAU_FASTFLAGVARIABLE(LuauStringFormatErrorSuppression) LUAU_FASTFLAG(AutocompleteRequirePathSuggestions2) LUAU_FASTFLAG(LuauVectorDefinitionsExtra) @@ -631,10 +632,29 @@ static void dcrMagicFunctionTypeCheckFormat(MagicFunctionTypeCheckContext contex Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; // use subtyping instead here SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); + if (!result.isSubtype) { - Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); - context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + if (FFlag::LuauStringFormatErrorSuppression) + { + switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) + { + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + break; + case ErrorSuppression::DoNotSuppress: + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + + if (!reasonings.suppressed) + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } + } + else + { + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); + } } } } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 1be02a71..e6e54916 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauNewSolverPopulateTableLocations) LUAU_FASTFLAGVARIABLE(LuauAllowNilAssignmentToIndexer) LUAU_FASTFLAG(LuauUserTypeFunNoExtraConstraint) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAGVARIABLE(LuauAlwaysFillInFunctionCallDiscriminantTypes) namespace Luau { @@ -1153,6 +1154,42 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul return true; } +void ConstraintSolver::fillInDiscriminantTypes( + NotNull constraint, + const std::vector>& discriminantTypes +) +{ + for (std::optional ty : discriminantTypes) + { + if (!ty) + continue; + + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } + + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } + } +} + bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull constraint) { TypeId fn = follow(c.fn); @@ -1168,6 +1205,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(asMutable(c.result), builtinTypes->anyTypePack); unblock(c.result, constraint->location); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1175,12 +1214,16 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(fn)) { bind(constraint, c.result, builtinTypes->errorRecoveryTypePack()); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } if (get(fn)) { bind(constraint, c.result, builtinTypes->neverTypePack); + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) + fillInDiscriminantTypes(constraint, c.discriminantTypes); return true; } @@ -1261,36 +1304,45 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(constraint, c.result, constraint->scope); } - for (std::optional ty : c.discriminantTypes) + if (FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes) { - if (!ty) - continue; + fillInDiscriminantTypes(constraint, c.discriminantTypes); + } + else + { + // NOTE: This is the body of the `fillInDiscriminantTypes` helper. + for (std::optional ty : c.discriminantTypes) + { + if (!ty) + continue; - // If the discriminant type has been transmuted, we need to unblock them. - if (!isBlocked(*ty)) - { - unblock(*ty, constraint->location); - continue; - } + // If the discriminant type has been transmuted, we need to unblock them. + if (!isBlocked(*ty)) + { + unblock(*ty, constraint->location); + continue; + } - if (FFlag::LuauRemoveNotAnyHack) - { - // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. - emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); - } - else - { - // We use `any` here because the discriminant type may be pointed at by both branches, - // where the discriminant type is not negated, and the other where it is negated, i.e. - // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` - // v.s. - // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` - // - // In practice, users cannot negate `any`, so this is an implementation detail we can always change. - emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + if (FFlag::LuauRemoveNotAnyHack) + { + // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. + emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); + } + else + { + // We use `any` here because the discriminant type may be pointed at by both branches, + // where the discriminant type is not negated, and the other where it is negated, i.e. + // `unknown ~ unknown` and `~unknown ~ never`, so `T & unknown ~ T` and `T & ~unknown ~ never` + // v.s. + // `any ~ any` and `~any ~ any`, so `T & any ~ T` and `T & ~any ~ T` + // + // In practice, users cannot negate `any`, so this is an implementation detail we can always change. + emplaceType(asMutable(follow(*ty)), builtinTypes->anyType); + } } } + OverloadResolver resolver{ builtinTypes, NotNull{arena}, diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index e794588f..937c414c 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,14 +2,14 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) -LUAU_FASTFLAG(LuauBufferBitMethods) LUAU_FASTFLAG(LuauVector2Constructor) +LUAU_FASTFLAG(LuauBufferBitMethods2) +LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition) namespace Luau { -// TODO: there has to be a better way, like splitting up per library -static const std::string kBuiltinDefinitionLuaSrcChecked = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionLuaSrcChecked_DEPRECATED = R"BUILTIN_SRC( declare bit32: { band: @checked (...number) -> number, @@ -202,6 +202,228 @@ declare function unpack(tab: {V}, i: number?, j: number?): ...V )BUILTIN_SRC"; +static const std::string kBuiltinDefinitionBaseSrc = R"BUILTIN_SRC( + +@checked declare function require(target: any): any + +@checked declare function getfenv(target: any): { [string]: any } + +declare _G: any +declare _VERSION: string + +declare function gcinfo(): number + +declare function print(...: T...) + +declare function type(value: T): string +declare function typeof(value: T): string + +-- `assert` has a magic function attached that will give more detailed type information +declare function assert(value: T, errorMessage: string?): T +declare function error(message: T, level: number?): never + +declare function tostring(value: T): string +declare function tonumber(value: T, radix: number?): number? + +declare function rawequal(a: T1, b: T2): boolean +declare function rawget(tab: {[K]: V}, k: K): V +declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} +declare function rawlen(obj: {[K]: V} | string): number + +declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? + +declare function ipairs(tab: {V}): (({V}, number) -> (number?, V), {V}, number) + +declare function pcall(f: (A...) -> R..., ...: A...): (boolean, R...) + +-- FIXME: The actual type of `xpcall` is: +-- (f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...) +-- Since we can't represent the return value, we use (boolean, R1...). +declare function xpcall(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...) + +-- `select` has a magic function attached to provide more detailed type information +declare function select(i: string | number, ...: A...): ...any + +-- FIXME: This type is not entirely correct - `loadstring` returns a function or +-- (nil, string). +declare function loadstring(src: string, chunkname: string?): (((A...) -> any)?, string?) + +@checked declare function newproxy(mt: boolean?): any + +-- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. +declare function unpack(tab: {V}, i: number?, j: number?): ...V + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( + +declare bit32: { + band: @checked (...number) -> number, + bor: @checked (...number) -> number, + bxor: @checked (...number) -> number, + btest: @checked (number, ...number) -> boolean, + rrotate: @checked (x: number, disp: number) -> number, + lrotate: @checked (x: number, disp: number) -> number, + lshift: @checked (x: number, disp: number) -> number, + arshift: @checked (x: number, disp: number) -> number, + rshift: @checked (x: number, disp: number) -> number, + bnot: @checked (x: number) -> number, + extract: @checked (n: number, field: number, width: number?) -> number, + replace: @checked (n: number, v: number, field: number, width: number?) -> number, + countlz: @checked (n: number) -> number, + countrz: @checked (n: number) -> number, + byteswap: @checked (n: number) -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionMathSrc = R"BUILTIN_SRC( + +declare math: { + frexp: @checked (n: number) -> (number, number), + ldexp: @checked (s: number, e: number) -> number, + fmod: @checked (x: number, y: number) -> number, + modf: @checked (n: number) -> (number, number), + pow: @checked (x: number, y: number) -> number, + exp: @checked (n: number) -> number, + + ceil: @checked (n: number) -> number, + floor: @checked (n: number) -> number, + abs: @checked (n: number) -> number, + sqrt: @checked (n: number) -> number, + + log: @checked (n: number, base: number?) -> number, + log10: @checked (n: number) -> number, + + rad: @checked (n: number) -> number, + deg: @checked (n: number) -> number, + + sin: @checked (n: number) -> number, + cos: @checked (n: number) -> number, + tan: @checked (n: number) -> number, + sinh: @checked (n: number) -> number, + cosh: @checked (n: number) -> number, + tanh: @checked (n: number) -> number, + atan: @checked (n: number) -> number, + acos: @checked (n: number) -> number, + asin: @checked (n: number) -> number, + atan2: @checked (y: number, x: number) -> number, + + min: @checked (number, ...number) -> number, + max: @checked (number, ...number) -> number, + + pi: number, + huge: number, + + randomseed: @checked (seed: number) -> (), + random: @checked (number?, number?) -> number, + + sign: @checked (n: number) -> number, + clamp: @checked (n: number, min: number, max: number) -> number, + noise: @checked (x: number, y: number?, z: number?) -> number, + round: @checked (n: number) -> number, + map: @checked (x: number, inmin: number, inmax: number, outmin: number, outmax: number) -> number, + lerp: @checked (a: number, b: number, t: number) -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionOsSrc = R"BUILTIN_SRC( + +type DateTypeArg = { + year: number, + month: number, + day: number, + hour: number?, + min: number?, + sec: number?, + isdst: boolean?, +} + +type DateTypeResult = { + year: number, + month: number, + wday: number, + yday: number, + day: number, + hour: number, + min: number, + sec: number, + isdst: boolean, +} + +declare os: { + time: (time: DateTypeArg?) -> number, + date: ((formatString: "*t" | "!*t", time: number?) -> DateTypeResult) & ((formatString: string?, time: number?) -> string), + difftime: (t2: DateTypeResult | number, t1: DateTypeResult | number) -> number, + clock: () -> number, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionCoroutineSrc = R"BUILTIN_SRC( + +declare coroutine: { + create: (f: (A...) -> R...) -> thread, + resume: (co: thread, A...) -> (boolean, R...), + running: () -> thread, + status: @checked (co: thread) -> "dead" | "running" | "normal" | "suspended", + wrap: (f: (A...) -> R...) -> ((A...) -> R...), + yield: (A...) -> R..., + isyieldable: () -> boolean, + close: @checked (co: thread) -> (boolean, any) +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionTableSrc = R"BUILTIN_SRC( + +declare table: { + concat: (t: {V}, sep: string?, i: number?, j: number?) -> string, + insert: ((t: {V}, value: V) -> ()) & ((t: {V}, pos: number, value: V) -> ()), + maxn: (t: {V}) -> number, + remove: (t: {V}, number?) -> V?, + sort: (t: {V}, comp: ((V, V) -> boolean)?) -> (), + create: (count: number, value: V?) -> {V}, + find: (haystack: {V}, needle: V, init: number?) -> number?, + + unpack: (list: {V}, i: number?, j: number?) -> ...V, + pack: (...V) -> { n: number, [number]: V }, + + getn: (t: {V}) -> number, + foreach: (t: {[K]: V}, f: (K, V) -> ()) -> (), + foreachi: ({V}, (number, V) -> ()) -> (), + + move: (src: {V}, a: number, b: number, t: number, dst: {V}?) -> {V}, + clear: (table: {[K]: V}) -> (), + + isfrozen: (t: {[K]: V}) -> boolean, +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionDebugSrc = R"BUILTIN_SRC( + +declare debug: { + info: ((thread: thread, level: number, options: string) -> R...) & ((level: number, options: string) -> R...) & ((func: (A...) -> R1..., options: string) -> R2...), + traceback: ((message: string?, level: number?) -> string) & ((thread: thread, message: string?, level: number?) -> string), +} + +)BUILTIN_SRC"; + +static const std::string kBuiltinDefinitionUtf8Src = R"BUILTIN_SRC( + +declare utf8: { + char: @checked (...number) -> string, + charpattern: string, + codes: @checked (str: string) -> ((string, number) -> (number, number), string, number), + codepoint: @checked (str: string, i: number?, j: number?) -> ...number, + len: @checked (s: string, i: number?, j: number?) -> (number?, number?), + offset: @checked (s: string, n: number?, i: number?) -> number, +} + +)BUILTIN_SRC"; + static const std::string kBuiltinDefinitionBufferSrc_DEPRECATED = R"BUILTIN_SRC( --- Buffer API declare buffer: { @@ -380,9 +602,20 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = kBuiltinDefinitionLuaSrcChecked; + std::string result = FFlag::LuauMathMapDefinition ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionLuaSrcChecked_DEPRECATED; - result += FFlag::LuauBufferBitMethods ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; + if (FFlag::LuauMathMapDefinition) + { + result += kBuiltinDefinitionBit32Src; + result += kBuiltinDefinitionMathSrc; + result += kBuiltinDefinitionOsSrc; + result += kBuiltinDefinitionCoroutineSrc; + result += kBuiltinDefinitionTableSrc; + result += kBuiltinDefinitionDebugSrc; + result += kBuiltinDefinitionUtf8Src; + } + + result += FFlag::LuauBufferBitMethods2 ? kBuiltinDefinitionBufferSrc : kBuiltinDefinitionBufferSrc_DEPRECATED; if (FFlag::LuauVectorDefinitionsExtra) { diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index bb0309fc..3766224b 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -18,6 +18,8 @@ LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixInner) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunPrintToError) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunFixNoReadWrite) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunGenerics) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunCloneTail) namespace Luau { @@ -161,6 +163,8 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty) return "function"; else if (get(ty)) return "class"; + else if (FFlag::LuauUserTypeFunGenerics && get(ty)) + return "generic"; LUAU_UNREACHABLE(); luaL_error(L, "VM encountered unexpected type variant when determining tag"); @@ -267,6 +271,20 @@ static int createSingleton(lua_State* L) luaL_error(L, "types.singleton: can't create singleton from `%s` type", lua_typename(L, 1)); } +// Luau: `types.generic(name: string, ispack: boolean?) -> type +// Create a generic type with the specified type. If an optinal boolean is set to true, result is a generic pack +static int createGeneric(lua_State* L) +{ + const char* name = luaL_checkstring(L, 1); + bool isPack = luaL_optboolean(L, 2, false); + + if (strlen(name) == 0) + luaL_error(L, "types.generic: generic name cannot be empty"); + + allocTypeUserData(L, TypeFunctionGenericType{/* isNamed */ true, isPack, name}); + return 1; +} + // Luau: `self:value() -> type` // Returns the value of a singleton static int getSingletonValue(lua_State* L) @@ -780,9 +798,159 @@ static int setTableMetatable(lua_State* L) return 0; } -// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}) -> type` -// Returns the type instance representing a function -static int createFunction(lua_State* L) +static std::tuple, std::vector> getGenerics(lua_State* L, int idx, const char* fname) +{ + std::vector types; + std::vector packs; + + if (lua_istable(L, idx)) + { + lua_pushvalue(L, idx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + TypeFunctionTypeId ty = getTypeUserData(L, -1); + + if (auto gty = get(ty)) + { + if (gty->isPack) + { + packs.push_back(allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name})); + } + else + { + if (!packs.empty()) + luaL_error(L, "%s: generic type cannot follow a generic pack", fname); + + types.push_back(ty); + } + } + else + { + luaL_error(L, "%s: table member was not a generic type", fname); + } + + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + else if (!lua_isnoneornil(L, idx)) + { + luaL_typeerrorL(L, idx, "table"); + } + + return {types, packs}; +} + +static TypeFunctionTypePackId getTypePack(lua_State* L, int headIdx, int tailIdx) +{ + TypeFunctionTypePackId result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + + std::vector head; + + if (lua_istable(L, headIdx)) + { + lua_pushvalue(L, headIdx); + + for (int i = 1; i <= lua_objlen(L, -1); i++) + { + lua_pushinteger(L, i); + lua_gettable(L, -2); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } + + head.push_back(getTypeUserData(L, -1)); + lua_pop(L, 1); + } + + lua_pop(L, 1); + } + + std::optional tail; + + if (auto type = optionalTypeUserData(L, tailIdx)) + { + if (auto gty = get(*type); gty && gty->isPack) + tail = allocateTypeFunctionTypePack(L, TypeFunctionGenericTypePack{gty->isNamed, gty->name}); + else + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + } + + if (head.size() == 0 && tail.has_value()) + result = *tail; + else + result = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + + return result; +} + +static void pushTypePack(lua_State* L, TypeFunctionTypePackId tp) +{ + if (auto tftp = get(tp)) + { + lua_createtable(L, 0, 2); + + if (!tftp->head.empty()) + { + lua_createtable(L, int(tftp->head.size()), 0); + int pos = 1; + + for (auto el : tftp->head) + { + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + lua_setfield(L, -2, "head"); + } + + if (tftp->tail.has_value()) + { + if (auto tfvp = get(*tftp->tail)) + allocTypeUserData(L, tfvp->type->type); + else if (auto tfgp = get(*tftp->tail)) + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + else + luaL_error(L, "unsupported type pack type"); + + lua_setfield(L, -2, "tail"); + } + } + else if (auto tfvp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + else if (auto tfgp = get(tp)) + { + lua_createtable(L, 0, 1); + + allocTypeUserData(L, TypeFunctionGenericType{tfgp->isNamed, true, tfgp->name}); + lua_setfield(L, -2, "tail"); + } + else + { + luaL_error(L, "unsupported type pack type"); + } +} + +static int createFunction_DEPRECATED(lua_State* L) { int argumentCount = lua_gettop(L); if (argumentCount > 2) @@ -870,7 +1038,62 @@ static int createFunction(lua_State* L) else if (!lua_isnoneornil(L, 2)) luaL_typeerrorL(L, 2, "table"); - allocTypeUserData(L, TypeFunctionFunctionType{argTypes, retTypes}); + allocTypeUserData(L, TypeFunctionFunctionType{{}, {}, argTypes, retTypes}); + + return 1; +} + +// Luau: `types.newfunction(parameters: {head: {type}?, tail: type?}, returns: {head: {type}?, tail: type?}, generics: {type}?) -> type` +// Returns the type instance representing a function +static int createFunction(lua_State* L) +{ + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "types.newfunction: expected 0-3 arguments, but got %d", argumentCount); + + TypeFunctionTypePackId argTypes = nullptr; + + if (lua_istable(L, 1)) + { + lua_getfield(L, 1, "head"); + lua_getfield(L, 1, "tail"); + + argTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 1)) + { + luaL_typeerrorL(L, 1, "table"); + } + else + { + argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + TypeFunctionTypePackId retTypes = nullptr; + + if (lua_istable(L, 2)) + { + lua_getfield(L, 2, "head"); + lua_getfield(L, 2, "tail"); + + retTypes = getTypePack(L, -2, -1); + + lua_pop(L, 2); + } + else if (!lua_isnoneornil(L, 2)) + { + luaL_typeerrorL(L, 2, "table"); + } + else + { + retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{}); + } + + auto [genericTypes, genericPacks] = getGenerics(L, 3, "types.newfunction"); + + allocTypeUserData(L, TypeFunctionFunctionType{std::move(genericTypes), std::move(genericPacks), argTypes, retTypes}); return 1; } @@ -888,38 +1111,45 @@ static int setFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.setparameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) - { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - break; - } - - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); - - lua_pop(L, 1); // Remove `ty` from stack - } + tfft->argTypes = getTypePack(L, 2, 3); } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else + { + std::vector head{}; + if (lua_istable(L, 2)) + { + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) + { + lua_pushinteger(L, i); + lua_gettable(L, 2); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } - if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack - tfft->argTypes = *tail; - else // Make argTypes a type pack - tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); + + lua_pop(L, 1); // Remove `ty` from stack + } + } + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); + + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + + if (head.size() == 0 && tail.has_value()) // Make argTypes a variadic type pack + tfft->argTypes = *tail; + else // Make argTypes a type pack + tfft->argTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -937,52 +1167,60 @@ static int getFunctionParameters(lua_State* L) if (!tfft) luaL_error(L, "type.parameters: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->argTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; - - lua_createtable(L, 0, size); - - int argSize = (int)tftp->head.size(); - if (argSize > 0) + pushTypePack(L, tfft->argTypes); + } + else + { + if (auto tftp = get(tfft->argTypes)) { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; + + lua_createtable(L, 0, size); + + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->argTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); + + return 1; } - return 1; + lua_createtable(L, 0, 0); } - if (auto tfvp = get(tfft->argTypes)) - { - lua_createtable(L, 0, 1); - - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); - - return 1; - } - - lua_createtable(L, 0, 0); return 1; } @@ -999,38 +1237,45 @@ static int setFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.setreturns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - std::vector head{}; - if (lua_istable(L, 2)) + if (FFlag::LuauUserTypeFunGenerics) { - int argSize = lua_objlen(L, 2); - for (int i = 1; i <= argSize; i++) - { - lua_pushinteger(L, i); - lua_gettable(L, 2); - - if (lua_isnil(L, -1)) - { - lua_pop(L, 1); - break; - } - - TypeFunctionTypeId ty = getTypeUserData(L, -1); - head.push_back(ty); - - lua_pop(L, 1); // Remove `ty` from stack - } + tfft->retTypes = getTypePack(L, 2, 3); } - else if (!lua_isnoneornil(L, 2)) - luaL_typeerrorL(L, 2, "table"); + else + { + std::vector head{}; + if (lua_istable(L, 2)) + { + int argSize = lua_objlen(L, 2); + for (int i = 1; i <= argSize; i++) + { + lua_pushinteger(L, i); + lua_gettable(L, 2); - std::optional tail; - if (auto type = optionalTypeUserData(L, 3)) - tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + break; + } - if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack - tfft->retTypes = *tail; - else // Make retTypes a type pack - tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + TypeFunctionTypeId ty = getTypeUserData(L, -1); + head.push_back(ty); + + lua_pop(L, 1); // Remove `ty` from stack + } + } + else if (!lua_isnoneornil(L, 2)) + luaL_typeerrorL(L, 2, "table"); + + std::optional tail; + if (auto type = optionalTypeUserData(L, 3)) + tail = allocateTypeFunctionTypePack(L, TypeFunctionVariadicTypePack{*type}); + + if (head.size() == 0 && tail.has_value()) // Make retTypes a variadic type pack + tfft->retTypes = *tail; + else // Make retTypes a type pack + tfft->retTypes = allocateTypeFunctionTypePack(L, TypeFunctionTypePack{head, tail}); + } return 0; } @@ -1048,52 +1293,109 @@ static int getFunctionReturns(lua_State* L) if (!tfft) luaL_error(L, "type.returns: expected self to be a function, but got %s instead", getTag(L, self).c_str()); - if (auto tftp = get(tfft->retTypes)) + if (FFlag::LuauUserTypeFunGenerics) { - int size = 0; - if (tftp->head.size() > 0) - size++; - if (tftp->tail.has_value()) - size++; - - lua_createtable(L, 0, size); - - int argSize = (int)tftp->head.size(); - if (argSize > 0) + pushTypePack(L, tfft->retTypes); + } + else + { + if (auto tftp = get(tfft->retTypes)) { - lua_createtable(L, argSize, 0); - for (int i = 0; i < argSize; i++) + int size = 0; + if (tftp->head.size() > 0) + size++; + if (tftp->tail.has_value()) + size++; + + lua_createtable(L, 0, size); + + int argSize = (int)tftp->head.size(); + if (argSize > 0) { - allocTypeUserData(L, tftp->head[i]->type); - lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + lua_createtable(L, argSize, 0); + for (int i = 0; i < argSize; i++) + { + allocTypeUserData(L, tftp->head[i]->type); + lua_rawseti(L, -2, i + 1); // Luau is 1-indexed while C++ is 0-indexed + } + lua_setfield(L, -2, "head"); } - lua_setfield(L, -2, "head"); + + if (tftp->tail.has_value()) + { + auto tfvp = get(*tftp->tail); + if (!tfvp) + LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + + allocTypeUserData(L, tfvp->type->type); + lua_setfield(L, -2, "tail"); + } + + return 1; } - if (tftp->tail.has_value()) + if (auto tfvp = get(tfft->retTypes)) { - auto tfvp = get(*tftp->tail); - if (!tfvp) - LUAU_ASSERT(!"We should only be supporting variadic packs as TypeFunctionTypePack.tail at the moment"); + lua_createtable(L, 0, 1); allocTypeUserData(L, tfvp->type->type); lua_setfield(L, -2, "tail"); + + return 1; } - return 1; + lua_createtable(L, 0, 0); } - if (auto tfvp = get(tfft->retTypes)) + return 1; +} + +// Luau: `self:setgenerics(generics: {type}?)` +static int setFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = getMutable(self); + if (!tfft) + luaL_error(L, "type.setgenerics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + + int argumentCount = lua_gettop(L); + if (argumentCount > 3) + luaL_error(L, "type.setgenerics: expected 3 arguments, but got %d", argumentCount); + + auto [genericTypes, genericPacks] = getGenerics(L, 2, "types.setgenerics"); + + tfft->generics = std::move(genericTypes); + tfft->genericPacks = std::move(genericPacks); + + return 0; +} + +// Luau: `self:generics() -> {type}` +static int getFunctionGenerics(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfft = get(self); + if (!tfft) + luaL_error(L, "type.generics: expected self to be a function, but got %s instead", getTag(L, self).c_str()); + + lua_createtable(L, int(tfft->generics.size()) + int(tfft->genericPacks.size()), 0); + + int pos = 1; + + for (const auto& el : tfft->generics) { - lua_createtable(L, 0, 1); - - allocTypeUserData(L, tfvp->type->type); - lua_setfield(L, -2, "tail"); - - return 1; + allocTypeUserData(L, el->type); + lua_rawseti(L, -2, pos++); + } + + for (const auto& el : tfft->genericPacks) + { + auto gty = get(el); + LUAU_ASSERT(gty); + allocTypeUserData(L, TypeFunctionGenericType{gty->isNamed, true, gty->name}); + lua_rawseti(L, -2, pos++); } - lua_createtable(L, 0, 0); return 1; } @@ -1119,6 +1421,36 @@ static int getClassParent(lua_State* L) return 1; } +// Luau: `self:name() -> string?` +// Returns the name of the generic or 'nil' if the generic is unnamed +static int getGenericName(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.name: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + if (tfgt->isNamed) + lua_pushstring(L, tfgt->name.c_str()); + else + lua_pushnil(L); + + return 1; +} + +// Luau: `self:ispack() -> boolean` +// Returns true if the generic is a pack +static int getGenericIsPack(lua_State* L) +{ + TypeFunctionTypeId self = getTypeUserData(L, 1); + auto tfgt = get(self); + if (!tfgt) + luaL_error(L, "type.ispack: expected self to be a generic, but got %s instead", getTag(L, self).c_str()); + + lua_pushboolean(L, tfgt->isPack); + return 1; +} + // Luau: `self:properties() -> {[type]: { read: type?, write: type? }}` // Returns the properties of a table or class type static int getProps(lua_State* L) @@ -1388,7 +1720,7 @@ static int checkTag(lua_State* L) TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty); // Forward declaration -// Luau: `types.copy(arg: string) -> type` +// Luau: `types.copy(arg: type) -> type` // Returns a deep copy of the argument static int deepCopy(lua_State* L) { @@ -1438,8 +1770,9 @@ void registerTypesLibrary(lua_State* L) {"unionof", createUnion}, {"intersectionof", createIntersection}, {"newtable", createTable}, - {"newfunction", createFunction}, + {"newfunction", FFlag::LuauUserTypeFunGenerics ? createFunction : createFunction_DEPRECATED}, {"copy", deepCopy}, + {FFlag::LuauUserTypeFunGenerics ? "generic" : nullptr, FFlag::LuauUserTypeFunGenerics ? createGeneric : nullptr}, {nullptr, nullptr} }; @@ -1504,6 +1837,8 @@ void registerTypeUserData(lua_State* L) {"parameters", getFunctionParameters}, {"setreturns", setFunctionReturns}, {"returns", getFunctionReturns}, + {"setgenerics", setFunctionGenerics}, + {"generics", getFunctionGenerics}, // Union and Intersection type methods {"components", getComponents}, @@ -1511,6 +1846,14 @@ void registerTypeUserData(lua_State* L) // Class type methods {"parent", getClassParent}, + // Function type methods (cont.) + {FFlag::LuauUserTypeFunGenerics ? "setgenerics" : nullptr, FFlag::LuauUserTypeFunGenerics ? setFunctionGenerics : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "generics" : nullptr, FFlag::LuauUserTypeFunGenerics ? getFunctionGenerics : nullptr}, + + // Generic type methods + {FFlag::LuauUserTypeFunGenerics ? "name" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericName : nullptr}, + {FFlag::LuauUserTypeFunGenerics ? "ispack" : nullptr, FFlag::LuauUserTypeFunGenerics ? getGenericIsPack : nullptr}, + {nullptr, nullptr} }; @@ -1776,6 +2119,27 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc if (seenSetContains(seen, &lhs, &rhs)) return true; + if (FFlag::LuauUserTypeFunGenerics) + { + if (lhs.generics.size() != rhs.generics.size()) + return false; + + for (auto l = lhs.generics.begin(), r = rhs.generics.begin(); l != lhs.generics.end() && r != rhs.generics.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + + if (lhs.genericPacks.size() != rhs.genericPacks.size()) + return false; + + for (auto l = lhs.genericPacks.begin(), r = rhs.genericPacks.begin(); l != lhs.genericPacks.end() && r != rhs.genericPacks.end(); ++l, ++r) + { + if (!areEqual(seen, **l, **r)) + return false; + } + } + if (bool(lhs.argTypes) != bool(rhs.argTypes)) return false; @@ -1876,6 +2240,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType return areEqual(seen, *lf, *rf); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericType* lg = get(&lhs); + const TypeFunctionGenericType* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->isPack == rg->isPack && lg->name == rg->name; + } + } + return false; } @@ -1922,6 +2296,16 @@ bool areEqual(SeenSet& seen, const TypeFunctionTypePackVar& lhs, const TypeFunct return areEqual(seen, *lv, *rv); } + if (FFlag::LuauUserTypeFunGenerics) + { + { + const TypeFunctionGenericTypePack* lg = get(&lhs); + const TypeFunctionGenericTypePack* rg = get(&rhs); + if (lg && rg) + return lg->isNamed == rg->isNamed && lg->name == rg->name; + } + } + return false; } @@ -2146,10 +2530,14 @@ private: else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) target = ty; // Don't copy a class since they are immutable + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); + else + LUAU_ASSERT(!"Unknown type"); types[ty] = target; queue.emplace_back(ty, target); @@ -2167,6 +2555,10 @@ private: target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); gPack && FFlag::LuauUserTypeFunGenerics) + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->isNamed, gPack->name}); + else + LUAU_ASSERT(!"Unknown type"); packs[tp] = target; queue.emplace_back(tp, target); @@ -2197,6 +2589,9 @@ private: cloneChildren(f1, f2); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) cloneChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + cloneChildren(g1, g2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2208,6 +2603,9 @@ private: else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) cloneChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + cloneChildren(gPack1, gPack2); else LUAU_ASSERT(!"Unknown pair?"); // First and argument should always represent the same types } @@ -2288,6 +2686,17 @@ private: void cloneChildren(TypeFunctionFunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowClone(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowClone(tp)); + } + f2->argTypes = shallowClone(f1->argTypes); f2->retTypes = shallowClone(f1->retTypes); } @@ -2297,16 +2706,32 @@ private: // noop. } + void cloneChildren(TypeFunctionGenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void cloneChildren(TypeFunctionTypePack* t1, TypeFunctionTypePack* t2) { for (TypeFunctionTypeId& ty : t1->head) t2->head.push_back(shallowClone(ty)); + + if (FFlag::LuauUserTypeFunCloneTail) + { + if (t1->tail) + t2->tail = shallowClone(*t1->tail); + } } void cloneChildren(TypeFunctionVariadicTypePack* v1, TypeFunctionVariadicTypePack* v2) { v2->type = shallowClone(v1->type); } + + void cloneChildren(TypeFunctionGenericTypePack* g1, TypeFunctionGenericTypePack* g2) + { + // noop. + } }; TypeFunctionTypeId deepClone(NotNull runtime, TypeFunctionTypeId ty) diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index a89784b9..4aa9344f 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -21,6 +21,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) namespace Luau { @@ -221,13 +222,22 @@ private: else if (auto f = get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); - target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{emptyTypePack, emptyTypePack}); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } else if (auto c = get(ty)) { state->classesSerialized[c->name] = ty; target = typeFunctionRuntime->typeArena.allocate(TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, c->name}); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + Name name = g->name; + + if (!g->explicitName) + name = format("g%d", g->index); + + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->explicitName, false, name}); + } else { std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -252,6 +262,15 @@ private: target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); else if (auto vPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + Name name = gPack->name; + + if (!gPack->explicitName) + name = format("g%d", gPack->index); + + target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->explicitName, name}); + } else { std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -289,6 +308,9 @@ private: serializeChildren(f1, f2); else if (auto [c1, c2] = std::tuple{get(ty), getMutable(tfti)}; c1 && c2) serializeChildren(c1, c2); + else if (auto [g1, g2] = std::tuple{get(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + serializeChildren(g1, g2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type %s is not currently serializable by type functions", toString(ty).c_str()); @@ -302,6 +324,9 @@ private: serializeChildren(tPack1, tPack2); else if (auto [vPack1, vPack2] = std::tuple{get(tp), getMutable(tftp)}; vPack1 && vPack2) serializeChildren(vPack1, vPack2); + else if (auto [gPack1, gPack2] = std::tuple{get(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + serializeChildren(gPack1, gPack2); else { // Either this or ty and tfti do not represent the same type std::string error = format("Argument of type pack %s is not currently serializable by type functions", toString(tp).c_str()); @@ -391,6 +416,17 @@ private: void serializeChildren(const FunctionType* f1, TypeFunctionFunctionType* f2) { + if (FFlag::LuauUserTypeFunGenerics) + { + f2->generics.reserve(f1->generics.size()); + for (auto ty : f1->generics) + f2->generics.push_back(shallowSerialize(ty)); + + f2->genericPacks.reserve(f1->genericPacks.size()); + for (auto tp : f1->genericPacks) + f2->genericPacks.push_back(shallowSerialize(tp)); + } + f2->argTypes = shallowSerialize(f1->argTypes); f2->retTypes = shallowSerialize(f1->retTypes); } @@ -420,6 +456,11 @@ private: c2->parent = shallowSerialize(*c1->parent); } + void serializeChildren(const GenericType* g1, TypeFunctionGenericType* g2) + { + // noop. + } + void serializeChildren(const TypePack* t1, TypeFunctionTypePack* t2) { for (const TypeId& ty : t1->head) @@ -433,6 +474,25 @@ private: { v2->type = shallowSerialize(v1->ty); } + + void serializeChildren(const GenericTypePack* v1, TypeFunctionGenericTypePack* v2) + { + // noop. + } +}; + +template +struct SerializedGeneric +{ + bool isNamed = false; + std::string name; + T type = nullptr; +}; + +struct SerializedFunctionScope +{ + size_t oldQueueSize = 0; + TypeFunctionFunctionType* function = nullptr; }; // Complete inverse of TypeFunctionSerializer @@ -453,6 +513,15 @@ class TypeFunctionDeserializer // second must be PrimitiveType; else there should be an error std::vector> queue; + // Generic types and packs currently in scope + // Generics are resolved by name even if runtime generic type pointers are different + // Multiple names mapping to the same generic can be in scope for nested generic functions + std::vector> genericTypes; + std::vector> genericPacks; + + // To track when generics go out of scope, we have a list of queue positions at which a specific function has introduced generics + std::vector functionScopes; + SeenTypes types; // Mapping of TypeFunctionTypeIds that have been shallow deserialized to TypeIds SeenTypePacks packs; // Mapping of TypeFunctionTypePackIds that have been shallow deserialized to TypePackIds @@ -520,6 +589,16 @@ private: queue.pop_back(); deserializeChildren(tfti, ty); + + if (FFlag::LuauUserTypeFunGenerics) + { + // If we have completed working on all children of a function, remove the generic parameters from scope + if (!functionScopes.empty() && queue.size() == functionScopes.back().oldQueueSize && state->errors.empty()) + { + closeFunctionScope(functionScopes.back().function); + functionScopes.pop_back(); + } + } } } @@ -552,6 +631,21 @@ private: } } + void closeFunctionScope(TypeFunctionFunctionType* f) + { + if (!f->generics.empty()) + { + LUAU_ASSERT(genericTypes.size() >= f->generics.size()); + genericTypes.erase(genericTypes.begin() + int(genericTypes.size() - f->generics.size()), genericTypes.end()); + } + + if (!f->genericPacks.empty()) + { + LUAU_ASSERT(genericPacks.size() >= f->genericPacks.size()); + genericPacks.erase(genericPacks.begin() + int(genericPacks.size() - f->genericPacks.size()), genericPacks.end()); + } + } + TypeId shallowDeserialize(TypeFunctionTypeId ty) { if (auto it = find(ty)) @@ -631,6 +725,33 @@ private: else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious class type is being deserialized"); } + else if (auto g = get(ty); FFlag::LuauUserTypeFunGenerics && g) + { + if (g->isPack) + { + state->errors.push_back(format("Generic type pack '%s...' cannot be placed in a type position", g->name.c_str())); + return nullptr; + } + else + { + auto it = std::find_if( + genericTypes.rbegin(), + genericTypes.rend(), + [&](const SerializedGeneric& el) + { + return g->isNamed == el.isNamed && g->name == el.name; + } + ); + + if (it == genericTypes.rend()) + { + state->errors.push_back(format("Generic type '%s' is not in a scope of the active generic function", g->name.c_str())); + return nullptr; + } + + target = it->type; + } + } else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); @@ -647,11 +768,36 @@ private: // Create a shallow deserialization TypePackId target = {}; if (auto tPack = get(tp)) + { target = state->ctx->arena->addTypePack(TypePack{}); + } else if (auto vPack = get(tp)) + { target = state->ctx->arena->addTypePack(VariadicTypePack{}); + } + else if (auto gPack = get(tp); FFlag::LuauUserTypeFunGenerics && gPack) + { + auto it = std::find_if( + genericPacks.rbegin(), + genericPacks.rend(), + [&](const SerializedGeneric& el) + { + return gPack->isNamed == el.isNamed && gPack->name == el.name; + } + ); + + if (it == genericPacks.rend()) + { + state->errors.push_back(format("Generic type pack '%s...' is not in a scope of the active generic function", gPack->name.c_str())); + return nullptr; + } + + target = it->type; + } else + { state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); + } packs[tp] = target; queue.emplace_back(tp, target); @@ -686,6 +832,9 @@ private: deserializeChildren(f2, f1); else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) deserializeChildren(c2, c1); + else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; + FFlag::LuauUserTypeFunGenerics && g1 && g2) + deserializeChildren(g2, g1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -697,6 +846,9 @@ private: else if (auto [vPack1, vPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; vPack1 && vPack2) deserializeChildren(vPack2, vPack1); + else if (auto [gPack1, gPack2] = std::tuple{getMutable(tp), getMutable(tftp)}; + FFlag::LuauUserTypeFunGenerics && gPack1 && gPack2) + deserializeChildren(gPack2, gPack1); else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } @@ -780,6 +932,64 @@ private: void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1) { + if (FFlag::LuauUserTypeFunGenerics) + { + functionScopes.push_back({queue.size(), f2}); + + std::set> genericNames; + + // Introduce generic function parameters into scope + for (auto ty : f2->generics) + { + auto gty = get(ty); + LUAU_ASSERT(gty && !gty->isPack); + + std::pair nameKey = std::make_pair(gty->isNamed, gty->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gty->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypeId mapping = state->ctx->arena->addTV(Type(gty->isNamed ? GenericType{state->ctx->scope.get(), gty->name} : GenericType{})); + genericTypes.push_back({gty->isNamed, gty->name, mapping}); + } + + for (auto tp : f2->genericPacks) + { + auto gtp = get(tp); + LUAU_ASSERT(gtp); + + std::pair nameKey = std::make_pair(gtp->isNamed, gtp->name); + + // Duplicates are not allowed + if (genericNames.find(nameKey) != genericNames.end()) + { + state->errors.push_back(format("Duplicate type parameter '%s'", gtp->name.c_str())); + return; + } + + genericNames.insert(nameKey); + + TypePackId mapping = + state->ctx->arena->addTypePack(TypePackVar(gtp->isNamed ? GenericTypePack{state->ctx->scope.get(), gtp->name} : GenericTypePack{}) + ); + genericPacks.push_back({gtp->isNamed, gtp->name, mapping}); + } + + f1->generics.reserve(f2->generics.size()); + for (auto ty : f2->generics) + f1->generics.push_back(shallowDeserialize(ty)); + + f1->genericPacks.reserve(f2->genericPacks.size()); + for (auto tp : f2->genericPacks) + f1->genericPacks.push_back(shallowDeserialize(tp)); + } + if (f2->argTypes) f1->argTypes = shallowDeserialize(f2->argTypes); @@ -792,6 +1002,11 @@ private: // noop. } + void deserializeChildren(TypeFunctionGenericType* g2, GenericType* g1) + { + // noop. + } + void deserializeChildren(TypeFunctionTypePack* t2, TypePack* t1) { for (TypeFunctionTypeId& ty : t2->head) @@ -805,6 +1020,11 @@ private: { v1->ty = shallowDeserialize(v2->type); } + + void deserializeChildren(TypeFunctionGenericTypePack* v2, GenericTypePack* v1) + { + // noop. + } }; TypeFunctionTypeId serialize(TypeId ty, TypeFunctionRuntimeBuilderState* state) diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 475d19da..ce98f58e 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -116,7 +116,7 @@ private: AstStat* parseFor(); // funcname ::= Name {`.' Name} [`:' Name] - AstExpr* parseFunctionName(Location start, bool& hasself, AstName& debugname); + AstExpr* parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname); // function funcname funcbody LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray& attributes = {nullptr, 0}); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index fcb74694..f618bc06 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauAllowFragmentParsing) LUAU_FASTFLAGVARIABLE(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAGVARIABLE(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAGVARIABLE(LuauFixFunctionNameStartPosition) namespace Luau { @@ -638,7 +639,7 @@ AstStat* Parser::parseFor() } // funcname ::= Name {`.' Name} [`:' Name] -AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debugname) +AstExpr* Parser::parseFunctionName(Location start_DEPRECATED, bool& hasself, AstName& debugname) { if (lexer.current().type == Lexeme::Name) debugname = AstName(lexer.current().name); @@ -658,7 +659,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, '.'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, '.' + ); // note: while the parser isn't recursive here, we're generating recursive structures of unbounded depth incrementRecursionCounter("function name"); @@ -677,7 +680,9 @@ AstExpr* Parser::parseFunctionName(Location start, bool& hasself, AstName& debug // while we could concatenate the name chain, for now let's just write the short name debugname = name.name; - expr = allocator.alloc(Location(start, name.location), expr, name.name, name.location, opPosition, ':'); + expr = allocator.alloc( + Location(FFlag::LuauFixFunctionNameStartPosition ? expr->location : start_DEPRECATED, name.location), expr, name.name, name.location, opPosition, ':' + ); hasself = true; } diff --git a/CodeGen/include/Luau/CodeAllocator.h b/CodeGen/include/Luau/CodeAllocator.h index dcc1de85..db1774d8 100644 --- a/CodeGen/include/Luau/CodeAllocator.h +++ b/CodeGen/include/Luau/CodeAllocator.h @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include diff --git a/CodeGen/include/Luau/CodeGen.h b/CodeGen/include/Luau/CodeGen.h index 0cf9d9a5..2e689fe2 100644 --- a/CodeGen/include/Luau/CodeGen.h +++ b/CodeGen/include/Luau/CodeGen.h @@ -1,7 +1,10 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once -#include +#include "Luau/CodeGenCommon.h" +#include "Luau/CodeGenOptions.h" +#include "Luau/LoweringStats.h" + #include #include #include @@ -12,25 +15,11 @@ struct lua_State; -#if defined(__x86_64__) || defined(_M_X64) -#define CODEGEN_TARGET_X64 -#elif defined(__aarch64__) || defined(_M_ARM64) -#define CODEGEN_TARGET_A64 -#endif - namespace Luau { namespace CodeGen { -enum CodeGenFlags -{ - // Only run native codegen for modules that have been marked with --!native - CodeGen_OnlyNativeModules = 1 << 0, - // Run native codegen for functions that the compiler considers not profitable - CodeGen_ColdFunctions = 1 << 1, -}; - // These enum values can be reported through telemetry. // To ensure consistency, changes should be additive. enum class CodeGenCompilationResult @@ -72,106 +61,6 @@ struct CompilationResult } }; -struct IrBuilder; -struct IrOp; - -using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); -using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostVectorNamecallHandler = - bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); - -enum class HostMetamethod -{ - Add, - Sub, - Mul, - Div, - Idiv, - Mod, - Pow, - Minus, - Equal, - LessThan, - LessEqual, - Length, - Concat, -}; - -using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); -using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); -using HostUserdataAccessHandler = - bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); -using HostUserdataMetamethodHandler = - bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); -using HostUserdataNamecallHandler = bool (*)( - IrBuilder& builder, - uint8_t type, - const char* member, - size_t memberLength, - int argResReg, - int sourceReg, - int params, - int results, - int pcpos -); - -struct HostIrHooks -{ - // Suggest result type of a vector field access - HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; - - // Suggest result type of a vector function namecall - HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; - - // Handle vector value field access - // 'sourceReg' is guaranteed to be a vector - // Guards should take a VM exit to 'pcpos' - HostVectorAccessHandler vectorAccess = nullptr; - - // Handle namecall performed on a vector value - // 'sourceReg' (self argument) is guaranteed to be a vector - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostVectorNamecallHandler vectorNamecall = nullptr; - - // Suggest result type of a userdata field access - HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; - - // Suggest result type of a metamethod call - HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; - - // Suggest result type of a userdata namecall - HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; - - // Handle userdata value field access - // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked - // Write to 'resultReg' might invalidate 'sourceReg' - // Guards should take a VM exit to 'pcpos' - HostUserdataAccessHandler userdataAccess = nullptr; - - // Handle metamethod operation on a userdata value - // 'lhs' and 'rhs' operands can be VM registers of constants - // Operand types have to be checked and userdata operand tags have to be checked - // Write to 'resultReg' might invalidate source operands - // Guards should take a VM exit to 'pcpos' - HostUserdataMetamethodHandler userdataMetamethod = nullptr; - - // Handle namecall performed on a userdata value - // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked - // All other arguments can be of any type - // Guards should take a VM exit to 'pcpos' - HostUserdataNamecallHandler userdataNamecall = nullptr; -}; - -struct CompilationOptions -{ - unsigned int flags = 0; - HostIrHooks hooks; - - // null-terminated array of userdata types names that might have custom lowering - const char* const* userdataTypes = nullptr; -}; - struct CompilationStats { size_t bytecodeSizeBytes = 0; @@ -184,8 +73,6 @@ struct CompilationStats uint32_t functionsBound = 0; }; -using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); - bool isSupported(); class SharedCodeGenContext; @@ -249,153 +136,6 @@ CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, unsig CompilationResult compile(lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); CompilationResult compile(const ModuleId& moduleId, lua_State* L, int idx, const CompilationOptions& options, CompilationStats* stats = nullptr); -using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); - -// Output "#" before IR blocks and instructions -enum class IncludeIrPrefix -{ - No, - Yes -}; - -// Output user count and last use information of blocks and instructions -enum class IncludeUseInfo -{ - No, - Yes -}; - -// Output CFG informations like block predecessors, successors and etc -enum class IncludeCfgInfo -{ - No, - Yes -}; - -// Output VM register live in/out information for blocks -enum class IncludeRegFlowInfo -{ - No, - Yes -}; - -struct AssemblyOptions -{ - enum Target - { - Host, - A64, - A64_NoFeatures, - X64_Windows, - X64_SystemV, - }; - - Target target = Host; - - CompilationOptions compilationOptions; - - bool outputBinary = false; - - bool includeAssembly = false; - bool includeIr = false; - bool includeOutlinedCode = false; - bool includeIrTypes = false; - - IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; - IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; - IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; - IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; - - // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id - AnnotatorFn annotator = nullptr; - void* annotatorContext = nullptr; -}; - -struct BlockLinearizationStats -{ - unsigned int constPropInstructionCount = 0; - double timeSeconds = 0.0; - - BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) - { - this->constPropInstructionCount += that.constPropInstructionCount; - this->timeSeconds += that.timeSeconds; - - return *this; - } - - BlockLinearizationStats operator+(const BlockLinearizationStats& other) const - { - BlockLinearizationStats result(*this); - result += other; - return result; - } -}; - -enum FunctionStatsFlags -{ - // Enable stats collection per function - FunctionStats_Enable = 1 << 0, - // Compute function bytecode summary - FunctionStats_BytecodeSummary = 1 << 1, -}; - -struct FunctionStats -{ - std::string name; - int line = -1; - unsigned bcodeCount = 0; - unsigned irCount = 0; - unsigned asmCount = 0; - unsigned asmSize = 0; - std::vector> bytecodeSummary; -}; - -struct LoweringStats -{ - unsigned totalFunctions = 0; - unsigned skippedFunctions = 0; - int spillsToSlot = 0; - int spillsToRestore = 0; - unsigned maxSpillSlotsUsed = 0; - unsigned blocksPreOpt = 0; - unsigned blocksPostOpt = 0; - unsigned maxBlockInstructions = 0; - - int regAllocErrors = 0; - int loweringErrors = 0; - - BlockLinearizationStats blockLinearizationStats; - - unsigned functionStatsFlags = 0; - std::vector functions; - - LoweringStats operator+(const LoweringStats& other) const - { - LoweringStats result(*this); - result += other; - return result; - } - - LoweringStats& operator+=(const LoweringStats& that) - { - this->totalFunctions += that.totalFunctions; - this->skippedFunctions += that.skippedFunctions; - this->spillsToSlot += that.spillsToSlot; - this->spillsToRestore += that.spillsToRestore; - this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); - this->blocksPreOpt += that.blocksPreOpt; - this->blocksPostOpt += that.blocksPostOpt; - this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); - this->regAllocErrors += that.regAllocErrors; - this->loweringErrors += that.loweringErrors; - this->blockLinearizationStats += that.blockLinearizationStats; - if (this->functionStatsFlags & FunctionStats_Enable) - this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); - return *this; - } -}; - // Generates assembly for target function and all inner functions std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {}, LoweringStats* stats = nullptr); diff --git a/CodeGen/include/Luau/CodeGenCommon.h b/CodeGen/include/Luau/CodeGenCommon.h index 84090423..a9d1761c 100644 --- a/CodeGen/include/Luau/CodeGenCommon.h +++ b/CodeGen/include/Luau/CodeGenCommon.h @@ -10,3 +10,9 @@ #else #define CODEGEN_ASSERT(expr) (void)sizeof(!!(expr)) #endif + +#if defined(__x86_64__) || defined(_M_X64) +#define CODEGEN_TARGET_X64 +#elif defined(__aarch64__) || defined(_M_ARM64) +#define CODEGEN_TARGET_A64 +#endif diff --git a/CodeGen/include/Luau/CodeGenOptions.h b/CodeGen/include/Luau/CodeGenOptions.h new file mode 100644 index 00000000..de95efa6 --- /dev/null +++ b/CodeGen/include/Luau/CodeGenOptions.h @@ -0,0 +1,188 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +enum CodeGenFlags +{ + // Only run native codegen for modules that have been marked with --!native + CodeGen_OnlyNativeModules = 1 << 0, + // Run native codegen for functions that the compiler considers not profitable + CodeGen_ColdFunctions = 1 << 1, +}; + +using AllocationCallback = void(void* context, void* oldPointer, size_t oldSize, void* newPointer, size_t newSize); + +struct IrBuilder; +struct IrOp; + +using HostVectorOperationBytecodeType = uint8_t (*)(const char* member, size_t memberLength); +using HostVectorAccessHandler = bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostVectorNamecallHandler = + bool (*)(IrBuilder& builder, const char* member, size_t memberLength, int argResReg, int sourceReg, int params, int results, int pcpos); + +enum class HostMetamethod +{ + Add, + Sub, + Mul, + Div, + Idiv, + Mod, + Pow, + Minus, + Equal, + LessThan, + LessEqual, + Length, + Concat, +}; + +using HostUserdataOperationBytecodeType = uint8_t (*)(uint8_t type, const char* member, size_t memberLength); +using HostUserdataMetamethodBytecodeType = uint8_t (*)(uint8_t lhsTy, uint8_t rhsTy, HostMetamethod method); +using HostUserdataAccessHandler = + bool (*)(IrBuilder& builder, uint8_t type, const char* member, size_t memberLength, int resultReg, int sourceReg, int pcpos); +using HostUserdataMetamethodHandler = + bool (*)(IrBuilder& builder, uint8_t lhsTy, uint8_t rhsTy, int resultReg, IrOp lhs, IrOp rhs, HostMetamethod method, int pcpos); +using HostUserdataNamecallHandler = bool (*)( + IrBuilder& builder, + uint8_t type, + const char* member, + size_t memberLength, + int argResReg, + int sourceReg, + int params, + int results, + int pcpos +); + +struct HostIrHooks +{ + // Suggest result type of a vector field access + HostVectorOperationBytecodeType vectorAccessBytecodeType = nullptr; + + // Suggest result type of a vector function namecall + HostVectorOperationBytecodeType vectorNamecallBytecodeType = nullptr; + + // Handle vector value field access + // 'sourceReg' is guaranteed to be a vector + // Guards should take a VM exit to 'pcpos' + HostVectorAccessHandler vectorAccess = nullptr; + + // Handle namecall performed on a vector value + // 'sourceReg' (self argument) is guaranteed to be a vector + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostVectorNamecallHandler vectorNamecall = nullptr; + + // Suggest result type of a userdata field access + HostUserdataOperationBytecodeType userdataAccessBytecodeType = nullptr; + + // Suggest result type of a metamethod call + HostUserdataMetamethodBytecodeType userdataMetamethodBytecodeType = nullptr; + + // Suggest result type of a userdata namecall + HostUserdataOperationBytecodeType userdataNamecallBytecodeType = nullptr; + + // Handle userdata value field access + // 'sourceReg' is guaranteed to be a userdata, but tag has to be checked + // Write to 'resultReg' might invalidate 'sourceReg' + // Guards should take a VM exit to 'pcpos' + HostUserdataAccessHandler userdataAccess = nullptr; + + // Handle metamethod operation on a userdata value + // 'lhs' and 'rhs' operands can be VM registers of constants + // Operand types have to be checked and userdata operand tags have to be checked + // Write to 'resultReg' might invalidate source operands + // Guards should take a VM exit to 'pcpos' + HostUserdataMetamethodHandler userdataMetamethod = nullptr; + + // Handle namecall performed on a userdata value + // 'sourceReg' (self argument) is guaranteed to be a userdata, but tag has to be checked + // All other arguments can be of any type + // Guards should take a VM exit to 'pcpos' + HostUserdataNamecallHandler userdataNamecall = nullptr; +}; + +struct CompilationOptions +{ + unsigned int flags = 0; + HostIrHooks hooks; + + // null-terminated array of userdata types names that might have custom lowering + const char* const* userdataTypes = nullptr; +}; + + +using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos); + +// Output "#" before IR blocks and instructions +enum class IncludeIrPrefix +{ + No, + Yes +}; + +// Output user count and last use information of blocks and instructions +enum class IncludeUseInfo +{ + No, + Yes +}; + +// Output CFG informations like block predecessors, successors and etc +enum class IncludeCfgInfo +{ + No, + Yes +}; + +// Output VM register live in/out information for blocks +enum class IncludeRegFlowInfo +{ + No, + Yes +}; + +struct AssemblyOptions +{ + enum Target + { + Host, + A64, + A64_NoFeatures, + X64_Windows, + X64_SystemV, + }; + + Target target = Host; + + CompilationOptions compilationOptions; + + bool outputBinary = false; + + bool includeAssembly = false; + bool includeIr = false; + bool includeOutlinedCode = false; + bool includeIrTypes = false; + + IncludeIrPrefix includeIrPrefix = IncludeIrPrefix::Yes; + IncludeUseInfo includeUseInfo = IncludeUseInfo::Yes; + IncludeCfgInfo includeCfgInfo = IncludeCfgInfo::Yes; + IncludeRegFlowInfo includeRegFlowInfo = IncludeRegFlowInfo::Yes; + + // Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id + AnnotatorFn annotator = nullptr; + void* annotatorContext = nullptr; +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 44f2495b..38519f95 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -20,6 +20,8 @@ namespace Luau namespace CodeGen { +struct LoweringStats; + // IR extensions to LuauBuiltinFunction enum (these only exist inside IR, and start from 256 to avoid collisions) enum { @@ -67,18 +69,18 @@ enum class IrCmd : uint8_t LOAD_ENV, // Get pointer (TValue) to table array at index - // A: pointer (Table) + // A: pointer (LuaTable) // B: int GET_ARR_ADDR, // Get pointer (LuaNode) to table node element at the active cached slot index - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (pcpos) // C: Kn GET_SLOT_NODE_ADDR, // Get pointer (LuaNode) to table node element at the main position of the specified key hash - // A: pointer (Table) + // A: pointer (LuaTable) // B: unsigned int (hash) GET_HASH_NODE_ADDR, @@ -273,7 +275,7 @@ enum class IrCmd : uint8_t JUMP_SLOT_MATCH, // Get table length - // A: pointer (Table) + // A: pointer (LuaTable) TABLE_LEN, // Get string length @@ -286,11 +288,11 @@ enum class IrCmd : uint8_t NEW_TABLE, // Duplicate a table - // A: pointer (Table) + // A: pointer (LuaTable) DUP_TABLE, // Insert an integer key into a table and return the pointer to inserted value (TValue) - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (key) TABLE_SETNUM, @@ -430,13 +432,13 @@ enum class IrCmd : uint8_t CHECK_TRUTHY, // Guard against readonly table - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_READONLY, // Guard against table having a metatable - // A: pointer (Table) + // A: pointer (LuaTable) // B: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure CHECK_NO_METATABLE, @@ -447,7 +449,7 @@ enum class IrCmd : uint8_t CHECK_SAFE_ENV, // Guard against index overflowing the table array size - // A: pointer (Table) + // A: pointer (LuaTable) // B: int (index) // C: block/vmexit/undef // When undef is specified instead of a block, execution is aborted on check failure @@ -503,11 +505,11 @@ enum class IrCmd : uint8_t BARRIER_OBJ, // Handle GC write barrier (backwards) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) BARRIER_TABLE_BACK, // Handle GC write barrier (forward) for a write into a table - // A: pointer (Table) + // A: pointer (LuaTable) // B: Rn (TValue that was written to the object) // C: tag/undef (tag of the value that was written) BARRIER_TABLE_FORWARD, @@ -1049,6 +1051,8 @@ struct IrFunction CfgInfo cfg; + LoweringStats* stats = nullptr; + IrBlock& blockOp(IrOp op) { CODEGEN_ASSERT(op.kind == IrOpKind::Block); diff --git a/CodeGen/include/Luau/IrDump.h b/CodeGen/include/Luau/IrDump.h index c0737ae8..27a9feb4 100644 --- a/CodeGen/include/Luau/IrDump.h +++ b/CodeGen/include/Luau/IrDump.h @@ -2,7 +2,7 @@ #pragma once #include "Luau/IrData.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include #include diff --git a/CodeGen/include/Luau/LoweringStats.h b/CodeGen/include/Luau/LoweringStats.h new file mode 100644 index 00000000..532a5270 --- /dev/null +++ b/CodeGen/include/Luau/LoweringStats.h @@ -0,0 +1,103 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include +#include +#include + +namespace Luau +{ +namespace CodeGen +{ + +struct BlockLinearizationStats +{ + unsigned int constPropInstructionCount = 0; + double timeSeconds = 0.0; + + BlockLinearizationStats& operator+=(const BlockLinearizationStats& that) + { + this->constPropInstructionCount += that.constPropInstructionCount; + this->timeSeconds += that.timeSeconds; + + return *this; + } + + BlockLinearizationStats operator+(const BlockLinearizationStats& other) const + { + BlockLinearizationStats result(*this); + result += other; + return result; + } +}; + +enum FunctionStatsFlags +{ + // Enable stats collection per function + FunctionStats_Enable = 1 << 0, + // Compute function bytecode summary + FunctionStats_BytecodeSummary = 1 << 1, +}; + +struct FunctionStats +{ + std::string name; + int line = -1; + unsigned bcodeCount = 0; + unsigned irCount = 0; + unsigned asmCount = 0; + unsigned asmSize = 0; + std::vector> bytecodeSummary; +}; + +struct LoweringStats +{ + unsigned totalFunctions = 0; + unsigned skippedFunctions = 0; + int spillsToSlot = 0; + int spillsToRestore = 0; + unsigned maxSpillSlotsUsed = 0; + unsigned blocksPreOpt = 0; + unsigned blocksPostOpt = 0; + unsigned maxBlockInstructions = 0; + + int regAllocErrors = 0; + int loweringErrors = 0; + + BlockLinearizationStats blockLinearizationStats; + + unsigned functionStatsFlags = 0; + std::vector functions; + + LoweringStats operator+(const LoweringStats& other) const + { + LoweringStats result(*this); + result += other; + return result; + } + + LoweringStats& operator+=(const LoweringStats& that) + { + this->totalFunctions += that.totalFunctions; + this->skippedFunctions += that.skippedFunctions; + this->spillsToSlot += that.spillsToSlot; + this->spillsToRestore += that.spillsToRestore; + this->maxSpillSlotsUsed = std::max(this->maxSpillSlotsUsed, that.maxSpillSlotsUsed); + this->blocksPreOpt += that.blocksPreOpt; + this->blocksPostOpt += that.blocksPostOpt; + this->maxBlockInstructions = std::max(this->maxBlockInstructions, that.maxBlockInstructions); + + this->regAllocErrors += that.regAllocErrors; + this->loweringErrors += that.loweringErrors; + + this->blockLinearizationStats += that.blockLinearizationStats; + + if (this->functionStatsFlags & FunctionStats_Enable) + this->functions.insert(this->functions.end(), that.functions.begin(), that.functions.end()); + + return *this; + } +}; + +} // namespace CodeGen +} // namespace Luau diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 134a49f2..b859b111 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -2,7 +2,7 @@ #include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index cb2d693a..137c554c 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -2,6 +2,7 @@ #include "Luau/CodeBlockUnwind.h" #include "Luau/CodeAllocator.h" +#include "Luau/CodeGenCommon.h" #include "Luau/UnwindBuilder.h" #include diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index f22b2379..a518165f 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -3,7 +3,7 @@ #include "CodeGenLower.h" -#include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/IrBuilder.h" @@ -44,6 +44,7 @@ LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering) +LUAU_FASTFLAGVARIABLE(CodegenWiderLoweringStats) // Per-module IR instruction count limit LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index bffce517..6bbdc473 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -1,5 +1,4 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/CodeGen.h" #include "Luau/BytecodeAnalysis.h" #include "Luau/BytecodeUtils.h" #include "Luau/BytecodeSummary.h" diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 262d4a42..82dfa17e 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -5,6 +5,7 @@ #include "CodeGenLower.h" #include "CodeGenX64.h" +#include "Luau/CodeGenCommon.h" #include "Luau/CodeBlockUnwind.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index f41211cd..406fe5c9 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -7,6 +7,7 @@ #include "Luau/IrBuilder.h" #include "Luau/IrDump.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/OptimizeConstProp.h" #include "Luau/OptimizeDeadStore.h" #include "Luau/OptimizeFinalX64.h" @@ -24,6 +25,7 @@ LUAU_FASTFLAG(DebugCodegenNoOpt) LUAU_FASTFLAG(DebugCodegenOptSize) LUAU_FASTFLAG(DebugCodegenSkipNumbering) +LUAU_FASTFLAG(CodegenWiderLoweringStats) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsBlockLimit) LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit) @@ -298,6 +300,9 @@ inline bool lowerFunction( CodeGenCompilationResult& codeGenCompilationResult ) { + if (FFlag::CodegenWiderLoweringStats) + ir.function.stats = stats; + killUnusedBlocks(ir.function); unsigned preOptBlockCount = 0; diff --git a/CodeGen/src/CodeGenUtils.cpp b/CodeGen/src/CodeGenUtils.cpp index 7244e6cc..26451eea 100644 --- a/CodeGen/src/CodeGenUtils.cpp +++ b/CodeGen/src/CodeGenUtils.cpp @@ -63,7 +63,7 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; @@ -106,7 +106,7 @@ bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra) return false; } -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra) +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra) { int sizearray = h->sizearray; int sizenode = 1 << h->lsizenode; @@ -233,7 +233,7 @@ Udata* newUserdata(lua_State* L, size_t s, int tag) { Udata* u = luaU_newudata(L, s, tag); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); @@ -345,7 +345,7 @@ const Instruction* executeGETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __index metamethod @@ -368,7 +368,7 @@ const Instruction* executeSETGLOBAL(lua_State* L, const Instruction* pc, StkId b LUAU_ASSERT(ttisstring(kv)); // fast-path should already have been checked, so we skip checking for it here - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; // slow-path, may invoke Lua calls via __newindex metamethod @@ -394,7 +394,7 @@ const Instruction* executeGETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -506,7 +506,7 @@ const Instruction* executeSETTABLEKS(lua_State* L, const Instruction* pc, StkId // fast-path: built-in table if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // we ignore the fast path that checks for the cached slot since IrTranslation already checks for it. @@ -591,7 +591,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -605,7 +605,7 @@ const Instruction* executeNAMECALL(lua_State* L, const Instruction* pc, StkId ba } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -662,7 +662,7 @@ const Instruction* executeSETLIST(lua_State* L, const Instruction* pc, StkId bas L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -697,7 +697,7 @@ const Instruction* executeFORGPREP(lua_State* L, const Instruction* pc, StkId ba } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { diff --git a/CodeGen/src/CodeGenUtils.h b/CodeGen/src/CodeGenUtils.h index 15d4c95d..1003a6f3 100644 --- a/CodeGen/src/CodeGenUtils.h +++ b/CodeGen/src/CodeGenUtils.h @@ -8,8 +8,8 @@ namespace Luau namespace CodeGen { -bool forgLoopTableIter(lua_State* L, Table* h, int index, TValue* ra); -bool forgLoopNodeIter(lua_State* L, Table* h, int index, TValue* ra); +bool forgLoopTableIter(lua_State* L, LuaTable* h, int index, TValue* ra); +bool forgLoopNodeIter(lua_State* L, LuaTable* h, int index, TValue* ra); bool forgLoopNonTableFallback(lua_State* L, int insnA, int aux); void forgPrepXnextFallback(lua_State* L, TValue* ra, int pc); diff --git a/CodeGen/src/EmitCommonX64.cpp b/CodeGen/src/EmitCommonX64.cpp index 79562b88..36b5130e 100644 --- a/CodeGen/src/EmitCommonX64.cpp +++ b/CodeGen/src/EmitCommonX64.cpp @@ -120,12 +120,12 @@ void getTableNodeAtCachedSlot(AssemblyBuilderX64& build, RegisterX64 tmp, Regist CODEGEN_ASSERT(tmp != node); CODEGEN_ASSERT(table != node); - build.mov(node, qword[table + offsetof(Table, node)]); + build.mov(node, qword[table + offsetof(LuaTable, node)]); // compute cached slot build.mov(tmp, sCode); build.movzx(dwordReg(tmp), byte[tmp + pcpos * sizeof(Instruction) + kOffsetOfInstructionC]); - build.and_(byteReg(tmp), byte[table + offsetof(Table, nodemask8)]); + build.and_(byteReg(tmp), byte[table + offsetof(LuaTable, nodemask8)]); // LuaNode* n = &h->node[slot]; build.shl(dwordReg(tmp), kLuaNodeSizeLog2); @@ -282,7 +282,7 @@ void callBarrierTableFast(IrRegAllocX64& regs, AssemblyBuilderX64& build, Regist IrCallWrapperX64 callWrap(regs, build); callWrap.addArgument(SizeX64::qword, rState); callWrap.addArgument(SizeX64::qword, table, tableOp); - callWrap.addArgument(SizeX64::qword, addr[table + offsetof(Table, gclist)]); + callWrap.addArgument(SizeX64::qword, addr[table + offsetof(LuaTable, gclist)]); callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaC_barrierback)]); } diff --git a/CodeGen/src/EmitInstructionX64.cpp b/CodeGen/src/EmitInstructionX64.cpp index ae3d1308..207f7f56 100644 --- a/CodeGen/src/EmitInstructionX64.cpp +++ b/CodeGen/src/EmitInstructionX64.cpp @@ -292,7 +292,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int Label skipResize; // Resize if h->sizearray < last - build.cmp(dword[table + offsetof(Table, sizearray)], last); + build.cmp(dword[table + offsetof(LuaTable, sizearray)], last); build.jcc(ConditionX64::NotBelow, skipResize); // Argument setup reordered to avoid conflicts @@ -309,7 +309,7 @@ void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int RegisterX64 arrayDst = rdx; RegisterX64 offset = rcx; - build.mov(arrayDst, qword[table + offsetof(Table, array)]); + build.mov(arrayDst, qword[table + offsetof(LuaTable, array)]); const int kUnrollSetListLimit = 4; @@ -380,7 +380,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // &array[index] build.mov(dwordReg(elemPtr), dwordReg(index)); build.shl(dwordReg(elemPtr), kTValueSizeLog2); - build.add(elemPtr, qword[table + offsetof(Table, array)]); + build.add(elemPtr, qword[table + offsetof(LuaTable, array)]); // Clear extra variables since we might have more than two for (int i = 2; i < aux; ++i) @@ -391,7 +391,7 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep // First we advance index through the array portion // while (unsigned(index) < unsigned(sizearray)) Label arrayLoop = build.setLabel(); - build.cmp(dwordReg(index), dword[table + offsetof(Table, sizearray)]); + build.cmp(dwordReg(index), dword[table + offsetof(LuaTable, sizearray)]); build.jcc(ConditionX64::NotBelow, skipArray); // If element is nil, we increment the index; if it's not, we still need 'index + 1' inside diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index d29755f1..2ec72445 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonA64.h" #include "NativeState.h" @@ -330,7 +331,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::GET_ARR_ADDR: { inst.regA64 = regs.allocReuse(KindA64::x, index, {inst.a}); - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, array))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, array))); if (inst.b.kind == IrOpKind::Inst) { @@ -376,11 +377,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // C field can be shifted as long as it's at the most significant byte of the instruction word CODEGEN_ASSERT(kOffsetOfInstructionC == 3); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, nodemask8))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, nodemask8))); build.and_(temp2, temp2, temp1w, -24); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -393,13 +394,13 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // hash & ((1 << lsizenode) - 1) == hash & ~(-1 << lsizenode) build.mov(temp1, -1); - build.ldrb(temp2, mem(regOp(inst.a), offsetof(Table, lsizenode))); + build.ldrb(temp2, mem(regOp(inst.a), offsetof(LuaTable, lsizenode))); build.lsl(temp1, temp1, temp2); build.mov(temp2, uintOp(inst.b)); build.bic(temp2, temp2, temp1); // note: this may clobber inst.a, so it's important that we don't use it after this - build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(Table, node))); + build.ldr(inst.regA64, mem(regOp(inst.a), offsetof(LuaTable, node))); build.add(inst.regA64, inst.regA64, temp2x, kLuaNodeSizeLog2); // "zero extend" temp2 to get a larger shift (top 32 bits are zero) break; } @@ -1075,10 +1076,10 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp2 = regs.allocTemp(KindA64::w); - build.ldr(temp1, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp1, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbz(temp1, labelOp(inst.c)); // no metatable - build.ldrb(temp2, mem(temp1, offsetof(Table, tmcache))); + build.ldrb(temp2, mem(temp1, offsetof(LuaTable, tmcache))); build.tst(temp2, 1 << intOp(inst.b)); // can't use tbz/tbnz because their jump offsets are too short build.b(ConditionA64::NotEqual, labelOp(inst.c)); // Equal = Zero after tst; tmcache caches *absence* of metamethods @@ -1515,7 +1516,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldrb(temp, mem(regOp(inst.a), offsetof(Table, readonly))); + build.ldrb(temp, mem(regOp(inst.a), offsetof(LuaTable, readonly))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1524,7 +1525,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { Label fresh; // used when guard aborts execution or jumps to a VM exit RegisterA64 temp = regs.allocTemp(KindA64::x); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, metatable))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, metatable))); build.cbnz(temp, getTargetLabel(inst.b, fresh)); finalizeTargetLabel(inst.b, fresh); break; @@ -1535,7 +1536,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp = regs.allocTemp(KindA64::x); RegisterA64 tempw = castReg(KindA64::w, temp); build.ldr(temp, mem(rClosure, offsetof(Closure, env))); - build.ldrb(tempw, mem(temp, offsetof(Table, safeenv))); + build.ldrb(tempw, mem(temp, offsetof(LuaTable, safeenv))); build.cbz(tempw, getTargetLabel(inst.a, fresh)); finalizeTargetLabel(inst.a, fresh); break; @@ -1546,7 +1547,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label& fail = getTargetLabel(inst.c, fresh); RegisterA64 temp = regs.allocTemp(KindA64::w); - build.ldr(temp, mem(regOp(inst.a), offsetof(Table, sizearray))); + build.ldr(temp, mem(regOp(inst.a), offsetof(LuaTable, sizearray))); if (inst.b.kind == IrOpKind::Inst) { @@ -1773,7 +1774,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) size_t spills = regs.spill(build, index, {reg}); build.mov(x1, reg); build.mov(x0, rState); - build.add(x2, x1, uint16_t(offsetof(Table, gclist))); + build.add(x2, x1, uint16_t(offsetof(LuaTable, gclist))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierback))); build.blr(x3); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index c1a84c8e..e0ece9da 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -4,6 +4,7 @@ #include "Luau/DenseHash.h" #include "Luau/IrData.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "Luau/IrCallWrapperX64.h" @@ -159,13 +160,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(dwordReg(inst.regX64), regOp(inst.b)); build.shl(dwordReg(inst.regX64), kTValueSizeLog2); - build.add(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.add(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); } else if (inst.b.kind == IrOpKind::Constant) { inst.regX64 = regs.allocRegOrReuse(SizeX64::qword, index, {inst.a}); - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, array)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, array)]); if (intOp(inst.b) != 0) build.lea(inst.regX64, addr[inst.regX64 + intOp(inst.b) * sizeof(TValue)]); @@ -193,9 +194,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(Table, node)]); + build.mov(inst.regX64, qword[regOp(inst.a) + offsetof(LuaTable, node)]); build.mov(dwordReg(tmp.reg), 1); - build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(Table, lsizenode)]); + build.mov(byteReg(shiftTmp.reg), byte[regOp(inst.a) + offsetof(LuaTable, lsizenode)]); build.shl(dwordReg(tmp.reg), byteReg(shiftTmp.reg)); build.dec(dwordReg(tmp.reg)); build.and_(dwordReg(tmp.reg), uintOp(inst.b)); @@ -954,13 +955,13 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { ScopedRegX64 tmp{regs, SizeX64::qword}; - build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(Table, metatable)]); + build.mov(tmp.reg, qword[regOp(inst.a) + offsetof(LuaTable, metatable)]); regs.freeLastUseReg(function.instOp(inst.a), index); // Release before the call if it's the last use build.test(tmp.reg, tmp.reg); build.jcc(ConditionX64::Zero, labelOp(inst.c)); // No metatable - build.test(byte[tmp.reg + offsetof(Table, tmcache)], 1 << intOp(inst.b)); + build.test(byte[tmp.reg + offsetof(LuaTable, tmcache)], 1 << intOp(inst.b)); build.jcc(ConditionX64::NotZero, labelOp(inst.c)); // No tag method ScopedRegX64 tmp2{regs, SizeX64::qword}; @@ -1320,11 +1321,11 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; } case IrCmd::CHECK_READONLY: - build.cmp(byte[regOp(inst.a) + offsetof(Table, readonly)], 0); + build.cmp(byte[regOp(inst.a) + offsetof(LuaTable, readonly)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_NO_METATABLE: - build.cmp(qword[regOp(inst.a) + offsetof(Table, metatable)], 0); + build.cmp(qword[regOp(inst.a) + offsetof(LuaTable, metatable)], 0); jumpOrAbortOnUndef(ConditionX64::NotEqual, inst.b, next); break; case IrCmd::CHECK_SAFE_ENV: @@ -1333,16 +1334,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.mov(tmp.reg, sClosure); build.mov(tmp.reg, qword[tmp.reg + offsetof(Closure, env)]); - build.cmp(byte[tmp.reg + offsetof(Table, safeenv)], 0); + build.cmp(byte[tmp.reg + offsetof(LuaTable, safeenv)], 0); jumpOrAbortOnUndef(ConditionX64::Equal, inst.a, next); break; } case IrCmd::CHECK_ARRAY_SIZE: if (inst.b.kind == IrOpKind::Inst) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], regOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], regOp(inst.b)); else if (inst.b.kind == IrOpKind::Constant) - build.cmp(dword[regOp(inst.a) + offsetof(Table, sizearray)], intOp(inst.b)); + build.cmp(dword[regOp(inst.a) + offsetof(LuaTable, sizearray)], intOp(inst.b)); else CODEGEN_ASSERT(!"Unsupported instruction form"); diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index bd2147a7..15a306c9 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -2,8 +2,8 @@ #include "IrRegAllocA64.h" #include "Luau/AssemblyBuilderA64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "BitUtils.h" #include "EmitCommonA64.h" diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index d647484b..64625868 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -1,8 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrRegAllocX64.h" -#include "Luau/CodeGen.h" #include "Luau/IrUtils.h" +#include "Luau/LoweringStats.h" #include "EmitCommonX64.h" diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 62829766..d15d57e2 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -3,7 +3,7 @@ #include "Luau/Bytecode.h" #include "Luau/BytecodeUtils.h" -#include "Luau/CodeGen.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "Luau/IrUtils.h" diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 74bbc6d7..02d19e49 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/IrUtils.h" +#include "Luau/CodeGenOptions.h" #include "Luau/IrBuilder.h" #include "BitUtils.h" @@ -9,6 +10,9 @@ #include "lua.h" #include "lnumutils.h" +#include +#include + #include #include diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 941db252..b4f74132 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,25 +44,25 @@ struct NativeContext void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; - void (*luaV_getimport)(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; + void (*luaV_getimport)(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; void (*luaV_concat)(lua_State* L, int total, int last) = nullptr; - int (*luaH_getn)(Table* t) = nullptr; - Table* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; - Table* (*luaH_clone)(lua_State* L, Table* tt) = nullptr; - void (*luaH_resizearray)(lua_State* L, Table* t, int nasize) = nullptr; - TValue* (*luaH_setnum)(lua_State* L, Table* t, int key); + int (*luaH_getn)(LuaTable* t) = nullptr; + LuaTable* (*luaH_new)(lua_State* L, int narray, int lnhash) = nullptr; + LuaTable* (*luaH_clone)(lua_State* L, LuaTable* tt) = nullptr; + void (*luaH_resizearray)(lua_State* L, LuaTable* t, int nasize) = nullptr; + TValue* (*luaH_setnum)(lua_State* L, LuaTable* t, int key); - void (*luaC_barriertable)(lua_State* L, Table* t, GCObject* v) = nullptr; + void (*luaC_barriertable)(lua_State* L, LuaTable* t, GCObject* v) = nullptr; void (*luaC_barrierf)(lua_State* L, GCObject* o, GCObject* v) = nullptr; void (*luaC_barrierback)(lua_State* L, GCObject* o, GCObject** gclist) = nullptr; size_t (*luaC_step)(lua_State* L, bool assist) = nullptr; void (*luaF_close)(lua_State* L, StkId level) = nullptr; UpVal* (*luaF_findupval)(lua_State* L, StkId level) = nullptr; - Closure* (*luaF_newLclosure)(lua_State* L, int nelems, Table* e, Proto* p) = nullptr; + Closure* (*luaF_newLclosure)(lua_State* L, int nelems, LuaTable* e, Proto* p) = nullptr; - const TValue* (*luaT_gettm)(Table* events, TMS event, TString* ename) = nullptr; + const TValue* (*luaT_gettm)(LuaTable* events, TMS event, TString* ename) = nullptr; const TString* (*luaT_objtypenamestr)(lua_State* L, const TValue* o) = nullptr; double (*libm_exp)(double) = nullptr; @@ -87,8 +87,8 @@ struct NativeContext double (*libm_modf)(double, double*) = nullptr; // Helper functions - bool (*forgLoopTableIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; - bool (*forgLoopNodeIter)(lua_State* L, Table* h, int index, TValue* ra) = nullptr; + bool (*forgLoopTableIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; + bool (*forgLoopNodeIter)(lua_State* L, LuaTable* h, int index, TValue* ra) = nullptr; bool (*forgLoopNonTableFallback)(lua_State* L, int insnA, int aux) = nullptr; void (*forgPrepXnextFallback)(lua_State* L, TValue* ra, int pc) = nullptr; Closure* (*callProlog)(lua_State* L, TValue* ra, StkId argtop, int nresults) = nullptr; diff --git a/Sources.cmake b/Sources.cmake index 14bfe6a0..306d1530 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -78,6 +78,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/CodeBlockUnwind.h CodeGen/include/Luau/CodeGen.h CodeGen/include/Luau/CodeGenCommon.h + CodeGen/include/Luau/CodeGenOptions.h CodeGen/include/Luau/ConditionA64.h CodeGen/include/Luau/ConditionX64.h CodeGen/include/Luau/IrAnalysis.h @@ -89,6 +90,7 @@ target_sources(Luau.CodeGen PRIVATE CodeGen/include/Luau/IrUtils.h CodeGen/include/Luau/IrVisitUseDef.h CodeGen/include/Luau/Label.h + CodeGen/include/Luau/LoweringStats.h CodeGen/include/Luau/NativeProtoExecData.h CodeGen/include/Luau/OperandX64.h CodeGen/include/Luau/OptimizeConstProp.h diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 052d8c82..1a8af74d 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -64,7 +64,7 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2024 Roblox Corporation $\n" ts->atom = L->global->cb.useratom ? L->global->cb.useratom(ts->data, ts->len) : -1; \ } -static Table* getcurrenv(lua_State* L) +static LuaTable* getcurrenv(lua_State* L) { if (L->ci == L->base_ci) // no enclosing function? return L->gt; // use global table as environment @@ -762,7 +762,7 @@ void lua_setreadonly(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); api_check(L, t != hvalue(registry(L))); t->readonly = bool(enabled); } @@ -771,7 +771,7 @@ int lua_getreadonly(lua_State* L, int objindex) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); int res = t->readonly; return res; } @@ -780,14 +780,14 @@ void lua_setsafeenv(lua_State* L, int objindex, int enabled) { const TValue* o = index2addr(L, objindex); api_check(L, ttistable(o)); - Table* t = hvalue(o); + LuaTable* t = hvalue(o); t->safeenv = bool(enabled); } int lua_getmetatable(lua_State* L, int objindex) { luaC_threadbarrier(L); - Table* mt = NULL; + LuaTable* mt = NULL; const TValue* obj = index2addr(L, objindex); switch (ttype(obj)) { @@ -894,7 +894,7 @@ int lua_setmetatable(lua_State* L, int objindex) api_checknelems(L, 1); TValue* obj = index2addr(L, objindex); api_checkvalidindex(L, obj); - Table* mt = NULL; + LuaTable* mt = NULL; if (!ttisnil(L->top - 1)) { api_check(L, ttistable(L->top - 1)); @@ -1214,7 +1214,7 @@ int lua_rawiter(lua_State* L, int idx, int iter) api_check(L, ttistable(t)); api_check(L, iter >= 0); - Table* h = hvalue(t); + LuaTable* h = hvalue(t); int sizearray = h->sizearray; // first we advance iter through the array portion @@ -1293,7 +1293,7 @@ void* lua_newuserdatataggedwithmetatable(lua_State* L, size_t sz, int tag) // currently, we always allocate unmarked objects, so forward barrier can be skipped LUAU_ASSERT(!isblack(obj2gco(u))); - Table* h = L->global->udatamt[tag]; + LuaTable* h = L->global->udatamt[tag]; api_check(L, h != nullptr); u->metatable = h; @@ -1394,7 +1394,7 @@ int lua_ref(lua_State* L, int idx) StkId p = index2addr(L, idx); if (!ttisnil(p)) { - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); if (g->registryfree != 0) { // reuse existing slot @@ -1421,7 +1421,7 @@ void lua_unref(lua_State* L, int ref) return; global_State* g = L->global; - Table* reg = hvalue(registry(L)); + LuaTable* reg = hvalue(registry(L)); TValue* slot = luaH_setnum(L, reg, ref); setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable g->registryfree = ref; @@ -1462,7 +1462,7 @@ void lua_getuserdatametatable(lua_State* L, int tag) api_check(L, unsigned(tag) < LUA_UTAG_LIMIT); luaC_threadbarrier(L); - if (Table* h = L->global->udatamt[tag]) + if (LuaTable* h = L->global->udatamt[tag]) { sethvalue(L, L->top, h); } @@ -1510,7 +1510,7 @@ void lua_cleartable(lua_State* L, int idx) { StkId t = index2addr(L, idx); api_check(L, ttistable(t)); - Table* tt = hvalue(t); + LuaTable* tt = hvalue(t); if (tt->readonly) luaG_readonlyerror(L); luaH_clear(tt); diff --git a/VM/src/lbuflib.cpp b/VM/src/lbuflib.cpp index 179e4171..643d3a9c 100644 --- a/VM/src/lbuflib.cpp +++ b/VM/src/lbuflib.cpp @@ -10,7 +10,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods) +LUAU_FASTFLAGVARIABLE(LuauBufferBitMethods2) // while C API returns 'size_t' for binary compatibility in case of future extensions, // in the current implementation, length and offset are limited to 31 bits @@ -370,7 +370,7 @@ static const luaL_Reg bufferlib[] = { int luaopen_buffer(lua_State* L) { - luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods ? bufferlib : bufferlib_DEPRECATED); + luaL_register(L, LUA_BUFFERLIBNAME, FFlag::LuauBufferBitMethods2 ? bufferlib : bufferlib_DEPRECATED); return 1; } diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 92234a0f..74702fe5 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -1000,7 +1000,7 @@ static int luauF_rawset(lua_State* L, StkId res, TValue* arg0, int nresults, Stk else if (ttisvector(key) && luai_vecisnan(vvalue(key))) return -1; - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1017,7 +1017,7 @@ static int luauF_tinsert(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams == 2 && nresults <= 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly) return -1; @@ -1034,7 +1034,7 @@ static int luauF_tunpack(lua_State* L, StkId res, TValue* arg0, int nresults, St { if (nparams >= 1 && nresults < 0 && ttistable(arg0)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); int n = -1; if (nparams == 1) @@ -1196,7 +1196,7 @@ static int luauF_rawlen(lua_State* L, StkId res, TValue* arg0, int nresults, Stk { if (ttistable(arg0)) { - Table* h = hvalue(arg0); + LuaTable* h = hvalue(arg0); setnvalue(res, double(luaH_getn(h))); return 1; } @@ -1240,7 +1240,7 @@ static int luauF_getmetatable(lua_State* L, StkId res, TValue* arg0, int nresult { if (nparams >= 1 && nresults <= 1) { - Table* mt = NULL; + LuaTable* mt = NULL; if (ttistable(arg0)) mt = hvalue(arg0)->metatable; else if (ttisuserdata(arg0)) @@ -1275,11 +1275,11 @@ static int luauF_setmetatable(lua_State* L, StkId res, TValue* arg0, int nresult // note: setmetatable(_, nil) is rare so we use fallback for it to optimize the fast path if (nparams >= 2 && nresults <= 1 && ttistable(arg0) && ttistable(args)) { - Table* t = hvalue(arg0); + LuaTable* t = hvalue(arg0); if (t->readonly || t->metatable != NULL) return -1; // note: overwriting non-null metatable is very rare but it requires __metatable check - Table* mt = hvalue(args); + LuaTable* mt = hvalue(args); t->metatable = mt; luaC_objbarrier(L, t, mt); diff --git a/VM/src/lfunc.cpp b/VM/src/lfunc.cpp index 2a1e45c4..b172d0ad 100644 --- a/VM/src/lfunc.cpp +++ b/VM/src/lfunc.cpp @@ -55,7 +55,7 @@ Proto* luaF_newproto(lua_State* L) return f; } -Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) +Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p) { Closure* c = luaM_newgco(L, Closure, sizeLclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); @@ -70,7 +70,7 @@ Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p) return c; } -Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e) +Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e) { Closure* c = luaM_newgco(L, Closure, sizeCclosure(nelems), L->activememcat); luaC_init(L, c, LUA_TFUNCTION); diff --git a/VM/src/lfunc.h b/VM/src/lfunc.h index 679836e7..453cf581 100644 --- a/VM/src/lfunc.h +++ b/VM/src/lfunc.h @@ -8,8 +8,8 @@ #define sizeLclosure(n) (offsetof(Closure, l.uprefs) + sizeof(TValue) * (n)) LUAI_FUNC Proto* luaF_newproto(lua_State* L); -LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, Table* e, Proto* p); -LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, Table* e); +LUAI_FUNC Closure* luaF_newLclosure(lua_State* L, int nelems, LuaTable* e, Proto* p); +LUAI_FUNC Closure* luaF_newCclosure(lua_State* L, int nelems, LuaTable* e); LUAI_FUNC UpVal* luaF_findupval(lua_State* L, StkId level); LUAI_FUNC void luaF_close(lua_State* L, StkId level); LUAI_FUNC void luaF_closeupval(lua_State* L, UpVal* uv, bool dead); diff --git a/VM/src/lgc.cpp b/VM/src/lgc.cpp index d9843ddc..c5e16e43 100644 --- a/VM/src/lgc.cpp +++ b/VM/src/lgc.cpp @@ -244,7 +244,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } case LUA_TUSERDATA: { - Table* mt = gco2u(o)->metatable; + LuaTable* mt = gco2u(o)->metatable; gray2black(o); // udata are never gray if (mt) markobject(g, mt); @@ -292,7 +292,7 @@ static void reallymarkobject(global_State* g, GCObject* o) } } -static const char* gettablemode(global_State* g, Table* h) +static const char* gettablemode(global_State* g, LuaTable* h) { const TValue* mode = gfasttm(g, h->metatable, TM_MODE); @@ -302,13 +302,13 @@ static const char* gettablemode(global_State* g, Table* h) return NULL; } -static int traversetable(global_State* g, Table* h) +static int traversetable(global_State* g, LuaTable* h) { int i; int weakkey = 0; int weakvalue = 0; if (h->metatable) - markobject(g, cast_to(Table*, h->metatable)); + markobject(g, cast_to(LuaTable*, h->metatable)); // is there a weak mode? if (const char* modev = gettablemode(g, h)) @@ -459,11 +459,11 @@ static size_t propagatemark(global_State* g) { case LUA_TTABLE: { - Table* h = gco2h(o); + LuaTable* h = gco2h(o); g->gray = h->gclist; if (traversetable(g, h)) // table is weak? black2gray(o); // keep it gray - return sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + return sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); } case LUA_TFUNCTION: { @@ -553,8 +553,8 @@ static size_t cleartable(lua_State* L, GCObject* l) size_t work = 0; while (l) { - Table* h = gco2h(l); - work += sizeof(Table) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); + LuaTable* h = gco2h(l); + work += sizeof(LuaTable) + sizeof(TValue) * h->sizearray + sizeof(LuaNode) * sizenode(h); int i = h->sizearray; while (i--) @@ -1155,7 +1155,7 @@ void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v) makewhite(g, o); // mark as white just to avoid other barriers } -void luaC_barriertable(lua_State* L, Table* t, GCObject* v) +void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v) { global_State* g = L->global; GCObject* o = obj2gco(t); diff --git a/VM/src/lgc.h b/VM/src/lgc.h index 722de9d1..683542b6 100644 --- a/VM/src/lgc.h +++ b/VM/src/lgc.h @@ -131,7 +131,7 @@ LUAI_FUNC void luaC_fullgc(lua_State* L); LUAI_FUNC void luaC_initobj(lua_State* L, GCObject* o, uint8_t tt); LUAI_FUNC void luaC_upvalclosed(lua_State* L, UpVal* uv); LUAI_FUNC void luaC_barrierf(lua_State* L, GCObject* o, GCObject* v); -LUAI_FUNC void luaC_barriertable(lua_State* L, Table* t, GCObject* v); +LUAI_FUNC void luaC_barriertable(lua_State* L, LuaTable* t, GCObject* v); LUAI_FUNC void luaC_barrierback(lua_State* L, GCObject* o, GCObject** gclist); LUAI_FUNC void luaC_validate(lua_State* L); LUAI_FUNC void luaC_dump(lua_State* L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 768561cb..7a47ab86 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -34,7 +34,7 @@ static void validateref(global_State* g, GCObject* f, TValue* v) } } -static void validatetable(global_State* g, Table* h) +static void validatetable(global_State* g, LuaTable* h) { int sizenode = 1 << h->lsizenode; @@ -290,9 +290,9 @@ static void dumpstring(FILE* f, TString* ts) fprintf(f, "\"}"); } -static void dumptable(FILE* f, Table* h) +static void dumptable(FILE* f, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); fprintf(f, "{\"type\":\"table\",\"cat\":%d,\"size\":%d", h->memcat, int(size)); @@ -654,9 +654,9 @@ static void enumstring(EnumContext* ctx, TString* ts) enumnode(ctx, obj2gco(ts), ts->len, NULL); } -static void enumtable(EnumContext* ctx, Table* h) +static void enumtable(EnumContext* ctx, LuaTable* h) { - size_t size = sizeof(Table) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); + size_t size = sizeof(LuaTable) + (h->node == &luaH_dummynode ? 0 : sizenode(h) * sizeof(LuaNode)) + h->sizearray * sizeof(TValue); // Provide a name for a special registry table enumnode(ctx, obj2gco(h), size, h == hvalue(registry(ctx->L)) ? "registry" : NULL); @@ -754,7 +754,7 @@ static void enumudata(EnumContext* ctx, Udata* u) { const char* name = NULL; - if (Table* h = u->metatable) + if (LuaTable* h = u->metatable) { if (h->node != &luaH_dummynode) { diff --git a/VM/src/lmem.cpp b/VM/src/lmem.cpp index 6fe82b30..0738840b 100644 --- a/VM/src/lmem.cpp +++ b/VM/src/lmem.cpp @@ -121,7 +121,7 @@ static_assert(sizeof(LuaNode) == ABISWITCH(32, 32, 32), "size mismatch for table static_assert(offsetof(TString, data) == ABISWITCH(24, 20, 20), "size mismatch for string header"); static_assert(offsetof(Udata, data) == ABISWITCH(16, 16, 12), "size mismatch for userdata header"); -static_assert(sizeof(Table) == ABISWITCH(48, 32, 32), "size mismatch for table header"); +static_assert(sizeof(LuaTable) == ABISWITCH(48, 32, 32), "size mismatch for table header"); static_assert(offsetof(Buffer, data) == ABISWITCH(8, 8, 8), "size mismatch for buffer header"); const size_t kSizeClasses = LUA_SIZECLASSES; diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 18c69641..6719faaf 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -263,7 +263,7 @@ typedef struct Udata int len; - struct Table* metatable; + struct LuaTable* metatable; union { @@ -390,7 +390,7 @@ typedef struct Closure uint8_t preload; GCObject* gclist; - struct Table* env; + struct LuaTable* env; union { @@ -454,7 +454,7 @@ typedef struct LuaNode } // clang-format off -typedef struct Table +typedef struct LuaTable { CommonHeader; @@ -473,11 +473,11 @@ typedef struct Table }; - struct Table* metatable; + struct LuaTable* metatable; TValue* array; // array part LuaNode* node; GCObject* gclist; -} Table; +} LuaTable; // clang-format on /* diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 3f4f9425..ad162391 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -198,7 +198,7 @@ typedef struct global_State struct lua_State* mainthread; UpVal uvhead; // head of double-linked list of all open upvalues - struct Table* mt[LUA_T_COUNT]; // metatables for basic types + struct LuaTable* mt[LUA_T_COUNT]; // metatables for basic types TString* ttname[LUA_T_COUNT]; // names for basic types TString* tmname[TM_N]; // array with tag-method names @@ -217,7 +217,7 @@ typedef struct global_State lua_ExecutionCallbacks ecb; void (*udatagc[LUA_UTAG_LIMIT])(lua_State*, void*); // for each userdata tag, a gc callback to be called immediately before freeing memory - Table* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata + LuaTable* udatamt[LUA_UTAG_LIMIT]; // metatables for tagged userdata TString* lightuserdataname[LUA_LUTAG_LIMIT]; // names for tagged lightuserdata @@ -266,7 +266,7 @@ struct lua_State int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? - Table* gt; // table of globals + LuaTable* gt; // table of globals UpVal* openupval; // list of open upvalues in this stack GCObject* gclist; @@ -285,7 +285,7 @@ union GCObject struct TString ts; struct Udata u; struct Closure cl; - struct Table h; + struct LuaTable h; struct Proto p; struct UpVal uv; struct lua_State th; // thread diff --git a/VM/src/ltable.cpp b/VM/src/ltable.cpp index dafb2b3f..ee5ae7ec 100644 --- a/VM/src/ltable.cpp +++ b/VM/src/ltable.cpp @@ -58,7 +58,7 @@ const LuaNode luaH_dummynode = { #define hashstr(t, str) hashpow2(t, (str)->hash) #define hashboolean(t, p) hashpow2(t, p) -static LuaNode* hashpointer(const Table* t, const void* p) +static LuaNode* hashpointer(const LuaTable* t, const void* p) { // we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway unsigned int h = unsigned(uintptr_t(p)); @@ -73,7 +73,7 @@ static LuaNode* hashpointer(const Table* t, const void* p) return hashpow2(t, h); } -static LuaNode* hashnum(const Table* t, double n) +static LuaNode* hashnum(const LuaTable* t, double n) { static_assert(sizeof(double) == sizeof(unsigned int) * 2, "expected a 8-byte double"); unsigned int i[2]; @@ -99,7 +99,7 @@ static LuaNode* hashnum(const Table* t, double n) return hashpow2(t, h2); } -static LuaNode* hashvec(const Table* t, const float* v) +static LuaNode* hashvec(const LuaTable* t, const float* v) { unsigned int i[LUA_VECTOR_SIZE]; memcpy(i, v, sizeof(i)); @@ -130,7 +130,7 @@ static LuaNode* hashvec(const Table* t, const float* v) ** returns the `main' position of an element in a table (that is, the index ** of its hash value) */ -static LuaNode* mainposition(const Table* t, const TValue* key) +static LuaNode* mainposition(const LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -166,7 +166,7 @@ static int arrayindex(double key) ** elements in the array part, then elements in the hash part. The ** beginning of a traversal is signalled by -1. */ -static int findindex(lua_State* L, Table* t, StkId key) +static int findindex(lua_State* L, LuaTable* t, StkId key) { int i; if (ttisnil(key)) @@ -194,7 +194,7 @@ static int findindex(lua_State* L, Table* t, StkId key) } } -int luaH_next(lua_State* L, Table* t, StkId key) +int luaH_next(lua_State* L, LuaTable* t, StkId key) { int i = findindex(L, t, key); // find original element for (i++; i < t->sizearray; i++) @@ -270,7 +270,7 @@ static int countint(double key, int* nums) return 0; } -static int numusearray(const Table* t, int* nums) +static int numusearray(const LuaTable* t, int* nums) { int lg; int ttlg; // 2^lg @@ -298,7 +298,7 @@ static int numusearray(const Table* t, int* nums) return ause; } -static int numusehash(const Table* t, int* nums, int* pnasize) +static int numusehash(const LuaTable* t, int* nums, int* pnasize) { int totaluse = 0; // total number of elements int ause = 0; // summation of `nums' @@ -317,7 +317,7 @@ static int numusehash(const Table* t, int* nums, int* pnasize) return totaluse; } -static void setarrayvector(lua_State* L, Table* t, int size) +static void setarrayvector(lua_State* L, LuaTable* t, int size) { if (size > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -328,7 +328,7 @@ static void setarrayvector(lua_State* L, Table* t, int size) t->sizearray = size; } -static void setnodevector(lua_State* L, Table* t, int size) +static void setnodevector(lua_State* L, LuaTable* t, int size) { int lsize; if (size == 0) @@ -357,9 +357,9 @@ static void setnodevector(lua_State* L, Table* t, int size) t->lastfree = size; // all positions are free } -static TValue* newkey(lua_State* L, Table* t, const TValue* key); +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key); -static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) +static TValue* arrayornewkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnumber(key)) { @@ -373,7 +373,7 @@ static TValue* arrayornewkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -static void resize(lua_State* L, Table* t, int nasize, int nhsize) +static void resize(lua_State* L, LuaTable* t, int nasize, int nhsize) { if (nasize > MAXSIZE || nhsize > MAXSIZE) luaG_runerror(L, "table overflow"); @@ -424,7 +424,7 @@ static void resize(lua_State* L, Table* t, int nasize, int nhsize) luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); // free old array } -static int adjustasize(Table* t, int size, const TValue* ek) +static int adjustasize(LuaTable* t, int size, const TValue* ek) { bool tbound = t->node != dummynode || size < t->sizearray; int ekindex = ek && ttisnumber(ek) ? arrayindex(nvalue(ek)) : -1; @@ -434,19 +434,19 @@ static int adjustasize(Table* t, int size, const TValue* ek) return size; } -void luaH_resizearray(lua_State* L, Table* t, int nasize) +void luaH_resizearray(lua_State* L, LuaTable* t, int nasize) { int nsize = (t->node == dummynode) ? 0 : sizenode(t); int asize = adjustasize(t, nasize, NULL); resize(L, t, asize, nsize); } -void luaH_resizehash(lua_State* L, Table* t, int nhsize) +void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize) { resize(L, t, t->sizearray, nhsize); } -static void rehash(lua_State* L, Table* t, const TValue* ek) +static void rehash(lua_State* L, LuaTable* t, const TValue* ek) { int nums[MAXBITS + 1]; // nums[i] = number of keys between 2^(i-1) and 2^i for (int i = 0; i <= MAXBITS; i++) @@ -491,9 +491,9 @@ static void rehash(lua_State* L, Table* t, const TValue* ek) ** }============================================================= */ -Table* luaH_new(lua_State* L, int narray, int nhash) +LuaTable* luaH_new(lua_State* L, int narray, int nhash) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = NULL; t->tmcache = cast_byte(~0); @@ -512,16 +512,16 @@ Table* luaH_new(lua_State* L, int narray, int nhash) return t; } -void luaH_free(lua_State* L, Table* t, lua_Page* page) +void luaH_free(lua_State* L, LuaTable* t, lua_Page* page) { if (t->node != dummynode) luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat); if (t->array) luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat); - luaM_freegco(L, t, sizeof(Table), t->memcat, page); + luaM_freegco(L, t, sizeof(LuaTable), t->memcat, page); } -static LuaNode* getfreepos(Table* t) +static LuaNode* getfreepos(LuaTable* t) { while (t->lastfree > 0) { @@ -541,7 +541,7 @@ static LuaNode* getfreepos(Table* t) ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ -static TValue* newkey(lua_State* L, Table* t, const TValue* key) +static TValue* newkey(lua_State* L, LuaTable* t, const TValue* key) { // enforce boundary invariant if (ttisnumber(key) && nvalue(key) == t->sizearray + 1) @@ -601,7 +601,7 @@ static TValue* newkey(lua_State* L, Table* t, const TValue* key) /* ** search function for integers */ -const TValue* luaH_getnum(Table* t, int key) +const TValue* luaH_getnum(LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -627,7 +627,7 @@ const TValue* luaH_getnum(Table* t, int key) /* ** search function for strings */ -const TValue* luaH_getstr(Table* t, TString* key) +const TValue* luaH_getstr(LuaTable* t, TString* key) { LuaNode* n = hashstr(t, key); for (;;) @@ -644,7 +644,7 @@ const TValue* luaH_getstr(Table* t, TString* key) /* ** main search function */ -const TValue* luaH_get(Table* t, const TValue* key) +const TValue* luaH_get(LuaTable* t, const TValue* key) { switch (ttype(key)) { @@ -677,7 +677,7 @@ const TValue* luaH_get(Table* t, const TValue* key) } } -TValue* luaH_set(lua_State* L, Table* t, const TValue* key) +TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key) { const TValue* p = luaH_get(t, key); invalidateTMcache(t); @@ -687,7 +687,7 @@ TValue* luaH_set(lua_State* L, Table* t, const TValue* key) return luaH_newkey(L, t, key); } -TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) +TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key) { if (ttisnil(key)) luaG_runerror(L, "table index is nil"); @@ -698,7 +698,7 @@ TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key) return newkey(L, t, key); } -TValue* luaH_setnum(lua_State* L, Table* t, int key) +TValue* luaH_setnum(lua_State* L, LuaTable* t, int key) { // (1 <= key && key <= t->sizearray) if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray)) @@ -715,7 +715,7 @@ TValue* luaH_setnum(lua_State* L, Table* t, int key) } } -TValue* luaH_setstr(lua_State* L, Table* t, TString* key) +TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key) { const TValue* p = luaH_getstr(t, key); invalidateTMcache(t); @@ -729,7 +729,7 @@ TValue* luaH_setstr(lua_State* L, Table* t, TString* key) } } -static int updateaboundary(Table* t, int boundary) +static int updateaboundary(LuaTable* t, int boundary) { if (boundary < t->sizearray && ttisnil(&t->array[boundary - 1])) { @@ -752,7 +752,7 @@ static int updateaboundary(Table* t, int boundary) ** Try to find a boundary in table `t'. A `boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ -int luaH_getn(Table* t) +int luaH_getn(LuaTable* t) { int boundary = getaboundary(t); @@ -793,9 +793,9 @@ int luaH_getn(Table* t) } } -Table* luaH_clone(lua_State* L, Table* tt) +LuaTable* luaH_clone(lua_State* L, LuaTable* tt) { - Table* t = luaM_newgco(L, Table, sizeof(Table), L->activememcat); + LuaTable* t = luaM_newgco(L, LuaTable, sizeof(LuaTable), L->activememcat); luaC_init(L, t, LUA_TTABLE); t->metatable = tt->metatable; t->tmcache = tt->tmcache; @@ -830,7 +830,7 @@ Table* luaH_clone(lua_State* L, Table* tt) return t; } -void luaH_clear(Table* tt) +void luaH_clear(LuaTable* tt) { // clear array part for (int i = 0; i < tt->sizearray; ++i) diff --git a/VM/src/ltable.h b/VM/src/ltable.h index 021f21bf..50d1e643 100644 --- a/VM/src/ltable.h +++ b/VM/src/ltable.h @@ -14,21 +14,21 @@ // reset cache of absent metamethods, cache is updated in luaT_gettm #define invalidateTMcache(t) t->tmcache = 0 -LUAI_FUNC const TValue* luaH_getnum(Table* t, int key); -LUAI_FUNC TValue* luaH_setnum(lua_State* L, Table* t, int key); -LUAI_FUNC const TValue* luaH_getstr(Table* t, TString* key); -LUAI_FUNC TValue* luaH_setstr(lua_State* L, Table* t, TString* key); -LUAI_FUNC const TValue* luaH_get(Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_set(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC TValue* luaH_newkey(lua_State* L, Table* t, const TValue* key); -LUAI_FUNC Table* luaH_new(lua_State* L, int narray, int lnhash); -LUAI_FUNC void luaH_resizearray(lua_State* L, Table* t, int nasize); -LUAI_FUNC void luaH_resizehash(lua_State* L, Table* t, int nhsize); -LUAI_FUNC void luaH_free(lua_State* L, Table* t, struct lua_Page* page); -LUAI_FUNC int luaH_next(lua_State* L, Table* t, StkId key); -LUAI_FUNC int luaH_getn(Table* t); -LUAI_FUNC Table* luaH_clone(lua_State* L, Table* tt); -LUAI_FUNC void luaH_clear(Table* tt); +LUAI_FUNC const TValue* luaH_getnum(LuaTable* t, int key); +LUAI_FUNC TValue* luaH_setnum(lua_State* L, LuaTable* t, int key); +LUAI_FUNC const TValue* luaH_getstr(LuaTable* t, TString* key); +LUAI_FUNC TValue* luaH_setstr(lua_State* L, LuaTable* t, TString* key); +LUAI_FUNC const TValue* luaH_get(LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_set(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC TValue* luaH_newkey(lua_State* L, LuaTable* t, const TValue* key); +LUAI_FUNC LuaTable* luaH_new(lua_State* L, int narray, int lnhash); +LUAI_FUNC void luaH_resizearray(lua_State* L, LuaTable* t, int nasize); +LUAI_FUNC void luaH_resizehash(lua_State* L, LuaTable* t, int nhsize); +LUAI_FUNC void luaH_free(lua_State* L, LuaTable* t, struct lua_Page* page); +LUAI_FUNC int luaH_next(lua_State* L, LuaTable* t, StkId key); +LUAI_FUNC int luaH_getn(LuaTable* t); +LUAI_FUNC LuaTable* luaH_clone(lua_State* L, LuaTable* tt); +LUAI_FUNC void luaH_clear(LuaTable* tt); #define luaH_setslot(L, t, slot, key) (invalidateTMcache(t), (slot == luaO_nilobject ? luaH_newkey(L, t, key) : cast_to(TValue*, slot))) diff --git a/VM/src/ltablib.cpp b/VM/src/ltablib.cpp index 75d9f400..dbe60e4e 100644 --- a/VM/src/ltablib.cpp +++ b/VM/src/ltablib.cpp @@ -53,7 +53,7 @@ static int maxn(lua_State* L) double max = 0; luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); for (int i = 0; i < t->sizearray; i++) { @@ -87,8 +87,8 @@ static int getn(lua_State* L) static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t) { - Table* src = hvalue(L->base + (srct - 1)); - Table* dst = hvalue(L->base + (dstt - 1)); + LuaTable* src = hvalue(L->base + (srct - 1)); + LuaTable* dst = hvalue(L->base + (dstt - 1)); if (dst->readonly) luaG_readonlyerror(L); @@ -213,7 +213,7 @@ static int tmove(lua_State* L) int n = e - f + 1; // number of elements to move luaL_argcheck(L, t <= INT_MAX - n + 1, 4, "destination wrap around"); - Table* dst = hvalue(L->base + (tt - 1)); + LuaTable* dst = hvalue(L->base + (tt - 1)); if (dst->readonly) // also checked in moveelements, but this blocks resizes of r/o tables luaG_readonlyerror(L); @@ -229,7 +229,7 @@ static int tmove(lua_State* L) return 1; } -static void addfield(lua_State* L, luaL_Strbuf* b, int i, Table* t) +static void addfield(lua_State* L, luaL_Strbuf* b, int i, LuaTable* t) { if (t && unsigned(i - 1) < unsigned(t->sizearray) && ttisstring(&t->array[i - 1])) { @@ -253,7 +253,7 @@ static int tconcat(lua_State* L) int i = luaL_optinteger(L, 3, 1); int last = luaL_opt(L, luaL_checkinteger, 4, lua_objlen(L, 1)); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); luaL_Strbuf b; luaL_buffinit(L, &b); @@ -274,7 +274,7 @@ static int tpack(lua_State* L) int n = lua_gettop(L); // number of elements to pack lua_createtable(L, n, 1); // create result table - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); for (int i = 0; i < n; ++i) { @@ -292,7 +292,7 @@ static int tpack(lua_State* L) static int tunpack(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int i = luaL_optinteger(L, 2, 1); int e = luaL_opt(L, luaL_checkinteger, 3, lua_objlen(L, 1)); @@ -335,7 +335,7 @@ static int sort_func(lua_State* L, const TValue* l, const TValue* r) return !l_isfalse(L->top); } -inline void sort_swap(lua_State* L, Table* t, int i, int j) +inline void sort_swap(lua_State* L, LuaTable* t, int i, int j) { TValue* arr = t->array; int n = t->sizearray; @@ -348,7 +348,7 @@ inline void sort_swap(lua_State* L, Table* t, int i, int j) setobj2t(L, &arr[j], &temp); } -inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) +inline int sort_less(lua_State* L, LuaTable* t, int i, int j, SortPredicate pred) { TValue* arr = t->array; int n = t->sizearray; @@ -363,7 +363,7 @@ inline int sort_less(lua_State* L, Table* t, int i, int j, SortPredicate pred) return res; } -static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pred, int root) +static void sort_siftheap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred, int root) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -389,7 +389,7 @@ static void sort_siftheap(lua_State* L, Table* t, int l, int u, SortPredicate pr sort_swap(L, t, l + root, l + lastleft); } -static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) +static void sort_heap(lua_State* L, LuaTable* t, int l, int u, SortPredicate pred) { LUAU_ASSERT(l <= u); int count = u - l + 1; @@ -404,7 +404,7 @@ static void sort_heap(lua_State* L, Table* t, int l, int u, SortPredicate pred) } } -static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredicate pred) +static void sort_rec(lua_State* L, LuaTable* t, int l, int u, int limit, SortPredicate pred) { // sort range [l..u] (inclusive, 0-based) while (l < u) @@ -477,7 +477,7 @@ static void sort_rec(lua_State* L, Table* t, int l, int u, int limit, SortPredic static int tsort(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); int n = luaH_getn(t); if (t->readonly) luaG_readonlyerror(L); @@ -504,7 +504,7 @@ static int tcreate(lua_State* L) if (!lua_isnoneornil(L, 2)) { lua_createtable(L, size, 0); - Table* t = hvalue(L->top - 1); + LuaTable* t = hvalue(L->top - 1); StkId v = L->base + 1; @@ -530,7 +530,7 @@ static int tfind(lua_State* L) if (init < 1) luaL_argerror(L, 3, "index out of range"); - Table* t = hvalue(L->base); + LuaTable* t = hvalue(L->base); StkId v = L->base + 1; for (int i = init;; ++i) @@ -554,7 +554,7 @@ static int tclear(lua_State* L) { luaL_checktype(L, 1, LUA_TTABLE); - Table* tt = hvalue(L->base); + LuaTable* tt = hvalue(L->base); if (tt->readonly) luaG_readonlyerror(L); @@ -587,7 +587,7 @@ static int tclone(lua_State* L) luaL_checktype(L, 1, LUA_TTABLE); luaL_argcheck(L, !luaL_getmetafield(L, 1, "__metatable"), 1, "table has a protected metatable"); - Table* tt = luaH_clone(L, hvalue(L->base)); + LuaTable* tt = luaH_clone(L, hvalue(L->base)); TValue v; sethvalue(L, &v, tt); diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index f38ab80b..f6b0079a 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -86,7 +86,7 @@ void luaT_init(lua_State* L) ** function to be used with macro "fasttm": optimized for absence of ** tag methods. */ -const TValue* luaT_gettm(Table* events, TMS event, TString* ename) +const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename) { const TValue* tm = luaH_getstr(events, ename); LUAU_ASSERT(event <= TM_EQ); @@ -105,7 +105,7 @@ const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event) NB: Tag-methods were replaced by meta-methods in Lua 5.0, but the old names are still around (this function, for example). */ - Table* mt; + LuaTable* mt; switch (ttype(o)) { case LUA_TTABLE: @@ -147,7 +147,7 @@ const TString* luaT_objtypenamestr(lua_State* L, const TValue* o) } // For all types except userdata and table, a global metatable can be set with a global name override - if (Table* mt = L->global->mt[ttype(o)]) + if (LuaTable* mt = L->global->mt[ttype(o)]) { const TValue* type = luaH_getstr(mt, L->global->tmname[TM_TYPE]); diff --git a/VM/src/ltm.h b/VM/src/ltm.h index 7dafd4ed..f3294b64 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -51,7 +51,7 @@ typedef enum LUAI_DATA const char* const luaT_typenames[]; LUAI_DATA const char* const luaT_eventname[]; -LUAI_FUNC const TValue* luaT_gettm(Table* events, TMS event, TString* ename); +LUAI_FUNC const TValue* luaT_gettm(LuaTable* events, TMS event, TString* ename); LUAI_FUNC const TValue* luaT_gettmbyobj(lua_State* L, const TValue* o, TMS event); LUAI_FUNC const TString* luaT_objtypenamestr(lua_State* L, const TValue* o); diff --git a/VM/src/lvm.h b/VM/src/lvm.h index 0b8690be..6989bcee 100644 --- a/VM/src/lvm.h +++ b/VM/src/lvm.h @@ -26,7 +26,7 @@ LUAI_FUNC int luaV_tostring(lua_State* L, StkId obj); LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val); 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(lua_State* L, LuaTable* env, TValue* k, StkId res, 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 e3a310ca..ce07d878 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -330,7 +330,7 @@ reentry: LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -361,7 +361,7 @@ reentry: LUAU_ASSERT(ttisstring(kv)); // fast-path: value is in expected slot - Table* h = cl->env; + LuaTable* h = cl->env; int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -451,7 +451,7 @@ reentry: // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -568,7 +568,7 @@ reentry: // fast-path: built-in table if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -642,7 +642,7 @@ reentry: // fast-path: array lookup if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -672,7 +672,7 @@ reentry: // fast-path: array assign if (ttistable(rb) && ttisnumber(rc)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); double indexd = nvalue(rc); int index = int(indexd); @@ -703,7 +703,7 @@ reentry: // fast-path: array lookup if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable)) { @@ -731,7 +731,7 @@ reentry: // fast-path: array assign if (ttistable(rb)) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (LUAU_LIKELY(unsigned(c) < unsigned(h->sizearray) && !h->metatable && !h->readonly)) { @@ -804,7 +804,7 @@ reentry: if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); // note: we can't use nodemask8 here because we need to query the main position of the table, and 8-bit nodemask8 only works // for predictive lookups LuaNode* n = &h->node[tsvalue(kv)->hash & (sizenode(h) - 1)]; @@ -844,7 +844,7 @@ reentry: } else { - Table* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; + LuaTable* mt = ttisuserdata(rb) ? uvalue(rb)->metatable : L->global->mt[ttype(rb)]; const TValue* tmi = 0; // fast-path: metatable with __namecall @@ -858,7 +858,7 @@ reentry: } else if ((tmi = fasttm(L, mt, TM_INDEX)) && ttistable(tmi)) { - Table* h = hvalue(tmi); + LuaTable* h = hvalue(tmi); int slot = LUAU_INSN_C(insn) & h->nodemask8; LuaNode* n = &h->node[slot]; @@ -2126,7 +2126,7 @@ reentry: // fast-path #1: tables if (LUAU_LIKELY(ttistable(rb))) { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if (fastnotm(h->metatable, TM_LEN)) { @@ -2196,7 +2196,7 @@ reentry: L->top = L->ci->top; } - Table* h = hvalue(ra); + LuaTable* h = hvalue(ra); // TODO: we really don't need this anymore if (!ttistable(ra)) @@ -2281,7 +2281,7 @@ reentry: } else { - Table* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(Table*, NULL); + LuaTable* mt = ttistable(ra) ? hvalue(ra)->metatable : ttisuserdata(ra) ? uvalue(ra)->metatable : cast_to(LuaTable*, NULL); if (const TValue* fn = fasttm(L, mt, TM_ITER)) { @@ -2340,7 +2340,7 @@ reentry: // TODO: remove the table check per guarantee above if (ttisnil(ra) && ttistable(ra + 1)) { - Table* h = hvalue(ra + 1); + LuaTable* h = hvalue(ra + 1); int index = int(reinterpret_cast(pvalue(ra + 2))); int sizearray = h->sizearray; diff --git a/VM/src/lvmload.cpp b/VM/src/lvmload.cpp index aa248fc1..2a3443eb 100644 --- a/VM/src/lvmload.cpp +++ b/VM/src/lvmload.cpp @@ -72,7 +72,7 @@ private: size_t originalThreshold = 0; }; -void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil) +void luaV_getimport(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) { int count = id >> 30; LUAU_ASSERT(count > 0); @@ -141,7 +141,7 @@ static TString* readString(TempBuffer& strings, const char* data, size return id == 0 ? NULL : strings[id - 1]; } -static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id) +static void resolveImportSafe(lua_State* L, LuaTable* env, TValue* k, uint32_t id) { struct ResolveImport { @@ -273,7 +273,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size const ScopedSetGCThreshold pauseGC{L->global, SIZE_MAX}; // env is 0 for current environment and a stack index otherwise - Table* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); + LuaTable* envt = (env == 0) ? L->gt : hvalue(luaA_toobject(L, env)); TString* source = luaS_new(L, chunkname); @@ -481,7 +481,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size case LBC_CONSTANT_TABLE: { int keys = readVarInt(data, size, offset); - Table* h = luaH_new(L, 0, keys); + LuaTable* h = luaH_new(L, 0, keys); for (int i = 0; i < keys; ++i) { int key = readVarInt(data, size, offset); diff --git a/VM/src/lvmutils.cpp b/VM/src/lvmutils.cpp index 0cf9d206..5c49139f 100644 --- a/VM/src/lvmutils.cpp +++ b/VM/src/lvmutils.cpp @@ -101,7 +101,7 @@ void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* res = luaH_get(h, key); // do a primitive get @@ -137,7 +137,7 @@ void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val) const TValue* tm; if (ttistable(t)) { // `t' is a table? - Table* h = hvalue(t); + LuaTable* h = hvalue(t); const TValue* oldval = luaH_get(h, key); @@ -185,7 +185,7 @@ static int call_binTM(lua_State* L, const TValue* p1, const TValue* p2, StkId re return 1; } -static const TValue* get_compTM(lua_State* L, Table* mt1, Table* mt2, TMS event) +static const TValue* get_compTM(lua_State* L, LuaTable* mt1, LuaTable* mt2, TMS event) { const TValue* tm1 = fasttm(L, mt1, event); const TValue* tm2; @@ -533,7 +533,7 @@ void luaV_dolen(lua_State* L, StkId ra, const TValue* rb) { case LUA_TTABLE: { - Table* h = hvalue(rb); + LuaTable* h = hvalue(rb); if ((tm = fasttm(L, h->metatable, TM_LEN)) == NULL) { setnvalue(ra, cast_num(luaH_getn(h))); diff --git a/tests/AnyTypeSummary.test.cpp b/tests/AnyTypeSummary.test.cpp index 6c55a140..7184ef76 100644 --- a/tests/AnyTypeSummary.test.cpp +++ b/tests/AnyTypeSummary.test.cpp @@ -19,6 +19,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(StudioReportLuauAny2) LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) +LUAU_FASTFLAG(LuauAlwaysFillInFunctionCallDiscriminantTypes) +LUAU_FASTFLAG(LuauRemoveNotAnyHack) struct ATSFixture : BuiltinsFixture @@ -410,6 +412,8 @@ TEST_CASE_FIXTURE(ATSFixture, "CannotExtendTable") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -425,7 +429,7 @@ end )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(3, result1); + LUAU_REQUIRE_ERROR_COUNT(1, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); @@ -502,6 +506,8 @@ TEST_CASE_FIXTURE(ATSFixture, "racing_collision_2") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::StudioReportLuauAny2, true}, + {FFlag::LuauAlwaysFillInFunctionCallDiscriminantTypes, true}, + {FFlag::LuauRemoveNotAnyHack, true}, }; fileResolver.source["game/Gui/Modules/A"] = R"( @@ -565,7 +571,7 @@ initialize() )"; CheckResult result1 = frontend.check("game/Gui/Modules/A"); - LUAU_REQUIRE_ERROR_COUNT(5, result1); + LUAU_REQUIRE_ERROR_COUNT(3, result1); ModulePtr module = frontend.moduleResolver.getModule("game/Gui/Modules/A"); diff --git a/tests/AstQuery.test.cpp b/tests/AstQuery.test.cpp index e730171f..702be46b 100644 --- a/tests/AstQuery.test.cpp +++ b/tests/AstQuery.test.cpp @@ -8,8 +8,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauDocumentationAtPosition) - struct DocumentationSymbolFixture : BuiltinsFixture { std::optional getDocSymbol(const std::string& source, Position position) @@ -167,7 +165,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "table_overloaded_function_prop") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; std::optional symbol = getDocSymbol( R"( local x: string = "Foo" @@ -181,7 +178,6 @@ TEST_CASE_FIXTURE(DocumentationSymbolFixture, "string_metatable_method") TEST_CASE_FIXTURE(DocumentationSymbolFixture, "parent_class_method") { - ScopedFastFlag sff{FFlag::LuauDocumentationAtPosition, true}; loadDefinition(R"( declare class Foo function bar(self, x: string): number diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 058a1100..c236b49c 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -3,6 +3,7 @@ #include "Luau/AssemblyBuilderA64.h" #include "Luau/CodeAllocator.h" #include "Luau/CodeBlockUnwind.h" +#include "Luau/CodeGen.h" #include "Luau/UnwindBuilder.h" #include "Luau/UnwindBuilderDwarf2.h" #include "Luau/UnwindBuilderWin.h" diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 9653397d..fdf868df 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -40,8 +40,9 @@ LUAU_FASTFLAG(LuauVectorLibNativeCodegen) LUAU_FASTFLAG(LuauVectorLibNativeDot) LUAU_FASTFLAG(LuauVectorMetatable) LUAU_FASTFLAG(LuauVector2Constructor) -LUAU_FASTFLAG(LuauBufferBitMethods) +LUAU_FASTFLAG(LuauBufferBitMethods2) LUAU_FASTFLAG(LuauCodeGenLimitLiveSlotReuse) +LUAU_FASTFLAG(LuauMathMapDefinition) static lua_CompileOptions defaultOptions() { @@ -655,7 +656,7 @@ TEST_CASE("Basic") TEST_CASE("Buffers") { - ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods, true}; + ScopedFastFlag luauBufferBitMethods{FFlag::LuauBufferBitMethods2, true}; runConformance("buffers.lua"); } @@ -989,7 +990,8 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) TEST_CASE("Types") { ScopedFastFlag luauVector2Constructor{FFlag::LuauVector2Constructor, true}; - ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, false}; // waiting for math.lerp to be added to embedded type definitions + ScopedFastFlag luauMathLerp{FFlag::LuauMathLerp, true}; + ScopedFastFlag luauMathMapDefinition{FFlag::LuauMathMapDefinition, true}; runConformance( "types.lua", diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index c31cfb65..434701b2 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAllowComplexTypesInGenericParams) LUAU_FASTFLAG(LuauErrorRecoveryForTableTypes) LUAU_FASTFLAG(LuauErrorRecoveryForClassNames) +LUAU_FASTFLAG(LuauFixFunctionNameStartPosition) namespace { @@ -3743,5 +3744,27 @@ TEST_CASE_FIXTURE(Fixture, "recover_from_bad_table_type") CHECK_EQ(result.errors.size(), 2); } +TEST_CASE_FIXTURE(Fixture, "function_name_has_correct_start_location") +{ + ScopedFastFlag _{FFlag::LuauFixFunctionNameStartPosition, true}; + AstStatBlock* block = parse(R"( + function simple() + end + + function T:complex() + end + )"); + + REQUIRE_EQ(2, block->body.size); + + const auto function1 = block->body.data[0]->as(); + LUAU_ASSERT(function1); + CHECK_EQ(Position{1, 17}, function1->name->location.begin); + + const auto function2 = block->body.data[1]->as(); + LUAU_ASSERT(function2); + CHECK_EQ(Position{4, 17}, function2->name->location.begin); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index aef63d06..66c397a1 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauUserTypeFunPrintToError) LUAU_FASTFLAG(LuauUserTypeFunExportedAndLocal) LUAU_FASTFLAG(LuauUserTypeFunThreadBuffer) LUAU_FASTFLAG(LuauUserTypeFunUpdateAllEnvs) +LUAU_FASTFLAG(LuauUserTypeFunGenerics) +LUAU_FASTFLAG(LuauUserTypeFunCloneTail) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -1414,4 +1416,479 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result") CHECK(toString(result.errors[3]) == R"(Type function instance t0 is uninhabited)"); } +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return arg +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + ScopedFastFlag luauUserTypeFunCloneTail{FFlag::LuauUserTypeFunCloneTail, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.copy(arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + return types.singleton(types.copy(arg) == arg) +end + +type test = (T) -> (T, U...) + +local function ok(idx: pass): true return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + return types.newfunction({ head = {T} }, { head = {T} }, {T}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T = generics[1] + local f = types.newfunction() + f:setparameters({T, T}); + f:setreturns({T}); + f:setgenerics({T}); + return f +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + assert(T.tag == "generic") + assert(T:name() == "T") + assert(T:ispack() == false) + + local Us, Vs = types.generic("U", true), types.generic("V", true) + assert(Us.tag == "generic") + assert(Us:name() == "U") + assert(Us:ispack() == true) + + local f = types.newfunction() + f:setparameters({T}, Us); + f:setreturns({T}, Vs); + f:setgenerics({T, Us, Vs}); + return f +end + +local function ok(idx: pass<>): (T, U...) -> (T, V...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T, U = types.generic("T"), types.generic("U") + + -- (T) -> () + local func = types.newfunction({ head = {T} }, {}, {T}); + + -- { x: (T) -> (), y: U } + local tbl = types.newtable({ [types.singleton("x")] = func, [types.singleton("y")] = U }) + + -- (T, { x: (T) -> (), y: U }, U) -> () + return types.newfunction({ head = {T, tbl, U } }, {}, {T, U}) +end + +type test = (T, { x: (y: T) -> (), y: U }, U) -> () + +local function ok(idx: pass<>): test return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass() + local T = types.generic("T") + return types.newfunction({ head = {T} }, {}, {types.copy(T)}) +end + +local function ok(idx: pass<>): (T) -> () return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local generics = arg:generics() + local T, U = generics[1], generics[2] + local f = types.newfunction() + f:setparameters({T}); + f:setreturns({U}); + f:setgenerics({T, U}); + return f +end + +local function m(a, b) + return {x = a, y = b} +end + +type test = typeof(m) + +local function ok(idx: pass): (T) -> (U) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (T, U...) -> (T, U...) + +local function ok(idx: pass): (T, U...) -> (T, U...) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_8") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters(p.head, p.tail); + f:setreturns(r.head, r.tail); + f:setgenerics(arg:generics()); + return f +end + +type test = (U...) -> (U...) + +local function ok(idx: pass): (T, T) -> (T) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + + local tbl1 = types.newtable({ [types.singleton("x")] = T }) + local tbl2 = types.newtable({ [types.singleton("x")] = Us }) -- it is possible to have invalid types in-flight + + return types.singleton(tbl1 == tbl2) +end + +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_1") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({}, {}, {Us, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK( + toString(result.errors[0]) == + R"('get' type function errored at runtime: [string "get"]:4: types.newfunction: generic type cannot follow a generic pack)" + ); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_2") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, {}, {}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'T' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_3") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, U = types.generic("T"), types.generic("U") + + -- (U) -> () + local func = types.newfunction({ head = {U} }, {}, {U}); + + -- broken: (T, (U) -> (), U) -> () + return types.newfunction({ head = {T, func, U } }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type 'U' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_4") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {T} }, { tail = Us }, {T, T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_5") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Ts = types.generic("T"), types.generic("T", true) + return types.newfunction({ head = {T} }, { tail = Ts }, {T, Ts}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_6") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ head = {Us} }, {}, {T, Us}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' cannot be placed in a type position)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_7") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function get() + local T, Us = types.generic("T"), types.generic("U", true) + return types.newfunction({ tail = Us }, {}, {T}) +end +local function ok(idx: get<>): false return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' is not in a scope of the active generic function)"); +} + +TEST_CASE_FIXTURE(ClassFixture, "udtf_variadic_api") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunGenerics{FFlag::LuauUserTypeFunGenerics, true}; + + CheckResult result = check(R"( +type function pass(arg) + local p, r = arg:parameters(), arg:returns() + local f = types.newfunction() + f:setparameters({p.tail}, p.head[1]); + f:setreturns({r.tail}, r.head[1]); + return f +end + +type test = (string, ...number) -> (number, ...string) + +local function ok(idx: pass): (number, ...string) -> (string, ...number) return idx end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index d9c4c13e..34e430ea 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTypestateBuiltins2) LUAU_FASTFLAG(LuauStringFormatArityFix) +LUAU_FASTFLAG(LuauStringFormatErrorSuppression) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1586,4 +1587,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_find_should_not_crash") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_should_support_any") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + local x: any = "world" + print(string.format("Hello, %s!", x)) + )"); + + if (FFlag::LuauStringFormatErrorSuppression) + LUAU_REQUIRE_NO_ERRORS(result); + else + LUAU_REQUIRE_ERROR_COUNT(1, result); +} + TEST_SUITE_END(); diff --git a/tests/main.cpp b/tests/main.cpp index bd5a0517..005a3e61 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Common.h" +#include "Luau/CodeGenCommon.h" + #define DOCTEST_CONFIG_IMPLEMENT // Our calls to parseOption/parseFlag don't provide a prefix so set the prefix to the empty string. #define DOCTEST_CONFIG_OPTIONS_PREFIX "" diff --git a/tools/natvis/VM.natvis b/tools/natvis/VM.natvis index 59bc43c4..adf603eb 100644 --- a/tools/natvis/VM.natvis +++ b/tools/natvis/VM.natvis @@ -77,7 +77,7 @@ --- - + table metatable From 82c9383a39dfc21b8b012685a072123c7618df21 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sun, 19 Jan 2025 14:14:12 -0800 Subject: [PATCH 2/3] Fix math.map/math.lerp merge fallout (#1621) - LuauMathMap flag was not removed but the uses of it were - LuauMathLerp addition to math table was duplicated --- VM/src/lmathlib.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/VM/src/lmathlib.cpp b/VM/src/lmathlib.cpp index 7bc99c35..9bd21607 100644 --- a/VM/src/lmathlib.cpp +++ b/VM/src/lmathlib.cpp @@ -7,7 +7,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauMathMap) LUAU_FASTFLAGVARIABLE(LuauMathLerp) #undef PI @@ -490,11 +489,5 @@ int luaopen_math(lua_State* L) lua_setfield(L, -2, "lerp"); } - if (FFlag::LuauMathLerp) - { - lua_pushcfunction(L, math_lerp, "lerp"); - lua_setfield(L, -2, "lerp"); - } - return 1; } From 6061a14e9f77608ad25ffa8e865471c65bf8404d Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:05:58 -0800 Subject: [PATCH 3/3] Fixup desync between internal and external codebases (#1622) During conflict resolution while transferring patches back and forth, some conflict resolutions may cause differences that are harder to spot. This process would have also cleaned up differences noted in https://github.com/luau-lang/luau/pull/1621 --- Analysis/src/Differ.cpp | 3 --- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index 7debae9c..e6222067 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -13,7 +13,6 @@ namespace Luau { - std::string DiffPathNode::toString() const { switch (kind) @@ -945,14 +944,12 @@ std::vector>::const_reverse_iterator DifferEnvironment return visitingStack.crend(); } - DifferResult diff(TypeId ty1, TypeId ty2) { DifferEnvironment differEnv{ty1, ty2, std::nullopt, std::nullopt}; return diffUsingEnv(differEnv, ty1, ty2); } - DifferResult diffWithSymbols(TypeId ty1, TypeId ty2, std::optional symbol1, std::optional symbol2) { DifferEnvironment differEnv{ty1, ty2, symbol1, symbol2}; diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 937c414c..dc16cb07 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,9 +2,9 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauVectorDefinitionsExtra) -LUAU_FASTFLAG(LuauVector2Constructor) LUAU_FASTFLAG(LuauBufferBitMethods2) LUAU_FASTFLAGVARIABLE(LuauMathMapDefinition) +LUAU_FASTFLAG(LuauVector2Constructor) namespace Luau {