Skip to content

Commit af0015e

Browse files
authored
Reflection free code gen (#12960)
Added --reflectionfree compiler flag to avoid %A string formatting
1 parent e0927a2 commit af0015e

38 files changed

+424
-169
lines changed

src/Compiler/Checking/CheckFormatStrings.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,9 @@ let parseFormatStringInternal
444444
parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, startFragCol) fragments
445445

446446
| 'A' ->
447+
if g.useReflectionFreeCodeGen then
448+
failwith (FSComp.SR.forPercentAInReflectionFreeCode())
449+
447450
match info.numPrefixIfPos with
448451
| None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic
449452
| Some '+' ->

src/Compiler/CodeGen/IlxGen.fs

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,9 @@ type IlxGenOptions =
288288
/// storage, even though 'it' is not logically mutable
289289
isInteractiveItExpr: bool
290290

291+
/// Suppress ToString emit
292+
useReflectionFreeCodeGen: bool
293+
291294
/// Whenever possible, use callvirt instead of call
292295
alwaysCallVirt: bool
293296
}
@@ -10449,64 +10452,65 @@ and GenPrintingMethod cenv eenv methName ilThisTy m =
1044910452
let g = cenv.g
1045010453

1045110454
[
10452-
match (eenv.valsInScope.TryFind g.sprintf_vref.Deref, eenv.valsInScope.TryFind g.new_format_vref.Deref) with
10453-
| Some (Lazy (Method (_, _, sprintfMethSpec, _, _, _, _, _, _, _, _, _))),
10454-
Some (Lazy (Method (_, _, newFormatMethSpec, _, _, _, _, _, _, _, _, _))) ->
10455-
// The type returned by the 'sprintf' call
10456-
let funcTy = EraseClosures.mkILFuncTy cenv.ilxPubCloEnv ilThisTy g.ilg.typ_String
10457-
10458-
// Give the instantiation of the printf format object, i.e. a Format`5 object compatible with StringFormat<ilThisTy>
10459-
let newFormatMethSpec =
10460-
mkILMethSpec (
10461-
newFormatMethSpec.MethodRef,
10462-
AsObject,
10463-
[ // 'T -> string'
10464-
funcTy
10465-
// rest follow from 'StringFormat<T>'
10466-
GenUnitTy cenv eenv m
10467-
g.ilg.typ_String
10468-
g.ilg.typ_String
10469-
ilThisTy
10470-
],
10471-
[]
10472-
)
10473-
10474-
// Instantiate with our own type
10475-
let sprintfMethSpec =
10476-
mkILMethSpec (sprintfMethSpec.MethodRef, AsObject, [], [ funcTy ])
10455+
if not g.useReflectionFreeCodeGen then
10456+
match (eenv.valsInScope.TryFind g.sprintf_vref.Deref, eenv.valsInScope.TryFind g.new_format_vref.Deref) with
10457+
| Some (Lazy (Method (_, _, sprintfMethSpec, _, _, _, _, _, _, _, _, _))),
10458+
Some (Lazy (Method (_, _, newFormatMethSpec, _, _, _, _, _, _, _, _, _))) ->
10459+
// The type returned by the 'sprintf' call
10460+
let funcTy = EraseClosures.mkILFuncTy cenv.ilxPubCloEnv ilThisTy g.ilg.typ_String
10461+
10462+
// Give the instantiation of the printf format object, i.e. a Format`5 object compatible with StringFormat<ilThisTy>
10463+
let newFormatMethSpec =
10464+
mkILMethSpec (
10465+
newFormatMethSpec.MethodRef,
10466+
AsObject,
10467+
[ // 'T -> string'
10468+
funcTy
10469+
// rest follow from 'StringFormat<T>'
10470+
GenUnitTy cenv eenv m
10471+
g.ilg.typ_String
10472+
g.ilg.typ_String
10473+
ilThisTy
10474+
],
10475+
[]
10476+
)
1047710477

10478-
// Here's the body of the method. Call printf, then invoke the function it returns
10479-
let callInstrs =
10480-
EraseClosures.mkCallFunc
10481-
cenv.ilxPubCloEnv
10482-
(fun _ -> 0us)
10483-
eenv.tyenv.Count
10484-
Normalcall
10485-
(Apps_app(ilThisTy, Apps_done g.ilg.typ_String))
10486-
10487-
let ilInstrs =
10488-
[ // load the hardwired format string
10489-
I_ldstr "%+A"
10490-
// make the printf format object
10491-
mkNormalNewobj newFormatMethSpec
10492-
// call sprintf
10493-
mkNormalCall sprintfMethSpec
10494-
// call the function returned by sprintf
10495-
mkLdarg0
10496-
if ilThisTy.Boxity = ILBoxity.AsValue then
10497-
mkNormalLdobj ilThisTy
10498-
yield! callInstrs
10499-
]
10478+
// Instantiate with our own type
10479+
let sprintfMethSpec =
10480+
mkILMethSpec (sprintfMethSpec.MethodRef, AsObject, [], [ funcTy ])
10481+
10482+
// Here's the body of the method. Call printf, then invoke the function it returns
10483+
let callInstrs =
10484+
EraseClosures.mkCallFunc
10485+
cenv.ilxPubCloEnv
10486+
(fun _ -> 0us)
10487+
eenv.tyenv.Count
10488+
Normalcall
10489+
(Apps_app(ilThisTy, Apps_done g.ilg.typ_String))
10490+
10491+
let ilInstrs =
10492+
[ // load the hardwired format string
10493+
I_ldstr "%+A"
10494+
// make the printf format object
10495+
mkNormalNewobj newFormatMethSpec
10496+
// call sprintf
10497+
mkNormalCall sprintfMethSpec
10498+
// call the function returned by sprintf
10499+
mkLdarg0
10500+
if ilThisTy.Boxity = ILBoxity.AsValue then
10501+
mkNormalLdobj ilThisTy
10502+
yield! callInstrs
10503+
]
1050010504

10501-
let ilMethodBody =
10502-
mkMethodBody (true, [], 2, nonBranchingInstrsToCode ilInstrs, None, eenv.imports)
10505+
let ilMethodBody =
10506+
mkMethodBody (true, [], 2, nonBranchingInstrsToCode ilInstrs, None, eenv.imports)
1050310507

10504-
let mdef =
10505-
mkILNonGenericVirtualInstanceMethod (methName, ILMemberAccess.Public, [], mkILReturn g.ilg.typ_String, ilMethodBody)
10508+
let mdef =
10509+
mkILNonGenericVirtualInstanceMethod (methName, ILMemberAccess.Public, [], mkILReturn g.ilg.typ_String, ilMethodBody)
1050610510

10507-
let mdef = mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ])
10508-
yield mdef
10509-
| _ -> ()
10511+
let mdef = mdef.With(customAttrs = mkILCustomAttrs [ g.CompilerGeneratedAttribute ])
10512+
yield mdef
10513+
| _ -> ()
1051010514
]
1051110515

