Sync to upstream/release/546 (#681)

This commit is contained in:
Arseny Kapoulkine 2022-09-23 12:17:25 -07:00 committed by GitHub
parent fc4871989a
commit 59fd9de485
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 3942 additions and 725 deletions

View file

@ -90,18 +90,49 @@ struct TypeAliasExpansionConstraint
TypeId target;
};
using ConstraintPtr = std::unique_ptr<struct Constraint>;
struct FunctionCallConstraint
{
std::vector<NotNull<const Constraint>> innerConstraints;
std::vector<NotNull<const struct Constraint>> innerConstraints;
TypeId fn;
TypePackId result;
class AstExprCall* astFragment;
};
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint>;
// result ~ prim ExpectedType SomeSingletonType MultitonType
//
// If ExpectedType is potentially a singleton (an actual singleton or a union
// that contains a singleton), then result ~ SomeSingletonType
//
// else result ~ MultitonType
struct PrimitiveTypeConstraint
{
TypeId resultType;
TypeId expectedType;
TypeId singletonType;
TypeId multitonType;
};
// result ~ hasProp type "prop_name"
//
// If the subject is a table, bind the result to the named prop. If the table
// has an indexer, bind it to the index result type. If the subject is a union,
// bind the result to the union of its constituents' properties.
//
// It would be nice to get rid of this constraint and someday replace it with
//
// T <: {p: X}
//
// Where {} describes an inexact shape type.
struct HasPropConstraint
{
TypeId resultType;
TypeId subjectType;
std::string prop;
};
using ConstraintV =
Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint, BinaryConstraint,
IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint>;
struct Constraint
{
@ -117,6 +148,8 @@ struct Constraint
std::vector<NotNull<Constraint>> dependencies;
};
using ConstraintPtr = std::unique_ptr<Constraint>;
inline Constraint& asMutable(const Constraint& c)
{
return const_cast<Constraint&>(c);

View file

@ -125,23 +125,25 @@ struct ConstraintGraphBuilder
void visit(const ScopePtr& scope, AstStatDeclareClass* declareClass);
void visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction);
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs);
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr);
TypePackId checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes = {});
TypePackId checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes = {});
/**
* Checks an expression that is expected to evaluate to one type.
* @param scope the scope the expression is contained within.
* @param expr the expression to check.
* @param expectedType the type of the expression that is expected from its
* surrounding context. Used to implement bidirectional type checking.
* @return the type of the expression.
*/
TypeId check(const ScopePtr& scope, AstExpr* expr);
TypeId check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType = {});
TypeId checkExprTable(const ScopePtr& scope, AstExprTable* expr);
TypeId check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
TypeId check(const ScopePtr& scope, AstExprIndexName* indexName);
TypeId check(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
TypeId check(const ScopePtr& scope, AstExprUnary* unary);
TypeId check(const ScopePtr& scope, AstExprBinary* binary);
TypeId check(const ScopePtr& scope, AstExprIfElse* ifElse);
TypeId check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType);
TypeId check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert);
struct FunctionSignature

View file

@ -100,6 +100,8 @@ struct ConstraintSolver
bool tryDispatch(const NameConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const FunctionCallConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
// for a, ... in some_table do
bool tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
@ -116,6 +118,16 @@ struct ConstraintSolver
bool block(TypeId target, NotNull<const Constraint> constraint);
bool block(TypePackId target, NotNull<const Constraint> constraint);
// Traverse the type. If any blocked or pending typevars are found, block
// the constraint on them.
//
// Returns false if a type blocks the constraint.
//
// FIXME: This use of a boolean for the return result is an appalling
// interface.
bool recursiveBlock(TypeId target, NotNull<const Constraint> constraint);
bool recursiveBlock(TypePackId target, NotNull<const Constraint> constraint);
void unblock(NotNull<const Constraint> progressed);
void unblock(TypeId progressed);
void unblock(TypePackId progressed);

View file

@ -17,8 +17,8 @@ struct SingletonTypes;
using ModulePtr = std::shared_ptr<Module>;
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true);
bool isSubtype(TypePackId subTy, TypePackId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop = true);
std::pair<TypeId, bool> normalize(
TypeId ty, NotNull<Scope> scope, TypeArena& arena, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice);

View file

