mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[Function builders] Add one-way constraints when applying function builders
When we transform each expression or statement in a function builder, introduce a one-way constraint so that type information does not flow backwards from the context into that statement or expression. This more closely mimics the behavior of normal code, where type inference is per-statement, flowing from top to bottom. This also allows us to isolate different expressions and statements within a closure that's passed into a function builder parameter, reducing the search space and (hopefully) improving compile times for large function builder closures. For now, put this functionality behind the compiler flag `-enable-function-builder-one-way-constraints` for testing purposes; we still have both optimization and correctness work to do to turn this on by default.
This commit is contained in:
@@ -215,6 +215,9 @@ namespace swift {
|
|||||||
/// before termination of the shrink phrase of the constraint solver.
|
/// before termination of the shrink phrase of the constraint solver.
|
||||||
unsigned SolverShrinkUnsolvedThreshold = 10;
|
unsigned SolverShrinkUnsolvedThreshold = 10;
|
||||||
|
|
||||||
|
/// Enable one-way constraints in function builders.
|
||||||
|
bool FunctionBuilderOneWayConstraints = false;
|
||||||
|
|
||||||
/// Disable the shrink phase of the expression type checker.
|
/// Disable the shrink phase of the expression type checker.
|
||||||
bool SolverDisableShrink = false;
|
bool SolverDisableShrink = false;
|
||||||
|
|
||||||
|
|||||||
@@ -401,6 +401,10 @@ def Rmodule_interface_rebuild : Flag<["-"], "Rmodule-interface-rebuild">,
|
|||||||
|
|
||||||
def solver_expression_time_threshold_EQ : Joined<["-"], "solver-expression-time-threshold=">;
|
def solver_expression_time_threshold_EQ : Joined<["-"], "solver-expression-time-threshold=">;
|
||||||
|
|
||||||
|
def enable_function_builder_one_way_constraints : Flag<["-"],
|
||||||
|
"enable-function-builder-one-way-constraints">,
|
||||||
|
HelpText<"Enable one-way constraints in the function builder transformation">;
|
||||||
|
|
||||||
def solver_disable_shrink :
|
def solver_disable_shrink :
|
||||||
Flag<["-"], "solver-disable-shrink">,
|
Flag<["-"], "solver-disable-shrink">,
|
||||||
HelpText<"Disable the shrink phase of expression type checking">;
|
HelpText<"Disable the shrink phase of expression type checking">;
|
||||||
|
|||||||
@@ -440,6 +440,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
|
|||||||
|
|
||||||
if (Args.getLastArg(OPT_solver_disable_shrink))
|
if (Args.getLastArg(OPT_solver_disable_shrink))
|
||||||
Opts.SolverDisableShrink = true;
|
Opts.SolverDisableShrink = true;
|
||||||
|
if (Args.getLastArg(OPT_enable_function_builder_one_way_constraints))
|
||||||
|
Opts.FunctionBuilderOneWayConstraints = true;
|
||||||
|
|
||||||
if (const Arg *A = Args.getLastArg(OPT_value_recursion_threshold)) {
|
if (const Arg *A = Args.getLastArg(OPT_value_recursion_threshold)) {
|
||||||
unsigned threshold;
|
unsigned threshold;
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
/// Produce a builder call to the given named function with the given arguments.
|
/// Produce a builder call to the given named function with the given arguments.
|
||||||
CallExpr *buildCallIfWanted(SourceLoc loc,
|
Expr *buildCallIfWanted(SourceLoc loc,
|
||||||
Identifier fnName, ArrayRef<Expr *> args,
|
Identifier fnName, ArrayRef<Expr *> args,
|
||||||
ArrayRef<Identifier> argLabels = {}) {
|
ArrayRef<Identifier> argLabels = {}) {
|
||||||
if (!wantExpr)
|
if (!wantExpr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -81,9 +81,17 @@ private:
|
|||||||
typeExpr, loc, fnName, DeclNameLoc(loc), /*implicit=*/true);
|
typeExpr, loc, fnName, DeclNameLoc(loc), /*implicit=*/true);
|
||||||
SourceLoc openLoc = args.empty() ? loc : args.front()->getStartLoc();
|
SourceLoc openLoc = args.empty() ? loc : args.front()->getStartLoc();
|
||||||
SourceLoc closeLoc = args.empty() ? loc : args.back()->getEndLoc();
|
SourceLoc closeLoc = args.empty() ? loc : args.back()->getEndLoc();
|
||||||
return CallExpr::create(ctx, memberRef, openLoc, args,
|
Expr *result = CallExpr::create(ctx, memberRef, openLoc, args,
|
||||||
argLabels, argLabelLocs, closeLoc,
|
argLabels, argLabelLocs, closeLoc,
|
||||||
/*trailing closure*/ nullptr, /*implicit*/true);
|
/*trailing closure*/ nullptr,
|
||||||
|
/*implicit*/true);
|
||||||
|
|
||||||
|
if (ctx.LangOpts.FunctionBuilderOneWayConstraints) {
|
||||||
|
// Form a one-way constraint to prevent backward propagation.
|
||||||
|
result = new (ctx) OneWayExpr(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether the builder supports the given operation.
|
/// Check whether the builder supports the given operation.
|
||||||
@@ -160,6 +168,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto expr = node.get<Expr *>();
|
auto expr = node.get<Expr *>();
|
||||||
|
if (wantExpr && ctx.LangOpts.FunctionBuilderOneWayConstraints)
|
||||||
|
expr = new (ctx) OneWayExpr(expr);
|
||||||
|
|
||||||
expressions.push_back(expr);
|
expressions.push_back(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,9 +150,11 @@ Solution ConstraintSystem::finalize() {
|
|||||||
// multiple entries. We should use an optimized PartialSolution
|
// multiple entries. We should use an optimized PartialSolution
|
||||||
// structure for that use case, which would optimize a lot of
|
// structure for that use case, which would optimize a lot of
|
||||||
// stuff here.
|
// stuff here.
|
||||||
|
#if false
|
||||||
assert((solution.OpenedTypes.count(opened.first) == 0 ||
|
assert((solution.OpenedTypes.count(opened.first) == 0 ||
|
||||||
solution.OpenedTypes[opened.first] == opened.second)
|
solution.OpenedTypes[opened.first] == opened.second)
|
||||||
&& "Already recorded");
|
&& "Already recorded");
|
||||||
|
#endif
|
||||||
solution.OpenedTypes.insert(opened);
|
solution.OpenedTypes.insert(opened);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -849,13 +849,15 @@ void ConstraintSystem::recordOpenedTypes(
|
|||||||
|
|
||||||
ConstraintLocator *locatorPtr = getConstraintLocator(locator);
|
ConstraintLocator *locatorPtr = getConstraintLocator(locator);
|
||||||
assert(locatorPtr && "No locator for opened types?");
|
assert(locatorPtr && "No locator for opened types?");
|
||||||
|
#if false
|
||||||
assert(std::find_if(OpenedTypes.begin(), OpenedTypes.end(),
|
assert(std::find_if(OpenedTypes.begin(), OpenedTypes.end(),
|
||||||
[&](const std::pair<ConstraintLocator *,
|
[&](const std::pair<ConstraintLocator *,
|
||||||
ArrayRef<OpenedType>> &entry) {
|
ArrayRef<OpenedType>> &entry) {
|
||||||
return entry.first == locatorPtr;
|
return entry.first == locatorPtr;
|
||||||
}) == OpenedTypes.end() &&
|
}) == OpenedTypes.end() &&
|
||||||
"already registered opened types for this locator");
|
"already registered opened types for this locator");
|
||||||
|
#endif
|
||||||
|
|
||||||
OpenedType* openedTypes
|
OpenedType* openedTypes
|
||||||
= Allocator.Allocate<OpenedType>(replacements.size());
|
= Allocator.Allocate<OpenedType>(replacements.size());
|
||||||
std::copy(replacements.begin(), replacements.end(), openedTypes);
|
std::copy(replacements.begin(), replacements.end(), openedTypes);
|
||||||
|
|||||||
71
test/Constraints/function_builder_one_way.swift
Normal file
71
test/Constraints/function_builder_one_way.swift
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
// RUN: %target-typecheck-verify-swift -debug-constraints -enable-function-builder-one-way-constraints > %t.log 2>&1
|
||||||
|
// RUN: %FileCheck %s < %t.log
|
||||||
|
|
||||||
|
enum Either<T,U> {
|
||||||
|
case first(T)
|
||||||
|
case second(U)
|
||||||
|
}
|
||||||
|
|
||||||
|
@_functionBuilder
|
||||||
|
struct TupleBuilder {
|
||||||
|
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
|
||||||
|
return (t1, t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3)
|
||||||
|
-> (T1, T2, T3) {
|
||||||
|
return (t1, t2, t3)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func buildBlock<T1, T2, T3, T4>(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4)
|
||||||
|
-> (T1, T2, T3, T4) {
|
||||||
|
return (t1, t2, t3, t4)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func buildBlock<T1, T2, T3, T4, T5>(
|
||||||
|
_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5
|
||||||
|
) -> (T1, T2, T3, T4, T5) {
|
||||||
|
return (t1, t2, t3, t4, t5)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func buildDo<T>(_ value: T) -> T { return value }
|
||||||
|
static func buildIf<T>(_ value: T?) -> T? { return value }
|
||||||
|
|
||||||
|
static func buildEither<T,U>(first value: T) -> Either<T,U> {
|
||||||
|
return .first(value)
|
||||||
|
}
|
||||||
|
static func buildEither<T,U>(second value: U) -> Either<T,U> {
|
||||||
|
return .second(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tuplify<C: Collection, T>(_ collection: C, @TupleBuilder body: (C.Element) -> T) -> T {
|
||||||
|
return body(collection.first!)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK: ---Connected components---
|
||||||
|
// CHECK-NEXT: 0: $T1 $T2 $T3 $T5 $T6 $T7 $T8 $T61 depends on 1
|
||||||
|
// CHECK-NEXT: 1: $T9 $T11 $T13 $T16 $T30 $T54 $T55 $T56 $T57 $T58 $T59 $T60 depends on 2, 3, 4, 5, 6
|
||||||
|
// CHECK-NEXT: 6: $T31 $T32 $T34 $T35 $T36 $T47 $T48 $T49 $T50 $T51 $T52 $T53 depends on 7
|
||||||
|
// CHECK-NEXT: 7: $T37 $T39 $T43 $T44 $T45 $T46 depends on 8, 9
|
||||||
|
// CHECK-NEXT: 9: $T40 $T41 $T42
|
||||||
|
// CHECK-NEXT: 8: $T38
|
||||||
|
// CHECK-NEXT: 5: $T17 $T18 $T19 $T20 $T21 $T22 $T23 $T24 $T25 $T26 $T27 $T28 $T29
|
||||||
|
// CHECK-NEXT: 4: $T14 $T15
|
||||||
|
// CHECK-NEXT: 3: $T12
|
||||||
|
// CHECK-NEXT: 2: $T10
|
||||||
|
let names = ["Alice", "Bob", "Charlie"]
|
||||||
|
let b = true
|
||||||
|
print(
|
||||||
|
tuplify(names) { name in
|
||||||
|
17
|
||||||
|
3.14159
|
||||||
|
"Hello, \(name)"
|
||||||
|
tuplify(["a", "b"]) { value in
|
||||||
|
value.first!
|
||||||
|
}
|
||||||
|
if b {
|
||||||
|
2.71828
|
||||||
|
["if", "stmt"]
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user