Skip to content

Commit

Permalink
Sync to upstream/release/580 (#951)
Browse files Browse the repository at this point in the history
* Added luau-compile executable target to build/test compilation without
having full REPL included.

In our new typechecker:
* Fixed the order in which constraints are checked to get more
deterministic errors in different environments
* Fixed `isNumber`/`isString` checks to fix false positive errors in
binary comparisons
* CannotCallNonFunction error is reported when calling an intersection
type of non-functions
 
In our native code generation (jit):
* Outlined X64 return instruction code to improve code size
* Improved performance of return instruction on A64
* Added construction of a dominator tree for future optimizations
  • Loading branch information
vegorov-rbx authored Jun 9, 2023
1 parent febebde commit 3ecd3a8
Show file tree
Hide file tree
Showing 42 changed files with 1,488 additions and 278 deletions.
134 changes: 134 additions & 0 deletions Analysis/include/Luau/InsertionOrderedMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once

#include "Luau/Common.h"

#include <unordered_map>
#include <vector>
#include <type_traits>
#include <iterator>

namespace Luau
{

template<typename K, typename V>
struct InsertionOrderedMap
{
static_assert(std::is_trivially_copyable_v<K>, "key must be trivially copyable");

private:
using vec = std::vector<std::pair<K, V>>;

public:
using iterator = typename vec::iterator;
using const_iterator = typename vec::const_iterator;

void insert(K k, V v)
{
if (indices.count(k) != 0)
return;

pairs.push_back(std::make_pair(k, std::move(v)));
indices[k] = pairs.size() - 1;
}

void clear()
{
pairs.clear();
indices.clear();
}

size_t size() const
{
LUAU_ASSERT(pairs.size() == indices.size());
return pairs.size();
}

bool contains(const K& k) const
{
return indices.count(k) > 0;
}

const V* get(const K& k) const
{
auto it = indices.find(k);
if (it == indices.end())
return nullptr;
else
return &pairs.at(it->second).second;
}

V* get(const K& k)
{
auto it = indices.find(k);
if (it == indices.end())
return nullptr;
else
return &pairs.at(it->second).second;
}

const_iterator begin() const
{
return pairs.begin();
}

const_iterator end() const
{
return pairs.end();
}

iterator begin()
{
return pairs.begin();
}

iterator end()
{
return pairs.end();
}

const_iterator find(K k) const
{
auto indicesIt = indices.find(k);
if (indicesIt == indices.end())
return end();
else
return begin() + indicesIt->second;
}

iterator find(K k)
{
auto indicesIt = indices.find(k);
if (indicesIt == indices.end())
return end();
else
return begin() + indicesIt->second;
}

void erase(iterator it)
{
if (it == pairs.end())
return;

K k = it->first;
auto indexIt = indices.find(k);
if (indexIt == indices.end())
return;

size_t removed = indexIt->second;
indices.erase(indexIt);
pairs.erase(it);

for (auto& [_, index] : indices)
{
if (index > removed)
--index;
}
}

private:
vec pairs;
std::unordered_map<K, size_t> indices;
};

}
25 changes: 19 additions & 6 deletions Analysis/include/Luau/Normalize.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class TypeIds

bool operator==(const TypeIds& there) const;
size_t getHash() const;
bool isNever() const;
};

} // namespace Luau
Expand Down Expand Up @@ -269,12 +270,24 @@ struct NormalizedType
NormalizedType& operator=(NormalizedType&&) = default;

// IsType functions

/// Returns true if the type is a subtype of function. This includes any and unknown.
bool isFunction() const;

/// Returns true if the type is a subtype of number. This includes any and unknown.
bool isNumber() const;
/// Returns true if the type is exactly a number. Behaves like Type::isNumber()
bool isExactlyNumber() const;

/// Returns true if the type is a subtype of string(it could be a singleton). Behaves like Type::isString()
bool isSubtypeOfString() const;

