Skip to content

Commit c40ec84

Browse files
mrangelatkin
authored andcommitted
for expression optimization on lists and strings
Previously 'for i in expr do body' only had optimization when the type of expr was an 1D-array. 'for i in expr do body' now has optimizations for when expr is of type 'string' and 'List`1'. These optimizations increases the performance of the for expression but also reduces the number of objects created. closes dotnet/fsharp#219 commit e3935d95ce12a28f87e2d77c537b6f18e6d54d98 Author: latkin <latkin@microsoft.com> Date: Thu Aug 6 12:53:35 2015 -0700 Add cross-version test cases commit 1427f418ab1a2fb2b06684fafff809932714cf0a Merge: 01dc508 73509e9 Author: latkin <latkin@microsoft.com> Date: Thu Aug 6 10:42:54 2015 -0700 Merge branch 'pr/for_optimization' of https://github.com/mrange/visualfsharp into mrange-pr/for_optimization Conflicts: src/fsharp/TcGlobals.fs tests/fsharpqa/Source/Optimizations/ForLoop/env.lst commit 73509e956679a52f7b77d692cc85b3a835bca5b4 Author: mrange <marten_range@hotmail.com> Date: Mon Mar 9 00:04:11 2015 +0100 'for i in expr do body' optimization.. Previously 'for i in expr do body' only had optimization when the type of expr was an 1D-array. 'for i in expr do body' now has optimizations for when expr is of type 'string' and 'List`1'. These optimizations increases the performance of the for expression but also reduces the number of objects created. latkin provided the following fixes: 1. Adapting tests to work with core\portable and core\netcore 2. Loop item over strings sometimes uses object, not char 3. Adjustments to debug ranges so that debug stepping behavior is consistent/unchanged 4. Updating codegen tests to represent corrected debug ranges
1 parent 01dc508 commit c40ec84

File tree

26 files changed

+1645
-122
lines changed

26 files changed

+1645
-122
lines changed

src/fsharp/Optimizer.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,9 +1716,6 @@ let TryDetectQueryQuoteAndRun cenv (expr:Expr) =
17161716

17171717
let rec OptimizeExpr cenv (env:IncrementalOptimizationEnv) expr =
17181718

1719-
// foreach --> fast integer for loops
1720-
let expr = DetectFastIntegerForLoops cenv.g expr
1721-
17221719
// Eliminate subsumption coercions for functions. This must be done post-typechecking because we need
17231720
// complete inference types.
17241721
let expr = NormalizeAndAdjustPossibleSubsumptionExprs cenv.g expr
@@ -2087,6 +2084,9 @@ and OptimizeLetRec cenv env (binds,bodyExpr,m) =
20872084
//-------------------------------------------------------------------------
20882085

20892086
and OptimizeLinearExpr cenv env expr contf =
2087+
2088+
let expr = DetectAndOptimizeForExpression cenv.g OptimizeAllForExpressions expr
2089+
20902090
if verboseOptimizations then dprintf "OptimizeLinearExpr\n";
20912091
let expr = if cenv.settings.ExpandStructrualValues() then ExpandStructuralBinding cenv expr else expr
20922092
match expr with

src/fsharp/QuotationTranslator.fs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,7 @@ and ConvExpr cenv env (expr : Expr) =
203203

204204
and private ConvExprCore cenv (env : QuotationTranslationEnv) (expr: Expr) : QP.ExprData =
205205

206-
// Eliminate integer 'for' loops
207-
let expr = DetectFastIntegerForLoops cenv.g expr
206+
let expr = DetectAndOptimizeForExpression cenv.g OptimizeIntRangesOnly expr
208207

209208
// Eliminate subsumption coercions for functions. This must be done post-typechecking because we need
210209
// complete inference types.

src/fsharp/TastOps.fs

Lines changed: 95 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,6 +1427,11 @@ let destArrayTy (g:TcGlobals) ty =
14271427
| [ty] -> ty
14281428
| _ -> failwith "destArrayTy";
14291429

