Skip to content

Commit

Permalink
Fix 635: combine boolean logic (#5116)
Browse files Browse the repository at this point in the history
* combine boolean logic

* generalize and ensure no code duplication

* fix tests

* update baselines

* fix baseline

* Update Optimizer.fs

* update baselines
  • Loading branch information
dsyme authored and KevinRansom committed Sep 15, 2018
1 parent 7f67ee2 commit 5e8352b
Show file tree
Hide file tree
Showing 26 changed files with 222 additions and 163 deletions.
82 changes: 81 additions & 1 deletion src/fsharp/Optimizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1382,6 +1382,84 @@ let rec (|KnownValApp|_|) expr =
| Expr.App(KnownValApp(vref, typeArgs1, otherArgs1), _, typeArgs2, otherArgs2, _) -> Some(vref, typeArgs1@typeArgs2, otherArgs1@otherArgs2)
| _ -> None

/// Matches boolean decision tree:
/// check single case with bool const.
let (|TDBoolSwitch|_|) dtree =
match dtree with
| TDSwitch( expr, [TCase (DecisionTreeTest.Const(Const.Bool testBool), caseTree )], Some defaultTree, range) ->
Some (expr, testBool, caseTree, defaultTree, range)
| _ ->
None

/// Check target that have a constant bool value
let (|ConstantBoolTarget|_|) target =
match target with
| TTarget([], Expr.Const (Const.Bool b,_,_),_) -> Some b
| _ -> None

/// Is this a tree, where each decision is a two-way switch (to prevent later duplication of trees), and each branch returns or true/false,
/// apart from one branch which defers to another expression
let rec CountBoolLogicTree ((targets: DecisionTreeTarget[], costOuterCaseTree, costOuterDefaultTree, testBool) as data) tree =
match tree with
| TDSwitch (_expr, [case], Some defaultTree, _range) ->
let tc1,ec1 = CountBoolLogicTree data case.CaseTree
let tc2, ec2 = CountBoolLogicTree data defaultTree
tc1 + tc2, ec1 + ec2
| TDSuccess([], idx) ->
match targets.[idx] with
| ConstantBoolTarget result -> (if result = testBool then costOuterCaseTree else costOuterDefaultTree), 0
| TTarget([], _exp, _) -> costOuterCaseTree + costOuterDefaultTree, 10
| _ -> 100, 100
| _ -> 100, 100

/// Rewrite a decision tree for which CountBoolLogicTree returned a low number (see below). Produce a new decision
/// tree where at each ConstantBoolSuccessTree tip we replace with either outerCaseTree or outerDefaultTree
/// depending on whether the target result was true/false
let rec RewriteBoolLogicTree ((targets: DecisionTreeTarget[], outerCaseTree, outerDefaultTree, testBool) as data) tree =
match tree with
| TDSwitch (expr, cases, defaultTree, range) ->
let cases2 = cases |> List.map (RewriteBoolLogicCase data)
let defaultTree2 = defaultTree |> Option.map (RewriteBoolLogicTree data)
TDSwitch (expr, cases2, defaultTree2, range)
| TDSuccess([], idx) ->
match targets.[idx] with
| ConstantBoolTarget result -> if result = testBool then outerCaseTree else outerDefaultTree
| TTarget([], exp, _) -> mkBoolSwitch exp.Range exp (if testBool then outerCaseTree else outerDefaultTree) (if testBool then outerDefaultTree else outerCaseTree)
| _ -> failwith "CountBoolLogicTree should exclude this case"
| _ -> failwith "CountBoolLogicTree should exclude this case"

and RewriteBoolLogicCase data (TCase(test, tree)) =
TCase(test, RewriteBoolLogicTree data tree)

/// Repeatedly combine switch-over-match decision trees, see https://github.com/Microsoft/visualfsharp/issues/635.
/// The outer decision tree is doing a swithc over a boolean result, the inner match is producing only
/// constant boolean results in its targets.
let rec CombineBoolLogic expr =

// try to find nested boolean switch
match expr with
| Expr.Match(outerSP, outerMatchRange,
TDBoolSwitch(Expr.Match(_innerSP, _innerMatchRange, innerTree, innerTargets, _innerDefaultRange, _innerMatchTy),
outerTestBool, outerCaseTree, outerDefaultTree, _outerSwitchRange ),
outerTargets, outerDefaultRange, outerMatchTy) ->

let costOuterCaseTree = match outerCaseTree with TDSuccess _ -> 0 | _ -> 1
let costOuterDefaultTree = match outerDefaultTree with TDSuccess _ -> 0 | _ -> 1
let tc, ec = CountBoolLogicTree (innerTargets, costOuterCaseTree, costOuterDefaultTree, outerTestBool) innerTree
// At most one expression, no overall duplication of TSwitch nodes
if tc <= costOuterCaseTree + costOuterDefaultTree && ec <= 10 then
let newExpr =
Expr.Match(outerSP, outerMatchRange,
RewriteBoolLogicTree (innerTargets, outerCaseTree, outerDefaultTree, outerTestBool) innerTree,
outerTargets, outerDefaultRange, outerMatchTy)

CombineBoolLogic newExpr
else
expr
| _ ->
expr


//-------------------------------------------------------------------------
// ExpandStructuralBinding
//
Expand Down Expand Up @@ -2814,7 +2892,9 @@ and OptimizeMatch cenv env (spMatch, exprm, dtree, targets, m, ty) =
// REVIEW: consider collecting, merging and using information flowing through each line of the decision tree to each target
let dtree', dinfo = OptimizeDecisionTree cenv env m dtree
let targets', tinfos = OptimizeDecisionTreeTargets cenv env m targets
RebuildOptimizedMatch (spMatch, exprm, m, ty, dtree', targets', dinfo, tinfos)
let newExpr, newInfo = RebuildOptimizedMatch (spMatch, exprm, m, ty, dtree', targets', dinfo, tinfos)
let newExpr2 = if not (cenv.settings.localOpt()) then newExpr else CombineBoolLogic newExpr
newExpr2, newInfo

and CombineMatchInfos dinfo tinfo =
{ TotalSize = dinfo.TotalSize + tinfo.TotalSize
Expand Down
81 changes: 44 additions & 37 deletions src/fsharp/tast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ type ValFlags(flags:int64) =
(flags &&& ~~~0b0011001100000000000L)

/// Represents the kind of a type parameter
[<RequireQualifiedAccess; StructuredFormatDisplay("{DebugText}")>]
[<RequireQualifiedAccess (* ; StructuredFormatDisplay("{DebugText}") *) >]
type TyparKind =

| Type
Expand All @@ -273,10 +273,10 @@ type TyparKind =
| TyparKind.Type -> None
| TyparKind.Measure -> Some "Measure"

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() =
override x.ToString() =
match x with
| TyparKind.Type -> "type"
| TyparKind.Measure -> "measure"
Expand Down Expand Up @@ -1349,7 +1349,7 @@ and
override x.ToString() = "TyconAugmentation(...)"

and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (*; StructuredFormatDisplay("{DebugText}") *) >]
/// The information for the contents of a type. Also used for a provided namespace.
TyconRepresentation =

Expand Down Expand Up @@ -1393,10 +1393,10 @@ and
/// The information for exception definitions should be folded into here.
| TNoRepr

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = "TyconRepresentation(...)"
override x.ToString() = sprintf "%+A" x

and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
Expand Down Expand Up @@ -1751,7 +1751,7 @@ and
override x.ToString() = x.Name

and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (*; StructuredFormatDisplay("{DebugText}") *) >]
ExceptionInfo =
/// Indicates that an exception is an abbreviation for the given exception
| TExnAbbrevRepr of TyconRef
Expand All @@ -1765,10 +1765,11 @@ and
/// Indicates that an exception is abstract, i.e. is in a signature file, and we do not know the representation
| TExnNone

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = "ExceptionInfo(...)"
override x.ToString() = sprintf "%+A" x

