Skip to content

Commit 263cbf2

Browse files
committed
Pull the method signature cache out of a static field so that it doesn't keep leaking memory indefinitely the more it's used.
Add support for manually emptying various cache data structures when they are no longer in use, to reduce GC complexity. Change the unit tests so that their output directory is the same as everything else.
1 parent 59907b8 commit 263cbf2

14 files changed

+287
-217
lines changed

JSIL.nunit

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<NUnitProject>
2+
<Settings activeconfig="Debug" />
3+
<Config name="Debug" binpathtype="Auto">
4+
<assembly path="bin\Tests.dll" />
5+
</Config>
6+
<Config name="Release" binpathtype="Auto" />
7+
</NUnitProject>

JSIL/AST/JSNodeTypes.cs

+22-140
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ public override string ToString () {
343343
}
344344

345345
public class JSFunctionExpression : JSExpression {
346+
public readonly MethodTypeFactory MethodTypes;
347+
346348
public readonly JSMethod Method;
347349

348350
public readonly Dictionary<string, JSVariable> AllVariables;
@@ -355,12 +357,14 @@ public class JSFunctionExpression : JSExpression {
355357

356358
public JSFunctionExpression (
357359
JSMethod method, Dictionary<string, JSVariable> allVariables,
358-
IEnumerable<JSVariable> parameters, JSBlockStatement body
360+
IEnumerable<JSVariable> parameters, JSBlockStatement body,
361+
MethodTypeFactory methodTypes
359362
) {
360363
Method = method;
361364
AllVariables = allVariables;
362365
Parameters = parameters;
363366
Body = body;
367+
MethodTypes = methodTypes;
364368
}
365369

366370
public override IEnumerable<JSNode> Children {
@@ -390,7 +394,7 @@ public override bool Equals (object obj) {
390394

391395
public override TypeReference GetExpectedType (TypeSystem typeSystem) {
392396
if (Method != null) {
393-
var delegateType = ConstructDelegateType(Method.Reference, typeSystem);
397+
var delegateType = MethodTypes.Get(Method.Reference, typeSystem);
394398
if (delegateType == null)
395399
return Method.Reference.ReturnType;
396400
else
@@ -895,59 +899,7 @@ public override void ReplaceChild (JSNode oldChild, JSNode newChild) {
895899
}
896900

897901
public abstract class JSExpression : JSNode {
898-
protected struct MethodSignature {
899-
public readonly TypeReference ReturnType;
900-
public readonly IEnumerable<TypeReference> ParameterTypes;
901-
public readonly int ParameterCount;
902-
private readonly int HashCode;
903-
904-
public MethodSignature (TypeReference returnType, IEnumerable<TypeReference> parameterTypes) {
905-
ReturnType = returnType;
906-
ParameterTypes = parameterTypes;
907-
ParameterCount = parameterTypes.Count();
908-
909-
HashCode = ReturnType.FullName.GetHashCode() ^ ParameterCount;
910-
911-
int i = 0;
912-
foreach (var p in ParameterTypes) {
913-
HashCode ^= (p.FullName.GetHashCode() << i);
914-
i += 1;
915-
}
916-
}
917-
918-
public override int GetHashCode () {
919-
return HashCode;
920-
}
921-
922-
public bool Equals (MethodSignature rhs) {
923-
if (!ILBlockTranslator.TypesAreEqual(
924-
ReturnType, rhs.ReturnType
925-
))
926-
return false;
927-
928-
if (ParameterCount != rhs.ParameterCount)
929-
return false;
930-
931-
using (var e1 = ParameterTypes.GetEnumerator())
932-
using (var e2 = rhs.ParameterTypes.GetEnumerator())
933-
while (e1.MoveNext() && e2.MoveNext()) {
934-
if (!ILBlockTranslator.TypesAreEqual(e1.Current, e2.Current))
935-
return false;
936-
}
937-
938-
return true;
939-
}
940-
941-
public override bool Equals (object obj) {
942-
if (obj is MethodSignature)
943-
return Equals((MethodSignature)obj);
944-
else
945-
return base.Equals(obj);
946-
}
947-
}
948-
949902
public static readonly JSNullExpression Null = new JSNullExpression();
950-
protected static readonly ConcurrentCache<MethodSignature, TypeReference> MethodTypeCache = new ConcurrentCache<MethodSignature, TypeReference>();
951903

952904
protected readonly IList<JSExpression> Values;
953905

@@ -1014,70 +966,6 @@ public static TypeReference SubstituteTypeArgs (ITypeInfoSource typeInfo, TypeRe
1014966
return TypeAnalysis.SubstituteTypeArgs(type, member);
1015967
}
1016968

1017-
public static TypeReference ConstructDelegateType (MethodReference method, TypeSystem typeSystem) {
1018-
return ConstructDelegateType(
1019-
method.ReturnType,
1020-
(from p in method.Parameters select p.ParameterType),
1021-
typeSystem
1022-
);
1023-
}
1024-
1025-
public static TypeReference ConstructDelegateType (TypeReference returnType, IEnumerable<TypeReference> parameterTypes, TypeSystem typeSystem) {
1026-
TypeReference result;
1027-
var ptypes = parameterTypes.ToArray();
1028-
var signature = new MethodSignature(returnType, ptypes);
1029-
1030-
return MethodTypeCache.GetOrCreate(
1031-
signature, () => {
1032-
TypeReference genericDelegateType;
1033-
1034-
var systemModule = typeSystem.Boolean.Resolve().Module;
1035-
bool hasReturnType;
1036-
1037-
if (ILBlockTranslator.TypesAreEqual(typeSystem.Void, returnType)) {
1038-
hasReturnType = false;
1039-
var name = String.Format("System.Action`{0}", signature.ParameterCount);
1040-
genericDelegateType = systemModule.GetType(
1041-
signature.ParameterCount == 0 ? "System.Action" : name
1042-
);
1043-
} else {
1044-
hasReturnType = true;
1045-
genericDelegateType = systemModule.GetType(String.Format(
1046-
"System.Func`{0}", signature.ParameterCount + 1
1047-
));
1048-
}
1049-
1050-
if (genericDelegateType != null) {
1051-
var git = new GenericInstanceType(genericDelegateType);
1052-
foreach (var pt in ptypes)
1053-
git.GenericArguments.Add(pt);
1054-
1055-
if (hasReturnType)
1056-
git.GenericArguments.Add(returnType);
1057-
1058-
return git;
1059-
} else {
1060-
var baseType = systemModule.GetType("System.MulticastDelegate");
1061-
1062-
var td = new TypeDefinition(
1063-
"JSIL.Meta", "MethodSignature", TypeAttributes.Class | TypeAttributes.NotPublic, baseType
1064-
);
1065-
td.DeclaringType = baseType;
1066-
1067-
var invoke = new MethodDefinition(
1068-
"Invoke", MethodAttributes.Public, returnType
1069-
);
1070-
foreach (var pt in ptypes)
1071-
invoke.Parameters.Add(new ParameterDefinition(pt));
1072-
1073-
td.Methods.Add(invoke);
1074-
1075-
return td;
1076-
}
1077-
}
1078-
);
1079-
}
1080-
1081969
public override void ReplaceChild (JSNode oldChild, JSNode newChild) {
1082970
if (oldChild == null)
1083971
throw new ArgumentNullException();
@@ -1233,7 +1121,8 @@ public static bool TryMaterialize (JSILIdentifier jsil, JSExpression reference,
12331121
materialized = JSInvocationExpression.InvokeMethod(
12341122
invocation.JSType, new JSFakeMethod(
12351123
"GetReference", new ByReferenceType(jsm.Reference.ReturnType),
1236-
(from p in jsm.Reference.Parameters select p.ParameterType).ToArray()
1124+
(from p in jsm.Reference.Parameters select p.ParameterType).ToArray(),
1125+
jsil.MethodTypes
12371126
), invocation.ThisReference, invocation.Arguments.ToArray(), true
12381127
);
12391128
return true;
@@ -1501,10 +1390,6 @@ public override TypeReference GetExpectedType (TypeSystem typeSystem) {
15011390
if (property != null)
15021391
return property.ReturnType;
15031392

1504-
var method = Member as MethodInfo;
1505-
if (method != null)
1506-
return JSExpression.ConstructDelegateType(method.Member, typeSystem);
1507-
15081393
return typeSystem.Void;
15091394
}
15101395
}
@@ -2113,16 +1998,22 @@ public override bool IsConstant {
21131998
}
21141999

21152000
public class JSMethod : JSIdentifier {
2001+
public readonly MethodTypeFactory MethodTypes;
2002+
21162003
public readonly IEnumerable<TypeReference> GenericArguments;
21172004
public readonly MethodReference Reference;
21182005
public readonly MethodInfo Method;
21192006

2120-
public JSMethod (MethodReference reference, MethodInfo method, IEnumerable<TypeReference> genericArguments = null) {
2007+
public JSMethod (
2008+
MethodReference reference, MethodInfo method, MethodTypeFactory methodTypes,
2009+
IEnumerable<TypeReference> genericArguments = null
2010+
) {
21212011
if ((reference == null) || (method == null))
21222012
throw new ArgumentNullException();
21232013

21242014
Reference = reference;
21252015
Method = method;
2016+
MethodTypes = methodTypes;
21262017

21272018
if (genericArguments == null) {
21282019
var gim = Reference as GenericInstanceMethod;
@@ -2146,39 +2037,30 @@ public QualifiedMemberIdentifier QualifiedIdentifier {
21462037
}
21472038

21482039
public override TypeReference GetExpectedType (TypeSystem typeSystem) {
2149-
return ConstructDelegateType(Reference, typeSystem);
2040+
return MethodTypes.Get(Reference, typeSystem);
21502041
}
21512042
}
21522043

21532044
public class JSFakeMethod : JSIdentifier {
2045+
public readonly MethodTypeFactory MethodTypes;
2046+
21542047
public readonly string Name;
21552048
public readonly TypeReference ReturnType;
21562049
public readonly TypeReference[] ParameterTypes;
21572050

2158-
public JSFakeMethod (string name, TypeReference returnType, params TypeReference[] parameterTypes) {
2051+
public JSFakeMethod (string name, TypeReference returnType, TypeReference[] parameterTypes, MethodTypeFactory methodTypes) {
21592052
Name = name;
21602053
ReturnType = returnType;
2161-
ParameterTypes = parameterTypes;
2162-
2163-
/*
2164-
if (IsOpenGenericType(ReturnType))
2165-
throw new Exception("Open generic return type");
2166-
else if (parameterTypes.Any(IsOpenGenericType))
2167-
throw new Exception("Open generic parameter type");
2168-
*/
2169-
2170-
/*
2171-
if (ReturnType.IsGenericParameter || parameterTypes.Any((p) => p.IsGenericParameter))
2172-
throw new ArgumentException()
2173-
*/
2054+
ParameterTypes = parameterTypes ?? new TypeReference[0];
2055+
MethodTypes = methodTypes;
21742056
}
21752057

21762058
public override string Identifier {
21772059
get { return Name; }
21782060
}
21792061

21802062
public override TypeReference GetExpectedType (TypeSystem typeSystem) {
2181-
return ConstructDelegateType(
2063+
return MethodTypes.Get(
21822064
ReturnType, ParameterTypes, typeSystem
21832065
);
21842066
}

JSIL/AssemblyTranslator.cs

+10-7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class AssemblyTranslator : IDisposable {
3030
public readonly Configuration Configuration;
3131

3232
public readonly SymbolProvider SymbolProvider = new SymbolProvider();
33+
public readonly AssemblyCache AssemblyCache = new AssemblyCache();
3334
public readonly FunctionCache FunctionCache;
3435
public readonly AssemblyManifest Manifest;
3536

@@ -101,7 +102,7 @@ protected virtual ReaderParameters GetReaderParameters (bool useSymbols, string
101102
readerParameters.AssemblyResolver = new AssemblyResolver(new string[] {
102103
Path.GetDirectoryName(mainAssemblyPath),
103104
Path.GetDirectoryName(Util.GetPathOfAssembly(Assembly.GetExecutingAssembly()))
104-
});
105+
}, AssemblyCache);
105106
}
106107

107108
if (useSymbols)
@@ -522,8 +523,8 @@ protected void TranslateModule (
522523

523524
context.CurrentModule = module;
524525

525-
var js = new JSSpecialIdentifiers(context.CurrentModule.TypeSystem);
526-
var jsil = new JSILIdentifier(context.CurrentModule.TypeSystem, js);
526+
var js = new JSSpecialIdentifiers(FunctionCache.MethodTypes, context.CurrentModule.TypeSystem);
527+
var jsil = new JSILIdentifier(FunctionCache.MethodTypes, context.CurrentModule.TypeSystem, js);
527528

528529
// Probably should be an argument, not a member variable...
529530
var astEmitter = new JavascriptAstEmitter(
@@ -1186,7 +1187,7 @@ private void OptimizeFunction (
11861187
).Visit(function);
11871188

11881189
new IntroduceEnumCasts(
1189-
si.TypeSystem, _TypeInfoProvider
1190+
si.TypeSystem, _TypeInfoProvider, FunctionCache.MethodTypes
11901191
).Visit(function);
11911192

11921193
new ExpandCastExpressions(
@@ -1265,7 +1266,7 @@ protected JSExpression TranslateField (
12651266

12661267
return JSInvocationExpression.InvokeStatic(
12671268
JSDotExpression.New(
1268-
dollarIdentifier, new JSFakeMethod("Constant", field.Module.TypeSystem.Void)
1269+
dollarIdentifier, new JSFakeMethod("Constant", field.Module.TypeSystem.Void, null, FunctionCache.MethodTypes)
12691270
), new JSExpression[] {
12701271
descriptor, JSLiteral.New(fieldInfo.Name), constant
12711272
}
@@ -1298,7 +1299,8 @@ protected JSExpression TranslateField (
12981299
new JSVariable[] { new JSParameter("$", field.DeclaringType, null) },
12991300
new JSBlockStatement(
13001301
new JSExpressionStatement(new JSReturnExpression(defaultValue))
1301-
)
1302+
),
1303+
FunctionCache.MethodTypes
13021304
);
13031305
}
13041306

@@ -1321,7 +1323,7 @@ protected JSExpression TranslateField (
13211323
} else
13221324
return JSInvocationExpression.InvokeStatic(
13231325
JSDotExpression.New(
1324-
dollarIdentifier, new JSFakeMethod("Field", field.Module.TypeSystem.Void)
1326+
dollarIdentifier, new JSFakeMethod("Field", field.Module.TypeSystem.Void, null, FunctionCache.MethodTypes)
13251327
), new JSExpression[] {
13261328
descriptor, JSLiteral.New(fieldInfo.Name), fieldTypeExpression, defaultValue
13271329
}
@@ -1671,6 +1673,7 @@ public void Dispose () {
16711673
_TypeInfoProvider.Dispose();
16721674

16731675
FunctionCache.Dispose();
1676+
AssemblyCache.Dispose();
16741677
}
16751678

16761679
public TypeInfoProvider GetTypeInfoProvider () {

JSIL/CILSupport.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@
1111
using Mono.Cecil.Pdb;
1212

1313
namespace JSIL.Internal {
14+
public class AssemblyCache : ConcurrentCache<string, AssemblyDefinition>, IDisposable {
15+
public void Dispose () {
16+
Clear();
17+
}
18+
}
19+
1420
public class AssemblyResolver : BaseAssemblyResolver {
15-
protected readonly ConcurrentCache<string, AssemblyDefinition> Cache = new ConcurrentCache<string, AssemblyDefinition>();
21+
protected readonly AssemblyCache Cache = new AssemblyCache();
22+
23+
public AssemblyResolver (IEnumerable<string> dirs, AssemblyCache cache = null) {
24+
if (cache != null)
25+
Cache = cache;
26+
else
27+
Cache = new AssemblyCache();
1628

17-
public AssemblyResolver (IEnumerable<string> dirs) {
1829
foreach (var dir in dirs)
1930
AddSearchDirectory(dir);
2031
}

0 commit comments

Comments
 (0)