@ -6,6 +6,7 @@
#include "Luau/Location.h"
#include <string>
#include <vector>
namespace Luau
{

View file

@ -36,6 +36,20 @@ static std::optional<AstExpr*> matchRequire(const AstExprCall& call)
return call.args.data[0];
}
static bool matchSetmetatable(const AstExprCall& call)
{
const char* smt = "setmetatable";
if (call.args.size != 2)
return false;
const AstExprGlobal* funcAsGlobal = call.func->as<AstExprGlobal>();
if (!funcAsGlobal || funcAsGlobal->name != smt)
return false;
return true;
}
ConstraintGraphBuilder::ConstraintGraphBuilder(const ModuleName& moduleName, ModulePtr module, TypeArena* arena,
NotNull<ModuleResolver> moduleResolver, NotNull<SingletonTypes> singletonTypes, NotNull<InternalErrorReporter> ice, const ScopePtr& globalScope, DcrLogger* logger)
: moduleName(moduleName)
@ -214,15 +228,16 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
for (AstLocal* local : local->vars)
{
TypeId ty = freshType(scope);
TypeId ty = nullptr;
Location location = local->location;
if (local->annotation)
{
location = local->annotation->location;
TypeId annotation = resolveType(scope, local->annotation, /* topLevel */ true);
addConstraint(scope, location, SubtypeConstraint{ty, annotation});
ty = resolveType(scope, local->annotation, /* topLevel */ true);
}
else
ty = freshType(scope);
varTypes.push_back(ty);
scope->bindings[local] = Binding{ty, location};
@ -231,6 +246,8 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
for (size_t i = 0; i < local->values.size; ++i)
{
AstExpr* value = local->values.data[i];
const bool hasAnnotation = i < local->vars.size && nullptr != local->vars.data[i]->annotation;
if (value->is<AstExprConstantNil>())
{
// HACK: we leave nil-initialized things floating under the assumption that they will later be populated.
@ -239,7 +256,11 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
}
else if (i == local->values.size - 1)
{
TypePackId exprPack = checkPack(scope, value);
std::vector<TypeId> expectedTypes;
if (hasAnnotation)
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
TypePackId exprPack = checkPack(scope, value, expectedTypes);
if (i < local->vars.size)
{
@ -250,7 +271,11 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
}
else
{
TypeId exprType = check(scope, value);
std::optional<TypeId> expectedType;
if (hasAnnotation)
expectedType = varTypes.at(i);
TypeId exprType = check(scope, value, expectedType);
if (i < varTypes.size())
addConstraint(scope, local->location, SubtypeConstraint{varTypes[i], exprType});
}
@ -458,7 +483,15 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatFunction* funct
void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatReturn* ret)
{
TypePackId exprTypes = checkPack(scope, ret->list);
// At this point, the only way scope->returnType should have anything
// interesting in it is if the function has an explicit return annotation.
// If this is the case, then we can expect that the return expression
// conforms to that.
std::vector<TypeId> expectedTypes;
for (TypeId ty : scope->returnType)
expectedTypes.push_back(ty);
TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes);
addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType});
}
@ -695,7 +728,7 @@ void ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareFunction
scope->bindings[global->name] = Binding{fnType, global->location};
}
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs)
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<AstExpr*> exprs, const std::vector<TypeId>& expectedTypes)
{
std::vector<TypeId> head;
std::optional<TypePackId> tail;
@ -704,9 +737,17 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
{
AstExpr* expr = exprs.data[i];
if (i < exprs.size - 1)
{
std::optional<TypeId> expectedType;
if (i < expectedTypes.size())
expectedType = expectedTypes[i];
head.push_back(check(scope, expr));
}
else
tail = checkPack(scope, expr);
{
std::vector<TypeId> expectedTailTypes{begin(expectedTypes) + i, end(expectedTypes)};
tail = checkPack(scope, expr, expectedTailTypes);
}
}
if (head.empty() && tail)
@ -715,7 +756,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstArray<Ast
return arena->addTypePack(TypePack{std::move(head), tail});
}
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr)
TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* expr, const std::vector<TypeId>& expectedTypes)
{
RecursionCounter counter{&recursionCount};
@ -730,7 +771,6 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
if (AstExprCall* call = expr->as<AstExprCall>())
{
TypeId fnType = check(scope, call->func);
const size_t constraintIndex = scope->constraints.size();
const size_t scopeIndex = scopes.size();
@ -743,12 +783,25 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
// TODO self
if (matchSetmetatable(*call))
{
LUAU_ASSERT(args.size() == 2);
TypeId target = args[0];
TypeId mt = args[1];
MetatableTypeVar mtv{target, mt};
TypeId resultTy = arena->addType(mtv);
result = arena->addTypePack({resultTy});
}
else
{
const size_t constraintEndIndex = scope->constraints.size();
const size_t scopeEndIndex = scopes.size();
astOriginalCallTypes[call->func] = fnType;
TypeId instantiatedType = arena->addType(BlockedTypeVar{});
// TODO: How do expectedTypes play into this? Do they?
TypePackId rets = arena->addTypePack(BlockedTypePack{});
FunctionTypeVar ftv(arena->addTypePack(TypePack{args, {}}), rets);
TypeId inferredFnType = arena->addType(ftv);
@ -787,6 +840,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
result = rets;
}
}
else if (AstExprVarargs* varargs = expr->as<AstExprVarargs>())
{
if (scope->varargPack)
@ -796,7 +850,10 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
}
else
{
TypeId t = check(scope, expr);
std::optional<TypeId> expectedType;
if (!expectedTypes.empty())
expectedType = expectedTypes[0];
TypeId t = check(scope, expr, expectedType);
result = arena->addTypePack({t});
}
@ -805,7 +862,7 @@ TypePackId ConstraintGraphBuilder::checkPack(const ScopePtr& scope, AstExpr* exp
return result;
}
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr, std::optional<TypeId> expectedType)
{
RecursionCounter counter{&recursionCount};
@ -819,12 +876,47 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
if (auto group = expr->as<AstExprGroup>())
result = check(scope, group->expr);
else if (expr->is<AstExprConstantString>())
else if (auto stringExpr = expr->as<AstExprConstantString>())
{
if (expectedType)
{
const TypeId expectedTy = follow(*expectedType);
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
{
result = arena->addType(BlockedTypeVar{});
TypeId singletonType = arena->addType(SingletonTypeVar(StringSingleton{std::string(stringExpr->value.data, stringExpr->value.size)}));
addConstraint(scope, expr->location, PrimitiveTypeConstraint{result, expectedTy, singletonType, singletonTypes->stringType});
}
else if (maybeSingleton(expectedTy))
result = arena->addType(SingletonTypeVar{StringSingleton{std::string{stringExpr->value.data, stringExpr->value.size}}});
else
result = singletonTypes->stringType;
}
else
result = singletonTypes->stringType;
}
else if (expr->is<AstExprConstantNumber>())
result = singletonTypes->numberType;
else if (expr->is<AstExprConstantBool>())
else if (auto boolExpr = expr->as<AstExprConstantBool>())
{
if (expectedType)
{
const TypeId expectedTy = follow(*expectedType);
const TypeId singletonType = boolExpr->value ? singletonTypes->trueType : singletonTypes->falseType;
if (get<BlockedTypeVar>(expectedTy) || get<PendingExpansionTypeVar>(expectedTy))
{
result = arena->addType(BlockedTypeVar{});
addConstraint(scope, expr->location, PrimitiveTypeConstraint{result, expectedTy, singletonType, singletonTypes->booleanType});
}
else if (maybeSingleton(expectedTy))
result = singletonType;
else
result = singletonTypes->booleanType;
}
else
result = singletonTypes->booleanType;
}
else if (expr->is<AstExprConstantNil>())
result = singletonTypes->nilType;
else if (auto a = expr->as<AstExprLocal>())
@ -864,13 +956,13 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExpr* expr)
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
result = check(scope, indexExpr);
else if (auto table = expr->as<AstExprTable>())
result = checkExprTable(scope, table);
result = check(scope, table, expectedType);
else if (auto unary = expr->as<AstExprUnary>())
result = check(scope, unary);
else if (auto binary = expr->as<AstExprBinary>())
result = check(scope, binary);
else if (auto ifElse = expr->as<AstExprIfElse>())
result = check(scope, ifElse);
result = check(scope, ifElse, expectedType);
else if (auto typeAssert = expr->as<AstExprTypeAssertion>())
result = check(scope, typeAssert);
else if (auto err = expr->as<AstExprError>())
@ -924,20 +1016,9 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
{
TypeId operandType = check(scope, unary->expr);
switch (unary->op)
{
case AstExprUnary::Minus:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, unary->location, UnaryConstraint{AstExprUnary::Minus, operandType, resultType});
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
return resultType;
}
default:
LUAU_ASSERT(0);
}
LUAU_UNREACHABLE();
return singletonTypes->errorRecoveryType();
}
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary)
@ -946,22 +1027,34 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
TypeId rightType = check(scope, binary->right);
switch (binary->op)
{
case AstExprBinary::And:
case AstExprBinary::Or:
{
addConstraint(scope, binary->location, SubtypeConstraint{leftType, rightType});
return leftType;
}
case AstExprBinary::Add:
case AstExprBinary::Sub:
case AstExprBinary::Mul:
case AstExprBinary::Div:
case AstExprBinary::Mod:
case AstExprBinary::Pow:
case AstExprBinary::CompareNe:
case AstExprBinary::CompareEq:
case AstExprBinary::CompareLt:
case AstExprBinary::CompareLe:
case AstExprBinary::CompareGt:
case AstExprBinary::CompareGe:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Add, leftType, rightType, resultType});
addConstraint(scope, binary->location, BinaryConstraint{binary->op, leftType, rightType, resultType});
return resultType;
}
case AstExprBinary::Sub:
case AstExprBinary::Concat:
{
TypeId resultType = arena->addType(BlockedTypeVar{});
addConstraint(scope, binary->location, BinaryConstraint{AstExprBinary::Sub, leftType, rightType, resultType});
return resultType;
addConstraint(scope, binary->left->location, SubtypeConstraint{leftType, singletonTypes->stringType});
addConstraint(scope, binary->right->location, SubtypeConstraint{rightType, singletonTypes->stringType});
return singletonTypes->stringType;
}
default:
LUAU_ASSERT(0);
@ -971,16 +1064,16 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binar
return nullptr;
}
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse)
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional<TypeId> expectedType)
{
check(scope, ifElse->condition);
TypeId thenType = check(scope, ifElse->trueExpr);
TypeId elseType = check(scope, ifElse->falseExpr);
TypeId thenType = check(scope, ifElse->trueExpr, expectedType);
TypeId elseType = check(scope, ifElse->falseExpr, expectedType);
if (ifElse->hasElse)
{
TypeId resultType = arena->addType(BlockedTypeVar{});
TypeId resultType = expectedType ? *expectedType : freshType(scope);
addConstraint(scope, ifElse->trueExpr->location, SubtypeConstraint{thenType, resultType});
addConstraint(scope, ifElse->falseExpr->location, SubtypeConstraint{elseType, resultType});
return resultType;
@ -995,7 +1088,7 @@ TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTypeAssertion
return resolveType(scope, typeAssert->annotation);
}
TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTable* expr)
TypeId ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
{
TypeId ty = arena->addType(TableTypeVar{});
TableTypeVar* ttv = getMutable<TableTypeVar>(ty);
@ -1015,7 +1108,18 @@ TypeId ConstraintGraphBuilder::checkExprTable(const ScopePtr& scope, AstExprTabl
for (const AstExprTable::Item& item : expr->items)
{
TypeId itemTy = check(scope, item.value);
std::optional<TypeId> expectedValueType;
if (item.key && expectedType)
{
if (auto stringKey = item.key->as<AstExprConstantString>())
{
expectedValueType = arena->addType(BlockedTypeVar{});
addConstraint(scope, item.value->location, HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data});
}
}
TypeId itemTy = check(scope, item.value, expectedValueType);
if (get<ErrorTypeVar>(follow(itemTy)))
return ty;
@ -1130,7 +1234,12 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
if (fn->returnAnnotation)
{
TypePackId annotatedRetType = resolveTypePack(signatureScope, *fn->returnAnnotation);
addConstraint(signatureScope, getLocation(*fn->returnAnnotation), PackSubtypeConstraint{returnType, annotatedRetType});
// We bind the annotated type directly here so that, when we need to
// generate constraints for return types, we have a guarantee that we
// know the annotated return type already, if one was provided.
LUAU_ASSERT(get<FreeTypePack>(returnType));
asMutable(returnType)->ty.emplace<BoundTypePack>(annotatedRetType);
}
std::vector<TypeId> argTypes;

View file

@ -396,8 +396,12 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*taec, constraint);
else if (auto fcc = get<FunctionCallConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else if (auto fcc = get<PrimitiveTypeConstraint>(*constraint))
success = tryDispatch(*fcc, constraint);
else if (auto hpc = get<HasPropConstraint>(*constraint))
success = tryDispatch(*hpc, constraint);
else
LUAU_ASSERT(0);
LUAU_ASSERT(false);
if (success)
{
@ -409,6 +413,11 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
{
if (!recursiveBlock(c.subType, constraint))
return false;
if (!recursiveBlock(c.superType, constraint))
return false;
if (isBlocked(c.subType))
return block(c.subType, constraint);
else if (isBlocked(c.superType))
@ -421,6 +430,9 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
{
if (!recursiveBlock(c.subPack, constraint) || !recursiveBlock(c.superPack, constraint))
return false;
if (isBlocked(c.subPack))
return block(c.subPack, constraint);
else if (isBlocked(c.superPack))
@ -480,13 +492,30 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
LUAU_ASSERT(get<BlockedTypeVar>(c.resultType));
switch (c.op)
{
case AstExprUnary::Not:
{
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->booleanType);
return true;
}
case AstExprUnary::Len:
{
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(singletonTypes->numberType);
return true;
}
case AstExprUnary::Minus:
{
if (isNumber(operandType) || get<AnyTypeVar>(operandType) || get<ErrorTypeVar>(operandType))
{
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(c.operandType);
return true;
}
break;
}
}
LUAU_ASSERT(0); // TODO metatable handling
LUAU_ASSERT(false); // TODO metatable handling
return false;
}
@ -906,6 +935,91 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
return true;
}
bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint)
{
TypeId expectedType = follow(c.expectedType);
if (isBlocked(expectedType) || get<PendingExpansionTypeVar>(expectedType))
return block(expectedType, constraint);
TypeId bindTo = maybeSingleton(expectedType) ? c.singletonType : c.multitonType;
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(bindTo);
return true;
}
bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint)
{
TypeId subjectType = follow(c.subjectType);
if (isBlocked(subjectType) || get<PendingExpansionTypeVar>(subjectType))
return block(subjectType, constraint);
TypeId resultType = nullptr;
auto collectParts = [&](auto&& unionOrIntersection) -> std::pair<bool, std::vector<TypeId>> {
bool blocked = false;
std::vector<TypeId> parts;
for (TypeId expectedPart : unionOrIntersection)
{
expectedPart = follow(expectedPart);
if (isBlocked(expectedPart) || get<PendingExpansionTypeVar>(expectedPart))
{
blocked = true;
block(expectedPart, constraint);
}
else if (const TableTypeVar* ttv = get<TableTypeVar>(follow(expectedPart)))
{
if (auto prop = ttv->props.find(c.prop); prop != ttv->props.end())
parts.push_back(prop->second.type);
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
parts.push_back(ttv->indexer->indexResultType);
}
}
return {blocked, parts};
};
if (auto ttv = get<TableTypeVar>(subjectType))
{
if (auto prop = ttv->props.find(c.prop); prop != ttv->props.end())
resultType = prop->second.type;
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
resultType = ttv->indexer->indexResultType;
}
else if (auto utv = get<UnionTypeVar>(subjectType))
{
auto [blocked, parts] = collectParts(utv);
if (blocked)
return false;
else if (parts.size() == 1)
resultType = parts[0];
else if (parts.size() > 1)
resultType = arena->addType(UnionTypeVar{std::move(parts)});
else
LUAU_ASSERT(false); // parts.size() == 0
}
else if (auto itv = get<IntersectionTypeVar>(subjectType))
{
auto [blocked, parts] = collectParts(itv);
if (blocked)
return false;
else if (parts.size() == 1)
resultType = parts[0];
else if (parts.size() > 1)
resultType = arena->addType(IntersectionTypeVar{std::move(parts)});
else
LUAU_ASSERT(false); // parts.size() == 0
}
if (resultType)
asMutable(c.resultType)->ty.emplace<BoundTypeVar>(resultType);
return true;
}
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
{
auto block_ = [&](auto&& t) {
@ -914,7 +1028,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
// TODO: I believe it is the case that, if we are asked to force
// this constraint, then we can do nothing but fail. I'd like to
// find a code sample that gets here.
LUAU_ASSERT(0);
LUAU_ASSERT(false);
}
else
block(t, constraint);
@ -979,7 +1093,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
if (get<FreeTypeVar>(metaTy))
return block_(metaTy);
LUAU_ASSERT(0);
LUAU_ASSERT(false);
}
else
errorify(c.variables);
@ -996,7 +1110,7 @@ bool ConstraintSolver::tryDispatchIterableFunction(
if (get<FreeTypeVar>(firstIndexTy))
{
if (force)
LUAU_ASSERT(0);
LUAU_ASSERT(false);
else
block(firstIndexTy, constraint);
return false;
@ -1061,6 +1175,48 @@ bool ConstraintSolver::block(TypePackId target, NotNull<const Constraint> constr
return false;
}
struct Blocker : TypeVarOnceVisitor
{
NotNull<ConstraintSolver> solver;
NotNull<const Constraint> constraint;
bool blocked = false;
explicit Blocker(NotNull<ConstraintSolver> solver, NotNull<const Constraint> constraint)
: solver(solver)
, constraint(constraint)
{
}
bool visit(TypeId ty, const BlockedTypeVar&)
{
blocked = true;
solver->block(ty, constraint);
return false;
}
bool visit(TypeId ty, const PendingExpansionTypeVar&)
{
blocked = true;
solver->block(ty, constraint);
return false;
}
};
bool ConstraintSolver::recursiveBlock(TypeId target, NotNull<const Constraint> constraint)
{
Blocker blocker{NotNull{this}, constraint};
blocker.traverse(target);
return !blocker.blocked;
}
bool ConstraintSolver::recursiveBlock(TypePackId pack, NotNull<const Constraint> constraint)
{
Blocker blocker{NotNull{this}, constraint};
blocker.traverse(pack);
return !blocker.blocked;
}
void ConstraintSolver::unblock_(BlockedConstraintId progressed)
{
auto it = blocked.find(progressed);

View file

@ -5,6 +5,7 @@
#include <algorithm>
#include "Luau/Clone.h"
#include "Luau/Common.h"
#include "Luau/Unifier.h"
#include "Luau/VisitTypeVar.h"
@ -13,8 +14,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCopyBeforeNormalizing, false)
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTFLAGVARIABLE(LuauNormalizeCombineTableFix, false);
LUAU_FASTFLAGVARIABLE(LuauFixNormalizationOfCyclicUnions, false);
LUAU_FASTFLAG(LuauUnknownAndNeverType)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
namespace Luau
{
@ -54,24 +55,24 @@ struct Replacer
} // anonymous namespace
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
bool isSubtype(TypeId subTy, TypeId superTy, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop)
{
UnifierSharedState sharedState{&ice};
TypeArena arena;
Unifier u{&arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.anyIsTop = true;
u.anyIsTop = anyIsTop;
u.tryUnify(subTy, superTy);
const bool ok = u.errors.empty() && u.log.empty();
return ok;
}
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice)
bool isSubtype(TypePackId subPack, TypePackId superPack, NotNull<Scope> scope, NotNull<SingletonTypes> singletonTypes, InternalErrorReporter& ice, bool anyIsTop)
{
UnifierSharedState sharedState{&ice};
TypeArena arena;
Unifier u{&arena, singletonTypes, Mode::Strict, scope, Location{}, Covariant, sharedState};
u.anyIsTop = true;
u.anyIsTop = anyIsTop;
u.tryUnify(subPack, superPack);
const bool ok = u.errors.empty() && u.log.empty();
@ -319,18 +320,11 @@ struct Normalize final : TypeVarVisitor
UnionTypeVar* utv = &const_cast<UnionTypeVar&>(utvRef);
// TODO: Clip tempOptions and optionsRef when clipping FFlag::LuauFixNormalizationOfCyclicUnions
std::vector<TypeId> tempOptions;
if (!FFlag::LuauFixNormalizationOfCyclicUnions)
tempOptions = std::move(utv->options);
std::vector<TypeId>& optionsRef = FFlag::LuauFixNormalizationOfCyclicUnions ? utv->options : tempOptions;
// We might transmute, so it's not safe to rely on the builtin traversal logic of visitTypeVar
for (TypeId option : optionsRef)
for (TypeId option : utv->options)
traverse(option);
std::vector<TypeId> newOptions = normalizeUnion(optionsRef);
std::vector<TypeId> newOptions = normalizeUnion(utv->options);
const bool normal = areNormal(newOptions, seen, ice);
@ -355,8 +349,6 @@ struct Normalize final : TypeVarVisitor
IntersectionTypeVar* itv = &const_cast<IntersectionTypeVar&>(itvRef);
if (FFlag::LuauFixNormalizationOfCyclicUnions)
{
std::vector<TypeId> oldParts = itv->parts;
IntersectionTypeVar newIntersection;
@ -406,56 +398,6 @@ struct Normalize final : TypeVarVisitor
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
}
}
else
{
std::vector<TypeId> oldParts = std::move(itv->parts);
for (TypeId part : oldParts)
traverse(part);
std::vector<TypeId> tables;
for (TypeId part : oldParts)
{
part = follow(part);
if (get<TableTypeVar>(part))
tables.push_back(part);
else
{
Replacer replacer{&arena, nullptr, nullptr}; // FIXME this is super super WEIRD
combineIntoIntersection(replacer, itv, part);
}
}
// Don't allocate a new table if there's just one in the intersection.
if (tables.size() == 1)
itv->parts.push_back(tables[0]);
else if (!tables.empty())
{
const TableTypeVar* first = get<TableTypeVar>(tables[0]);
LUAU_ASSERT(first);
TypeId newTable = arena.addType(TableTypeVar{first->state, first->level});
TableTypeVar* ttv = getMutable<TableTypeVar>(newTable);
for (TypeId part : tables)
{
// Intuition: If combineIntoTable() needs to clone a table, any references to 'part' are cyclic and need
// to be rewritten to point at 'newTable' in the clone.
Replacer replacer{&arena, part, newTable};
combineIntoTable(replacer, ttv, part);
}
itv->parts.push_back(newTable);
}
asMutable(ty)->normal = areNormal(itv->parts, seen, ice);
if (itv->parts.size() == 1)
{
TypeId part = itv->parts[0];
*asMutable(ty) = BoundTypeVar{part};
}
}
return false;
}
@ -629,8 +571,6 @@ struct Normalize final : TypeVarVisitor
table->props.insert({propName, prop});
}
if (FFlag::LuauFixNormalizationOfCyclicUnions)
{
if (tyTable->indexer)
{
if (table->indexer)
@ -645,7 +585,6 @@ struct Normalize final : TypeVarVisitor
TableIndexer{replacer.smartClone(tyTable->indexer->indexType), replacer.smartClone(tyTable->indexer->indexResultType)};
}
}
}
table->state = combineTableStates(table->state, tyTable->state);
table->level = max(table->level, tyTable->level);

View file