1430+
let destListTy (g:TcGlobals) ty =
1431+
let _,tinst = destAppTy g ty
1432+
match tinst with
1433+
| [ty] -> ty
1434+
| _ -> failwith "destListTy";
14301435

14311436
let isTypeConstructorEqualToOptional g tcOpt tc =
14321437
match tcOpt with
@@ -1439,6 +1444,8 @@ let isByrefLikeTyconRef g tcref =
14391444
isTypeConstructorEqualToOptional g g.system_ArgIterator_tcref tcref ||
14401445
isTypeConstructorEqualToOptional g g.system_RuntimeArgumentHandle_tcref tcref
14411446

1447+
let isStringTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref,_) -> tyconRefEq g tcref g.system_String_tcref | _ -> false)
1448+
let isListTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref,_) -> tyconRefEq g tcref g.list_tcr_canon | _ -> false)
14421449
let isArrayTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref,_) -> isArrayTyconRef g tcref | _ -> false)
14431450
let isArray1DTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref,_) -> tyconRefEq g tcref g.il_arr_tcr_map.[0] | _ -> false)
14441451
let isUnitTy g ty = ty |> stripTyEqns g |> (function TType_app(tcref,_) -> tyconRefEq g g.unit_tcr_canon tcref | _ -> false)
@@ -5856,6 +5863,8 @@ let mkIsInst ty e m = mkAsmExpr ([ isinst ], [ty],[e], [ ty ], m)
58565863

58575864
let mspec_Object_GetHashCode ilg = IL.mkILNonGenericInstanceMethSpecInTy(ilg.typ_Object,"GetHashCode",[],ilg.typ_int32)
58585865
let mspec_Type_GetTypeFromHandle ilg = IL.mkILNonGenericStaticMethSpecInTy(ilg.typ_Type,"GetTypeFromHandle",[ilg.typ_RuntimeTypeHandle],ilg.typ_Type)
5866+
let mspec_String_Length ilg = mkILNonGenericInstanceMethSpecInTy (ilg.typ_String, "get_Length", [], ilg.typ_int32)
5867+
58595868
let fspec_Missing_Value ilg = IL.mkILFieldSpecInTy(ilg.typ_Missing.Value, "Value", ilg.typ_Missing.Value)
58605869

58615870

@@ -6002,6 +6011,14 @@ let mkCallQuoteToLinqLambdaExpression g m ty e1 =
60026011
let mkLazyDelayed g m ty f = mkApps g (typedExprForIntrinsic g m g.lazy_create_info, [[ty]], [ f ], m)
60036012
let mkLazyForce g m ty e = mkApps g (typedExprForIntrinsic g m g.lazy_force_info, [[ty]], [ e; mkUnit g m ], m)
60046013

6014+
let mkGetString g m e1 e2 = mkApps g (typedExprForIntrinsic g m g.getstring_info, [], [e1;e2], m)
6015+
let mkGetStringChar = mkGetString
6016+
let mkGetStringLength g m e =
6017+
let mspec = mspec_String_Length g.ilg
6018+
/// ILCall(useCallvirt,isProtected,valu,newobj,valUseFlags,isProp,noTailCall,mref,actualTypeInst,actualMethInst, retTy)
6019+
Expr.Op(TOp.ILCall(false,false,false,false,ValUseFlag.NormalValUse,true,false,mspec.MethodRef,[],[],[g.int32_ty]),[],[e],m)
6020+
6021+
60056022
// Quotations can't contain any IL.
60066023
// As a result, we aim to get rid of all IL generation in the typechecker and pattern match
60076024
// compiler, or else train the quotation generator to understand the generated IL.
@@ -7723,34 +7740,92 @@ let (|RangeInt32Step|_|) g expr =
77237740
when valRefEq g vf g.range_op_vref && typeEquiv g tyarg g.int_ty -> Some(startExpr, 1, finishExpr)
77247741

