mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-13 13:30:40 +00:00
Sync to upstream/release/581
This commit is contained in:
parent
88cd3dda87
commit
6ee4f190ab
53 changed files with 1118 additions and 633 deletions
|
@ -174,10 +174,10 @@ struct ConstraintSolver
|
|||
bool blockOnPendingTypes(TypePackId target, NotNull<const Constraint> constraint);
|
||||
|
||||
void unblock(NotNull<const Constraint> progressed);
|
||||
void unblock(TypeId progressed);
|
||||
void unblock(TypePackId progressed);
|
||||
void unblock(const std::vector<TypeId>& types);
|
||||
void unblock(const std::vector<TypePackId>& packs);
|
||||
void unblock(TypeId progressed, Location location);
|
||||
void unblock(TypePackId progressed, Location location);
|
||||
void unblock(const std::vector<TypeId>& types, Location location);
|
||||
void unblock(const std::vector<TypePackId>& packs, Location location);
|
||||
|
||||
/**
|
||||
* @returns true if the TypeId is in a blocked state.
|
||||
|
|
|
@ -752,6 +752,7 @@ struct AstJsonEncoder : public AstVisitor
|
|||
if (node->superName)
|
||||
write("superName", *node->superName);
|
||||
PROP(props);
|
||||
PROP(indexer);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
LUAU_FASTINT(LuauCheckRecursionLimit);
|
||||
LUAU_FASTFLAG(DebugLuauMagicTypes);
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1157,6 +1158,23 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatDeclareC
|
|||
|
||||
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
|
||||
|
||||
if (FFlag::LuauParseDeclareClassIndexer && declaredClass->indexer)
|
||||
{
|
||||
RecursionCounter counter{&recursionCount};
|
||||
|
||||
if (recursionCount >= FInt::LuauCheckRecursionLimit)
|
||||
{
|
||||
reportCodeTooComplex(declaredClass->indexer->location);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctv->indexer = TableIndexer{
|
||||
resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false),
|
||||
resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
for (const AstDeclaredClassProp& prop : declaredClass->props)
|
||||
{
|
||||
Name propName(prop.name.value);
|
||||
|
|
|
@ -539,8 +539,8 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
|||
asMutable(c.generalizedType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
unblock(c.generalizedType);
|
||||
unblock(c.sourceType);
|
||||
unblock(c.generalizedType, constraint->location);
|
||||
unblock(c.sourceType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -564,7 +564,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
|||
reportError(UnificationTooComplex{}, constraint->location);
|
||||
|
||||
asMutable(c.subType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(c.subType);
|
||||
unblock(c.subType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -574,7 +574,7 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
|||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(c.subType);
|
||||
|
||||
unblock(c.subType);
|
||||
unblock(c.subType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -597,7 +597,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Len:
|
||||
|
@ -605,7 +605,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
// __len must return a number.
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->numberType);
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
case AstExprUnary::Minus:
|
||||
|
@ -635,7 +635,7 @@ bool ConstraintSolver::tryDispatch(const UnaryConstraint& c, NotNull<const Const
|
|||
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
}
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -684,7 +684,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
if (isBlocked(leftType) || (hasTypeInIntersection<FreeType>(leftType) && !isLogical))
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -697,7 +697,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
{
|
||||
// TODO: Boolean singleton false? The result is _always_ boolean false.
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -760,7 +760,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
}
|
||||
|
||||
asMutable(resultType)->ty.emplace<BoundType>(mmResult);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
|
||||
(*c.astOriginalCallTypes)[c.astFragment] = *mm;
|
||||
(*c.astOverloadResolvedTypes)[c.astFragment] = *instantiatedMm;
|
||||
|
@ -790,14 +790,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->neverType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -814,14 +814,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
unify(leftType, rightType, constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->neverType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -840,14 +840,14 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
if (lt && rt && (lt->isExactlyNumber() || get<AnyType>(lt->tops)) && rt->isExactlyNumber())
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lt && rt && (lt->isSubtypeOfString() || get<AnyType>(lt->tops)) && rt->isSubtypeOfString())
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -855,7 +855,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
if (get<NeverType>(leftType) || get<NeverType>(rightType))
|
||||
{
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -867,7 +867,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
case AstExprBinary::Op::CompareEq:
|
||||
case AstExprBinary::Op::CompareNe:
|
||||
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
// And evalutes to a boolean if the LHS is falsey, and the RHS type if LHS is
|
||||
// truthy.
|
||||
|
@ -876,7 +876,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
TypeId leftFilteredTy = simplifyIntersection(builtinTypes, arena, leftType, builtinTypes->falsyType).result;
|
||||
|
||||
asMutable(resultType)->ty.emplace<BoundType>(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
// Or evaluates to the LHS type if the LHS is truthy, and the RHS type if
|
||||
|
@ -886,7 +886,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
TypeId leftFilteredTy = simplifyIntersection(builtinTypes, arena, leftType, builtinTypes->truthyType).result;
|
||||
|
||||
asMutable(resultType)->ty.emplace<BoundType>(simplifyUnion(builtinTypes, arena, rightType, leftFilteredTy).result);
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
|
@ -898,7 +898,7 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
|
|||
unify(leftType, errorRecoveryType(), constraint->scope);
|
||||
unify(rightType, errorRecoveryType(), constraint->scope);
|
||||
asMutable(resultType)->ty.emplace<BoundType>(errorRecoveryType());
|
||||
unblock(resultType);
|
||||
unblock(resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1065,14 +1065,14 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
|||
const PendingExpansionType* petv = get<PendingExpansionType>(follow(c.target));
|
||||
if (!petv)
|
||||
{
|
||||
unblock(c.target);
|
||||
unblock(c.target, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bindResult = [this, &c](TypeId result) {
|
||||
auto bindResult = [this, &c, constraint](TypeId result) {
|
||||
LUAU_ASSERT(get<PendingExpansionType>(c.target));
|
||||
asMutable(c.target)->ty.emplace<BoundType>(result);
|
||||
unblock(c.target);
|
||||
unblock(c.target, constraint->location);
|
||||
};
|
||||
|
||||
std::optional<TypeFun> tf = (petv->prefix) ? constraint->scope->lookupImportedType(petv->prefix->value, petv->name.value)
|
||||
|
@ -1400,9 +1400,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
|||
const auto [changedTypes, changedPacks] = bestOverloadLog->getChanges();
|
||||
bestOverloadLog->commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
unblock(c.result);
|
||||
unblock(changedTypes, constraint->location);
|
||||
unblock(changedPacks, constraint->location);
|
||||
unblock(c.result, constraint->location);
|
||||
|
||||
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||
queuer.traverse(fn);
|
||||
|
@ -1421,7 +1421,7 @@ bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull<con
|
|||
|
||||
TypeId bindTo = maybeSingleton(expectedType) ? c.singletonType : c.multitonType;
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(bindTo);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1440,7 +1440,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
TableType& ttv = asMutable(subjectType)->ty.emplace<TableType>(TableState::Free, TypeLevel{}, constraint->scope);
|
||||
ttv.props[c.prop] = Property{c.resultType};
|
||||
asMutable(c.resultType)->ty.emplace<FreeType>(constraint->scope);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1454,7 +1454,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
|||
}
|
||||
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result.value_or(builtinTypes->anyType));
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1568,7 +1568,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||
if (!isBlocked(c.propType))
|
||||
unify(c.propType, *existingPropType, constraint->scope);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1593,8 +1593,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||
bind(subjectType, ty);
|
||||
if (follow(c.resultType) != follow(ty))
|
||||
bind(c.resultType, ty);
|
||||
unblock(subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(subjectType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (auto ttv = getMutable<TableType>(subjectType))
|
||||
|
@ -1605,7 +1605,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||
|
||||
ttv->props[c.path[0]] = Property{c.propType};
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (ttv->state == TableState::Unsealed)
|
||||
|
@ -1614,14 +1614,14 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||
|
||||
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(subjectType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1630,7 +1630,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
|||
// Other kinds of types don't change shape when properties are assigned
|
||||
// to them. (if they allow properties at all!)
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1649,8 +1649,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
|||
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
asMutable(c.propType)->ty.emplace<FreeType>(scope);
|
||||
unblock(c.propType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1662,8 +1662,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
|||
unify(c.indexType, tt->indexer->indexType, constraint->scope);
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
unblock(c.propType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||
|
@ -1675,8 +1675,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
|||
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
||||
asMutable(c.propType)->ty.emplace<FreeType>(tt->scope);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
unblock(c.propType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
// Do not augment sealed or generic tables that lack indexers
|
||||
|
@ -1684,8 +1684,8 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
|||
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
unblock(c.propType);
|
||||
unblock(c.resultType);
|
||||
unblock(c.propType, constraint->location);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1704,7 +1704,7 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul
|
|||
else
|
||||
*asMutable(c.resultType) = BoundType{builtinTypes->anyType};
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1720,7 +1720,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
if (isBlocked(resultPack))
|
||||
{
|
||||
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
|
||||
unblock(resultPack);
|
||||
unblock(resultPack, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1745,7 +1745,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
}
|
||||
else
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(srcTy);
|
||||
unblock(*destIter);
|
||||
unblock(*destIter, constraint->location);
|
||||
}
|
||||
else
|
||||
unify(*destIter, srcTy, constraint->scope);
|
||||
|
@ -1763,7 +1763,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
|||
if (isBlocked(*destIter))
|
||||
{
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
unblock(*destIter);
|
||||
unblock(*destIter, constraint->location);
|
||||
}
|
||||
|
||||
++destIter;
|
||||
|
@ -1852,7 +1852,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
|||
if (c.mode == RefineConstraint::Intersection && isNegatedAny(c.discriminant))
|
||||
{
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.type);
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1880,7 +1880,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
|||
else
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(c.discriminant);
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1892,7 +1892,7 @@ bool ConstraintSolver::tryDispatch(const RefineConstraint& c, NotNull<const Cons
|
|||
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(result);
|
||||
|
||||
unblock(c.resultType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1904,10 +1904,10 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
|||
reduceFamilies(ty, constraint->location, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force);
|
||||
|
||||
for (TypeId r : result.reducedTypes)
|
||||
unblock(r);
|
||||
unblock(r, constraint->location);
|
||||
|
||||
for (TypePackId r : result.reducedPacks)
|
||||
unblock(r);
|
||||
unblock(r, constraint->location);
|
||||
|
||||
if (force)
|
||||
return true;
|
||||
|
@ -1928,10 +1928,10 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
|
|||
reduceFamilies(tp, constraint->location, NotNull{arena}, builtinTypes, constraint->scope, normalizer, nullptr, force);
|
||||
|
||||
for (TypeId r : result.reducedTypes)
|
||||
unblock(r);
|
||||
unblock(r, constraint->location);
|
||||
|
||||
for (TypePackId r : result.reducedPacks)
|
||||
unblock(r);
|
||||
unblock(r, constraint->location);
|
||||
|
||||
if (force)
|
||||
return true;
|
||||
|
@ -2374,8 +2374,8 @@ bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy,
|
|||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
unblock(changedTypes, constraint->location);
|
||||
unblock(changedPacks, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -2509,7 +2509,7 @@ void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
|
|||
return unblock_(progressed.get());
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(TypeId ty)
|
||||
void ConstraintSolver::unblock(TypeId ty, Location location)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
|
||||
|
@ -2517,7 +2517,7 @@ void ConstraintSolver::unblock(TypeId ty)
|
|||
while (true)
|
||||
{
|
||||
if (seen.find(progressed))
|
||||
iceReporter.ice("ConstraintSolver::unblock encountered a self-bound type!");
|
||||
iceReporter.ice("ConstraintSolver::unblock encountered a self-bound type!", location);
|
||||
seen.insert(progressed);
|
||||
|
||||
if (logger)
|
||||
|
@ -2532,7 +2532,7 @@ void ConstraintSolver::unblock(TypeId ty)
|
|||
}
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(TypePackId progressed)
|
||||
void ConstraintSolver::unblock(TypePackId progressed, Location)
|
||||
{
|
||||
if (logger)
|
||||
logger->popBlock(progressed);
|
||||
|
@ -2540,16 +2540,16 @@ void ConstraintSolver::unblock(TypePackId progressed)
|
|||
return unblock_(progressed);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(const std::vector<TypeId>& types)
|
||||
void ConstraintSolver::unblock(const std::vector<TypeId>& types, Location location)
|
||||
{
|
||||
for (TypeId t : types)
|
||||
unblock(t);
|
||||
unblock(t, location);
|
||||
}
|
||||
|
||||
void ConstraintSolver::unblock(const std::vector<TypePackId>& packs)
|
||||
void ConstraintSolver::unblock(const std::vector<TypePackId>& packs, Location location)
|
||||
{
|
||||
for (TypePackId t : packs)
|
||||
unblock(t);
|
||||
unblock(t, location);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::isBlocked(TypeId ty)
|
||||
|
@ -2586,8 +2586,8 @@ ErrorVec ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope
|
|||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
unblock(changedTypes, Location{});
|
||||
unblock(changedPacks, Location{});
|
||||
|
||||
return std::move(u.errors);
|
||||
}
|
||||
|
@ -2604,8 +2604,8 @@ ErrorVec ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNu
|
|||
|
||||
u.log.commit();
|
||||
|
||||
unblock(changedTypes);
|
||||
unblock(changedPacks);
|
||||
unblock(changedTypes, Location{});
|
||||
unblock(changedPacks, Location{});
|
||||
|
||||
return std::move(u.errors);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
|
|||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauFixBuildQueueExceptionUnwrap, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -596,6 +597,7 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
|||
sendCycleItemTask();
|
||||
|
||||
std::vector<size_t> nextItems;
|
||||
std::optional<size_t> itemWithException;
|
||||
|
||||
while (remaining != 0)
|
||||
{
|
||||
|
@ -603,17 +605,25 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
|||
std::unique_lock guard(mtx);
|
||||
|
||||
// If nothing is ready yet, wait
|
||||
if (readyQueueItems.empty())
|
||||
{
|
||||
cv.wait(guard, [&readyQueueItems] {
|
||||
return !readyQueueItems.empty();
|
||||
});
|
||||
}
|
||||
|
||||
// Handle checked items
|
||||
for (size_t i : readyQueueItems)
|
||||
{
|
||||
const BuildQueueItem& item = buildQueueItems[i];
|
||||
|
||||
if (FFlag::LuauFixBuildQueueExceptionUnwrap)
|
||||
{
|
||||
// If exception was thrown, stop adding new items and wait for processing items to complete
|
||||
if (item.exception)
|
||||
itemWithException = i;
|
||||
|
||||
if (itemWithException)
|
||||
break;
|
||||
}
|
||||
|
||||
recordItemResult(item);
|
||||
|
||||
// Notify items that were waiting for this dependency
|
||||
|
@ -648,8 +658,17 @@ std::vector<ModuleName> Frontend::checkQueuedModules(std::optional<FrontendOptio
|
|||
|
||||
// If we aren't done, but don't have anything processing, we hit a cycle
|
||||
if (remaining != 0 && processing == 0)
|
||||
{
|
||||
// We might have stopped because of a pending exception
|
||||
if (FFlag::LuauFixBuildQueueExceptionUnwrap && itemWithException)
|
||||
{
|
||||
recordItemResult(buildQueueItems[*itemWithException]);
|
||||
break;
|
||||
}
|
||||
|
||||
sendCycleItemTask();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModuleName> checkedModules;
|
||||
checkedModules.reserve(buildQueueItems.size());
|
||||
|
@ -1104,6 +1123,8 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
|||
result->name = sourceModule.name;
|
||||
result->humanReadableName = sourceModule.humanReadableName;
|
||||
|
||||
iceHandler->moduleName = sourceModule.name;
|
||||
|
||||
std::unique_ptr<DcrLogger> logger;
|
||||
if (recordJsonLog)
|
||||
{
|
||||
|
@ -1189,10 +1210,20 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
|||
prepareModuleScope(name, scope, forAutocomplete);
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, recordJsonLog);
|
||||
}
|
||||
catch (const InternalCompilerError& err)
|
||||
{
|
||||
InternalCompilerError augmented = err.location.has_value()
|
||||
? InternalCompilerError{err.message, sourceModule.humanReadableName, *err.location}
|
||||
: InternalCompilerError{err.message, sourceModule.humanReadableName};
|
||||
throw augmented;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeChecker typeChecker(forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope,
|
||||
|
|
|
@ -2117,15 +2117,15 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
|||
TypeId hmtable = nullptr;
|
||||
if (const MetatableType* hmtv = get<MetatableType>(here))
|
||||
{
|
||||
htable = hmtv->table;
|
||||
hmtable = hmtv->metatable;
|
||||
htable = follow(hmtv->table);
|
||||
hmtable = follow(hmtv->metatable);
|
||||
}
|
||||
TypeId ttable = there;
|
||||
TypeId tmtable = nullptr;
|
||||
if (const MetatableType* tmtv = get<MetatableType>(there))
|
||||
{
|
||||
ttable = tmtv->table;
|
||||
tmtable = tmtv->metatable;
|
||||
ttable = follow(tmtv->table);
|
||||
tmtable = follow(tmtv->metatable);
|
||||
}
|
||||
|
||||
const TableType* httv = get<TableType>(htable);
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Luau/ToString.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/Normalize.h" // TypeIds
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTINT(LuauTypeReductionRecursionLimit)
|
||||
|
||||
|
@ -236,6 +237,17 @@ Relation relateTables(TypeId left, TypeId right)
|
|||
NotNull<const TableType> leftTable{get<TableType>(left)};
|
||||
NotNull<const TableType> rightTable{get<TableType>(right)};
|
||||
LUAU_ASSERT(1 == rightTable->props.size());
|
||||
// Disjoint props have nothing in common
|
||||
// t1 with props p1's cannot appear in t2 and t2 with props p2's cannot appear in t1
|
||||
bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props), [&](auto prop) {
|
||||
return rightTable->props.find(prop.first) != end(rightTable->props);
|
||||
});
|
||||
bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) {
|
||||
return leftTable->props.find(prop.first) != end(leftTable->props);
|
||||
});
|
||||
|
||||
if (!(foundPropFromLeftInRight || foundPropFromRightInLeft) && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
|
||||
return Relation::Disjoint;
|
||||
|
||||
const auto [propName, rightProp] = *begin(rightTable->props);
|
||||
|
||||
|
|
|
@ -111,11 +111,14 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
|||
else if constexpr (std::is_same_v<T, GenericType>)
|
||||
return dest.addType(a);
|
||||
else if constexpr (std::is_same_v<T, BlockedType>)
|
||||
return ty;
|
||||
return dest.addType(a);
|
||||
else if constexpr (std::is_same_v<T, PrimitiveType>)
|
||||
return ty;
|
||||
else if constexpr (std::is_same_v<T, PendingExpansionType>)
|
||||
return ty;
|
||||
{
|
||||
PendingExpansionType clone = PendingExpansionType{a.prefix, a.name, a.typeArguments, a.packArguments};
|
||||
return dest.addType(std::move(clone));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, AnyType>)
|
||||
return ty;
|
||||
else if constexpr (std::is_same_v<T, ErrorType>)
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer);
|
||||
|
||||
static char* allocateString(Luau::Allocator& allocator, std::string_view contents)
|
||||
{
|
||||
char* result = (char*)allocator.allocate(contents.size() + 1);
|
||||
|
@ -227,7 +229,17 @@ public:
|
|||
idx++;
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeTable>(Location(), props);
|
||||
AstTableIndexer* indexer = nullptr;
|
||||
if (FFlag::LuauParseDeclareClassIndexer && ctv.indexer)
|
||||
{
|
||||
RecursionCounter counter(&count);
|
||||
|
||||
indexer = allocator->alloc<AstTableIndexer>();
|
||||
indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty);
|
||||
indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty);
|
||||
}
|
||||
|
||||
return allocator->alloc<AstTypeTable>(Location(), props, indexer);
|
||||
}
|
||||
|
||||
AstType* operator()(const FunctionType& ftv)
|
||||
|
|
|
@ -41,6 +41,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
|||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckClassTypeIndexers, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAlwaysCommitInferencesOfFunctionCalls, false)
|
||||
LUAU_FASTFLAG(LuauParseDeclareClassIndexer)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -1757,6 +1758,9 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass&
|
|||
if (!ctv->metatable)
|
||||
ice("No metatable for declared class");
|
||||
|
||||
if (const auto& indexer = declaredClass.indexer; FFlag::LuauParseDeclareClassIndexer && indexer)
|
||||
ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType));
|
||||
|
||||
TableType* metatable = getMutable<TableType>(*ctv->metatable);
|
||||
for (const AstDeclaredClassProp& prop : declaredClass.props)
|
||||
{
|
||||
|
|
|
@ -801,12 +801,20 @@ struct AstDeclaredClassProp
|
|||
bool isMethod = false;
|
||||
};
|
||||
|
||||
struct AstTableIndexer
|
||||
{
|
||||
AstType* indexType;
|
||||
AstType* resultType;
|
||||
Location location;
|
||||
};
|
||||
|
||||
class AstStatDeclareClass : public AstStat
|
||||
{
|
||||
public:
|
||||
LUAU_RTTI(AstStatDeclareClass)
|
||||
|
||||
AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props);
|
||||
AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props,
|
||||
AstTableIndexer* indexer = nullptr);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
|
@ -814,6 +822,7 @@ public:
|
|||
std::optional<AstName> superName;
|
||||
|
||||
AstArray<AstDeclaredClassProp> props;
|
||||
AstTableIndexer* indexer;
|
||||
};
|
||||
|
||||
class AstType : public AstNode
|
||||
|
@ -862,13 +871,6 @@ struct AstTableProp
|
|||
AstType* type;
|
||||
};
|
||||
|
||||
struct AstTableIndexer
|
||||
{
|
||||
AstType* indexType;
|
||||
AstType* resultType;
|
||||
Location location;
|
||||
};
|
||||
|
||||
class AstTypeTable : public AstType
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -714,12 +714,13 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor)
|
|||
}
|
||||
}
|
||||
|
||||
AstStatDeclareClass::AstStatDeclareClass(
|
||||
const Location& location, const AstName& name, std::optional<AstName> superName, const AstArray<AstDeclaredClassProp>& props)
|
||||
AstStatDeclareClass::AstStatDeclareClass(const Location& location, const AstName& name, std::optional<AstName> superName,
|
||||
const AstArray<AstDeclaredClassProp>& props, AstTableIndexer* indexer)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, name(name)
|
||||
, superName(superName)
|
||||
, props(props)
|
||||
, indexer(indexer)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// See docs/SyntaxChanges.md for an explanation.
|
||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false)
|
||||
|
||||
#define ERROR_INVALID_INTERP_DOUBLE_BRACE "Double braces are not permitted within interpolated strings. Did you mean '\\{'?"
|
||||
|
||||
|
@ -877,6 +878,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
|||
}
|
||||
|
||||
TempVector<AstDeclaredClassProp> props(scratchDeclaredClassProps);
|
||||
AstTableIndexer* indexer = nullptr;
|
||||
|
||||
while (lexer.current().type != Lexeme::ReservedEnd)
|
||||
{
|
||||
|
@ -885,7 +887,8 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
|||
{
|
||||
props.push_back(parseDeclaredClassMethod());
|
||||
}
|
||||
else if (lexer.current().type == '[')
|
||||
else if (lexer.current().type == '[' && (!FFlag::LuauParseDeclareClassIndexer || lexer.lookahead().type == Lexeme::RawString ||
|
||||
lexer.lookahead().type == Lexeme::QuotedString))
|
||||
{
|
||||
const Lexeme begin = lexer.current();
|
||||
nextLexeme(); // [
|
||||
|
@ -904,6 +907,22 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
|||
else
|
||||
report(begin.location, "String literal contains malformed escape sequence");
|
||||
}
|
||||
else if (lexer.current().type == '[' && FFlag::LuauParseDeclareClassIndexer)
|
||||
{
|
||||
if (indexer)
|
||||
{
|
||||
// maybe we don't need to parse the entire badIndexer...
|
||||
// however, we either have { or [ to lint, not the entire table type or the bad indexer.
|
||||
AstTableIndexer* badIndexer = parseTableIndexer();
|
||||
|
||||
// we lose all additional indexer expressions from the AST after error recovery here
|
||||
report(badIndexer->location, "Cannot have more than one class indexer");
|
||||
}
|
||||
else
|
||||
{
|
||||
indexer = parseTableIndexer();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Name propName = parseName("property name");
|
||||
|
@ -916,7 +935,7 @@ AstStat* Parser::parseDeclaration(const Location& start)
|
|||
Location classEnd = lexer.current().location;
|
||||
nextLexeme(); // skip past `end`
|
||||
|
||||
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props));
|
||||
return allocator.alloc<AstStatDeclareClass>(Location(classStart, classEnd), className.name, superName, copy(props), indexer);
|
||||
}
|
||||
else if (std::optional<Name> globalName = parseNameOpt("global variable name"))
|
||||
{
|
||||
|
|
293
CLI/Repl.cpp
293
CLI/Repl.cpp
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "Luau/CodeGen.h"
|
||||
#include "Luau/Compiler.h"
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
#include "Luau/Parser.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
|
||||
|
@ -40,27 +39,6 @@
|
|||
|
||||
LUAU_FASTFLAG(DebugLuauTimeTracing)
|
||||
|
||||
enum class CliMode
|
||||
{
|
||||
Unknown,
|
||||
Repl,
|
||||
Compile,
|
||||
RunSourceFiles
|
||||
};
|
||||
|
||||
enum class CompileFormat
|
||||
{
|
||||
Text,
|
||||
Binary,
|
||||
Remarks,
|
||||
Codegen, // Prints annotated native code including IR and assembly
|
||||
CodegenAsm, // Prints annotated native code assembly
|
||||
CodegenIr, // Prints annotated native code IR
|
||||
CodegenVerbose, // Prints annotated native code including IR, assembly and outlined code
|
||||
CodegenNull,
|
||||
Null
|
||||
};
|
||||
|
||||
constexpr int MaxTraversalLimit = 50;
|
||||
|
||||
static bool codegen = false;
|
||||
|
@ -668,178 +646,11 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
|||
return status == 0;
|
||||
}
|
||||
|
||||
static void report(const char* name, const Luau::Location& location, const char* type, const char* message)
|
||||
{
|
||||
fprintf(stderr, "%s(%d,%d): %s: %s\n", name, location.begin.line + 1, location.begin.column + 1, type, message);
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::ParseError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "SyntaxError", error.what());
|
||||
}
|
||||
|
||||
static void reportError(const char* name, const Luau::CompileError& error)
|
||||
{
|
||||
report(name, error.getLocation(), "CompileError", error.what());
|
||||
}
|
||||
|
||||
static std::string getCodegenAssembly(const char* name, const std::string& bytecode, Luau::CodeGen::AssemblyOptions options)
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
if (luau_load(L, name, bytecode.data(), bytecode.size(), 0) == 0)
|
||||
return Luau::CodeGen::getAssembly(L, -1, options);
|
||||
|
||||
fprintf(stderr, "Error loading bytecode %s\n", name);
|
||||
return "";
|
||||
}
|
||||
|
||||
static void annotateInstruction(void* context, std::string& text, int fid, int instpos)
|
||||
{
|
||||
Luau::BytecodeBuilder& bcb = *(Luau::BytecodeBuilder*)context;
|
||||
|
||||
bcb.annotateInstruction(text, fid, instpos);
|
||||
}
|
||||
|
||||
struct CompileStats
|
||||
{
|
||||
size_t lines;
|
||||
size_t bytecode;
|
||||
size_t codegen;
|
||||
|
||||
double readTime;
|
||||
double miscTime;
|
||||
double parseTime;
|
||||
double compileTime;
|
||||
double codegenTime;
|
||||
};
|
||||
|
||||
static double recordDeltaTime(double& timer)
|
||||
{
|
||||
double now = Luau::TimeTrace::getClock();
|
||||
double delta = now - timer;
|
||||
timer = now;
|
||||
return delta;
|
||||
}
|
||||
|
||||
static bool compileFile(const char* name, CompileFormat format, CompileStats& stats)
|
||||
{
|
||||
double currts = Luau::TimeTrace::getClock();
|
||||
|
||||
std::optional<std::string> source = readFile(name);
|
||||
if (!source)
|
||||
{
|
||||
fprintf(stderr, "Error opening %s\n", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
stats.readTime += recordDeltaTime(currts);
|
||||
|
||||
// NOTE: Normally, you should use Luau::compile or luau_compile (see lua_require as an example)
|
||||
// This function is much more complicated because it supports many output human-readable formats through internal interfaces
|
||||
|
||||
try
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
|
||||
Luau::CodeGen::AssemblyOptions options;
|
||||
options.outputBinary = format == CompileFormat::CodegenNull;
|
||||
|
||||
if (!options.outputBinary)
|
||||
{
|
||||
options.includeAssembly = format != CompileFormat::CodegenIr;
|
||||
options.includeIr = format != CompileFormat::CodegenAsm;
|
||||
options.includeOutlinedCode = format == CompileFormat::CodegenVerbose;
|
||||
}
|
||||
|
||||
options.annotator = annotateInstruction;
|
||||
options.annotatorContext = &bcb;
|
||||
|
||||
if (format == CompileFormat::Text)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
|
||||
Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
else if (format == CompileFormat::Remarks)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
else if (format == CompileFormat::Codegen || format == CompileFormat::CodegenAsm || format == CompileFormat::CodegenIr ||
|
||||
format == CompileFormat::CodegenVerbose)
|
||||
{
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Source | Luau::BytecodeBuilder::Dump_Locals |
|
||||
Luau::BytecodeBuilder::Dump_Remarks);
|
||||
bcb.setDumpSource(*source);
|
||||
}
|
||||
|
||||
stats.miscTime += recordDeltaTime(currts);
|
||||
|
||||
Luau::Allocator allocator;
|
||||
Luau::AstNameTable names(allocator);
|
||||
Luau::ParseResult result = Luau::Parser::parse(source->c_str(), source->size(), names, allocator);
|
||||
|
||||
if (!result.errors.empty())
|
||||
throw Luau::ParseErrors(result.errors);
|
||||
|
||||
stats.lines += result.lines;
|
||||
stats.parseTime += recordDeltaTime(currts);
|
||||
|
||||
Luau::compileOrThrow(bcb, result, names, copts());
|
||||
stats.bytecode += bcb.getBytecode().size();
|
||||
stats.compileTime += recordDeltaTime(currts);
|
||||
|
||||
switch (format)
|
||||
{
|
||||
case CompileFormat::Text:
|
||||
printf("%s", bcb.dumpEverything().c_str());
|
||||
break;
|
||||
case CompileFormat::Remarks:
|
||||
printf("%s", bcb.dumpSourceRemarks().c_str());
|
||||
break;
|
||||
case CompileFormat::Binary:
|
||||
fwrite(bcb.getBytecode().data(), 1, bcb.getBytecode().size(), stdout);
|
||||
break;
|
||||
case CompileFormat::Codegen:
|
||||
case CompileFormat::CodegenAsm:
|
||||
case CompileFormat::CodegenIr:
|
||||
case CompileFormat::CodegenVerbose:
|
||||
printf("%s", getCodegenAssembly(name, bcb.getBytecode(), options).c_str());
|
||||
break;
|
||||
case CompileFormat::CodegenNull:
|
||||
stats.codegen += getCodegenAssembly(name, bcb.getBytecode(), options).size();
|
||||
stats.codegenTime += recordDeltaTime(currts);
|
||||
break;
|
||||
case CompileFormat::Null:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Luau::ParseErrors& e)
|
||||
{
|
||||
for (auto& error : e.getErrors())
|
||||
reportError(name, error);
|
||||
return false;
|
||||
}
|
||||
catch (Luau::CompileError& e)
|
||||
{
|
||||
reportError(name, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void displayHelp(const char* argv0)
|
||||
{
|
||||
printf("Usage: %s [--mode] [options] [file list]\n", argv0);
|
||||
printf("Usage: %s [options] [file list]\n", argv0);
|
||||
printf("\n");
|
||||
printf("When mode and file list are omitted, an interactive REPL is started instead.\n");
|
||||
printf("\n");
|
||||
printf("Available modes:\n");
|
||||
printf(" omitted: compile and run input files one by one\n");
|
||||
printf(" --compile[=format]: compile input files and output resulting bytecode/assembly (binary, text, remarks, codegen)\n");
|
||||
printf("When file list is omitted, an interactive REPL is started instead.\n");
|
||||
printf("\n");
|
||||
printf("Available options:\n");
|
||||
printf(" --coverage: collect code coverage while running the code and output results to coverage.out\n");
|
||||
|
@ -864,67 +675,12 @@ int replMain(int argc, char** argv)
|
|||
|
||||
setLuauFlagsDefault();
|
||||
|
||||
CliMode mode = CliMode::Unknown;
|
||||
CompileFormat compileFormat{};
|
||||
int profile = 0;
|
||||
bool coverage = false;
|
||||
bool interactive = false;
|
||||
bool codegenPerf = false;
|
||||
|
||||
// Set the mode if the user has explicitly specified one.
|
||||
int argStart = 1;
|
||||
if (argc >= 2 && strncmp(argv[1], "--compile", strlen("--compile")) == 0)
|
||||
{
|
||||
argStart++;
|
||||
mode = CliMode::Compile;
|
||||
if (strcmp(argv[1], "--compile") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Text;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=binary") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Binary;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=text") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Text;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=remarks") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Remarks;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegen") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Codegen;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegenasm") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::CodegenAsm;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegenir") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::CodegenIr;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegenverbose") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::CodegenVerbose;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=codegennull") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::CodegenNull;
|
||||
}
|
||||
else if (strcmp(argv[1], "--compile=null") == 0)
|
||||
{
|
||||
compileFormat = CompileFormat::Null;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: Unrecognized value for '--compile' specified.\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = argStart; i < argc; i++)
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
|
||||
{
|
||||
|
@ -1026,50 +782,20 @@ int replMain(int argc, char** argv)
|
|||
#endif
|
||||
}
|
||||
|
||||
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
if (mode == CliMode::Unknown)
|
||||
{
|
||||
mode = files.empty() ? CliMode::Repl : CliMode::RunSourceFiles;
|
||||
}
|
||||
|
||||
if (mode != CliMode::Compile && codegen && !Luau::CodeGen::isSupported())
|
||||
if (codegen && !Luau::CodeGen::isSupported())
|
||||
{
|
||||
fprintf(stderr, "Cannot enable --codegen, native code generation is not supported in current configuration\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case CliMode::Compile:
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (compileFormat == CompileFormat::Binary)
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||
|
||||
CompileStats stats = {};
|
||||
int failed = 0;
|
||||
|
||||
for (const std::string& path : files)
|
||||
failed += !compileFile(path.c_str(), compileFormat, stats);
|
||||
|
||||
if (compileFormat == CompileFormat::Null)
|
||||
printf("Compiled %d KLOC into %d KB bytecode (read %.2fs, parse %.2fs, compile %.2fs)\n", int(stats.lines / 1000),
|
||||
int(stats.bytecode / 1024), stats.readTime, stats.parseTime, stats.compileTime);
|
||||
else if (compileFormat == CompileFormat::CodegenNull)
|
||||
printf("Compiled %d KLOC into %d KB bytecode => %d KB native code (%.2fx) (read %.2fs, parse %.2fs, compile %.2fs, codegen %.2fs)\n",
|
||||
int(stats.lines / 1000), int(stats.bytecode / 1024), int(stats.codegen / 1024),
|
||||
stats.bytecode == 0 ? 0.0 : double(stats.codegen) / double(stats.bytecode), stats.readTime, stats.parseTime, stats.compileTime,
|
||||
stats.codegenTime);
|
||||
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
case CliMode::Repl:
|
||||
if (files.empty())
|
||||
{
|
||||
runRepl();
|
||||
return 0;
|
||||
}
|
||||
case CliMode::RunSourceFiles:
|
||||
else
|
||||
{
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
@ -1101,9 +827,4 @@ int replMain(int argc, char** argv)
|
|||
|
||||
return failed ? 1 : 0;
|
||||
}
|
||||
case CliMode::Unknown:
|
||||
default:
|
||||
LUAU_ASSERT(!"Unhandled cli mode.");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,6 +98,8 @@ public:
|
|||
void call(Label& label);
|
||||
void call(OperandX64 op);
|
||||
|
||||
void lea(RegisterX64 lhs, Label& label);
|
||||
|
||||
void int3();
|
||||
void ud2();
|
||||
|
||||
|
@ -243,6 +245,7 @@ private:
|
|||
LUAU_NOINLINE void log(const char* opcode, OperandX64 op1, OperandX64 op2, OperandX64 op3, OperandX64 op4);
|
||||
LUAU_NOINLINE void log(Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, Label label);
|
||||
LUAU_NOINLINE void log(const char* opcode, RegisterX64 reg, Label label);
|
||||
void log(OperandX64 op);
|
||||
|
||||
const char* getSizeName(SizeX64 size) const;
|
||||
|
|
|
@ -801,6 +801,8 @@ struct IrBlock
|
|||
uint32_t start = ~0u;
|
||||
uint32_t finish = ~0u;
|
||||
|
||||
uint32_t sortkey = ~0u;
|
||||
|
||||
Label label;
|
||||
};
|
||||
|
||||
|
|
|
@ -38,6 +38,8 @@ std::string toString(const IrFunction& function, bool includeUseInfo);
|
|||
std::string dump(const IrFunction& function);
|
||||
|
||||
std::string toDot(const IrFunction& function, bool includeInst);
|
||||
std::string toDotCfg(const IrFunction& function);
|
||||
std::string toDotDjGraph(const IrFunction& function);
|
||||
|
||||
std::string dumpDot(const IrFunction& function, bool includeInst);
|
||||
|
||||
|
|
|
@ -463,6 +463,20 @@ void AssemblyBuilderX64::call(OperandX64 op)
|
|||
commit();
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::lea(RegisterX64 lhs, Label& label)
|
||||
{
|
||||
LUAU_ASSERT(lhs.size == SizeX64::qword);
|
||||
|
||||
placeBinaryRegAndRegMem(lhs, OperandX64(SizeX64::qword, noreg, 1, rip, 0), 0x8d, 0x8d);
|
||||
|
||||
codePos -= 4;
|
||||
placeLabel(label);
|
||||
commit();
|
||||
|
||||
if (logText)
|
||||
log("lea", lhs, label);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::int3()
|
||||
{
|
||||
if (logText)
|
||||
|
@ -1415,7 +1429,7 @@ void AssemblyBuilderX64::commit()
|
|||
{
|
||||
LUAU_ASSERT(codePos <= codeEnd);
|
||||
|
||||
if (codeEnd - codePos < kMaxInstructionLength)
|
||||
if (unsigned(codeEnd - codePos) < kMaxInstructionLength)
|
||||
extend();
|
||||
}
|
||||
|
||||
|
@ -1501,6 +1515,14 @@ void AssemblyBuilderX64::log(const char* opcode, Label label)
|
|||
logAppend(" %-12s.L%d\n", opcode, label.id);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::log(const char* opcode, RegisterX64 reg, Label label)
|
||||
{
|
||||
logAppend(" %-12s", opcode);
|
||||
log(reg);
|
||||
text.append(",");
|
||||
logAppend(".L%d\n", label.id);
|
||||
}
|
||||
|
||||
void AssemblyBuilderX64::log(OperandX64 op)
|
||||
{
|
||||
switch (op.cat)
|
||||
|
|
|
@ -56,8 +56,10 @@ static void makePagesExecutable(uint8_t* mem, size_t size)
|
|||
|
||||
static void flushInstructionCache(uint8_t* mem, size_t size)
|
||||
{
|
||||
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
|
||||
if (FlushInstructionCache(GetCurrentProcess(), mem, size) == 0)
|
||||
LUAU_ASSERT(!"Failed to flush instruction cache");
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static uint8_t* allocatePages(size_t size)
|
||||
|
|
|
@ -125,7 +125,7 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
|||
return (a.kind == IrBlockKind::Fallback) < (b.kind == IrBlockKind::Fallback);
|
||||
|
||||
// Try to order by instruction order
|
||||
return a.start < b.start;
|
||||
return a.sortkey < b.sortkey;
|
||||
});
|
||||
|
||||
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
|
||||
|
@ -234,6 +234,8 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
|||
build.setLabel(abandoned.label);
|
||||
}
|
||||
|
||||
lowering.finishFunction();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -244,7 +246,15 @@ static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
|||
build.logAppend("#\n");
|
||||
}
|
||||
|
||||
if (outputEnabled && !options.includeOutlinedCode && seenFallback)
|
||||
if (!seenFallback)
|
||||
{
|
||||
textSize = build.text.length();
|
||||
codeSize = build.getCodeSize();
|
||||
}
|
||||
|
||||
lowering.finishFunction();
|
||||
|
||||
if (outputEnabled && !options.includeOutlinedCode && textSize < build.text.size())
|
||||
{
|
||||
build.text.resize(textSize);
|
||||
|
||||
|
@ -594,6 +604,12 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
|||
X64::assembleHelpers(build, helpers);
|
||||
#endif
|
||||
|
||||
if (!options.includeOutlinedCode && options.includeAssembly)
|
||||
{
|
||||
build.text.clear();
|
||||
build.logAppend("; skipping %u bytes of outlined helpers\n", unsigned(build.getCodeSize() * sizeof(build.code[0])));
|
||||
}
|
||||
|
||||
for (Proto* p : protos)
|
||||
if (p)
|
||||
if (std::optional<NativeProto> np = assembleFunction(build, data, helpers, p, options))
|
||||
|
|
|
@ -288,27 +288,27 @@ void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
|
|||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; exitContinueVm\n");
|
||||
helpers.exitContinueVm = build.setLabel();
|
||||
build.setLabel(helpers.exitContinueVm);
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; exitNoContinueVm\n");
|
||||
helpers.exitNoContinueVm = build.setLabel();
|
||||
build.setLabel(helpers.exitNoContinueVm);
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; reentry\n");
|
||||
helpers.reentry = build.setLabel();
|
||||
build.setLabel(helpers.reentry);
|
||||
emitReentry(build, helpers);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; interrupt\n");
|
||||
helpers.interrupt = build.setLabel();
|
||||
build.setLabel(helpers.interrupt);
|
||||
emitInterrupt(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; return\n");
|
||||
helpers.return_ = build.setLabel();
|
||||
build.setLabel(helpers.return_);
|
||||
emitReturn(build, helpers);
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,11 @@ static EntryLocations buildEntryFunction(AssemblyBuilderX64& build, UnwindBuilde
|
|||
locations.start = build.setLabel();
|
||||
unwind.startFunction();
|
||||
|
||||
RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi;
|
||||
RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi;
|
||||
RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx;
|
||||
RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx;
|
||||
|
||||
// Save common non-volatile registers
|
||||
if (build.abi == ABIX64::SystemV)
|
||||
{
|
||||
|
@ -177,22 +182,27 @@ void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
|||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; exitContinueVm\n");
|
||||
helpers.exitContinueVm = build.setLabel();
|
||||
build.setLabel(helpers.exitContinueVm);
|
||||
emitExit(build, /* continueInVm */ true);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; exitNoContinueVm\n");
|
||||
helpers.exitNoContinueVm = build.setLabel();
|
||||
build.setLabel(helpers.exitNoContinueVm);
|
||||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; continueCallInVm\n");
|
||||
helpers.continueCallInVm = build.setLabel();
|
||||
build.setLabel(helpers.continueCallInVm);
|
||||
emitContinueCallInVm(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; interrupt\n");
|
||||
build.setLabel(helpers.interrupt);
|
||||
emitInterrupt(build);
|
||||
|
||||
if (build.logText)
|
||||
build.logAppend("; return\n");
|
||||
helpers.return_ = build.setLabel();
|
||||
build.setLabel(helpers.return_);
|
||||
emitReturn(build, helpers);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,13 +25,13 @@ struct ModuleHelpers
|
|||
Label exitContinueVm;
|
||||
Label exitNoContinueVm;
|
||||
Label return_;
|
||||
Label interrupt;
|
||||
|
||||
// X64
|
||||
Label continueCallInVm;
|
||||
|
||||
// A64
|
||||
Label reentry; // x0: closure
|
||||
Label interrupt; // x0: pc offset, x1: return address, x2: interrupt
|
||||
};
|
||||
|
||||
} // namespace CodeGen
|
||||
|
|
|
@ -278,39 +278,34 @@ void emitUpdateBase(AssemblyBuilderX64& build)
|
|||
build.mov(rBase, qword[rState + offsetof(lua_State, base)]);
|
||||
}
|
||||
|
||||
static void emitSetSavedPc(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos)
|
||||
void emitInterrupt(AssemblyBuilderX64& build)
|
||||
{
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
// rax = pcpos + 1
|
||||
// rbx = return address in native code
|
||||
|
||||
build.mov(tmp1.reg, sCode);
|
||||
build.add(tmp1.reg, pcpos * sizeof(Instruction));
|
||||
build.mov(tmp2.reg, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(qword[tmp2.reg + offsetof(CallInfo, savedpc)], tmp1.reg);
|
||||
}
|
||||
// note: rbx is non-volatile so it will be saved across interrupt call automatically
|
||||
|
||||
RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi;
|
||||
RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi;
|
||||
|
||||
void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos)
|
||||
{
|
||||
Label skip;
|
||||
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
// Update L->ci->savedpc; required in case interrupt errors
|
||||
build.mov(rcx, sCode);
|
||||
build.lea(rcx, addr[rcx + rax * sizeof(Instruction)]);
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(qword[rax + offsetof(CallInfo, savedpc)], rcx);
|
||||
|
||||
// Skip if there is no interrupt set
|
||||
build.mov(tmp.reg, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(tmp.reg, qword[tmp.reg + offsetof(global_State, cb.interrupt)]);
|
||||
build.test(tmp.reg, tmp.reg);
|
||||
// Load interrupt handler; it may be nullptr in case the update raced with the check before we got here
|
||||
build.mov(rax, qword[rState + offsetof(lua_State, global)]);
|
||||
build.mov(rax, qword[rax + offsetof(global_State, cb.interrupt)]);
|
||||
build.test(rax, rax);
|
||||
build.jcc(ConditionX64::Zero, skip);
|
||||
|
||||
emitSetSavedPc(regs, build, pcpos + 1);
|
||||
|
||||
// Call interrupt
|
||||
// TODO: This code should move to the end of the function, or even be outlined so that it can be shared by multiple interruptible instructions
|
||||
IrCallWrapperX64 callWrap(regs, build);
|
||||
callWrap.addArgument(SizeX64::qword, rState);
|
||||
callWrap.addArgument(SizeX64::dword, -1);
|
||||
callWrap.call(tmp.release());
|
||||
|
||||
emitUpdateBase(build); // interrupt may have reallocated stack
|
||||
build.mov(rArg1, rState);
|
||||
build.mov(dwordReg(rArg2), -1);
|
||||
build.call(rax);
|
||||
|
||||
// Check if we need to exit
|
||||
build.mov(al, byte[rState + offsetof(lua_State, status)]);
|
||||
|
@ -322,6 +317,10 @@ void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos)
|
|||
emitExit(build, /* continueInVm */ false);
|
||||
|
||||
build.setLabel(skip);
|
||||
|
||||
emitUpdateBase(build); // interrupt may have reallocated stack
|
||||
|
||||
build.jmp(rbx);
|
||||
}
|
||||
|
||||
void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos)
|
||||
|
@ -354,14 +353,15 @@ void emitContinueCallInVm(AssemblyBuilderX64& build)
|
|||
|
||||
void emitReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers)
|
||||
{
|
||||
// input: ci in r8, res in rdi, number of written values in ecx
|
||||
RegisterX64 ci = r8;
|
||||
// input: res in rdi, number of written values in ecx
|
||||
RegisterX64 res = rdi;
|
||||
RegisterX64 written = ecx;
|
||||
|
||||
RegisterX64 ci = r8;
|
||||
RegisterX64 cip = r9;
|
||||
RegisterX64 nresults = esi;
|
||||
|
||||
build.mov(ci, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.lea(cip, addr[ci - sizeof(CallInfo)]);
|
||||
|
||||
// nresults = ci->nresults
|
||||
|
|
|
@ -53,31 +53,6 @@ constexpr OperandX64 sCode = qword[rsp + kStackSize + 8]; // Instruction* cod
|
|||
constexpr OperandX64 sTemporarySlot = addr[rsp + kStackSize + 16];
|
||||
constexpr OperandX64 sSpillArea = addr[rsp + kStackSize + 24];
|
||||
|
||||
// TODO: These should be replaced with a portable call function that checks the ABI at runtime and reorders moves accordingly to avoid conflicts
|
||||
#if defined(_WIN32)
|
||||
|
||||
constexpr RegisterX64 rArg1 = rcx;
|
||||
constexpr RegisterX64 rArg2 = rdx;
|
||||
constexpr RegisterX64 rArg3 = r8;
|
||||
constexpr RegisterX64 rArg4 = r9;
|
||||
constexpr RegisterX64 rArg5 = noreg;
|
||||
constexpr RegisterX64 rArg6 = noreg;
|
||||
constexpr OperandX64 sArg5 = qword[rsp + 32];
|
||||
constexpr OperandX64 sArg6 = qword[rsp + 40];
|
||||
|
||||
#else
|
||||
|
||||
constexpr RegisterX64 rArg1 = rdi;
|
||||
constexpr RegisterX64 rArg2 = rsi;
|
||||
constexpr RegisterX64 rArg3 = rdx;
|
||||
constexpr RegisterX64 rArg4 = rcx;
|
||||
constexpr RegisterX64 rArg5 = r8;
|
||||
constexpr RegisterX64 rArg6 = r9;
|
||||
constexpr OperandX64 sArg5 = noreg;
|
||||
constexpr OperandX64 sArg6 = noreg;
|
||||
|
||||
#endif
|
||||
|
||||
inline OperandX64 luauReg(int ri)
|
||||
{
|
||||
return xmmword[rBase + ri * sizeof(TValue)];
|
||||
|
@ -202,7 +177,7 @@ void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build);
|
|||
|
||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm);
|
||||
void emitUpdateBase(AssemblyBuilderX64& build);
|
||||
void emitInterrupt(IrRegAllocX64& regs, AssemblyBuilderX64& build, int pcpos);
|
||||
void emitInterrupt(AssemblyBuilderX64& build);
|
||||
void emitFallback(IrRegAllocX64& regs, AssemblyBuilderX64& build, int offset, int pcpos);
|
||||
|
||||
void emitContinueCallInVm(AssemblyBuilderX64& build);
|
||||
|
|
|
@ -18,6 +18,12 @@ namespace X64
|
|||
|
||||
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults)
|
||||
{
|
||||
// TODO: This should use IrCallWrapperX64
|
||||
RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi;
|
||||
RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi;
|
||||
RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx;
|
||||
RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx;
|
||||
|
||||
build.mov(rArg1, rState);
|
||||
build.lea(rArg2, luauRegAddress(ra));
|
||||
|
||||
|
@ -163,20 +169,34 @@ void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int
|
|||
}
|
||||
}
|
||||
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults)
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults, bool functionVariadic)
|
||||
{
|
||||
RegisterX64 ci = r8;
|
||||
RegisterX64 res = rdi;
|
||||
RegisterX64 written = ecx;
|
||||
|
||||
build.mov(ci, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(res, qword[ci + offsetof(CallInfo, func)]);
|
||||
if (functionVariadic)
|
||||
{
|
||||
build.mov(res, qword[rState + offsetof(lua_State, ci)]);
|
||||
build.mov(res, qword[res + offsetof(CallInfo, func)]);
|
||||
}
|
||||
else if (actualResults != 1)
|
||||
build.lea(res, addr[rBase - sizeof(TValue)]); // invariant: ci->func + 1 == ci->base for non-variadic frames
|
||||
|
||||
if (actualResults == 0)
|
||||
{
|
||||
build.xor_(written, written);
|
||||
build.jmp(helpers.return_);
|
||||
}
|
||||
else if (actualResults == 1 && !functionVariadic)
|
||||
{
|
||||
// fast path: minimizes res adjustments
|
||||
// note that we skipped res computation for this specific case above
|
||||
build.vmovups(xmm0, luauReg(ra));
|
||||
build.vmovups(xmmword[rBase - sizeof(TValue)], xmm0);
|
||||
build.mov(res, rBase);
|
||||
build.mov(written, 1);
|
||||
build.jmp(helpers.return_);
|
||||
}
|
||||
else if (actualResults >= 1 && actualResults <= 3)
|
||||
{
|
||||
for (int r = 0; r < actualResults; ++r)
|
||||
|
@ -206,8 +226,11 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i
|
|||
|
||||
Label repeatValueLoop, exitValueLoop;
|
||||
|
||||
if (actualResults == LUA_MULTRET)
|
||||
{
|
||||
build.cmp(vali, valend);
|
||||
build.jcc(ConditionX64::NotBelow, exitValueLoop);
|
||||
}
|
||||
|
||||
build.setLabel(repeatValueLoop);
|
||||
build.vmovups(xmm0, xmmword[vali]);
|
||||
|
@ -225,6 +248,11 @@ void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, i
|
|||
|
||||
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index)
|
||||
{
|
||||
// TODO: This should use IrCallWrapperX64
|
||||
RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi;
|
||||
RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi;
|
||||
RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx;
|
||||
|
||||
OperandX64 last = index + count - 1;
|
||||
|
||||
// Using non-volatile 'rbx' for dynamic 'count' value (for LUA_MULTRET) to skip later recomputation
|
||||
|
@ -327,6 +355,12 @@ void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRep
|
|||
// ipairs-style traversal is handled in IR
|
||||
LUAU_ASSERT(aux >= 0);
|
||||
|
||||
// TODO: This should use IrCallWrapperX64
|
||||
RegisterX64 rArg1 = (build.abi == ABIX64::Windows) ? rcx : rdi;
|
||||
RegisterX64 rArg2 = (build.abi == ABIX64::Windows) ? rdx : rsi;
|
||||
RegisterX64 rArg3 = (build.abi == ABIX64::Windows) ? r8 : rdx;
|
||||
RegisterX64 rArg4 = (build.abi == ABIX64::Windows) ? r9 : rcx;
|
||||
|
||||
// This is a fast-path for builtin table iteration, tag check for 'ra' has to be performed before emitting this instruction
|
||||
|
||||
// Registers are chosen in this way to simplify fallback code for the node part
|
||||
|
|
|
@ -18,7 +18,7 @@ class AssemblyBuilderX64;
|
|||
struct IrRegAllocX64;
|
||||
|
||||
void emitInstCall(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int nparams, int nresults);
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults);
|
||||
void emitInstReturn(AssemblyBuilderX64& build, ModuleHelpers& helpers, int ra, int actualResults, bool functionVariadic);
|
||||
void emitInstSetList(IrRegAllocX64& regs, AssemblyBuilderX64& build, int ra, int rb, int count, uint32_t index);
|
||||
void emitInstForGLoop(AssemblyBuilderX64& build, int ra, int aux, Label& loopRepeat);
|
||||
|
||||
|
|
|
@ -429,6 +429,7 @@ void IrBuilder::beginBlock(IrOp block)
|
|||
LUAU_ASSERT(target.start == ~0u || target.start == uint32_t(function.instructions.size()));
|
||||
|
||||
target.start = uint32_t(function.instructions.size());
|
||||
target.sortkey = target.start;
|
||||
|
||||
inTerminatedBlock = false;
|
||||
}
|
||||
|
|
|
@ -656,12 +656,8 @@ std::string dump(const IrFunction& function)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string toDot(const IrFunction& function, bool includeInst)
|
||||
static void appendLabelRegset(IrToStringContext& ctx, const std::vector<RegisterSet>& regSets, size_t blockIdx, const char* name)
|
||||
{
|
||||
std::string result;
|
||||
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
|
||||
|
||||
auto appendLabelRegset = [&ctx](const std::vector<RegisterSet>& regSets, size_t blockIdx, const char* name) {
|
||||
if (blockIdx < regSets.size())
|
||||
{
|
||||
const RegisterSet& rs = regSets[blockIdx];
|
||||
|
@ -673,11 +669,10 @@ std::string toDot(const IrFunction& function, bool includeInst)
|
|||
append(ctx.result, "}");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
append(ctx.result, "digraph CFG {\n");
|
||||
append(ctx.result, "node[shape=record]\n");
|
||||
}
|
||||
|
||||
static void appendBlocks(IrToStringContext& ctx, const IrFunction& function, bool includeInst, bool includeIn, bool includeOut, bool includeDef)
|
||||
{
|
||||
for (size_t i = 0; i < function.blocks.size(); i++)
|
||||
{
|
||||
const IrBlock& block = function.blocks[i];
|
||||
|
@ -692,7 +687,8 @@ std::string toDot(const IrFunction& function, bool includeInst)
|
|||
append(ctx.result, "label=\"{");
|
||||
toString(ctx, block, uint32_t(i));
|
||||
|
||||
appendLabelRegset(ctx.cfg.in, i, "in");
|
||||
if (includeIn)
|
||||
appendLabelRegset(ctx, ctx.cfg.in, i, "in");
|
||||
|
||||
if (includeInst && block.start != ~0u)
|
||||
{
|
||||
|
@ -709,11 +705,25 @@ std::string toDot(const IrFunction& function, bool includeInst)
|
|||
}
|
||||
}
|
||||
|
||||
appendLabelRegset(ctx.cfg.def, i, "def");
|
||||
appendLabelRegset(ctx.cfg.out, i, "out");
|
||||
if (includeDef)
|
||||
appendLabelRegset(ctx, ctx.cfg.def, i, "def");
|
||||
|
||||
if (includeOut)
|
||||
appendLabelRegset(ctx, ctx.cfg.out, i, "out");
|
||||
|
||||
append(ctx.result, "}\"];\n");
|
||||
}
|
||||
}
|
||||
|
||||
std::string toDot(const IrFunction& function, bool includeInst)
|
||||
{
|
||||
std::string result;
|
||||
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
|
||||
|
||||
append(ctx.result, "digraph CFG {\n");
|
||||
append(ctx.result, "node[shape=record]\n");
|
||||
|
||||
appendBlocks(ctx, function, includeInst, /* includeIn */ true, /* includeOut */ true, /* includeDef */ true);
|
||||
|
||||
for (size_t i = 0; i < function.blocks.size(); i++)
|
||||
{
|
||||
|
@ -750,6 +760,107 @@ std::string toDot(const IrFunction& function, bool includeInst)
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string toDotCfg(const IrFunction& function)
|
||||
{
|
||||
std::string result;
|
||||
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
|
||||
|
||||
append(ctx.result, "digraph CFG {\n");
|
||||
append(ctx.result, "node[shape=record]\n");
|
||||
|
||||
appendBlocks(ctx, function, /* includeInst */ false, /* includeIn */ false, /* includeOut */ false, /* includeDef */ true);
|
||||
|
||||
for (size_t i = 0; i < function.blocks.size() && i < ctx.cfg.successorsOffsets.size(); i++)
|
||||
{
|
||||
BlockIteratorWrapper succ = successors(ctx.cfg, unsigned(i));
|
||||
|
||||
for (uint32_t target : succ)
|
||||
append(ctx.result, "b%u -> b%u;\n", unsigned(i), target);
|
||||
}
|
||||
|
||||
append(ctx.result, "}\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string toDotDjGraph(const IrFunction& function)
|
||||
{
|
||||
std::string result;
|
||||
IrToStringContext ctx{result, function.blocks, function.constants, function.cfg};
|
||||
|
||||
append(ctx.result, "digraph CFG {\n");
|
||||
|
||||
for (size_t i = 0; i < ctx.blocks.size(); i++)
|
||||
{
|
||||
const IrBlock& block = ctx.blocks[i];
|
||||
|
||||
append(ctx.result, "b%u [", unsigned(i));
|
||||
|
||||
if (block.kind == IrBlockKind::Fallback)
|
||||
append(ctx.result, "style=filled;fillcolor=salmon;");
|
||||
else if (block.kind == IrBlockKind::Bytecode)
|
||||
append(ctx.result, "style=filled;fillcolor=palegreen;");
|
||||
|
||||
append(ctx.result, "label=\"");
|
||||
toString(ctx, block, uint32_t(i));
|
||||
append(ctx.result, "\"];\n");
|
||||
}
|
||||
|
||||
// Layer by depth in tree
|
||||
uint32_t depth = 0;
|
||||
bool found = true;
|
||||
|
||||
while (found)
|
||||
{
|
||||
found = false;
|
||||
|
||||
append(ctx.result, "{rank = same;");
|
||||
for (size_t i = 0; i < ctx.cfg.domOrdering.size(); i++)
|
||||
{
|
||||
if (ctx.cfg.domOrdering[i].depth == depth)
|
||||
{
|
||||
append(ctx.result, "b%u;", unsigned(i));
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
append(ctx.result, "}\n");
|
||||
|
||||
depth++;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ctx.cfg.domChildrenOffsets.size(); i++)
|
||||
{
|
||||
BlockIteratorWrapper dom = domChildren(ctx.cfg, unsigned(i));
|
||||
|
||||
for (uint32_t target : dom)
|
||||
append(ctx.result, "b%u -> b%u;\n", unsigned(i), target);
|
||||
|
||||
// Join edges are all successor edges that do not strongly dominate
|
||||
BlockIteratorWrapper succ = successors(ctx.cfg, unsigned(i));
|
||||
|
||||
for (uint32_t successor : succ)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (uint32_t target : dom)
|
||||
{
|
||||
if (target == successor)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
append(ctx.result, "b%u -> b%u [style=dotted];\n", unsigned(i), successor);
|
||||
}
|
||||
}
|
||||
|
||||
append(ctx.result, "}\n");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string dumpDot(const IrFunction& function, bool includeInst)
|
||||
{
|
||||
std::string result = toDot(function, includeInst);
|
||||
|
|
|
@ -1165,25 +1165,17 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
}
|
||||
case IrCmd::INTERRUPT:
|
||||
{
|
||||
RegisterA64 temp = regs.allocTemp(KindA64::x);
|
||||
regs.spill(build, index);
|
||||
|
||||
Label skip, next;
|
||||
build.ldr(temp, mem(rState, offsetof(lua_State, global)));
|
||||
build.ldr(temp, mem(temp, offsetof(global_State, cb.interrupt)));
|
||||
build.cbz(temp, skip);
|
||||
Label self;
|
||||
|
||||
size_t spills = regs.spill(build, index);
|
||||
build.ldr(x0, mem(rState, offsetof(lua_State, global)));
|
||||
build.ldr(x0, mem(x0, offsetof(global_State, cb.interrupt)));
|
||||
build.cbnz(x0, self);
|
||||
|
||||
// Jump to outlined interrupt handler, it will give back control to x1
|
||||
build.mov(x0, (uintOp(inst.a) + 1) * sizeof(Instruction));
|
||||
build.adr(x1, next);
|
||||
build.b(helpers.interrupt);
|
||||
Label next = build.setLabel();
|
||||
|
||||
build.setLabel(next);
|
||||
|
||||
regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state
|
||||
|
||||
build.setLabel(skip);
|
||||
interruptHandlers.push_back({self, uintOp(inst.a), next});
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_GC:
|
||||
|
@ -1733,6 +1725,20 @@ void IrLoweringA64::finishBlock()
|
|||
regs.assertNoSpills();
|
||||
}
|
||||
|
||||
void IrLoweringA64::finishFunction()
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; interrupt handlers\n");
|
||||
|
||||
for (InterruptHandler& handler : interruptHandlers)
|
||||
{
|
||||
build.setLabel(handler.self);
|
||||
build.mov(x0, (handler.pcpos + 1) * sizeof(Instruction));
|
||||
build.adr(x1, handler.next);
|
||||
build.b(helpers.interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
bool IrLoweringA64::hasError() const
|
||||
{
|
||||
return error;
|
||||
|
|
|
@ -27,6 +27,7 @@ struct IrLoweringA64
|
|||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
void finishBlock();
|
||||
void finishFunction();
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
|
@ -53,6 +54,13 @@ struct IrLoweringA64
|
|||
IrBlock& blockOp(IrOp op) const;
|
||||
Label& labelOp(IrOp op) const;
|
||||
|
||||
struct InterruptHandler
|
||||
{
|
||||
Label self;
|
||||
unsigned int pcpos;
|
||||
Label next;
|
||||
};
|
||||
|
||||
AssemblyBuilderA64& build;
|
||||
ModuleHelpers& helpers;
|
||||
NativeState& data;
|
||||
|
@ -63,6 +71,8 @@ struct IrLoweringA64
|
|||
|
||||
IrValueLocationTracking valueTracker;
|
||||
|
||||
std::vector<InterruptHandler> interruptHandlers;
|
||||
|
||||
bool error = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -958,8 +958,27 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
break;
|
||||
}
|
||||
case IrCmd::INTERRUPT:
|
||||
emitInterrupt(regs, build, uintOp(inst.a));
|
||||
{
|
||||
unsigned pcpos = uintOp(inst.a);
|
||||
|
||||
// We unconditionally spill values here because that allows us to ignore register state when we synthesize interrupt handler
|
||||
// This can be changed in the future if we can somehow record interrupt handler code separately
|
||||
// Since interrupts are loop edges or call/ret, we don't have a significant opportunity for register reuse here anyway
|
||||
regs.preserveAndFreeInstValues();
|
||||
|
||||
ScopedRegX64 tmp{regs, SizeX64::qword};
|
||||
|
||||
Label self;
|
||||
|
||||
build.mov(tmp.reg, qword[rState + offsetof(lua_State, global)]);
|
||||
build.cmp(qword[tmp.reg + offsetof(global_State, cb.interrupt)], 0);
|
||||
build.jcc(ConditionX64::NotEqual, self);
|
||||
|
||||
Label next = build.setLabel();
|
||||
|
||||
interruptHandlers.push_back({self, pcpos, next});
|
||||
break;
|
||||
}
|
||||
case IrCmd::CHECK_GC:
|
||||
callStepGc(regs, build);
|
||||
break;
|
||||
|
@ -991,7 +1010,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
}
|
||||
case IrCmd::SET_SAVEDPC:
|
||||
{
|
||||
// This is like emitSetSavedPc, but using register allocation instead of relying on rax/rdx
|
||||
ScopedRegX64 tmp1{regs, SizeX64::qword};
|
||||
ScopedRegX64 tmp2{regs, SizeX64::qword};
|
||||
|
||||
|
@ -1048,7 +1066,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
|||
case IrCmd::RETURN:
|
||||
regs.assertAllFree();
|
||||
regs.assertNoSpills();
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b));
|
||||
emitInstReturn(build, helpers, vmRegOp(inst.a), intOp(inst.b), function.variadic);
|
||||
break;
|
||||
case IrCmd::FORGLOOP:
|
||||
regs.assertAllFree();
|
||||
|
@ -1350,6 +1368,20 @@ void IrLoweringX64::finishBlock()
|
|||
regs.assertNoSpills();
|
||||
}
|
||||
|
||||
void IrLoweringX64::finishFunction()
|
||||
{
|
||||
if (build.logText)
|
||||
build.logAppend("; interrupt handlers\n");
|
||||
|
||||
for (InterruptHandler& handler : interruptHandlers)
|
||||
{
|
||||
build.setLabel(handler.self);
|
||||
build.mov(rax, handler.pcpos + 1);
|
||||
build.lea(rbx, handler.next);
|
||||
build.jmp(helpers.interrupt);
|
||||
}
|
||||
}
|
||||
|
||||
bool IrLoweringX64::hasError() const
|
||||
{
|
||||
// If register allocator had to use more stack slots than we have available, this function can't run natively
|
||||
|
|
|
@ -29,6 +29,7 @@ struct IrLoweringX64
|
|||
|
||||
void lowerInst(IrInst& inst, uint32_t index, IrBlock& next);
|
||||
void finishBlock();
|
||||
void finishFunction();
|
||||
|
||||
bool hasError() const;
|
||||
|
||||
|
@ -53,6 +54,13 @@ struct IrLoweringX64
|
|||
IrBlock& blockOp(IrOp op) const;
|
||||
Label& labelOp(IrOp op) const;
|
||||
|
||||
struct InterruptHandler
|
||||
{
|
||||
Label self;
|
||||
unsigned int pcpos;
|
||||
Label next;
|
||||
};
|
||||
|
||||
AssemblyBuilderX64& build;
|
||||
ModuleHelpers& helpers;
|
||||
NativeState& data;
|
||||
|
@ -62,6 +70,8 @@ struct IrLoweringX64
|
|||
IrRegAllocX64 regs;
|
||||
|
||||
IrValueLocationTracking valueTracker;
|
||||
|
||||
std::vector<InterruptHandler> interruptHandlers;
|
||||
};
|
||||
|
||||
} // namespace X64
|
||||
|
|
|
@ -1059,16 +1059,21 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector<uint8_t>& visited
|
|||
// TODO: using values from the first block can cause 'live out' of the linear block predecessor to not have all required registers
|
||||
constPropInBlock(build, startingBlock, state);
|
||||
|
||||
// Veryfy that target hasn't changed
|
||||
// Verify that target hasn't changed
|
||||
LUAU_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx);
|
||||
|
||||
// Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below
|
||||
uint32_t startingInsn = startingBlock.start;
|
||||
|
||||
// Create new linearized block into which we are going to redirect starting block jump
|
||||
IrOp newBlock = build.block(IrBlockKind::Linearized);
|
||||
visited.push_back(false);
|
||||
|
||||
// TODO: placement of linear blocks in final lowering is sub-optimal, it should follow our predecessor
|
||||
build.beginBlock(newBlock);
|
||||
|
||||
// By default, blocks are ordered according to start instruction; we alter sort order to make sure linearized block is placed right after the starting block
|
||||
function.blocks[newBlock.index].sortkey = startingInsn + 1;
|
||||
|
||||
replace(function, termInst.a, newBlock);
|
||||
|
||||
// Clone the collected path into our fresh block
|
||||
|
|
|
@ -413,8 +413,10 @@ enum LuauBytecodeTag
|
|||
{
|
||||
// Bytecode version; runtime supports [MIN, MAX], compiler emits TARGET by default but may emit a higher version when flags are enabled
|
||||
LBC_VERSION_MIN = 3,
|
||||
LBC_VERSION_MAX = 3,
|
||||
LBC_VERSION_MAX = 4,
|
||||
LBC_VERSION_TARGET = 3,
|
||||
// Type encoding version
|
||||
LBC_TYPE_VERSION = 1,
|
||||
// Types of constant table entries
|
||||
LBC_CONSTANT_NIL = 0,
|
||||
LBC_CONSTANT_BOOLEAN,
|
||||
|
@ -425,6 +427,25 @@ enum LuauBytecodeTag
|
|||
LBC_CONSTANT_CLOSURE,
|
||||
};
|
||||
|
||||
// Type table tags
|
||||
enum LuauBytecodeEncodedType
|
||||
{
|
||||
LBC_TYPE_NIL = 0,
|
||||
LBC_TYPE_BOOLEAN,
|
||||
LBC_TYPE_NUMBER,
|
||||
LBC_TYPE_STRING,
|
||||
LBC_TYPE_TABLE,
|
||||
LBC_TYPE_FUNCTION,
|
||||
LBC_TYPE_THREAD,
|
||||
LBC_TYPE_USERDATA,
|
||||
LBC_TYPE_VECTOR,
|
||||
|
||||
LBC_TYPE_ANY = 15,
|
||||
LBC_TYPE_OPTIONAL_BIT = 1 << 7,
|
||||
|
||||
LBC_TYPE_INVALID = 256,
|
||||
};
|
||||
|
||||
// Builtin function ids, used in LOP_FASTCALL
|
||||
enum LuauBuiltinFunction
|
||||
{
|
||||
|
|
|
@ -74,6 +74,8 @@ public:
|
|||
void foldJumps();
|
||||
void expandJumps();
|
||||
|
||||
void setFunctionTypeInfo(std::string value);
|
||||
|
||||
void setDebugFunctionName(StringRef name);
|
||||
void setDebugFunctionLineDefined(int line);
|
||||
void setDebugLine(int line);
|
||||
|
@ -118,6 +120,7 @@ public:
|
|||
std::string dumpFunction(uint32_t id) const;
|
||||
std::string dumpEverything() const;
|
||||
std::string dumpSourceRemarks() const;
|
||||
std::string dumpTypeInfo() const;
|
||||
|
||||
void annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const;
|
||||
|
||||
|
@ -132,6 +135,7 @@ public:
|
|||
static std::string getError(const std::string& message);
|
||||
|
||||
static uint8_t getVersion();
|
||||
static uint8_t getTypeEncodingVersion();
|
||||
|
||||
private:
|
||||
struct Constant
|
||||
|
@ -186,6 +190,7 @@ private:
|
|||
std::string dump;
|
||||
std::string dumpname;
|
||||
std::vector<int> dumpinstoffs;
|
||||
std::string typeinfo;
|
||||
};
|
||||
|
||||
struct DebugLocal
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(BytecodeVersion4, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
@ -513,6 +515,11 @@ bool BytecodeBuilder::patchSkipC(size_t jumpLabel, size_t targetLabel)
|
|||
return true;
|
||||
}
|
||||
|
||||
void BytecodeBuilder::setFunctionTypeInfo(std::string value)
|
||||
{
|
||||
functions[currentFunction].typeinfo = std::move(value);
|
||||
}
|
||||
|
||||
void BytecodeBuilder::setDebugFunctionName(StringRef name)
|
||||
{
|
||||
unsigned int index = addStringTableEntry(name);
|
||||
|
@ -606,6 +613,13 @@ void BytecodeBuilder::finalize()
|
|||
|
||||
bytecode = char(version);
|
||||
|
||||
if (FFlag::BytecodeVersion4)
|
||||
{
|
||||
uint8_t typesversion = getTypeEncodingVersion();
|
||||
LUAU_ASSERT(typesversion == 1);
|
||||
writeByte(bytecode, typesversion);
|
||||
}
|
||||
|
||||
writeStringTable(bytecode);
|
||||
|
||||
writeVarInt(bytecode, uint32_t(functions.size()));
|
||||
|
@ -628,6 +642,14 @@ void BytecodeBuilder::writeFunction(std::string& ss, uint32_t id) const
|
|||
writeByte(ss, func.numupvalues);
|
||||
writeByte(ss, func.isvararg);
|
||||
|
||||
if (FFlag::BytecodeVersion4)
|
||||
{
|
||||
writeByte(ss, 0); // Reserved for cgflags
|
||||
|
||||
writeVarInt(ss, uint32_t(func.typeinfo.size()));
|
||||
ss.append(func.typeinfo);
|
||||
}
|
||||
|
||||
// instructions
|
||||
writeVarInt(ss, uint32_t(insns.size()));
|
||||
|
||||
|
@ -1092,9 +1114,18 @@ std::string BytecodeBuilder::getError(const std::string& message)
|
|||
uint8_t BytecodeBuilder::getVersion()
|
||||
{
|
||||
// This function usually returns LBC_VERSION_TARGET but may sometimes return a higher number (within LBC_VERSION_MIN/MAX) under fast flags
|
||||
|
||||
if (FFlag::BytecodeVersion4)
|
||||
return 4;
|
||||
|
||||
return LBC_VERSION_TARGET;
|
||||
}
|
||||
|
||||
uint8_t BytecodeBuilder::getTypeEncodingVersion()
|
||||
{
|
||||
return LBC_TYPE_VERSION;
|
||||
}
|
||||
|
||||
#ifdef LUAU_ASSERTENABLED
|
||||
void BytecodeBuilder::validate() const
|
||||
{
|
||||
|
@ -2269,6 +2300,75 @@ std::string BytecodeBuilder::dumpSourceRemarks() const
|
|||
return result;
|
||||
}
|
||||
|
||||
static const char* getBaseTypeString(uint8_t type)
|
||||
{
|
||||
uint8_t tag = type & ~LBC_TYPE_OPTIONAL_BIT;
|
||||
switch (tag)
|
||||
{
|
||||
case LBC_TYPE_NIL:
|
||||
return "nil";
|
||||
case LBC_TYPE_BOOLEAN:
|
||||
return "boolean";
|
||||
case LBC_TYPE_NUMBER:
|
||||
return "number";
|
||||
case LBC_TYPE_STRING:
|
||||
return "string";
|
||||
case LBC_TYPE_TABLE:
|
||||
return "{ }";
|
||||
case LBC_TYPE_FUNCTION:
|
||||
return "function( )";
|
||||
case LBC_TYPE_THREAD:
|
||||
return "thread";
|
||||
case LBC_TYPE_USERDATA:
|
||||
return "userdata";
|
||||
case LBC_TYPE_VECTOR:
|
||||
return "vector";
|
||||
case LBC_TYPE_ANY:
|
||||
return "any";
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!"Unhandled type in getBaseTypeString");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string BytecodeBuilder::dumpTypeInfo() const
|
||||
{
|
||||
std::string result;
|
||||
|
||||
for (size_t i = 0; i < functions.size(); ++i)
|
||||
{
|
||||
const std::string& typeinfo = functions[i].typeinfo;
|
||||
if (typeinfo.empty())
|
||||
continue;
|
||||
|
||||
uint8_t encodedType = typeinfo[0];
|
||||
|
||||
LUAU_ASSERT(encodedType == LBC_TYPE_FUNCTION);
|
||||
|
||||
formatAppend(result, "%zu: function(", i);
|
||||
|
||||
LUAU_ASSERT(typeinfo.size() >= 2);
|
||||
|
||||
uint8_t numparams = typeinfo[1];
|
||||
|
||||
LUAU_ASSERT(size_t(1 + numparams - 1) < typeinfo.size());
|
||||
|
||||
for (uint8_t i = 0; i < numparams; ++i)
|
||||
{
|
||||
uint8_t et = typeinfo[2 + i];
|
||||
const char* optional = (et & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
formatAppend(result, "%s%s", getBaseTypeString(et), optional);
|
||||
|
||||
if (i + 1 != numparams)
|
||||
formatAppend(result, ", ");
|
||||
}
|
||||
|
||||
formatAppend(result, ")\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BytecodeBuilder::annotateInstruction(std::string& result, uint32_t fid, uint32_t instpos) const
|
||||
{
|
||||
if ((dumpFlags & Dump_Code) == 0)
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "ConstantFolding.h"
|
||||
#include "CostModel.h"
|
||||
#include "TableShape.h"
|
||||
#include "Types.h"
|
||||
#include "ValueTracking.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
@ -25,7 +26,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
|||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileInlineDefer, false)
|
||||
LUAU_FASTFLAGVARIABLE(CompileFunctionType, false)
|
||||
LUAU_FASTFLAG(BytecodeVersion4)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
@ -202,6 +204,13 @@ struct Compiler
|
|||
|
||||
setDebugLine(func);
|
||||
|
||||
if (FFlag::BytecodeVersion4 && FFlag::CompileFunctionType)
|
||||
{
|
||||
std::string funcType = getFunctionType(func);
|
||||
if (!funcType.empty())
|
||||
bytecode.setFunctionTypeInfo(std::move(funcType));
|
||||
}
|
||||
|
||||
if (func->vararg)
|
||||
bytecode.emitABC(LOP_PREPVARARGS, uint8_t(self + func->args.size), 0, 0);
|
||||
|
||||
|
@ -560,15 +569,7 @@ struct Compiler
|
|||
size_t oldLocals = localStack.size();
|
||||
|
||||
std::vector<InlineArg> args;
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
{
|
||||
args.reserve(func->args.size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// note that we push the frame early; this is needed to block recursive inline attempts
|
||||
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
||||
}
|
||||
|
||||
// evaluate all arguments; note that we don't emit code for constant arguments (relying on constant folding)
|
||||
// note that compiler state (variable registers/values) does not change here - we defer that to a separate loop below to handle nested calls
|
||||
|
@ -590,16 +591,8 @@ struct Compiler
|
|||
else
|
||||
LUAU_ASSERT(!"Unexpected expression type");
|
||||
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
{
|
||||
for (size_t j = i; j < func->args.size; ++j)
|
||||
args.push_back({func->args.data[j], uint8_t(reg + (j - i))});
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t j = i; j < func->args.size; ++j)
|
||||
pushLocal(func->args.data[j], uint8_t(reg + (j - i)));
|
||||
}
|
||||
|
||||
// all remaining function arguments have been allocated and assigned to
|
||||
break;
|
||||
|
@ -614,26 +607,17 @@ struct Compiler
|
|||
else
|
||||
bytecode.emitABC(LOP_LOADNIL, reg, 0, 0);
|
||||
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
args.push_back({var, reg});
|
||||
else
|
||||
pushLocal(var, reg);
|
||||
}
|
||||
else if (arg == nullptr)
|
||||
{
|
||||
// since the argument is not mutated, we can simply fold the value into the expressions that need it
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
args.push_back({var, kInvalidReg, {Constant::Type_Nil}});
|
||||
else
|
||||
locstants[var] = {Constant::Type_Nil};
|
||||
}
|
||||
else if (const Constant* cv = constants.find(arg); cv && cv->type != Constant::Type_Unknown)
|
||||
{
|
||||
// since the argument is not mutated, we can simply fold the value into the expressions that need it
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
args.push_back({var, kInvalidReg, *cv});
|
||||
else
|
||||
locstants[var] = *cv;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -643,20 +627,14 @@ struct Compiler
|
|||
// if the argument is a local that isn't mutated, we will simply reuse the existing register
|
||||
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
|
||||
{
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
args.push_back({var, uint8_t(reg)});
|
||||
else
|
||||
pushLocal(var, uint8_t(reg));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t temp = allocReg(arg, 1);
|
||||
compileExprTemp(arg, temp);
|
||||
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
args.push_back({var, temp});
|
||||
else
|
||||
pushLocal(var, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -668,8 +646,6 @@ struct Compiler
|
|||
compileExprAuto(expr->args.data[i], rsi);
|
||||
}
|
||||
|
||||
if (FFlag::LuauCompileInlineDefer)
|
||||
{
|
||||
// apply all evaluated arguments to the compiler state
|
||||
// note: locals use current startpc for debug info, although some of them have been computed earlier; this is similar to compileStatLocal
|
||||
for (InlineArg& arg : args)
|
||||
|
@ -680,7 +656,6 @@ struct Compiler
|
|||
|
||||
// the inline frame will be used to compile return statements as well as to reject recursive inlining attempts
|
||||
inlineFrames.push_back({func, oldLocals, target, targetCount});
|
||||
}
|
||||
|
||||
// fold constant values updated above into expressions in the function body
|
||||
foldConstants(constants, variables, locstants, builtinsFold, func->body);
|
||||
|
|
106
Compiler/src/Types.cpp
Normal file
106
Compiler/src/Types.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/BytecodeBuilder.h"
|
||||
|
||||
#include "Types.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static LuauBytecodeEncodedType getType(AstType* ty)
|
||||
{
|
||||
if (AstTypeReference* ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
if (ref->name == "nil")
|
||||
return LBC_TYPE_NIL;
|
||||
else if (ref->name == "boolean")
|
||||
return LBC_TYPE_BOOLEAN;
|
||||
else if (ref->name == "number")
|
||||
return LBC_TYPE_NUMBER;
|
||||
else if (ref->name == "string")
|
||||
return LBC_TYPE_STRING;
|
||||
else if (ref->name == "thread")
|
||||
return LBC_TYPE_THREAD;
|
||||
else if (ref->name == "any" || ref->name == "unknown")
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
else if (AstTypeTable* table = ty->as<AstTypeTable>())
|
||||
{
|
||||
return LBC_TYPE_TABLE;
|
||||
}
|
||||
else if (AstTypeFunction* func = ty->as<AstTypeFunction>())
|
||||
{
|
||||
return LBC_TYPE_FUNCTION;
|
||||
}
|
||||
else if (AstTypeUnion* un = ty->as<AstTypeUnion>())
|
||||
{
|
||||
bool optional = false;
|
||||
LuauBytecodeEncodedType type = LBC_TYPE_INVALID;
|
||||
|
||||
for (AstType* ty : un->types)
|
||||
{
|
||||
LuauBytecodeEncodedType et = getType(ty);
|
||||
|
||||
if (et == LBC_TYPE_NIL)
|
||||
{
|
||||
optional = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == LBC_TYPE_INVALID)
|
||||
{
|
||||
type = et;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type != et)
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
||||
if (type == LBC_TYPE_INVALID)
|
||||
return LBC_TYPE_ANY;
|
||||
|
||||
return LuauBytecodeEncodedType(type | (optional && (type != LBC_TYPE_ANY) ? LBC_TYPE_OPTIONAL_BIT : 0));
|
||||
}
|
||||
else if (AstTypeIntersection* inter = ty->as<AstTypeIntersection>())
|
||||
{
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
||||
std::string getFunctionType(const AstExprFunction* func)
|
||||
{
|
||||
if (func->vararg || func->generics.size || func->genericPacks.size)
|
||||
return {};
|
||||
|
||||
bool self = func->self != 0;
|
||||
|
||||
std::string typeInfo;
|
||||
typeInfo.reserve(func->args.size + self + 2);
|
||||
|
||||
typeInfo.push_back(LBC_TYPE_FUNCTION);
|
||||
typeInfo.push_back(uint8_t(self + func->args.size));
|
||||
|
||||
if (self)
|
||||
typeInfo.push_back(LBC_TYPE_TABLE);
|
||||
|
||||
bool haveNonAnyParam = false;
|
||||
for (AstLocal* arg : func->args)
|
||||
{
|
||||
LuauBytecodeEncodedType ty = arg->annotation ? getType(arg->annotation) : LBC_TYPE_ANY;
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
haveNonAnyParam = true;
|
||||
|
||||
typeInfo.push_back(ty);
|
||||
}
|
||||
|
||||
// If all parameters simplify to any, we can just omit type info for this function
|
||||
if (!haveNonAnyParam)
|
||||
return {};
|
||||
|
||||
return typeInfo;
|
||||
}
|
||||
|
||||
} // namespace Luau
|
9
Compiler/src/Types.h
Normal file
9
Compiler/src/Types.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
std::string getFunctionType(const AstExprFunction* func);
|
||||
} // namespace Luau
|
|
@ -43,6 +43,7 @@ target_sources(Luau.Compiler PRIVATE
|
|||
Compiler/src/ConstantFolding.cpp
|
||||
Compiler/src/CostModel.cpp
|
||||
Compiler/src/TableShape.cpp
|
||||
Compiler/src/Types.cpp
|
||||
Compiler/src/ValueTracking.cpp
|
||||
Compiler/src/lcode.cpp
|
||||
Compiler/src/Builtins.h
|
||||
|
@ -50,6 +51,7 @@ target_sources(Luau.Compiler PRIVATE
|
|||
Compiler/src/ConstantFolding.h
|
||||
Compiler/src/CostModel.h
|
||||
Compiler/src/TableShape.h
|
||||
Compiler/src/Types.h
|
||||
Compiler/src/ValueTracking.h
|
||||
)
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ LUAI_FUNC void luaV_gettable(lua_State* L, const TValue* t, TValue* key, StkId v
|
|||
LUAI_FUNC void luaV_settable(lua_State* L, const TValue* t, TValue* key, StkId val);
|
||||
LUAI_FUNC void luaV_concat(lua_State* L, int total, int last);
|
||||
LUAI_FUNC void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id, bool propagatenil);
|
||||
LUAI_FUNC void luaV_getimport_dep(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil);
|
||||
LUAI_FUNC void luaV_prepareFORN(lua_State* L, StkId plimit, StkId pstep, StkId pinit);
|
||||
LUAI_FUNC void luaV_callTM(lua_State* L, int nparams, int res);
|
||||
LUAI_FUNC void luaV_tryfuncTM(lua_State* L, StkId func);
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauGetImportDirect)
|
||||
|
||||
// Disable c99-designator to avoid the warning in CGOTO dispatch table
|
||||
#ifdef __clang__
|
||||
#if __has_warning("-Wc99-designator")
|
||||
|
@ -432,21 +430,9 @@ reentry:
|
|||
{
|
||||
uint32_t aux = *pc++;
|
||||
|
||||
if (FFlag::LuauGetImportDirect)
|
||||
{
|
||||
VM_PROTECT(luaV_getimport(L, cl->env, k, ra, aux, /* propagatenil= */ false));
|
||||
VM_NEXT();
|
||||
}
|
||||
else
|
||||
{
|
||||
VM_PROTECT(luaV_getimport_dep(L, cl->env, k, aux, /* propagatenil= */ false));
|
||||
ra = VM_REG(LUAU_INSN_A(insn)); // previous call may change the stack
|
||||
|
||||
setobj2s(L, ra, L->top - 1);
|
||||
L->top--;
|
||||
VM_NEXT();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VM_CASE(LOP_GETTABLEKS)
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauGetImportDirect, false)
|
||||
|
||||
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
|
||||
template<typename T>
|
||||
struct TempBuffer
|
||||
|
@ -77,34 +75,6 @@ void luaV_getimport(lua_State* L, Table* env, TValue* k, StkId res, uint32_t id,
|
|||
luaV_gettable(L, res, &k[id2], res);
|
||||
}
|
||||
|
||||
void luaV_getimport_dep(lua_State* L, Table* env, TValue* k, uint32_t id, bool propagatenil)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauGetImportDirect);
|
||||
|
||||
int count = id >> 30;
|
||||
int id0 = count > 0 ? int(id >> 20) & 1023 : -1;
|
||||
int id1 = count > 1 ? int(id >> 10) & 1023 : -1;
|
||||
int id2 = count > 2 ? int(id) & 1023 : -1;
|
||||
|
||||
// allocate a stack slot so that we can do table lookups
|
||||
luaD_checkstack(L, 1);
|
||||
setnilvalue(L->top);
|
||||
L->top++;
|
||||
|
||||
// global lookup into L->top-1
|
||||
TValue g;
|
||||
sethvalue(L, &g, env);
|
||||
luaV_gettable(L, &g, &k[id0], L->top - 1);
|
||||
|
||||
// table lookup for id1
|
||||
if (id1 >= 0 && (!propagatenil || !ttisnil(L->top - 1)))
|
||||
luaV_gettable(L, L->top - 1, &k[id1], L->top - 1);
|
||||
|
||||
// table lookup for id2
|
||||
if (id2 >= 0 && (!propagatenil || !ttisnil(L->top - 1)))
|
||||
luaV_gettable(L, L->top - 1, &k[id2], L->top - 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static T read(const char* data, size_t size, size_t& offset)
|
||||
{
|
||||
|
@ -153,8 +123,6 @@ static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id)
|
|||
// note: we call getimport with nil propagation which means that accesses to table chains like A.B.C will resolve in nil
|
||||
// this is technically not necessary but it reduces the number of exceptions when loading scripts that rely on getfenv/setfenv for global
|
||||
// injection
|
||||
if (FFlag::LuauGetImportDirect)
|
||||
{
|
||||
// allocate a stack slot so that we can do table lookups
|
||||
luaD_checkstack(L, 1);
|
||||
setnilvalue(L->top);
|
||||
|
@ -162,9 +130,6 @@ static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id)
|
|||
|
||||
luaV_getimport(L, L->gt, self->k, L->top - 1, self->id, /* propagatenil= */ true);
|
||||
}
|
||||
else
|
||||
luaV_getimport_dep(L, L->gt, self->k, self->id, /* propagatenil= */ true);
|
||||
}
|
||||
};
|
||||
|
||||
ResolveImport ri = {k, id};
|
||||
|
@ -194,6 +159,8 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||
|
||||
uint8_t version = read<uint8_t>(data, size, offset);
|
||||
|
||||
|
||||
|
||||
// 0 means the rest of the bytecode is the error message
|
||||
if (version == 0)
|
||||
{
|
||||
|
@ -221,6 +188,13 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||
|
||||
TString* source = luaS_new(L, chunkname);
|
||||
|
||||
|
||||
if (version >= 4)
|
||||
{
|
||||
uint8_t typesversion = read<uint8_t>(data, size, offset);
|
||||
LUAU_ASSERT(typesversion == 1);
|
||||
}
|
||||
|
||||
// string table
|
||||
unsigned int stringCount = readVarInt(data, size, offset);
|
||||
TempBuffer<TString*> strings(L, stringCount);
|
||||
|
@ -248,6 +222,25 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
|||
p->nups = read<uint8_t>(data, size, offset);
|
||||
p->is_vararg = read<uint8_t>(data, size, offset);
|
||||
|
||||
if (version >= 4)
|
||||
{
|
||||
uint8_t cgflags = read<uint8_t>(data, size, offset);
|
||||
LUAU_ASSERT(cgflags == 0);
|
||||
|
||||
uint32_t typesize = readVarInt(data, size, offset);
|
||||
|
||||
if (typesize)
|
||||
{
|
||||
uint8_t* types = (uint8_t*)data + offset;
|
||||
|
||||
LUAU_ASSERT(typesize == unsigned(2 + p->numparams));
|
||||
LUAU_ASSERT(types[0] == LBC_TYPE_FUNCTION);
|
||||
LUAU_ASSERT(types[1] == p->numparams);
|
||||
|
||||
offset += typesize;
|
||||
}
|
||||
}
|
||||
|
||||
p->sizecode = readVarInt(data, size, offset);
|
||||
p->code = luaM_newarray(L, p->sizecode, Instruction, p->memcat);
|
||||
for (int j = 0; j < p->sizecode; ++j)
|
||||
|
|
|
@ -73,7 +73,7 @@ def arrayRangeOffset(count, offset):
|
|||
|
||||
return result
|
||||
|
||||
def getCallgrindOutput(lines):
|
||||
def getCallgrindOutput(stdout, lines):
|
||||
result = []
|
||||
name = None
|
||||
|
||||
|
@ -86,12 +86,36 @@ def getCallgrindOutput(lines):
|
|||
result += "|><|" + name + "|><|" + str(insn / CALLGRIND_INSN_PER_SEC * 1000.0) + "||_||"
|
||||
name = None
|
||||
|
||||
# If no results were found above, this may indicate the native executable running
|
||||
# the benchmark doesn't have support for callgrind builtin. In that case just
|
||||
# report the "totals" from the output file.
|
||||
if len(result) == 0:
|
||||
elements = stdout.decode('utf8').split("|><|")
|
||||
if len(elements) >= 2:
|
||||
name = elements[1]
|
||||
|
||||
for l in lines:
|
||||
if l.startswith("totals: "):
|
||||
insn = int(l[8:])
|
||||
# Note: we only run each bench once under callgrind so we only report a single time per run; callgrind instruction count variance is ~0.01% so it might as well be zero
|
||||
result += "|><|" + name + "|><|" + str(insn / CALLGRIND_INSN_PER_SEC * 1000.0) + "||_||"
|
||||
|
||||
return "".join(result)
|
||||
|
||||
def conditionallyShowCommand(cmd):
|
||||
if arguments.show_commands:
|
||||
print(f'{colored(Color.BLUE, "EXECUTING")}: {cmd}')
|
||||
|
||||
def checkValgrindExecutable():
|
||||
"""Return true if valgrind can be successfully spawned"""
|
||||
try:
|
||||
subprocess.check_call("valgrind --version", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except:
|
||||
print(f"{colored(Color.YELLOW, 'WARNING')}: Unable to spawn 'valgrind'. Please ensure valgrind is installed when using '--callgrind'.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def getVmOutput(cmd):
|
||||
if os.name == "nt":
|
||||
try:
|
||||
|
@ -103,17 +127,24 @@ def getVmOutput(cmd):
|
|||
except:
|
||||
return ""
|
||||
elif arguments.callgrind:
|
||||
if not checkValgrindExecutable():
|
||||
return ""
|
||||
output_path = os.path.join(scriptdir, "callgrind.out")
|
||||
try:
|
||||
os.unlink(output_path) # Remove stale output
|
||||
except:
|
||||
pass
|
||||
fullCmd = "valgrind --tool=callgrind --callgrind-out-file=callgrind.out --combine-dumps=yes --dump-line=no " + cmd
|
||||
conditionallyShowCommand(fullCmd)
|
||||
subprocess.check_call(fullCmd, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=scriptdir)
|
||||
path = os.path.join(scriptdir, "callgrind.out")
|
||||
with open(path, "r") as file:
|
||||
try:
|
||||
output = subprocess.check_output(fullCmd, shell=True, stderr=subprocess.DEVNULL, cwd=scriptdir)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"{colored(Color.YELLOW, 'WARNING')}: Valgrind returned error code {e.returncode}")
|
||||
output = e.output
|
||||
with open(output_path, "r") as file:
|
||||
lines = file.readlines()
|
||||
os.unlink(path)
|
||||
return getCallgrindOutput(lines)
|
||||
except:
|
||||
return ""
|
||||
os.unlink(output_path)
|
||||
return getCallgrindOutput(output, lines)
|
||||
else:
|
||||
conditionallyShowCommand(cmd)
|
||||
with subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, cwd=scriptdir) as p:
|
||||
|
@ -352,7 +383,7 @@ def analyzeResult(subdir, main, comparisons):
|
|||
if influxReporter != None:
|
||||
influxReporter.report_result(subdir, main.name, main.filename, "SUCCESS", main.min, main.avg, main.max, main.sampleConfidenceInterval, main.shortVm, main.vm)
|
||||
|
||||
print(colored(Color.YELLOW, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(main.avg) + "ms +/- " +
|
||||
print(colored(Color.GREEN, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(main.avg) + "ms +/- " +
|
||||
'{:6.3f}'.format(main.sampleConfidenceInterval / main.avg * 100) + "% on " + main.shortVm)
|
||||
|
||||
plotLabels.append(main.name)
|
||||
|
@ -449,7 +480,7 @@ def analyzeResult(subdir, main, comparisons):
|
|||
'P(T<=t)': '---' if pValue < 0 else '{:.0f}%'.format(pValue * 100)
|
||||
})
|
||||
|
||||
print(colored(Color.YELLOW, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(compare.avg) + "ms +/- " +
|
||||
print(colored(Color.GREEN, 'SUCCESS') + ': {:<40}'.format(main.name) + ": " + '{:8.3f}'.format(compare.avg) + "ms +/- " +
|
||||
'{:6.3f}'.format(compare.sampleConfidenceInterval / compare.avg * 100) + "% on " + compare.shortVm +
|
||||
' ({:+7.3f}%, '.format(speedup * 100) + verdict + ")")
|
||||
|
||||
|
@ -727,6 +758,10 @@ def run(args, argsubcb):
|
|||
arguments = args
|
||||
argumentSubstituionCallback = argsubcb
|
||||
|
||||
if os.name == "nt" and arguments.callgrind:
|
||||
print(f"{colored(Color.RED, 'ERROR')}: --callgrind is not supported on Windows. Please consider using this option on another OS, or Linux using WSL.")
|
||||
sys.exit(1)
|
||||
|
||||
if arguments.report_metrics or arguments.print_influx_debugging:
|
||||
import influxbench
|
||||
influxReporter = influxbench.InfluxReporter(arguments)
|
||||
|
|
|
@ -57,4 +57,46 @@ function bench.runCode(f, description)
|
|||
print(report)
|
||||
end
|
||||
|
||||
-- This function acts a bit like a Unix "fork" operation
|
||||
-- When it is first called it clones `scriptInstance` and starts executing
|
||||
-- the cloned script parented to an Actor. When the cloned script calls "runScriptCodeUnderActor"
|
||||
-- it will run 'f' and print out the provided 'description'.
|
||||
--
|
||||
-- The function returns 'true' if it was invoked from a script running under an Actor
|
||||
-- and 'false' otherwise.
|
||||
--
|
||||
-- Example usage:
|
||||
-- local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||
-- function testFunc()
|
||||
-- ...
|
||||
-- end
|
||||
-- bench.runScriptCodeUnderActor(script, testFunc, "test function")
|
||||
function bench.runScriptCodeUnderActor(scriptInstance, f, description)
|
||||
if scriptInstance:GetActor() then
|
||||
-- If this function was called from an Actor script, just run the function provided using runCode
|
||||
bench.runCode(f, description)
|
||||
return true
|
||||
else
|
||||
-- If this function was not called from an Actor script, clone the script and place it under
|
||||
-- Actor instance.
|
||||
|
||||
-- Create an Actor to run the script under
|
||||
local actor = Instance.new("Actor")
|
||||
-- Clone this script (i.e. the bench_support module) and place it under the Actor where
|
||||
-- the script script would expect it to be when using 'require'.
|
||||
local benchModule = script:Clone()
|
||||
benchModule.Parent = actor
|
||||
-- Clone the scriptInstance
|
||||
local actorScript = scriptInstance:Clone()
|
||||
-- Enable the script since `scriptInstance` may be started by roblox-cli without ever being enabled.
|
||||
actorScript.Disabled = false
|
||||
actorScript.Parent = actor
|
||||
-- Add the actor to the workspace which will start executing the cloned script.
|
||||
-- Note: the script needs to be placed under a instance that implements 'IScriptFilter'
|
||||
-- (which workspace does) or it will never start executing.
|
||||
actor.Parent = workspace
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return bench
|
||||
|
|
|
@ -542,6 +542,20 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "MiscInstructions")
|
|||
SINGLE_COMPARE(bsf(eax, edx), 0x0f, 0xbc, 0xc2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "LabelLea")
|
||||
{
|
||||
CHECK(check(
|
||||
[](AssemblyBuilderX64& build) {
|
||||
Label fn;
|
||||
build.lea(rax, fn);
|
||||
build.ret();
|
||||
|
||||
build.setLabel(fn);
|
||||
build.ret();
|
||||
},
|
||||
{0x48, 0x8d, 0x05, 0x01, 0x00, 0x00, 0x00, 0xc3, 0xc3}));
|
||||
}
|
||||
|
||||
TEST_CASE("LogTest")
|
||||
{
|
||||
AssemblyBuilderX64 build(/* logText= */ true);
|
||||
|
@ -561,6 +575,7 @@ TEST_CASE("LogTest")
|
|||
Label start = build.setLabel();
|
||||
build.cmp(rsi, rdi);
|
||||
build.jcc(ConditionX64::Equal, start);
|
||||
build.lea(rcx, start);
|
||||
|
||||
build.jmp(qword[rdx]);
|
||||
build.vaddps(ymm9, ymm12, ymmword[rbp + 0xc]);
|
||||
|
@ -605,6 +620,7 @@ TEST_CASE("LogTest")
|
|||
.L1:
|
||||
cmp rsi,rdi
|
||||
je .L1
|
||||
lea rcx,.L1
|
||||
jmp qword ptr [rdx]
|
||||
vaddps ymm9,ymm12,ymmword ptr [rbp+0Ch]
|
||||
vaddpd ymm2,ymm7,qword ptr [.start-8]
|
||||
|
|
|
@ -432,11 +432,11 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass")
|
|||
REQUIRE(2 == root->body.size);
|
||||
|
||||
std::string_view expected1 =
|
||||
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}]})";
|
||||
R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]}},{"name":"method","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,21 - 4,11","generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}}],"indexer":null})";
|
||||
CHECK(toJson(root->body.data[0]) == expected1);
|
||||
|
||||
std::string_view expected2 =
|
||||
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]}}]})";
|
||||
R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]}}],"indexer":null})";
|
||||
CHECK(toJson(root->body.data[1]) == expected2);
|
||||
}
|
||||
|
||||
|
|
|
@ -49,6 +49,15 @@ static std::string compileFunction0Coverage(const char* source, int level)
|
|||
return bcb.dumpFunction(0);
|
||||
}
|
||||
|
||||
static std::string compileFunction0TypeTable(const char* source)
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code);
|
||||
Luau::compileOrThrow(bcb, source);
|
||||
|
||||
return bcb.dumpTypeInfo();
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("Compiler");
|
||||
|
||||
TEST_CASE("CompileToBytecode")
|
||||
|
@ -5796,8 +5805,6 @@ RETURN R3 1
|
|||
|
||||
TEST_CASE("InlineRecurseArguments")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileInlineDefer", true);
|
||||
|
||||
// the example looks silly but we preserve it verbatim as it was found by fuzzer for a previous version of the compiler
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local function foo(a, b)
|
||||
|
@ -7071,4 +7078,56 @@ L1: RETURN R3 1
|
|||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("EncodedTypeTable")
|
||||
{
|
||||
ScopedFastFlag sffs[] = {
|
||||
{"BytecodeVersion4", true},
|
||||
{"CompileFunctionType", true},
|
||||
};
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0TypeTable(R"(
|
||||
function myfunc(test: string, num: number)
|
||||
print(test)
|
||||
end
|
||||
|
||||
function myfunc2(test: number?)
|
||||
end
|
||||
|
||||
function myfunc3(test: string, n: number)
|
||||
end
|
||||
|
||||
function myfunc4(test: string | number, n: number)
|
||||
end
|
||||
|
||||
-- Promoted to function(any, any) since general unions are not supported.
|
||||
-- Functions with all `any` parameters will have omitted type info.
|
||||
function myfunc5(test: string | number, n: number | boolean)
|
||||
end
|
||||
|
||||
myfunc('test')
|
||||
)"),
|
||||
R"(
|
||||
0: function(string, number)
|
||||
1: function(number?)
|
||||
2: function(string, number)
|
||||
3: function(any, number)
|
||||
)");
|
||||
|
||||
CHECK_EQ("\n" + compileFunction0TypeTable(R"(
|
||||
local Str = {
|
||||
a = 1
|
||||
}
|
||||
|
||||
-- Implicit `self` parameter is automatically assumed to be table type.
|
||||
function Str:test(n: number)
|
||||
print(self.a, n)
|
||||
end
|
||||
|
||||
Str:test(234)
|
||||
)"),
|
||||
R"(
|
||||
0: function({ }, number)
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
|
|
@ -93,7 +93,7 @@ public:
|
|||
{
|
||||
for (uint32_t succIdx : successors(build.function.cfg, k))
|
||||
{
|
||||
if (succIdx == i)
|
||||
if (succIdx == uint32_t(i))
|
||||
build.function.cfg.predecessors.push_back(k);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,8 @@ TEST_SUITE_BEGIN("AllocatorTests");
|
|||
TEST_CASE("allocator_can_be_moved")
|
||||
{
|
||||
Counter* c = nullptr;
|
||||
auto inner = [&]() {
|
||||
auto inner = [&]()
|
||||
{
|
||||
Luau::Allocator allocator;
|
||||
c = allocator.alloc<Counter>();
|
||||
Luau::Allocator moved{std::move(allocator)};
|
||||
|
@ -921,7 +922,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid")
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace")
|
||||
{
|
||||
auto columnOfEndBraceError = [this](const char* code) {
|
||||
auto columnOfEndBraceError = [this](const char* code)
|
||||
{
|
||||
try
|
||||
{
|
||||
parse(code);
|
||||
|
@ -1882,6 +1884,44 @@ TEST_CASE_FIXTURE(Fixture, "class_method_properties")
|
|||
CHECK_EQ(2, klass2->props.size);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_indexer")
|
||||
{
|
||||
ScopedFastFlag LuauParseDeclareClassIndexer("LuauParseDeclareClassIndexer", true);
|
||||
|
||||
AstStatBlock* stat = parseEx(R"(
|
||||
declare class Foo
|
||||
prop: boolean
|
||||
[string]: number
|
||||
end
|
||||
)")
|
||||
.root;
|
||||
|
||||
REQUIRE_EQ(stat->body.size, 1);
|
||||
|
||||
AstStatDeclareClass* declaredClass = stat->body.data[0]->as<AstStatDeclareClass>();
|
||||
REQUIRE(declaredClass);
|
||||
REQUIRE(declaredClass->indexer);
|
||||
REQUIRE(declaredClass->indexer->indexType->is<AstTypeReference>());
|
||||
CHECK(declaredClass->indexer->indexType->as<AstTypeReference>()->name == "string");
|
||||
REQUIRE(declaredClass->indexer->resultType->is<AstTypeReference>());
|
||||
CHECK(declaredClass->indexer->resultType->as<AstTypeReference>()->name == "number");
|
||||
|
||||
const ParseResult p1 = matchParseError(R"(
|
||||
declare class Foo
|
||||
[string]: number
|
||||
-- can only have one indexer
|
||||
[number]: number
|
||||
end
|
||||
)",
|
||||
"Cannot have more than one class indexer");
|
||||
|
||||
REQUIRE_EQ(1, p1.root->body.size);
|
||||
|
||||
AstStatDeclareClass* klass = p1.root->body.data[0]->as<AstStatDeclareClass>();
|
||||
REQUIRE(klass != nullptr);
|
||||
CHECK(klass->indexer);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_variadics")
|
||||
{
|
||||
//clang-format off
|
||||
|
@ -2347,7 +2387,8 @@ public:
|
|||
|
||||
TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
|
||||
{
|
||||
auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code) {
|
||||
auto checkAstEquivalence = [this](const char* codeWithErrors, const char* code)
|
||||
{
|
||||
try
|
||||
{
|
||||
parse(codeWithErrors);
|
||||
|
@ -2367,7 +2408,8 @@ TEST_CASE_FIXTURE(Fixture, "recovery_of_parenthesized_expressions")
|
|||
CHECK_EQ(counterWithErrors.count, counter.count);
|
||||
};
|
||||
|
||||
auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount) {
|
||||
auto checkRecovery = [this, checkAstEquivalence](const char* codeWithErrors, const char* code, unsigned expectedErrorCount)
|
||||
{
|
||||
try
|
||||
{
|
||||
parse(codeWithErrors);
|
||||
|
|
|
@ -341,7 +341,7 @@ TEST_CASE_FIXTURE(SimplifyFixture, "tables")
|
|||
CHECK(t2 == intersect(t2, t1));
|
||||
|
||||
TypeId t3 = mkTable({});
|
||||
|
||||
// {tag : string} intersect {{}}
|
||||
CHECK(t1 == intersect(t1, t3));
|
||||
CHECK(t1 == intersect(t3, t1));
|
||||
}
|
||||
|
|
|
@ -394,6 +394,36 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_string_props")
|
|||
CHECK_EQ(toString(requireType("y")), "string");
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definition_indexer")
|
||||
{
|
||||
ScopedFastFlag LuauParseDeclareClassIndexer("LuauParseDeclareClassIndexer", true);
|
||||
ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true);
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class Foo
|
||||
[number]: string
|
||||
end
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x: Foo
|
||||
local y = x[1]
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
const ClassType* ctv = get<ClassType>(requireType("x"));
|
||||
REQUIRE(ctv != nullptr);
|
||||
|
||||
REQUIRE(bool(ctv->indexer));
|
||||
|
||||
CHECK_EQ(*ctv->indexer->indexType, *builtinTypes->numberType);
|
||||
CHECK_EQ(*ctv->indexer->indexResultType, *builtinTypes->stringType);
|
||||
|
||||
CHECK_EQ(toString(requireType("y")), "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes")
|
||||
{
|
||||
unfreeze(frontend.globals.globalTypes);
|
||||
|
|
Loading…
Reference in a new issue