@ -1512,6 +1512,15 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
{
return "call " + tos(c.fn, opts) + " with { result = " + tos(c.result, opts) + " }";
}
else if constexpr (std::is_same_v<T, PrimitiveTypeConstraint>)
{
return tos(c.resultType, opts) + " ~ prim " + tos(c.expectedType, opts) + ", " + tos(c.singletonType, opts) + ", " +
tos(c.multitonType, opts);
}
else if constexpr (std::is_same_v<T, HasPropConstraint>)
{
return tos(c.resultType, opts) + " ~ hasProp " + tos(c.subjectType, opts) + ", \"" + c.prop + "\"";
}
else
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
};

View file

@ -307,10 +307,9 @@ struct TypeChecker2
if (var->annotation)
{
TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(*it, varType, stack.back(), singletonTypes, ice))
{
reportError(TypeMismatch{varType, *it}, value->location);
}
ErrorVec errors = tryUnify(stack.back(), value->location, *it, varType);
if (!errors.empty())
reportErrors(std::move(errors));
}
++it;
@ -325,7 +324,7 @@ struct TypeChecker2
if (var->annotation)
{
TypeId varType = lookupAnnotation(var->annotation);
if (!isSubtype(varType, valueType, stack.back(), singletonTypes, ice))
if (!isSubtype(varType, valueType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{varType, valueType}, value->location);
}
@ -540,7 +539,7 @@ struct TypeChecker2
visit(rhs);
TypeId rhsType = lookupType(rhs);
if (!isSubtype(rhsType, lhsType, stack.back(), singletonTypes, ice))
if (!isSubtype(rhsType, lhsType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{lhsType, rhsType}, rhs->location);
}
@ -691,7 +690,7 @@ struct TypeChecker2
TypeId actualType = lookupType(number);
TypeId numberType = singletonTypes->numberType;
if (!isSubtype(numberType, actualType, stack.back(), singletonTypes, ice))
if (!isSubtype(numberType, actualType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{actualType, numberType}, number->location);
}
@ -702,7 +701,7 @@ struct TypeChecker2
TypeId actualType = lookupType(string);
TypeId stringType = singletonTypes->stringType;
if (!isSubtype(stringType, actualType, stack.back(), singletonTypes, ice))
if (!isSubtype(stringType, actualType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{actualType, stringType}, string->location);
}
@ -762,7 +761,7 @@ struct TypeChecker2
FunctionTypeVar ftv{argsTp, expectedRetType};
TypeId expectedType = arena.addType(ftv);
if (!isSubtype(expectedType, instantiatedFunctionType, stack.back(), singletonTypes, ice))
if (!isSubtype(instantiatedFunctionType, expectedType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
CloneState cloneState;
expectedType = clone(expectedType, module->internalTypes, cloneState);
@ -781,7 +780,7 @@ struct TypeChecker2
getIndexTypeFromType(module->getModuleScope(), leftType, indexName->index.value, indexName->location, /* addErrors */ true);
if (ty)
{
if (!isSubtype(resultType, *ty, stack.back(), singletonTypes, ice))
if (!isSubtype(resultType, *ty, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{resultType, *ty}, indexName->location);
}
@ -814,7 +813,7 @@ struct TypeChecker2
TypeId inferredArgTy = *argIt;
TypeId annotatedArgTy = lookupAnnotation(arg->annotation);
if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back(), singletonTypes, ice))
if (!isSubtype(annotatedArgTy, inferredArgTy, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
{
reportError(TypeMismatch{annotatedArgTy, inferredArgTy}, arg->location);
}
@ -859,10 +858,10 @@ struct TypeChecker2
TypeId computedType = lookupType(expr->expr);
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
if (isSubtype(annotationType, computedType, stack.back(), singletonTypes, ice))
if (isSubtype(annotationType, computedType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
return;
if (isSubtype(computedType, annotationType, stack.back(), singletonTypes, ice))
if (isSubtype(computedType, annotationType, stack.back(), singletonTypes, ice, /* anyIsTop */ false))
return;
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);

View file

@ -5,7 +5,6 @@
#include <functional>
#include <utility>
#include <vector>
#include <type_traits>
#include <stdint.h>
@ -35,30 +34,125 @@ public:
class iterator;
DenseHashTable(const Key& empty_key, size_t buckets = 0)
: count(0)
: data(nullptr)
, capacity(0)
, count(0)
, empty_key(empty_key)
{
// validate that equality operator is at least somewhat functional
LUAU_ASSERT(eq(empty_key, empty_key));
// buckets has to be power-of-two or zero
LUAU_ASSERT((buckets & (buckets - 1)) == 0);
// don't move this to initializer list! this works around an MSVC codegen issue on AMD CPUs:
// https://developercommunity.visualstudio.com/t/stdvector-constructor-from-size-t-is-25-times-slow/1546547
if (buckets)
resize_data<Item>(buckets);
{
data = static_cast<Item*>(::operator new(sizeof(Item) * buckets));
capacity = buckets;
ItemInterface::fill(data, buckets, empty_key);
}
}
~DenseHashTable()
{
if (data)
destroy();
}
DenseHashTable(const DenseHashTable& other)
: data(nullptr)
, capacity(0)
, count(other.count)
, empty_key(other.empty_key)
{
if (other.capacity)
{
data = static_cast<Item*>(::operator new(sizeof(Item) * other.capacity));
for (size_t i = 0; i < other.capacity; ++i)
{
new (&data[i]) Item(other.data[i]);
capacity = i + 1; // if Item copy throws, capacity will note the number of initialized objects for destroy() to clean up
}
}
}
DenseHashTable(DenseHashTable&& other)
: data(other.data)
, capacity(other.capacity)
, count(other.count)
, empty_key(other.empty_key)
{
other.data = nullptr;
other.capacity = 0;
other.count = 0;
}
DenseHashTable& operator=(DenseHashTable&& other)
{
if (this != &other)
{
if (data)
destroy();
data = other.data;
capacity = other.capacity;
count = other.count;
empty_key = other.empty_key;
other.data = nullptr;
other.capacity = 0;
other.count = 0;
}
return *this;
}
DenseHashTable& operator=(const DenseHashTable& other)
{
if (this != &other)
{
DenseHashTable copy(other);
*this = std::move(copy);
}
return *this;
}
void clear()
{
data.clear();
if (count == 0)
return;
if (capacity > 32)
{
destroy();
}
else
{
ItemInterface::destroy(data, capacity);
ItemInterface::fill(data, capacity, empty_key);
}
count = 0;
}
void destroy()
{
ItemInterface::destroy(data, capacity);
::operator delete(data);
data = nullptr;
capacity = 0;
}
Item* insert_unsafe(const Key& key)
{
// It is invalid to insert empty_key into the table since it acts as a "entry does not exist" marker
LUAU_ASSERT(!eq(key, empty_key));
size_t hashmod = data.size() - 1;
size_t hashmod = capacity - 1;
size_t bucket = hasher(key) & hashmod;
for (size_t probe = 0; probe <= hashmod; ++probe)
@ -90,12 +184,12 @@ public:
const Item* find(const Key& key) const
{
if (data.empty())
if (count == 0)
return 0;
if (eq(key, empty_key))
return 0;
size_t hashmod = data.size() - 1;
size_t hashmod = capacity - 1;
size_t bucket = hasher(key) & hashmod;
for (size_t probe = 0; probe <= hashmod; ++probe)
@ -121,18 +215,11 @@ public:
void rehash()
{
size_t newsize = data.empty() ? 16 : data.size() * 2;
if (data.empty() && data.capacity() >= newsize)
{
LUAU_ASSERT(count == 0);
resize_data<Item>(newsize);
return;
}
size_t newsize = capacity == 0 ? 16 : capacity * 2;
DenseHashTable newtable(empty_key, newsize);
for (size_t i = 0; i < data.size(); ++i)
for (size_t i = 0; i < capacity; ++i)
{
const Key& key = ItemInterface::getKey(data[i]);
@ -144,12 +231,14 @@ public:
}
LUAU_ASSERT(count == newtable.count);
data.swap(newtable.data);
std::swap(data, newtable.data);
std::swap(capacity, newtable.capacity);
}
void rehash_if_full()
{
if (count >= data.size() * 3 / 4)
if (count >= capacity * 3 / 4)
{
rehash();
}
@ -159,7 +248,7 @@ public:
{
size_t start = 0;
while (start < data.size() && eq(ItemInterface::getKey(data[start]), empty_key))
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
start++;
return const_iterator(this, start);
@ -167,14 +256,14 @@ public:
const_iterator end() const
{
return const_iterator(this, data.size());
return const_iterator(this, capacity);
}
iterator begin()
{
size_t start = 0;
while (start < data.size() && eq(ItemInterface::getKey(data[start]), empty_key))
while (start < capacity && eq(ItemInterface::getKey(data[start]), empty_key))
start++;
return iterator(this, start);
@ -182,7 +271,7 @@ public:
iterator end()
{
return iterator(this, data.size());
return iterator(this, capacity);
}
size_t size() const
@ -227,7 +316,7 @@ public:
const_iterator& operator++()
{
size_t size = set->data.size();
size_t size = set->capacity;
do
{
@ -286,7 +375,7 @@ public:
iterator& operator++()
{
size_t size = set->data.size();
size_t size = set->capacity;
do
{
@ -309,23 +398,8 @@ public:
};
private:
template<typename T>
void resize_data(size_t count, typename std::enable_if_t<std::is_copy_assignable_v<T>>* dummy = nullptr)
{
data.resize(count, ItemInterface::create(empty_key));
}
template<typename T>
void resize_data(size_t count, typename std::enable_if_t<!std::is_copy_assignable_v<T>>* dummy = nullptr)
{
size_t size = data.size();
data.resize(count);
for (size_t i = size; i < count; i++)
data[i].first = empty_key;
}
std::vector<Item> data;
Item* data;
size_t capacity;
size_t count;
Key empty_key;
Hash hasher;
@ -345,9 +419,16 @@ struct ItemInterfaceSet
item = key;
}
static Key create(const Key& key)
static void fill(Key* data, size_t count, const Key& key)
{
return key;
for (size_t i = 0; i < count; ++i)
new (&data[i]) Key(key);
}
static void destroy(Key* data, size_t count)
{
for (size_t i = 0; i < count; ++i)
data[i].~Key();
}
};
@ -364,9 +445,22 @@ struct ItemInterfaceMap
item.first = key;
}
static std::pair<Key, Value> create(const Key& key)
static void fill(std::pair<Key, Value>* data, size_t count, const Key& key)
{
return std::pair<Key, Value>(key, Value());
for (size_t i = 0; i < count; ++i)
{
new (&data[i].first) Key(key);
new (&data[i].second) Value();
}
}
static void destroy(std::pair<Key, Value>* data, size_t count)
{
for (size_t i = 0; i < count; ++i)
{
data[i].first.~Key();
data[i].second.~Value();
}
}
};

View file

@ -6,6 +6,8 @@
#include "Luau/DenseHash.h"
#include "Luau/Common.h"
#include <vector>
namespace Luau
{

View file

@ -20,7 +20,6 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseWrongNamedType, false)
bool lua_telemetry_parsed_named_non_function_type = false;
LUAU_FASTFLAGVARIABLE(LuauErrorDoubleHexPrefix, false)
LUAU_FASTFLAGVARIABLE(LuauLintParseIntegerIssues, false)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuaReportParseIntegerIssues, false)
LUAU_FASTFLAGVARIABLE(LuauInterpolatedStringBaseSupport, false)
@ -2070,95 +2069,8 @@ AstExpr* Parser::parseAssertionExpr()
return expr;
}
static const char* parseInteger_DEPRECATED(double& result, const char* data, int base)
{
LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues);
char* end = nullptr;
unsigned long long value = strtoull(data, &end, base);
if (value == ULLONG_MAX && errno == ERANGE)
{
// 'errno' might have been set before we called 'strtoull', but we don't want the overhead of resetting a TLS variable on each call
// so we only reset it when we get a result that might be an out-of-range error and parse again to make sure
errno = 0;
value = strtoull(data, &end, base);
if (errno == ERANGE)
{
if (DFFlag::LuaReportParseIntegerIssues)
{
if (base == 2)
lua_telemetry_parsed_out_of_range_bin_integer = true;
else
lua_telemetry_parsed_out_of_range_hex_integer = true;
}
}
}
result = double(value);
return *end == 0 ? nullptr : "Malformed number";
}
static const char* parseNumber_DEPRECATED2(double& result, const char* data)
{
LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues);
// binary literal
if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2])
return parseInteger_DEPRECATED(result, data + 2, 2);
// hexadecimal literal
if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2])
{
if (DFFlag::LuaReportParseIntegerIssues && data[2] == '0' && (data[3] == 'x' || data[3] == 'X'))
lua_telemetry_parsed_double_prefix_hex_integer = true;
return parseInteger_DEPRECATED(result, data + 2, 16);
}
char* end = nullptr;
double value = strtod(data, &end);
result = value;
return *end == 0 ? nullptr : "Malformed number";
}
static bool parseNumber_DEPRECATED(double& result, const char* data)
{
LUAU_ASSERT(!FFlag::LuauLintParseIntegerIssues);
// binary literal
if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2])
{
char* end = nullptr;
unsigned long long value = strtoull(data + 2, &end, 2);
result = double(value);
return *end == 0;
}
// hexadecimal literal
else if (data[0] == '0' && (data[1] == 'x' || data[1] == 'X') && data[2])
{
char* end = nullptr;
unsigned long long value = strtoull(data + 2, &end, 16);
result = double(value);
return *end == 0;
}
else
{
char* end = nullptr;
double value = strtod(data, &end);
result = value;
return *end == 0;
}
}
static ConstantNumberParseResult parseInteger(double& result, const char* data, int base)
{
LUAU_ASSERT(FFlag::LuauLintParseIntegerIssues);
LUAU_ASSERT(base == 2 || base == 16);
char* end = nullptr;
@ -2195,8 +2107,6 @@ static ConstantNumberParseResult parseInteger(double& result, const char* data,
static ConstantNumberParseResult parseDouble(double& result, const char* data)
{
LUAU_ASSERT(FFlag::LuauLintParseIntegerIssues);
// binary literal
if (data[0] == '0' && (data[1] == 'b' || data[1] == 'B') && data[2])
return parseInteger(result, data + 2, 2);
@ -2771,8 +2681,6 @@ AstExpr* Parser::parseNumber()
scratchData.erase(std::remove(scratchData.begin(), scratchData.end(), '_'), scratchData.end());
}
if (FFlag::LuauLintParseIntegerIssues)
{
double value = 0;
ConstantNumberParseResult result = parseDouble(value, scratchData.c_str());
nextLexeme();
@ -2781,39 +2689,6 @@ AstExpr* Parser::parseNumber()
return reportExprError(start, {}, "Malformed number");
return allocator.alloc<AstExprConstantNumber>(start, value, result);
}
else if (DFFlag::LuaReportParseIntegerIssues)
{
double value = 0;
if (const char* error = parseNumber_DEPRECATED2(value, scratchData.c_str()))
{
nextLexeme();
return reportExprError(start, {}, "%s", error);
}
else
{
nextLexeme();
return allocator.alloc<AstExprConstantNumber>(start, value);
}
}
else
{
double value = 0;
if (parseNumber_DEPRECATED(value, scratchData.c_str()))
{
nextLexeme();
return allocator.alloc<AstExprConstantNumber>(start, value);
}
else
{
nextLexeme();
return reportExprError(start, {}, "Malformed number");
}
}
}
AstLocal* Parser::pushLocal(const Binding& binding)

View file

@ -51,6 +51,8 @@ if(LUAU_BUILD_WEB)
add_executable(Luau.Web)
endif()
# Proxy target to make it possible to depend on private VM headers
add_library(Luau.VM.Internals INTERFACE)
include(Sources.cmake)
@ -70,6 +72,7 @@ target_link_libraries(Luau.Analysis PUBLIC Luau.Ast)
target_compile_features(Luau.CodeGen PRIVATE cxx_std_17)
target_include_directories(Luau.CodeGen PUBLIC CodeGen/include)
target_link_libraries(Luau.CodeGen PRIVATE Luau.VM Luau.VM.Internals) # Code generation needs VM internals
target_link_libraries(Luau.CodeGen PUBLIC Luau.Common)
target_compile_features(Luau.VM PRIVATE cxx_std_11)
@ -78,6 +81,8 @@ target_link_libraries(Luau.VM PUBLIC Luau.Common)
target_include_directories(isocline PUBLIC extern/isocline/include)
target_include_directories(Luau.VM.Internals INTERFACE VM/src)
set(LUAU_OPTIONS)
if(MSVC)

View file

@ -27,13 +27,13 @@ public:
private:
static const unsigned kRawDataLimit = 128;
char rawData[kRawDataLimit];
char* pos = rawData;
uint8_t rawData[kRawDataLimit];
uint8_t* pos = rawData;
uint32_t stackOffset = 0;
// We will remember the FDE location to write some of the fields like entry length, function start and size later
char* fdeEntryStart = nullptr;
uint8_t* fdeEntryStart = nullptr;
};
} // namespace CodeGen