1051210516
and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) =
@@ -10646,6 +10650,8 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) =
1064610650

1064710651
let tyconRepr = tycon.TypeReprInfo
1064810652

10653+
let reprAccess = ComputeMemberAccess hiddenRepr
10654+
1064910655
// DebugDisplayAttribute gets copied to the subtypes generated as part of DU compilation
1065010656
let debugDisplayAttrs, normalAttrs =
1065110657
tycon.Attribs
@@ -10656,7 +10662,10 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) =
1065610662
|> List.partition (fun a -> IsSecurityAttribute g cenv.amap cenv.casApplied a m)
1065710663

1065810664
let generateDebugDisplayAttribute =
10659-
not g.compilingFSharpCore && tycon.IsUnionTycon && isNil debugDisplayAttrs
10665+
not g.useReflectionFreeCodeGen
10666+
&& not g.compilingFSharpCore
10667+
&& tycon.IsUnionTycon
10668+
&& isNil debugDisplayAttrs
1066010669

1066110670
let generateDebugProxies =
1066210671
not (tyconRefEq g tcref g.unit_tcr_canon)
@@ -10687,8 +10696,6 @@ and GenTypeDef cenv mgbuf lazyInitInfo eenv m (tycon: Tycon) =
1068710696
yield! ilDebugDisplayAttributes
1068810697
]
1068910698

10690-
let reprAccess = ComputeMemberAccess hiddenRepr
10691-
1069210699
let ilTypeDefKind =
1069310700
match tyconRepr with
1069410701
| TFSharpObjectRepr o ->

src/Compiler/CodeGen/IlxGen.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ type internal IlxGenOptions =
5454
/// storage, even though 'it' is not logically mutable
5555
isInteractiveItExpr: bool
5656

57+
/// Suppress ToString emit
58+
useReflectionFreeCodeGen: bool
59+
5760
/// Indicates that, whenever possible, use callvirt instead of call
5861
alwaysCallVirt: bool
5962
}

src/Compiler/Driver/CompilerConfig.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ type TcConfigBuilder =
527527
/// If true, strip away data that would not be of use to end users, but is useful to us for debugging
528528
mutable noDebugAttributes: bool
529529

530+
/// If true, do not emit ToString implementations for unions, records, structs, exceptions
531+
mutable useReflectionFreeCodeGen: bool
532+
530533
/// If true, indicates all type checking and code generation is in the context of fsi.exe
531534
isInteractive: bool
532535

@@ -730,6 +733,7 @@ type TcConfigBuilder =
730733
pause = false
731734
alwaysCallVirt = true
732735
noDebugAttributes = false
736+
useReflectionFreeCodeGen = false
733737
emitDebugInfoInQuotations = false
734738
exename = None
735739
shadowCopyReferences = false
@@ -1279,6 +1283,7 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) =
12791283
member _.pause = data.pause
12801284
member _.alwaysCallVirt = data.alwaysCallVirt
12811285
member _.noDebugAttributes = data.noDebugAttributes
1286+
member _.useReflectionFreeCodeGen = data.useReflectionFreeCodeGen
12821287
member _.isInteractive = data.isInteractive
12831288
member _.isInvalidationSupported = data.isInvalidationSupported
12841289
member _.emitDebugInfoInQuotations = data.emitDebugInfoInQuotations