// Helpers that improve readability of the above (they just say if the component is present)
bool hasTops() const;
bool hasBooleans() const;
bool hasClasses() const;
bool hasErrors() const;
bool hasNils() const;
bool hasNumbers() const;
bool hasStrings() const;
bool hasThreads() const;
bool hasTables() const;
bool hasFunctions() const;
bool hasTyvars() const;
};


Expand Down
22 changes: 14 additions & 8 deletions Analysis/src/ConstraintGraphBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "Luau/TypeFamily.h"
#include "Luau/Simplify.h"
#include "Luau/VisitType.h"
#include "Luau/InsertionOrderedMap.h"

#include <algorithm>

Expand Down Expand Up @@ -196,7 +197,7 @@ struct RefinementPartition
bool shouldAppendNilType = false;
};

using RefinementContext = std::unordered_map<DefId, RefinementPartition>;
using RefinementContext = InsertionOrderedMap<DefId, RefinementPartition>;

static void unionRefinements(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const RefinementContext& lhs, const RefinementContext& rhs,
RefinementContext& dest, std::vector<ConstraintV>* constraints)
Expand Down Expand Up @@ -229,8 +230,9 @@ static void unionRefinements(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAre
TypeId rightDiscriminantTy =
rhsIt->second.discriminantTypes.size() == 1 ? rhsIt->second.discriminantTypes[0] : intersect(rhsIt->second.discriminantTypes);

dest[def].discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result);
dest[def].shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType;
dest.insert(def, {});
dest.get(def)->discriminantTypes.push_back(simplifyUnion(builtinTypes, arena, leftDiscriminantTy, rightDiscriminantTy).result);
dest.get(def)->shouldAppendNilType |= partition.shouldAppendNilType || rhsIt->second.shouldAppendNilType;
}
}

Expand Down Expand Up @@ -285,11 +287,12 @@ static void computeRefinement(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAr
}

RefinementContext uncommittedRefis;
uncommittedRefis[proposition->breadcrumb->def].discriminantTypes.push_back(discriminantTy);
uncommittedRefis.insert(proposition->breadcrumb->def, {});
uncommittedRefis.get(proposition->breadcrumb->def)->discriminantTypes.push_back(discriminantTy);

// When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`.
if ((sense || !eq) && getMetadata<SubscriptMetadata>(proposition->breadcrumb))
uncommittedRefis[proposition->breadcrumb->def].shouldAppendNilType = true;
uncommittedRefis.get(proposition->breadcrumb->def)->shouldAppendNilType = true;