77257742
// detect (RangeInt32 startExpr N finishExpr), the inlined/compiled form of 'n .. m' and 'n .. N .. m'
7726-
| Expr.App(Expr.Val(vf,_,_),_,[],[startExpr; Int32Expr n; finishExpr],_)
7743+
| Expr.App(Expr.Val(vf,_,_),_,[],[startExpr; Int32Expr n; finishExpr],_)
77277744
when valRefEq g vf g.range_int32_op_vref -> Some(startExpr, n, finishExpr)
77287745

77297746
| _ -> None
77307747

7731-
7732-
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <finishExpr> do <bodyExpr>' expression over integers
7733-
// Detect the compiled or optimized form of a 'for <elemVar> in <startExpr> .. <step> .. <finishExpr> do <bodyExpr>' expression over integers when step is positive
7734-
let (|CompiledInt32ForEachExprWithKnownStep|_|) g expr =
7735-
match expr with
7736-
| Let (_enumerableVar, RangeInt32Step g (startExpr, step, finishExpr), _,
7737-
Let (_enumeratorVar, _getEnumExpr, spBind,
7738-
TryFinally (WhileLoopForCompiledForEachExpr (_guardExpr, Let (elemVar,_currentExpr,_,bodyExpr), m), _cleanupExpr))) ->
7748+
let (|ExtractTypeOfExpr|_|) g expr = Some (tyOfExpr g expr)
77397749

7740-
let spForLoop = match spBind with SequencePointAtBinding(spStart) -> SequencePointAtForLoop(spStart) | _ -> NoSequencePointAtForLoop
7750+
type OptimizeForExpressionOptions = OptimizeIntRangesOnly | OptimizeAllForExpressions
77417751

7742-
Some(spForLoop,elemVar,startExpr,step,finishExpr,bodyExpr,m)
7743-
| _ ->
7744-
None
7752+
let DetectAndOptimizeForExpression g option expr =
7753+
match expr with
7754+
| Let (_, enumerableExpr, _,
7755+
Let (_, _, enumeratorBind,
7756+
TryFinally (WhileLoopForCompiledForEachExpr (_, Let (elemVar,_,_,bodyExpr), _), _))) ->
77457757

7746-
let DetectFastIntegerForLoops g expr =
7747-
match expr with
7748-
| CompiledInt32ForEachExprWithKnownStep g (spForLoop,elemVar,startExpr,step,finishExpr,bodyExpr,m)
7749-
// fast for loops only allow steps 1 and -1 steps at the moment
7750-
when step = 1 || step = -1 ->
7758+
let m = enumerableExpr.Range
7759+
let mBody = bodyExpr.Range
7760+
7761+
let spForLoop,mForLoop = match enumeratorBind with SequencePointAtBinding(spStart) -> SequencePointAtForLoop(spStart),spStart | _ -> NoSequencePointAtForLoop,m
7762+
let spWhileLoop = match enumeratorBind with SequencePointAtBinding(spStart) -> SequencePointAtWhileLoop(spStart)| _ -> NoSequencePointAtWhileLoop
7763+
7764+
match option,enumerableExpr with
7765+
| _,RangeInt32Step g (startExpr, step, finishExpr) ->
7766+
match step with
7767+
| -1 | 1 ->
77517768
mkFastForLoop g (spForLoop,m,elemVar,startExpr,(step = 1),finishExpr,bodyExpr)
7752-
| _ -> expr
7769+
| _ -> expr
7770+
| OptimizeAllForExpressions,ExtractTypeOfExpr g ty when isStringTy g ty ->
7771+
// type is string, optimize for expression as:
7772+
// let $str = enumerable
7773+
// for $idx in 0..(str.Length - 1) do
7774+
// let elem = str.[idx]
7775+
// body elem
7776+
7777+
let strVar ,strExpr = mkCompGenLocal m "str" ty
7778+
let idxVar ,idxExpr = mkCompGenLocal m "idx" g.int32_ty
7779+
7780+
let lengthExpr = mkGetStringLength g m strExpr
7781+
let charExpr = mkGetStringChar g m strExpr idxExpr
7782+
7783+
let startExpr = mkZero g m
7784+
let finishExpr = mkDecr g mForLoop lengthExpr
7785+
let loopItemExpr = mkCoerceIfNeeded g elemVar.Type g.char_ty charExpr // for compat reasons, loop item over string is sometimes object, not char
7786+
let bodyExpr = mkCompGenLet mBody elemVar loopItemExpr bodyExpr
7787+
let forExpr = mkFastForLoop g (spForLoop,m,idxVar,startExpr,true,finishExpr,bodyExpr)
7788+
let expr = mkCompGenLet m strVar enumerableExpr forExpr
77537789

