Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitf2de3ba

Browse files
TIHanKevinRansom
authored andcommitted
Optimizing string concatenations to use String.Concat overloads (dotnet#5570)
* Optimize String.Concat calls* Small refactor* Added test* Another test* Fixing tests* using /noca* Fixing test* Fixing tests again* Minor adjustments from feedback* Quick fix* More tests* Updated baselines + more tests* Added better way to test IL directly in unit tests* Moved StringConcat.fs tests* Removed bad file* Using FSharpChecker directly* Added more tests* Comparing lines for tests* Better IL check output* Small cleanup* Fixed test
1 parente709f52 commitf2de3ba

File tree

7 files changed

+1077
-21
lines changed

7 files changed

+1077
-21
lines changed

‎src/fsharp/Optimizer.fs‎

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,7 +1764,15 @@ let TryDetectQueryQuoteAndRun cenv (expr:Expr) =
17641764
//printfn "Not eliminating because no Run found"
17651765
None
17661766

1767-
1767+
letIsSystemStringConcatOverload(methRef:ILMethodRef)=
1768+
methRef.Name="Concat"&& methRef.DeclaringTypeRef.FullName="System.String"&&
1769+
methRef.ReturnType.BasicQualifiedName="System.String"&&
1770+
methRef.ArgTypes|> List.forall(fun ilty-> ilty.BasicQualifiedName="System.String")
1771+
1772+
letIsSystemStringConcatArray(methRef:ILMethodRef)=
1773+
methRef.Name="Concat"&& methRef.DeclaringTypeRef.FullName="System.String"&&
1774+
methRef.ReturnType.BasicQualifiedName="System.String"&&
1775+
methRef.ArgTypes.Length=1&& methRef.ArgTypes.Head.BasicQualifiedName="System.String[]"
17681776

17691777
//-------------------------------------------------------------------------
17701778
// The traversal
@@ -1824,7 +1832,6 @@ let rec OptimizeExpr cenv (env:IncrementalOptimizationEnv) expr =
18241832
assert("unexpected reclink"="")
18251833
failwith"Unexpected reclink"
18261834

1827-
18281835
//-------------------------------------------------------------------------
18291836
// Optimize/analyze an object expression
18301837
//-------------------------------------------------------------------------
@@ -1874,9 +1881,54 @@ and OptimizeInterfaceImpl cenv env baseValOpt (ty, overrides) =
18741881
Info=UnknownValue}
18751882

18761883
//-------------------------------------------------------------------------
1877-
//Optimize/analyze an application of an intrinsic operator to arguments
1884+
//Make and optimize String.Concat calls
18781885
//-------------------------------------------------------------------------
18791886

1887+
andMakeOptimizedSystemStringConcatCall cenv env m args=
1888+
let recoptimizeArg e accArgs=
1889+
match e, accArgswith
1890+
| Expr.Op(TOp.ILCall(_,_,_,_,_,_,_, methRef,_,_,_),_,[ Expr.Op(TOp.Array,_, args,_)],_),_when IsSystemStringConcatArray methRef->
1891+
optimizeArgs args accArgs
1892+
1893+
| Expr.Op(TOp.ILCall(_,_,_,_,_,_,_, methRef,_,_,_),_, args,_),_when IsSystemStringConcatOverload methRef->
1894+
optimizeArgs args accArgs
1895+
1896+
// Optimize string constants, e.g. "1" + "2" will turn into "12"
1897+
| Expr.Const(Const.String str1,_,_), Expr.Const(Const.String str2,_,_):: accArgs->
1898+
mkString cenv.g m(str1+ str2):: accArgs
1899+
1900+
| arg,_-> arg:: accArgs
1901+
1902+
andoptimizeArgs args accArgs=
1903+
(args, accArgs)
1904+
||> List.foldBack(fun arg accArgs-> optimizeArg arg accArgs)
1905+
1906+
letargs= optimizeArgs args[]
1907+
1908+
lete=
1909+
match argswith
1910+
|[ arg]->
1911+
arg
1912+
|[ arg1; arg2]->
1913+
mkStaticCall_String_Concat2 cenv.g m arg1 arg2
1914+
|[ arg1; arg2; arg3]->
1915+
mkStaticCall_String_Concat3 cenv.g m arg1 arg2 arg3
1916+
|[ arg1; arg2; arg3; arg4]->
1917+
mkStaticCall_String_Concat4 cenv.g m arg1 arg2 arg3 arg4
1918+
| args->
1919+
letarg= mkArray(cenv.g.string_ty, args, m)
1920+
mkStaticCall_String_Concat_Array cenv.g m arg
1921+
1922+
match ewith
1923+
| Expr.Op(TOp.ILCall(_,_,_,_,_,_,_, methRef,_,_,_)as op, tyargs, args, m)when IsSystemStringConcatOverload methRef|| IsSystemStringConcatArray methRef->
1924+
OptimizeExprOpReductions cenv env(op, tyargs, args, m)
1925+
|_->
1926+
OptimizeExpr cenv env e
1927+
1928+
//-------------------------------------------------------------------------
1929+
// Optimize/analyze an application of an intrinsic operator to arguments
1930+
//-------------------------------------------------------------------------
1931+
18801932
andOptimizeExprOp cenv env(op,tyargs,args,m)=
18811933

