classdef (Hidden = true) CodeInfoUtils < matlab.mixin.Copyable 
%CODEINFOUTILS provides CodeInfo extensions and utilities
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2008-2020 The MathWorks, Inc.

    % private properties
    properties (SetAccess = 'private', GetAccess = 'protected')
        expInports;
        expInportsDataElements;
        typeReplaceMap;
        ClientInterface;        
        CodeInfoStruct; % cache the CodeInfoStruct, needed only if not
        % using CodeDescriptor
        ComponentForDiagnostics char;
    end
    
    properties (SetAccess = 'protected', GetAccess = 'public')
        UseMF0CodeDescriptor;
    end
    
    properties (SetAccess = 'private', GetAccess = 'public')
        UseMF0Logging;
        UseMF0StateLogging;
        ConfigInterface;
        GraphicalPath;
        IsRowMajor; % flag indicating whether Row Major is active
    end
    
    properties (SetAccess = 'protected', GetAccess = 'public', NonCopyable = true)
        % do not copy these properties on copy operations
        CodeDescriptorInternals = [];
        CoderGroupsInfo = rtw.pil.CoderGroupsInfo.empty;
    end
    
    properties (SetAccess = 'private', GetAccess = 'protected', NonCopyable = true)
        % do not copy these properties on copy operations
        CodeDescriptorHandle;
    end
    
    properties (SetAccess = 'private', GetAccess = 'private', NonCopyable = true)
        % do not copy these properties on copy operations
        SubModelCodeDescHandles = [];
        
        MF0Model; % cache away one instance of the MF0 model, it is needed
                  % to instantiate new interfaces/variables
        globalDS       = [];
        sharedLocalDS  = [];
        allSharedDS    = [];
        readerSharedDS = [];
        writerSharedDS = [];
        InportSizes = [];
        InportSizesIndices = [];
        OutportSizes = [];
        OutportSizesIndices = []; 
        SigLogs = [];
        StateLogs = [];
        StatesUploadedLikeSignals = [];
        SubModelCodeInfo = [];
    end
    
    properties (SetAccess = 'protected', GetAccess = 'protected', NonCopyable = true)
        % do not copy these properties on copy operations
        CodeInfo;
        ServerFunctionsInfo = [];
        CallerFunctionsInfo = [];
        HasDSMWritesInInit = false;
    end
  
    methods
        % constructor
        function this = CodeInfoUtils(codeInfoPath, ...
                                      clientInterface, ...
                                      configInterface, ...
                                      componentName, ...
                                      runValidation)
            narginchk(5, 5);            
            
            
            expInports = [];
            typeReplaceMap = [];
            this.ComponentForDiagnostics = componentName;
            this.initUseMF0CodeDescriptor;
            this.UseMF0Logging = coder.internal.connectivity.featureOn('UseMF0CodeDescriptorLogging');
            this.UseMF0StateLogging = this.UseMF0Logging && ...
                coder.internal.connectivity.featureOn('UseMF0CodeDescriptorStateLogging');
            % codeInfoPath will be replaced by the build dir when we
            % permanently switch to CodeDescriptor
            buildDir = fileparts(codeInfoPath);
            this.CodeDescriptorHandle = rtw.connectivity.CodeInfoUtils. ...
                getCodeDescriptorHandle(this.addRebuildNeverMessage, buildDir);
            if this.UseMF0CodeDescriptor
                this.CodeInfo = this.getComponentInterface(this.CodeDescriptorHandle);
                this.MF0Model = this.CodeDescriptorHandle.getMF0Model;
                
                expInports = this.CodeDescriptorHandle.getExpInports;
                typeReplaceMap = this.CodeDescriptorHandle.getTypeReplaceMap;
            else
                addRebuildNeverMessage = this.addRebuildNeverMessage;
                [this.CodeInfo, this.CodeInfoStruct] = rtw.connectivity.CodeInfoUtils.loadCodeInfo(...
                    addRebuildNeverMessage, codeInfoPath);
                
                if isfield(this.CodeInfoStruct, 'expInports') 
                    expInports = this.CodeInfoStruct.expInports;
                end
                if isfield(this.CodeInfoStruct, 'typeReplaceMap')
                    typeReplaceMap = this.CodeInfoStruct.typeReplaceMap;
                end
            end
            
            this.IsRowMajor = this.CodeDescriptorHandle.isRowMajor();
            this.GraphicalPath = this.CodeInfo.GraphicalPath;

            % validate and store configInterface
            rtw.connectivity.Utils.validateArg(configInterface,'coder.connectivity.ConfigInterface');
            this.ConfigInterface = configInterface;
            this.ClientInterface = clientInterface;
            
            if ~isempty(expInports)
                rtw.connectivity.Utils.validateArg(expInports, 'struct');
                this.expInports = expInports;
                this.createExpInportsDataElements;
            end
            
            rtw.connectivity.Utils.validateArg(clientInterface, ...
                                               'coder.connectivity.CoreClientInterface');
            if ~isempty(typeReplaceMap)
                rtw.connectivity.Utils.validateArg(typeReplaceMap, 'containers.Map');
                this.typeReplaceMap = typeReplaceMap;
            end
            
            % initialize CSFunctionInfoFactory;
            this.initializeCSFunctionInfoFactory; 
            % initialize ServerFunctionsInfo
            this.initializeServerFunctionsInfo;
            % initialize DataStores
            this.initializeDataStores();         
            % initialize variable-size signal support
            this.initializeVariableSizeSignals;           
            % initialize CallerFunctionsInfo
            this.initializeCallerFunctionsInfo;
            % initialize CodeDescriptorInternals
            this.initializeCodeDescriptorInternals;
            % initialize CoderGroupsInfo
            this.initializeCoderGroupsInfo;
            % initialize OutputFunction
            this.initializeOutputFunction;
            % run validation methods
            this.validate(runValidation);
        end
    end        
    
    methods (Access = 'public')                                        
        
        function codeInfo = getCodeInfo(this)
            codeInfo = this.CodeInfo;
        end
        
        
        function getViewer(this)
            inspect(this.CodeInfo);
        end
        
        function instance = getMF0ModelInstance(this)
            instance = this.MF0Model;
        end
        
        % abstract away the differences between the output tasks
        function tasks = getOutputTasks(this)
            if this.isSampleTimeIndependent
                if (this.isExportFunctions && ~this.isRightClickSubsystemExportFcns) || ...
                        (this.isa(this.CodeInfo.OutputFunctions, 'SimulinkFunctionInterface'))
                    tasks = this.getAllAsyncOutputTasks;
                    % if any explicit periodic tasking, we append their tasks
                    if this.hasPeriodicExplicitTasking
                        tasks = [tasks this.getOutputTasksForTimingMode('PERIODIC')];
                    end
                    if ~this.hasServerFcnAndNoPeriodicRate
                        assert(isempty(this.getOutputTasksForTimingMode('INHERITED')),...
                            'INHERITED rates are not expected for export function diagram');
                    end
                else
                    % process inherited task
                    tasks = this.getOutputTasksForTimingMode('INHERITED');
                end
                % Also get reset function tasks
                if this.hasResetFunction
                    tasks = [tasks ...
                        this.getOutputTasksForTimingMode('RESET') ...
                        this.getOutputTasksForTimingMode('RESETWITHINIT')];
                end
            else
                % process all output tasks
                tasks = this.getAllOutputTasks;
            end
        end
        
        function [loggedStates, isPostUpload] = getLoggedStates(this)
            bHierarchy = this.CodeDescriptorHandle.getMF0BlockHierarchyMap;
        
            [loggedStates, multiRateStates, isPostUpload] = ...
                rtw.connectivity.CodeInfoUtils.getLoggableStates(bHierarchy);
            if ~isempty(loggedStates)
                if any(multiRateStates)
                    this.updateBlocksWithHybridRates(loggedStates, multiRateStates);
                end
                this.updateBaseRegionImplementationForStates(loggedStates);
            end
        end

        function [loggedSignals, sigLogInfo] = getLoggedSignals(this, loopType)
            bhierarchy = this.CodeDescriptorHandle.getMF0BlockHierarchyMap;
            
            [loggedSignals, sigLogInfo] = rtw.connectivity.CodeInfoUtils.getLoggableSignals(bhierarchy);
            if ~isempty(loggedSignals)
                idsToKeep = this.findTimingModesExecuted(loggedSignals);
                loggedSignals(~idsToKeep) = [];
                sigLogInfo(~idsToKeep) = [];
            end
            if loopType == rtw.pil.InTheLoopType.ModelBlock && ~isempty(loggedSignals)
                % with model blocks, replace the top-most variable in the 
                % DataInterface because it is not generated in model.c but 
                % in xil_interface.c (it is passed as argument)
                this.updateBaseRegionImplementation(loggedSignals);
            end
        end
        
        % abstract away the differences between the update tasks
        function tasks = getUpdateTasks(this)
            if this.isSampleTimeIndependent
                % process inherited task
                tasks = this.getUpdateTasksForTimingMode('INHERITED');
                % validateTimingProperties already asserts that output and
                % update functions should be combined for pure export 
                % functions.
            else
                % process all update tasks
                tasks = this.getAllUpdateTasks;
            end
        end
        
        % return output tasks for all the supported rates
        function tasks = getAllOutputTasks(this)
            allowedRates = this.getAllowedRates;            
            tasks = [];
            for rateIdx = 1:length(allowedRates)
                rate = allowedRates{rateIdx};
                tasks = [tasks this.getOutputTasksForTimingMode(rate)]; %#ok<AGROW>
            end
            tasks = [tasks this.getAsyncOutputTasksFromUnionTM];
        end
        
        % return update tasks for all the supported rates
        function tasks = getAllUpdateTasks(this)
            allowedRates = this.getAllowedRates;            
            tasks = [];
            for rateIdx = 1:length(allowedRates)
                rate = allowedRates{rateIdx};
                tasks = [tasks this.getUpdateTasksForTimingMode(rate)]; %#ok<AGROW>
            end
        end                                 
         
        % get output tasks for a particular timingMode
        %
        % periodic tasks will be sorted
        function tasks = getOutputTasksForTimingMode(this, timingMode)
            % output functions correspond to output tasks to schedule
            %
            % Note: see validateTimingProperties
            tasks = this.getTasks('output', timingMode);                        
        end 
        
        % get update tasks for a particular timingMode
        %
        % periodic tasks will be sorted
        function tasks = getUpdateTasksForTimingMode(this, timingMode)
            % update functions correspond to update tasks to schedule
            %
            % Note: see validateTimingProperties
            tasks = this.getTasks('update', timingMode);            
        end    
        
        % get initialize tasks
        %
        function tasks = getInitializeTasks(this)
            timingMode = 'ONESHOT';
            tasks = this.getTasks('init', timingMode);            
        end
        
        % get terminate tasks
        %
        function tasks = getTerminateTasks(this)
            timingMode = 'ONESHOT';
            tasks = this.getTasks('term', timingMode);            
        end

        
        % get rates for a particular timingMode
        % 
        % periodic rates will be sorted
        function timingProperties = getRates(this, timingMode)
            % TimingProperties corresponds to rates
            %
            % Note: see validateTimingProperties
            switch timingMode
                case 'PERIODIC'
                    % sort tasks so base rate is always processed first
                    sortRates = true;
                case {'INHERITED' 'ONESHOT' 'ASYNCHRONOUS' 'CONTINUOUS' 'INITIALIZE' 'RESET' 'RESETWITHINIT' 'TERMINATE' 'UNION'}
                    sortRates = false;              
                otherwise
                    assert(false, 'Unknown timingMode: %s', timingMode);
            end                        
            timingProperties = this.CodeInfo.TimingProperties;
            numTimings = length(timingProperties);
            keepIndices = zeros(1, numTimings);
            for i=1:numTimings
                timing = timingProperties(i);
                % keep requested rates
                if strcmp(timing.TimingMode, timingMode)
                    keepIndices(i) = true;
                end
            end
            % remove unwanted rates
            timingProperties = timingProperties(logical(keepIndices));
            if sortRates
                if ~isempty(timingProperties)
                    timingProperties = this.getSortedTimingProperties(timingProperties);
                end
            end
        end
                 
        % returns true if this is explicit tasking mode in export function  
        function val = hasPeriodicExplicitTasking(this)
            val = false;
            if this.isExportFunctions
                tmProp = this.CodeInfo.TimingProperties;
                for idx=1:length(tmProp)
                    if strcmp(tmProp(idx).TimingMode, 'PERIODIC') &&...
                            strcmp(tmProp(idx).TaskingMode, 'EXPLICIT_TASKING')
                    val = true;
                    break;
                    end
                end                        
            end            
        end 
        
        %  By default, we will treat async as inherited
        function [sampleTimeCExpr sampleOffsetCExpr] = getAsyncSampleTimeString(this) %#ok
            sampleTimeCExpr = 'INHERITED_SAMPLE_TIME';
            sampleOffsetCExpr = '0';
        end        

        % returns true if there are any outputs with constant / infinite 
        % sample time
        function hasConstOutput = hasConstOutput(this)
            indices = this.getOutputPortsForRates(this.getRates('ONESHOT'));
            if isempty(indices)
                hasConstOutput = false;
            else
                hasConstOutput = true;
            end
        end    
                
        % returns true if this component has any input or output ports
        function hasPorts = hasPorts(this)
           hasPorts = ~isempty(this.CodeInfo.Inports) || ~isempty(this.CodeInfo.Outports); 
        end
        
        % returns true if the component is sample time independent (e.g. no
        % periodic rates)
        %
        % note that component is sample time independent even if it
        % hasConstOutputs
        function isSTIndependent = isSampleTimeIndependent(this)                                    
            if this.isExportFunctions
                if this.isRightClickSubsystemExportFcns || this.isTopModelExportFunctions
                    % always sample time independent, even for top model
                    % periodic export functions (see also getOutputTasks)
                    isSTIndependent = true; 
                else                                                        
                    % Even if the model exports functions, it is possible that
                    % it also has tasks with periodic rates. Also, for these
                    % models, if the model does not have any tasks with
                    % periodic rates, codeInfo.TimingProperties will still
                    % contain a periodic base rate, so we can't use the
                    % getRates method in this case.
                    if isempty(this.getOutputTasksForTimingMode('PERIODIC'))
                        isSTIndependent = true;
                    else
                        isSTIndependent = false;
                    end
                end
            else
                if isempty(this.getRates('PERIODIC'))
                    isSTIndependent = true;
                else
                    isSTIndependent = false;
                end
            end
        end
                                 
        function isMultiTasking = isMultiTasking(this)
            if this.isExportFcnDiagram
                isMultiTasking = false;
            else
                % only multitasking if there are multiple periodic tasks
                periodicTasks = this.getOutputTasksForTimingMode('PERIODIC');
                switch length(periodicTasks)
                    case 0
                        % if *ALL* periodic tasks are virtual then they can be
                        % optimized away by code generation (model ref). Otherwise there
                        % will at least be a stub function for each rate.
                        %
                        % check for this case by inspecting the number of
                        % periodic rates
                        periodicRates = this.getRates('PERIODIC');
                        switch length(periodicRates)
                            case {0 1}
                                % inherited sample time or single rate
                                %
                                % ==> singletasking
                                isMultiTasking = false;
                            otherwise
                                % multirate - can't determine tasking mode
                                rtw.connectivity.ProductInfo.error('target', ...
                                    'CodeInfoOptimizedOutputTask', ...
                                    this.ComponentForDiagnostics);
                        end
                    case 1
                        % 1 periodic tasks
                        isMultiTasking = false;
                    otherwise
                        % multitasking
                        isMultiTasking = true;
                end
            end
        end
               
        function isMultiRate = isMultiRate(this)
            % only multirate if there are multiple periodic rates
            %
            % (const output ports are not counted)
            periodicRates = this.getRates('PERIODIC');
            if length(periodicRates) > 1
                % multirate
                isMultiRate = true;
            else
                % 0 (i.e. inherited) or 1 periodic rates
                isMultiRate = false;                
            end            
        end                                              
        
        % returns true if this a top model Export Functions Diagram
        function val = isTopModelExportFunctions(~)
            % default value, sub-classes can override this value
            val = false;
        end
        
        % returns true if this a top model or model ref export functions
        % diagram
        function isExportFcnDiagram = isExportFcnDiagram(this)
            isExportFcnDiagram = ...
                this.ClientInterface.isExportFcnModel(this.ConfigInterface);
        end

        % A model is considered to export functions if it
        % is exporting function call subsystems (i.e. either an
        % export function diagram or older right-click export functions).
        % In these cases, an expInports specification will be available.
        function hasExpInportsSpecification = hasExpInportsSpecification(this)            
            hasExpInportsSpecification = ~isempty(this.expInports);
        end                
        
        % defines when to use the SIL/PIL export functions scheduling scheme
        function isExportFunctions = isExportFunctions(this)
            
            %Determine if all non reset output functions (if there are any)
            %are in fact Simulink functions. If they are then treat this
            %model as Export Functions. Otherwise don't
            allOutFunctionsAreSLFunOrNotReset = true;
            allOutputFunctions = this.CodeInfo.OutputFunctions;
            if isempty(allOutputFunctions)
                allOutFunctionsAreSLFunOrNotReset = false;
            else
                for idx = 1:numel(allOutputFunctions)
                    if ~this.isa(allOutputFunctions(idx), 'SimulinkFunctionInterface') && ...
                            ~ismember(allOutputFunctions(idx).Timing.TimingMode, {'RESET','RESETWITHINIT'})
                        %Found a none RESET non Simulink function output
                        %function. So break out.
                        allOutFunctionsAreSLFunOrNotReset = false;
                        break;
                    end
                end
            end
                        
            isExportFunctions = this.hasExpInportsSpecification || ...
                allOutFunctionsAreSLFunOrNotReset || this.isExportFunctionsHook();
        end
        
        % return true if the SIL/PIL component under test has server
        % functions.
        function hasIt = hasServerFunctions(this)
            hasIt = ~isempty(this.getServerFunctionsInfo);
        end   
        
        function hasResetFunction = hasResetFunction(this)
            hasResetFunction = ~isempty(this.getOutputTasksForTimingMode('RESET')) || ...
                ~isempty(this.getOutputTasksForTimingMode('RESETWITHINIT'));
        end

        % return true if the SIL/PIL component under test has caller
        % functions calling into Simulink functions outside XIL component
        function hasIt = hasUndefinedCallerFunctions(this)
            hasIt = ~isempty(this.getUndefinedCallerFunctionsInfo);            
        end
        
        % getter method for ServerFunctionsInfo
        function serverFcnsInfo = getServerFunctionsInfo(this)
            serverFcnsInfo = this.ServerFunctionsInfo;
        end
        
        % returns all CallerFunctionsInfo (both defined and undefined)
        function callerFcnsInfo = getAllCallerFunctionsInfo(this)
            callerFcnsInfo = this.CallerFunctionsInfo;
        end
        
        % returns all the undefined CallerFunctionsInfo
        function callerFcnsInfo = getUndefinedCallerFunctionsInfo(this)
            allCallerFunctionsInfo = this.getAllCallerFunctionsInfo;
            callerFcnsInfo = [];
            for callFcnIdx = 1:length(allCallerFunctionsInfo)
                callerFcnInfo = allCallerFunctionsInfo(callFcnIdx);
                if ~callerFcnInfo.getIsDefined
                    callerFcnsInfo = [callerFcnsInfo callerFcnInfo]; %#ok<AGROW>
                end
            end
        end
        
        % returns true if a given OutputFunction is for a Server function
        function [isSvrFcn, svrFcnInfo] = isServerOutputFunction(this, codeInfoData)
            isSvrFcn = false;
            svrFcnInfo = [];
            
            % codeInfoData can be RTW.FunctionInterface or
            % RTW.AutosarClientCall, in the latter case isSvrFcn is false
            if this.isa(codeInfoData, 'FunctionInterface') && ...
                    this.hasServerFunctions
                serverFunctionsInfo = this.getServerFunctionsInfo;
                for svrIdx = 1:length(serverFunctionsInfo)
                    serverFunctionInfo= serverFunctionsInfo(svrIdx);
                    if (codeInfoData == serverFunctionInfo.getCodeInfoData)
                        isSvrFcn = true;
                        svrFcnInfo = serverFunctionInfo;
                        break;
                    end
                end
            end
        end
        
        % query: Given a target type.Identifier == 'origTypeId', 
        % Check if the identifier is replaced via a perl script.
        % Return replacedId.
        function replacedId = getReplacedTypeIdentifier(this, origTypeId)
            replacedId = origTypeId;
            if ~isempty(this.typeReplaceMap) && ...
                    this.typeReplaceMap.isKey(origTypeId)
                replacedId = this.typeReplaceMap(origTypeId);
            end
        end                
        
        function enableDisableFcns = getAllExportFunctionsEnableDisableFcns(this, isEnableFcn)
            % get all non-empty export functions enable / disable functions
            enableDisableFcns = [];
            numPorts = this.getNumExportFunctionsInports;
            for portIdx = 1:numPorts                                                
                enableDisableFcn = this.getExportFunctionsEnableDisableFcn(portIdx, isEnableFcn);
                if ~isempty(enableDisableFcn)       
                    enableDisableFcns = [enableDisableFcns enableDisableFcn]; %#ok<AGROW>
                end
            end
        end
        
        % map from expInports to CodeInfo OutputFunction
        function outputFcn = getExportFunctionsCodeInfoOutputFunction(this, inportIdx)           
            outputFcn = [];
            [isFcn, codeInfoData, codeInfoIndex] = this.getExportFunctionsCodeInfoData(inportIdx);
            if isFcn
                outputFcn.codeInfoData = codeInfoData;                               
                % use codeInfoIndex as unique id
                outputFcn.id = codeInfoIndex;
            end            
        end
        
        % map from expInports to CodeInfo Inport
        function inport = getExportFunctionsCodeInfoInport(this, inportIdx)
            inport = [];
            [isFcn, codeInfoData, codeInfoIndex] = this.getExportFunctionsCodeInfoData(inportIdx);
            if ~isFcn
                inport.codeInfoData = codeInfoData;                               
                inport.codeInfoIndex = codeInfoIndex;
            end            
        end     

        % map from expInports index to the data port index. Returns 0 if
        % the expInports of index inportIdx is not a data port (but, for
        % example, a function-call port)
        function idx = getExportFunctionsDataPortIndex(this, inportIdx)
            idx = this.expInportsDataElements(inportIdx);
        end
        
        % return enable / disable fcn + associated CodeInfo OutputFunction
        function enableDisableFcn = getExportFunctionsEnableDisableFcn(this, inportIdx, isEnableFcn)
            enableDisableFcn = [];            
            expInportData = this.expInports(inportIdx);            
            if strcmp(expInportData.PortType, 'fcn_call')
                if isEnableFcn
                    fcn = expInportData.EnableFcn;
                else
                    fcn = expInportData.DisableFcn;
                end                
                if ~isempty(fcn)
                    enableDisableFcn.fcn = fcn;                    
                    % we expect and require an output function in order to
                    % transfer I/O data correctly
                    outputFcn = this.getExportFunctionsCodeInfoOutputFunction(inportIdx);
                    assert(~isempty(outputFcn), 'found enable / disable fcn, but no corresponding output function');
                    enableDisableFcn.outputFcn = outputFcn;                    
                end            
            end
        end               
        
        % determine if export functions inport is latched
        function isLatched = isExportFunctionsLatchedInport(this, inportIdx)
            expInportData = this.expInports(inportIdx);
            isLatched = expInportData.IsLatched;            
        end
        
        % map from codeInfo.Inports to expInports
        function inportIdx = getExportFunctionsInportIndex(this, codeInfoIndex)
            assert(codeInfoIndex > 0, 'codeInfoIndex must be > 0');
            inportIdx = [];
            for currInportIdx=1:this.getNumExportFunctionsInports
                expInportData = this.expInports(currInportIdx);               
                switch expInportData.PortType
                    case 'fcn_call'
                        % not interested in function calls
                        continue;
                    case 'input'
                        % check for match
                        if expInportData.Index == codeInfoIndex
                           % done
                           inportIdx = currInportIdx;
                           break;
                        end                        
                    otherwise
                        assert(false, 'Unexpected expInport PortType: %s', expInportData.PortType);
                end
            end
            assert(~isempty(inportIdx), 'Failed to map from codeInfo.Inport to expInports.');
        end        
        
        % return the number of inports associated with an export functions
        % component
        function numExportFunctionsInports = getNumExportFunctionsInports(this)
           numExportFunctionsInports = length(this.expInports);            
        end
        
        % return the number of export functions "fcn_call" entries in
        % expInports
        function numExportFunctions = getNumExportFunctions(this)
            if isempty(this.expInports)
                numExportFunctions = 0;
            else
                exportFunctionIndices = find(strcmp({this.expInports.PortType}, ...
                    'fcn_call'));
                numExportFunctions = length(exportFunctionIndices);
            end
        end
        
        % get input port indices associated with the given task
        %
        % this function abstracts scheduling details such as tasking mode
        function indices = getInputPortsForTask(this, task)
            if this.canUseDirectReadsAndWrites(task.codeInfoData.Timing)
                indices = [];                
                directReads = task.codeInfoData.DirectReads;
            else
                directReads = this.getEmptyInterface('DataInterface');
                if this.isSampleTimeIndependent
                    indices = this.getInputPortsForRates(task.codeInfoData.Timing);
                else
                    if this.isMultiTasking
                        indices = this.getInputPortsForRates(task.codeInfoData.Timing);
                    elseif ismember(task.codeInfoData.Timing.TimingMode, {'RESET', 'RESETWITHINIT'})
                        indices = this.getInputPortsForRates(task.codeInfoData.Timing);
                    else
                        % find ** all ** ports associated with this task
                        indices = this.getInputPortsForRates(this.getRates('PERIODIC'));
                    end
                end
            end
            % add in additional direct reads
            directReads = this.uniquifyDataInterfaces([directReads, this.getAdditionalDirectReads]);
            for i=1:length(directReads)
                di = directReads(i);
                [isInport, index] = this.isMemberOf('Inports', di);
                if isInport
                    indices = [indices index];  %#ok<AGROW>
                end
            end
        end
        
        % get output port indices associated with the given task
        %
        % this function abstracts scheduling details such as tasking mode
        function indices = getOutputPortsForTask(this, task)
            if this.canUseDirectReadsAndWrites(task.codeInfoData.Timing)
                indices = [];
                directWrites = task.codeInfoData.DirectWrites;
            else
                directWrites = this.getEmptyInterface('DataInterface');
                if this.isSampleTimeIndependent
                    indices = this.getOutputPortsForRates(task.codeInfoData.Timing);
                else
                    if this.isMultiTasking
                        indices = this.getOutputPortsForRates(task.codeInfoData.Timing);
                    elseif ismember(task.codeInfoData.Timing.TimingMode, {'RESET','RESETWITHINIT'})
                        indices = this.getOutputPortsForRates(task.codeInfoData.Timing);
                    else
                        % find ** all ** ports associated with this task
                        indices = this.getOutputPortsForRates(this.getRates('PERIODIC'));
                    end
                end
            end
            % add in additional direct writes
            directWrites = this.uniquifyDataInterfaces([directWrites, this.getAdditionalDirectWrites]);
            for i=1:length(directWrites)
                di = directWrites(i);
                [isOutport, index] = this.isMemberOf('Outports', di);
                if isOutport
                    indices = [indices index];  %#ok<AGROW>
                end
            end
        end                       
        
       % get output port indices associated with the given task
       %
       % this function abstracts scheduling details such as tasking mode
        function indices = getLoggingForTask(this, task)
           if this.isExportFcnDiagram || ...
                   this.isSampleTimeIndependent || ...
                   this.isMultiTasking || ...
                   strcmp(task.codeInfoData.Timing.TimingMode, 'ASYNCHRONOUS')
                indices = this.getLoggingForRates(task.codeInfoData.Timing);
           else               
               % Periodic single tasking scheduling:
               % 
               % find ** all ** sig logs associated with this task
                indices = this.getLoggingForRates(this.getRates('PERIODIC'));
            end
        end
        
        function indices = getLoggingForRates(this, rates)
            indices = this.getPortsForTimingProperties(rates, [this.SigLogs, this.StateLogs]);
        end
        
        function indices = getSignalLoggingIndices(this, indices)
            signals = indices;
            signals(indices > length(this.SigLogs)) = [];
            states = intersect(indices, this.StatesUploadedLikeSignals);
            indices = [signals states];
        end
        
        function indices = getStateLoggingIndices(this, indices)
            indices(indices <= length(this.SigLogs)) = [];
            indices = setdiff(indices, this.StatesUploadedLikeSignals);
        end
       
       function indices = getDataStoresForRates(this, rates)
           % This function returns indices for ALL (not just Global) 
           % DataStores associated with the given rates.   Non-global data
           % stores appear in CodeInfo when the data store has an
           % associated storage class.
           %
           % Note that some global data stores (not non-global data stores 
           % because they never cross model boundaries!) in the list may 
           % just be proxies for global data stores fully captured in a 
           % sub-model CodeInfo (if those data stores are only accessed in 
           % the sub-model) - see ModelRefCodeInfoUtils 
           % "getSubModelGlobalDataStores".
           %
           % In this case, these proxy global data stores are missing
           % details such as TimingProperties and therefore will not return
           % a match during the call to getPortsForTimingProperties.                     
           %
           % To instead get complete shared data store information, see
           % get(Reader|Writer)SharedDataStores.
           %
           indices = this.getPortsForTimingProperties(rates, ...
               this.CodeInfo.DataStores);
       end
       
        % get input port indices associated with the given rates
        function indices = getInputPortsForRates(this, rates)
            indices = this.getPortsForTimingProperties(rates, ...
                                                   this.CodeInfo.Inports);
        end
        
        % get output port indices associated with the given rates
        function indices = getOutputPortsForRates(this, rates)
            ports = this.CodeInfo.Outports;
            numPorts = length(ports);
            numTiming = length(rates);
            
            for timingIdx = 1:numTiming
                if ismember(rates(timingIdx).TimingMode,{'RESET','RESETWITHINIT'})
                    %For reset functions, return all outputs.
                    %This is needed to support Merged outputs
                    indices = 1:numPorts;
                    return;
                end
            end
            indices = this.getPortsForTimingProperties(rates, ...
                ports);
        end                         
                                                
        % returns whether an Inport is variable size 
        function isInportVarSize = isInportVarSize(this, dataInterface)
            % get port idx
            [isInport, index] = this.isMemberOf('Inports', dataInterface);
            assert(isInport, 'dataInterface must be an Inport');
            if isempty(this.getInportSizesIndices(index))
               isInportVarSize = false;  
            else
               isInportVarSize = true;  
            end                                    
        end
        
        % returns whether an Outport is variable size
        function isOutportVarSize = isOutportVarSize(this, dataInterface)
            % get port idx
            [isOutport, index] = this.isMemberOf('Outports', dataInterface);
            assert(isOutport, 'dataInterface must be an Outport');
            if isempty(this.getOutportSizesIndices(index))
               isOutportVarSize = false;  
            else
               isOutportVarSize = true;  
            end                        
        end
        
        % return InportSizes
        function inportSizes = getInportSizes(this, idx)
            if nargin == 1
                inportSizes = this.InportSizes;
            else
                inportSizes = this.InportSizes(idx);
            end
        end
        
        % return port index for InportSize
        function portIdx = getInportSizesPortIdx(this, idx)
           portIdx = this.InportSizesIndices(idx); 
        end
        
        % return InportSizes indices corresponding to inportIndices
        function inportSizesIndices = getInportSizesIndices(this, inportIndices)
            [~, inportSizesIndices] = intersect(this.InportSizesIndices, inportIndices);
        end
        
        % return OutportSizes
        function outportSizes = getOutportSizes(this, idx)
            if nargin == 1
                outportSizes = this.OutportSizes;                
            else
                outportSizes = this.OutportSizes(idx);                
            end
        end
        
        % return port index for OutportSize
        function portIdx = getOutportSizesPortIdx(this, idx)
           portIdx = this.OutportSizesIndices(idx); 
        end
            
        % return OutportSizes indices corresponding to outportIndices
        function outportSizesIndices = getOutportSizesIndices(this, outportIndices)
            [~, outportSizesIndices] = intersect(this.OutportSizesIndices, outportIndices);
        end
        
        % return global data stores
        function globalDS = getGlobalDataStores(this)
            globalDS = this.globalDS;
        end
        
        % return shared local data stores
        function result = getSharedLocalDataStores(this, idx)
            if nargin == 1
                result = this.sharedLocalDS;
            else
                result = this.sharedLocalDS(idx);
            end
        end
        
        % return all shared data stores (global + shared local data stores)
        function sharedDS = getAllSharedDataStores(this)
            sharedDS = this.allSharedDS;
        end
        
        % return all signal logging data interfaces
        function dataInterfaces = getSigLogs(this)
           dataInterfaces = this.SigLogs; 
        end
        
        function setStateLogs(this, dataInterfaces, states)
            this.StateLogs = dataInterfaces;

            if isempty(states)
                this.StatesUploadedLikeSignals = [];
            else
                numStates = length(states);
                this.StatesUploadedLikeSignals = zeros(1, numStates);
                notSignalLike = false(1, numStates);
                for i = 1:numStates
                    state = states(i);
                    this.StatesUploadedLikeSignals(i) = state.getDataInterfaceIdx();
                    notSignalLike(i) = ~state.uploadAfterUpdate();
                end
                this.StatesUploadedLikeSignals(notSignalLike) = [];
            end
        end
        
        % set the list of signal logging data interfaces that must be
        % processed
        function setSigLogs(this, dataInterfaces)
            this.SigLogs = dataInterfaces;
            % we assume that dataInterfaces have already been validated,
            % however, one final validation is that each dataInterface 
            % is associated with a task that we will schedule
            tasks = this.getOutputTasks;
            sigLogIndices = [];
            for taskIdx = 1:length(tasks)
                task = tasks(taskIdx);
                % Do not consider Simulink Functions as we don't support
                % logging of signals inside the Simulink functions because
                % these signals are asynchronous.
                if ~this.isServerOutputFunction(task.codeInfoData)
                    % get siglog indices associated with all tasks
                    taskSigLogIndices = this.getLoggingForTask(task);
                    assert(isempty(intersect(taskSigLogIndices, sigLogIndices)), ...
                        'Signal logging data is associated with multiple tasks!');
                    sigLogIndices = [sigLogIndices taskSigLogIndices]; %#ok<AGROW>
                end
            end                        
            % handle constant data
            constSigLogIndices = this.getLoggingForRates(this.getRates('ONESHOT'));
            assert(isempty(intersect(constSigLogIndices, sigLogIndices)), ...
                       'Constant signal logging data is also associated with a task!');
            sigLogIndices = [sigLogIndices constSigLogIndices];
            if this.UseMF0Logging && this.hasModelWideEvents
                termSigLogIndices = this.getLoggingForRates(this.getRates('TERMINATE'));
                initSigLogIndices = this.getLoggingForRates(this.getRates('INITIALIZE'));
                assert(isempty(intersect(termSigLogIndices, sigLogIndices)), ...
                       'Terminate logging data is also associated with a task or constant!');
                assert(isempty(intersect(initSigLogIndices, sigLogIndices)), ...
                       'Initialize logging data is also associated with a task or constant!');
                sigLogIndices = [sigLogIndices termSigLogIndices initSigLogIndices];
            end
            expectedSigLogIndices = 1:length(this.SigLogs);
            assert(isempty(setdiff(expectedSigLogIndices, sigLogIndices)), ...
                   'Signal logging data will not be scheduled!');
        end
        
        % return Reader and Reader/Writer shared data stores
        function result = getReaderSharedDataStores(this, idx)
            if nargin == 1
                result = this.readerSharedDS;
            else
                result = this.readerSharedDS(idx);
            end            
        end               
        
        % return Writer and Reader&Writer shared data stores
        function result = getWriterSharedDataStores(this, idx)
            if nargin == 1
                result = this.writerSharedDS;    
            else
                result = this.writerSharedDS(idx);
            end            
        end
        
        % Return the indices for data stores read in this task (indices into this.readerSharedDS)
        % See implementation for criteria used to determine which DS to transfer.
        function dsIndices = getReaderSharedDataStoresForTask(this, task, varargin)
            isReader = true;
            dsIndices = this.getSharedDataStoresForTask(task, isReader, varargin{:});
        end
        
        % Return the indices for data stores written in this task (indices into this.writerSharedDS)
        % See implementation for criteria used to determine which DS to transfer.
        function dsIndices = getWriterSharedDataStoresForTask(this, task, varargin)
            isReader = false;
            dsIndices = this.getSharedDataStoresForTask(task, isReader, varargin{:});
        end
        
        % Given a DataStore, return a unique list of the rates of its 
        % readers and writers
        function dsRates = getRatesForDataStore(this, ds)
            dsRates = [];
            dataWrites = this.getArray(ds.DataWrites);
            for dwIdx = 1:length(dataWrites)
                dsRates = [dsRates dataWrites(dwIdx)]; %#ok<AGROW>
            end
            dataReads = this.getArray(ds.DataReads);
            for drIdx = 1:length(dataReads)
                dsRates = [dsRates dataReads(drIdx)]; %#ok<AGROW>
            end
            dsRates = unique(dsRates, 'stable');
        end
        
        function indices = getWorkspaceParams(this)
            indices = [];
            for idx = 1:numel(this.CodeInfo.Parameters)
                if this.isWorkspaceParam(this.CodeInfo.Parameters(idx))
                    indices = [indices, idx]; %#ok<AGROW>
                end
            end
        end
        
        function isBaseWorkspaceParam = isBaseWorkspaceParam(this, dataInterface)            
            sid = this.getParamSID(dataInterface);
            isBaseWorkspaceParam = strcmp(sid,'');
        end                                
                        
        function isModelWorkspaceParam = isModelWorkspaceParam(this, dataInterface)
            sid = this.getParamSID(dataInterface);
            isModelWorkspaceParam = ...
                (~isempty(sid) && contains(sid, '#var:'));
        end
                        
        function modelWorkspaceParametersCanBeUninitialized = modelWorkspaceParametersCanBeUninitialized(this) %#ok<MANU>
        % model workspace params can typically be uninitialized, e.g. if a
        % parameter is mapped to a storage class that does not generate
        % data initialization
            modelWorkspaceParametersCanBeUninitialized = true;
        end

        function isWorkspaceParam = isWorkspaceParam(this, dataInterface)
            isWorkspaceParam = isBaseWorkspaceParam(this, dataInterface) || isModelWorkspaceParam(this, dataInterface);
        end                                
                        
        % returns true if the dataInterface is constant
        function isConstant = isDataInterfaceConstant(this, dataInterface)
            isConstant = this.isa(dataInterface.Implementation, 'Literal');
            if isConstant
                assert(this.isMemberOf('Inports', dataInterface),...
                    'Only inport data interfaces can have Literal implementation');
            end
        end
        
        % get data interfaces indices associated with the given function
        function indices = getDataInterfacesForFunction(this, functionInterfaces, interfaceType)
            
            %Get data interfaces for current type
            dataInterfaces = getDataInterfacesForType(this, functionInterfaces, interfaceType);
                          
            if ~isempty(dataInterfaces)
                if strcmp(interfaceType, 'output')
                    % return all non-constant outputs
                    allIndices = 1:length(dataInterfaces);
                    constIndices = this.getOutputPortsForRates(this.getRates('ONESHOT'));
                    indices = setdiff(allIndices, constIndices);
                else
                    indices = this.matchInterfacesToFunctions(dataInterfaces, functionInterfaces);
                end
                queuedIndices = this.isQueuedDataInterface(dataInterfaces, indices);
                indices = setdiff(indices, queuedIndices);
            else
                indices = [];
            end
            % assert that WorkspaceParams are never
            % associated with interface functions
            if strcmp(interfaceType, 'parameter')                
                if ~isempty(intersect(indices, this.getWorkspaceParams))
                    assert(false, 'Workspace parameter is part of interface function prototype');
                end
            end
        end
        
        function paramIndices = getParamIndicesForFunctionImpl(this, functionInterface) %#ok<INUSD>
            % default is no parameters
            paramIndices = [];
        end  
        
        function paramIndices = getInstPArgumentIndices(this, inTheLoopType) %#ok<INUSD>
            % default is no parameters
            paramIndices = [];
        end  
                
        % flag whether dataInterface is in InternalData, and if so, return its
        % index
        function [isInternalData, index] = isInternalData(this, dataInterface)
            [isInternalData, index] = this.isMemberOf('InternalData', dataInterface);
        end
        
        % flag whether dataInterface is a DataStore, and if so, return its
        % index
        function [isDataStore, index] = isDataStore(this, dataInterface)
            [isDataStore, index] = this.isMemberOf('DataStores', dataInterface);
        end

        % resolve any given I/O dataInterface to its root location in CodeInfo
        function [type, index] = resolveIODataInterface(this, dataInterface)
            type = [];    
            [isInport, index] = this.isMemberOf('Inports', dataInterface);                                    
            if isInport
               type = 'Inport';               
               return;
            end
            [isOutport, index] = this.isMemberOf('Outports', dataInterface);
            if isOutport
               type = 'Outport';
               return;
            end                
            [isParameter, index] = this.isMemberOf('Parameters', dataInterface);
            if isParameter
               type = 'Parameter';
               return;
            end
            [isSharedDS, index] = this.isMemberOf('SharedDS', dataInterface);
            if isSharedDS
               type = 'DataStore';
               return;
            end
            [isInternalData, index] = this.isMemberOf('InternalData', dataInterface);
            if isInternalData
               type = 'InternalData';
               return;               
            end  
            [isSigLog, index] = this.isMemberOf('SigLog', dataInterface);
            if isSigLog
               type = 'SigLog';
               return;
            end
            % should always be able to resolve the data interface
            assert(~isempty(type), 'Could not resolve data interface');
        end                                       
        
        function isStatic = isDataInterfaceStatic(this, dataInterface)
            isStatic = zeros(1,length(dataInterface));
            for i=1:length(dataInterface)
                di = dataInterface(i);
                if isempty(di.Implementation)
                    %If no Implementation skip interface
                    continue;
                end

                storageSpecifiers = split(this.getStorageSpecifier(di.Implementation));
                for j = 1:numel(storageSpecifiers)
                    storageSpecifier = storageSpecifiers{j};
                    isStatic(i) = strcmp(storageSpecifier,'static');                    
                end
            end
        end
        
        function isReadOnly = isDataInterfaceReadOnly(this, dataInterface) 
            isReadOnly = false(1,length(dataInterface));
            for i=1:length(dataInterface)
                di = dataInterface(i);
                if isempty(di.Implementation)
                    %If no Implementation skip interface
                    continue;
                end
                
                if this.isa(di.Implementation,'TypedCollection')
                    collectionElements = rtw.connectivity.CodeInfoUtils.getArray(di.Implementation.Elements);
                    
                    for j=1:length(collectionElements)
                        isReadOnly(i) = isReadOnly(i) || rtw.connectivity.CodeInfoTypeUtils.isReadOnly(collectionElements(j).Type);
                    end
                else
                    isReadOnly(i) = rtw.connectivity.CodeInfoTypeUtils.isReadOnly(di.Implementation.Type) || ...
                        contains(this.getStorageSpecifier(di.Implementation),'const');
                end
            end
        end
        
        function condition = getCodeVariant(~, dataInterface) 
            condition = '';
            if ~isempty(dataInterface.VariantInfo)
                variantInfo = dataInterface.VariantInfo;
                if isprop(variantInfo,'NetCGVCEPoundIf')
                    condition = variantInfo.NetCGVCEPoundIf;
                else
                    condition = variantInfo.CodeVariantCondition;
                end
            end
        end
        
        % return the storage specifier string for an implementation
        function storageSpecifier = getStorageSpecifier(this, implementation)
            switch this.class(implementation)
                case {'Variable', ...
                        'StructAccessorVariable', ...
                        'PointerVariable', ...
                        'CustomVariable'}
                    storageSpecifier = implementation.StorageSpecifier;
                case {'StructExpression', ...
                        'ArrayExpression',  ...
                        'PointerExpression', ...
                        'ClassMethodExpression', ...
                        'ClassMemberExpression', ...
                        'StaticMemberExpression'}
                    % recurse to a terminal Variable or PointerVariable
                    % case
                    storageSpecifier = this.getStorageSpecifier(implementation.BaseRegion);
                case 'AutosarCalibration'
                    baseRegion = this.getAutosarCalibrationBaseRegion(implementation);
                    storageSpecifier = this.getStorageSpecifier(baseRegion);      
                case {'CustomExpression', ...
                        'Literal', ...
                        'BasicAccessFunctionExpression'}
                    % only Variable's have a storage specifier
                    % we don't know the storage specifier for a
                    % CustomExpression - return "unknown"
                    storageSpecifier = 'unknown';
                case 'TypedCollection'
                    elements = rtw.connectivity.CodeInfoUtils.getArray(implementation.Elements);
                    storageSpecifier = elements(1).StorageSpecifier;
                    %Use storageSpecifier from first element. This might
                    %need to change as we extend support for
                    %TypedCollection items.
                otherwise
                    assert(false, 'Unexpected implementation class: %s', ...
                        class(implementation));
            end
        end                                
        
        function dispString = getDataInterfaceString(this, dataInterface, dataInterfaceType)
            % get a description of a DataInterface based on its Type, SID
            % and GraphicalName
            
            switch lower(dataInterfaceType)
                case {'inport', 'input'}
                    sidString = this.getDisplayStringFromSID(dataInterface.SID);
                    dispString = ['Inport ' dataInterface.GraphicalName ' (' sidString ')'];
                case {'outport', 'output'}
                    sidString = this.getDisplayStringFromSID(dataInterface.SID);
                    dispString = ['Outport ' dataInterface.GraphicalName ' (' sidString ')'];
                case 'parameter'
                    if isempty(dataInterface.SID)
                        % SID is empty for workspace variables
                        dispString = ['Workspace parameter ''' dataInterface.GraphicalName ''''];
                    elseif contains(dataInterface.SID, '#var:')
                        dispString = ['Model workspace parameter ''' dataInterface.GraphicalName ''''];
                    else
                        sidString = this.getDisplayStringFromSID(dataInterface.SID);
                        dispString = ['Parameter ' dataInterface.GraphicalName ' in ' sidString];
                    end
                    dispString = this.getParameterInterfaceString_Hook(dataInterface, dispString);
                case {'datastore', 'dsread', 'dswrite', 'sharedlocaldatastore'}
                    dispString = ['Data store ''' dataInterface.GraphicalName ''''];
                case {'internaldata', 'inputsize', 'outputsize', 'internal'}
                    dispString = ['Internal data ''' dataInterface.GraphicalName ''''];
                case {'callerfcninputarg', 'callerfcnoutputarg','callerfcninputoutputarg'}
                    dispString = 'Caller function Argument';
                case {'queuedfcninputarg', 'queuedfcnoutputarg'}
                    dispString = 'Queued I/O Argument';
                case 'siglog'
                    if this.UseMF0Logging
                        dispString = ['Logged signal ''' dataInterface.GraphicalName ''];
                    else
                        url = dataInterface.SID;
                        % parse out the Simulink URL
                        tokens = regexp(url, '(.*?)#out:(.*)', 'tokens', 'once');
                        sid = tokens{1};
                        port = tokens{2};
                        sidString = this.getDisplayStringFromSID(sid);
                        dispString = ['Logged signal "' ...
                            dataInterface.GraphicalName ...
                            '" (' sidString ':' port ')'];
                    end
                case 'statelog'
                    dispString = ['Logged state ''' ...
                        dataInterface.GraphicalName ...
                        ''' (' this.getDisplayStringFromSID(dataInterface.SID) ')'];
                case 'unspecified'
                    % resolve to type and recurse
                    dataInterfaceType = this.resolveIODataInterface(dataInterface);
                    dispString = this.getDataInterfaceString(dataInterface, dataInterfaceType);
                otherwise
                    assert(false, 'Unsupported data interface type: %s', ...
                        dataInterfaceType);
            end
        end
               
        % find ports associated with a TimingProperties (e.g. Task or Rate
        % Timing Interfaces)
        function indices = getPortsForTimingProperties(this, timingProperties, ports)
            if isempty(ports)
                indices = [];
            else
                numPorts = length(ports);
                numTiming = length(timingProperties);
                % init match array
                matchArray = zeros(1, numPorts);
                for timingIdx = 1:numTiming
                    timing = timingProperties(timingIdx);                    
                    for portIdx = 1:numPorts
                        port = ports(portIdx);
                        % compare Timing property values
                        if ~isempty(port.Timing)
                            if (strcmp(port.Timing.TimingMode, 'UNION'))
                                unionInfo = this.getArray(port.Timing.UnionTimingInfo);
                                for j = 1:length(unionInfo)
                                    if rtw.connectivity.CodeInfoUtils.isEquivalentTo(...
                                            timing, unionInfo(j))
                                        matchArray(portIdx) = true;
                                        break;
                                    end
                                end
                            else
                                if rtw.connectivity.CodeInfoUtils.isEquivalentTo(...
                                        timing, port.Timing)
                                    matchArray(portIdx) = true;
                                end
                            end
                        end
                    end
                end
                % return indices of matching ports
                indices = find(matchArray);
            end
        end            
        
        function allowAsync = isAsyncAllowed(this)
            allowedRates = this.getAllowedRates;
            allowAsync = any(strcmp('ASYNCHRONOUS', allowedRates));
        end
        
        function hasModelWideEvents = hasModelWideEvents(this)
            hasModelWideEvents = false;
            timingProperties = this.getCodeInfo.TimingProperties;
            for idx =1:numel(timingProperties)
                if timingProperties(idx).SamplePeriod == inf && timingProperties(idx).SampleOffset > 0                    
                    hasModelWideEvents = true;
                    return;
                end
            end
        end
        
        function hasParameterChangeEvent = hasParameterChangeEvent(this)
            hasParameterChangeEvent = false;
            timingProperties = this.getCodeInfo.TimingProperties;
            for idx =1:numel(timingProperties)
                if timingProperties(idx).SamplePeriod == inf && timingProperties(idx).SampleOffset == 0                    
                    hasParameterChangeEvent = true;
                    return;
                end
            end
        end
        
        % return any additional input indices. For most systems transfer
        % all inputs with the Initialize timing property.
        % Overridden in subclasses
        function inportIndices = getAdditionalInitializeInportIndices(this, inportIndices)
            inportIndices = unique([inportIndices ...
                this.getInputPortsForRates(this.getRates('INITIALIZE'))]);
        end
        
        function hasDSMWritesInInit = hasDSMWritesInInit(this)
            %Does this model contain Data Store Writes in Initialize
            %functions. This flag captures the true state of the loaded
            %codeInfo before it is modified to add possibly missing
            %Data Store Read and Writes for Initialize and Terminate Rates.
            hasDSMWritesInInit = this.HasDSMWritesInInit;
        end
        function [emptyImplementations, dispString] = checkForEmptyImplementations(this, ...
                dataInterfaces, ...
                dataInterfaceType)
            dispString = '';
            emptyImplementations = false;
            % find data interfaces with empty implementations.  Allow the
            % interface to be empty if the interface is variant disabled.
            
            for i=1:length(dataInterfaces)
                dataInterface = dataInterfaces(i);
                if (isempty(dataInterface.VariantInfo) || ...
                        this.isActiveVariant(dataInterface.VariantInfo)) ...
                        && isempty(dataInterface.Implementation)
                    emptyImplementations = true;
                    % build disp string
                    diString = this.getDataInterfaceString(dataInterface, dataInterfaceType);
                    dispString = [dispString ...
                        diString ...
                        newline]; %#ok<AGROW>
                end
            end
        end
        
        function dataInterfaces = getClassConstructorActualArgs(this)
            % get the data interfaces associated to the ClassConstructor
            % actual arguments
            if ~isempty(this.CodeInfo.ConstructorFunction)
                actualArgs = this.CodeInfo.ConstructorFunction.ActualArgs;
                numArgs = numel(actualArgs);
                % currently we expect only one argument in the constructor
                % and it is the InstP struct
                assert(numArgs <= 1, 'more than one argument in class constructor function');
                dataInterfaces = actualArgs;
            else
                dataInterfaces = [];
            end
        end

        function dataInterface = getEmptyInterface(this, class)
            dataInterface = rtw.connectivity.CodeInfoUtils.getEmptyInterfaceCommon(...
                class, this.UseMF0CodeDescriptor);
        end
        
        function capiData = initCAPIData(this, capiStruct)
            if this.UseMF0CodeDescriptor
                numInterfaces = numel(capiStruct.CAPIData.DataInterfaces);
                mf0CAPIData = repmat(coder.descriptor.DataInterface.empty, 1, numInterfaces);
                for idx =1:numInterfaces
                    mf0CAPIData(idx) = capiStruct.CAPIData.DataInterfaces(idx).serializeMF0(...
                        this.getMF0ModelInstance);
                end
                capiData = struct('DataInterfaces', mf0CAPIData);
            else
                capiData = capiStruct.CAPIData;
            end
        end
        
        function type = getDataInterfaceHostType(this, dataInterface, targetType) %#ok<STOUT,INUSD>
            % empty implementation, overwritten by subclasses
        end
        
        function result = hasPromotedModelArguments(~, ~)
            % default implementation, overwritten by subclasses
            result = false;
        end
        
        function status = hasValidCodeDescriptorHandle(this)
            % check if we have access to the componentInterface object
            status = ~isempty(this.CodeInfo);
        end
        
        function modelH = getModelHeader(~)
            % default implementation, overwritten by subclasses
            modelH = [];
        end        
                
        function member = getDefaultMember(this, objType)
            % workaround CODEINFR-1052
            if this.UseMF0CodeDescriptor
                member = objType.DefaultMember;
                className = class(member);
                offset = eval([className '(1)']);
                member = member + offset;
            else
                member = objType.DefaultMember;
            end
        end
        
        function [instParamArgs] = getInstanceSpecificParameters(~)
            % default implementation, overwritten by subclasses
            instParamArgs = [];
        end
        
        function [hasName] = hasInstanceSpecificParameterName(~, names)
            % return a logical array, true for names that contain the 
            % 'InstParameterArgument' string
            hasName = contains(names, 'InstParameterArgument');
        end
        
        function baseRegion = getAutosarCalibrationBaseRegion(this, calibration)
            assert(this.isa(calibration, 'AutosarCalibration'), ...
                'getAutosarCalibrationBaseRegion should only be called with AutosarCalibration implementations');
            if this.UseMF0CodeDescriptor
                baseRegion = calibration.BaseRegion;
            else
                baseRegion = calibration.ParameterBaseRegion;
            end
        end
    end
             
    methods (Access = 'public', Static = true)        
        % convert double to string with the same precision as BdDbl in
        % slutil/utdtype.cpp.   BdDbl is used to write doubles such as
        % SamplePeriod, SampleOffset, FracSlope and Bias to the .rtw file
        % during code generation.   Values with this level of precision are
        % known to work well in s-functions such as SIL & Accelerator, so it
        % is a good choice for use in s-functions derived from CodeInfo.
        %
        % see also g743266 and g609073
        function strOut = double2str(doubleIn)
            assert(isa(doubleIn, 'double'), 'doubleIn must be a double.');
            assert(isscalar(doubleIn), 'doubleIn must be a scalar.');
            % mimic slsv_flt2ascii.cpp BDRTW_CONCISE used by BdDbl
            %
            % first try "compact" 15 digit precision, then 17 if required
            strOut = num2str(doubleIn, 15);
            if doubleIn == eval(strOut)
               return; 
            end
            strOut = num2str(doubleIn, 17);   
            assert(doubleIn == eval(strOut), ...
                   'double2str: bad precision');
        end
        
        % accurateDbl2Str
        %
        % due to extra undesired digits being printed with double2str for
        % some double values (i.e. 3276.7 becomes '3276.700000003'),
        % this function iterates through a range of precisions to generate
        % the shortest possible accurate string.
        % in addition, if the double is an integer value, the function
        % returns an integer string and true for isIntStr (used for 
        % early return in fixptBase10Dbl2Str below). otherwise
        % isIntStr is false.
        function [accurStrOut, isIntStr] = accurateDbl2Str(doubleIn)
            assert(isa(doubleIn, 'double'), 'doubleIn must be a double.');
            assert(isscalar(doubleIn), 'doubleIn must be a scalar.');
            % mimic fixpoint/fix_fat2txt.cpp : fhpAccurateStringFromDouble
            
            % check whether doubleIn is an integer value
            if rem(doubleIn, 1) == 0.0
                % doubleIn must be a whole number, so just output
                % as an integer string.
                accurStrOut = sprintf('%ld', doubleIn);
                isIntStr = true;
                return;
            end
            isIntStr = false;

            for precision = 1:19
                accurStrOut = num2str(doubleIn, precision);
                if isequaln(doubleIn, str2double(accurStrOut))
                    return;
                end
            end
            assert(isequaln(doubleIn, str2double(accurStrOut)), ...
                   'accurateDbl2Str: bad precision');
        end

        % slopeDigitsAfterBinPt
        %
        % helper function for fixptBase10Dbl2Str below.
        % finds the precision of a data type slope, for
        % generating a shorter "good enough" string.
        function n = slopeDigitsAfterBinPt(slopeIn)
            assert(isa(slopeIn, 'double'), 'doubleIn must be a double.');
            assert(isscalar(slopeIn), 'doubleIn must be a scalar.');

            n = 20;
            [slopeStr, intStrBool] = rtw.connectivity.CodeInfoUtils.accurateDbl2Str(slopeIn);
            if contains(slopeStr, 'e')   % slope is too complicated; default to 20
                return
            end
            if intStrBool
                n = 0;
                return
            end
            i = strfind(slopeStr, '.');
            if isempty(i)
                n = 0;
            else
                n = length(slopeStr)-i;
            end
        end

        % removeTrailingZeros
        %
        % helper function for fixptBase10Dbl2Str below.
        % using a large precision can result in extra trailing
        % zeros (i.e. "123.450000"); this function removes them.
        function returnStr = removeTrailingZeros(valueStr)
            assert(ischar(valueStr), 'valueStr must be a string.');

            returnStr = valueStr;
            ptIndex = strfind(valueStr, '.');
            if isempty(ptIndex)
                return
            end
            

            for i = length(valueStr):-1:ptIndex
                if valueStr(i) == '0'
                    continue
                end
                returnStr = valueStr(1:i);
                if valueStr(i) == '.'
                    returnStr = valueStr(1:i-1);
                end
                break
            end
        end

        % fixptBase10Dbl2Str
        %
        % mimics the string conversion algorithm in
        % src/fixpoint/fix_fat2txt.cpp : fhpBuildDecStringFromValue and
        % src/fixpoint/fix_fat2txt.cpp : fhpGetDTLimitString.
        %
        % resolves the Bosch arxml issue (see g1270098, g1279648).
        function fxpStrOut = fixptBase10Dbl2Str(doubleIn, numerictypeIn)
            assert(isa(doubleIn, 'double'), 'doubleIn must be a double.');
            assert(isscalar(doubleIn), 'doubleIn must be a scalar.');
            assert(rtw.connectivity.CodeInfoTypeUtils.isaType(numerictypeIn, 'Numeric'), ...
                'numerictypeIn must be a numerictype.');
            assert(isscalar(numerictypeIn), 'numerictypeIn must be a scalar.');

            [accurStr, intStrBool] = rtw.connectivity.CodeInfoUtils.accurateDbl2Str(doubleIn);
            fxpStrOut = rtw.connectivity.CodeInfoUtils.removeTrailingZeros(accurStr);
            % return now if integer string; requires no additional formatting
            if intStrBool
                return;
            end
            nSlopeDigits = rtw.connectivity.CodeInfoUtils.slopeDigitsAfterBinPt(numerictypeIn.Slope);
            if nSlopeDigits > 11
                % if the slope is complicated enough, just default to double2str
                fxpStrOut = rtw.connectivity.CodeInfoUtils.double2str(doubleIn);
                return;
            end

            % ensures that the "close enough" string isn't too short due to 
            % low-precision slope but still potentially shorter than accurStr.
            % for example, with doubleIn = 0.88 and nSlopeDigits = 1, we
            % want '0.88,' so set nSlopeDigits to 2.
            % but if doubleIn = 125.200002 and nSlopeDigits = 1, then we
            % still want nSlopeDigits to be 2 so that the result is
            % '125.2.'
            nDblDigits = rtw.connectivity.CodeInfoUtils.slopeDigitsAfterBinPt(doubleIn);
            if nSlopeDigits < nDblDigits && nDblDigits < (nSlopeDigits + 4)
                nSlopeDigits = nDblDigits;
            end
            % generates a "close enough" string of the double value
            fmtStr = sprintf('%%.%df', nSlopeDigits);
            valueStr = sprintf(fmtStr, doubleIn);
            % check the error of the "close enough" string with the original value
            roundTripValue = str2double(valueStr);
            error = abs(roundTripValue - doubleIn);
            if error < sqrt(eps(doubleIn))
                fxpStrOut = rtw.connectivity.CodeInfoUtils.removeTrailingZeros(valueStr);
            end
        end
        
        function implementations = getInterfaceImplementations(interfaceImplementation)
            if rtw.connectivity.CodeInfoUtils.isa(interfaceImplementation,'TypedCollection')
                implementations = rtw.connectivity.CodeInfoUtils.getArray(interfaceImplementation.Elements);
            else
                implementations = interfaceImplementation;
            end
            implementations = rtw.connectivity.CodeInfoUtils.getTransferImplementation(implementations);
        end
        
        function implementations = getTransferImplementation(implementations)
            %Some interfaces need transformation before being set to/from
            %the target. Where populated the Dynamic Region captures the
            %transfer implementation variable.
            for idx=1:numel(implementations)
                impl = implementations(idx);                
                if ~isempty(impl) && ~isempty(impl.DynamicMemory)
                    if (rtw.connectivity.CodeInfoUtils.isa(impl,'StructExpression') || ...
                            rtw.connectivity.CodeInfoUtils.isa(impl,'Variable'))
                        %Check for unsupported types
                        if impl.DynamicMemory.HasUnsupportedType || ...
                                (impl.Type.isStructure && ... %Or if an array of Containers
                                ~impl.Type.isScalar && ...
                                impl.Type.isContainerClass && ...
                                impl.Type.ReferenceType.isMatrix && ... %Have to check reference type to look for
                                impl.Type.ReferenceType.BaseType.isStructure) %array of struct
                            if rtw.connectivity.CodeInfoUtils.isa(impl,'Variable')
                                identifier = impl.Identifier;
                            else
                                identifier = impl.ElementIdentifier;
                            end
                            DAStudio.error('PIL:pil:UnsupportedInterfaceType', identifier);
                        end
                        
                        if rtw.connectivity.CodeInfoTypeUtils.isaType(impl.Type, 'Class') || ...
                                rtw.connectivity.CodeInfoTypeUtils.isaType(impl.Type, 'Matrix')
                            implementations(idx) = impl.DynamicMemory.StaticVariable;
                        end
                    else
                        %Check for unsupported types
                        if impl.DynamicMemory.HasUnsupportedType
                            DAStudio.error('PIL:pil:UnsupportedInterfaceType', impl.Identifier);
                        end
                        implementations(idx) = impl.DynamicMemory.StaticVariable;
                    end
                end
            end
        end
        
        function isSystemConstant = isDataInterfaceSystemConstant(this, dataInterface) %#ok<INUSD>
            isSystemConstant = false;
        end
        
                
        function identifier = getIdentifier(dataInterface)
            % get the Implementation Identifier field from DataInterface
            switch(rtw.connectivity.CodeInfoUtils.class(dataInterface.Implementation))
                case {'Variable', 'PointerVariable', 'StructAccessorVariable'}
                    identifier = dataInterface.Implementation.Identifier;
                case 'PointerExpression'
                    identifier = dataInterface.Implementation.TargetRegion.Identifier;
                case 'StructExpression'
                    identifier = dataInterface.Implementation.ElementIdentifier;
                otherwise
                    % The assertion will fail for C++ model with grouped
                    % model arguments when parameter visibility is not
                    % public (Implementation is empty). The work is in
                    % progress and will be addressed in g1804785.
                    assert(false, ...
                        sprintf('%s class is not expected as dataInterface argument', class(dataInterface)));
            end
        end
        
        function typeId = getTypeIdentifier(dataInterface)
            % get the Implementation Type Identifier from a DataInterface
            impl = dataInterface.Implementation;
            if rtw.connectivity.CodeInfoUtils.isa(impl, 'PointerExpression')
                implType = impl.TargetRegion.Type;
            else
                implType = impl.Type;
            end
            baseType = rtw.connectivity.CodeInfoTypeUtils.getBaseType(...
                implType);
            typeId = baseType.Identifier;
        end
        
        function ret = isImportedGroupedCSC(dataImplementation)
            % Return true if dataImplementation is an imported grouped CSC
            ret = rtw.connectivity.CodeInfoUtils.isa(dataImplementation, 'CustomVariable') && ...
                ~isempty(dataImplementation.ClassAccess) && ...
                    strcmp(dataImplementation.StorageSpecifier, 'extern') && ...
                    isempty(rtw.pil.OwnerAccessor.getOwner(dataImplementation));
        end
        
        function array = getArray(anObject)
            if isa(anObject, 'mf.zero.Sequence') || ...
                    isa(anObject, 'mf.zero.PrimitiveSequence')
                array = anObject.toArray;
            else
                array = anObject;
            end
        end
        
        function timingModes = getCharTimingModeNames(anObject)
            % given a cell array of TimingModes, return a cell array of
            % char TimingModes
            assert(iscell(anObject), 'Unexpected input of class %s', class(anObject));
            numElements = numel(anObject);
            timingModes = cell(1, numElements);
            for idx=1:numElements
                assert(isa(anObject{idx}, 'char') || ...
                    isa(anObject{idx}, 'coder.descriptor.TimingModes'), ...
                    'Unexpected element of class %s', class(anObject{idx}));
                timingModes{idx} = char(anObject{idx});
            end
        end
        
        function res = isa(obj, classCategory)
            res = isa(obj, ['RTW.' classCategory]) || ...
                  isa(obj, ['coder.descriptor.' classCategory]);
        end
        
        function className = class(obj)
            fullClassName = class(obj);
            tokenClassName = regexp(fullClassName, 'RTW.(.*)|coder.descriptor.(.*)','tokens');
            assert(isequal(size(tokenClassName), [1,1]), 'CodeInfoUtils.class used on unsupported object');
            className = tokenClassName{1}{1};
        end
        
        function isEquivalent = isEquivalentTo(firstRef, secondRef)
            % this is a temporary workaround that will be removed once we
            % fully switch to CodeDescriptor
            if isa(firstRef, 'coder.descriptor.TimingInterface') || isa(firstRef, 'RTW.TimingInterface')
                % use the MF0/RTW comparison method for TimingInterfaces
                isEquivalent = firstRef.isEquivalentTo(secondRef);
            elseif isa(firstRef, 'old.coder.descriptor.TimingInterface')
                isEquivalent = isequal(firstRef, secondRef);
            elseif isa(firstRef, 'coder.descriptor.DataInterface')
                % g1974144: call isEquivalentTo directly
                isEquivalent = firstRef.isEquivalentTo(secondRef);
            elseif isa(firstRef, 'RTW.DataInterface')
                isEquivalent = isequal(firstRef, secondRef);
            else
                assert(false, 'Unexpected class');
            end
        end
               
        function dataInterface = getEmptyInterfaceCommon(class, useMF0)
            if useMF0
               dataInterface = coder.descriptor.(class).empty;
            else
               dataInterface = RTW.(class).empty;
            end
        end
        
        function isActive = isActiveVariant(aVariantInfo)
            if isprop(aVariantInfo, 'NetSVCE')
                isActive = aVariantInfo.NetSVCE;
            elseif isprop(aVariantInfo, 'VariantActive')
                isActive = aVariantInfo.VariantActive;
            else
                assert('Cannot find the right property to check if the variant is active');
            end
        end
        
        function isAccessible = isDataInterfaceAccessible(interface)
            % Handle C++ Class codegen private members
            isAccessible = true;
            if rtw.connectivity.CodeInfoUtils.isa(interface.Implementation, 'StructExpression')
                tmp = interface.Implementation;
                while rtw.connectivity.CodeInfoUtils.isa(tmp, 'StructExpression') || ...
                        rtw.connectivity.CodeInfoUtils.isa(tmp, 'ArrayExpression')
                    tmp = tmp.BaseRegion;
                    if rtw.connectivity.CodeInfoUtils.isa(tmp, 'ClassMemberExpression')
                        % this also captures StaticMemberExpression that
                        % extends ClassMemberExpression
                        isAccessible = strcmp(tmp.Visibility, 'PUBLIC_MEMBER');
                        break;
                    end
                end
            end
        end
        
        function shouldUse = shouldUseOpaqueRegion(implementation)
            assert(isa(implementation, 'RTW.DataImplementation') || ...
                   isa(implementation, 'coder.descriptor.DataImplementation'));
            shouldUse = false;
            if isempty(implementation)
                return;
            end            
            if isprop(implementation,'OpaqueRegion') &&...
                    ~isempty(implementation.OpaqueRegion) &&...
                    implementation.OpaqueRegion.ContainsStdContainer
                shouldUse = true;
            end
            return;
        end
        
        % check if the dataInterface is a LookupTable or Breakpoint that
        % takes its data from the block inport
        function result = isLUTorBPwithDataFromInport(dataInterface)
            switch class(dataInterface)
                case {'coder.descriptor.BreakpointDataInterface', ...
                        'coder.descriptor.LookupTableDataInterface'}
                    result = dataInterface.isDataFromInport;
                otherwise
                    result = false;
            end
        end
    end
    
    methods (Access = 'protected')                    
        
        % flag whether dataInterface is a member of 'category', and if so, 
        % return its index
        function [isInCategory, index] = isMemberOf(this, category, dataInterface)
            switch category
                case {'Inports','Outports','Parameters','DataStores','InternalData'}
                    elementsInCategory = this.CodeInfo.(category);
                case 'SigLogs'
                    elementsInCategory = this.SigLogs;
                case 'GlobalDS'
                    elementsInCategory = this.getAllGlobalDataStores;
                case 'SharedDS'
                    elementsInCategory = this.getAllSharedDataStores;
                otherwise
                    assert(false, 'Unexpected category');
            end
            isInCategoryArray = arrayfun(...
                @(x)rtw.connectivity.CodeInfoUtils.isEquivalentTo(dataInterface, x), ...
                elementsInCategory);
            index = find(isInCategoryArray);
            isInCategory = ~isempty(index);
        end
        
        % get data interfaces for global data stores
        function ds = getGlobalDataStoresFromCodeInfo(this)
            assert(isempty(this.globalDS), ...
                   'We should only call this to initialize the data member globalDS');
            
            % find global data stores accessed by the model
            ds = findobj(this.CodeInfo.DataStores, 'SID', '');
            ds = ds'; % Return a row vector
        end                
        
        % get data interfaces for shared local data stores
        function ds = getSharedLocalDataStoresFromCodeInfo(this)
            assert(isempty(this.sharedLocalDS), ...
                   'We should only call this to initialize the data member sharedLocalDS');
            
            % find shared local data stores accessed by the model
            % (data stores where SharedLocalDSMInfo is not empty)
            topModelDS = setdiff(this.CodeInfo.DataStores, ...
                findobj(this.CodeInfo.DataStores, 'SharedLocalDSMInfo', ''));

            % prune out any not owned by this model. These will be picked
            % up when processing sub-models
            idxToPrune = [];
            
            for idx = 1:numel(topModelDS)
                if isempty(regexp(topModelDS(idx).SID,[this.CodeInfo.GraphicalPath '\:+.*'], 'once'))
                    idxToPrune(end+1) = idx; %#ok<AGROW>
                end
            end
            topModelDS(idxToPrune) = [];
            
            subModelDS = this.getSubModelSharedLocalDataStores;
            % combine
            ds = [subModelDS, topModelDS];
            
            % remove any duplicates, use GraphicalName as criteria.
            % Duplicates will exist if a DSM is accessed in multiple models
            % within the test system. The first in the list is the top
            % level version, so keep that.
            if length(ds) > 1
                dsNames = {ds.GraphicalName};
                [~,uniqueIdx] = unique(dsNames, 'stable');
                ds = ds(uniqueIdx);
                
                % All shared data stores must have initialize reader/writer
                timingMode = 'ONESHOT';
                initTiming = this.createTimingInterface(this.getMF0ModelInstance, timingMode);

                if this.UseMF0CodeDescriptor
                    for idx = 1:length(ds)
                        ds(idx).DataReads.add(initTiming);
                        ds(idx).DataWrites.add(initTiming);
                    end
                else
                    for idx = 1:length(ds)
                        ds(idx).DataReads(end+1)  = initTiming;
                        ds(idx).DataWrites(end+1) = initTiming;
                    end
                end
            end
        end                
        
        function subModelBuildDir = getSubModelBuildDir(this, subModel, parallelBuildInProgress)
            % Handle case where subModel might be in a different folder
            % hierarchy due to parallel build
            %
            % Note: g1818532 should remove the need for this.
            
            % Determine the anchor folder.
            if parallelBuildInProgress
                codeGenFolder = coder.internal.ParallelAnchorDirManager('get', 'RTW');
            else
                codeGenFolder = Simulink.fileGenControl('get', 'CodeGenFolder');
            end
            
            % Must NOT use RTW.getBuildDir(subModel) because subModel might
            % not actually exist as a model, e.g., if it is a child model
            % ref deep inside a protected model.  Instead, get the relative
            % path to build dir from infoStruct.
            infoStruct = this.ConfigInterface.getInfoStruct;
            modelRefRelativeBuildDir = ...
                infoStruct.modelRefsBuildDirsAll(strcmp(subModel, infoStruct.modelRefsAll));
            assert(numel(modelRefRelativeBuildDir) == 1, 'Could not find unique model ref relative build dir for %s', subModel);
            subModelBuildDir = fullfile(codeGenFolder, modelRefRelativeBuildDir{1});
        end
        
        function subModelComponentInterface = getSubModelCodeInfo(this, subModel)
            if isempty(this.SubModelCodeInfo)
                % initialize SubModelCodeInfo cache
                this.SubModelCodeInfo = containers.Map;
                this.SubModelCodeDescHandles = containers.Map;
            end
            % check for a map hit
            if this.SubModelCodeInfo.isKey(subModel)
                % return item from map
                subModelComponentInterface = this.SubModelCodeInfo(subModel);
            else
                % load and store in map
                %                
                parallelBuildInProgress = Simulink.fileGenControl('GetParallelBuildInProgress');
                subModelBuildDir = this.getSubModelBuildDir(subModel, parallelBuildInProgress);
                addRebuildNeverMessage = true;
                if this.UseMF0CodeDescriptor
                    if parallelBuildInProgress
                        this.SubModelCodeDescHandles(subModel) = rtw.connectivity.CodeInfoUtils. ...
                            getCodeDescriptorHandle(addRebuildNeverMessage, subModelBuildDir);
                    else
                        rtw.connectivity.CodeInfoUtils.errorIfMissingCodeDescriptor(addRebuildNeverMessage, subModelBuildDir);
                        this.SubModelCodeDescHandles(subModel) = this.CodeDescriptorHandle.getReferencedModelCodeDescriptor(subModel);
                    end
                    subModelComponentInterface = this.getComponentInterface(...
                           this.SubModelCodeDescHandles(subModel));
                else
                    codeInfoPath = fullfile(subModelBuildDir, [subModel '_mr_codeInfo.mat']);
                    % call static method to load CodeInfo
                    %
                    % model ref rebuild options could be responsible for a
                    % missing CodeInfo
                    codeInfoStruct = rtw.pil.loadCodeInfo(codeInfoPath, ...
                        addRebuildNeverMessage);
                    subModelComponentInterface = codeInfoStruct.codeInfo;
                end
                this.SubModelCodeInfo(subModel) = subModelComponentInterface;
            end
        end
        
        function directReads = getAdditionalDirectReads(~)
            % overridden in SimulinkCoderInfoUtils
            directReads = [];
        end     
        
        function directWrites = getAdditionalDirectWrites(~)
           % overridden in SimulinkCoderInfoUtils
           directWrites = []; 
        end
        
        % template method to return variable size signal information
        function [inportSizes, ...
                  inportSizesIndices, ...
                  outportSizes, ...
                  outportSizesIndices] = getVariableSignalSizes(this) %#ok<MANU>
            inportSizes = [];
            inportSizesIndices = [];
            outportSizes = [];
            outportSizesIndices = [];
        end
        
        function throwContinuousTimeError(this)
            % throw an error saying that continuous time is not supported
            % across the component boundary
            rtw.connectivity.ProductInfo.error('target', ...
                                               'CodeInfoContinuousTimingMode', ...
                                               this.ComponentForDiagnostics);
        end
        
        function allowedRates = getAllowedRates(this)
            allowedRates = {'PERIODIC' 'ONESHOT' 'INHERITED'};
            if this.isExportFunctions
                allowedRates = [allowedRates(:); 'ASYNCHRONOUS'];                
            end
            if this.hasModelWideEvents()
                allowedRates = [allowedRates(:); 'INITIALIZE'; 'RESET'; 'RESETWITHINIT'; 'TERMINATE'];
            end
        end        
        
        function val = canUseDirectReadsAndWrites(this, ci)
            % DirectReads and DirectWrites are available for:
            % 
            % 1) Right-click export functions
            % 2) ExportFcnDiagram (Async timing only, top or model reference)
            % 3) AsyncFcnCallModel (Async timing only, top or model reference)
            %
            % See g961561                             
            val = this.isRightClickSubsystemExportFcns || ...
                (this.isExportFunctions && strcmp(ci.TimingMode, 'ASYNCHRONOUS'));
        end
                       
        function validateTimingProperties(this)
            % the TimingProperties can contain the following types of
            % TimingMode:
            %
            % ASYNCHRONOUS
            % CONTINUOUS
            % INHERITED
            % ONESHOT
            % PERIODIC            
            %
            % Currently, support ONESHOT, PERIODIC and INHERITED rates (and 
            % ASYNCHRONOUS in some cases) so lets validate that this 
            % CodeInfo object conforms  
            timingProperties = this.CodeInfo.TimingProperties;
            allowAsync = this.isAsyncAllowed;
            for i=1:length(timingProperties)
                timing = timingProperties(i);
                switch timing.TimingMode
                    case 'PERIODIC'
                        % ok, except for continuous time edge case g675944
                        if timing.SamplePeriod == 0                            
                            this.throwContinuousTimeError;    
                        end
                    case {'ONESHOT' 'INHERITED'}
                        % ok
                    case 'CONTINUOUS'
                        this.throwContinuousTimeError;
                    case 'ASYNCHRONOUS'
                        if ~allowAsync && timing.SamplePeriod == -1
                            % Async not supported for non-export functions
                            rtw.connectivity.ProductInfo.error('target', ...
                                'CodeInfoAsyncTimingMode', ...
                                this.ComponentForDiagnostics);
                        end
                    case {'UNION' 'INITIALIZE' 'RESET' 'RESETWITHINIT' 'TERMINATE'}
                        % ok
                    case 'CONTROLLABLE'
                        % controllable rates are not supported by XIL
                        DAStudio.error('Simulink:SampleTime:ControllableSampleTimeNotSupportMdlRefXILSimulation', this.ComponentForDiagnostics);
                     
                    otherwise
                        rtw.connectivity.ProductInfo.error('target', ...
                            'CodeInfoUnsupportedTimingMode', ...
                            timing.TimingMode);
                end
            end                
            % assert that each port must be associated with a rate
            allPorts = [this.CodeInfo.Inports, this.CodeInfo.Outports];
            allowedRates = this.getAllowedRates;
            allRates = [];
            for i = 1:length(allowedRates)
                allRates = [allRates, this.getRates(allowedRates{i})]; %#ok<AGROW>
            end

            indices = this.getPortsForTimingProperties(allRates, ...
                allPorts);
            assert(length(allPorts) == length(indices), ...
                'Not all ports are associated with a rate');
                                
            updateTasks = this.getUpdateTasksForTimingMode('PERIODIC');            
            if this.isExportFunctions && this.isSampleTimeIndependent
                assert(isempty(updateTasks), ...
                    'Output / update tasks must always be combined for export functions.');
                 if this.isTopModelExportFunctions && ~this.hasPeriodicExplicitTasking
                     % For a top model export functions diagram, it is not
                     % allowed to have any periodic or inherited rates in
                     % the top model canvas.
                     nonAsyncTasks = [...
                         this.getOutputTasksForTimingMode('INHERITED') ...
                         this.getOutputTasksForTimingMode('PERIODIC')];
                     assert(isempty(nonAsyncTasks), ['INHERITED and PERIODIC '...
                         'timing modes are not expected in codeInfo '...
                         'for top model export functions!']);
                 end
            else
                % check that INHERITED and PERIODIC tasks are mutually
                % exclusive, and one or the other must exist
                %
                % see also CodeInfoSfunction.m which relies on this check to
                % guarantee the "const block" case does not occur
                if ~isempty(this.getCodeInfo.OutputFunctions)
                    if isempty(this.getOutputTasksForTimingMode('INHERITED'))
                        assert(~isempty(this.getOutputTasksForTimingMode('PERIODIC')), ...
                            'No INHERITED or PERIODIC tasks found in CodeInfo');
                    else
                        assert(isempty(this.getOutputTasksForTimingMode('PERIODIC')), ...
                            'INHERITED and PERIODIC tasks found in CodeInfo');
                    end
                end
                
                % check our independent derivations of multitasking and multirate are
                % consistent with one another
                %
                % multitasking, single rate should never happen
                if this.isMultiTasking && ~this.isMultiRate
                    assert(false, 'Unexpected error: Multitasking with single rate');
                end
            end
            % always expect a const rate
            % it will at least be associated with an initialization
            % function.
            constRate = this.getRates('ONESHOT');
            assert(~isempty(constRate), 'No ONESHOT rate found in CodeInfo');
            %
            % assert that no input port has a const rate
            indices = this.getInputPortsForRates(this.getRates('ONESHOT'));
            constInputInterfaces = this.CodeInfo.Inports(indices);
            noActiveOneShotInports = true;
            for idx=1:numel(constInputInterfaces)
                %Check that any constant inputs are inactive. Constant inputs are
                %not expected (hence this is an assertion not an error),
                %however if a port is inactive it's timing properties might
                %not be complete and therefore should be ignored here as
                %they are ignored later anyway (as they are inactive).
                if rtw.pil.CodeInfoData.isInterfaceActive(constInputInterfaces(idx))
                    noActiveOneShotInports = false;
                    break;
                end
            end
            assert(noActiveOneShotInports, 'Found Active Inport with ONESHOT rate');
        end

        function validateEmptyImplementations(this)
            
            % check inports
            if checkForEmptyInterface(this.ClientInterface, 'Inport')
                this.errorForEmptyImplementations(this.CodeInfo.Inports, ...
                                                'Inport', ...
                                                'PIL_InportUnsupportedImplementation', ...
                                                'CodeInfoInportUnsupportedImplementation');
            end
            
            % check outports
            if checkForEmptyInterface(this.ClientInterface, 'Outport')
                this.errorForEmptyImplementations(this.CodeInfo.Outports, ...
                                                'Outport', ...
                                                'PIL_OutportUnsupportedImplementation', ...
                                                'CodeInfoOutportUnsupportedImplementation');
            end
            
            % check shared data stores
            if checkForEmptyInterface(this.ClientInterface, 'DataStore')
                allGlobalDSMs = this.getGlobalDataStores;
                this.errorForEmptyImplementations(allGlobalDSMs, ...
                    'DataStore', ...
                    'PIL_DataStoreUnsupportedImplementation', ...
                    'CodeInfoDataStoreUnsupportedImplementation');
            end
            
            if checkForEmptyInterface(this.ClientInterface, 'InternalData')
                % check internal data
                [emptyImplementations, dispString] = ...
                    this.checkForEmptyImplementations(this.CodeInfo.InternalData, ...
                                                      'InternalData');
                if emptyImplementations
                    rtw.connectivity.ProductInfo.error('target', ...
                                                       'CodeInfoInternalDataUnsupportedImplementation', ...
                                                       dispString);
                end
            end
        end
        
        function validateTypes(~)
            %Template method. Overridden in some sub classes.
        end

        function validateUnsupportedZeroCrossings(~)
            % default implementation, overridden by subclasses if needed
        end
        
        function validateUnsupportedSharedLocalDS(~)
            % default implementation, overridden by subclasses if needed
        end
        
        % accessor for ClientInterface
        function clientInterface = getClientInterface(this)
           clientInterface = this.ClientInterface; 
        end
        
        function dataInterfaces = getDataInterfacesForType(this, functionInterfaces, interfaceType)
            if ~isempty(functionInterfaces)
                switch interfaceType
                    case 'input'
                        dataInterfaces = this.CodeInfo.Inports;
                    case 'inputsize'
                        dataInterfaces = this.getInportSizes;
                    case 'output'
                        dataInterfaces = this.CodeInfo.Outports;
                    case 'outputsize'
                        dataInterfaces = this.getOutportSizes;
                    case 'parameter'
                        dataInterfaces = this.CodeInfo.Parameters;
                    otherwise
                        assert(false, 'Unknown dataInterfaceType: %s', interfaceType);
                end
            else
                dataInterfaces = [];
            end
        end
        
        function indices = matchInterfacesToFunctions(~, dataInterfaces, functionInterfaces)
            % return only the data interfaces associated with
            % the function prototype
            numDataInterfaces = length(dataInterfaces);
            % functionInterface may be a vector (e.g.
            % InitializeFunctions may contain initialize and Start)
            for i=1:length(functionInterfaces)
                functionInterface = functionInterfaces(i);
                actualArgs = [functionInterface.ActualArgs, functionInterface.ActualReturn];
                numArgs = length(actualArgs);
                % init match array
                matchArray = zeros(1, numDataInterfaces);
                for argIdx = 1:numArgs
                    arg = actualArgs(argIdx);
                    for dataInterfaceIdx = 1:numDataInterfaces
                        dataInterface = dataInterfaces(dataInterfaceIdx);
                        % comparison checks that the DataInterface objects are the same
                        % instance
                        if arg == dataInterface
                            matchArray(dataInterfaceIdx) = true;
                        elseif ~isempty(dataInterface.Implementation)
                            %This is required because for MATLAB Coder for certain
                            %interfaces, only the implementations are references
                            %to each other, not the rest of the information which is
                            %irrelevant in the MATLAB Coder case. Matching the
                            %interfaces this way is completely consistent.
                            allImplementations = ...
                                rtw.connectivity.CodeInfoUtils.getInterfaceImplementations(dataInterface.Implementation);
                            for idx = 1:numel(allImplementations)
                                %Loop over all interfaces that are
                                %associated with a particular port. This
                                %will only be more than one for
                                %TypedCollections.
                                if arg.Implementation == allImplementations(idx)
                                    matchArray(dataInterfaceIdx) = true;
                                    break;
                                end
                            end
                        end
                    end
                end
            end
            % return indices of matching datainterfaces
            indices = find(matchArray);
            
        end    
        
        function initializeCSFunctionInfoFactory(this) %#ok<MANU>
            % method only applies for Simulink use-case and implemented in
            % SimulinkCoderInfoUtils
        end
        
        function initializeServerFunctionsInfo(this) %#ok<MANU>
            % method only applies for Simulink use-case and implemented in
            % SimulinkCoderInfoUtils
        end
        
        function initializeCallerFunctionsInfo(this) %#ok<MANU>
            % method only applies for Simulink use-case and implemented in
            % SimulinkCoderInfoUtils
        end
        
        function initializeCoderGroupsInfo(~)
            % method only applies for Simulink use-case and implemented in
            % SimulinkCoderInfoUtils
        end
        
        function initializeOutputFunction(~)
            % method only applies for Simulink use-case and implemented in
            % subclasses
        end
        
        % query: is this a right-click subsystem Export Functions build?
        function val = isRightClickSubsystemExportFcns(this)
            val = this.isExportFunctions && ...
                this.ClientInterface.isSubsystemBuild(this.ConfigInterface);
        end
        
        
        function tasks = getAllAsyncOutputTasks(this)
        % get the Output tasks with Async timing mode and get the
        % Async output tasks from UNION timing mode
            tasks = [...
                    this.getOutputTasksForTimingMode('ASYNCHRONOUS') ...
                    this.getAsyncOutputTasksFromUnionTM ];
        end
                
        
        function tasks = getAsyncOutputTasksFromUnionTM(this)
            % get Asynchronous output tasks needed by external Callers
            % from SimulinkFunctions that have a UNION timing mode
            functionInterfaces = this.CodeInfo.OutputFunctions;
            numTasks = length(functionInterfaces);
            tasks = [];            
            for codeInfoIndex=1:numTasks
                functionInterface = functionInterfaces(codeInfoIndex);
                if ~isempty(functionInterface.Timing) && ...
                        strcmp(functionInterface.Timing.TimingMode, 'UNION')
                    assert(isServerOutputFunction(this, functionInterface), ...
                        'UNION timing mode is expected only on SimulinkFunctions');
                    % SimulinkFunctions can have UNION timing mode if both
                    % external and internal Callers exist in an
                    % ExportFunctions modeling scenario.
                    % The external Callers will be mapped to an ASYNCHRONOUS 
                    % timing mode. The internal Callers are contained within 
                    % the application and we can ignore them.
                    tasks(end+1).codeInfoData = functionInterface; %#ok<AGROW>
                    % use codeInfoIndex as unique id
                    tasks(end).id = codeInfoIndex;
                end
            end
        end
        %Overridden in sub-classes, return any interfaces that are Queued
        function queuedIndices = isQueuedDataInterface(this, dataInterfaces, indices) %#ok<INUSD>
            queuedIndices = [];            
        end
        
        function subModelDS = getSubModelSharedLocalDataStores(this)
            % look for DataStores where the Shared Local DSM Info is a
            % model name that doesn't match the SID:
            %
            subModelDS = [];
            if ~isempty(this.CodeInfo.DataStores)
                % find indices that could correspond to a root model
                SIDdelim = ':';
                allSIDs = arrayfun(@(x) strtok(x.SID, SIDdelim), this.CodeInfo.DataStores, ...
                    'UniformOutput', false);
                allSLDSMInfos = arrayfun(@(x) strtok(x.SharedLocalDSMInfo, SIDdelim), this.CodeInfo.DataStores, ...
                    'UniformOutput', false);
                indices = ~strcmp(allSIDs, allSLDSMInfos);
                dsToProcess = this.CodeInfo.DataStores(indices);
                % process each ds
                for dsIdx = 1:length(dsToProcess)
                    ds = dsToProcess(dsIdx);
                    modelName = strtok(ds.SharedLocalDSMInfo, SIDdelim);
                    if isempty(modelName)
                        %If not shared local DSM info, skip this block
                        continue;
                    end
                    assert(isempty(ds.Implementation), ...
                        'Implementation of data store accessed only in sub-model must be empty.');
                    subModelCodeInfo = this.getSubModelCodeInfo(modelName);
                    
                    % find data stores with matching GraphicalName
                    subModelGraphicalNameMatchIdx = ...
                        strcmp({subModelCodeInfo.DataStores.GraphicalName}, ds.GraphicalName);
                    if ~isempty(ds.SharedLocalDSMInfo)
                        %Match up shared local DSMs
                        newSubModelDS = subModelCodeInfo.DataStores(subModelGraphicalNameMatchIdx);
                        subModelDS = [subModelDS, newSubModelDS]; %#ok<AGROW>
                    end
                end
            end
        end
        
        function compInterface = getComponentInterface(~, ~) %#ok<STOUT>
            % implemented in subclasses
        end
        
        function res = addRebuildNeverMessage(this) %#ok<MANU>
            % this method can be overwritten by subclasses as needed
            res = false;
        end
        
        function typesMap = getTypesMap(this)
            typesMap = this.CodeDescriptorHandle.getTypesMap;
        end
        
        function initializeCodeDescriptorInternals(~)
            % method only applies for Simulink use-case and implemented in
            % SimulinkCoderInfoUtils
        end
        
        function initUseMF0CodeDescriptor(this)
            this.UseMF0CodeDescriptor = coder.internal.connectivity.featureOn('UseMF0CodeDescriptor');
        end
        
        function dispString = getParameterInterfaceString_Hook(~, ~, origString)
            % default implementation, this method is overwritten
            % by subclasses
            dispString = origString;
        end
        
        function string = getDisplayStringFromSID(this, SID)
            resolvedSID = this.resolveSID(SID);
            string = this.getClientInterface.getDisplayStringFromSID(resolvedSID);
        end

        function resolvedSID = resolveSID(~, SID)
            % default implementation
            resolvedSID = SID;
        end
        
        function initalizeFunctions = getInitializeFunctions(this)
            initalizeFunctions = this.CodeInfo.InitializeFunctions;
        end
	
        function changeDataStoresOwnership(~)
            % nothing to change in the default implementation
        end

        function sid = getParamSID(this, dataInterface)
            % resolve SID for parameters
            if this.isLookupTableInterface(dataInterface)
                sid = dataInterface.OriginalSID;
            else
                sid = dataInterface.SID;
            end
        end

        function svrFcnInfo = getServerOutputFunctionByName(~, ~, ~)
            % default implementation
            svrFcnInfo = [];
        end

        function isExportFunctions = isExportFunctionsHook(~)
            %default implementation
            isExportFunctions = false;
        end
        
        function allReferencedModelInstances = getAllReferenceModelInstances(this, codeDescriptor)
            %Recursive Function to get all instances of child models
            if this.UseMF0CodeDescriptor && ~isempty(codeDescriptor.getBlockHierarchyMap)
                allReferencedModelInstances = codeDescriptor.getBlockHierarchyMap.getBlocksByType('ModelReference');
                for idx=1:numel(allReferencedModelInstances)
                    if ~allReferencedModelInstances(idx).IsProtectedModelBlock
                        %Skip protected models, as these can't be loaded
                        this.getSubModelCodeInfo(allReferencedModelInstances(idx).ReferencedModelName);
                        childReferenedModelInstances = this.getAllReferenceModelInstances(this.SubModelCodeDescHandles(allReferencedModelInstances(idx).ReferencedModelName));
                        if ~isempty(childReferenedModelInstances)
                            allReferencedModelInstances = [allReferencedModelInstances childReferenedModelInstances]; %#ok<AGROW>
                        end
                    end
                end
            else
                allReferencedModelInstances = [];
            end
        end
    end
    
    methods (Access = 'private')
        
        function ids = findTimingModesExecuted(this, signals)
            outputFcns = this.CodeDescriptorHandle.getFullComponentInterface.OutputFunctions;
            numSignals = length(signals);
            ids = true(1, numSignals);
            if outputFcns.Size == 0
                for i = 1:numSignals
                    if strcmp(signals(i).Timing.TimingMode, 'PERIODIC')
                        ids(i) = false;
                    end
                end
            end
        end
        
        function updateBaseRegionImplementation(this, dataInterfaces)
            % the method getExpression returns the code to access the
            % memory area for the DataInterface. The base variable which
            % contains all the logged elements is generated in model.c.
            % With model blocks in SIL/PIL, this variable is not generated
            % because it is passed as argument to the step method.
            % So, we need to manually modify the DataInterface to refers to
            % the variable that the SIL/PIL interface passes to the model 
            % block methods which is defined in xil_interface.c
            % Code Descriptor lists these variables in InternalData: we
            % only need to find the correct one by looking at their types
            internalData = this.CodeInfo.InternalData;
            for i = 1:length(dataInterfaces)
                impl = dataInterfaces(i).Implementation;
                lHasBaseRegion = false;
                while ~this.isa(impl, 'Variable')
                    lHasBaseRegion = true;
                    impl = impl.BaseRegion;
                end
                if isempty(impl.Identifier) || lHasBaseRegion
                    varType = impl.Type.Identifier;
                    for j = 1:length(internalData)
                        data = internalData(j);
                        if this.isa(data, 'DataInterface') && ...
                                ~isempty(data.Implementation) && ...
                                ~isempty(data.Implementation.Type) && ...
                                strcmp(data.Implementation.Type.Identifier, varType)
                            impl.Identifier = data.Implementation.Identifier;
                            break;
                        end
                    end
                    % the implementation can be missing with Simulink functions
                end
            end
        end
        
        function updateBaseRegionImplementationForStates(this, dataInterfaces)
            internalData = this.CodeInfo.InternalData;
            for i = 1:length(dataInterfaces)
                impl = dataInterfaces(i).Implementation;
                preImpl = [];
                lHasBaseRegion = false;
                % find first variable in the data structure
                while ~this.isa(impl, 'Variable')
                    lHasBaseRegion = true;
                    preImpl = impl;
                    impl = impl.BaseRegion;
                end
                if isempty(impl.Identifier) || lHasBaseRegion
                    varType = impl.Type.Identifier;
                    for j = 1:length(internalData)
                        data = internalData(j);
                        if this.isa(data, 'DataInterface') && ...
                                ~isempty(data.Implementation) && ...
                                ~isempty(data.Implementation.Type)
                            if (this.isa(data.Implementation, 'Variable') && ...
                                    strcmp(data.Implementation.Type.Identifier, varType)) || ...
                                   (this.isa(data.Implementation, 'PointerVariable') && ...
                                        strcmp(data.Implementation.TargetVariable.Type.Identifier, varType))
                                % replace top-most variable or pointer with
                                % the internal data defined in xil_interface
                                if lHasBaseRegion
                                    preImpl.BaseRegion = data.Implementation;
                                else
                                    impl.Identifier = data.Implementation.Identifier;
                                end
                                break;
                            end
                        end
                    end
                end
            end
        end
        
        function updateBlocksWithHybridRates(this, dataInterfaces, multiRateStates)
            % Update hybrid-rate states to be logged at the fastest rate in
            % the system

            % find fastest periodic rate in the system
            rates = this.getRates('PERIODIC');
            if isempty(rates)
                return;
            end
            fastestRate = rates(1);
            % replace timing for hybrid-rate states
            idxsToUpdate = find(multiRateStates);
            for idx = idxsToUpdate
                dataInterfaces(idx).Timing = fastestRate;
            end
        end
        
        % map from expInports to CodeInfo
        function [isFcn, codeInfoData, codeInfoIndex] = getExportFunctionsCodeInfoData(this, inportIdx)
            expInportData = this.expInports(inportIdx);
            index = expInportData.Index;
            switch expInportData.PortType
                case 'fcn_call'
                    isFcn = true;                    
                    if index == -1
                        % function has been optimized away                        
                        codeInfoIndex = [];
                        codeInfoData = []; 
                    else
                        % map to OutputFunctions
                        codeInfoIndex = index;
                        codeInfoData = this.CodeInfo.OutputFunctions(codeInfoIndex);
                    end
                case 'input'
                    isFcn = false;
                    codeInfoIndex = index;
                    % map to Inports
                    codeInfoData = this.CodeInfo.Inports(codeInfoIndex);
                otherwise
                    assert(false, 'Unexpected expInport PortType: %s', expInportData.PortType);
            end            
        end        
                                                        
        function errorForEmptyImplementations(this, dataInterfaces, dataInterfaceType, docTopic, errorId)
            [emptyImplementations, dispString] = ...
                this.checkForEmptyImplementations(dataInterfaces, dataInterfaceType);
            if emptyImplementations
               
                rtw.connectivity.CodeInfoUtils.throwErrorForEmptyImplementations(dispString, docTopic, errorId)
                
            end
        end
        function [timingProperties, indices] = getSortedTimingProperties(this, timingProperties)
            sampleTimeVector = this.getSampleTimeVector(timingProperties);
            %Sort sample time vector. Ensure any negative offsets (used for
            %explicit partitions) don't come before the base rate.
            [~, indices] = sortrows(sampleTimeVector, 'ComparisonMethod', 'abs');
            % reorder timingProperties according to sorted sampleTimeProperties
            timingProperties = timingProperties(indices);
        end       
        
        function [tasks, timingProperties] = sortTasks(this, tasks)            
            % function does not handle empty tasks
            assert(~isempty(tasks), 'sortTasks: tasks is empty');
            numTasks = length(tasks);
            % construct timing properties to sort
            for i=1:numTasks
                task = tasks(i);
                timingProperties(i) = task.codeInfoData.Timing; %#ok<AGROW>
            end
            % sort timing properties
            [timingProperties, indices] = this.getSortedTimingProperties(timingProperties);
            % reorder tasks according to sorted timing properties
            tasks = tasks(indices);
        end
        
        function sampleTimeVector = getSampleTimeVector(this, timingProperties)  %#ok<INUSL>
            % Note: see validateTimingProperties
            numTimings = length(timingProperties);
            sampleTimeVector = [];
            for i=1:numTimings
                timing = timingProperties(i);
                sampleTimeVector(i,:) = [timing.SamplePeriod timing.SampleOffset]; %#ok<AGROW>
            end            
        end 
                  
        function dsIndices = getSharedDataStoresForTask(this, task, isReader, varargin)
            % Criteria to decide which data stores to transfer during a
            % SIL/PIL step:
            % 1) For Export function diagrams and multi-rate multi-tasking
            %    models, we only transfer the data stores that match the
            %    timing information of the task being scheduled.
            %
            % 2) For single-tasking models, we transfer all the data stores
            %    within the SIL/PIL component because there is only one 
            %    task.
            %
            % 2) For the legacy right-click export functions, we don't have
            %    enough information in codeInfo to determine which data
            %    stores to transfer for each exported function. The timing
            %    information for the tasks in codeInfo is being recorded as 
            %    periodic or inherited. However, the data stores timing
            %    information is being correctly recorded as Asynchronous.
            %    This makes it impossible to match the rates and determine
            %    which data stores to transfer for each exported function.
            %    In this case, we sort of transfer all the data stores (see
            %    comments in initializeDataStoreProperties) for each
            %    SIL/PIL step. Ideally, users should really use the new 
            %    Model Block (CodeInterface = Top model) as a replacement 
            %    for the SIL/PIL block.     
            taskName = '';
            assert(length(task) == 1, 'task argument should be scalar');
            if nargin == 4
                % Logic to help work around G1411813
                if strcmp(varargin{1},'init')
                    taskName = 'INITIALIZE';
                elseif strcmp(varargin{1},'term')
                    taskName = 'TERMINATE';
                end
            end
            
            isExportFunctionsOrMultiTasking = (this.isExportFunctions || this.isMultiTasking);
            matchTiming = ...
                (isExportFunctionsOrMultiTasking && ~this.isRightClickSubsystemExportFcns) || ...
                ~isempty(taskName);  %This is needed to workaround G1411813 - Match for INITIALIZE and TERMINATE
            
            if isReader
                dataStores = this.readerSharedDS;
            else
                dataStores = this.writerSharedDS;
            end
            
            if ~matchTiming
                % If we're not matching timing, then all DataStores will be
                % associated with the current task.
                dsIndices = 1:numel(dataStores);
                return;
            end
            
            dsIndices = [];
            for dsIdx = 1:numel(dataStores)
                ds = dataStores(dsIdx);
                dataReads = this.getArray(ds.DataReads);
                dataWrites = this.getArray(ds.DataWrites);
                timingInterfaces = unique([dataReads, dataWrites]);
                
                % One timing instance that matches:
                %       * Task Name matches
                %       * Is Export Functions and Inherited;
                %       * Matches timing interface of task or one of its children (by value)
                % is enough to flag the data store as being used by the
                % current task.  Search them all and break out if one of
                % the conditions is met.
                for timingIdx=1:numel(timingInterfaces)
                    dsTiming = timingInterfaces(timingIdx);
                    if strcmp(dsTiming.TimingMode, taskName)
                        %For I and T systems, timing properties
                        %don't match completely, so need to add
                        %them specially
                        %This is needed to workaround G1411813
                        dsIndices = [dsIndices dsIdx]; %#ok<AGROW>
                        % data store added, continue to the next one
                        break;
                    elseif strcmp(dsTiming.TimingMode, 'INHERITED')
                        %For Export functions, models below the top
                        %level might only have INHERITED sample
                        %times, so not match ASYNCHRONOUS G1538574
                        %
                        % The same goes for periodic models where the child
                        % model has an INHERITED sample rate - we have no
                        % way of matching this to the actual rate in the
                        % parent.  So we'll just have to transfer for every
                        % task in this situation.
                        
                        dsIndices = [dsIndices dsIdx]; %#ok<AGROW>
                        % data store added, continue to the next one
                        break;
                    end
                    
                    
                    lIsTaskExplicitTasking = strcmp(task.codeInfoData.Timing.TaskingMode, 'EXPLICIT_TASKING');
                    [isSvrFcn, svrFcnInfo] = isServerOutputFunction(this, task.codeInfoData);
                    if isSvrFcn && svrFcnInfo.hasChildCaller
                        % get the Timing info for all the child callers
                        % in the current task
                        childCallers =  svrFcnInfo.getChildCallersHierarchy(...
                            this.getServerFunctionsInfo);
                        childCallersTiming = this.getTimingInfoFromChildCallers(childCallers);
                    elseif ~isSvrFcn && lIsTaskExplicitTasking
                        % If not a server function, but explicit tasking,
                        % then the explicit task might still call a server
                        % function.
                        mf0 = this.ConfigInterface.getMF0ModelInfo;
                        lCallerQualifiedFunctionNames = this.getChildCallersFromFcnCallPortTask(task, mf0);
                        
                        % Find the child server functions and the recursive
                        % child caller timing interfaces
                        childCallersTiming = this.getEmptyInterface('TimingInterface');
                        for kCaller = 1:numel(lCallerQualifiedFunctionNames)
                            lSvrFcnInfo = this.getServerOutputFunctionByName( ...
                                lCallerQualifiedFunctionNames(kCaller).functionName, ...
                                lCallerQualifiedFunctionNames(kCaller).fullPathToFunction);
                            
                            % The server function might not be found if the
                            % task is calling out of the model.
                            if ~isempty(lSvrFcnInfo)
                                lChildCallers =  lSvrFcnInfo.getChildCallersHierarchy( this.getServerFunctionsInfo);
                                childCallersTiming = [childCallersTiming, lSvrFcnInfo.getCodeInfoData.Timing, this.getTimingInfoFromChildCallers(lChildCallers)]; %#ok<AGROW>
                            end
                        end
                    else
                        childCallersTiming = this.getEmptyInterface('TimingInterface');
                    end
                    allTimingInfo = [task.codeInfoData.Timing, childCallersTiming];
                    
                    % use the Timing info to associate a DSM to the
                    % current task if it is accessed by it or by one of
                    % its child tasks
                    found = any(arrayfun( ...
                        @(x)rtw.connectivity.CodeInfoUtils.isEquivalentTo(x, dsTiming), ...
                        allTimingInfo));
                    if found
                        dsIndices = [dsIndices dsIdx]; %#ok<AGROW>
                        % data store added, continue to the next one
                        break;
                    end
                end
            end
        end
        
        function timing = getTimingInfoFromChildCallers(this, callers)
            % get the Timing info for a list of child callers
            
            % get all the server functions 
            serverFunctionsInfo = this.getServerFunctionsInfo;
            timing = this.getEmptyInterface('TimingInterface');
            % match a caller to the simulink function it is calling
            for idxCaller = 1:length(callers)
                fcnInfo = [];
                currCaller = callers{idxCaller};
                for idxFcn = 1:length(serverFunctionsInfo)
                    currFcn = serverFunctionsInfo(idxFcn);
                    if strcmp(currCaller, currFcn.getSlFunctionName)
                        fcnInfo = currFcn;
                        break;
                    end
                end
                % get the Timing info
                if ~isempty(fcnInfo)
                    timing(end+1) = fcnInfo.getCodeInfoData.Timing; %#ok<AGROW>
                end
            end
            
        end
        
        function lCallerQualifiedFunctionNames = getChildCallersFromFcnCallPortTask(this, task, mf0)
            lCallerQualifiedFunctionNames = Simulink.ModelReference.internal.compileInfo.QualifiedFunctionName.empty;
            lFcnCallPortGroups = mf0.fcnCallPortGroups.toArray;
            lFcnCallPortGroupTids = [lFcnCallPortGroups.tid];
            % See if the current task is any of the function
            % call port groups from MF0.
            %
            % From the current timing object, see if we can get
            % the TID from codeInfo:
            bMatchTimingInCodeInfo = arrayfun(@(x)(task.codeInfoData.Timing.isEquivalentTo(x)), this.getCodeInfo.TimingProperties);
            lTid = this.getCodeInfo.TimingInternalIds(bMatchTimingInCodeInfo);
            if ~isempty(lTid)
                assert(numel(lTid) == 1, 'Expected timing interface to match only one TID');
                lCurrentFcnCallPortGroup = lFcnCallPortGroups(lFcnCallPortGroupTids == lTid);
                for idx=1:numel(lCurrentFcnCallPortGroup)
                    if ~isempty(lCurrentFcnCallPortGroup(idx))
                        bIsSimulinkFunction = ~isempty(lCurrentFcnCallPortGroup(idx).simulinkFunctionName.functionName);
                        % Simulink Functions with child callers are handled
                        % separately.
                        if ~bIsSimulinkFunction
                            %Add corresponding caller qualified function
                            %names
                            tmpCallerQualifiedFunctionNames = lCurrentFcnCallPortGroup(idx).callerNames.toArray;
                            lCallerQualifiedFunctionNames(end+(1:numel(tmpCallerQualifiedFunctionNames))) =  tmpCallerQualifiedFunctionNames;
                        end
                    end
                end
            end
        end
            
        % filter tasks by timingMode, sorting periodic tasks
        
        function tasks = getTasks(this, taskType, timingMode)
            
            switch taskType
                case 'output'
                    %Get output and any reset functions. Combined because
                    %Reset functions scheduled as Output functions.                                        
                    functionInterfaces = this.CodeInfo.OutputFunctions;
                case 'update'
                    functionInterfaces = this.CodeInfo.UpdateFunctions;
                case 'init'
                    functionInterfaces = this.getInitializeFunctions;
                case 'term'
                    functionInterfaces = this.CodeInfo.TerminateFunctions; 
                otherwise
                    assert(false, 'Unexpected task type: %s', taskType);
            end
            switch timingMode
                case 'PERIODIC'
                    % sort tasks so base rate is always processed first
                    sortTasks = true;
                case {'INHERITED' 'ONESHOT', 'ASYNCHRONOUS','TERMINATE','INITIALIZE', 'RESET', 'RESETWITHINIT'}
                    sortTasks = false;
                otherwise
                    assert(false, 'Unexpected timing mode: %s', timingMode);
            end
            numTasks = length(functionInterfaces);
            tasks = [];            
            for codeInfoIndex=1:numTasks
                functionInterface = functionInterfaces(codeInfoIndex);
                % keep only requested timing modes
                if ~isempty(functionInterface.Timing) && ...
                        strcmp(functionInterface.Timing.TimingMode, timingMode)                    
                    % Exclude Simulink Functions that can't be invoked
                    % outside of the model task i.e. they are simply part
                    % of the implementation.
                    [isServerFcn, serverFcnInfo] = isServerOutputFunction(this, functionInterface);
                    if ~isServerFcn || (isServerFcn && ~serverFcnInfo.IsInvokedInModelTaskOnly)
                        tasks(end+1).codeInfoData = functionInterface; %#ok<AGROW>
                        % use codeInfoIndex as unique id
                        tasks(end).id = codeInfoIndex;
                    end
                end                                                            
            end            
            if sortTasks   
                if ~isempty(tasks)
                    tasks = this.sortTasks(tasks);
                end
            end
        end
        
        function initializeDataStores(this)
            % initialize globalDS, sharedLocalDS, allSharedDS, readerSharedDS, writerSharedDS
            this.initializeDataStoreProperties();
            % setup DataStore ownership if needed
            this.changeDataStoresOwnership();
        end
        
        % initialize properties relating to global data stores
        function initializeDataStoreProperties(this)
            % get shared data stores (global + shared local data stores)
            this.globalDS      = this.getGlobalDataStoresFromCodeInfo;
            this.sharedLocalDS = this.getSharedLocalDataStoresFromCodeInfo;
            this.allSharedDS   = [this.globalDS(:)', this.sharedLocalDS(:)'];
            % derive internal readerSharedDS & writerSharedDS lists
            for dsIdx = 1:length(this.allSharedDS)
                ds = this.allSharedDS(dsIdx);
                dataReads = this.getArray(ds.DataReads);
                if ~isempty(dataReads)
                    this.readerSharedDS = [this.readerSharedDS; ds];
                end
                dataWrites = this.getArray(ds.DataWrites);
                if ~isempty(dataWrites)                    
                    if isempty(dataReads)
                        % currently, write only requires an implicit read
                        % in order to support element access. Additionally,
                        % this allows us to support the right-click export
                        % functions when the same data store is accessed by
                        % multiple exported functions running at different
                        % rates. 
                        this.readerSharedDS = [this.readerSharedDS; ds];
                    end
                    this.writerSharedDS = [this.writerSharedDS; ds];
                end
            end
        end     
        
        % initialize variable size signal support
        function initializeVariableSizeSignals(this)
            % call template method
            [this.InportSizes, ...
             this.InportSizesIndices, ...
             this.OutportSizes, ...
             this.OutportSizesIndices] = this.getVariableSignalSizes;
        end               
        
        function validateDataInterfaces(this)       
            % template method to validate data interfaces
            this.validateUnsupportedZeroCrossings;
            this.validateUnsupportedSharedLocalDS;
            this.validateEmptyImplementations;
        end
        
        function validateExpInports(this)
            % for top model export functions, we don't expect any
            % Enable/Disable functions and no latched inports.
            if this.isTopModelExportFunctions
                for i = 1:length(this.expInports)
                    assert(isempty(this.expInports(i).EnableFcn),...
                        'EnableFcn should be empty for top model export functions');
                    
                    assert(isempty(this.expInports(i).DisableFcn),...
                        'DisableFcn should be empty for top model export functions');
                    
                    assert(~this.expInports(i).IsLatched,...
                        'IsLatched should be false for top model export functions');
                end
            end
        end
        
        function createExpInportsDataElements(this)
            % expInportsDataElements is an array of the same size as
            % this.expInports : it caches the mapping from expInports index
            % to the data port index
            this.expInportsDataElements = zeros(size(this.expInports));

            % Find the data ports in expInports
            dataElements = strcmp({this.expInports.PortType}, 'input');

            dataElementCount = 0;
            for i = 1:length(dataElements)
                if dataElements(i) ~= 0
                    % The i-th element of expInports is a data element (not
                    % a function-call): compute its index and store it in
                    % expInportsDataElements
                    dataElementCount = dataElementCount + 1;
                    this.expInportsDataElements(i) = dataElementCount;
                end
            end
        end

        function validate(this, runValidation)
            % entry point to validate the CodeInfoUtils object
            if runValidation
                % validate expInports
                this.validateExpInports;
                % validate timing properties
                this.validateTimingProperties;
                % validate data interface implementations
                this.validateDataInterfaces;
                % validate types
                this.validateTypes;
            end
        end
        
        function isLookupTableInterface = isLookupTableInterface(this, dataInterface) %#ok<INUSL>
            isLookupTableInterface = ...
                ~contains(dataInterface.GraphicalName,'Dialog:') &&...
                (ismethod(dataInterface,'isLookupTableDataInterface') && dataInterface.isLookupTableDataInterface ||...
                  ismethod(dataInterface,'isBreakpointDataInterface') && dataInterface.isBreakpointDataInterface);
        end
    end
    
    methods (Hidden, Access = 'public')
        function expInports = getExpInports(this)
            expInports = this.expInports;
        end
        
        function dataInterfaces = uniquifyDataInterfaces(this,dI)
            
            dataInterfaces = this.getEmptyInterface('DataInterface');
            for idx=1:numel(dI)
                if ~this.isPresentDI(dataInterfaces, dI(idx))
                    dataInterfaces(end+1) = dI(idx); %#ok<AGROW>
                end
            end
        end
        
        function isPresent = isPresentDI(~, list, di)
            isPresent = false;
            for idx = 1:numel(list)
                if isequal(list(idx), di)
                    isPresent = true;
                    break
                end
            end
        end
        
        % Does the component have server functions, but no periodic rates
        % (i.e. an export function diagram or a top model, with purely
        % server function blocks).
        function hasServerFcnAndNoPeriodicRate = hasServerFcnAndNoPeriodicRate(this)
            % use getOutputTasksForTimingMode so we are not vulnerable to
            % spurious periodic rates in CodeInfo
            hasServerFcnAndNoPeriodicRate = ...
                this.hasServerFunctions && ...
                isempty([this.getOutputTasksForTimingMode('PERIODIC') ...
                this.getOutputTasksForTimingMode('INHERITED')]);
        end
    end    
    
    methods (Hidden, Static)
        function throwErrorForEmptyImplementations(dispString, docTopic, errorId)
            % construct a hyperlink to the documentation on unsupported
            % features that lead to empty CodeInfo implementations
            commandText = ['helpview([docroot ''/toolbox/ecoder/helptargets.map''], ''' docTopic ''');'];
            link = targets_hyperlink_manager('new', 'documentation', commandText);
            
            rtw.connectivity.ProductInfo.error('target', ...
                errorId, ...
                dispString, ...
                link);
        end
    end
    
    methods(Static, Access = private)
        
        function [codeInfo, codeInfoStruct] = loadCodeInfo(addRebuildNeverMessage, codeInfoPath)
            % load CodeInfo
            codeInfoStruct = rtw.pil.loadCodeInfo(codeInfoPath, addRebuildNeverMessage);
            codeInfo = codeInfoStruct.codeInfo;            
        end
        
        function errorIfMissingCodeDescriptor(addRebuildNeverMessage, buildDir)
            codeDescPath = fullfile(buildDir, 'codedescriptor.dmr');
            if ~isfile(codeDescPath)
                exception = MSLException('PIL:pilverification:MissingCodeDescriptor', ...
                    codeDescPath);
                if addRebuildNeverMessage
                    cause = MSLException('PIL:pilverification:RebuildNeverMessageCodeDescriptor');
                    exception = exception.addCause(cause);
                end
                exception.throw;
            end
        end
        
        function handle = getCodeDescriptorHandle(addRebuildNeverMessage, buildDir)
            % get codedescriptor handle
            rtw.connectivity.CodeInfoUtils.errorIfMissingCodeDescriptor(addRebuildNeverMessage, buildDir);
            handle = coder.getCodeDescriptor(buildDir);
        end
    end
    
    methods(Static, Access = protected)
        function timingInterface = createTimingInterface(mf0Instance, TimingMode)
            %Create dummy timing interfaces.
            
            timingInterface = RTW.TimingInterface.empty;
            timingInterface(1).TimingMode = TimingMode;
            if ~isempty(mf0Instance)
                timingInterface = timingInterface.serializeMF0(mf0Instance);
            end
        end
    end
    
    methods(Hidden, Static, Access = public)
        
        function [loggedSignals, sigLogInfo] = getLoggableSignals(bhierarchy)
            loggedSignals = rtw.connectivity.CodeInfoUtils.getEmptyInterfaceCommon(...
                'DataInterface', true);
            sigLogInfo = [];
            
            for i=1:bhierarchy.GraphicalSystems.Size
                gs = bhierarchy.GraphicalSystems(i);
                for j=1:gs.GraphicalBlocks.Size
                    gb = gs.GraphicalBlocks(j);
                    for w=1:gb.DataOutputPorts.Size
                        gOutputPort = gb.DataOutputPorts(w);
                        % gOutputPort.TestPoint is a sufficient condition
                        % to enable logging but it is not necessary: check
                        % Connected and Logged only and if there is a valid
                        % and active DataInterface
                        if gOutputPort.Connected && gOutputPort.Logged && ...
                                gOutputPort.DataInterfaces.Size==1
                            datainterface = gOutputPort.DataInterfaces(1);
                            if ~rtw.connectivity.CodeInfoUtils.isDataInterfaceAccessible(datainterface)
                                % the variable isn't accessible in the
                                % generated code due to its scope
                                continue;
                            end
                            if ~isempty(datainterface.VariantInfo) && ~datainterface.VariantInfo.VariantActive
                                % variant not active
                                continue;
                            end
                            loggedSignals(end+1) = datainterface; %#ok<AGROW>
                            loggedOffset = 0;
                            loggedLength = 0;
                            useRegion = datainterface.Type.isMatrix && ...
                                datainterface.Type.Dimensions.Size == 1 && ...
                                ~datainterface.Type.BaseType.isChar;
                            if useRegion
                                if gOutputPort.RegionLength.Size == 1
                                    loggedLength = gOutputPort.RegionLength(1);
                                end
                                if gOutputPort.RegionOffset.Size == 1
                                    loggedOffset = gOutputPort.RegionOffset(1);
                                end
                            end
                            portId = gOutputPort.OriginalPortIndex + 1;
                            loggedSID = char(Simulink.URL.PortURL(gb.SID, 'out', portId));
                            loggedName = gOutputPort.EscapedSignalLabel;
                            st = struct('name', loggedName, 'sid', loggedSID, ...
                                'offset', loggedOffset, 'length', loggedLength);
                            if isempty(sigLogInfo)
                                sigLogInfo = st;
                            else 
                                sigLogInfo(end+1) = st; %#ok<AGROW>
                            end
                        end
                    end
                end
            end
        end
        
        function [loggedStates, isMultiRateState, isPostUpload] = getLoggableStates(bHierarchy)
            loggedStates = rtw.connectivity.CodeInfoUtils.getEmptyInterfaceCommon(...
                'DataInterface', true);
            isMultiRateState = [];
            isPostUpload = [];
            
            for i=1:bHierarchy.GraphicalSystems.Size
                gs = bHierarchy.GraphicalSystems(i);
                for j=1:gs.GraphicalBlocks.Size
                    gb = gs.GraphicalBlocks(j);
                    for w = 1:gb.DiscreteStates.Size
                        dataInterface = gb.DiscreteStates(w);
                        impl = dataInterface.Implementation;
                        if isempty(impl)
                            % skip if there is no implementation:
                            % the state is implemented as bitfield
                            continue;
                        end
                        if isa(impl, 'coder.descriptor.AutosarMemoryExpression')
                            % with this memory expression, the type of the
                            % base region is empty
                            continue;
                        end
                        if ~rtw.connectivity.CodeInfoUtils.isDataInterfaceAccessible(dataInterface)
                            % member not accessible outside the class - no
                            % public interface
                            continue;
                        end
                        if ~rtw.pil.CodeInfoData.isInterfaceActive(dataInterface)
                            % part of inactive variant
                            continue;
                        end
                        while ~rtw.connectivity.CodeInfoUtils.isa(impl, 'Variable')
                            impl = impl.BaseRegion;
                        end
                        if rtw.connectivity.CodeInfoUtils.isa(impl, 'Variable') && ...
                                isprop(impl, 'StorageSpecifier') && ...
                                strcmp(impl.StorageSpecifier, 'static')
                            % the first element in the data structure has a
                            % static scope - xil_interface.c cannot access
                            % it
                            continue;
                        end
                        if isempty(dataInterface.SID)
                            % if the SID is missing, use parent's
                            dataInterface.SID = gb.SID;
                        end
                        % assign correct state name
                        dataInterface.GraphicalName = rtw.connectivity. ...
                            CodeInfoUtils.getStateName(gb, dataInterface);
                        
                        loggedStates(end+1) = dataInterface; %#ok<AGROW>
                        isMultiRateState(end+1) = gb.IsMultirate; %#ok<AGROW>
                        isPostUpload(end+1) = rtw.connectivity.CodeInfoUtils. ...
                            isStateUploadedLikeSignals(gb.Type); %#ok<AGROW>
                    end
                end
            end
        end
        
        function stateName = getStateName(gb, dataInterface)
            stateName = '';
            if rtw.connectivity.CodeInfoUtils.isa(gb, 'DiscreteStateBlock')
                stateName = gb.StateName;
            end
            % if the StateName block parameter is set for the block, use
            % it. Otherwise, process the GraphicalName in the dataInterface
            if isempty(stateName)
                % remove block name from the state name. For each
                % state, Code descriptor uses as name the name of
                % the variable. In the generated code, it is set as
                % block name + '_' + state name (DSTATE if not
                % specified) + postfix to assure uniqueness.
                % This will help to match with states from normal
                % mode simulations. Those states are listed in SDI
                % using only the state name
                prefix = [regexprep(gb.GraphicalName, '[\s-]+', '') '_'];
                if startsWith(dataInterface.GraphicalName, prefix)
                    stateName = eraseBetween(dataInterface.GraphicalName, 1, length(prefix));
                else
                    stateName = dataInterface.GraphicalName;
                end
            end
        end
        
        function res = isStateUploadedLikeSignals(blockType)
            % this method returns true if the specified block type needs
            % that its states are uploaded like signals when the simulation
            % step. Otherwise, it returns false and the state is uploaded
            % before the execution of the simulation step
            
            res = any(strcmp(blockType, {'Buffer', 'Unbuffer', 'S-Function'}));
        end
        
    end
    
end