src/Compiler/Driver/CompilerConfig.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ type TcConfigBuilder =
431431

432432
mutable noDebugAttributes: bool
433433

434+
mutable useReflectionFreeCodeGen: bool
435+
434436
/// If true, indicates all type checking and code generation is in the context of fsi.exe
435437
isInteractive: bool
436438

@@ -740,6 +742,7 @@ type TcConfig =
740742
member alwaysCallVirt: bool
741743

742744
member noDebugAttributes: bool
745+
member useReflectionFreeCodeGen: bool
743746

744747
/// If true, indicates all type checking and code generation is in the context of fsi.exe
745748
member isInteractive: bool

src/Compiler/Driver/CompilerImports.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2410,6 +2410,7 @@ and [<Sealed>] TcImports
24102410
tcConfig.implicitIncludeDir,
24112411
tcConfig.mlCompatibility,
24122412
tcConfig.isInteractive,
2413+
tcConfig.useReflectionFreeCodeGen,
24132414
tryFindSysTypeCcu,
24142415
tcConfig.emitDebugInfoInQuotations,
24152416
tcConfig.noDebugAttributes,

src/Compiler/Driver/CompilerOptions.fs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,6 +1054,13 @@ let codeGenerationFlags isFsi (tcConfigB: TcConfigBuilder) =
10541054
Some(FSComp.SR.optsCrossoptimize ())
10551055
)
10561056

1057+
CompilerOption(
1058+
"reflectionfree",
1059+
tagNone,
1060+
OptionUnit(fun () -> tcConfigB.useReflectionFreeCodeGen <- true),
1061+
None,
1062+
Some(FSComp.SR.optsReflectionFree ())
1063+
)
10571064
]
10581065

10591066
if isFsi then debug @ codegen else debug @ embed @ codegen

src/Compiler/Driver/OptimizeInputs.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ let GenerateIlxCode
251251
mainMethodInfo = mainMethodInfo
252252
ilxBackend = ilxBackend
253253
fsiMultiAssemblyEmit = tcConfig.fsiMultiAssemblyEmit
254+
useReflectionFreeCodeGen = tcConfig.useReflectionFreeCodeGen
254255
isInteractive = tcConfig.isInteractive
255256
isInteractiveItExpr = isInteractiveItExpr
256257
alwaysCallVirt = tcConfig.alwaysCallVirt

src/Compiler/FSComp.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ forLIsUnnecessary,"The 'l' or 'L' in this format specifier is unnecessary. In F#
235235
forHIsUnnecessary,"The 'h' or 'H' in this format specifier is unnecessary. You can use %%d, %%x, %%o or %%u instead, which are overloaded to work with all basic integer types."
236236
forDoesNotSupportPrefixFlag,"'%s' does not support prefix '%s' flag"
237237
forBadFormatSpecifierGeneral,"Bad format specifier: '%s'"
238+
forPercentAInReflectionFreeCode,"The '%%A' format specifier may not be used in an assembly being compiled with option '--reflectionfree'. This construct implicitly uses reflection."
238239
elSysEnvExitDidntExit,"System.Environment.Exit did not exit"
239240
elDeprecatedOperator,"The treatment of this operator is now handled directly by the F# compiler and its meaning cannot be redefined"
240241
405,chkProtectedOrBaseCalled,"A protected member is called or 'base' is being used. This is only allowed in the direct implementation of members since they could escape their object scope."
@@ -873,6 +874,7 @@ optsRefOnly,"Produce a reference assembly, instead of a full assembly, as the pr
873874
optsRefOut,"Produce a reference assembly with the specified file path."
874875
optsPathMap,"Maps physical paths to source path names output by the compiler"
875876
optsCrossoptimize,"Enable or disable cross-module optimizations"
877+
optsReflectionFree,"Disable implicit generation of constructs using reflection"
876878
optsWarnaserrorPM,"Report all warnings as errors"
877879
optsWarnaserror,"Report specific warnings as errors"
878880
optsWarn,"Set a warning level (0-5)"

src/Compiler/TypedTree/TcGlobals.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ type TcGlobals(
191191
directoryToResolveRelativePaths,
192192
mlCompatibility: bool,
193193
isInteractive: bool,
194+
useReflectionFreeCodeGen: bool,
194195
// The helper to find system types amongst referenced DLLs
195196
tryFindSysTypeCcu,
196197
emitDebugInfoInQuotations: bool,
@@ -1004,6 +1005,8 @@ type TcGlobals(
10041005

10051006
member _.compilingFSharpCore = compilingFSharpCore
10061007

1008+
member _.useReflectionFreeCodeGen = useReflectionFreeCodeGen
1009+
10071010
member _.mlCompatibility = mlCompatibility
10081011

10091012
member _.emitDebugInfoInQuotations = emitDebugInfoInQuotations

0 commit comments

Comments
 (0)