7790+
expr
7791+
| OptimizeAllForExpressions,ExtractTypeOfExpr g ty when isListTy g ty ->
7792+
// type is list, optimize for expression as:
7793+
// let mutable $currentVar = listExpr
7794+
// let mutable $nextVar = $tailOrNull
7795+
// while $guardExpr do
7796+
// let i = $headExpr
7797+
// bodyExpr ()
7798+
// $current <- $next
7799+
// $next <- $tailOrNull
7800+
7801+
let IndexHead = 0
7802+
let IndexTail = 1
7803+
7804+
let currentVar ,currentExpr = mkMutableCompGenLocal m "current" ty
7805+
let nextVar ,nextExpr = mkMutableCompGenLocal m "next" ty
7806+
let elemTy = destListTy g ty
7807+
7808+
let guardExpr = mkNonNullTest g m nextExpr
7809+
let headOrDefaultExpr = mkUnionCaseFieldGetUnproven(currentExpr,g.cons_ucref,[elemTy],IndexHead,m)
7810+
let tailOrNullExpr = mkUnionCaseFieldGetUnproven(currentExpr,g.cons_ucref,[elemTy],IndexTail,mBody)
7811+
let bodyExpr =
7812+
mkCompGenLet m elemVar headOrDefaultExpr
7813+
(mkCompGenSequential mBody
7814+
bodyExpr
7815+
(mkCompGenSequential mBody
7816+
(mkValSet mBody (mkLocalValRef currentVar) nextExpr)
7817+
(mkValSet mBody (mkLocalValRef nextVar) tailOrNullExpr)
7818+
)
7819+
)
7820+
let whileExpr = mkWhile g (spWhileLoop, WhileLoopForCompiledForEachExprMarker, guardExpr, bodyExpr, m)
7821+
7822+
let expr =
7823+
mkCompGenLet m currentVar enumerableExpr
7824+
(mkCompGenLet m nextVar tailOrNullExpr whileExpr)
7825+
7826+
expr
7827+
| _ -> expr
7828+
| _ -> expr
77547829

77557830
// Used to remove Expr.Link for inner expressions in pattern matches
7756-
let (|InnerExprPat|) expr = stripExpr expr
7831+
let (|InnerExprPat|) expr = stripExpr expr

src/fsharp/TastOps.fsi

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,10 +949,13 @@ type TypeDefMetadata =
949949
val metadataOfTycon : Tycon -> TypeDefMetadata
950950
val metadataOfTy : TcGlobals -> TType -> TypeDefMetadata
951951

952+
val isStringTy : TcGlobals -> TType -> bool
953+
val isListTy : TcGlobals -> TType -> bool
952954
val isILAppTy : TcGlobals -> TType -> bool
953955
val isArrayTy : TcGlobals -> TType -> bool
954956
val isArray1DTy : TcGlobals -> TType -> bool
955957
val destArrayTy : TcGlobals -> TType -> TType
958+
val destListTy : TcGlobals -> TType -> TType
956959

