diff --git a/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs b/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs
new file mode 100644
index 0000000000000..4a7e1121d57fb
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker.Steps/IMarkHandler.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Mono.Linker.Steps
+{
+
+ ///
+ /// This API supports the product infrastructure and is not intended to be used directly from your code.
+ /// Extensibility point for custom logic that run during MarkStep, for marked members.
+ ///
+ public interface IMarkHandler
+ {
+ ///
+ /// Initialize is called at the beginning of MarkStep. This should be
+ /// used to perform global setup, and register callbacks through the
+ /// MarkContext.Register* methods) to be called when pieces of IL are marked.
+ ///
+ void Initialize (LinkContext context, MarkContext markContext);
+ }
+}
diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs b/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs
new file mode 100644
index 0000000000000..158379f93d344
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkContext.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Mono.Cecil;
+
+namespace Mono.Linker.Steps
+{
+ ///
+ /// Context which can be used to register actions to call during MarkStep
+ /// when various members are marked.
+ ///
+ public abstract class MarkContext
+ {
+ ///
+ /// Register a callback that will be invoked once for each marked assembly.
+ ///
+ public abstract void RegisterMarkAssemblyAction (Action action);
+
+ ///
+ /// Register a callback that will be invoked once for each marked type.
+ ///
+ public abstract void RegisterMarkTypeAction (Action action);
+
+ ///
+ /// Register a callback that will be invoked once for each marked method.
+ ///
+ public abstract void RegisterMarkMethodAction (Action action);
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
index 95bdef9320772..fce48529eb001 100644
--- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs
@@ -56,6 +56,7 @@ public partial class MarkStep : IStep
readonly List<(TypeDefinition Type, MethodBody Body, Instruction Instr)> _pending_isinst_instr;
UnreachableBlocksOptimizer _unreachableBlocksOptimizer;
+ MarkStepContext _markContext;
#if DEBUG
static readonly DependencyKind[] _entireTypeReasons = new DependencyKind[] {
@@ -190,6 +191,7 @@ public virtual void Process (LinkContext context)
{
_context = context;
_unreachableBlocksOptimizer = new UnreachableBlocksOptimizer (_context);
+ _markContext = new MarkStepContext ();
Initialize ();
Process ();
@@ -199,6 +201,7 @@ public virtual void Process (LinkContext context)
void Initialize ()
{
InitializeCorelibAttributeXml ();
+ _context.Pipeline.InitializeMarkHandlers (_context, _markContext);
ProcessMarkedPending ();
}
@@ -1296,6 +1299,9 @@ protected void MarkAssembly (AssemblyDefinition assembly, DependencyInfo reason)
EmbeddedXmlInfo.ProcessDescriptors (assembly, _context);
+ foreach (Action handleMarkAssembly in _markContext.MarkAssemblyActions)
+ handleMarkAssembly (assembly);
+
// Security attributes do not respect the attributes XML
if (_context.StripSecurity)
RemoveSecurity.ProcessAssembly (assembly, _context);
@@ -1616,6 +1622,10 @@ protected internal virtual TypeDefinition MarkType (TypeReference reference, Dep
return null;
MarkModule (type.Scope as ModuleDefinition, new DependencyInfo (DependencyKind.ScopeOfType, type));
+
+ foreach (Action handleMarkType in _markContext.MarkTypeActions)
+ handleMarkType (type);
+
MarkType (type.BaseType, new DependencyInfo (DependencyKind.BaseType, type), type);
if (type.DeclaringType != null)
MarkType (type.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, type), type);
@@ -2655,6 +2665,9 @@ protected virtual void ProcessMethod (MethodDefinition method, in DependencyInfo
_unreachableBlocksOptimizer.ProcessMethod (method);
+ foreach (Action handleMarkMethod in _markContext.MarkMethodActions)
+ handleMarkMethod (method);
+
if (!markedForCall)
MarkType (method.DeclaringType, new DependencyInfo (DependencyKind.DeclaringType, method), method);
MarkCustomAttributes (method, new DependencyInfo (DependencyKind.CustomAttribute, method), method);
diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs
new file mode 100644
index 0000000000000..f47fe101ff314
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkStepContext.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using Mono.Cecil;
+
+namespace Mono.Linker.Steps
+{
+ public class MarkStepContext : MarkContext
+ {
+
+ public List> MarkAssemblyActions { get; }
+ public List> MarkTypeActions { get; }
+ public List> MarkMethodActions { get; }
+
+ public MarkStepContext ()
+ {
+ MarkAssemblyActions = new List> ();
+ MarkTypeActions = new List> ();
+ MarkMethodActions = new List> ();
+ }
+
+ public override void RegisterMarkAssemblyAction (Action action)
+ {
+ MarkAssemblyActions.Add (action);
+ }
+
+ public override void RegisterMarkTypeAction (Action action)
+ {
+ MarkTypeActions.Add (action);
+ }
+
+ public override void RegisterMarkMethodAction (Action action)
+ {
+ MarkMethodActions.Add (action);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs b/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs
new file mode 100644
index 0000000000000..9d5c57fcd597a
--- /dev/null
+++ b/src/tools/illink/src/linker/Linker.Steps/MarkSubStepsDispatcher.cs
@@ -0,0 +1,179 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+using Mono.Cecil;
+using Mono.Collections.Generic;
+
+namespace Mono.Linker.Steps
+{
+ //
+ // Dispatcher for SubSteps which only need to run on marked assemblies.
+ // This simplifies the implementation of linker custom steps, in the same
+ // way that SubStepsDispatcher does, but it implements IMarkHandler
+ // and registers a callback that gets invoked during MarkStep when an
+ // assembly gets marked.
+ //
+ public abstract class MarkSubStepsDispatcher : IMarkHandler
+ {
+ readonly List substeps;
+
+ List on_assemblies;
+ List on_types;
+ List on_fields;
+ List on_methods;
+ List on_properties;
+ List on_events;
+
+ protected MarkSubStepsDispatcher (IEnumerable subSteps)
+ {
+ substeps = new List (subSteps);
+ }
+
+ public virtual void Initialize (LinkContext context, MarkContext markContext)
+ {
+ InitializeSubSteps (context);
+ markContext.RegisterMarkAssemblyAction (assembly => BrowseAssembly (assembly));
+ }
+
+ static bool HasSubSteps (List substeps) => substeps?.Count > 0;
+
+ void BrowseAssembly (AssemblyDefinition assembly)
+ {
+ CategorizeSubSteps (assembly);
+
+ if (HasSubSteps (on_assemblies))
+ DispatchAssembly (assembly);
+
+ if (!ShouldDispatchTypes ())
+ return;
+
+ BrowseTypes (assembly.MainModule.Types);
+ }
+
+ bool ShouldDispatchTypes ()
+ {
+ return HasSubSteps (on_types)
+ || HasSubSteps (on_fields)
+ || HasSubSteps (on_methods)
+ || HasSubSteps (on_properties)
+ || HasSubSteps (on_events);
+ }
+
+ void BrowseTypes (Collection types)
+ {
+ foreach (TypeDefinition type in types) {
+ DispatchType (type);
+
+ if (type.HasFields && HasSubSteps (on_fields)) {
+ foreach (FieldDefinition field in type.Fields)
+ DispatchField (field);
+ }
+
+ if (type.HasMethods && HasSubSteps (on_methods)) {
+ foreach (MethodDefinition method in type.Methods)
+ DispatchMethod (method);
+ }
+
+ if (type.HasProperties && HasSubSteps (on_properties)) {
+ foreach (PropertyDefinition property in type.Properties)
+ DispatchProperty (property);
+ }
+
+ if (type.HasEvents && HasSubSteps (on_events)) {
+ foreach (EventDefinition @event in type.Events)
+ DispatchEvent (@event);
+ }
+
+ if (type.HasNestedTypes)
+ BrowseTypes (type.NestedTypes);
+ }
+ }
+
+ void DispatchAssembly (AssemblyDefinition assembly)
+ {
+ foreach (var substep in on_assemblies) {
+ substep.ProcessAssembly (assembly);
+ }
+ }
+
+ void DispatchType (TypeDefinition type)
+ {
+ foreach (var substep in on_types) {
+ substep.ProcessType (type);
+ }
+ }
+
+ void DispatchField (FieldDefinition field)
+ {
+ foreach (var substep in on_fields) {
+ substep.ProcessField (field);
+ }
+ }
+
+ void DispatchMethod (MethodDefinition method)
+ {
+ foreach (var substep in on_methods) {
+ substep.ProcessMethod (method);
+ }
+ }
+
+ void DispatchProperty (PropertyDefinition property)
+ {
+ foreach (var substep in on_properties) {
+ substep.ProcessProperty (property);
+ }
+ }
+
+ void DispatchEvent (EventDefinition @event)
+ {
+ foreach (var substep in on_events) {
+ substep.ProcessEvent (@event);
+ }
+ }
+
+ void InitializeSubSteps (LinkContext context)
+ {
+ foreach (var substep in substeps)
+ substep.Initialize (context);
+ }
+
+ void CategorizeSubSteps (AssemblyDefinition assembly)
+ {
+ on_assemblies = new List ();
+ on_types = new List ();
+ on_fields = new List ();
+ on_methods = new List ();
+ on_properties = new List ();
+ on_events = new List ();
+
+ foreach (var substep in substeps)
+ CategorizeSubStep (substep, assembly);
+ }
+
+ void CategorizeSubStep (ISubStep substep, AssemblyDefinition assembly)
+ {
+ if (!substep.IsActiveFor (assembly))
+ return;
+
+ CategorizeTarget (substep, SubStepTargets.Assembly, on_assemblies);
+ CategorizeTarget (substep, SubStepTargets.Type, on_types);
+ CategorizeTarget (substep, SubStepTargets.Field, on_fields);
+ CategorizeTarget (substep, SubStepTargets.Method, on_methods);
+ CategorizeTarget (substep, SubStepTargets.Property, on_properties);
+ CategorizeTarget (substep, SubStepTargets.Event, on_events);
+ }
+
+ static void CategorizeTarget (ISubStep substep, SubStepTargets target, List list)
+ {
+ if (!Targets (substep, target))
+ return;
+
+ list.Add (substep);
+ }
+
+ static bool Targets (ISubStep substep, SubStepTargets target) => (substep.Targets & target) == target;
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/src/linker/Linker/Driver.cs b/src/tools/illink/src/linker/Linker/Driver.cs
index 59c73b8a63215..02f1050ec8448 100644
--- a/src/tools/illink/src/linker/Linker/Driver.cs
+++ b/src/tools/illink/src/linker/Linker/Driver.cs
@@ -776,57 +776,111 @@ protected virtual void AddXmlDependencyRecorder (LinkContext context, string fil
context.Tracer.AddRecorder (new XmlDependencyRecorder (context, fileName));
}
- protected bool AddCustomStep (Pipeline pipeline, string arg)
+ protected bool AddMarkHandler (Pipeline pipeline, string arg)
+ {
+ if (!TryGetCustomAssembly (ref arg, out Assembly custom_assembly))
+ return false;
+
+ var step = ResolveStep (arg, custom_assembly);
+ if (step == null)
+ return false;
+
+ pipeline.AppendMarkHandler (step);
+ return true;
+ }
+
+ bool TryGetCustomAssembly (ref string arg, out Assembly assembly)
{
- Assembly custom_assembly = null;
+ assembly = null;
int pos = arg.IndexOf (",");
- if (pos != -1) {
- custom_assembly = GetCustomAssembly (arg.Substring (pos + 1));
- if (custom_assembly == null)
+ if (pos == -1)
+ return true;
+
+ assembly = GetCustomAssembly (arg.Substring (pos + 1));
+ if (assembly == null)
+ return false;
+
+ arg = arg.Substring (0, pos);
+ return true;
+ }
+
+ protected bool AddCustomStep (Pipeline pipeline, string arg)
+ {
+ if (!TryGetCustomAssembly (ref arg, out Assembly custom_assembly))
+ return false;
+
+ string customStepName;
+ string targetName = null;
+ bool before = false;
+ if (!arg.Contains (":")) {
+ customStepName = arg;
+ } else {
+ string[] parts = arg.Split (':');
+ if (parts.Length != 2) {
+ context.LogError ($"Invalid value '{arg}' specified for '--custom-step' option", 1024);
return false;
- arg = arg.Substring (0, pos);
- }
+ }
+ customStepName = parts[1];
- pos = arg.IndexOf (":");
- if (pos == -1) {
- var step = ResolveStep (arg, custom_assembly);
- if (step == null)
+ if (!parts[0].StartsWith ("-") && !parts[0].StartsWith ("+")) {
+ context.LogError ($"Expected '+' or '-' to control new step insertion", 1025);
return false;
+ }
- pipeline.AppendStep (step);
- return true;
+ before = parts[0][0] == '-';
+ targetName = parts[0].Substring (1);
}
- string[] parts = arg.Split (':');
- if (parts.Length != 2) {
- context.LogError ($"Invalid value '{arg}' specified for '--custom-step' option", 1024);
+ var stepType = ResolveStepType (customStepName, custom_assembly);
+ if (stepType == null)
return false;
- }
- if (!parts[0].StartsWith ("-") && !parts[0].StartsWith ("+")) {
- context.LogError ($"Expected '+' or '-' to control new step insertion", 1025);
- return false;
- }
+ if (typeof (IStep).IsAssignableFrom (stepType)) {
+
+ var customStep = (IStep) Activator.CreateInstance (stepType);
+ if (targetName == null) {
+ pipeline.AppendStep (customStep);
+ return true;
+ }
- bool before = parts[0][0] == '-';
- string name = parts[0].Substring (1);
+ IStep target = FindStep (pipeline, targetName);
+ if (target == null) {
+ context.LogError ($"Pipeline step '{targetName}' could not be found", 1026);
+ return false;
+ }
- IStep target = FindStep (pipeline, name);
- if (target == null) {
- context.LogError ($"Pipeline step '{name}' could not be found", 1026);
- return false;
+ if (before)
+ pipeline.AddStepBefore (target, customStep);
+ else
+ pipeline.AddStepAfter (target, customStep);
+
+ return true;
}
- IStep newStep = ResolveStep (parts[1], custom_assembly);
- if (newStep == null)
- return false;
+ if (typeof (IMarkHandler).IsAssignableFrom (stepType)) {
- if (before)
- pipeline.AddStepBefore (target, newStep);
- else
- pipeline.AddStepAfter (target, newStep);
+ var customStep = (IMarkHandler) Activator.CreateInstance (stepType);
+ if (targetName == null) {
+ pipeline.AppendMarkHandler (customStep);
+ return true;
+ }
- return true;
+ IMarkHandler target = FindMarkHandler (pipeline, targetName);
+ if (target == null) {
+ context.LogError ($"Pipeline step '{targetName}' could not be found", 1026);
+ return false;
+ }
+
+ if (before)
+ pipeline.AddMarkHandlerBefore (target, customStep);
+ else
+ pipeline.AddMarkHandlerAfter (target, customStep);
+
+ return true;
+ }
+
+ context.LogError ($"Custom step '{stepType}' is incompatible with this linker version", 1028);
+ return false;
}
static IStep FindStep (Pipeline pipeline, string name)
@@ -840,7 +894,30 @@ static IStep FindStep (Pipeline pipeline, string name)
return null;
}
- IStep ResolveStep (string type, Assembly assembly)
+ static IMarkHandler FindMarkHandler (Pipeline pipeline, string name)
+ {
+ foreach (IMarkHandler step in pipeline.MarkHandlers) {
+ Type t = step.GetType ();
+ if (t.Name == name)
+ return step;
+ }
+
+ return null;
+ }
+
+ Type ResolveStepType (string type, Assembly assembly)
+ {
+ Type step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
+
+ if (step == null) {
+ context.LogError ($"Custom step '{type}' could not be found", 1027);
+ return null;
+ }
+
+ return step;
+ }
+
+ TStep ResolveStep (string type, Assembly assembly) where TStep : class
{
Type step = assembly != null ? assembly.GetType (type) : Type.GetType (type, false);
@@ -849,12 +926,12 @@ IStep ResolveStep (string type, Assembly assembly)
return null;
}
- if (!typeof (IStep).IsAssignableFrom (step)) {
+ if (!typeof (TStep).IsAssignableFrom (step)) {
context.LogError ($"Custom step '{type}' is incompatible with this linker version", 1028);
return null;
}
- return (IStep) Activator.CreateInstance (step);
+ return (TStep) Activator.CreateInstance (step);
}
static string[] GetFiles (string param)
diff --git a/src/tools/illink/src/linker/Linker/LinkContext.cs b/src/tools/illink/src/linker/Linker/LinkContext.cs
index 01fbbabd537bf..12ea9ce9f5769 100644
--- a/src/tools/illink/src/linker/Linker/LinkContext.cs
+++ b/src/tools/illink/src/linker/Linker/LinkContext.cs
@@ -32,7 +32,7 @@
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
-
+using Mono.Linker.Steps;
namespace Mono.Linker
{
@@ -190,6 +190,8 @@ public ISymbolWriterProvider SymbolWriterProvider {
public string AssemblyListFile { get; set; }
+ public List MarkHandlers { get; }
+
public LinkContext (Pipeline pipeline, ILogger logger)
{
_pipeline = pipeline;
@@ -221,6 +223,7 @@ public LinkContext (Pipeline pipeline, ILogger logger)
GeneralWarnAsError = false;
WarnAsError = new Dictionary ();
WarnVersion = WarnVersion.Latest;
+ MarkHandlers = new List ();
const CodeOptimizations defaultOptimizations =
CodeOptimizations.BeforeFieldInit |
diff --git a/src/tools/illink/src/linker/Linker/Pipeline.cs b/src/tools/illink/src/linker/Linker/Pipeline.cs
index 0aa5a3d1ae8b6..8d5ebc30a994b 100644
--- a/src/tools/illink/src/linker/Linker/Pipeline.cs
+++ b/src/tools/illink/src/linker/Linker/Pipeline.cs
@@ -28,7 +28,6 @@
using System;
using System.Collections.Generic;
-
using Mono.Linker.Steps;
namespace Mono.Linker
@@ -38,10 +37,12 @@ public class Pipeline
{
readonly List _steps;
+ public List MarkHandlers { get; }
public Pipeline ()
{
_steps = new List ();
+ MarkHandlers = new List ();
}
public void PrependStep (IStep step)
@@ -54,6 +55,11 @@ public void AppendStep (IStep step)
_steps.Add (step);
}
+ public void AppendMarkHandler (IMarkHandler step)
+ {
+ MarkHandlers.Add (step);
+ }
+
public void AddStepBefore (Type target, IStep step)
{
for (int i = 0; i < _steps.Count; i++) {
@@ -62,8 +68,7 @@ public void AddStepBefore (Type target, IStep step)
return;
}
}
- string msg = String.Format ("Step {0} could not be inserted before (not found) {1}", step, target);
- throw new InvalidOperationException (msg);
+ throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}");
}
public void AddStepBefore (IStep target, IStep step)
@@ -74,6 +79,18 @@ public void AddStepBefore (IStep target, IStep step)
return;
}
}
+ throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}");
+ }
+
+ public void AddMarkHandlerBefore (IMarkHandler target, IMarkHandler step)
+ {
+ for (int i = 0; i < MarkHandlers.Count; i++) {
+ if (MarkHandlers[i] == target) {
+ MarkHandlers.Insert (i, step);
+ return;
+ }
+ }
+ throw new InternalErrorException ($"Step {step} could not be inserted before (not found) {target}");
}
public void ReplaceStep (Type target, IStep step)
@@ -93,8 +110,7 @@ public void AddStepAfter (Type target, IStep step)
return;
}
}
- string msg = String.Format ("Step {0} could not be inserted after (not found) {1}", step, target);
- throw new InvalidOperationException (msg);
+ throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}");
}
public void AddStepAfter (IStep target, IStep step)
@@ -108,6 +124,21 @@ public void AddStepAfter (IStep target, IStep step)
return;
}
}
+ throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}");
+ }
+
+ public void AddMarkHandlerAfter (IMarkHandler target, IMarkHandler step)
+ {
+ for (int i = 0; i < MarkHandlers.Count; i++) {
+ if (MarkHandlers[i] == target) {
+ if (i == MarkHandlers.Count - 1)
+ MarkHandlers.Add (step);
+ else
+ MarkHandlers.Insert (i + 1, step);
+ return;
+ }
+ }
+ throw new InternalErrorException ($"Step {step} could not be inserted after (not found) {target}");
}
public void RemoveStep (Type target)
@@ -140,6 +171,15 @@ public IStep[] GetSteps ()
return _steps.ToArray ();
}
+ public void InitializeMarkHandlers (LinkContext context, MarkContext markContext)
+ {
+ while (MarkHandlers.Count > 0) {
+ IMarkHandler markHandler = MarkHandlers[0];
+ markHandler.Initialize (context, markContext);
+ MarkHandlers.Remove (markHandler);
+ }
+ }
+
public bool ContainsStep (Type type)
{
foreach (IStep step in _steps)
diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs b/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs
new file mode 100644
index 0000000000000..3f9aea88feb49
--- /dev/null
+++ b/src/tools/illink/src/linker/ref/Linker.Steps/IMarkHandler.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace Mono.Linker.Steps
+{
+ ///
+ /// This API supports the product infrastructure and is not intended to be used directly from your code.
+ ///
+ public interface IMarkHandler
+ {
+ void Initialize (LinkContext context, MarkContext markContext);
+ }
+}
diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs b/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs
new file mode 100644
index 0000000000000..b4c231432219e
--- /dev/null
+++ b/src/tools/illink/src/linker/ref/Linker.Steps/MarkContext.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Mono.Cecil;
+
+namespace Mono.Linker.Steps
+{
+ public abstract class MarkContext
+ {
+ public abstract void RegisterMarkAssemblyAction (Action action);
+
+ public abstract void RegisterMarkTypeAction (Action action);
+
+ public abstract void RegisterMarkMethodAction (Action action);
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs b/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs
new file mode 100644
index 0000000000000..a90d923c665ea
--- /dev/null
+++ b/src/tools/illink/src/linker/ref/Linker.Steps/MarkSubStepsDispatcher.cs
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace Mono.Linker.Steps
+{
+ public abstract class MarkSubStepsDispatcher : IMarkHandler
+ {
+ protected MarkSubStepsDispatcher (IEnumerable subSteps) => throw null;
+
+ public virtual void Initialize (LinkContext context, MarkContext markContext) => throw null;
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs
new file mode 100644
index 0000000000000..7f257dc49c99d
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/CustomMarkHandler.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using Mono.Cecil;
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+public class CustomMarkHandler : IMarkHandler
+{
+ LinkContext _context;
+
+ public void Initialize (LinkContext context, MarkContext markContext)
+ {
+ _context = context;
+ markContext.RegisterMarkAssemblyAction (assembly => DiscoverTypesInAssembly (assembly));
+ markContext.RegisterMarkTypeAction (type => DiscoverMethodsInType (type));
+ markContext.RegisterMarkMethodAction (method => DiscoverMethodsOnDeclaringType (method));
+ }
+
+ void MarkTypeFoo (TypeDefinition type)
+ {
+ if (type.Name == "DiscoveredTypeForAssembly")
+ _context.Annotations.Mark (type);
+
+ if (!type.HasNestedTypes)
+ return;
+
+ foreach (var nested in type.NestedTypes)
+ MarkTypeFoo (nested);
+ }
+
+ void DiscoverTypesInAssembly (AssemblyDefinition assembly)
+ {
+ foreach (var type in assembly.MainModule.Types)
+ MarkTypeFoo (type);
+ }
+
+ void DiscoverMethodsInType (TypeDefinition type)
+ {
+ foreach (var method in type.Methods) {
+ if (method.Name == $"DiscoveredMethodForType_{type.Name}")
+ _context.Annotations.Mark (method);
+ }
+ }
+
+ void DiscoverMethodsOnDeclaringType (MethodDefinition method)
+ {
+ foreach (var otherMethod in method.DeclaringType.Methods)
+ if (otherMethod.Name == $"DiscoveredMethodForMethod_{method.Name}")
+ _context.Annotations.Mark (otherMethod);
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs
new file mode 100644
index 0000000000000..9dab0d3feeec6
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/Dependencies/MyMarkSubStepsDispatcher.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+public class MyMarkSubStepsDispatcher : MarkSubStepsDispatcher
+{
+ public MyMarkSubStepsDispatcher ()
+ : base (GetSubSteps ())
+ {
+ }
+
+ public override void Initialize (LinkContext context, MarkContext markContext)
+ {
+ base.Initialize (context, markContext);
+ }
+
+ static IEnumerable GetSubSteps ()
+ {
+ yield return new CustomSubStep ();
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs
new file mode 100644
index 0000000000000..8a8208c1f7692
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkHandlerUsage.cs
@@ -0,0 +1,89 @@
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.Extensibility
+{
+ [SetupCompileBefore ("MarkHandler.dll", new[] { "Dependencies/CustomMarkHandler.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })]
+ [SetupLinkerArgument ("--custom-step", "CustomMarkHandler,MarkHandler.dll")]
+ public class MarkHandlerUsage
+ {
+ public static void Main ()
+ {
+ UsedType.UsedMethod ();
+ }
+
+ [Kept]
+ public class DiscoveredTypeForAssembly
+ {
+ [Kept]
+ public static void DiscoveredMethodForType_DiscoveredTypeForAssembly ()
+ {
+ }
+
+ [Kept]
+ public static void DiscoveredMethodForMethod_DiscoveredMethodForType_DiscoveredTypeForAssembly ()
+ {
+ }
+
+ public static void UnusedMethod ()
+ {
+ }
+
+ public static void DiscoveredMethodForMethod_UnusedMethod ()
+ {
+ }
+ }
+
+ [Kept]
+ public class UsedType
+ {
+ [Kept]
+ public static void DiscoveredMethodForType_UsedType ()
+ {
+ }
+
+ [Kept]
+ public static void DiscoveredMethodForMethod_DiscoveredMethodForType_UsedType ()
+ {
+ }
+
+ [Kept]
+ public static void UsedMethod ()
+ {
+ }
+
+ [Kept]
+ public static void DiscoveredMethodForMethod_UsedMethod ()
+ {
+ }
+
+ [Kept]
+ public static void DiscoveredMethodForMethod_DiscoveredMethodForMethod_UsedMethod ()
+ {
+ }
+
+ public static void UnusedMethod ()
+ {
+ }
+
+ public static void DiscoveredMethodForMethod_UnusedMethod ()
+ {
+ }
+ }
+
+ public class UnusedType
+ {
+ public static void DiscoveredMethodForType_UnusedType ()
+ {
+ }
+
+ public static void DiscoveredMethodForMethod_DiscoveredMethodForType_UnusedType ()
+ {
+ }
+
+ public static void UnusedMethod ()
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs
new file mode 100644
index 0000000000000..794b89e51ded5
--- /dev/null
+++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Extensibility/MarkSubStepsDispatcherUsage.cs
@@ -0,0 +1,24 @@
+using Mono.Linker.Tests.Cases.Expectations.Assertions;
+using Mono.Linker.Tests.Cases.Expectations.Metadata;
+
+namespace Mono.Linker.Tests.Cases.Extensibility
+{
+ [SetupCompileBefore ("MyMarkSubStepsDispatcher.dll", new[] { "Dependencies/MyMarkSubStepsDispatcher.cs", "Dependencies/CustomSubStep.cs" }, new[] { "illink.dll", "Mono.Cecil.dll", "netstandard.dll" })]
+ [SetupLinkerArgument ("--custom-step", "MyMarkSubStepsDispatcher,MyMarkSubStepsDispatcher.dll")]
+ public class MarkSubStepsDispatcherUsage
+ {
+ public static void Main ()
+ {
+ }
+
+ [Kept]
+ public class NestedType
+ {
+ public int field;
+
+ public static void SomeMethod ()
+ {
+ }
+ }
+ }
+}
\ No newline at end of file