Management of Code Dependencies =============================== The Management of dependencies from compiled methods to classes, methods and other entities where such dependencies may change and result in some action (e.g. deoptimization) being applied to a compiled method. Overall Architecture -------------------- A dependency is a relationship between a ```TargetMethod`` <./Glossary#target-method>`__, that is, the result of a compilation, and an assumption that was made by the compiler during the compilation. The assumption may be any invariant that can be checked for validity at a future time. Assumptions are specified by subclasses of ``CiAssumptions.Assumption``. Instances of such classes typically contain references to VM objects that, for example, represent methods, i.e., ``RiResolvedMethod``. Note that assumptions at this level are generally specified using compiler and VM independent types, and are defined in a compiler and VM independent project (package). However, there is nothing that prevents a VM specific assumption being defined using VM specific types. Since an assumption has to be validated any time the global state of the VM changes, for example, a new class is loaded, it must persist as long as the associated ``TargetMethod``. To minimize the amount of storage space occupied by assumptions, and to simplify analysis in a concrete VM, validated assumptions are converted to dependencies, which use a densely encoded form of the concrete VM types using small integers, such as ``ClassID``. All assumptions have an associated context class which identifies the class that the assumption affects. For example, the ConcreteSubtype assumption specifies that a class ``T`` has a single unique subtype ``U``. In this case, ``T`` is defined to be the context class. The possible set of assumptions and associated dependencies is open-ended. In order to provide for easy extensibility while keeping the core of the system independent, the concept of a ``DependencyProcessor`` is introduced. A ``DependencyProcessor`` is responsible for the following: - the validation of the associated assumption. - the encoding of the assumption into an efficient packed form - the processing of the packed form, converting back to an object form for ease of analysis - supporting the application of a dependency visitor for analysis - providing a string based representation of the dependency for tracing Analysing Dependencies ---------------------- A visitor pattern is used to support the analysis of a ``Dependencies`` instance. Recall that each such instance relates to a single ``TargetMethod``, may contain dependencies related to several context classes and each of these may contain dependencies corresponding to several dependency processors. Since the set of ``DependencyProcessor``\ s is open ended, and a visitor may want to visit the data corresponding to several dependency processors in one visit, implementation class inheritance cannot be used to create a specific visitor. Instead, a two-level type structure is used, with interfaces defined in the specific ``DependencyProcessor`` class that declare the statically typed methods that result from decoding the packed form of the dependency. Note that these typically correspond closely to the original ``CiAssumptions.Assumption`` but with compiler/VM independent types replaced with Maxine specific types. E.g., ``RiResolvedType`` replaced with ``ClassActor``. Dependencies Visitor -------------------- ``Dependencies.DependencyVisitor`` handles the aspects of the iteration that are independent of the dependency processors. See ``Dependencies.DependencyVisitor`` for more details. The data for each dependency processor is visited by invoking ``Dependencies.DependencyVisitor.visit`` for each individual dependency. This method is generic since it cannot know anything about the types of the data associated with the dependency. The default implementation handles this by calling ``DependencyProcessor.match`` which returns ``dependencyVisitor`` if the visitor implements the ``DependencyProcessorVisitor`` interface defined by the processor that specifies the types of the data in the dependency, or ``null`` if not. It then invokes ``DependencyProcessor.visit`` with this value, which invokes the typed method in the interface if the value is non-null, and steps the index to the next dependency. Defining ``DependencyProcessor.visit`` this way allows a different ``DependencyProcessorVisitor`` to be called by an overriding implementation of ``Dependencies.DependencyVisitor.visit``. For example, a visitor that cannot know all the dependency processors in the system, yet wants to invoke the ``DependencyProcessor.ToStringDependencyProcessorVisitor``. Defining a new Dependency Processor ----------------------------------- The first step is to define a new subclass of ``CiAssumptions.Assumption``. If, as is typical, the dependency is used within the optimizing compiler, then this subclass should be defined by adding it to ``CiAssumptions``. Next define a subclass of ``DependencyProcessor`` that will handle this assumption in Maxine, and place it in the ``com.sun.max.vm.compiler.deps`` package. Define a nested interface that extends of ``DependencyProcessorVisitor`` and defines a method with the same arguments as the method in the ``CiAssumptions.Assumption`` subclass. To support generic tracing of dependencies you should also define a subclass of ``DependencyProcessor.ToStringDependencyProcessorVisitor`` that implements your interface method(s) and appends appropriate tracing data to the ``StringBuilder`` variable in ``DependencyProcessor.ToStringDependencyProcessorVisitor``. Define a static final instance of the ``DependencyProcessor`` subclass, which will cause it to be registered with ``DependenciesManager`` during boot image generation. Finally, implement the remaining abstract methods: - ``DependencyProcessor.match`` - ``DependencyProcessor.getToStringDependencyProcessorVisitor`` - ``DependencyProcessor.visit`` The first two have trivial implementations. The visit method must step over the specific dependency data and, if the ``dependencyProcessorVisitor`` is not null, invoke the associated method, with the encoded data transformed into the appropriate argument types. Evidently, if the visitor is null, processing related to transforming the encoded data should be avoided. Automatically generated from ``com.sun.max.vm.compiler.deps.package-info``