957960
val mkArrayTy : TcGlobals -> int -> TType -> range -> TType
958961
val isArrayTyconRef : TcGlobals -> TyconRef -> bool
@@ -1373,7 +1376,9 @@ val (|SpecialComparableHeadType|_|) : TcGlobals -> TType -> TType list option
13731376
val (|SpecialEquatableHeadType|_|) : TcGlobals -> TType -> TType list option
13741377
val (|SpecialNotEquatableHeadType|_|) : TcGlobals -> TType -> unit option
13751378

1376-
val DetectFastIntegerForLoops : TcGlobals -> Expr -> Expr
1379+
type OptimizeForExpressionOptions = OptimizeIntRangesOnly | OptimizeAllForExpressions
1380+
val DetectAndOptimizeForExpression : TcGlobals -> OptimizeForExpressionOptions -> Expr -> Expr
1381+
13771382
val TryEliminateDesugaredConstants : TcGlobals -> range -> Const -> Expr option
13781383

13791384
val ValIsExplicitImpl : TcGlobals -> Val -> bool

src/fsharp/TcGlobals.fs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ type public TcGlobals =
242242
system_Exception_typ : TType
243243
system_Int32_typ : TType
244244
system_String_typ : TType
245+
system_String_tcref : TyconRef
245246
system_Type_typ : TType
246247
system_TypedReference_tcref : TyconRef option
247248
system_ArgIterator_tcref : TyconRef option
@@ -478,10 +479,12 @@ type public TcGlobals =
478479
istype_fast_info : IntrinsicValRef
479480

480481
dispose_info : IntrinsicValRef
482+
483+
getstring_info : IntrinsicValRef
481484

482485
range_op_vref : ValRef
486+
range_step_op_vref : ValRef
483487
range_int32_op_vref : ValRef
484-
//range_step_op_vref : ValRef
485488
array_get_vref : ValRef
486489
array2D_get_vref : ValRef
487490
array3D_get_vref : ValRef
@@ -639,6 +642,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
639642

640643
let bool_ty = mkNonGenericTy bool_tcr
641644
let int_ty = mkNonGenericTy int_tcr
645+
let char_ty = mkNonGenericTy char_tcr
642646
let obj_ty = mkNonGenericTy obj_tcr
643647
let string_ty = mkNonGenericTy string_tcr
644648
let byte_ty = mkNonGenericTy byte_tcr
@@ -788,7 +792,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
788792
fslib_MFQueryRunExtensionsHighPriority_nleref
789793

790794
fslib_MFSeqModule_nleref
791-
fslib_MFListModule_nleref
795+
fslib_MFListModule_nleref
792796
fslib_MFArrayModule_nleref
793797
fslib_MFArray2DModule_nleref
794798
fslib_MFArray3DModule_nleref
@@ -890,6 +894,8 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
890894

891895
let dispose_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "Dispose" ,None ,None ,[vara], ([[varaTy]],unit_ty))
892896

897+
let getstring_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "GetString" ,None ,None ,[], ([[string_ty];[int_ty]],char_ty))
898+
893899
let reference_equality_inner_info = makeIntrinsicValRef(fslib_MFHashCompare_nleref, "PhysicalEqualityIntrinsic" ,None ,None ,[vara], mk_rel_sig varaTy)
894900

