[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:
Doug Gregor
2019-08-02 13:22:17 -07:00
parent 3c69f6a305
commit be73a9d641
7 changed files with 102 additions and 7 deletions

View File

@@ -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;

View File

@@ -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">;

View File

@@ -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;

View File

@@ -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);
} }

View File

@@ -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);
} }

View File

@@ -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);

View 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"]
}
})