View file

@ -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/AssemblyBuilderX64.h"
#include "ByteUtils.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
@ -46,44 +48,6 @@ const unsigned AVX_F2 = 0b11;
const unsigned kMaxAlign = 16;
// Utility functions to correctly write data on big endian machines
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#include <endian.h>
static void writeu32(uint8_t* target, uint32_t value)
{
value = htole32(value);
memcpy(target, &value, sizeof(value));
}
static void writeu64(uint8_t* target, uint64_t value)
{
value = htole64(value);
memcpy(target, &value, sizeof(value));
}
static void writef32(uint8_t* target, float value)
{
static_assert(sizeof(float) == sizeof(uint32_t), "type size must match to reinterpret data");
uint32_t data;
memcpy(&data, &value, sizeof(value));
writeu32(target, data);
}
static void writef64(uint8_t* target, double value)
{
static_assert(sizeof(double) == sizeof(uint64_t), "type size must match to reinterpret data");
uint64_t data;
memcpy(&data, &value, sizeof(value));
writeu64(target, data);
}
#else
#define writeu32(target, value) memcpy(target, &value, sizeof(value))
#define writeu64(target, value) memcpy(target, &value, sizeof(value))
#define writef32(target, value) memcpy(target, &value, sizeof(value))
#define writef64(target, value) memcpy(target, &value, sizeof(value))
#endif
AssemblyBuilderX64::AssemblyBuilderX64(bool logText)
: logText(logText)
{
@ -1014,16 +978,14 @@ void AssemblyBuilderX64::placeImm32(int32_t imm)
{
uint8_t* pos = codePos;
LUAU_ASSERT(pos + sizeof(imm) < codeEnd);
writeu32(pos, imm);
codePos = pos + sizeof(imm);
codePos = writeu32(pos, imm);
}
void AssemblyBuilderX64::placeImm64(int64_t imm)
{
uint8_t* pos = codePos;
LUAU_ASSERT(pos + sizeof(imm) < codeEnd);
writeu64(pos, imm);
codePos = pos + sizeof(imm);
codePos = writeu64(pos, imm);
}
void AssemblyBuilderX64::placeLabel(Label& label)

78
CodeGen/src/ByteUtils.h Normal file
View file

@ -0,0 +1,78 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#include <endian.h>
#endif
#include <string.h>
inline uint8_t* writeu8(uint8_t* target, uint8_t value)
{
*target = value;
return target + sizeof(value);
}
inline uint8_t* writeu32(uint8_t* target, uint32_t value)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
value = htole32(value);
#endif
memcpy(target, &value, sizeof(value));
return target + sizeof(value);
}
inline uint8_t* writeu64(uint8_t* target, uint64_t value)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
value = htole64(value);
#endif
memcpy(target, &value, sizeof(value));
return target + sizeof(value);
}
inline uint8_t* writeuleb128(uint8_t* target, uint64_t value)
{
do
{
uint8_t byte = value & 0x7f;
value >>= 7;
if (value)
byte |= 0x80;
*target++ = byte;
} while (value);
return target;
}
inline uint8_t* writef32(uint8_t* target, float value)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
static_assert(sizeof(float) == sizeof(uint32_t), "type size must match to reinterpret data");
uint32_t data;
memcpy(&data, &value, sizeof(value));
writeu32(target, data);
#else
memcpy(target, &value, sizeof(value));
#endif
return target + sizeof(value);
}
inline uint8_t* writef64(uint8_t* target, double value)
{
#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
static_assert(sizeof(double) == sizeof(uint64_t), "type size must match to reinterpret data");
uint64_t data;
memcpy(&data, &value, sizeof(value));
writeu64(target, data);
#else
memcpy(target, &value, sizeof(value));
#endif
return target + sizeof(value);
}

2511
CodeGen/src/Fallbacks.cpp Normal file

File diff suppressed because it is too large Load diff

93
CodeGen/src/Fallbacks.h Normal file
View file