18821934
// Special cases
@@ -1940,22 +1992,30 @@ and OptimizeExprOp cenv env (op, tyargs, args, m) =
19401992
// if the types match up.
19411993
| TOp.ILAsm([],[ty]),_,[a]when typeEquiv cenv.g(tyOfExpr cenv.g a) ty-> OptimizeExpr cenv env a
19421994

1943-
|_->
1944-
// Reductions
1945-
letargs',arginfos= OptimizeExprsThenConsiderSplits cenv env args
1946-
letknownValue=
1947-
match op, arginfoswith
1948-
| TOp.ValFieldGet(rf),[e1info]-> TryOptimizeRecordFieldGet cenv env(e1info, rf, tyargs, m)
1949-
| TOp.TupleFieldGet(tupInfo, n),[e1info]-> TryOptimizeTupleFieldGet cenv env(tupInfo, e1info, tyargs, n, m)
1950-
| TOp.UnionCaseFieldGet(cspec, n),[e1info]-> TryOptimizeUnionCaseGet cenv env(e1info, cspec, tyargs, n, m)
1951-
|_-> None
1952-
match knownValuewith
1953-
| Some valu->
1954-
match TryOptimizeVal cenv env(false, valu, m)with
1955-
| Some res-> OptimizeExpr cenv env res(* discard e1 since guard ensures it has no effects*)
1956-
| None-> OptimizeExprOpFallback cenv env(op, tyargs, args', m) arginfos valu
1957-
| None-> OptimizeExprOpFallback cenv env(op, tyargs, args', m) arginfos UnknownValue
1995+
// Optimize calls when concatenating strings, e.g. "1" + "2" + "3" + "4" .. etc.
1996+
| TOp.ILCall(_,_,_,_,_,_,_, methRef,_,_,_),_,[ Expr.Op(TOp.Array,_, args,_)]when IsSystemStringConcatArray methRef->
1997+
MakeOptimizedSystemStringConcatCall cenv env m args
1998+
| TOp.ILCall(_,_,_,_,_,_,_, methRef,_,_,_),_, argswhen IsSystemStringConcatOverload methRef->
1999+
MakeOptimizedSystemStringConcatCall cenv env m args
19582000

2001+
|_->
2002+
// Reductions
2003+
OptimizeExprOpReductions cenv env(op, tyargs, args, m)
2004+
2005+
andOptimizeExprOpReductions cenv env(op,tyargs,args,m)=
2006+
letargs',arginfos= OptimizeExprsThenConsiderSplits cenv env args
2007+
letknownValue=
2008+
match op, arginfoswith
2009+
| TOp.ValFieldGet(rf),[e1info]-> TryOptimizeRecordFieldGet cenv env(e1info, rf, tyargs, m)
2010+
| TOp.TupleFieldGet(tupInfo, n),[e1info]-> TryOptimizeTupleFieldGet cenv env(tupInfo, e1info, tyargs, n, m)
2011+
| TOp.UnionCaseFieldGet(cspec, n),[e1info]-> TryOptimizeUnionCaseGet cenv env(e1info, cspec, tyargs, n, m)
2012+
|_-> None
2013+
match knownValuewith
2014+
| Some valu->
2015+
match TryOptimizeVal cenv env(false, valu, m)with
2016+
| Some res-> OptimizeExpr cenv env res(* discard e1 since guard ensures it has no effects*)
2017+
| None-> OptimizeExprOpFallback cenv env(op, tyargs, args', m) arginfos valu
2018+
| None-> OptimizeExprOpFallback cenv env(op, tyargs, args', m) arginfos UnknownValue
19592019

19602020
andOptimizeExprOpFallback cenv env(op,tyargs,args',m)arginfos valu=
19612021
// The generic case - we may collect information, but the construction/projection doesn't disappear
@@ -2682,7 +2742,7 @@ and TryInlineApplication cenv env finfo (tyargs: TType list, args: Expr list, m)
26822742
// Inlining: beta reducing
26832743
letexpr'= MakeApplicationAndBetaReduce cenv.g(f2', f2ty,[tyargs], args', m)
26842744
// Inlining: reoptimizing
2685-
Some(OptimizeExpr cenv{envwith dontInline= Zset.add lambdaId env.dontInline} expr')
2745+
Some(OptimizeExpr cenv{envwith dontInline= Zset.add lambdaId env.dontInline} expr')
26862746

26872747
|_-> None
26882748

‎src/fsharp/TastOps.fs‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6366,6 +6366,18 @@ let mkIsInst ty e m = mkAsmExpr ([ isinst ], [ty], [e], [ ty ], m)
63666366
letmspec_Type_GetTypeFromHandle(g:TcGlobals)= IL.mkILNonGenericStaticMethSpecInTy(g.ilg.typ_Type,"GetTypeFromHandle",[g.iltyp_RuntimeTypeHandle], g.ilg.typ_Type)
63676367
letmspec_String_Length(g:TcGlobals)= mkILNonGenericInstanceMethSpecInTy(g.ilg.typ_String,"get_Length",[], g.ilg.typ_Int32)
63686368

6369+
letmspec_String_Concat2(g:TcGlobals)=
6370+
mkILNonGenericStaticMethSpecInTy(g.ilg.typ_String,"Concat",[ g.ilg.typ_String; g.ilg.typ_String], g.ilg.typ_String)
6371+
6372+
letmspec_String_Concat3(g:TcGlobals)=
6373+
mkILNonGenericStaticMethSpecInTy(g.ilg.typ_String,"Concat",[ g.ilg.typ_String; g.ilg.typ_String; g.ilg.typ_String], g.ilg.typ_String)
6374+
6375+
letmspec_String_Concat4(g:TcGlobals)=
6376+
mkILNonGenericStaticMethSpecInTy(g.ilg.typ_String,"Concat",[ g.ilg.typ_String; g.ilg.typ_String; g.ilg.typ_String; g.ilg.typ_String], g.ilg.typ_String)
6377+
6378+
letmspec_String_Concat_Array(g:TcGlobals)=
6379+
mkILNonGenericStaticMethSpecInTy(g.ilg.typ_String,"Concat",[ mkILArr1DTy g.ilg.typ_String], g.ilg.typ_String)
6380+
63696381
letfspec_Missing_Value(g:TcGlobals)= IL.mkILFieldSpecInTy(g.iltyp_Missing,"Value", g.iltyp_Missing)
63706382

63716383
letmkInitializeArrayMethSpec(g:TcGlobals)=
@@ -6587,6 +6599,21 @@ let mkGetStringLength g m e =
65876599
/// ILCall(useCallvirt, isProtected, valu, newobj, valUseFlags, isProp, noTailCall, mref, actualTypeInst, actualMethInst, retTy)
65886600
Expr.Op(TOp.ILCall(false,false,false,false, ValUseFlag.NormalValUse,true,false, mspec.MethodRef,[],[],[g.int32_ty]),[],[e], m)
65896601

6602+
letmkStaticCall_String_Concat2 g m arg1 arg2=
6603+
letmspec= mspec_String_Concat2 g
6604+
Expr.Op(TOp.ILCall(false,false,false,false, ValUseFlag.NormalValUse,false,false, mspec.MethodRef,[],[],[g.string_ty]),[],[arg1; arg2], m)
6605+
6606+
letmkStaticCall_String_Concat3 g m arg1 arg2 arg3=
6607+
letmspec= mspec_String_Concat3 g
6608+
Expr.Op(TOp.ILCall(false,false,false,false, ValUseFlag.NormalValUse,false,false, mspec.MethodRef,[],[],[g.string_ty]),[],[arg1; arg2; arg3], m)
6609+
6610+
letmkStaticCall_String_Concat4 g m arg1 arg2 arg3 arg4=
6611+
letmspec= mspec_String_Concat4 g
6612+
Expr.Op(TOp.ILCall(false,false,false,false, ValUseFlag.NormalValUse,false,false, mspec.MethodRef,[],[],[g.string_ty]),[],[arg1; arg2; arg3; arg4], m)
6613+
6614+
letmkStaticCall_String_Concat_Array g m arg=
6615+
letmspec= mspec_String_Concat_Array g
6616+
Expr.Op(TOp.ILCall(false,false,false,false, ValUseFlag.NormalValUse,false,false, mspec.MethodRef,[],[],[g.string_ty]),[],[arg], m)
65906617

65916618
// Quotations can't contain any IL.
65926619
// As a result, we aim to get rid of all IL generation in the typechecker and pattern match

‎src/fsharp/TastOps.fsi‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,11 @@ val mkCallNewQuerySource : TcGlobals -> range -> TType -> TType -> Expr -> Expr
13861386

13871387
val mkArray: TType* Exprs* range-> Expr
13881388

1389+
val mkStaticCall_String_Concat2: TcGlobals-> range-> Expr-> Expr-> Expr
1390+
val mkStaticCall_String_Concat3: TcGlobals-> range-> Expr-> Expr-> Expr-> Expr
1391+
val mkStaticCall_String_Concat4: TcGlobals-> range-> Expr-> Expr-> Expr-> Expr-> Expr
1392+
val mkStaticCall_String_Concat_Array: TcGlobals-> range-> Expr-> Expr
1393+
13891394
//-------------------------------------------------------------------------
13901395
// operations primarily associated with the optimization to fix
13911396
// up loops to generate .NET code that does not include array bound checks

‎tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
<ReferenceInclude="System.Numerics"Condition="'$(TargetDotnetProfile)' == 'net40'" />
4545
<ReferenceInclude="System.Core" />
4646
<ReferenceInclude="System.ValueTuple">
47-
<HintPath>..\..\..\packages\System.ValueTuple.$(SystemValueTuplePackageVersion)\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
47+
<HintPath>..\..\..\packages\System.ValueTuple.$(SystemValueTuplePackageVersion)\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
4848
</Reference>
4949
</ItemGroup>
5050
<ItemGroup>
@@ -54,6 +54,8 @@
5454
<CompileInclude="HashIfExpression.fs" />
5555
<CompileInclude="ProductVersion.fs" />
5656
<CompileInclude="EditDistance.fs" />
57+
<CompileInclude="ILHelpers.fs" />
58+
<CompileInclude="Language\StringConcat.fs" />
5759
</ItemGroup>
5860
<ItemGroup>
5961
<ProjectReferenceInclude="$(FSharpSourcesRoot)\fsharp\FSharp.Compiler.Private\FSharp.Compiler.Private.fsproj">
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespaceFSharp.Compiler.UnitTests
4+
5+
openSystem
6+
openSystem.IO
7+
openSystem.Diagnostics
8+
9+
openNUnit.Framework
10+
11+
openMicrosoft.FSharp.Compiler.SourceCodeServices
12+
13+
moduleILChecker=
14+
15+
letchecker= FSharpChecker.Create()
16+
17+
letprivate(++)a b= Path.Combine(a,b)
18+
19+
letprivategetfullpath workDir path=
20+
letrooted=
21+
if Path.IsPathRooted(path)then path
22+
else Path.Combine(workDir, path)
23+
rooted|> Path.GetFullPath
24+
25+
letprivatefileExists workDir path=
26+
if path|> getfullpath workDir|> File.Existsthen Some pathelse None
27+
28+
letprivaterequireFile nm=
29+
if fileExists__SOURCE_DIRECTORY__ nm|> Option.isSomethen nmelse failwith(sprintf"couldn't find%s. Running 'build test' once might solve this issue" nm)
30+
31+
letprivateexec exe args=
32+
letstartInfo= ProcessStartInfo(exe, String.concat"" args)
33+
startInfo.RedirectStandardError<-true
34+
startInfo.UseShellExecute<-false
35+
use p= Process.Start(startInfo)
36+
p.WaitForExit()
37+
p.StandardError.ReadToEnd(), p.ExitCode
38+
39+
/// Compile the source and check to see if the expected IL exists.
40+
/// The first line of each expected IL string is found first.
41+
letcheck source expectedIL=
42+
letSCRIPT_ROOT=__SOURCE_DIRECTORY__
43+
letpackagesDir= SCRIPT_ROOT++".."++".."++"packages"
44+
letIs64BitOperatingSystem= sizeof<nativeint>=8
45+
letarchitectureMoniker=if Is64BitOperatingSystemthen"x64"else"x86"
46+
letildasmExe= requireFile(packagesDir++("runtime.win-"+ architectureMoniker+".Microsoft.NETCore.ILDAsm.2.0.3")++"runtimes"++("win-"+ architectureMoniker)++"native"++"ildasm.exe")
47+
letcoreclrDll= requireFile(packagesDir++("runtime.win-"+ architectureMoniker+".Microsoft.NETCore.Runtime.CoreCLR.2.0.3")++"runtimes"++("win-"+ architectureMoniker)++"native"++"coreclr.dll")
48+
49+
lettmp= Path.GetTempFileName()
50+
lettmpFs= Path.ChangeExtension(tmp,".fs")
51+
lettmpDll= Path.ChangeExtension(tmp,".dll")
52+
lettmpIL= Path.ChangeExtension(tmp,".il")
53+
54+
let mutableerrorMsgOpt= None
55+
try
56+
// ildasm requires coreclr.dll to run which has already been restored to the packages directory
57+
File.Copy(coreclrDll, Path.GetDirectoryName(ildasmExe)++"coreclr.dll", overwrite=true)
58+
59+
File.WriteAllText(tmpFs, source)
60+
61+
leterrors,exitCode= checker.Compile([|"fsc.exe";"--optimize+";"-o"; tmpDll;"-a"; tmpFs|])|> Async.RunSynchronously
62+
leterrors=
63+
String.concat"\n"(errors|> Array.map(fun x-> x.Message))
64+
65+
if exitCode=0then
66+
exec ildasmExe[ sprintf"%s /out=%s" tmpDll tmpIL]|> ignore
67+
68+
lettext= File.ReadAllText(tmpIL)
69+
letblockComments=@"/\*(.*?)\*/"
70+
letlineComments=@"//(.*?)\r?\n"
71+
letstrings=@"""((\\[^\n]|[^""\n])*)"""
72+
letverbatimStrings=@"@(""[^""]*"")+"
73+
lettextNoComments=
74+
System.Text.RegularExpressions.Regex.Replace(text,
75+
blockComments+"|"+ lineComments+"|"+ strings+"|"+ verbatimStrings,
76+
(fun me->
77+
if(me.Value.StartsWith("/*")|| me.Value.StartsWith("//"))then
78+
if me.Value.StartsWith("//")then Environment.NewLineelse String.Empty
79+
else
80+
me.Value), System.Text.RegularExpressions.RegexOptions.Singleline)
81+
82+
expectedIL
83+
|> List.iter(fun(ilCode: string)->
84+
letexpectedLines= ilCode.Split('\n')
85+
letstartIndex= textNoComments.IndexOf(expectedLines.[0])
86+
if startIndex=-1|| textNoComments.Length< startIndex+ ilCode.Lengththen
87+
errorMsgOpt<- Some("==EXPECTED CONTAINS==\n"+ ilCode+"\n")
88+
else
89+
leterrors= ResizeArray()
90+
letactualLines= textNoComments.Substring(startIndex, textNoComments.Length- startIndex).Split('\n')
91+
for i=0to expectedLines.Length-1do
92+
letexpected= expectedLines.[i].Trim()
93+
letactual= actualLines.[i].Trim()
94+
if expected<> actualthen
95+
errors.Add(sprintf"\n==\nName:%s\n\nExpected:\t%s\nActual:\t\t%s\n==" actualLines.[0] expected actual)
96+
97+
if errors.Count>0then
98+
letmsg= String.concat"\n" errors+"\n\n\n==EXPECTED==\n"+ ilCode+"\n"
99+
errorMsgOpt<- Some(msg+"\n\n\n==ACTUAL==\n"+ String.Join("\n", actualLines,0, expectedLines.Length))
100+
)
101+
102+
match errorMsgOptwith
103+
| Some(msg)-> errorMsgOpt<- Some(msg+"\n\n\n==ENTIRE ACTUAL==\n"+ textNoComments)
104+
|_->()
105+
else
106+
errorMsgOpt<- Some(errors)
107+
finally
108+
try File.Delete(tmp)with|_->()
109+
try File.Delete(tmpFs)with|_->()
110+
try File.Delete(tmpDll)with|_->()
111+
try File.Delete(tmpIL)with|_->()
112+
113+
match errorMsgOptwith
114+
| Some(errorMsg)->
115+
Assert.Fail(errorMsg)
116+
|_->()
117+

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp