Skip to content

[CSSolver] Implementation of disjunction choice favoring algorithm #63585

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 60 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
757ca24
[ConstraintSystem] Remove `shrink`
xedin Feb 3, 2023
4432c51
[CSGen] Remove ConstraintOptimizer and all favoring logic
xedin Feb 5, 2023
b5f08a4
[ConstraintSystem] Add skeleton of constraint optimizer
xedin Feb 5, 2023
672ae3d
[CSOptimizer] Initial implementation of disjunction choice favoring a…
xedin Feb 10, 2023
c2f7451
[CSOptimizer] Favor SIMD related arithmetic operator choices if argum…
xedin Feb 10, 2023
a094c3e
[CSOptimizer] Keep track of mismatches while evaluating candidates
xedin Feb 10, 2023
e404ed7
[CSStep] Don't favor choices until the disjunction is picked
xedin Feb 10, 2023
7c1c46d
[CSOptimizer] Make sure that all parameters without arguments are def…
xedin Feb 11, 2023
bc5f70a
[CSOptimizer] Allow generic operator overloads without associated typ…
xedin Feb 13, 2023
14e2a16
[CSOptimizer] Don't attempt to optimize calls with code completion to…
xedin Feb 15, 2023
cb1cb20
[CSOptimizer] Use `matchCallArguments` to establish argument-to-param…
xedin Feb 15, 2023
11b897b
[CSOptimizer] Relax candidate type requirements from equality to set …
xedin Feb 17, 2023
2c44e37
[CSStep] Remove disjunction pruning logic from DisjunctionStep
xedin Feb 17, 2023
957a5f4
[CSOptimizer] Treat all type parameters equally
xedin Feb 21, 2023
da65333
[CSOptimizer] NFC: Adjust conformance check to use `ConstraintSystem:…
xedin Oct 10, 2023
0fc6806
[CSOptimizer] NFC: Switch to llvm::Optional
xedin Oct 10, 2023
2869dff
[CSOptimizer] Increase score when type matches opaque type
xedin Oct 11, 2023
c429f5b
[CSOptimizer] NFC: Switch from llvm::Optional to std::optional post-r…
xedin Aug 5, 2024
1760bd1
[CSOptimizer] Remove an outdated optimization to compare resolved arg…
xedin Aug 7, 2024
d69b6a0
[CSOptimizer] Prefer homogeneous arithmetic operator overloads when a…
xedin Aug 7, 2024
670127a
[Tests] NFC: Add a test-case for rdar://133340307 which is now fast
xedin Aug 24, 2024
527de22
[CSOptimizer] Emulate old behavior related to favoring of unary calls…
xedin Sep 5, 2024
cf05405
[CSOptimizer] Let `determineBestChoicesInContext` return the best dis…
xedin Sep 7, 2024
3819ddf
[CSOptimizer] Record best scores for each disjunction and use them in…
xedin Sep 7, 2024
deca9b6
[CSOptimizer] attempt to rank only standard/simd operators and fully …
xedin Sep 10, 2024
8a918e2
[CSOptimizer] Don't optimize (implicit) calls with code completion ar…
xedin Sep 11, 2024
6698136
[Tests] NFC: Adjust a couple of improved tests
xedin Sep 11, 2024
23589ad
[CSOptimizer] Average score should reflect number of defaulted parame…
xedin Sep 11, 2024
d0ff6c8
[Tests] NFC: Add more test-cases that were previously solved due to o…
xedin Sep 11, 2024
3996b25
[CSOptimizer] Rank results of operators regardless of whether anythin…
xedin Sep 16, 2024
8818d39
[CSOptimizer] Rank disjunctions based on score only if both sides are…
xedin Sep 17, 2024
6fb6d1c
[CSOptimizer] Enable ranking of `Int*`, `Float{80}` and `Double` init…
xedin Sep 17, 2024
59109c2
[CSOptimizer] Score all of the overload choices matching on literals …
xedin Sep 18, 2024
f2a6677
[CSOptimizer] Infer argument candidates from calls to `Double` and CG…
xedin Sep 20, 2024
8d5cb11
[ConstraintSystem] Narrowly disable `tryOptimizeGenericDisjunction` w…
xedin Sep 22, 2024
802f5cd
[CSOptimizer] Desugar types before checking for equality
xedin Sep 23, 2024
a3a3ec4
[CSOptimizer] Restore old hack behavior which used to favor overloads…
xedin Sep 24, 2024
e30587b
[CSOptimizer] A more comprehensive generic overload checking when can…
xedin Sep 24, 2024
6caf1cc
[CSOptimizer] Fix Double<->CGFloat implicit conversion support when a…
xedin Oct 7, 2024
9b62c84
[CSOptimizer] Adjust `scoreCandidateMatch` to indicate when match can…
xedin Oct 8, 2024
2fdd4b6
[CSOptimizer] Allow literal arguments to match parameters that confor…
xedin Oct 8, 2024
8bd2884
[CSGen] NFC: Remove obsolete `ConstraintSystem::{get, set}FavoredType`
xedin Oct 16, 2024
28396a6
[Tests] NFC: Move simd related test-case from `slow` to `fast`
xedin Oct 21, 2024
ff8663f
[Tests] NFC: Update a couple of type-checker tests
xedin Oct 31, 2024
9fb7314
[CSOptimizer] Limit "old" behavior compatibility to unlabeled unary a…
xedin Nov 1, 2024
c2a5588
[CSOptimizer] Fix `selectDisjunction` to use favored choices even if …
xedin Nov 4, 2024
15c773b
[CSOptimizer] Make a light-weight generic overload check if some requ…
xedin Nov 11, 2024
867e641
[CSOptimizer] Mark compiler synthesized disjunctions as optimized
xedin Nov 11, 2024
87cd5f8
[CSOptimizer] Add support for chained members without arguments
xedin Nov 12, 2024
b7e7493
[CSBindings] Prevent `BindingSet::isViable` from dropping viable bind…
xedin Nov 23, 2024
cb876cb
[CSSimplify] CGFloat-Double: Rank narrowing correctly when result is …
xedin Nov 26, 2024
bf8ae3b
[CSOptimizer] Allow only widening CGFloat->Double conversions while m…
xedin Nov 26, 2024
56d6635
[CSOptimizer] Implement special prioritization rules for result build…
xedin Nov 28, 2024
40a41c8
[CSBindings] Don't attempt to strip optional for closure result types
xedin Dec 5, 2024
95b47ae
[CSOptimizer] Reduce overload types before ranking
xedin Dec 7, 2024
c767f7a
[ConstraintSystem] Fix `getEffectiveOverloadType` handling of `mutati…
xedin Dec 17, 2024
f7d81d5
[TypeCheckEffects] Downgrade missing `await` to a warning for initial…
xedin Dec 17, 2024
43ca7df
[CSOptimizer] Simplify handling of non-applied disjunctions
xedin Dec 17, 2024
860ae08
[CSOptimizer] Mark bitwise operators as supported
xedin Dec 17, 2024
bc3a15f
[CSOptimizer] Disable CGFloat -> Double conversion for unary operators
xedin Dec 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions include/swift/Sema/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -825,11 +825,6 @@ class Constraint final : public llvm::ilist_node<Constraint>,
});
}