and [<Sealed; StructuredFormatDisplay("{DebugText}")>]
ModuleOrNamespaceType(kind: ModuleOrNamespaceKind, vals: QueueList<Val>, entities: QueueList<Entity>) =
Expand Down Expand Up @@ -2340,11 +2341,11 @@ and
/// Indicates a constraint that a type is .NET unmanaged type
| IsUnmanaged of range

// Prefer the default formatting of this union type
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()
//
//override x.ToString() = "TyparConstraint(...)"

override x.ToString() = sprintf "%+A" x

/// The specification of a member constraint that must be solved
and
Expand Down Expand Up @@ -2374,7 +2375,7 @@ and
override x.ToString() = "TTrait(" + x.MemberName + ")"

and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (* ; StructuredFormatDisplay("{DebugText}") *) >]
/// Indicates the solution of a member constraint during inference.
TraitConstraintSln =

Expand Down Expand Up @@ -2411,10 +2412,11 @@ and
/// Indicates a trait is solved by a 'fake' instance of an operator, like '+' on integers
| BuiltInSln

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = "TraitConstraintSln(...)"
override x.ToString() = sprintf "%+A" x

/// The partial information used to index the methods of all those in a ModuleOrNamespace.
and [<RequireQualifiedAccess; StructuredFormatDisplay("{DebugText}")>]
Expand Down Expand Up @@ -3996,11 +3998,11 @@ and
/// Raising a measure to a rational power
| RationalPower of Measure * Rational

// Prefer the default formatting of this union type
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()
//
//override x.ToString() = "Measure(...)"

override x.ToString() = sprintf "%+A" x

and
[<NoEquality; NoComparison; RequireQualifiedAccess; StructuredFormatDisplay("{DebugText}")>]
Expand Down Expand Up @@ -4249,7 +4251,7 @@ and
and Attribs = Attrib list

and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (* ; StructuredFormatDisplay("{DebugText}") *) >]
AttribKind =

