luau/Analysis/include/Luau/TxnLog.h

323 lines
11 KiB
C
Raw Normal View History

// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/Type.h"
#include "Luau/TypePack.h"
#include <memory>
#include <unordered_map>
namespace Luau
{
2022-03-05 00:36:33 +08:00
using TypeOrPackId = const void*;
// Pending state for a Type. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingType
{
// The pending Type state.
Type pending;
Sync to upstream/release/576 (#928) * `ClassType` can now have an indexer defined on it. This allows custom types to be used in `t[x]` expressions. * Fixed search for closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function * Fixed how unification is performed for two optional types `a? <: b?`, previously it might have unified either 'a' or 'b' with 'nil'. Note that this fix is not enabled by default yet (see the list in `ExperimentalFlags.h`) In the new type solver, a concept of 'Type Families' has been introduced. Type families can be thought of as type aliases with custom type inference/reduction logic included with them. For example, we can have an `Add<T, U>` type family that will resolve the type that is the result of adding two values together. This will help type inference to figure out what 'T' and 'U' might be when explicit type annotations are not provided. In this update we don't define any type families, but they will be added in the near future. It is also possible for Luau embedders to define their own type families in the global/environment scope. Other changes include: * Fixed scope used to find out which generic types should be included in the function generic type list * Fixed a crash after cyclic bound types were created during unification And in native code generation (jit): * Use of arm64 target on M1 now requires macOS 13 * Entry into native code has been optimized. This is especially important for coroutine call/pcall performance as they involve going through a C call frame * LOP_LOADK(X) translation into IR has been improved to enable type tag/constant propagation * arm64 can use integer immediate values to synthesize floating-point values * x64 assembler removes duplicate 64bit numbers from the data section to save space * Linux `perf` can now be used to profile native Luau code (when running with --codegen-perf CLI argument)
2023-05-13 01:50:47 +08:00
// On very rare occasions, we need to delete an entry from the TxnLog.
// DenseHashMap does not afford that so we note its deadness here.
bool dead = false;
explicit PendingType(Type state)
: pending(std::move(state))
{
}
};
2022-02-05 00:45:57 +08:00
std::string toString(PendingType* pending);
std::string dump(PendingType* pending);
// Pending state for a TypePackVar. Generated by a TxnLog and committed via
// TxnLog::commit.
struct PendingTypePack
{
// The pending TypePackVar state.
TypePackVar pending;
explicit PendingTypePack(TypePackVar state)
: pending(std::move(state))
{
}
};
2022-02-05 00:45:57 +08:00
std::string toString(PendingTypePack* pending);
std::string dump(PendingTypePack* pending);
template<typename T>
T* getMutable(PendingType* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
template<typename T>
T* getMutable(PendingTypePack* pending)
{
// We use getMutable here because this state is intended to be mutated freely.
return getMutable<T>(&pending->pending);
}
// Log of what TypeIds we are rebinding, to be committed later.
struct TxnLog
{
Sync to upstream/release/576 (#928) * `ClassType` can now have an indexer defined on it. This allows custom types to be used in `t[x]` expressions. * Fixed search for closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function * Fixed how unification is performed for two optional types `a? <: b?`, previously it might have unified either 'a' or 'b' with 'nil'. Note that this fix is not enabled by default yet (see the list in `ExperimentalFlags.h`) In the new type solver, a concept of 'Type Families' has been introduced. Type families can be thought of as type aliases with custom type inference/reduction logic included with them. For example, we can have an `Add<T, U>` type family that will resolve the type that is the result of adding two values together. This will help type inference to figure out what 'T' and 'U' might be when explicit type annotations are not provided. In this update we don't define any type families, but they will be added in the near future. It is also possible for Luau embedders to define their own type families in the global/environment scope. Other changes include: * Fixed scope used to find out which generic types should be included in the function generic type list * Fixed a crash after cyclic bound types were created during unification And in native code generation (jit): * Use of arm64 target on M1 now requires macOS 13 * Entry into native code has been optimized. This is especially important for coroutine call/pcall performance as they involve going through a C call frame * LOP_LOADK(X) translation into IR has been improved to enable type tag/constant propagation * arm64 can use integer immediate values to synthesize floating-point values * x64 assembler removes duplicate 64bit numbers from the data section to save space * Linux `perf` can now be used to profile native Luau code (when running with --codegen-perf CLI argument)
2023-05-13 01:50:47 +08:00
explicit TxnLog(bool useScopes = false)
2022-04-15 07:57:43 +08:00
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, ownedSeen()
Sync to upstream/release/576 (#928) * `ClassType` can now have an indexer defined on it. This allows custom types to be used in `t[x]` expressions. * Fixed search for closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function * Fixed how unification is performed for two optional types `a? <: b?`, previously it might have unified either 'a' or 'b' with 'nil'. Note that this fix is not enabled by default yet (see the list in `ExperimentalFlags.h`) In the new type solver, a concept of 'Type Families' has been introduced. Type families can be thought of as type aliases with custom type inference/reduction logic included with them. For example, we can have an `Add<T, U>` type family that will resolve the type that is the result of adding two values together. This will help type inference to figure out what 'T' and 'U' might be when explicit type annotations are not provided. In this update we don't define any type families, but they will be added in the near future. It is also possible for Luau embedders to define their own type families in the global/environment scope. Other changes include: * Fixed scope used to find out which generic types should be included in the function generic type list * Fixed a crash after cyclic bound types were created during unification And in native code generation (jit): * Use of arm64 target on M1 now requires macOS 13 * Entry into native code has been optimized. This is especially important for coroutine call/pcall performance as they involve going through a C call frame * LOP_LOADK(X) translation into IR has been improved to enable type tag/constant propagation * arm64 can use integer immediate values to synthesize floating-point values * x64 assembler removes duplicate 64bit numbers from the data section to save space * Linux `perf` can now be used to profile native Luau code (when running with --codegen-perf CLI argument)
2023-05-13 01:50:47 +08:00
, useScopes(useScopes)
, sharedSeen(&ownedSeen)
{
}
explicit TxnLog(TxnLog* parent)
2022-04-15 07:57:43 +08:00
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, parent(parent)
{
if (parent)
{
sharedSeen = parent->sharedSeen;
}
else
{
sharedSeen = &ownedSeen;
}
}
2022-03-05 00:36:33 +08:00
explicit TxnLog(std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen)
2022-04-15 07:57:43 +08:00
: typeVarChanges(nullptr)
, typePackChanges(nullptr)
, sharedSeen(sharedSeen)
{
}
TxnLog(const TxnLog&) = delete;
TxnLog& operator=(const TxnLog&) = delete;
TxnLog(TxnLog&&) = default;
TxnLog& operator=(TxnLog&&) = default;
// Gets an empty TxnLog pointer. This is useful for constructs that
// take a TxnLog, like TypePackIterator - use the empty log if you
// don't have a TxnLog to give it.
static const TxnLog* empty();
// Joins another TxnLog onto this one. You should use std::move to avoid
// copying the rhs TxnLog.
//
// If both logs talk about the same type, pack, or table, the rhs takes
// priority.
void concat(TxnLog rhs);
void concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena);
void concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena);
// Commits the TxnLog, rebinding all type pointers to their pending states.
// Clears the TxnLog afterwards.
void commit();
// Clears the TxnLog without committing any pending changes.
void clear();
// Computes an inverse of this TxnLog at the current time.
// This method should be called before commit is called in order to give an
// accurate result. Committing the inverse of a TxnLog will undo the changes
// made by commit, assuming the inverse log is accurate.
TxnLog inverse();
bool haveSeen(TypeId lhs, TypeId rhs) const;
void pushSeen(TypeId lhs, TypeId rhs);
void popSeen(TypeId lhs, TypeId rhs);
2022-03-05 00:36:33 +08:00
bool haveSeen(TypePackId lhs, TypePackId rhs) const;
void pushSeen(TypePackId lhs, TypePackId rhs);
void popSeen(TypePackId lhs, TypePackId rhs);
// Queues a type for modification. The original type will not change until commit
// is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* queue(TypeId ty);
// Queues a type pack for modification. The original type pack will not change
// until commit is called. Use pending to get the pending state.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* queue(TypePackId tp);
// Returns the pending state of a type, or nullptr if there isn't any. It is important
// to note that this pending state is not transitive: the pending state may reference
// non-pending types freely, so you may need to call pending multiple times to view the
// entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* pending(TypeId ty) const;
// Returns the pending state of a type pack, or nullptr if there isn't any. It is
// important to note that this pending state is not transitive: the pending state may
// reference non-pending types freely, so you may need to call pending multiple times
// to view the entire pending state of a type graph.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* pending(TypePackId tp) const;
// Queues a replacement of a type with another type.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* replace(TypeId ty, Type replacement);
// Queues a replacement of a type pack with another type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* replace(TypePackId tp, TypePackVar replacement);
// Queues a replacement of a table type with another table type that is bound
// to a specific value.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* bindTable(TypeId ty, std::optional<TypeId> newBoundTo);
// Queues a replacement of a type with a level with a duplicate of that type
// with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeLevel(TypeId ty, TypeLevel newLevel);
// Queues a replacement of a type pack with a level with a duplicate of that
// type pack with a new type level.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeLevel(TypePackId tp, TypeLevel newLevel);
// Queues the replacement of a type's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeScope(TypeId ty, NotNull<Scope> scope);
// Queues the replacement of a type pack's scope with the provided scope.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingTypePack* changeScope(TypePackId tp, NotNull<Scope> scope);
// Queues a replacement of a table type with another table type with a new
// indexer.
//
// The pointer returned lives until `commit` or `clear` is called.
PendingType* changeIndexer(TypeId ty, std::optional<TableIndexer> indexer);
// Returns the type level of the pending state of the type, or the level of that
// type, if no pending state exists. If the type doesn't have a notion of a level,
// returns nullopt. If the pending state doesn't have a notion of a level, but the
// original state does, returns nullopt.
std::optional<TypeLevel> getLevel(TypeId ty) const;
// Follows a type, accounting for pending type states. The returned type may have
// pending state; you should use `pending` or `get` to find out.
2022-02-05 00:45:57 +08:00
TypeId follow(TypeId ty) const;
// Follows a type pack, accounting for pending type states. The returned type pack
// may have pending state; you should use `pending` or `get` to find out.
TypePackId follow(TypePackId tp) const;
// Replaces a given type's state with a new variant. Returns the new pending state
// of that type.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingType* replace(TypeId ty, T replacement)
{
return replace(ty, Type(replacement));
}
// Replaces a given type pack's state with a new variant. Returns the new
// pending state of that type pack.
//
// The pointer returned lives until `commit` or `clear` is called.
template<typename T>
PendingTypePack* replace(TypePackId tp, T replacement)
{
return replace(tp, TypePackVar(replacement));
}
// Returns T if a given type or type pack is this variant, respecting the
// log's pending state.
//
// Do not retain this pointer; it has the potential to be invalidated when
// commit or clear is called.
template<typename T, typename TID>
T* getMutable(TID ty) const
{
auto* pendingTy = pending(ty);
if (pendingTy)
return Luau::getMutable<T>(pendingTy);
return Luau::getMutable<T>(ty);
}
2022-04-15 07:57:43 +08:00
template<typename T, typename TID>
const T* get(TID ty) const
{
return this->getMutable<T>(ty);
}
// Returns whether a given type or type pack is a given state, respecting the
// log's pending state.
//
// This method will not assert if called on a BoundType or BoundTypePack.
template<typename T, typename TID>
bool is(TID ty) const
{
// We do not use getMutable here because this method can be called on
// BoundTypes, which triggers an assertion.
auto* pendingTy = pending(ty);
if (pendingTy)
return Luau::get_if<T>(&pendingTy->pending.ty) != nullptr;
return Luau::get_if<T>(&ty->ty) != nullptr;
}
std::pair<std::vector<TypeId>, std::vector<TypePackId>> getChanges() const;
private:
// unique_ptr is used to give us stable pointers across insertions into the
// map. Otherwise, it would be really easy to accidentally invalidate the
// pointers returned from queue/pending.
2022-04-15 07:57:43 +08:00
DenseHashMap<TypeId, std::unique_ptr<PendingType>> typeVarChanges;
DenseHashMap<TypePackId, std::unique_ptr<PendingTypePack>> typePackChanges;
TxnLog* parent = nullptr;
// Owned version of sharedSeen. This should not be accessed directly in
// TxnLogs; use sharedSeen instead. This field exists because in the tree
// of TxnLogs, the root must own its seen set. In all descendant TxnLogs,
// this is an empty vector.
2022-03-05 00:36:33 +08:00
std::vector<std::pair<TypeOrPackId, TypeOrPackId>> ownedSeen;
bool haveSeen(TypeOrPackId lhs, TypeOrPackId rhs) const;
void pushSeen(TypeOrPackId lhs, TypeOrPackId rhs);
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
public:
Sync to upstream/release/576 (#928) * `ClassType` can now have an indexer defined on it. This allows custom types to be used in `t[x]` expressions. * Fixed search for closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function * Fixed how unification is performed for two optional types `a? <: b?`, previously it might have unified either 'a' or 'b' with 'nil'. Note that this fix is not enabled by default yet (see the list in `ExperimentalFlags.h`) In the new type solver, a concept of 'Type Families' has been introduced. Type families can be thought of as type aliases with custom type inference/reduction logic included with them. For example, we can have an `Add<T, U>` type family that will resolve the type that is the result of adding two values together. This will help type inference to figure out what 'T' and 'U' might be when explicit type annotations are not provided. In this update we don't define any type families, but they will be added in the near future. It is also possible for Luau embedders to define their own type families in the global/environment scope. Other changes include: * Fixed scope used to find out which generic types should be included in the function generic type list * Fixed a crash after cyclic bound types were created during unification And in native code generation (jit): * Use of arm64 target on M1 now requires macOS 13 * Entry into native code has been optimized. This is especially important for coroutine call/pcall performance as they involve going through a C call frame * LOP_LOADK(X) translation into IR has been improved to enable type tag/constant propagation * arm64 can use integer immediate values to synthesize floating-point values * x64 assembler removes duplicate 64bit numbers from the data section to save space * Linux `perf` can now be used to profile native Luau code (when running with --codegen-perf CLI argument)
2023-05-13 01:50:47 +08:00
// There is one spot in the code where TxnLog has to reconcile collisions
// between parallel logs. In that codepath, we have to work out which of two
// FreeTypes subsumes the other. If useScopes is false, the TypeLevel is
// used. Else we use the embedded Scope*.
bool useScopes = false;
Sync to upstream/release/577 (#934) Lots of things going on this week: * Fix a crash that could occur in the presence of a cyclic union. We shouldn't be creating cyclic unions, but we shouldn't be crashing when they arise either. * Minor cleanup of `luau_precall` * Internal change to make L->top handling slightly more uniform * Optimize SETGLOBAL & GETGLOBAL fallback C functions. * https://github.com/Roblox/luau/pull/929 * The syntax to the `luau-reduce` commandline tool has changed. It now accepts a script, a command to execute, and an error to search for. It no longer automatically passes the script to the command which makes it a lot more flexible. Also be warned that it edits the script it is passed **in place**. Do not point it at something that is not in source control! New solver * Switch to a greedier but more fallible algorithm for simplifying union and intersection types that are created as part of refinement calculation. This has much better and more predictable performance. * Fix a constraint cycle in recursive function calls. * Much improved inference of binary addition. Functions like `function add(x, y) return x + y end` can now be inferred without annotations. We also accurately typecheck calls to functions like this. * Many small bugfixes surrounding things like table indexers * Add support for indexers on class types. This was previously added to the old solver; we now add it to the new one for feature parity. JIT * https://github.com/Roblox/luau/pull/931 * Fuse key.value and key.tt loads for CEHCK_SLOT_MATCH in A64 * Implement remaining aliases of BFM for A64 * Implement new callinfo flag for A64 * Add instruction simplification for int->num->int conversion chains * Don't even load execdata for X64 calls * Treat opcode fallbacks the same as manually written fallbacks --------- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
2023-05-20 03:37:30 +08:00
// It is sometimes the case under DCR that we speculatively rebind
// GenericTypes to other types as though they were free. We mark logs that
// contain these kinds of substitutions as radioactive so that we know that
// we must never commit one.
bool radioactive = false;
// Used to avoid infinite recursion when types are cyclic.
// Shared with all the descendent TxnLogs.
2022-03-05 00:36:33 +08:00
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
};
} // namespace Luau