Skip to content

Commit 8dd81c6

Browse files
authored
Added inref immutability assumption removal (#7407)
* Added inref immutability assumption fix. Aware of IsReadOnly attribute, only on ILMethods. * Added isILStructTy * targeting assumption * Fixed tests * renaming * Better IsReadOnly check * Added tests * Splitting read-only assumption and read-only attribute check * Func removal * Better tests * Trying to fix test * More tests * Fixed extension member inref check * small cleanup * Added mkDereferencedByrefExpr * Minor cleanup * More tests. Changed how we deref addresses * Update comment * Being more specific on dereference addr work * Drastically simplified the solution * Added tests * Verifying current behavior
1 parent 650a166 commit 8dd81c6

File tree

12 files changed

+498
-112
lines changed

12 files changed

+498
-112
lines changed

src/fsharp/MethodCalls.fs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,19 @@ let TakeObjAddrForMethodCall g amap (minfo: MethInfo) isMutable m objArgs f =
938938
let hasCallInfo = ccallInfo.IsSome
939939
let mustTakeAddress = hasCallInfo || minfo.ObjArgNeedsAddress(amap, m)
940940
let objArgTy = tyOfExpr g objArgExpr
941+
942+
let isMutable =
943+
match isMutable with
944+
| DefinitelyMutates
945+
| NeverMutates
946+
| AddressOfOp -> isMutable
947+
| PossiblyMutates ->
948+
// Check to see if the method is read-only. Perf optimization.
949+
// If there is an extension member whose first arg is an inref, we must return NeverMutates.
950+
if mustTakeAddress && (minfo.IsReadOnly || minfo.IsReadOnlyExtensionMember (amap, m)) then
951+
NeverMutates
952+
else
953+
isMutable
941954

942955
let wrap, objArgExprAddr, isReadOnly, _isWriteOnly =
943956
mkExprAddrOfExpr g mustTakeAddress hasCallInfo isMutable objArgExpr None m

src/fsharp/PostInferenceChecks.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ let GetLimitValByRef cenv env m v =
263263
{ scope = scope; flags = flags }
264264

265265
let LimitVal cenv (v: Val) limit =
266-
cenv.limitVals.[v.Stamp] <- limit
266+
if not v.IgnoresByrefScope then
267+
cenv.limitVals.[v.Stamp] <- limit
267268

268269
let BindVal cenv env (v: Val) =
269270
//printfn "binding %s..." v.DisplayName

src/fsharp/TastOps.fs

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3004,8 +3004,8 @@ let isByrefTyconRef (g: TcGlobals) (tcref: TyconRef) =
30043004
let isByrefLikeTyconRef (g: TcGlobals) m (tcref: TyconRef) =
30053005
tcref.CanDeref &&
30063006
match tcref.TryIsByRefLike with
3007-
| Some res -> res
3008-
| None ->
3007+
| ValueSome res -> res
3008+
| _ ->
30093009
let res =
30103010
isByrefTyconRef g tcref ||
30113011
(isStructTyconRef tcref && TyconRefHasAttribute g m g.attrib_IsByRefLikeAttribute tcref)
@@ -5941,34 +5941,53 @@ let mkAndSimplifyMatch spBind exprm matchm ty tree targets =
59415941
//-------------------------------------------------------------------------
59425942

59435943
type Mutates = AddressOfOp | DefinitelyMutates | PossiblyMutates | NeverMutates
5944-
exception DefensiveCopyWarning of string * range
5944+
exception DefensiveCopyWarning of string * range
59455945

59465946
let isRecdOrStructTyconRefAssumedImmutable (g: TcGlobals) (tcref: TyconRef) =
59475947
tcref.CanDeref &&
59485948
not (isRecdOrUnionOrStructTyconRefDefinitelyMutable tcref) ||
5949-
tyconRefEq g tcref g.decimal_tcr ||
5949+
tyconRefEq g tcref g.decimal_tcr ||
59505950
tyconRefEq g tcref g.date_tcr
59515951

5952-
let isRecdOrStructTyconRefReadOnly (g: TcGlobals) m (tcref: TyconRef) =
5952+
let isTyconRefReadOnly g m (tcref: TyconRef) =
59535953
tcref.CanDeref &&
59545954
match tcref.TryIsReadOnly with
5955-
| Some res -> res
5956-
| None ->
5957-
let isImmutable = isRecdOrStructTyconRefAssumedImmutable g tcref
5958-
let hasAttrib = TyconRefHasAttribute g m g.attrib_IsReadOnlyAttribute tcref
5959-
let res = isImmutable || hasAttrib
5955+
| ValueSome res -> res
5956+
| _ ->
5957+
let res = TyconRefHasAttribute g m g.attrib_IsReadOnlyAttribute tcref
59605958
tcref.SetIsReadOnly res
59615959
res
59625960

5963-
let isRecdOrStructTyReadOnly (g: TcGlobals) m ty =
5961+
let isTyconRefAssumedReadOnly g (tcref: TyconRef) =
5962+
tcref.CanDeref &&
5963+
match tcref.TryIsAssumedReadOnly with
5964+
| ValueSome res -> res
5965+
| _ ->
5966+
let res = isRecdOrStructTyconRefAssumedImmutable g tcref
5967+
tcref.SetIsAssumedReadOnly res
5968+
res
5969+
5970+
let isRecdOrStructTyconRefReadOnlyAux g m isInref (tcref: TyconRef) =
5971+
if isInref && tcref.IsILStructOrEnumTycon then
5972+
isTyconRefReadOnly g m tcref
5973+
else
5974+
isTyconRefReadOnly g m tcref || isTyconRefAssumedReadOnly g tcref
5975+
5976+
let isRecdOrStructTyconRefReadOnly g m tcref =
5977+
isRecdOrStructTyconRefReadOnlyAux g m false tcref
5978+
5979+
let isRecdOrStructTyReadOnlyAux (g: TcGlobals) m isInref ty =
59645980
match tryDestAppTy g ty with
59655981
| ValueNone -> false
5966-
| ValueSome tcref -> isRecdOrStructTyconRefReadOnly g m tcref
5982+
| ValueSome tcref -> isRecdOrStructTyconRefReadOnlyAux g m isInref tcref
5983+
5984+
let isRecdOrStructTyReadOnly g m ty =
5985+
isRecdOrStructTyReadOnlyAux g m false ty
59675986

5968-
let CanTakeAddressOf g m ty mut =
5987+
let CanTakeAddressOf g m isInref ty mut =
59695988
match mut with
59705989
| NeverMutates -> true
5971-
| PossiblyMutates -> isRecdOrStructTyReadOnly g m ty
5990+
| PossiblyMutates -> isRecdOrStructTyReadOnlyAux g m isInref ty
59725991
| DefinitelyMutates -> false
59735992
| AddressOfOp -> true // you can take the address but you might get a (readonly) inref<T> as a result
59745993

@@ -5996,7 +6015,7 @@ let CanTakeAddressOfImmutableVal (g: TcGlobals) m (vref: ValRef) mut =
59966015
// || valRefInThisAssembly g.compilingFslib vref
59976016
// This is because we don't actually guarantee to generate static backing fields for all values like these, e.g. simple constants "let x = 1".
59986017
// We always generate a static property but there is no field to take an address of
5999-
CanTakeAddressOf g m vref.Type mut
6018+
CanTakeAddressOf g m false vref.Type mut
60006019

60016020
let MustTakeAddressOfVal (g: TcGlobals) (vref: ValRef) =
60026021
vref.IsMutable &&
@@ -6008,7 +6027,7 @@ let MustTakeAddressOfByrefGet (g: TcGlobals) (vref: ValRef) =
60086027

60096028
let CanTakeAddressOfByrefGet (g: TcGlobals) (vref: ValRef) mut =
60106029
isInByrefTy g vref.Type &&
6011-
CanTakeAddressOf g vref.Range (destByrefTy g vref.Type) mut
6030+
CanTakeAddressOf g vref.Range true (destByrefTy g vref.Type) mut
60126031

60136032
let MustTakeAddressOfRecdField (rfref: RecdField) =
60146033
// Static mutable fields must be private, hence we don't have to take their address
@@ -6021,14 +6040,18 @@ let CanTakeAddressOfRecdFieldRef (g: TcGlobals) m (rfref: RecdFieldRef) tinst mu
60216040
// We only do this if the field is defined in this assembly because we can't take addresses across assemblies for immutable fields
60226041
entityRefInThisAssembly g.compilingFslib rfref.TyconRef &&
60236042
not rfref.RecdField.IsMutable &&
6024-
CanTakeAddressOf g m (actualTyOfRecdFieldRef rfref tinst) mut
6043+
CanTakeAddressOf g m false (actualTyOfRecdFieldRef rfref tinst) mut
60256044

60266045
let CanTakeAddressOfUnionFieldRef (g: TcGlobals) m (uref: UnionCaseRef) cidx tinst mut =
60276046
// We only do this if the field is defined in this assembly because we can't take addresses across assemblies for immutable fields
60286047
entityRefInThisAssembly g.compilingFslib uref.TyconRef &&
60296048
let rfref = uref.FieldByIndex cidx
60306049
not rfref.IsMutable &&
6031-
CanTakeAddressOf g m (actualTyOfUnionFieldRef uref cidx tinst) mut
6050+
CanTakeAddressOf g m false (actualTyOfUnionFieldRef uref cidx tinst) mut
6051+
6052+
let mkDerefAddrExpr mAddrGet expr mExpr exprTy =
6053+
let v, _ = mkCompGenLocal mAddrGet "byrefReturn" exprTy
6054+
mkCompGenLet mExpr v expr (mkAddrGet mAddrGet (mkLocalValRef v))
60326055

60336056
/// Make the address-of expression and return a wrapper that adds any allocated locals at an appropriate scope.
60346057
/// Also return a flag that indicates if the resulting pointer is a not a pointer where writing is allowed and will
@@ -6166,8 +6189,12 @@ let rec mkExprAddrOfExprAux g mustTakeAddress useReadonlyForGenericArrayAddress
61666189
// Take a defensive copy
61676190
let tmp, _ =
61686191
match mut with
6169-
| NeverMutates -> mkCompGenLocal m "copyOfStruct" ty
6192+
| NeverMutates -> mkCompGenLocal m "copyOfStruct" ty
61706193
| _ -> mkMutableCompGenLocal m "copyOfStruct" ty
6194+
6195+
// This local is special in that it ignore byref scoping rules.
6196+
tmp.SetIgnoresByrefScope()
6197+
61716198
let readonly = true
61726199
let writeonly = false
61736200
Some (tmp, expr), (mkValAddr m readonly (mkLocalValRef tmp)), readonly, writeonly

src/fsharp/TastOps.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ exception DefensiveCopyWarning of string * range
369369

370370
type Mutates = AddressOfOp | DefinitelyMutates | PossiblyMutates | NeverMutates
371371

372+
/// Helper to create an expression that dereferences an address.
373+
val mkDerefAddrExpr: mAddrGet: range -> expr: Expr -> mExpr: range -> exprTy: TType -> Expr
374+
372375
/// Helper to take the address of an expression
373376
val mkExprAddrOfExprAux : TcGlobals -> bool -> bool -> Mutates -> Expr -> ValRef option -> range -> (Val * Expr) option * Expr * bool * bool
374377

src/fsharp/TypeChecker.fs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3394,8 +3394,7 @@ let AnalyzeArbitraryExprAsEnumerable cenv (env: TcEnv) localAlloc m exprty expr
33943394
let currentExpr, enumElemTy =
33953395
// Implicitly dereference byref for expr 'for x in ...'
33963396
if isByrefTy cenv.g enumElemTy then
3397-
let v, _ = mkCompGenLocal m "byrefReturn" enumElemTy
3398-
let expr = mkCompGenLet currentExpr.Range v currentExpr (mkAddrGet m (mkLocalValRef v))
3397+
let expr = mkDerefAddrExpr m currentExpr currentExpr.Range enumElemTy
33993398
expr, destByrefTy cenv.g enumElemTy
34003399
else
34013400
currentExpr, enumElemTy
@@ -4139,9 +4138,8 @@ let buildApp cenv expr resultTy arg m =
41394138

41404139
| _ when isByrefTy g resultTy ->
41414140
// Handle byref returns, byref-typed returns get implicitly dereferenced
4142-
let v, _ = mkCompGenLocal m "byrefReturn" resultTy
41434141
let expr = expr.SupplyArgument (arg, m)
4144-
let expr = mkCompGenLet m v expr.Expr (mkAddrGet m (mkLocalValRef v))
4142+
let expr = mkDerefAddrExpr m expr.Expr m resultTy
41454143
let resultTy = destByrefTy g resultTy
41464144
MakeApplicableExprNoFlex cenv expr, resultTy
41474145

@@ -10214,8 +10212,7 @@ and TcMethodApplication
1021410212
// byref-typed returns get implicitly dereferenced
1021510213
let vty = tyOfExpr cenv.g callExpr0
1021610214
if isByrefTy cenv.g vty then
10217-
let v, _ = mkCompGenLocal mMethExpr "byrefReturn" vty
10218-
mkCompGenLet mMethExpr v callExpr0 (mkAddrGet mMethExpr (mkLocalValRef v))
10215+
mkDerefAddrExpr mMethExpr callExpr0 mMethExpr vty
1021910216
else
1022010217
callExpr0
1022110218

src/fsharp/infos.fs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,15 @@ type ILMethInfo =
835835
member x.IsDllImport (g: TcGlobals) =
836836
match g.attrib_DllImportAttribute with
837837
| None -> false
838-
| Some (AttribInfo(tref, _)) ->x.RawMetadata.CustomAttrs |> TryDecodeILAttribute g tref |> Option.isSome
838+
| Some attr ->
839+
x.RawMetadata.CustomAttrs
840+
|> TryFindILAttribute attr
841+
842+
/// Indicates if the method is marked with the [<IsReadOnly>] attribute. This is done by looking at the IL custom attributes on
843+
/// the method.
844+
member x.IsReadOnly (g: TcGlobals) =
845+
x.RawMetadata.CustomAttrs
846+
|> TryFindILAttribute g.attrib_IsReadOnlyAttribute
839847

840848
/// Get the (zero or one) 'self'/'this'/'object' arguments associated with an IL method.
841849
/// An instance extension method returns one object argument.
@@ -1238,6 +1246,25 @@ type MethInfo =
12381246
member x.IsStruct =
12391247
isStructTy x.TcGlobals x.ApparentEnclosingType
12401248

1249+
/// Indicates if this method is read-only; usually by the [<IsReadOnly>] attribute.
1250+
/// Must be an instance method.
1251+
/// Receiver must be a struct type.
1252+
member x.IsReadOnly =
1253+
// Perf Review: Is there a way we can cache this result?
1254+
x.IsInstance &&
1255+
x.IsStruct &&
1256+
match x with
1257+
| ILMeth (g, ilMethInfo, _) -> ilMethInfo.IsReadOnly g
1258+
| FSMeth _ -> false // F# defined methods not supported yet. Must be a language feature.
1259+
| _ -> false
1260+
1261+
/// Indicates if this method is an extension member that is read-only.
1262+
/// An extension member is considered read-only if the first argument is a read-only byref (inref) type.
1263+
member x.IsReadOnlyExtensionMember (amap: Import.ImportMap, m) =
1264+
x.IsExtensionMember &&
1265+
x.TryObjArgByrefType(amap, m, x.FormalMethodInst)
1266+
|> Option.exists (isInByrefTy amap.g)
1267+
12411268
/// Build IL method infos.
12421269
static member CreateILMeth (amap: Import.ImportMap, m, ty: TType, md: ILMethodDef) =
12431270
let tinfo = ILTypeInfo.FromType amap.g ty

0 commit comments

Comments
 (0)