classdef (Hidden = true) CodeInfoSfunction < rtw.connectivity.CodeInfoWriterProject
%CODEINFOSFUNCTION creates an s-function
%   Creates an s-function with interface defined by RTW.CodeInfo
%
%   This is an undocumented class. Its methods and properties are likely to
%   change without warning from one release to the next.

%   Copyright 2008-2024 The MathWorks, Inc.
%   

    % properties
    properties (SetAccess = 'immutable', GetAccess = 'protected')
        sfunSourcePath;
        sfunOutDir;
        designDataLocation;
        
        HasResetFunction;
        IsPeriodicTriggered;
        IsConstant;

        HasPortlessSimulinkFunctionPortGroups;
        
        % feature control, used in order to avoid using Slfeature here.
        XilNoWrapper;
    end
    
    properties (SetAccess = 'immutable', GetAccess = 'private')
        AllSampleTimesOnPorts;
        SampleTimes;
    end
    
    properties (SetAccess = 'protected', GetAccess = 'protected')
       CodeFunctionArgDecs = 'SimStruct *S';
       CodeFunctionArgs = 'S';
    end
    
    properties (Access = 'private')
        AllNamedObjectsDefined;
    end

    methods (Abstract, Access = 'protected')
        writeSectionServerFunctionWrappers(this);
    end
        
    methods
        % constructor
        function this = CodeInfoSfunction(codeInfoUtils, ...                                          
                                          sfunSourcePath, ...
                                          sfunOutDir, ...
                                          designDataLocation, ...
                                          xilNoWrapper, ...
                                          timingInterfaceFlags)
            narginchk(6, 6);
            % call super class constructor
            this@rtw.connectivity.CodeInfoWriterProject(codeInfoUtils, sfunSourcePath);                              
            % store sfunSourcePath & sfunOutDir
            this.XilNoWrapper = xilNoWrapper;
            this.sfunSourcePath = sfunSourcePath;
            this.sfunOutDir = sfunOutDir;
            this.designDataLocation = designDataLocation;
            
            this.IsConstant = timingInterfaceFlags.IsConstant;
            this.IsPeriodicTriggered = timingInterfaceFlags.IsPeriodicTriggered;
            this.HasPortlessSimulinkFunctionPortGroups = timingInterfaceFlags.HasPortlessSimulinkFunctionPortGroups;
            this.HasResetFunction = timingInterfaceFlags.HasResetFunction;
            
            this.SampleTimes = this.initializeSampleTimes( ...
                this.codeInfoUtils, ...
                this.HasResetFunction);
            
            this.AllSampleTimesOnPorts = this.initializeAllSampleTimesOnPorts( ...
                this.codeInfo, ...
                this.codeInfoUtils, ...
                this.SampleTimes, ...
                this.IsConstant, ...
                this.HasPortlessSimulinkFunctionPortGroups, ...
                timingInterfaceFlags.HasInternalModelWideEventTs, ...
                timingInterfaceFlags.HasContinuousFundamentalSampleTime, ...
                timingInterfaceFlags.ExposesITPorts);
            
            this.AllNamedObjectsDefined = containers.Map.empty;
        end
    end
    
    methods (Access = 'public')       
        function sfunSourcePath = getSfunSourcePath(this)
           sfunSourcePath = this.sfunSourcePath; 
        end                                        
    end       
    
    methods (Access = 'protected')     
        function writeOutputBody(this)
            this.writeSectionHeader;
            this.writeSectionPreprocessor;
            this.writeSectionGlobalVars;
            this.writeSectionGlobalData;
            this.writeSectionUserFunctions;            
            this.writeSectionCheckParameters;
            this.writeSectionExportFunctionsWrappers;
            this.writeSectionServerFunctionWrappers;
            % Model initialization
            this.writeSectionInitializeSizes;
            this.writeSectionSetInputPortSampleTime;
            this.writeSectionSetOutputPortSampleTime;
            this.writeSectionInitializeSampleTimes;
            this.writeSectionSetupRTR;
            this.writeSectionPause;
            this.writeSectionStart;
            % Simulation loop
            this.writeSectionPeriodicFunctions;
            this.writeSectionProcessParameters;
            this.writeSectionInitializeConditions;
            this.writeSectionSetOutputDimsRuleFcn;
            this.writeSectionFinalizeDimsFcn;
            this.writeSectionSetWorkWidths;
            this.writeSectionOutputs;
            this.writeSectionUpdate;
            % Simulation end
            this.writeSectionTerminate;
            this.writeSectionCleanupRTR;
            % Others callbacks
            this.writeSectionEnable;
            this.writeSectionDisable;
            this.writeSectionTrailer;
        end
        
        function mexOptions = getAdditionalMEXOptions(this) %#ok<MANU>
            % template method to override in subclass
            %
            mexOptions = {};
        end
               
        function buildSfunction(this, lDefaultMexCompilerKey)             
            % On Unix, mex should work just fine. On PC, use supplied input
            % argument specifying default mex compiler that was determined
            % at the start of the build. With this approach, we latch any
            % mex configuration options from pwd at the point where the
            % build was initiated.
            mexOptsFileOption = [];
            
            % sfunOutDir can be used unmodified by mex in most situations.
            % But for lcc on PC it will be transformed to avoid spaces.
            locSfunOutDir = this.sfunOutDir;
            if ispc
                compInfo = coder.make.internal.getMexCompInfoFromKey(lDefaultMexCompilerKey);
                
                % need to provide full path to the mexopts file so mex
                % works when current dir is a UNC path. If g854386 gets
                % fixed, then we could simply just pass the file name (no
                % full-path)
                mexOptsFile = compInfo.comp.MexOpt;
                assert(exist(mexOptsFile,'file')==2, 'mex opts file "%s" does not exist!', mexOptsFile);
                mexOptsFileOption = {'-f', mexOptsFile};
                
                if strcmpi(compInfo.toolChain,'lcc')
                    %See g1770467, working around LCC bug when there are
                    %spaces in the out directory.
                    locSfunOutDir = coder.make.internal.transformPaths(locSfunOutDir);
                end
            end
            
            % build s-function via MEX
            % add -compatibleArrayDims per g678632
            mexOptions = this.getAdditionalMEXOptions;
            mexOptions = [mexOptions mexOptsFileOption];
            mexLibs = this.getMEXLibs();
            % use the MexWrapper to call mex, this offers a test hook to
            % add extra mex options
            mexWrapper = rtw.connectivity.MexWrapper;
            mexWrapper.mex(locSfunOutDir, ...
                mexOptions, ...               
                this.getSfunSourcePath, ...
                mexLibs);
        end
        
        function writeSectionHeader(this)
            [~, sfunName, sfunExt] = fileparts(this.getSfunSourcePath);
            sfunName = [sfunName sfunExt];
            this.writer.writeCellLines({'/*';
                [' * ' sfunName];
                ' *';
                ' * Automatically generated s-function with I/O interface for:';
                [' * Component: ' this.codeInfo.Name];
                [' * Component Simulink Path: ' this.codeInfo.GraphicalPath];});
            % optional customization
            this.writeHeaderCustomizationHook;                            
            this.writer.writeCellLines({' *';
                ' */'; 
                '';});
        end
        
        function writeHeaderCustomizationHook(this) %#ok<MANU>
            % hook for header customization
            %
            % override in subclass            
        end
                
        function writeSectionPreprocessor(this)
            [~, sfunName] = fileparts(this.getSfunSourcePath);
            this.writer.writeCellLines({['#define S_FUNCTION_NAME ' sfunName];
                '#define S_FUNCTION_LEVEL 2';
                '#if !defined(RTW_GENERATED_S_FUNCTION)';
                '#define RTW_GENERATED_S_FUNCTION';
                '#endif';
                '';});
            
            % write preprocessor defines
            this.writeSectionPreprocessorDefines;
            
            this.writer.wLine('#include <stdio.h>');
            if slfeature('SupportNonfiniteLiterals') > 0 || ...
                    this.codeInfoUtils.isTaskCounterRequired()
                this.writer.wLine('#include <math.h>');
            end
            % for memcpy
            this.writer.wLine('#include <string.h>');
            this.writeSectionAdditionalIncludes;           
            this.writer.wLine('');
        end  
        
        function writeSectionAdditionalIncludes(this)
            this.writer.writeCellLines({'#include "simstruc.h"';
                '#include "simtarget/slMdlrefSimTargetCoreHeaders.h"';
                '#include "simtarget/slMdlrefSimTargetInstrumentationHeaders.h"';
                '#include "fixedpoint.h"'});
            % note that fixedpoint.h is installed with Simulink            
        end
        
        function writeSectionPreprocessorDefines(this)
            if this.codeInfoUtils.isExportFunctions
                this.writer.writeLine('#define S_FUNCTION_EXPORTS_FUNCTION_CALLS');
                this.writer.newLine;
            end
        end
        
        function writeSectionGlobalData(this) %#ok<MANU>            
        end                
        
        function writeSectionGlobalVars(~)
        end                

        function writeSectionInitializeSizes(this)
            this.writer.wBlockStart('static void mdlInitializeSizes(%s)', this.CodeFunctionArgDecs);
            this.writeSectionInitializeSizesBody;
            this.writer.wBlockEnd();
            this.writer.newLine;
        end                
        
        % by default, 0 s-fun params are registered
        function numSFcnParams = getNumSFcnParams(this) %#ok<MANU>
            numSFcnParams = 0;
        end
                       
        function writeSectionInitializeSizesBodyHeader(this) 
            this.writer.writeCellLines({['ssSetNumSFcnParams(S, ' ...
                int2str(this.getNumSFcnParams) ');  /* Number of expected parameters */'];
                'if (ssGetNumSFcnParams(S) == ssGetSFcnParamsCount(S)) {';
                '#if defined(MDL_CHECK_PARAMETERS)'
                '   mdlCheckParameters(S);';
                '#endif';
                '   if (ssGetErrorStatus(S) != (NULL)) return;';
                '} else {';
                '   /* Parameter mismatch will be reported by Simulink */';
                '   return;';
                '}';
                '';
                'ssSetNumContStates(S, 0);';
                'ssSetNumDiscStates(S, 0);';                
                '/* no support for SimState */';
                'ssSetSimStateCompliance(S, DISALLOW_SIM_STATE);';
                '';});
            %removed calls to SFunction.cpp for the new architecture.
            if ~this.XilNoWrapper
                this.writer.writeCellLines({ ...
                '/* Allow signal dimensions greater than 2 */';
                'ssAllowSignalsWithMoreThan2D(S);';
                '';
                '/* Allow fixed-point data types with 33 or more bits */';
                'ssFxpSetU32BitRegionCompliant(S,1);';
                ''});
            end
        end                                         
        
        function writeSectionInitializeSizesGlobalDS(this)
            dataInterfaces = this.codeInfoUtils.getGlobalDataStores;
            numDI = length(dataInterfaces);
            if numDI > 0
                this.writer.writeLine('{');
                this.writer.incIndent;
                this.writer.writeLine('boolean_T success;');
                this.writer.writeLine(['ssSetNumDataStores(S, ' num2str(numDI) ');']);
                for i=1:numDI
                    di = dataInterfaces(i);
                    diIdx = num2str(i-1);
                    dataReads = this.codeInfoUtils.getArray(di.DataReads);
                    dataWrites = this.codeInfoUtils.getArray(di.DataWrites);
                    % determine read / write type
                    if ~isempty(dataReads) && ~isempty(dataWrites)
                        readWrite = 'SS_READER_AND_WRITER';
                    elseif ~isempty(dataReads)
                        readWrite = 'SS_READER_ONLY';
                    elseif ~isempty(dataWrites)
                        % currently, write only requires an implicit read
                        % in order to support element access
                        readWrite = 'SS_READER_AND_WRITER';
                    else
                        % Set readWrite to a safe default value
                        % see g1690866 for further details.
                        readWrite = 'SS_READER_ONLY';
                    end
                    elementAccess = 'false';
                    this.writer.writeLine(['ssRegisterGlobalDataStoreFromName(S, ' ...
                        diIdx ', ' ...
                        '"' di.GraphicalName '", ' ...
                        readWrite ', ' ...
                        elementAccess ', ' ...
                        '&success);']);
                    this.writer.writeLine('if (!success) return;');
                end
                this.writer.decIndent;
                this.writer.writeLine('}');
                this.writer.newLine;
            end
            % If statement used to skip inports and outports processing for
            % the new XIL architecture during the top-model XIL simulation,
            % but the processing is needed for the build process. 
            if this.XilNoWrapper
                this.writer.writeLine('if(S->portInfo.regNumInputPortsFcn){');
            end
        end                                            
        
        function writeSectionInitializeSizesPort(~, ...
                                                 ~, ...
                                                 ~, ...
                                                 ~, ...
                                                 ~)
            % need to move all related functions down to
            % PILBlockSfunction...
            assert(false, 'writeSectionInitializeSizesPort should not be called in CodeInfoSfunction!');
        end
                
        function writeSectionInitializeSizesPortSetup(this, isInport)
            if isInport
                portTypeString = 'Input';
                ports = this.codeInfo.Inports;
            else
                portTypeString = 'Output';
                ports = this.codeInfo.Outports;
            end
            numPorts = length(ports);
            
            this.writer.writeLine(['if (!ssSetNum' portTypeString 'Ports(S, ' int2str(numPorts) ')) return;']);
            this.writer.newLine;

            for portIdx = 1:numPorts
                % 0-based indices in s-function
                sfunPortIdx = int2str(portIdx - 1);
                dataInterface = ports(portIdx);               
                this.writeSectionInitializeSizesPort(isInport, ...
                    portTypeString, ...
                    dataInterface, ...
                    sfunPortIdx);
            end
        end                
        
        function writeSectionInitializeSizesDatatypeId(this, type, includeCodeBlock, allowDuplicates)                        
            % Get storageId and namedObject for the data type.
            %
            % We don't support multi-word types at the component
            % boundary, but for components deep in a hierarchy the
            % longer word lengths need to be correctly registered
            % to enable the code to be generated from the parent
            % model. If the word length is > 64 bits, then we will get an
            % empty storage id.  This will trigger the
            % type to be registered as an anonymous fixdt below.
            throwErrorForMultiWordFix = false;
            [storageId, namedObject] = ...
                rtw.connectivity.CodeInfoTypeUtils.getCodeInfoStoredValueDTypeId(type, throwErrorForMultiWordFix);
            
            if ~isempty(namedObject)
                % register datatype with named object - enumtype or
                % alias type if we haven't already registered it
                if ~this.AllNamedObjectsDefined.isKey(namedObject) || allowDuplicates
                    if includeCodeBlock
                        this.writer.wBlockStart('');
                    end
                    this.writer.wLine('DTypeId dataTypeId = INVALID_DTYPE_ID;');
                    this.writer.writeCellLines({['ssRegisterTypeFromNamedObject(S, ' ...
                        '"' namedObject '", ' ...
                        '&dataTypeId);'];
                        'if (dataTypeId == INVALID_DTYPE_ID) return;'});
                    this.AllNamedObjectsDefined(namedObject) = true;
                    if includeCodeBlock
                        this.writer.wBlockEnd(''); %Empty code block
                    end
                end
            else
                if includeCodeBlock
                    this.writer.wBlockStart('');
                end
                this.writer.wLine('DTypeId dataTypeId = INVALID_DTYPE_ID;');
                % Simulink specific
                isBuiltIn64BitAllowed = false;
                if ~rtw.connectivity.CodeInfoTypeUtils.isFixedPoint(type, isBuiltIn64BitAllowed)
                    %Create a local Simulink instance of TypeUtils for this one
                    %call. Only Simulink builds will call this code and the
                    %overhead of creating this instance is low, so this is
                    %neater than passing the SILPILWrapperUtils up into the
                    %superclass.

                    this.writer.writeLine('/* set datatype */');
                    if storageId == rtw.connectivity.CodeInfoTypeEnum.TypeChar
                        % Register string data type id based on code info
                        % Use 0 for dynamic string 
                        if endsWith(type.Name, '_dynamic')
                            strSize = 0;
                        else
                            strSize = rtw.connectivity.CodeInfoUtils.getArray(type.Dimensions)-1; %Strip NULL terminator
                        end
                        
                        this.writer.wLine('dataTypeId = ssRegisterStringDataType(%s, %d);', ...
                            this.CodeFunctionArgs, strSize);
                    elseif storageId == rtw.connectivity.CodeInfoTypeEnum.TypeHalf                        
                        
                        this.writer.wLine('dataTypeId = ssRegisterDataTypeHalfPrecision(%s, 0);', ...
                            this.CodeFunctionArgs);
                    else
                        simulinkTypeUtilsInstance = rtw.connectivity.SimulinkTypeUtils;
                        builtInType = rtw.connectivity.TypeUtils.codeInfoTypeIdToType(storageId);
                        storageId = simulinkTypeUtilsInstance.builtinTypeToTypeId(builtInType);
                        % add datatype name for the datatypeid                    
                        this.writer.wLine('dataTypeId = %d;', storageId);
                    end
                else
                    % NOTE: if there is no fixed point license available
                    % when creating the component then Simulink will
                    % allow the use of doubles everywhere in place of fixed
                    % point types and we won't even enter this branch.
                    baseType = rtw.connectivity.CodeInfoTypeUtils.getBaseType(type);
                    assert(baseType.isNumeric, 'Base type is not numerictype');
                    % obtain a datatype id by registering the fixed point datatype
                    % using function in libfixedpoint library (installed
                    % with Simulink)
                    %
                    % obeyDataTypeOverride is 0 because this s-function
                    % does not support dynamic data type override.
                    % I.e. it must be regenerated if the data type changes.
                    this.writer.writeCellLines({'/* Register fixed point datatype to obtain datatype id */';
                        ['/* isSigned = ' int2str(baseType.Signed) ' */'];
                        ['/* wordLength = ' int2str(baseType.WordLength) ' */'];
                        ['/* fracSlope = ' rtw.connectivity.CodeInfoUtils.double2str(baseType.SlopeAdjustmentFactor) ' */'];
                        ['/* fixedExp = ' int2str(baseType.FixedExponent) ' */'];
                        ['/* bias = ' rtw.connectivity.CodeInfoUtils.double2str(baseType.Bias) ' */'];
                        '/* obeyDataTypeOverride = 0 */';});                           
                    if baseType.isScaledDouble
                        regFunction = 'ssRegisterDataTypeFxpScaledDouble';                        
                    else
                        regFunction = 'ssRegisterDataTypeFxpFSlopeFixExpBias';
                    end                    
                    this.writer.writeLine(['dataTypeId = ' regFunction '(S, ' ...
                        int2str(baseType.Signed) ', ' ...
                        int2str(baseType.WordLength) ', ' ...
                        rtw.connectivity.CodeInfoUtils.double2str(baseType.SlopeAdjustmentFactor) ', ' ...
                        int2str(baseType.FixedExponent) ', ' ...
                        rtw.connectivity.CodeInfoUtils.double2str(baseType.Bias) ', ' ...
                        '0);']);                                              
                end
                if includeCodeBlock
                    this.writer.wBlockEnd(''); %Empty code block
                end
            end
        end
        
        function writeSectionInitializeSizesBodyTrailerCommon(this)                      
            % Place this code in a separate method so ModelBlockSfunction 
            % can leverage it
            
            this.writeSectionSetNumPWork;
            this.writer.writeCellLines({'ssSetNumRWork(S, 0);';
                'ssSetNumIWork(S, 0);';
                'ssSetNumModes(S, 0);';
                'ssSetNumNonsampledZCs(S, 0);'});            
            
            % write sfun options
            this.writeSectionSfunctionOptions;     
            
            % there is no execution order dependency between mdlStart and
            % mdlProcessParameters
            this.writer.writeLine('ssSetModelReferenceNormalModeSupport(S, MDL_START_AND_MDL_PROCESS_PARAMS_OK);');
            
            % Define any missing object types
            this.writeCallerFunctionTypeRegistrations();
        end
        
        function writeSectionSetNumPWork(this)
            % Write the number of PWorks - overridden by subclass when
            % XIL application PWorks are to be excluded for code generation.
            numPWork = this.getNumPWork;
            this.writer.writeLine(['ssSetNumPWork(S, ' int2str(numPWork) ');']);
        end
        
        function writeCallerFunctionTypeRegistrations(this)            
            %Define any types on undefined caller function ports.
                      
            callerFunctionsInfo = this.codeInfoUtils.getUndefinedCallerFunctionsInfo;
            definedTypeList = {}; %Keep a list of types we've defined to
            %ensure we don't repeatedly define them again.
            for callFcnIdx = 1:length(callerFunctionsInfo)
                callerFunctionInfo = callerFunctionsInfo(callFcnIdx);
                args = callerFunctionInfo.getAllCodeInfoArgs;
                for codeInfoArgIdx = 1:length(args)
                    if callerFunctionInfo.skipSLProcessingForArg(args(codeInfoArgIdx))
                        continue
                    end
                    curArgType = args(codeInfoArgIdx).Type;                    
                    type = rtw.connectivity.CodeInfoTypeUtils.getBaseType(curArgType);
                    if ~any(cellfun(@(x) isequal(type,x), definedTypeList))
                        this.writer.wBlockStart('if (ssGetSimMode(S) != SS_SIMMODE_SIZES_CALL_ONLY)');
                        this.writer.wLine('#if defined (MATLAB_MEX_FILE)');
                        includeCodeBlock = false;
                        allowDuplicates = false;
                        this.writeSectionInitializeSizesDatatypeId(type, includeCodeBlock, allowDuplicates);
                        this.writer.wLine('#endif');
                        this.writer.wBlockEnd() % if (ssGetSimMode(S) != SS_SIMMODE_SIZES_CALL_ONLY)
                    end
                    definedTypeList{end+1} = type; %#ok<AGROW>
                end
            end
        end
        
        function writeSectionInitializeSizesBodyTrailer(this)
            if this.codeInfoUtils.isExportFunctions
                if this.getHasInitTermPorts
                    this.writer.writeLine('/* using port based sample times */');
                    this.writer.writeLine('ssSetNumSampleTimes(S, PORT_BASED_SAMPLE_TIMES);');
                else 
                    this.writer.writeLine('/* export functions */');
                    this.writer.writeLine('ssSetNumSampleTimes(S, 1);');
                end
            else                                               
                sampleTimes = this.getSampleTimes;
                numSampleTimes = int2str(length(sampleTimes));
                if this.usePortBasedSampleTimes
                    this.writer.writeLine('/* using port based sample times */');
                    this.writer.writeLine('ssSetNumSampleTimes(S, PORT_BASED_SAMPLE_TIMES);');
                else
                    if this.useHybridSampleTimes
                        this.writer.writeLine('/* using hybrid block and port based sample times */');
                    else
                        this.writer.writeLine('/* using block based sample times */');
                    end
                    this.writer.writeLine(['ssSetNumSampleTimes(S, ' numSampleTimes ');']);
                end
            end
                                     
            if this.codeInfoUtils.isSampleTimeIndependent
               this.writer.writeLine('/* this s-function is sample time independent: allow sub-models containing it to inherit sample times */');
               this.writer.writeLine('ssSetModelReferenceSampleTimeInheritanceRule(S, USE_DEFAULT_FOR_DISCRETE_INHERITANCE);');
            else
               this.writer.writeLine('/* this s-function is sample time dependent: do not allow sub-models containing it to inherit sample times */');                
               this.writer.writeLine('ssSetModelReferenceSampleTimeInheritanceRule(S, DISALLOW_SAMPLE_TIME_INHERITANCE);');               
            end
            if this.XilNoWrapper
                this.writer.writeLine('}');
            end
            this.writeSectionInitializeSizesBodyTrailerCommon;
        end
        
        function writeSectionSfunctionOptions(this)
            
            this.writer.writeLine('{');
            this.writer.incIndent;
            
            % write sfun options
            this.writer.writeLine('uint_T options = 0;');
            
            if ~this.codeInfoUtils.isExportFunctions && this.useHybridSampleTimes
                this.writer.writeLine('/* using hybrid block and port based sample times */');
                this.writer.writeLine('options |= SS_OPTION_PORT_SAMPLE_TIMES_ASSIGNED;');
            end
                        
            % CodeInfoUtils already checks that INHERITED and PERIODIC 
            % rates are mutually exclusive, and one or the other must exist.
            % 
            % Therefore, PIL never sees a "const block" 
            % (as defined by model reference) component that only has 
            % constant sample time - the CodeInfo representation stops this. 
            %
            % A component that inherits a constant 
            % sample time is not a "const block" and will be disallowed by
            % the SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME option.
            %
            % A "const block" does not have inports and may or may not have
            % outports with constant sample time.
            %
            % XIL simulation of a "const block" is never allowed.
            % But the Model Block XIL S-function can also be used for
            % diagram update/information propagation from submodels
            % deeper in a XIL hierarchy, in which case a "const block" is
            % permissible.
            %
            % But we can deduce from binfo/modelinterface whether we have a
            % const block.
            %
            % In all other cases, we set SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME
            % which is equivalent to the %if !isConstBlk ||
            % (CompiledModel.DWorks.NumSFcnWrapperDWorks >0) condition in 
            % modelrefsfcnbody.tlc; for the actual model being simulated
            % for PIL, isConstBlk is always false.
            if ~this.IsConstant
                this.writer.writeLine('/* do not allow (including inheritance of) constant block-based sample times*/');
                this.writer.writeLine('options |= SS_OPTION_DISALLOW_CONSTANT_SAMPLE_TIME;');
            end
                        
            % check for const outports
            if this.codeInfoUtils.hasConstOutput || ...
                    this.codeInfoUtils.hasModelWideEvents ||...
                    this.codeInfoUtils.hasParameterChangeEvent
                this.writer.writeLine('/* constant sample times on ports */');
                this.writer.writeLine('options |= SS_OPTION_ALLOW_CONSTANT_PORT_SAMPLE_TIME;');
            end                        
            
            if (this.codeInfoUtils.isSampleTimeIndependent && ~this.codeInfoUtils.isExportFunctions) ...
                    || this.isXrelBlockFromModelRef || this.IsPeriodicTriggered
                % add this option for cross-release SFunctions generated
                % from model reference code interface. this avoids
                % receiving an error about the missing SS_OPTION if the
                % block is simulated in a triggered subsystem. if the
                % sample time of the ports is not compatible with the
                % trigger we receive an error (ctrl-D) with a more meaningful
                % message. see tXrelSystemReset_Trigger/negative_InvBlkInTrigSubsys
                this.writer.writeLine('/* allow s-function to be called from a triggered subsystem */');
                this.writer.writeLine('options |= SS_OPTION_ALLOW_PORT_SAMPLE_TIME_IN_TRIGSS;');
            end
            
            if this.isXrelBlock
                % To support model ref interface cross release blocks (with
                % SS_OPTION_ALLOW_PORT_SAMPLE_TIME_IN_TRIGSS), cross
                % release blocks with parameters, and cross release blocks
                % with data store memories inside model block SIL submodels
                % the cross release block will inline stub simulation
                % behavior into the model ref SIM target S-function.  A top
                % model accelerator build should NOT inline since
                %   (a) we want the accelerator sim of the top model to
                %   still use SIL/PIL for the cross release block rather
                %   than inlining;
                %   (b) the imported code might not be compatible with the
                %   host (PIL);
                %   (c) the type definitions in the imported code might not
                %   be consistent with those defined for S-functions even
                %   for code generated for SIL.
                this.writer.writeLine('/* Use TLC with accelerator _ONLY_ for model reference sim target   */');
                this.writer.writeLine('/* Model ref accelerator simulation of the cross release block is   */');
                this.writer.writeLine('/* NOT supported, but we need to be able to build a stub SIM-target */');
                this.writer.wBlockStart('if ssRTWGenIsModelReferenceSimTarget(S)');
                this.writer.writeLine('options |= SS_OPTION_USE_TLC_WITH_ACCELERATOR;');
                this.writer.wBlockEnd();
                
                if this.isXrelBlockReusable
                    % Set SS_OPTION_WORKS_WITH_CODE_REUSE if the
                    % cross-release block is for code with a multi-instance
                    % model reference code interface.
                    this.writer.writeLine('options |= SS_OPTION_WORKS_WITH_CODE_REUSE;');
                end
            end
            
            % add standard options
            this.writer.writeLine('options |= SS_OPTION_SUPPORTS_ALIAS_DATA_TYPES;');
            this.writer.writeLine('options |= SS_OPTION_CALL_TERMINATE_ON_EXIT;');
            % PIL s-fun not inlined so not setting
            % SS_OPTION_WORKS_WITH_CODE_REUSE

            this.writer.writeLine('ssSetOptions(S, options);');
            this.writer.decIndent;
            this.writer.writeLine('}');

        end
        
        function writeSectionSetInputPortSampleTime(this)
            this.writer.writeCellLines({'#define MDL_SET_INPUT_PORT_SAMPLE_TIME /* Change to #undef to remove function */';
                                        '#if defined(MDL_SET_INPUT_PORT_SAMPLE_TIME) && defined(MATLAB_MEX_FILE)'; 
                                        'static void mdlSetInputPortSampleTime(SimStruct *S, int_T portIdx, real_T sampleTime, real_T offsetTime) {'});
            this.writer.incIndent;
            this.writeSectionSetInputPortSampleTimeBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_SET_INPUT_PORT_SAMPLE_TIME */');            
            this.writer.newLine;
        end
        
        function writeSectionSetInputPortSampleTimeBody(this) %#ok<MANU>
        end                
           
        function writeSectionSetOutputPortSampleTime(this)
            this.writer.writeCellLines({'#define MDL_SET_OUTPUT_PORT_SAMPLE_TIME /* Change to #undef to remove function */';
                                        '#if defined(MDL_SET_OUTPUT_PORT_SAMPLE_TIME) && defined(MATLAB_MEX_FILE)'; 
                                        'static void mdlSetOutputPortSampleTime(SimStruct *S, int_T portIdx, real_T sampleTime, real_T offsetTime) {'});
            this.writer.incIndent;
            this.writeSectionSetOutputPortSampleTimeBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_SET_OUTPUT_PORT_SAMPLE_TIME */');                        
            this.writer.newLine;
        end

        function writeSectionSetOutputPortSampleTimeBody(this) %#ok<MANU>
        end
               
        function writeSectionInitializeSampleTimes(this)
            this.writer.writeLine('static void mdlInitializeSampleTimes(SimStruct *S) {');
            this.writer.incIndent;
            this.writeSectionReturnIfCodeGen;
            if this.codeInfoUtils.isExportFunctions
                if ~this.getHasInitTermPorts
                    this.writer.writeLine('/* export functions */');
                    this.writer.writeLine('ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);');
                end
            else                
                if this.usePortBasedSampleTimes
                    this.writer.writeLine('/* using port based sample times */');
                else
                    if this.useHybridSampleTimes
                        this.writer.writeLine('/* using hybrid block and port based sample times */');
                    else
                        this.writer.writeLine('/* using block based sample times */');
                    end
                    
                    if this.IsPeriodicTriggered
                        this.writer.writeLine('ssSetSampleTime(S, 0, -1);');
                        this.writer.writeLine('ssSetOffsetTime(S, 0, 0);');
                    else
                        rates = this.getSampleTimes;
                        for rateIdx = 1:length(rates)
                            rate = rates(rateIdx);
                            sampleTimeIndex = int2str(this.getSampleTimeIndex(rate));
                            [sampleTimeCExpr, sampleOffsetCExpr] = this.getSampleTimeCExpression(rate);
                            this.writer.writeLine(['ssSetSampleTime(S, ' sampleTimeIndex ', ' sampleTimeCExpr ');']);
                            this.writer.writeLine(['ssSetOffsetTime(S, ' sampleTimeIndex ', ' sampleOffsetCExpr ');']);
                        end
                    end
                end
            end
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.newLine;            
        end
               
        function writeSectionCheckParameters(this)
            this.writer.writeCellLines({'/* This function checks the attributes of tunable parameters. */'; ...
                                        '#define MDL_CHECK_PARAMETERS'; ...
                                        '#if defined(MDL_CHECK_PARAMETERS) && defined(MATLAB_MEX_FILE)'; ...
                                        'static void mdlCheckParameters(SimStruct *S) {';});
            this.writer.incIndent;
            this.writeSectionCheckParametersBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_CHECK_PARAMETERS */');
            this.writer.newLine;
        end
        
        function writeSectionCheckParametersBody(this) %#ok<MANU>
        end
        
        function writeSectionReturnIfCodeGen(this)
            this.writer.wBlockStart('if(ssRTWGenIsCodeGen(S) || ssIsExternalSim(S))');
            this.writer.writeLine('return;');
            this.writer.wBlockEnd;
        end
        
        function writeSectionSetupRTR(this)           
           this.writer.writeCellLines({'#define MDL_SETUP_RUNTIME_RESOURCES  /* Change to #undef to remove function */';
           '#if defined(MDL_SETUP_RUNTIME_RESOURCES)';           
           ['static void mdlSetupRuntimeResources(' this.CodeFunctionArgDecs ') {'];});
           this.writer.incIndent;
           this.writeSectionSetupRTRBody;    
           this.writer.decIndent;
           this.writer.writeLine('}');
           this.writer.writeLine('#endif /* MDL_SETUP_RUNTIME_RESOURCES */');           
           this.writer.newLine;           
        end
                
        function writeSectionSetupRTRBody(this) %#ok<MANU>
        end
        
        function writeSectionStart(this)           
           this.writer.writeCellLines({'#define MDL_START  /* Change to #undef to remove function */';
           '#if defined(MDL_START)';           
           ['static void mdlStart(' this.CodeFunctionArgDecs ') {'];});
           this.writer.incIndent;
           this.writeSectionStartBody;    
           this.writer.decIndent;
           this.writer.writeLine('}');
           this.writer.writeLine('#endif /* MDL_START */');           
           this.writer.newLine;           
        end
        
        function writeSectionStartBody(this) %#ok<MANU>
        end
        
        function writeSectionPause(this)
           this.writer.writeCellLines({'#define MDL_SIM_STATUS_CHANGE  /* Change to #undef to remove function */';
                                       '#if defined(MDL_SIM_STATUS_CHANGE)';
                                       'static void mdlSimStatusChange(SimStruct *S,  ssSimStatusChangeType simStatus) {';});
           this.writer.incIndent;
           this.writeSectionPauseBody;
           this.writer.decIndent;
           this.writer.writeLine('}');
           this.writer.writeLine('#endif /* MDL_SIM_STATUS_CHANGE */');           
           this.writer.newLine;      
        end

        function writeSectionPauseBody(this) %#ok<MANU>
        end
        
        function writeSectionProcessParameters(this)
            
            this.writer.writeCellLines({'#define MDL_PROCESS_PARAMETERS';
                 '#if defined(MDL_PROCESS_PARAMETERS)';
                ['static void mdlProcessParameters(' this.CodeFunctionArgDecs ') {'];});
            this.writer.incIndent;
            this.writeSectionProcessParametersBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_PROCESS_PARAMETERS */');
            this.writer.newLine;            
        end
        
        function writeSectionProcessParametersBody(this)             %#ok<MANU>
        end                
        
        function writeSectionInitializeConditions(this)
            if this.isInitializeConditionsRequired
                this.writer.writeCellLines({'#define MDL_INITIALIZE_CONDITIONS   /* Change to #undef to remove function */';
                    '#if defined(MDL_INITIALIZE_CONDITIONS)';
                    'static void mdlInitializeConditions(SimStruct *S) {';});
                this.writer.incIndent;
                this.writeSectionReturnIfCodeGen;
                this.writeSectionInitializeConditionsBody;
                this.writer.decIndent;
                this.writer.writeLine('}');
                this.writer.writeLine('#endif /* MDL_INITIALIZE_CONDITIONS */');
                this.writer.newLine;
            end
        end
        
        function writeSectionInitializeConditionsBody(this) %#ok<MANU>
        end
        
        function writeSectionSetOutputDimsRuleFcn(this) %#ok<MANU>
        end
        
        function writeSectionFinalizeDimsFcn(this) %#ok<MANU>
        end
        
        function writeSectionSetWorkWidths(this)
            this.writer.writeCellLines({'#define MDL_SET_WORK_WIDTHS   /* Change to #undef to remove function */';
                '#if defined(MDL_SET_WORK_WIDTHS)';
                'static void mdlSetWorkWidths(SimStruct *S) {';});
            this.writer.incIndent;
            this.writeSectionSetWorkWidthsBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_SET_WORK_WIDTHS */');
            this.writer.newLine;
        end
        
        function writeSectionSetWorkWidthsBody(this) %#ok<MANU>
        end
                                
        function writeSectionOutputs(this)  
            if ~this.codeInfoUtils.isExportFunctions
                this.writer.wLine('static void mdlOutputs(%s, int_T tid) {',this.CodeFunctionArgDecs);
                this.writer.incIndent;
                defaultTaskIdx = 1;
                this.writeSectionOutputsBody(defaultTaskIdx);
                this.writer.decIndent;
                this.writer.writeLine('}');
                this.writer.newLine;
            end
        end  
        
        function writeSectionUpdate(this)  
            this.writer.wLine('#define MDL_UPDATE');
            this.writer.wLine('#if defined(MDL_UPDATE)'); % Need MDL_UPDATE defined, otherwise mdlUpdate does not get called
            this.writer.wBlockStart('static void mdlUpdate(%s, int_T tid)',this.CodeFunctionArgDecs);
            this.writeSectionUpdateBody;
            this.writer.wBlockEnd; % static void mdlUpdate
            this.writer.writeLine('#endif');
            this.writer.newLine;
        end

        
        function writeSectionUpdateBody(this) %#ok<MANU>
        end
        
        function writeSectionOutputsBody(this, taskIdx) %#ok<INUSD>
        end     
        
        function writeSectionPeriodicFunctions(this)  %#ok<MANU>
        end
        
        function writeSectionTerminate(this)
            this.writer.wLine('static void mdlTerminate(%s) {', this.CodeFunctionArgDecs);
            this.writer.incIndent;
            this.writeSectionReturnIfCodeGen;
            this.writeSectionTerminateBody;
            
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.newLine;            
        end               
        
        function writeSectionTerminateBody(this) %#ok<MANU>
        end
        
        function writeSectionCleanupRTR(this)           
           this.writer.writeCellLines({'#define MDL_CLEANUP_RUNTIME_RESOURCES  /* Change to #undef to remove function */';
           '#if defined(MDL_CLEANUP_RUNTIME_RESOURCES)';           
           ['static void mdlCleanupRuntimeResources(' this.CodeFunctionArgDecs ') {'];});
           this.writer.incIndent;
           this.writeSectionCleanupRTRBody;    
           this.writer.decIndent;
           this.writer.writeLine('}');
           this.writer.writeLine('#endif /* MDL_CLEANUP_RUNTIME_RESOURCES */');           
           this.writer.newLine;           
        end
        
        function writeSectionCleanupRTRBody(this) %#ok<MANU>
        end
        
        function writeSectionEnable(this)
            this.writer.writeCellLines({'#define MDL_ENABLE';
                '#if defined(MDL_ENABLE)';
                'static void mdlEnable(SimStruct *S) {';});
            this.writer.incIndent;
            this.writeSectionEnableBody;
            this.writer.decIndent;
            this.writer.writeLine('}');
            this.writer.writeLine('#endif /* MDL_ENABLE */');
            this.writer.newLine;
        end                
        
        function writeSectionDisable(this)
           this.writer.writeCellLines({'#define MDL_DISABLE';
                   '#if defined(MDL_DISABLE)';
                   'static void mdlDisable(SimStruct *S) {';});
           this.writer.incIndent;
           this.writeSectionDisableBody;
           this.writer.decIndent;
           this.writer.writeLine('}');
           this.writer.writeLine('#endif /* MDL_DISABLE */');       
           this.writer.newLine;           
        end       
                
        function writeSectionTrailer(this)
            this.writer.writeCellLines({'/* Required S-function trailer */';
                '#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */';
                '#include "simulink.c" /* MEX-file interface mechanism */';
                '#include "fixedpoint.c"';
                '#else';
                '#error Assertion failed: file must be compiled as a MEX-file';
                '#endif'});
            this.writer.newLine;
            % note that fixedpoint.c is installed with Simulink
        end
        
        % returns C expressions for sample time and sample offset that can
        % be used with ssSetSampleTime and ssSetIOPortSampleTime, etc.
        function [sampleTimeCExpr, sampleOffsetCExpr] = getSampleTimeCExpression(this, rate)
            switch rate.TimingMode
                case 'ONESHOT'
                    sampleTimeCExpr = 'mxGetInf()';
                    sampleOffsetCExpr = '0';
                case 'INHERITED'
                    sampleTimeCExpr = 'INHERITED_SAMPLE_TIME';
                    sampleOffsetCExpr = '0';
                case 'PERIODIC'                    
                    sampleTimeCExpr = rtw.connectivity.CodeInfoUtils.double2str(rate.SamplePeriod);
                    sampleOffsetCExpr = rtw.connectivity.CodeInfoUtils.double2str(rate.SampleOffset);
                case {'INITIALIZE', 'TERMINATE', 'RESET', 'PARAMETERCHANGE', 'RESETWITHINIT'}
                    sampleTimeCExpr = 'mxGetInf()';
                    sampleOffsetCExpr = rtw.connectivity.CodeInfoUtils.double2str(rate.SampleOffset);
                case 'CONTINUOUS'
                    sampleTimeCExpr = '0.0';
                    sampleOffsetCExpr = '0.0';
                case 'ASYNCHRONOUS'
                    assert(this.codeInfoUtils.isAsyncAllowed, ...
                           'Asynchronous sample times are only supported for export functions and SIM Target.');
                    % Ask the codeInfoUtils how to write out ASYNC
                    [sampleTimeCExpr,sampleOffsetCExpr] = this.codeInfoUtils.getAsyncSampleTimeString;
                otherwise
                    assert(false, 'Unexpected TimingMode: %s', rate.TimingMode);
            end
        end
        
        function validateMethodCallForExportFunctions(this, methodName)
            % sub-classes can override this validation method
            assert(~this.codeInfoUtils.isExportFunctions, ...
                'Calling "%s" not allowed for export functions', methodName);
        end 
               
        % returns a "ssIsSampleHit" C expression that can be used to
        % schedule a sample rate.
        %
        % this function takes account of the sample time scheme being used
        % via this.usePortBasedSampleTimes.
        %
        function [sampleHitCExpr, sampleTimeIndexCExpr] = getRateSampleHitCExpr(this, timing)
            if this.usePortBasedTsIndexMatching()
               % port based sample times
               % find a port associated with the sample rate
               portType = 'Input';
               isInput = true;
               indices = this.codeInfoUtils.getInputPortsForRates(timing);
               if isempty(indices)
                  isInput = false;
                  portType = 'Output';
                  indices = this.codeInfoUtils.getOutputPortsForRates(timing);
               end
               % usePortBasedSampleTimes guarantees we will find at least
               % one port for each rate
               if this.IsConstant && isempty(indices)
                   sampleHitCExpr = '';
                   sampleTimeIndexCExpr = '';
                   return;
               else
                   assert(~isempty(indices), 'No input or output port found for rate');
               end
               
               if isInput && this.codeInfoUtils.isExportFunctions                    
                   % map back first index to expInports index, remove
                   % others
                   indices = this.codeInfoUtils.getExportFunctionsInportIndex(indices(1));                   
               end
               
               % use first index, 0-based
               index = int2str(indices(1) - 1);
               sampleTimeIndexCExpr = ['ssGet' portType 'PortSampleTimeIndex(S, ' index ')'];
               sampleHitCExpr = ['ssIsSampleHit(S, ' sampleTimeIndexCExpr ', tid)'];               
            else
               % hybrid or block based sample times
               %
               % directly use sample time index of rate since we set it up
               % in mdlInitializeSampleTimes               
               sampleTimeIndexCExpr = int2str(this.getSampleTimeIndex(timing));
               sampleHitCExpr = ['ssIsSampleHit(S, ' sampleTimeIndexCExpr ', tid)'];
            end            
        end  
        
        % determine whether the sample time scheme is port-based sample
        % times
        function usePortBasedSampleTimes = usePortBasedSampleTimes(this)            
            this.validateMethodCallForExportFunctions('usePortBasedSampleTimes');

            % getAllSampleTimesOnPorts determines if each sample time has an IO 
            % port associated with it.
            %
            % if so use purely port-based sample times (support for
            % inherited sample times)
            %
            % otherwise use either hybrid or block based sample times - see
            % useHybridSampleTimes
            usePortBasedSampleTimes = this.usePortBasedSampleTimesHook(this.AllSampleTimesOnPorts);
        end          
        
        % determine whether the sample time scheme is hybrid sample times
        function useHybridSampleTimes = useHybridSampleTimes(this)
            this.validateMethodCallForExportFunctions('useHybridSampleTimes');
            
           if this.usePortBasedSampleTimes
               % using port based sample times
               useHybridSampleTimes = false;
           else
               if this.codeInfoUtils.hasPorts
                   % some ports => use hyrbid port and block sample times                    
                   useHybridSampleTimes = true;
               else               
                   % no ports => use block based sample times
                   useHybridSampleTimes = false;
               end            
           end
        end

        function hasInitTermPorts = getHasInitTermPorts(this)
            inports = this.codeInfo.Inports;
            for portIdx = 1:length(inports)
                rate = inports(portIdx).Timing;
                if strcmp(rate.TimingMode,'UNION')
                    rate = rtw.connectivity.CodeInfoUtils.getArray(rate.UnionTimingInfo);
                end
                for rateIdx = 1:length(rate)
                    if (rate(rateIdx).SamplePeriod == Inf && ...
                        (rate(rateIdx).SampleOffset == 1 || rate(rateIdx).SampleOffset == 2))
                        hasInitTermPorts = true;
                        return;
                    end
                end
            end            
            
            outports = this.codeInfo.Outports;
            for portIdx = 1:length(outports)
                rate = outports(portIdx).Timing;
                if strcmp(rate.TimingMode,'UNION')
                    rate = rtw.connectivity.CodeInfoUtils.getArray(rate.UnionTimingInfo);
                end
                for rateIdx = 1:length(rate)
                    if (rate(rateIdx).SamplePeriod == Inf && ...
                        (rate(rateIdx).SampleOffset == 1 || rate(rateIdx).SampleOffset == 2))
                        hasInitTermPorts = true;
                        return;
                    end
                end
            end
            hasInitTermPorts = false;
        end

        % return the sample time index of the given timing object
        function sampleTimeIndex = getSampleTimeIndex(this, rate)
            sampleTimes = this.getSampleTimes;
            foundSampleTimeIndex = false;
            for idx = 1:length(sampleTimes)
                if rtw.connectivity.CodeInfoUtils.isEquivalentTo(...
                        sampleTimes(idx), rate)
                    foundSampleTimeIndex = true;
                    % return 0-based sampleTimeIndex
                    sampleTimeIndex = idx - 1;
                    break;
                end                
            end            
            assert(foundSampleTimeIndex, 'Failed to find a sample time index.');
        end   
        
        function numPWork = getNumPWork(this) %#ok<MANU>
            numPWork = 0;
        end
        
        function writeSymbolicDimsVarInitialization(this, symbDims, dimArrayVar)
            %Create list of array initializers for symDims, then
            %prune off unneeded ", " from final dimension.
            assert(iscell(symbDims));
            dimStr = '';
            for dimsIdx=1:length(symbDims)
                dimStr = sprintf('%s%s, ', dimStr, symbDims{dimsIdx});
            end
            dimStr = dimStr(1:end-2);
            nDims  = numel(symbDims);
            
            this.writer.wLine('int_T %s[ %d ] = { %s };', ...
                              dimArrayVar, nDims, dimStr);
        end
        
        function writeSectionEnableBody(this) %#ok<MANU>
            % template method to be overridden in subclass
        end
        
        function writeSectionDisableBody(this) %#ok<MANU>
            % template method to be overridden in subclass
        end
        
        function is_xrelblock_from_modelref = isXrelBlockFromModelRef(~)
            % always return false, this hook is overridden in
            % PILBlockSfunction
            is_xrelblock_from_modelref = false;
        end
        
        function is_xrelblock_reusable = isXrelBlockReusable(~)
            % always return false, this hook is overridden in
            % PILBlockSfunction
            is_xrelblock_reusable = false;
        end
        
        function is_xrelblock = isXrelBlock(~)
            % always return false, this hook is overridden in
            % PILBlockSfunction
            is_xrelblock = false;
        end
        
        function usePortBasedSampleTimes = usePortBasedSampleTimesHook(~,usePortBasedSampleTimes)
            %By default don't override port based sample time setting.
        end
        
        function usePortBasedTIDMatching = usePortBasedTsIndexMatching(this)
            % port based sample times, only used with Export functions
            % models for most cases
            usePortBasedTIDMatching = this.usePortBasedSampleTimes && ...
                this.codeInfoUtils.isExportFunctions;
        end
    end
    
    methods (Access = 'protected', Abstract)
        writeSectionInitializeSizesBody(this);        
        writeSectionExportFunctionsWrappers(this);
        % if this method returns true, mdlInitializeConditions is generated
        % and this.writeSectionInitializeConditionsBody is called
        isInitializeConditionsRequired = isInitializeConditionsRequired(this);        
        writeSectionUserFunctions(this);
        mexLibs = getMEXLibs(this);
    end
    
    methods (Access = 'private')
        function ret = getSampleTimes(this)
            ret = this.SampleTimes;
        end
    end
    
    methods (Access = 'private', Static)
        % get timing objects corresponding to the Simulink 
        % sample times associated with this component
        %
        % special care is taken to add the constant sample time only if
        % there are some constant outputs
        function sampleTimes = initializeSampleTimes(codeInfoUtils, hasResetFunction)
            % get periodic rates
            sampleTimes = codeInfoUtils.getRates('PERIODIC');
            % get aperiodic rates
            sampleTimes = [sampleTimes, codeInfoUtils.getRates('APERIODIC')];
            % get inherited rates
            sampleTimes = [sampleTimes, codeInfoUtils.getRates('INHERITED')];            
            % get const rate if applicable
            %
            % Note: we do not need to explicitly register internal constant
            % sample times associated with signal logging - these do not 
            % impact s-function registration; they are logged in mdlStart.
            if codeInfoUtils.hasConstOutput
               sampleTimes = [sampleTimes, codeInfoUtils.getRates('ONESHOT')]; 
            end
            % Add ASYNCHRONOUS sample times since we expect those for
            % export functions
            if codeInfoUtils.isExportFunctions
                sampleTimes = [sampleTimes, codeInfoUtils.getRates('ASYNCHRONOUS')]; 
            end
            % Add I, R and T rates
            sampleTimes = [sampleTimes, codeInfoUtils.getRates('INITIALIZE'), ...
                codeInfoUtils.getRates('TERMINATE')];
            
            if hasResetFunction
                sampleTimes = [sampleTimes, ...
                    codeInfoUtils.getRates('RESET'), ...
                    codeInfoUtils.getRates('RESETWITHINIT')];
            end
        end

        % Determine if each sample time we need to register is associated
        % with an Input or Output port.    Note that there may not be "output
        % tasks" associated with each sample time - for example, a component
        % with singletasking scheduling may have internal rates captured in
        % sample times but all associated with a single output function.
        function allSampleTimesOnPorts = initializeAllSampleTimesOnPorts( ...
                codeInfo, codeInfoUtils, ...
                sampleTimes, ...
                isConstant, ...
                hasPortlessSimulinkFunctionPortGroups, ...
                hasInternalModelWideEventTs, ...
                hasContinuousFundamentalSampleTime, ...
                lExposesITPorts)
            
            if hasPortlessSimulinkFunctionPortGroups
                allSampleTimesOnPorts = false;
                return;
            end

            if hasInternalModelWideEventTs
                allSampleTimesOnPorts = false;
                return;
            end
            
            allSampleTimesOnPorts = true;            
            
            % Purely constant models always set this to true these days,
            % unless there are no ports, in which case the sample times
            % can't be on ports.
            if isConstant
                if numel(codeInfo.Inports) + numel(codeInfo.Outports) == 0
                    allSampleTimesOnPorts = false;
                else
                    allSampleTimesOnPorts = true;
                end
                return;
            end
                        
            if hasContinuousFundamentalSampleTime
                timingModes = rtw.connectivity.CodeInfoUtils.getCharTimingModeNames({sampleTimes.TimingMode});
                if ~ismember('CONTINUOUS', timingModes)
                    % If the fundamental sample time is continuous, but we
                    % don't see any continuous sample times in CodeInfo, 
                    % then evidently not all sample times are on ports.
                    allSampleTimesOnPorts = false;
                end
            end
            
            ports = [codeInfo.Inports, codeInfo.Outports];
            for i=1:length(sampleTimes)
                % check to see if each sampleTime has at least 1 
                % associated IO port
                sampleTimeOnPort = false;
                sampleTime = sampleTimes(i);

                indices = codeInfoUtils.getPortsForTimingProperties(sampleTime, ports);
                
                allFoundPortsHaveUnionRateWithoutTask = true; %assume
                for idx=1:length(indices)
                    port = ports(indices(idx));
                    portTiming = port.Timing;
                    if ~strcmp(portTiming.TimingMode, 'UNION') || ...
                            codeInfoUtils.unionRateHasTask(portTiming)
                        % Only check that Union sample time is on a port for
                        % export function models or when union sample time
                        % has a task in non-export function models
                        allFoundPortsHaveUnionRateWithoutTask = false;
                        break;
                    end
                end
                    
                if ~isempty(indices) && ~allFoundPortsHaveUnionRateWithoutTask
                    sampleTimeOnPort = true;                    
                end
                
                if ~sampleTimeOnPort
                    if codeInfoUtils.isExportFunctions
                        switch sampleTime.TimingMode
                            case 'ASYNCHRONOUS'
                                % for export functions, we know that any asynchronous 
                                % sample time corresponds to a port (except one 
                                % special case below)
                                numPorts = codeInfoUtils.getNumExportFunctions + ...
                                    length(codeInfo.Inports) +...
                                    length(codeInfo.Outports);
                                if (numPorts == 0)
                                    % with client/server, it is possible
                                    % that a model can contain a number of
                                    % Simulink functions with no I/O ports.
                                    % In this case, we don't want to use
                                    % PORT_BASED_SAMPLE_TIMES.
                                    sampleTimeOnPort = false; 
                                else
                                    sampleTimeOnPort = true; 
                                end

                            case 'PERIODIC'
                                if codeInfoUtils.isExportFcnDiagram && ...
                                        any(strcmp(sampleTime.TaskingMode, {'IMPLICIT_TASKING' ...
                                                                            'EXPLICIT_TASKING'}))
                                    % ExportFcnDiagram: 
                                    % ignore implicit periodic sample times
                                    % and explicit periodic sample times 
                                    % that aren't on a root level data 
                                    % port (g1385346)
                                    sampleTimeOnPort = true;
                                end 
                                % For non-export fcn diagram, e.g. 
                                % AsyncFcnCallModelTopLevelBuild, when
                                % an empty periodic task is optimized away,
                                % ignoring the periodic rate can lead to a 
                                % mismatch between the
                                % number of rates the s-function
                                % would register (1:inherited) and the
                                % number of TIDs recorded in the .rtw
                                % file (2: base rate & inherited).
                                % This triggers an assertion in
                                % st_config.cpp BuildTsMapForBlock
                                % because "getLocalTid" gives an out of
                                % range value (note: that code is only 
                                % active for the non-export fcn diagram case).
                                % Additionally, we didn't hit this
                                % in the model reference with async task specification 
                                % case because in that case the periodic 
                                % task is not optimized away!
                            case 'INHERITED'
                                % For export functions, allow inherited 
                                % rates for DataStores. See
                                % validateTimingProperties@ModelRefCodeInfoUtils
                                % for more info.
                                if ~isempty(codeInfoUtils.getDataStoresForRates(sampleTime))
                                    sampleTimeOnPort = true;
                                end
                        end
                    else % not exporting functions
                        if strcmp(sampleTime.TimingMode, 'INHERITED')
                            % CodeInfo will mark data stores that are
                            % read and written at different rates as
                            % "INHERITED".
                            %
                            % Make sure that each data store with inherited
                            % rate has all its reader and writer sample times
                            % on ports before accepting the inherited rate for
                            % port based sample times.   See
                            % test/toolbox/simulink/modelref/fcncall/tfcncallmdlref15
                            % lvlTwo_g865425 for an example that has multirate
                            % datastores with sample times not available on I/O
                            % ports - this cannot use port based sample times.                            
                            %
                            % Note that we process both global and non-global
                            % data stores.
                            dataStoresInherited = ...
                                codeInfo.DataStores(codeInfoUtils.getDataStoresForRates(sampleTime));
                            if ~isempty(dataStoresInherited)
                                readersAndWritersTiming = [];
                                for dsIdx = 1:length(dataStoresInherited)
                                    ds = dataStoresInherited(dsIdx);
                                    readersAndWritersTiming =...
                                        [readersAndWritersTiming, ...
                                        codeInfoUtils.getArray(ds.DataReads), ...
                                        codeInfoUtils.getArray(ds.DataWrites)]; %#ok<AGROW>
                                end
                                readersAndWritersTiming = unique(readersAndWritersTiming);
                                
                                % Find out if all the reader and writer
                                % rates are used on the ports
                                dsTimingHitVector = false(1, length(readersAndWritersTiming));
                                for rwIdx = 1:length(readersAndWritersTiming)
                                    %skip "ONESHOT" Data Store read/write
                                    %timing properties as all Data Stores
                                    %have ONESHOT added
                                    if strcmp(readersAndWritersTiming(rwIdx).TimingMode,'ONESHOT')
                                        dsTimingHitVector(rwIdx) = true;
                                    else
                                        indices = codeInfoUtils.getPortsForTimingProperties(...
                                            readersAndWritersTiming(rwIdx), ...
                                            [codeInfo.Inports, ...
                                            codeInfo.Outports]);
                                        dsTimingHitVector(rwIdx) = ~isempty(indices);
                                    end
                                end
                                sampleTimeOnPort = all(dsTimingHitVector);
                            end
                        elseif ~lExposesITPorts && ...
                                ismember(sampleTime.TimingMode, {'INITIALIZE','TERMINATE','RESET'})
                            sampleTimeOnPort = true;
                        end
                    end                                                                                           
                end
                
                if ~sampleTimeOnPort
                    allSampleTimesOnPorts = false;                    
                    return;
                end
            end
        end
    end
end

% LocalWords:  Nonfinite PARAMETERCHANGE