895901
let bitwise_or_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "op_BitwiseOr" ,None ,None ,[vara], mk_binop_ty varaTy)
@@ -914,6 +920,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
914920
let typedefof_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "typedefof" ,None ,Some "TypeDefOf",[vara], ([],system_Type_typ))
915921
let enum_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "enum" ,None ,Some "ToEnum" ,[vara], ([[int_ty]],varaTy))
916922
let range_op_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "op_Range" ,None ,None ,[vara], ([[varaTy];[varaTy]],mkSeqTy varaTy))
923+
let range_step_op_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "op_RangeStep" ,None ,None ,[vara;varb],([[varaTy];[varbTy];[varaTy]],mkSeqTy varaTy))
917924
let range_int32_op_info = makeIntrinsicValRef(fslib_MFOperatorIntrinsics_nleref, "RangeInt32" ,None ,None ,[], ([[int_ty];[int_ty];[int_ty]],mkSeqTy int_ty))
918925
let array2D_get_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "GetArray2D" ,None ,None ,[vara], ([[mkArrayType 2 varaTy];[int_ty]; [int_ty]],varaTy))
919926
let array3D_get_info = makeIntrinsicValRef(fslib_MFIntrinsicFunctions_nleref, "GetArray3D" ,None ,None ,[vara], ([[mkArrayType 3 varaTy];[int_ty]; [int_ty]; [int_ty]],varaTy))
@@ -938,6 +945,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
938945
// Lazy\Value for > 4.0
939946
makeIntrinsicValRef(fslib_MFLazyExtensions_nleref, "Force" ,Some "Lazy`1" ,None ,[vara], ([[mkLazyTy varaTy]; []], varaTy))
940947
let lazy_create_info = makeIntrinsicValRef(fslib_MFLazyExtensions_nleref, "Create" ,Some "Lazy`1" ,None ,[vara], ([[unit_ty --> varaTy]], mkLazyTy varaTy))
948+
941949
let seq_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "seq" ,None ,Some "CreateSequence" ,[vara], ([[mkSeqTy varaTy]], mkSeqTy varaTy))
942950
let splice_expr_info = makeIntrinsicValRef(fslib_MFExtraTopLevelOperators_nleref, "op_Splice" ,None ,None ,[vara], ([[mkQuotedExprTy varaTy]], varaTy))
943951
let splice_raw_expr_info = makeIntrinsicValRef(fslib_MFExtraTopLevelOperators_nleref, "op_SpliceUntyped" ,None ,None ,[vara], ([[mkRawQuotedExprTy]], varaTy))
@@ -1083,6 +1091,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
10831091
system_Enum_typ = mkSysNonGenericTy sys "Enum"
10841092
system_Exception_typ = mkSysNonGenericTy sys "Exception"
10851093
system_String_typ = mkSysNonGenericTy sys "String"
1094+
system_String_tcref = mkSysTyconRef sys "String"
10861095
system_Int32_typ = mkSysNonGenericTy sys "Int32"
10871096
system_Type_typ = system_Type_typ
10881097
system_TypedReference_tcref = if ilg.traits.TypedReferenceTypeScopeRef.IsSome then Some(mkSysTyconRef sys "TypedReference") else None
@@ -1355,8 +1364,8 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
13551364
enum_vref = ValRefForIntrinsic enum_info
13561365
enumOfValue_vref = ValRefForIntrinsic enumOfValue_info
13571366
range_op_vref = ValRefForIntrinsic range_op_info
1367+
range_step_op_vref = ValRefForIntrinsic range_step_op_info
13581368
range_int32_op_vref = ValRefForIntrinsic range_int32_op_info
1359-
//range_step_op_vref = ValRefForIntrinsic range_step_op_info
13601369
array_length_info = array_length_info
13611370
array_get_vref = ValRefForIntrinsic array_get_info
13621371
array2D_get_vref = ValRefForIntrinsic array2D_get_info
@@ -1394,6 +1403,7 @@ let mkTcGlobals (compilingFslib,sysCcu,ilg,fslibCcu,directoryToResolveRelativePa
13941403
get_generic_er_equality_comparer_info = get_generic_er_equality_comparer_info
13951404
get_generic_per_equality_comparer_info = get_generic_per_equality_comparer_info
13961405
dispose_info = dispose_info
1406+
getstring_info = getstring_info
13971407
unbox_fast_info = unbox_fast_info
13981408
istype_info = istype_info
13991409
istype_fast_info = istype_fast_info
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@if "%_echo%"=="" echo off
2+
3+
call %~d0%~p0..\..\single-test-build.bat
4+
5+
exit /b %ERRORLEVEL%
6+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@if "%_echo%"=="" echo off
2+
3+
call %~d0%~p0..\..\single-test-run.bat
4+
5+
exit /b %ERRORLEVEL%
6+
7+

0 commit comments

Comments
 (0)