/// Indicates an attribute refers to a type defined in an imported .NET assembly
Expand All @@ -4258,10 +4260,11 @@ and
/// Indicates an attribute refers to a type defined in an imported F# assembly
| FSAttrib of ValRef

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = sprintf "AttribKind(...)"
override x.ToString() = sprintf "%+A" x

/// Attrib(kind,unnamedArgs,propVal,appliedToAGetterOrSetter,targetsOpt,range)
and
Expand Down Expand Up @@ -4325,10 +4328,11 @@ and [<RequireQualifiedAccess>]

/// Decision trees. Pattern matching has been compiled down to
/// a decision tree by this point. The right-hand-sides (actions) of
/// a decision tree by this point. The right-hand-sides (actions) of
/// the decision tree are labelled by integers that are unique for that
/// particular tree.
and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (* ; StructuredFormatDisplay("{DebugText}") *) >]
DecisionTree =

/// TDSwitch(input, cases, default, range)
Expand Down Expand Up @@ -4357,10 +4361,11 @@ and
/// body -- the rest of the decision tree
| TDBind of Binding * DecisionTree

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = sprintf "DecisionTree(...)"
override x.ToString() = sprintf "%+A" x

/// Represents a test and a subsequent decision tree
and
Expand All @@ -4380,7 +4385,7 @@ and
override x.ToString() = sprintf "DecisionTreeCase(...)"

and
[<NoEquality; NoComparison; RequireQualifiedAccess; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison; RequireQualifiedAccess (*; StructuredFormatDisplay("{DebugText}") *) >]
DecisionTreeTest =
/// Test if the input to a decision tree matches the given union case
| UnionCase of UnionCaseRef * TypeInst
Expand Down Expand Up @@ -4410,10 +4415,11 @@ and
/// activePatternInfo -- The extracted info for the active pattern.
| ActivePatternCase of Expr * TTypes * (ValRef * TypeInst) option * int * ActivePatternInfo

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
//member x.DebugText = x.ToString()

override x.ToString() = sprintf "DecisionTreeTest(...)"
override x.ToString() = sprintf "%+A" x

/// A target of a decision tree. Can be thought of as a little function, though is compiled as a local block.
and
Expand Down Expand Up @@ -4907,7 +4913,7 @@ and

/// The contents of a module-or-namespace-fragment definition
and
[<NoEquality; NoComparison; StructuredFormatDisplay("{DebugText}")>]
[<NoEquality; NoComparison (* ; StructuredFormatDisplay("{DebugText}") *) >]
ModuleOrNamespaceExpr =
/// Indicates the module is a module with a signature
| TMAbstract of ModuleOrNamespaceExprWithSig
Expand All @@ -4924,10 +4930,11 @@ and
/// Indicates the module fragment is a 'rec' or 'non-rec' definition of types and modules
| TMDefRec of isRec:bool * Tycon list * ModuleOrNamespaceBinding list * range

[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
// %+A formatting is used, so this is not needed
//[<DebuggerBrowsable(DebuggerBrowsableState.Never)>]
member x.DebugText = x.ToString()

override x.ToString() = "ModuleOrNamespaceExpr(...)"
override x.ToString() = sprintf "%+A" x

/// A named module-or-namespace-fragment definition
and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
// Offset: 0x00000208 Length: 0x0000007A
}
.module GenIter01.exe
// MVID: {5B9A68C0-F836-DC98-A745-0383C0689A5B}
// MVID: {5B9A6329-F836-DC98-A745-038329639A5B}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00950000
// Image base: 0x01080000


// =============== CLASS MEMBERS DECLARATION ===================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
// Offset: 0x00000208 Length: 0x0000007B
}
.module GenIter02.exe
// MVID: {5B9A68C0-F857-DC98-A745-0383C0689A5B}
// MVID: {5B9A6329-F857-DC98-A745-038329639A5B}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x01060000
// Image base: 0x02900000


// =============== CLASS MEMBERS DECLARATION ===================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
// Offset: 0x00000208 Length: 0x0000007B
}
.module GenIter03.exe
// MVID: {5B9A68C0-F77C-DC98-A745-0383C0689A5B}
// MVID: {5B9A6329-F77C-DC98-A745-038329639A5B}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00770000
// Image base: 0x026B0000


// =============== CLASS MEMBERS DECLARATION ===================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
// Offset: 0x000001F8 Length: 0x0000007B
}
.module GenIter04.exe
// MVID: {5B9A68C0-F79D-DC98-A745-0383C0689A5B}
// MVID: {5B9A6329-F79D-DC98-A745-038329639A5B}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02580000
// Image base: 0x00790000


// =============== CLASS MEMBERS DECLARATION ===================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
// Offset: 0x00000280 Length: 0x000000AF
}
.module ListExpressionSteppingTest5.exe
// MVID: {5B9A68C1-CBE3-BFEA-A745-0383C1689A5B}
// MVID: {5B9A6329-CBE3-BFEA-A745-038329639A5B}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x02900000
// Image base: 0x027C0000


// =============== CLASS MEMBERS DECLARATION ===================
Expand Down
Loading

0 comments on commit 5e8352b

Please sign in to comment.