@ -0,0 +1,93 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This file was generated by 'tools/lvmexecute_split.py' script, do not modify it by hand
#pragma once
#include <stdint.h>
struct lua_State;
struct Closure;
typedef uint32_t Instruction;
typedef struct lua_TValue TValue;
typedef TValue* StkId;
const Instruction* execute_LOP_NOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LOADNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LOADB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LOADN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LOADK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MOVE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETGLOBAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETUPVAL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_CLOSEUPVALS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETIMPORT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETTABLEKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETTABLEN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_NEWCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_NAMECALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_CALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_RETURN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIF(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFNOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFNOTEQ(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFNOTLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPIFNOTLT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_ADD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SUB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MUL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DIV(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MOD(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_POW(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_ADDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SUBK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MULK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DIVK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MODK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_POWK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_AND(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_OR(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_ANDK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_ORK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_CONCAT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_NOT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_MINUS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LENGTH(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_NEWTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DUPTABLE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_SETLIST(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORNPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORNLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORGPREP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORGLOOP(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORGPREP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DEP_FORGLOOP_INEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FORGPREP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DEP_FORGLOOP_NEXT(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_GETVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DUPCLOSURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_PREPVARARGS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPBACK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_LOADKX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPX(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FASTCALL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_COVERAGE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_CAPTURE(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DEP_JUMPIFEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_DEP_JUMPIFNOTEQK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FASTCALL1(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FASTCALL2(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_FASTCALL2K(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_BREAK(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPXEQKNIL(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPXEQKB(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPXEQKN(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);
const Instruction* execute_LOP_JUMPXEQKS(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k);

View file

@ -0,0 +1,56 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "lvm.h"
#include "lbuiltins.h"
#include "lbytecode.h"
#include "ldebug.h"
#include "ldo.h"
#include "lfunc.h"
#include "lgc.h"
#include "lmem.h"
#include "lnumutils.h"
#include "lstate.h"
#include "lstring.h"
#include "ltable.h"
#include <string.h>
// All external function calls that can cause stack realloc or Lua calls have to be wrapped in VM_PROTECT
// This makes sure that we save the pc (in case the Lua call needs to generate a backtrace) before the call,
// and restores the stack pointer after in case stack gets reallocated
// Should only be used on the slow paths.
#define VM_PROTECT(x) \
{ \
L->ci->savedpc = pc; \
{ \
x; \
}; \
base = L->base; \
}
// Some external functions can cause an error, but never reallocate the stack; for these, VM_PROTECT_PC() is
// a cheaper version of VM_PROTECT that can be called before the external call.
#define VM_PROTECT_PC() L->ci->savedpc = pc
#define VM_REG(i) (LUAU_ASSERT(unsigned(i) < unsigned(L->top - base)), &base[i])
#define VM_KV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->l.p->sizek)), &k[i])
#define VM_UV(i) (LUAU_ASSERT(unsigned(i) < unsigned(cl->nupvalues)), &cl->l.uprefs[i])
#define VM_PATCH_C(pc, slot) *const_cast<Instruction*>(pc) = ((uint8_t(slot) << 24) | (0x00ffffffu & *(pc)))
#define VM_PATCH_E(pc, slot) *const_cast<Instruction*>(pc) = ((uint32_t(slot) << 8) | (0x000000ffu & *(pc)))
#define VM_INTERRUPT() \
{ \
void (*interrupt)(lua_State*, int) = L->global->cb.interrupt; \
if (LUAU_UNLIKELY(!!interrupt)) \
{ /* the interrupt hook is called right before we advance pc */ \
VM_PROTECT(L->ci->savedpc++; interrupt(L, -1)); \
if (L->status != 0) \
{ \
L->ci->savedpc--; \
return NULL; \
} \
} \
}

View file

@ -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/UnwindBuilderDwarf2.h"
#include "ByteUtils.h"
#include <string.h>
// General information about Dwarf2 format can be found at:
@ -11,40 +13,6 @@
// https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf [System V Application Binary Interface (AMD64 Architecture Processor Supplement)]
// Interaction between Dwarf2 and System V ABI can be found in sections '3.6.2 DWARF Register Number Mapping' and '4.2.4 EH_FRAME sections'
static char* writeu8(char* target, uint8_t value)
{
memcpy(target, &value, sizeof(value));
return target + sizeof(value);
}
static char* writeu32(char* target, uint32_t value)
{
memcpy(target, &value, sizeof(value));
return target + sizeof(value);
}
static char* writeu64(char* target, uint64_t value)
{
memcpy(target, &value, sizeof(value));
return target + sizeof(value);
}
static char* writeuleb128(char* target, uint64_t value)
{
do
{
char byte = value & 0x7f;
value >>= 7;
if (value)
byte |= 0x80;
*target++ = byte;
} while (value);
return target;
}
// Call frame instruction opcodes
#define DW_CFA_advance_loc 0x40
#define DW_CFA_offset 0x80
@ -104,7 +72,7 @@ const int kFdeInitialLocationOffset = 8;
const int kFdeAddressRangeOffset = 16;
// Define canonical frame address expression as [reg + offset]
static char* defineCfaExpression(char* pos, int dwReg, uint32_t stackOffset)
static uint8_t* defineCfaExpression(uint8_t* pos, int dwReg, uint32_t stackOffset)
{
pos = writeu8(pos, DW_CFA_def_cfa);
pos = writeuleb128(pos, dwReg);
@ -113,14 +81,14 @@ static char* defineCfaExpression(char* pos, int dwReg, uint32_t stackOffset)
}
// Update offset value in canonical frame address expression
static char* defineCfaExpressionOffset(char* pos, uint32_t stackOffset)
static uint8_t* defineCfaExpressionOffset(uint8_t* pos, uint32_t stackOffset)
{
pos = writeu8(pos, DW_CFA_def_cfa_offset);
pos = writeuleb128(pos, stackOffset);
return pos;
}
static char* defineSavedRegisterLocation(char* pos, int dwReg, uint32_t stackOffset)
static uint8_t* defineSavedRegisterLocation(uint8_t* pos, int dwReg, uint32_t stackOffset)
{
LUAU_ASSERT(stackOffset % kDataAlignFactor == 0 && "stack offsets have to be measured in kDataAlignFactor units");
@ -138,14 +106,14 @@ static char* defineSavedRegisterLocation(char* pos, int dwReg, uint32_t stackOff
return pos;
}
static char* advanceLocation(char* pos, uint8_t offset)
static uint8_t* advanceLocation(uint8_t* pos, uint8_t offset)
{
pos = writeu8(pos, DW_CFA_advance_loc1);
pos = writeu8(pos, offset);
return pos;
}
static char* alignPosition(char* start, char* pos)
static uint8_t* alignPosition(uint8_t* start, uint8_t* pos)
{
size_t size = pos - start;
size_t pad = ((size + kDwarfAlign - 1) & ~(kDwarfAlign - 1)) - size;
@ -163,7 +131,7 @@ namespace CodeGen
void UnwindBuilderDwarf2::start()
{
char* cieLength = pos;
uint8_t* cieLength = pos;
pos = writeu32(pos, 0); // Length (to be filled later)
pos = writeu32(pos, 0); // CIE id. 0 -- .eh_frame
@ -245,8 +213,8 @@ void UnwindBuilderDwarf2::finalize(char* target, void* funcAddress, size_t funcS
memcpy(target, rawData, getSize());
unsigned fdeEntryStartPos = unsigned(fdeEntryStart - rawData);
writeu64(target + fdeEntryStartPos + kFdeInitialLocationOffset, uintptr_t(funcAddress));
writeu64(target + fdeEntryStartPos + kFdeAddressRangeOffset, funcSize);
writeu64((uint8_t*)target + fdeEntryStartPos + kFdeInitialLocationOffset, uintptr_t(funcAddress));
writeu64((uint8_t*)target + fdeEntryStartPos + kFdeAddressRangeOffset, funcSize);
}
} // namespace CodeGen

View file

@ -3,6 +3,7 @@
#include "BuiltinFolding.h"
#include <vector>
#include <math.h>
namespace Luau

View file

@ -105,7 +105,7 @@ endif
$(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include
$(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include
$(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include
$(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include
$(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals
$(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include
$(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include
$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -ICodeGen/include -IVM/include -ICLI -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY

View file

@ -69,8 +69,13 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/AssemblyBuilderX64.cpp
CodeGen/src/CodeAllocator.cpp
CodeGen/src/CodeBlockUnwind.cpp
CodeGen/src/Fallbacks.cpp
CodeGen/src/UnwindBuilderDwarf2.cpp
CodeGen/src/UnwindBuilderWin.cpp
CodeGen/src/ByteUtils.h
CodeGen/src/Fallbacks.h
CodeGen/src/FallbacksProlog.h
)
# Luau.Analysis Sources

View file

@ -304,6 +304,7 @@ LUA_API size_t lua_totalbytes(lua_State* L, int category);
LUA_API l_noret lua_error(lua_State* L);
LUA_API int lua_next(lua_State* L, int idx);
LUA_API int lua_rawiter(lua_State* L, int idx, int iter);
LUA_API void lua_concat(lua_State* L, int n);

View file

@ -109,6 +109,11 @@
#define LUA_MAXCAPTURES 32
#endif
// enables callbacks to redirect code execution from Luau VM to a custom implementation
#ifndef LUA_CUSTOM_EXECUTION
#define LUA_CUSTOM_EXECUTION 0
#endif
// }==================================================================
/*

View file

@ -51,6 +51,12 @@ const char* luau_ident = "$Luau: Copyright (C) 2019-2022 Roblox Corporation $\n"
L->top++; \
}
#define api_update_top(L, p) \
{ \
api_check(L, p >= L->base && p < L->ci->top); \
L->top = p; \
}
#define updateatom(L, ts) \
{ \
if (ts->atom == ATOM_UNDEF) \
@ -851,7 +857,7 @@ void lua_rawsetfield(lua_State* L, int idx, const char* k)
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
if (hvalue(t)->readonly)
luaG_runerror(L, "Attempt to modify a readonly table");
luaG_readonlyerror(L);
setobj2t(L, luaH_setstr(L, hvalue(t), luaS_new(L, k)), L->top - 1);
luaC_barriert(L, hvalue(t), L->top - 1);
L->top--;
@ -1204,6 +1210,52 @@ int lua_next(lua_State* L, int idx)
return more;
}
int lua_rawiter(lua_State* L, int idx, int iter)
{
luaC_threadbarrier(L);
StkId t = index2addr(L, idx);
api_check(L, ttistable(t));
api_check(L, iter >= 0);
Table* h = hvalue(t);
int sizearray = h->sizearray;
// first we advance iter through the array portion
for (; unsigned(iter) < unsigned(sizearray); ++iter)
{
TValue* e = &h->array[iter];
if (!ttisnil(e))
{
StkId top = L->top;
setnvalue(top + 0, double(iter + 1));
setobj2s(L, top + 1, e);
api_update_top(L, top + 2);
return iter + 1;
}
}
int sizenode = 1 << h->lsizenode;
// then we advance iter through the hash portion
for (; unsigned(iter - sizearray) < unsigned(sizenode); ++iter)
{
LuaNode* n = &h->node[iter - sizearray];
if (!ttisnil(gval(n)))
{
StkId top = L->top;
getnodekey(L, top + 0, n);
setobj2s(L, top + 1, gval(n));
api_update_top(L, top + 2);
return iter + 1;
}
}
// traversal finished
return -1;
}
void lua_concat(lua_State* L, int n)
{
api_checknelems(L, n);
@ -1382,7 +1434,7 @@ void lua_cleartable(lua_State* L, int idx)
api_check(L, ttistable(t));
Table* tt = hvalue(t);
if (tt->readonly)
luaG_runerror(L, "Attempt to modify a readonly table");
luaG_readonlyerror(L);
luaH_clear(tt);
}

View file

@ -340,6 +340,11 @@ void luaG_breakpoint(lua_State* L, Proto* p, int line, bool enable)
p->code[i] |= op;
LUAU_ASSERT(LUAU_INSN_OP(p->code[i]) == op);
#if LUA_CUSTOM_EXECUTION
if (L->global->ecb.setbreakpoint)
L->global->ecb.setbreakpoint(L, p, i);
#endif
// note: this is important!
// we only patch the *first* instruction in each proto that's attributed to a given line
// this can be changed, but if requires making patching a bit more nuanced so that we don't patch AUX words

View file

@ -31,6 +31,11 @@ Proto* luaF_newproto(lua_State* L)
f->source = NULL;
f->debugname = NULL;
f->debuginsn = NULL;
#if LUA_CUSTOM_EXECUTION
f->execdata = NULL;
#endif
return f;
}
@ -149,6 +154,15 @@ void luaF_freeproto(lua_State* L, Proto* f, lua_Page* page)
luaM_freearray(L, f->upvalues, f->sizeupvalues, TString*, f->memcat);
if (f->debuginsn)
luaM_freearray(L, f->debuginsn, f->sizecode, uint8_t, f->memcat);
#if LUA_CUSTOM_EXECUTION
if (f->execdata)
{
LUAU_ASSERT(L->global->ecb.destroy);
L->global->ecb.destroy(L, f);
}
#endif
luaM_freegco(L, f, sizeof(Proto), f->memcat, page);
}

View file

@ -281,6 +281,10 @@ typedef struct Proto
TString* debugname;
uint8_t* debuginsn; // a copy of code[] array with just opcodes
#if LUA_CUSTOM_EXECUTION
void* execdata;
#endif
GCObject* gclist;

View file

@ -212,6 +212,11 @@ lua_State* lua_newstate(lua_Alloc f, void* ud)
g->memcatbytes[0] = sizeof(LG);
g->cb = lua_Callbacks();
#if LUA_CUSTOM_EXECUTION
g->ecb = lua_ExecutionCallbacks();
#endif
g->gcstats = GCStats();
#ifdef LUAI_GCMETRICS

View file

@ -146,6 +146,19 @@ struct GCMetrics
};
#endif
#if LUA_CUSTOM_EXECUTION
// Callbacks that can be used to to redirect code execution from Luau bytecode VM to a custom implementation (AoT/JiT/sandboxing/...)
typedef struct lua_ExecutionCallbacks
{
void* context;
void (*destroy)(lua_State* L, Proto* proto); // called when function is destroyed
int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM
void (*setbreakpoint)(lua_State* L, Proto* proto, int line); // called when a breakpoint is set in a function
} lua_ExecutionCallbacks;
#endif
/*
** `global state', shared by all threads of this state
*/
@ -202,6 +215,10 @@ typedef struct global_State
lua_Callbacks cb;
#if LUA_CUSTOM_EXECUTION
lua_ExecutionCallbacks ecb;
#endif
GCStats gcstats;
#ifdef LUAI_GCMETRICS

View file

@ -24,6 +24,9 @@ LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId v
LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val);
LUAI_FUNC void luaV_concat(lua_State* L, int total, int last);
LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, 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);
LUAI_FUNC void luau_execute(lua_State* L);
LUAI_FUNC int luau_precall(lua_State* L, struct lua_TValue* func, int nresults);

View file

@ -131,84 +131,6 @@
goto dispatchContinue
#endif
LUAU_NOINLINE static void luau_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit)
{
if (!ttisnumber(pinit) && !luaV_tonumber(pinit, pinit))
luaG_forerror(L, pinit, "initial value");
if (!ttisnumber(plimit) && !luaV_tonumber(plimit, plimit))
luaG_forerror(L, plimit, "limit");
if (!ttisnumber(pstep) && !luaV_tonumber(pstep, pstep))
luaG_forerror(L, pstep, "step");
}
// calls a C function f with no yielding support; optionally save one resulting value to the res register
// the function and arguments have to already be pushed to L->top
LUAU_NOINLINE static void luau_callTM(lua_State* L, int nparams, int res)
{
++L->nCcalls;
if (L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
luaD_checkstack(L, LUA_MINSTACK);
StkId top = L->top;
StkId fun = top - nparams - 1;
CallInfo* ci = incr_ci(L);
ci->func = fun;
ci->base = fun + 1;
ci->top = top + LUA_MINSTACK;
ci->savedpc = NULL;
ci->flags = 0;
ci->nresults = (res >= 0);
LUAU_ASSERT(ci->top <= L->stack_last);
LUAU_ASSERT(ttisfunction(ci->func));
LUAU_ASSERT(clvalue(ci->func)->isC);
L->base = fun + 1;
LUAU_ASSERT(L->top == L->base + nparams);
lua_CFunction func = clvalue(fun)->c.f;
int n = func(L);
LUAU_ASSERT(n >= 0); // yields should have been blocked by nCcalls
// ci is our callinfo, cip is our parent
// note that we read L->ci again since it may have been reallocated by the call
CallInfo* cip = L->ci - 1;
// copy return value into parent stack
if (res >= 0)
{
if (n > 0)
{
setobj2s(L, &cip->base[res], L->top - n);
}
else
{
setnilvalue(&cip->base[res]);
}
}
L->ci = cip;
L->base = cip->base;
L->top = cip->top;
--L->nCcalls;
}
LUAU_NOINLINE static void luau_tryfuncTM(lua_State* L, StkId func)
{
const TValue* tm = luaT_gettmbyobj(L, func, TM_CALL);
if (!ttisfunction(tm))
luaG_typeerror(L, func, "call");
for (StkId p = L->top; p > func; p--) // open space for metamethod
setobjs2s(L, p, p - 1);
L->top++; // stack space pre-allocated by the caller
setobj2s(L, func, tm); // tag method is the new function to be called
}
LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata)
{
ptrdiff_t base = savestack(L, L->base);
@ -284,6 +206,20 @@ static void luau_execute(lua_State* L)
LUAU_ASSERT(L->isactive);
LUAU_ASSERT(!isblack(obj2gco(L))); // we don't use luaC_threadbarrier because active threads never turn black
#if LUA_CUSTOM_EXECUTION
Proto* p = clvalue(L->ci->func)->l.p;
if (p->execdata)
{
if (L->global->ecb.enter(L, p) == 0)
return;
}
reentry:
#endif
LUAU_ASSERT(isLua(L->ci));
pc = L->ci->savedpc;
cl = clvalue(L->ci->func);
base = L->base;
@ -564,7 +500,7 @@ static void luau_execute(lua_State* L)
L->top = top + 3;
L->cachedslot = LUAU_INSN_C(insn);
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
VM_PATCH_C(pc - 2, L->cachedslot);
VM_NEXT();
@ -601,7 +537,7 @@ static void luau_execute(lua_State* L)
L->top = top + 3;
L->cachedslot = LUAU_INSN_C(insn);
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
VM_PATCH_C(pc - 2, L->cachedslot);
VM_NEXT();
@ -680,7 +616,7 @@ static void luau_execute(lua_State* L)
L->top = top + 4;
L->cachedslot = LUAU_INSN_C(insn);
VM_PROTECT(luau_callTM(L, 3, -1));
VM_PROTECT(luaV_callTM(L, 3, -1));
// save cachedslot to accelerate future lookups; patches currently executing instruction since pc-2 rolls back two pc++
VM_PATCH_C(pc - 2, L->cachedslot);
VM_NEXT();
@ -973,7 +909,7 @@ static void luau_execute(lua_State* L)
// slow-path: not a function call
if (LUAU_UNLIKELY(!ttisfunction(ra)))
{
VM_PROTECT(luau_tryfuncTM(L, ra));
VM_PROTECT(luaV_tryfuncTM(L, ra));
argtop++; // __call adds an extra self
}
@ -1009,6 +945,18 @@ static void luau_execute(lua_State* L)
setnilvalue(argi++); // complete missing arguments
L->top = p->is_vararg ? argi : ci->top;
#if LUA_CUSTOM_EXECUTION
if (p->execdata)
{
LUAU_ASSERT(L->global->ecb.enter);
if (L->global->ecb.enter(L, p) == 1)
goto reentry;
else
goto exit;
}
#endif
// reentry
pc = p->code;
cl = ccl;
@ -1092,11 +1040,26 @@ static void luau_execute(lua_State* L)
LUAU_ASSERT(isLua(L->ci));
Closure* nextcl = clvalue(cip->func);
Proto* nextproto = nextcl->l.p;
#if LUA_CUSTOM_EXECUTION
if (nextproto->execdata)
{
LUAU_ASSERT(L->global->ecb.enter);
if (L->global->ecb.enter(L, nextproto) == 1)
goto reentry;
else
goto exit;
}
#endif
// reentry
pc = cip->savedpc;
cl = clvalue(cip->func);
cl = nextcl;
base = L->base;
k = cl->l.p->k;
k = nextproto->k;
VM_NEXT();
}
@ -1212,7 +1175,7 @@ static void luau_execute(lua_State* L)
int res = int(top - base);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, res));
VM_PROTECT(luaV_callTM(L, 2, res));
pc += !l_isfalse(&base[res]) ? LUAU_INSN_D(insn) : 1;
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
@ -1324,7 +1287,7 @@ static void luau_execute(lua_State* L)
int res = int(top - base);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, res));
VM_PROTECT(luaV_callTM(L, 2, res));
pc += l_isfalse(&base[res]) ? LUAU_INSN_D(insn) : 1;
LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode));
VM_NEXT();
@ -1519,7 +1482,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, rc);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -1565,7 +1528,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, rc);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -1626,7 +1589,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, rc);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -1687,7 +1650,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, rc);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -1819,7 +1782,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, kv);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -1865,7 +1828,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 2, kv);
L->top = top + 3;
VM_PROTECT(luau_callTM(L, 2, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 2, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -2029,7 +1992,7 @@ static void luau_execute(lua_State* L)
setobj2s(L, top + 1, rb);
L->top = top + 2;
VM_PROTECT(luau_callTM(L, 1, LUAU_INSN_A(insn)));
VM_PROTECT(luaV_callTM(L, 1, LUAU_INSN_A(insn)));
VM_NEXT();
}
else
@ -2145,7 +2108,7 @@ static void luau_execute(lua_State* L)
// Note: this doesn't reallocate stack so we don't need to recompute ra/base
VM_PROTECT_PC();
luau_prepareFORN(L, ra + 0, ra + 1, ra + 2);
luaV_prepareFORN(L, ra + 0, ra + 1, ra + 2);
}
double limit = nvalue(ra + 0);
@ -2861,7 +2824,7 @@ int luau_precall(lua_State* L, StkId func, int nresults)
{
if (!ttisfunction(func))
{
luau_tryfuncTM(L, func);
luaV_tryfuncTM(L, func);
// L->top is incremented by tryfuncTM
}

View file

@ -508,3 +508,81 @@ void luaV_dolen(lua_State* L, StkId ra, const TValue* rb)
if (!ttisnumber(res))
luaG_runerror(L, "'__len' must return a number"); // note, we can't access rb since stack may have been reallocated
}
LUAU_NOINLINE void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit)
{
if (!ttisnumber(pinit) && !luaV_tonumber(pinit, pinit))
luaG_forerror(L, pinit, "initial value");
if (!ttisnumber(plimit) && !luaV_tonumber(plimit, plimit))
luaG_forerror(L, plimit, "limit");
if (!ttisnumber(pstep) && !luaV_tonumber(pstep, pstep))
luaG_forerror(L, pstep, "step");
}
// calls a C function f with no yielding support; optionally save one resulting value to the res register
// the function and arguments have to already be pushed to L->top
LUAU_NOINLINE void luaV_callTM(lua_State* L, int nparams, int res)
{
++L->nCcalls;
if (L->nCcalls >= LUAI_MAXCCALLS)
luaD_checkCstack(L);
luaD_checkstack(L, LUA_MINSTACK);
StkId top = L->top;
StkId fun = top - nparams - 1;
CallInfo* ci = incr_ci(L);
ci->func = fun;
ci->base = fun + 1;
ci->top = top + LUA_MINSTACK;
ci->savedpc = NULL;
ci->flags = 0;
ci->nresults = (res >= 0);
LUAU_ASSERT(ci->top <= L->stack_last);
LUAU_ASSERT(ttisfunction(ci->func));
LUAU_ASSERT(clvalue(ci->func)->isC);
L->base = fun + 1;
LUAU_ASSERT(L->top == L->base + nparams);
lua_CFunction func = clvalue(fun)->c.f;
int n = func(L);
LUAU_ASSERT(n >= 0); // yields should have been blocked by nCcalls
// ci is our callinfo, cip is our parent
// note that we read L->ci again since it may have been reallocated by the call
CallInfo* cip = L->ci - 1;
// copy return value into parent stack
if (res >= 0)
{
if (n > 0)
{
setobj2s(L, &cip->base[res], L->top - n);
}
else
{
setnilvalue(&cip->base[res]);
}
}
L->ci = cip;
L->base = cip->base;
L->top = cip->top;
--L->nCcalls;
}
LUAU_NOINLINE void luaV_tryfuncTM(lua_State* L, StkId func)
{
const TValue* tm = luaT_gettmbyobj(L, func, TM_CALL);
if (!ttisfunction(tm))
luaG_typeerror(L, func, "call");
for (StkId p = L->top; p > func; p--) // open space for metamethod
setobjs2s(L, p, p - 1);
L->top++; // stack space pre-allocated by the caller
setobj2s(L, func, tm); // tag method is the new function to be called
}

View file

@ -62,6 +62,29 @@ LOADN R0 5
LOADK R1 K0
RETURN R0 2
)");
CHECK_EQ("\n" + bcb.dumpEverything(), R"(
Function 0 (??):
LOADN R0 5
LOADK R1 K0
RETURN R0 2
)");
}
TEST_CASE("CompileError")
{
std::string source = "local " + rep("a,", 300) + "a = ...";
// fails to parse
std::string bc1 = Luau::compile(source + " !#*$!#$^&!*#&$^*");
// parses, but fails to compile (too many locals)
std::string bc2 = Luau::compile(source);
// 0 acts as a special marker for error bytecode
CHECK_EQ(bc1[0], 0);
CHECK_EQ(bc2[0], 0);
}
TEST_CASE("LocalsDirectReference")
@ -1230,6 +1253,27 @@ RETURN R0 0
)");
}
TEST_CASE("UnaryBasic")
{
CHECK_EQ("\n" + compileFunction0("local a = ... return not a"), R"(
GETVARARGS R0 1
NOT R1 R0
RETURN R1 1
)");
CHECK_EQ("\n" + compileFunction0("local a = ... return -a"), R"(
GETVARARGS R0 1
MINUS R1 R0
RETURN R1 1
)");
CHECK_EQ("\n" + compileFunction0("local a = ... return #a"), R"(
GETVARARGS R0 1
LENGTH R1 R0
RETURN R1 1
)");
}
TEST_CASE("InterpStringWithNoExpressions")
{
ScopedFastFlag sff{"LuauInterpolatedStringBaseSupport", true};
@ -4975,6 +5019,27 @@ MOVE R1 R0
CALL R1 0 1
RETURN R1 1
)");
// we can't inline any functions in modules with getfenv/setfenv
CHECK_EQ("\n" + compileFunction(R"(
local function foo()
return 42
end
local x = foo()
getfenv()
return x
)",
1, 2),
R"(
DUPCLOSURE R0 K0
MOVE R1 R0
CALL R1 0 1
GETIMPORT R2 2
CALL R2 0 0
RETURN R1 1
)");
}
TEST_CASE("InlineNestedLoops")
@ -6101,6 +6166,7 @@ return
bit32.extract(-1, 31),
bit32.replace(100, 1, 0),
math.log(100, 10),
typeof(nil),
(type("fin"))
)",
0, 2),
@ -6156,7 +6222,8 @@ LOADN R47 1
LOADN R48 101
LOADN R49 2
LOADK R50 K3
RETURN R0 51
LOADK R51 K4
RETURN R0 52
)");
}

View file

@ -786,6 +786,44 @@ TEST_CASE("ApiTables")
lua_pop(L, 1);
}
TEST_CASE("ApiIter")
{
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
lua_newtable(L);
lua_pushnumber(L, 123.0);
lua_setfield(L, -2, "key");
lua_pushnumber(L, 456.0);
lua_rawsetfield(L, -2, "key2");
lua_pushstring(L, "test");
lua_rawseti(L, -2, 1);
// Lua-compatible iteration interface: lua_next
double sum1 = 0;
lua_pushnil(L);
while (lua_next(L, -2))
{
sum1 += lua_tonumber(L, -2); // key
sum1 += lua_tonumber(L, -1); // value
lua_pop(L, 1); // pop value, key is used by lua_next
}
CHECK(sum1 == 580);
// Luau iteration interface: lua_rawiter (faster and preferable to lua_next)
double sum2 = 0;
for (int index = 0; index = lua_rawiter(L, -1, index), index >= 0; )
{
sum2 += lua_tonumber(L, -2); // key
sum2 += lua_tonumber(L, -1); // value
lua_pop(L, 2); // pop both key and value
}
CHECK(sum2 == 580);
// pop table
lua_pop(L, 1);
}
TEST_CASE("ApiCalls")
{
StateRef globalState = runConformance("apicalls.lua");

View file

@ -1705,8 +1705,6 @@ TEST_CASE_FIXTURE(Fixture, "TestStringInterpolation")
TEST_CASE_FIXTURE(Fixture, "IntegerParsing")
{
ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true};
LintResult result = lint(R"(
local _ = 0b10000000000000000000000000000000000000000000000000000000000000000
local _ = 0x10000000000000000
@ -1720,7 +1718,6 @@ local _ = 0x10000000000000000
// TODO: remove with FFlagLuauErrorDoubleHexPrefix
TEST_CASE_FIXTURE(Fixture, "IntegerParsingDoublePrefix")
{
ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true};
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false}; // Lint will be available until we start rejecting code
LintResult result = lint(R"(

View file

@ -2,6 +2,7 @@
#include "Fixture.h"
#include "Luau/Common.h"
#include "doctest.h"
#include "Luau/Normalize.h"
@ -747,7 +748,6 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_union")
{
ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", true},
{"LuauFixNormalizationOfCyclicUnions", true},
};
CheckResult result = check(R"(
@ -765,7 +765,6 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_intersection")
{
ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", true},
{"LuauFixNormalizationOfCyclicUnions", true},
};
CheckResult result = check(R"(
@ -784,7 +783,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_indexers")
{
ScopedFastFlag sff[] = {
{"LuauLowerBoundsCalculation", true},
{"LuauFixNormalizationOfCyclicUnions", true},
};
CheckResult result = check(R"(

View file

@ -682,7 +682,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_binary")
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
{
ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true};
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", true};
CHECK_EQ(getParseError("return 0b123"), "Malformed number");
@ -695,7 +694,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_numbers_error")
TEST_CASE_FIXTURE(Fixture, "parse_numbers_error_soft")
{
ScopedFastFlag luauLintParseIntegerIssues{"LuauLintParseIntegerIssues", true};
ScopedFastFlag luauErrorDoubleHexPrefix{"LuauErrorDoubleHexPrefix", false};
CHECK_EQ(getParseError("return 0x0x0x0x0x0x0x0"), "Malformed number");

View file

@ -198,8 +198,12 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const char* expectedError = "Type '{ v: string }' could not be converted into 'T<number>'\n"
"caused by:\n"
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
CHECK(toString(result.errors[0]) == "Type '{ v: string }' could not be converted into 'T<number>'");
CHECK(toString(result.errors[0]) == expectedError);
}
TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
@ -215,8 +219,14 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const char* expectedError = "Type '{ t: { v: string } }' could not be converted into 'U<number>'\n"
"caused by:\n"
" Property 't' is not compatible. Type '{ v: string }' could not be converted into 'T<number>'\n"
"caused by:\n"
" Property 'v' is not compatible. Type 'string' could not be converted into 'number'";
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
CHECK(toString(result.errors[0]) == "Type '{ t: { v: string } }' could not be converted into 'U<number>'");
CHECK(toString(result.errors[0]) == expectedError);
}
TEST_CASE_FIXTURE(Fixture, "mutually_recursive_generic_aliases")

View file

@ -558,11 +558,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "greedy_inference_with_shared_self_triggers_f
CHECK_EQ("Not all codepaths in this function return 'self, a...'.", toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "dcr_cant_partially_dispatch_a_constraint")
TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
{"LuauSpecialTypesAsterisked", true},
};
CheckResult result = check(R"(
@ -577,7 +576,6 @@ TEST_CASE_FIXTURE(Fixture, "dcr_cant_partially_dispatch_a_constraint")
LUAU_REQUIRE_NO_ERRORS(result);
// We should be able to resolve this to number, but we're not there yet.
// Solving this requires recognizing that we can partially solve the
// following constraint:
//
@ -586,7 +584,7 @@ TEST_CASE_FIXTURE(Fixture, "dcr_cant_partially_dispatch_a_constraint")
// The correct thing for us to do is to consider the constraint dispatched,
// but we need to also record a new constraint number <: *blocked* to finish
// the job later.
CHECK("<a>(a, *error-type*) -> ()" == toString(requireType("prime_iter")));
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
}
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")

View file

@ -314,6 +314,31 @@ caused by:
toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
};
CheckResult result = check(R"(
type Ok<T> = {success: true, result: T}
type Err<T> = {success: false, error: T}
type Result<O, E> = Ok<O> | Err<E>
local a : Result<string, number> = {success = false, result = "hotdogs"}
local b : Result<string, number> = {success = true, result = "hotdogs"}
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expectedError = "Type '{ result: string, success: false }' could not be converted into 'Err<number> | Ok<string>'\n"
"caused by:\n"
" None of the union options are compatible. For example: Table type '{ result: string, success: false }'"
" not compatible with type 'Err<number>' because the former is missing field 'error'";
CHECK(toString(result.errors[0]) == expectedError);
}
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")
{
CheckResult result = check(R"(

View file

@ -1082,7 +1082,7 @@ TEST_CASE_FIXTURE(Fixture, "do_not_bind_a_free_table_to_a_union_containing_that_
)");
}
TEST_CASE_FIXTURE(Fixture, "types stored in astResolvedTypes")
TEST_CASE_FIXTURE(Fixture, "types_stored_in_astResolvedTypes")
{
CheckResult result = check(R"(
type alias = typeof("hello")
@ -1122,4 +1122,41 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_higher_order_function")
CHECK(location.end.line == 4);
}
TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
{
ScopedFastFlag sff[] = {
{"DebugLuauDeferredConstraintResolution", true},
};
CheckResult result = check(R"(
local function hasDivisors(value: number)
end
function prime_iter(state, index)
hasDivisors(index)
index += 1
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
// Solving this requires recognizing that we can't dispatch a constraint
// like this without doing further work:
//
// (*blocked*) -> () <: (number) -> (b...)
//
// We solve this by searching both types for BlockedTypeVars and block the
// constraint on any we find. It also gets the job done, but I'm worried
// about the efficiency of doing so many deep type traversals and it may
// make us more prone to getting stuck on constraint cycles.
//
// If this doesn't pan out, a possible solution is to go further down the
// path of supporting partial constraint dispatch. The way it would work is
// that we'd dispatch the above constraint by binding b... to (), but we
// would append a new constraint number <: *blocked* to the constraint set
// to be solved later. This should be faster and theoretically less prone
// to cyclic constraint dependencies.
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
}
TEST_SUITE_END();

View file

@ -10,7 +10,6 @@ AnnotationTests.luau_ice_triggers_an_ice_handler
AnnotationTests.luau_print_is_magic_if_the_flag_is_set
AnnotationTests.occurs_check_on_cyclic_intersection_typevar
AnnotationTests.occurs_check_on_cyclic_union_typevar
AnnotationTests.too_many_type_params
AnnotationTests.two_type_params
AnnotationTests.use_type_required_from_another_file
AstQuery.last_argument_function_call_type
@ -28,7 +27,6 @@ AutocompleteTest.autocomplete_interpolated_string
AutocompleteTest.autocomplete_on_string_singletons
AutocompleteTest.autocomplete_oop_implicit_self
AutocompleteTest.autocomplete_repeat_middle_keyword
AutocompleteTest.autocomplete_string_singleton_equality
AutocompleteTest.autocomplete_string_singleton_escape
AutocompleteTest.autocomplete_string_singletons
AutocompleteTest.autocomplete_while_middle_keywords
@ -85,7 +83,6 @@ AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
AutocompleteTest.type_correct_expected_argument_type_suggestion
AutocompleteTest.type_correct_expected_argument_type_suggestion_optional
AutocompleteTest.type_correct_expected_argument_type_suggestion_self
AutocompleteTest.type_correct_expected_return_type_pack_suggestion
AutocompleteTest.type_correct_expected_return_type_suggestion
AutocompleteTest.type_correct_full_type_suggestion
AutocompleteTest.type_correct_function_no_parenthesis
@ -113,7 +110,6 @@ BuiltinTests.dont_add_definitions_to_persistent_types
BuiltinTests.find_capture_types
BuiltinTests.find_capture_types2
BuiltinTests.find_capture_types3
BuiltinTests.getfenv
BuiltinTests.global_singleton_types_are_sealed
BuiltinTests.gmatch_capture_types
BuiltinTests.gmatch_capture_types2
@ -130,13 +126,9 @@ BuiltinTests.next_iterator_should_infer_types_and_type_check
BuiltinTests.os_time_takes_optional_date_table
BuiltinTests.pairs_iterator_should_infer_types_and_type_check
BuiltinTests.see_thru_select
BuiltinTests.see_thru_select_count
BuiltinTests.select_on_variadic
BuiltinTests.select_slightly_out_of_range
BuiltinTests.select_way_out_of_range
BuiltinTests.select_with_decimal_argument_is_rounded_down
BuiltinTests.select_with_variadic_typepack_tail
BuiltinTests.select_with_variadic_typepack_tail_and_string_head
BuiltinTests.set_metatable_needs_arguments
BuiltinTests.setmetatable_should_not_mutate_persisted_types
BuiltinTests.sort
@ -147,20 +139,16 @@ BuiltinTests.string_format_arg_types_inference
BuiltinTests.string_format_as_method
BuiltinTests.string_format_correctly_ordered_types
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
BuiltinTests.string_format_tostring_specifier
BuiltinTests.string_format_tostring_specifier_type_constraint
BuiltinTests.string_format_use_correct_argument
BuiltinTests.string_format_use_correct_argument2
BuiltinTests.string_format_use_correct_argument3
BuiltinTests.string_lib_self_noself
BuiltinTests.table_concat_returns_string
BuiltinTests.table_dot_remove_optionally_returns_generic
BuiltinTests.table_freeze_is_generic
BuiltinTests.table_insert_correctly_infers_type_of_array_2_args_overload
BuiltinTests.table_insert_correctly_infers_type_of_array_3_args_overload
BuiltinTests.table_pack
BuiltinTests.table_pack_reduce
BuiltinTests.table_pack_variadic
BuiltinTests.tonumber_returns_optional_number_type
BuiltinTests.tonumber_returns_optional_number_type2
DefinitionTests.class_definition_overload_metamethods
@ -168,7 +156,6 @@ DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
FrontendTest.ast_node_at_position
FrontendTest.automatically_check_dependent_scripts
FrontendTest.dont_reparse_clean_file_when_linting
FrontendTest.environments
FrontendTest.imported_table_modification_2
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
@ -187,11 +174,8 @@ GenericsTests.check_mutual_generic_functions
GenericsTests.correctly_instantiate_polymorphic_member_functions
GenericsTests.do_not_always_instantiate_generic_intersection_types
GenericsTests.do_not_infer_generic_functions
GenericsTests.dont_unify_bound_types
GenericsTests.duplicate_generic_type_packs
GenericsTests.duplicate_generic_types
GenericsTests.error_detailed_function_mismatch_generic_pack
GenericsTests.error_detailed_function_mismatch_generic_types
GenericsTests.factories_of_generics
GenericsTests.generic_argument_count_too_few
GenericsTests.generic_argument_count_too_many
@ -205,30 +189,22 @@ GenericsTests.generic_type_pack_unification2
GenericsTests.generic_type_pack_unification3
GenericsTests.infer_generic_function_function_argument
GenericsTests.infer_generic_function_function_argument_overloaded
GenericsTests.infer_generic_lib_function_function_argument
GenericsTests.infer_generic_methods
GenericsTests.inferred_local_vars_can_be_polytypes
GenericsTests.instantiate_cyclic_generic_function
GenericsTests.instantiate_generic_function_in_assignments
GenericsTests.instantiate_generic_function_in_assignments2
GenericsTests.instantiated_function_argument_names
GenericsTests.instantiation_sharing_types
GenericsTests.local_vars_can_be_instantiated_polytypes
GenericsTests.mutable_state_polymorphism
GenericsTests.no_stack_overflow_from_quantifying
GenericsTests.properties_can_be_instantiated_polytypes
GenericsTests.rank_N_types_via_typeof
GenericsTests.reject_clashing_generic_and_pack_names
GenericsTests.self_recursive_instantiated_param
IntersectionTypes.argument_is_intersection
IntersectionTypes.error_detailed_intersection_all
IntersectionTypes.error_detailed_intersection_part
IntersectionTypes.fx_intersection_as_argument
IntersectionTypes.fx_union_as_argument_fails
IntersectionTypes.index_on_an_intersection_type_with_mixed_types
IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist
IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth
IntersectionTypes.no_stack_overflow_from_flattenintersection
IntersectionTypes.overload_is_not_a_function
IntersectionTypes.select_correct_union_fn
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
IntersectionTypes.table_intersection_write_sealed
@ -236,17 +212,13 @@ IntersectionTypes.table_intersection_write_sealed_indirect
IntersectionTypes.table_write_sealed_indirect
isSubtype.intersection_of_tables
isSubtype.table_with_table_prop
Linter.TableOperations
ModuleTests.clone_self_property
ModuleTests.deepClone_cyclic_table
ModuleTests.do_not_clone_reexports
NonstrictModeTests.delay_function_does_not_require_its_argument_to_return_anything
NonstrictModeTests.for_in_iterator_variables_are_any
NonstrictModeTests.function_parameters_are_any
NonstrictModeTests.inconsistent_module_return_types_are_ok
NonstrictModeTests.inconsistent_return_types_are_ok
NonstrictModeTests.infer_nullary_function
NonstrictModeTests.infer_the_maximum_number_of_values_the_function_could_return
NonstrictModeTests.inline_table_props_are_also_any
NonstrictModeTests.local_tables_are_not_any
NonstrictModeTests.locals_are_any_by_default
@ -294,20 +266,16 @@ ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_ret
ProvisionalTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
ProvisionalTests.it_should_be_agnostic_of_actual_size
ProvisionalTests.lower_bounds_calculation_is_too_permissive_with_overloaded_higher_order_functions
ProvisionalTests.lvalue_equals_another_lvalue_with_no_overlap
ProvisionalTests.normalization_fails_on_certain_kinds_of_cyclic_tables
ProvisionalTests.operator_eq_completely_incompatible
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
ProvisionalTests.typeguard_inference_incomplete
ProvisionalTests.weirditer_should_not_loop_forever
ProvisionalTests.while_body_are_also_refined
RefinementTest.and_constraint
RefinementTest.and_or_peephole_refinement
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
RefinementTest.assert_non_binary_expressions_actually_resolve_constraints
RefinementTest.assign_table_with_refined_property_with_a_similar_type_is_illegal
RefinementTest.call_a_more_specific_function_using_typeguard
RefinementTest.correctly_lookup_a_shadowed_local_that_which_was_previously_refined
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
@ -319,24 +287,19 @@ RefinementTest.discriminate_tag
RefinementTest.either_number_or_string
RefinementTest.eliminate_subclasses_of_instance
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
RefinementTest.free_type_is_equal_to_an_lvalue
RefinementTest.impossible_type_narrow_is_not_an_error
RefinementTest.index_on_a_refined_property
RefinementTest.invert_is_truthy_constraint
RefinementTest.invert_is_truthy_constraint_ifelse_expression
RefinementTest.is_truthy_constraint
RefinementTest.is_truthy_constraint_ifelse_expression
RefinementTest.lvalue_is_equal_to_a_term
RefinementTest.lvalue_is_equal_to_another_lvalue
RefinementTest.lvalue_is_not_nil
RefinementTest.merge_should_be_fully_agnostic_of_hashmap_ordering
RefinementTest.narrow_boolean_to_true_or_false
RefinementTest.narrow_property_of_a_bounded_variable
RefinementTest.narrow_this_large_union
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
RefinementTest.not_a_and_not_b
RefinementTest.not_a_and_not_b2
RefinementTest.not_a_or_not_b
RefinementTest.not_a_or_not_b2
RefinementTest.not_and_constraint
RefinementTest.not_t_or_some_prop_of_t
RefinementTest.or_predicate_with_truthy_predicates
@ -344,7 +307,6 @@ RefinementTest.parenthesized_expressions_are_followed_through
RefinementTest.refine_a_property_not_to_be_nil_through_an_intersection_table
RefinementTest.refine_the_correct_types_opposite_of_when_a_is_not_number_or_string
RefinementTest.refine_unknowns
RefinementTest.string_not_equal_to_string_or_nil
RefinementTest.term_is_equal_to_an_lvalue
RefinementTest.truthy_constraint_on_properties
RefinementTest.type_assertion_expr_carry_its_constraints
@ -363,11 +325,9 @@ RefinementTest.typeguard_narrows_for_functions
RefinementTest.typeguard_narrows_for_table
RefinementTest.typeguard_not_to_be_string
RefinementTest.typeguard_only_look_up_types_from_global_scope
RefinementTest.unknown_lvalue_is_not_synonymous_with_other_on_not_equal
RefinementTest.what_nonsensical_condition
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
RefinementTest.x_is_not_instance_or_else_not_part
RuntimeLimits.typescript_port_of_Result_type
TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
TableTests.access_index_metamethod_that_returns_variadic
@ -383,7 +343,6 @@ TableTests.casting_sealed_tables_with_props_into_table_with_indexer
TableTests.casting_tables_with_props_into_table_with_indexer3
TableTests.casting_tables_with_props_into_table_with_indexer4
TableTests.checked_prop_too_early
TableTests.confusing_indexing
TableTests.defining_a_method_for_a_builtin_sealed_table_must_fail
TableTests.defining_a_method_for_a_local_sealed_table_must_fail
TableTests.defining_a_self_method_for_a_builtin_sealed_table_must_fail
@ -393,11 +352,7 @@ TableTests.dont_hang_when_trying_to_look_up_in_cyclic_metatable_index
TableTests.dont_leak_free_table_props
TableTests.dont_quantify_table_that_belongs_to_outer_scope
TableTests.dont_suggest_exact_match_keys
TableTests.error_detailed_indexer_key
TableTests.error_detailed_indexer_value
TableTests.error_detailed_metatable_prop
TableTests.error_detailed_prop
TableTests.error_detailed_prop_nested
TableTests.expected_indexer_from_table_union
TableTests.expected_indexer_value_type_extra
TableTests.expected_indexer_value_type_extra_2
@ -422,11 +377,7 @@ TableTests.infer_indexer_from_value_property_in_literal
TableTests.inferred_return_type_of_free_table
TableTests.inferring_crazy_table_should_also_be_quick
TableTests.instantiate_table_cloning_3
TableTests.instantiate_tables_at_scope_level
TableTests.leaking_bad_metatable_errors
TableTests.length_operator_intersection
TableTests.length_operator_non_table_union
TableTests.length_operator_union
TableTests.length_operator_union_errors
TableTests.less_exponential_blowup_please
TableTests.meta_add
@ -444,16 +395,15 @@ TableTests.open_table_unification_2
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
TableTests.persistent_sealed_table_is_immutable
TableTests.prop_access_on_key_whose_types_mismatches
TableTests.property_lookup_through_tabletypevar_metatable
TableTests.quantify_even_that_table_was_never_exported_at_all
TableTests.quantify_metatables_of_metatables_of_table
TableTests.quantifying_a_bound_var_works
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
TableTests.result_is_always_any_if_lhs_is_any
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
TableTests.right_table_missing_key
TableTests.right_table_missing_key2
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
@ -463,14 +413,11 @@ TableTests.shared_selfs_through_metatables
TableTests.table_indexing_error_location
TableTests.table_insert_should_cope_with_optional_properties_in_nonstrict
TableTests.table_insert_should_cope_with_optional_properties_in_strict
TableTests.table_length
TableTests.table_param_row_polymorphism_2
TableTests.table_param_row_polymorphism_3
TableTests.table_simple_call
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
TableTests.table_unifies_into_map
TableTests.tables_get_names_from_their_locals
TableTests.tc_member_function
TableTests.tc_member_function_2
@ -480,10 +427,8 @@ TableTests.unification_of_unions_in_a_self_referential_type
TableTests.unifying_tables_shouldnt_uaf2
TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon
TableTests.width_subtyping
ToDot.bound_table
ToDot.function
ToDot.metatable
ToDot.table
ToString.exhaustive_toString_of_cyclic_table
ToString.function_type_with_argument_names_generic
@ -500,7 +445,6 @@ TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.typepack_unification_should_trim_free_tails
TryUnifyTests.variadics_should_use_reversed_properly
TypeAliases.cli_38393_recursive_intersection_oom
TypeAliases.forward_declared_alias_is_not_clobbered_by_prior_unification_with_any
TypeAliases.generic_param_remap
TypeAliases.mismatched_generic_pack_type_param
@ -517,29 +461,19 @@ TypeAliases.type_alias_local_mutation
TypeAliases.type_alias_local_rename
TypeAliases.type_alias_of_an_imported_recursive_generic_type
TypeInfer.checking_should_not_ice
TypeInfer.cyclic_follow
TypeInfer.do_not_bind_a_free_table_to_a_union_containing_that_table
TypeInfer.dont_report_type_errors_within_an_AstStatError
TypeInfer.globals
TypeInfer.globals2
TypeInfer.infer_assignment_value_types_mutable_lval
TypeInfer.no_stack_overflow_from_isoptional
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
TypeInfer.tc_if_else_expressions1
TypeInfer.tc_if_else_expressions2
TypeInfer.tc_if_else_expressions_expected_type_1
TypeInfer.tc_if_else_expressions_expected_type_2
TypeInfer.tc_if_else_expressions_expected_type_3
TypeInfer.tc_if_else_expressions_type_union
TypeInfer.tc_interpolated_string_basic
TypeInfer.tc_interpolated_string_constant_type
TypeInfer.tc_interpolated_string_with_invalid_expression
TypeInfer.type_infer_recursion_limit_no_ice
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
TypeInferAnyError.can_get_length_of_any
TypeInferAnyError.for_in_loop_iterator_is_any2
TypeInferAnyError.length_of_error_type_does_not_produce_an_error
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
TypeInferAnyError.union_of_types_regression_test
TypeInferClasses.call_base_method
TypeInferClasses.call_instance_method
@ -549,39 +483,23 @@ TypeInferClasses.class_type_mismatch_with_name_conflict
TypeInferClasses.classes_can_have_overloaded_operators
TypeInferClasses.classes_without_overloaded_operators_cannot_be_added
TypeInferClasses.detailed_class_unification_error
TypeInferClasses.function_arguments_are_covariant
TypeInferClasses.higher_order_function_return_type_is_not_contravariant
TypeInferClasses.higher_order_function_return_values_are_covariant
TypeInferClasses.higher_order_function_arguments_are_contravariant
TypeInferClasses.optional_class_field_access_error
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
TypeInferClasses.table_indexers_are_invariant
TypeInferClasses.table_properties_are_invariant
TypeInferClasses.warn_when_prop_almost_matches
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
TypeInferFunctions.another_recursive_local_function
TypeInferFunctions.call_o_with_another_argument_after_foo_was_quantified
TypeInferFunctions.calling_function_with_anytypepack_doesnt_leak_free_types
TypeInferFunctions.calling_function_with_incorrect_argument_type_yields_errors_spanning_argument
TypeInferFunctions.complicated_return_types_require_an_explicit_annotation
TypeInferFunctions.dont_give_other_overloads_message_if_only_one_argument_matching_overload_exists
TypeInferFunctions.dont_infer_parameter_types_for_functions_from_their_call_site
TypeInferFunctions.duplicate_functions_with_different_signatures_not_allowed_in_nonstrict
TypeInferFunctions.error_detailed_function_mismatch_arg
TypeInferFunctions.error_detailed_function_mismatch_arg_count
TypeInferFunctions.error_detailed_function_mismatch_ret
TypeInferFunctions.error_detailed_function_mismatch_ret_count
TypeInferFunctions.error_detailed_function_mismatch_ret_mult
TypeInferFunctions.free_is_not_bound_to_unknown
TypeInferFunctions.func_expr_doesnt_leak_free
TypeInferFunctions.function_cast_error_uses_correct_language
TypeInferFunctions.function_decl_non_self_sealed_overwrite
TypeInferFunctions.function_decl_non_self_sealed_overwrite_2
TypeInferFunctions.function_decl_non_self_unsealed_overwrite
TypeInferFunctions.function_does_not_return_enough_values
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
TypeInferFunctions.higher_order_function_2
TypeInferFunctions.higher_order_function_4
TypeInferFunctions.ignored_return_values
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
TypeInferFunctions.improved_function_arg_mismatch_errors
TypeInferFunctions.inconsistent_higher_order_function
@ -589,15 +507,11 @@ TypeInferFunctions.inconsistent_return_types
TypeInferFunctions.infer_anonymous_function_arguments
TypeInferFunctions.infer_return_type_from_selected_overload
TypeInferFunctions.infer_that_function_does_not_return_a_table
TypeInferFunctions.it_is_ok_not_to_supply_enough_retvals
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
TypeInferFunctions.no_lossy_function_type
TypeInferFunctions.occurs_check_failure_in_function_return_type
TypeInferFunctions.quantify_constrained_types
TypeInferFunctions.record_matching_overload
TypeInferFunctions.recursive_function
TypeInferFunctions.recursive_local_function
TypeInferFunctions.report_exiting_without_return_nonstrict
TypeInferFunctions.report_exiting_without_return_strict
TypeInferFunctions.return_type_by_overload
@ -608,11 +522,7 @@ TypeInferFunctions.too_few_arguments_variadic_generic2
TypeInferFunctions.too_many_arguments
TypeInferFunctions.too_many_return_values
TypeInferFunctions.vararg_function_is_quantified
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
TypeInferLoops.for_in_loop_with_custom_iterator
TypeInferLoops.for_in_loop_with_next
TypeInferLoops.for_in_with_just_one_iterator_is_ok
TypeInferLoops.loop_iter_iter_metamethod
TypeInferLoops.loop_iter_no_indexer_nonstrict
TypeInferLoops.loop_iter_trailing_nil
TypeInferLoops.loop_typecheck_crash_on_empty_optional
@ -631,7 +541,6 @@ TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
TypeInferOOP.methods_are_topologically_sorted
TypeInferOperators.and_adds_boolean
TypeInferOperators.and_adds_boolean_no_superfluous_union
TypeInferOperators.and_binexps_dont_unify
@ -641,13 +550,10 @@ TypeInferOperators.cannot_compare_tables_that_do_not_have_the_same_metatable
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_have_a_metatable
TypeInferOperators.cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators
TypeInferOperators.cli_38355_recursive_union
TypeInferOperators.compare_numbers
TypeInferOperators.compare_strings
TypeInferOperators.compound_assign_mismatch_metatable
TypeInferOperators.compound_assign_mismatch_op
TypeInferOperators.compound_assign_mismatch_result
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
TypeInferOperators.dont_strip_nil_from_rhs_or_operator
TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds
@ -655,18 +561,12 @@ TypeInferOperators.error_on_invalid_operand_types_to_relational_operators
TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2
TypeInferOperators.expected_types_through_binary_and
TypeInferOperators.expected_types_through_binary_or
TypeInferOperators.in_nonstrict_mode_strip_nil_from_intersections_when_considering_relational_operators
TypeInferOperators.infer_any_in_all_modes_when_lhs_is_unknown
TypeInferOperators.operator_eq_operands_are_not_subtypes_of_each_other_but_has_overlap
TypeInferOperators.operator_eq_verifies_types_do_intersect
TypeInferOperators.or_joins_types
TypeInferOperators.or_joins_types_with_no_extras
TypeInferOperators.primitive_arith_no_metatable
TypeInferOperators.primitive_arith_no_metatable_with_follows
TypeInferOperators.primitive_arith_possible_metatable
TypeInferOperators.produce_the_correct_error_message_when_comparing_a_table_with_a_metatable_with_one_that_does_not
TypeInferOperators.refine_and_or
TypeInferOperators.some_primitive_binary_ops
TypeInferOperators.strict_binary_op_where_lhs_unknown
TypeInferOperators.strip_nil_from_lhs_or_operator
TypeInferOperators.strip_nil_from_lhs_or_operator2
@ -676,13 +576,11 @@ TypeInferOperators.typecheck_unary_len_error
TypeInferOperators.typecheck_unary_minus
TypeInferOperators.typecheck_unary_minus_error
TypeInferOperators.unary_not_is_boolean
TypeInferOperators.unknown_type_in_comparison
TypeInferOperators.UnknownGlobalCompoundAssign
TypeInferPrimitives.CheckMethodsOfNumber
TypeInferPrimitives.singleton_types
TypeInferPrimitives.string_function_other
TypeInferPrimitives.string_index
TypeInferPrimitives.string_length
TypeInferPrimitives.string_method
TypeInferUnknownNever.assign_to_global_which_is_never
TypeInferUnknownNever.assign_to_local_which_is_never
@ -692,9 +590,7 @@ TypeInferUnknownNever.call_never
TypeInferUnknownNever.dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
TypeInferUnknownNever.length_of_never
TypeInferUnknownNever.math_operators_and_never
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable2
TypeInferUnknownNever.unary_minus_of_never
TypePackTests.higher_order_function
@ -718,18 +614,17 @@ TypePackTests.type_alias_type_packs
TypePackTests.type_alias_type_packs_errors
TypePackTests.type_alias_type_packs_import
TypePackTests.type_alias_type_packs_nested
TypePackTests.type_pack_hidden_free_tail_infinite_growth
TypePackTests.type_pack_type_parameters
TypePackTests.unify_variadic_tails_in_arguments
TypePackTests.unify_variadic_tails_in_arguments_free
TypePackTests.varargs_inference_through_multiple_scopes
TypePackTests.variadic_packs
TypeSingletons.bool_singleton_subtype
TypeSingletons.bool_singletons
TypeSingletons.bool_singletons_mismatch
TypeSingletons.enums_using_singletons
TypeSingletons.enums_using_singletons_mismatch
TypeSingletons.enums_using_singletons_subtyping
TypeSingletons.error_detailed_tagged_union_mismatch_bool
TypeSingletons.error_detailed_tagged_union_mismatch_string
TypeSingletons.function_call_with_singletons
TypeSingletons.function_call_with_singletons_mismatch
TypeSingletons.if_then_else_expression_singleton_options
TypeSingletons.indexing_on_string_singletons
@ -752,7 +647,6 @@ TypeSingletons.widening_happens_almost_everywhere
TypeSingletons.widening_happens_almost_everywhere_except_for_tables
UnionTypes.error_detailed_optional
UnionTypes.error_detailed_union_all
UnionTypes.error_detailed_union_part
UnionTypes.error_takes_optional_arguments
UnionTypes.index_on_a_union_type_with_missing_property
UnionTypes.index_on_a_union_type_with_mixed_types
@ -771,6 +665,4 @@ UnionTypes.optional_union_follow
UnionTypes.optional_union_functions
UnionTypes.optional_union_members
UnionTypes.optional_union_methods
UnionTypes.return_types_can_be_disjoint
UnionTypes.table_union_write_indirect
UnionTypes.union_equality_comparisons

100
tools/lvmexecute_split.py Normal file
View file

@ -0,0 +1,100 @@
#!/usr/bin/python
# This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
# This code can be used to split lvmexecute.cpp VM switch into separate functions for use as native code generation fallbacks
import sys
import re
input = sys.stdin.readlines()
inst = ""
header = """// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This file was generated by 'tools/lvmexecute_split.py' script, do not modify it by hand
#pragma once
#include <stdint.h>
struct lua_State;
struct Closure;
typedef uint32_t Instruction;
typedef struct lua_TValue TValue;
typedef TValue* StkId;
"""
source = """// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
// This code is based on Lua 5.x implementation licensed under MIT License; see lua_LICENSE.txt for details
// This file was generated by 'tools/lvmexecute_split.py' script, do not modify it by hand
#include "Fallbacks.h"
#include "FallbacksProlog.h"
"""
function = ""
state = 0
# parse with the state machine
for line in input:
# find the start of an instruction
if state == 0:
match = re.match("\s+VM_CASE\((LOP_[A-Z_0-9]+)\)", line)
if match:
inst = match[1]
signature = "const Instruction* execute_" + inst + "(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)"
header += signature + ";\n"
function = signature + "\n"
state = 1
# find the end of an instruction
elif state == 1:
# remove jumps back into the native code
if line == "#if LUA_CUSTOM_EXECUTION\n":
state = 2
continue
if line[0] == ' ':
finalline = line[12:-1] + "\n"
else:
finalline = line
finalline = finalline.replace("VM_NEXT();", "return pc;");
finalline = finalline.replace("goto exit;", "return NULL;");
finalline = finalline.replace("return;", "return NULL;");
function += finalline
match = re.match(" }", line)
if match:
# break is not supported
if inst == "LOP_BREAK":
function = "const Instruction* execute_" + inst + "(lua_State* L, const Instruction* pc, Closure* cl, StkId base, TValue* k)\n"
function += "{\n LUAU_ASSERT(!\"Unsupported deprecated opcode\");\n LUAU_UNREACHABLE();\n}\n"
# handle fallthrough
elif inst == "LOP_NAMECALL":
function = function[:-len(finalline)]
function += " return pc;\n}\n"
source += function + "\n"
state = 0
# skip LUA_CUSTOM_EXECUTION code blocks
elif state == 2:
if line == "#endif\n":
state = 3
continue
# skip extra line
elif state == 3:
state = 1
# make sure we found the ending
assert(state == 0)
with open("Fallbacks.h", "w") as fp:
fp.writelines(header)
with open("Fallbacks.cpp", "w") as fp:
fp.writelines(source)