for (NullableBreadcrumbId current = proposition->breadcrumb; current && current->previous; current = current->previous)
{
Expand All @@ -302,17 +305,20 @@ static void computeRefinement(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeAr
{
TableType::Props props{{field->prop, Property{discriminantTy}}};
discriminantTy = arena->addType(TableType{std::move(props), std::nullopt, TypeLevel{}, scope.get(), TableState::Sealed});
uncommittedRefis[current->previous->def].discriminantTypes.push_back(discriminantTy);
uncommittedRefis.insert(current->previous->def, {});
uncommittedRefis.get(current->previous->def)->discriminantTypes.push_back(discriminantTy);
}
}

// And now it's time to commit it.
for (auto& [def, partition] : uncommittedRefis)
{
(*refis).insert(def, {});

for (TypeId discriminantTy : partition.discriminantTypes)
(*refis)[def].discriminantTypes.push_back(discriminantTy);
(*refis).get(def)->discriminantTypes.push_back(discriminantTy);

(*refis)[def].shouldAppendNilType |= partition.shouldAppendNilType;
(*refis).get(def)->shouldAppendNilType |= partition.shouldAppendNilType;
}
}
}
Expand Down
36 changes: 30 additions & 6 deletions Analysis/src/ConstraintSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,8 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
const NormalizedType* normLeftTy = normalizer->normalize(leftType);
if (hasTypeInIntersection<FreeType>(leftType) && force)
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->numberType);
if (normLeftTy && normLeftTy->isNumber())
// We want to check if the left type has tops because `any` is a valid type for the lhs
if (normLeftTy && (normLeftTy->isExactlyNumber() || get<AnyType>(normLeftTy->tops)))
{
unify(leftType, rightType, constraint->scope);
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
Expand All @@ -805,9 +806,11 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
// For concatenation, if the LHS is a string, the RHS must be a string as
// well. The result will also be a string.
case AstExprBinary::Op::Concat:
{
if (hasTypeInIntersection<FreeType>(leftType) && force)
asMutable(leftType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : builtinTypes->stringType);
if (isString(leftType))
const NormalizedType* leftNormTy = normalizer->normalize(leftType);
if (leftNormTy && leftNormTy->isSubtypeOfString())
{
unify(leftType, rightType, constraint->scope);
asMutable(resultType)->ty.emplace<BoundType>(anyPresent ? builtinTypes->anyType : leftType);
Expand All @@ -823,21 +826,42 @@ bool ConstraintSolver::tryDispatch(const BinaryConstraint& c, NotNull<const Cons
}

break;
}
// Inexact comparisons require that the types be both numbers or both
// strings, and evaluate to a boolean.
case AstExprBinary::Op::CompareGe:
case AstExprBinary::Op::CompareGt:
case AstExprBinary::Op::CompareLe:
case AstExprBinary::Op::CompareLt:
if ((isNumber(leftType) && isNumber(rightType)) || (isString(leftType) && isString(rightType)) || get<NeverType>(leftType) ||
get<NeverType>(rightType))
{
const NormalizedType* lt = normalizer->normalize(leftType);
const NormalizedType* rt = normalizer->normalize(rightType);
// If the lhs is any, comparisons should be valid.
if (lt && rt && (lt->isExactlyNumber() || get<AnyType>(lt->tops)) && rt->isExactlyNumber())
{
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
unblock(resultType);
return true;
}

if (lt && rt && (lt->isSubtypeOfString() || get<AnyType>(lt->tops)) && rt->isSubtypeOfString())
{
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
unblock(resultType);
return true;
}


if (get<NeverType>(leftType) || get<NeverType>(rightType))
{
asMutable(resultType)->ty.emplace<BoundType>(builtinTypes->booleanType);
unblock(resultType);
return true;
}

break;
}

// == and ~= always evaluate to a boolean, and impose no other constraints
// on their parameters.
case AstExprBinary::Op::CompareEq:
Expand Down Expand Up @@ -1776,7 +1800,7 @@ struct FindRefineConstraintBlockers : TypeOnceVisitor
}
};

}
} // namespace

static bool isNegatedAny(TypeId ty)
{
Expand Down Expand Up @@ -2319,7 +2343,7 @@ static TypePackId getErrorType(NotNull<BuiltinTypes> builtinTypes, TypePackId)
return builtinTypes->errorRecoveryTypePack();
}

template <typename TID>
template<typename TID>
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
{
Unifier u{normalizer, constraint->scope, constraint->location, Covariant};
Expand Down
4 changes: 1 addition & 3 deletions Analysis/src/Frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ LUAU_FASTINTVARIABLE(LuauAutocompleteCheckTimeoutMs, 100)
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerUseCorrectScope, false)

namespace Luau
{
Expand Down Expand Up @@ -1196,8 +1195,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
}
else
{
TypeChecker typeChecker(FFlag::LuauTypeCheckerUseCorrectScope ? (forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope)
: globals.globalScope,
TypeChecker typeChecker(forAutocomplete ? globalsForAutocomplete.globalScope : globals.globalScope,
forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver, builtinTypes, &iceHandler);

if (prepareModuleScope)
Expand Down
Loading

0 comments on commit 3ecd3a8

Please sign in to comment.