/// Returns the number of resolved argument types for an applied disjunction
/// constraint. This is always zero for disjunctions that do not represent
/// an applied overload.
unsigned countResolvedArgumentTypes(ConstraintSystem &cs) const;

/// Determine if this constraint represents explicit conversion,
/// e.g. coercion constraint "as X" which forms a disjunction.
bool isExplicitConversion() const;
Expand Down
122 changes: 21 additions & 101 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,14 @@ class TypeVariableType::Implementation {
/// literal (represented by `ArrayExpr` and `DictionaryExpr` in AST).
bool isCollectionLiteralType() const;

/// Determine whether this type variable represents a literal such
/// as an integer value, a floating-point value with and without a sign.
bool isNumberLiteralType() const;

/// Determine whether this type variable represents a result type of a
/// function call.
bool isFunctionResult() const;

/// Retrieve the representative of the equivalence class to which this
/// type variable belongs.
///
Expand Down Expand Up @@ -2203,10 +2211,6 @@ class ConstraintSystem {

llvm::SetVector<TypeVariableType *> TypeVariables;

/// Maps expressions to types for choosing a favored overload
/// type in a disjunction constraint.
llvm::DenseMap<Expr *, TypeBase *> FavoredTypes;

/// Maps discovered closures to their types inferred
/// from declared parameters/result and body.
///
Expand Down Expand Up @@ -2424,75 +2428,6 @@ class ConstraintSystem {
SynthesizedConformances;

private:
/// Describe the candidate expression for partial solving.
/// This class used by shrink & solve methods which apply
/// variation of directional path consistency algorithm in attempt
/// to reduce scopes of the overload sets (disjunctions) in the system.
class Candidate {
Expr *E;
DeclContext *DC;
llvm::BumpPtrAllocator &Allocator;

// Contextual Information.
Type CT;
ContextualTypePurpose CTP;

public:
Candidate(ConstraintSystem &cs, Expr *expr, Type ct = Type(),
ContextualTypePurpose ctp = ContextualTypePurpose::CTP_Unused)
: E(expr), DC(cs.DC), Allocator(cs.Allocator), CT(ct), CTP(ctp) {}

/// Return underlying expression.
Expr *getExpr() const { return E; }

/// Try to solve this candidate sub-expression
/// and re-write it's OSR domains afterwards.
///
/// \param shrunkExprs The set of expressions which
/// domains have been successfully shrunk so far.
///
/// \returns true on solver failure, false otherwise.
bool solve(llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs);

/// Apply solutions found by solver as reduced OSR sets for
/// for current and all of it's sub-expressions.
///
/// \param solutions The solutions found by running solver on the
/// this candidate expression.
///
/// \param shrunkExprs The set of expressions which
/// domains have been successfully shrunk so far.
void applySolutions(
llvm::SmallVectorImpl<Solution> &solutions,
llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs) const;

/// Check if attempt at solving of the candidate makes sense given
/// the current conditions - number of shrunk domains which is related
/// to the given candidate over the total number of disjunctions present.
static bool
isTooComplexGiven(ConstraintSystem *const cs,
llvm::SmallSetVector<OverloadSetRefExpr *, 4> &shrunkExprs) {
SmallVector<Constraint *, 8> disjunctions;
cs->collectDisjunctions(disjunctions);

unsigned unsolvedDisjunctions = disjunctions.size();
for (auto *disjunction : disjunctions) {
auto *locator = disjunction->getLocator();
if (!locator)
continue;

if (auto *OSR = getAsExpr<OverloadSetRefExpr>(locator->getAnchor())) {
if (shrunkExprs.count(OSR) > 0)
--unsolvedDisjunctions;
}
}

unsigned threshold =
cs->getASTContext().TypeCheckerOpts.SolverShrinkUnsolvedThreshold;
return unsolvedDisjunctions >= threshold;
}
};

/// Describes the current solver state.
struct SolverState {
SolverState(ConstraintSystem &cs,
Expand Down Expand Up @@ -3016,15 +2951,6 @@ class ConstraintSystem {
return nullptr;
}

TypeBase* getFavoredType(Expr *E) {
assert(E != nullptr);
return this->FavoredTypes[E];
}
void setFavoredType(Expr *E, TypeBase *T) {
assert(E != nullptr);
this->FavoredTypes[E] = T;
}

/// Set the type in our type map for the given node, and record the change
/// in the trail.
///
Expand Down Expand Up @@ -5280,19 +5206,11 @@ class ConstraintSystem {
/// \returns true if an error occurred, false otherwise.
bool solveSimplified(SmallVectorImpl<Solution> &solutions);

/// Find reduced domains of disjunction constraints for given
/// expression, this is achieved to solving individual sub-expressions
/// and combining resolving types. Such algorithm is called directional
/// path consistency because it goes from children to parents for all
/// related sub-expressions taking union of their domains.
///
/// \param expr The expression to find reductions for.
void shrink(Expr *expr);

/// Pick a disjunction from the InactiveConstraints list.
///
/// \returns The selected disjunction.
Constraint *selectDisjunction();
/// \returns The selected disjunction and a set of it's favored choices.
std::optional<std::pair<Constraint *, llvm::TinyPtrVector<Constraint *>>>
selectDisjunction();

/// Pick a conjunction from the InactiveConstraints list.
///
Expand Down Expand Up @@ -5481,11 +5399,6 @@ class ConstraintSystem {
bool applySolutionToBody(TapExpr *tapExpr,
SyntacticElementTargetRewriter &rewriter);

/// Reorder the disjunctive clauses for a given expression to
/// increase the likelihood that a favored constraint will be successfully
/// resolved before any others.
void optimizeConstraints(Expr *e);

void startExpressionTimer(ExpressionTimer::AnchorType anchor);

/// Determine if we've already explored too many paths in an
Expand Down Expand Up @@ -6226,7 +6139,8 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
public:
using Element = DisjunctionChoice;

DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction)
DisjunctionChoiceProducer(ConstraintSystem &cs, Constraint *disjunction,
llvm::TinyPtrVector<Constraint *> &favorites)
: BindingProducer(cs, disjunction->shouldRememberChoice()
? disjunction->getLocator()
: nullptr),
Expand All @@ -6236,6 +6150,11 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
assert(disjunction->getKind() == ConstraintKind::Disjunction);
assert(!disjunction->shouldRememberChoice() || disjunction->getLocator());

// Mark constraints as favored. This information
// is going to be used by partitioner.
for (auto *choice : favorites)
cs.favorConstraint(choice);

// Order and partition the disjunction choices.
partitionDisjunction(Ordering, PartitionBeginning);
}
Expand Down Expand Up @@ -6280,8 +6199,9 @@ class DisjunctionChoiceProducer : public BindingProducer<DisjunctionChoice> {
// Partition the choices in the disjunction into groups that we will
// iterate over in an order appropriate to attempt to stop before we
// have to visit all of the options.
void partitionDisjunction(SmallVectorImpl<unsigned> &Ordering,
SmallVectorImpl<unsigned> &PartitionBeginning);
void
partitionDisjunction(SmallVectorImpl<unsigned> &Ordering,
SmallVectorImpl<unsigned> &PartitionBeginning);

/// Partition the choices in the range \c first to \c last into groups and
/// order the groups in the best order to attempt based on the argument
Expand Down
1 change: 1 addition & 0 deletions lib/Sema/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ add_swift_host_library(swiftSema STATIC
CSStep.cpp
CSTrail.cpp
CSFix.cpp
CSOptimizer.cpp
CSDiagnostics.cpp
CodeSynthesis.cpp
CodeSynthesisDistributedActor.cpp
Expand Down
70 changes: 59 additions & 11 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ using namespace swift;
using namespace constraints;
using namespace inference;

/// Check whether there exists a type that could be implicitly converted
/// to a given type i.e. is the given type is Double or Optional<..> this
/// function is going to return true because CGFloat could be converted
/// to a Double and non-optional value could be injected into an optional.
static bool hasConversions(Type);

static std::optional<Type> checkTypeOfBinding(TypeVariableType *typeVar,
Type type);

Expand Down Expand Up @@ -1209,7 +1215,31 @@ bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) {
if (!existingNTD || NTD != existingNTD)
continue;

// FIXME: What is going on here needs to be thoroughly re-evaluated.
// What is going on in this method needs to be thoroughly re-evaluated!
//
// This logic aims to skip dropping bindings if
// collection type has conversions i.e. in situations like:
//
// [$T1] conv $T2
// $T2 conv [(Int, String)]
// $T2.Element equal $T5.Element
//
// `$T1` could be bound to `(i: Int, v: String)` after
// `$T2` is bound to `[(Int, String)]` which is is a problem
// because it means that `$T2` was attempted to early
// before the solver had a chance to discover all viable
// bindings.
//
// Let's say existing binding is `[(Int, String)]` and
// relation is "exact", in this case there is no point
// tracking `[$T1]` because upcasts are only allowed for
// subtype and other conversions.
if (existing->Kind != AllowedBindingKind::Exact) {
if (existingType->isKnownStdlibCollectionType() &&
hasConversions(existingType)) {
continue;
}
}

// If new type has a type variable it shouldn't
// be considered viable.
Expand Down Expand Up @@ -2417,17 +2447,35 @@ bool TypeVarBindingProducer::computeNext() {
if (binding.Kind == BindingKind::Subtypes || CS.shouldAttemptFixes()) {
// If we were unsuccessful solving for T?, try solving for T.
if (auto objTy = type->getOptionalObjectType()) {
// If T is a type variable, only attempt this if both the
// type variable we are trying bindings for, and the type
// variable we will attempt to bind, both have the same
// polarity with respect to being able to bind lvalues.
if (auto otherTypeVar = objTy->getAs<TypeVariableType>()) {
if (TypeVar->getImpl().canBindToLValue() ==
otherTypeVar->getImpl().canBindToLValue()) {
addNewBinding(binding.withSameSource(objTy, binding.Kind));
// TODO: This could be generalized in the future to cover all patterns
// that have an intermediate type variable in subtype/conversion chain.
//
// Let's not perform $T? -> $T for closure result types to avoid having
// to re-discover solutions that differ only in location of optional
// injection.
//
// The pattern with such type variables is:
//
// $T_body <conv/subtype> $T_result <conv/subtype> $T_contextual_result
//
// When $T_contextual_result is Optional<$U>, the optional injection
// can either happen from $T_body or from $T_result (if `return`
// expression is non-optional), if we allow both the solver would
// find two solutions that differ only in location of optional
// injection.
if (!TypeVar->getImpl().isClosureResultType()) {
// If T is a type variable, only attempt this if both the
// type variable we are trying bindings for, and the type
// variable we will attempt to bind, both have the same
// polarity with respect to being able to bind lvalues.
if (auto otherTypeVar = objTy->getAs<TypeVariableType>()) {
if (TypeVar->getImpl().canBindToLValue() ==
otherTypeVar->getImpl().canBindToLValue()) {
addNewBinding(binding.withType(objTy));
}
} else {
addNewBinding(binding.withType(objTy));
}
} else {
addNewBinding(binding.withSameSource(objTy, binding.Kind));
}
}
}
Expand Down
Loading