classdef ExecutionTimeProbeTarget < coder.profile.TargetInterface
%

% Copyright 2013-2020 The MathWorks, Inc.

    properties (Constant, Access=protected)
        TaskProbe   = 0;
        ExecProbe   = 1;
        FilterProbe = 2;
    end

    properties (Access = private)
        IsXpcMode;
        IsExtReadStoreManagement; % this property states if timer read and
                                  % storage in buffer are done externally
        ProfilingStartFnc;
        ProfilingEndFnc;
        ProbeType;

        IsOverheadFilterEnabled;
        
        XCPEventID;
        
        % if true, samples are uploaded immediately. They are stored on target
        % until the end otherwise
        RealTimeUpload;
    end
    
    methods (Access = protected)
        function isXpc = getIsXpcMode(this)
            if isempty(this.IsXpcMode)
                this.IsXpcMode = false;
                if ~isempty(this.ProfilingTimer)
                    this.IsXpcMode =...
                        this.ProfilingTimer.getIsXpcMode;
                end
            end
            
            isXpc = this.IsXpcMode;
        end
        
        function [timer_expr_prefix, timer_expr_suffix] = getTimerDirection (this, writer)
            if strcmp(this.ProfilingTimer.getCountDirection,'up')
                writer.wComment('Using a timer that increments on each tick');
                timer_expr_prefix = '';
                timer_expr_suffix = '';
            else
                writer.wComment('Using a timer that decrements on each tick');
                timer_expr_prefix = ' ~(';
                timer_expr_suffix = ')';
            end
        end
        
        function isExtReadStoreManagement = getIsExtReadStoreManagement(this)
            if isempty(this.IsExtReadStoreManagement)
                this.IsExtReadStoreManagement = getIsXpcMode(this) || ...
                    ~isempty(this.GlobalRegistry.AtomicReadStoreFcnName);
            end
            isExtReadStoreManagement = this.IsExtReadStoreManagement;
        end
        
        function targetWriteOverheadSetting(this, writer)
            lTimerType = this.ProfilingTimer.getTimerCType;
            writer.wBlockStart('void xilProfilingOverheadSetting(%s tskVal, %s fcnVal, %s frzVal)', ...
                lTimerType, lTimerType, lTimerType);
            writer.writeLine('xsd_xil_timer_overhead_task = tskVal;');
            writer.writeLine('xsd_xil_timer_overhead_function = fcnVal;');
            writer.writeLine('xsd_xil_timer_overhead_freezing = frzVal;');
            writer.writeLine('xsd_xil_timer_overhead_total = 0;');
            writer.wBlockEnd;
        end

        function idContainerCType = getSectionIdContainerCType(this)
            % If the timer type is larger than the section id type, we put 
            % the section id for on-target metrics in a container.
            % This allows the uploaded data to be located in statically 
            % allocated contiguous memory without having to handle 
            % alignment issues for the timer data
                    
            idContainerCType =  this.IdentifierCDataType;
            if ~isempty(this.ProfilingTimer)
                timerWordLength = this.ProfilingTimer.getTimerWordLength;
                lIdentifierTypeSize = ...
                    coder.profile.ExecTimeConfig.getIdTypeSize...
                    (this.TargetWordSize);
                if timerWordLength > lIdentifierTypeSize
                    idContainerCType = sprintf('uint%d_T', timerWordLength);
                end
            end
        end        

        
        function targetWriteSectionLocalFcnPrototypes(this, writer, executed)
            if ~this.GlobalRegistry.InlineGeneratedCode
                lIdType = this.IdentifierCDataType;
                
                writer.wComment('Called before starting a profiled section of code');
                writer.writeLine('void %s(%s);', this.ProfilingStartFnc, lIdType);
                writer.newLine;
                
                writer.wComment('Called on finishing a profiled section of code');
                writer.writeLine('void %s(%s);', this.ProfilingEndFnc, lIdType);
                writer.newLine;
                % when the code is inlined, those methods aren't exposed in
                % the header file
            end
            
            if ~executed && ~this.GlobalRegistry.RequireXCPSupport
                if ~isempty(this.ProfilingTimer)
                    writer.wComment('Uploads data');
                    if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                        writer.writeLine('void xilUploadProfilingData(%s sectionId, %s timerVal);',...
                                         this.IdentifierCDataType,...
                                         this.ProfilingTimer.getTimerCType);
                    else
                        writer.writeLine('void xilUploadProfilingData(%s sectionId);',...
                                         this.IdentifierCDataType);
                    end
                    writer.newLine;
                end
                writer.newLine;
                % for XCP targets, the upload method is called directly
                % instead of using xilUploadProfilingData as intermediary
            end
            
        end
        
        function targetWriteSectionStorage(this, writer)
            writer.newLine;
                unusedParamMacro = {
                    '/* A function parameter may be intentionally unused */'
                    '#ifndef UNUSED_PARAMETER  '
                    '# if defined(__LCC__)  '
                    '#   define UNUSED_PARAMETER(x)'
                    '# else  '
                    '#   define UNUSED_PARAMETER(x) (void) (x)  '
                    '# endif  '
                    '#endif  '
                                   };
                writer.writeCellLines(unusedParamMacro);
                writer.newLine;

            if isempty(this.ProfilingTimer)
                return;
            end
                               
            lTimerType = this.ProfilingTimer.getTimerCType;
                        
            if ~this.getIsExtReadStoreManagement
                lTimerType = this.ProfilingTimer.getTimerCType;
                lIdType = this.IdentifierCDataType;
                lSectionIdContainerCType = this.getSectionIdContainerCType;
                if ~strcmp(lSectionIdContainerCType, lIdType)
                    writer.wLine('#define SIZEOF_SECTION_ID_CONTAINER sizeof(%s)', ...
                                 lSectionIdContainerCType);
                end
                writer.wLine('#define SIZEOF_TIMER_TYPE sizeof(%s)', lTimerType);
            end            
            
            if this.SingleThreadTiming
                writer.writeLine('static %s xsd_xil_timer_corrected = 0;', ...
                    lTimerType);
                writer.newLine;
                
                writer.writeLine('static %s xsd_xil_timer_unfreeze = 0;', ...
                    lTimerType);
                writer.newLine;

                writer.writeLine('static %s xsd_xil_freezing_busy = 0;', ...
                                 this.IdentifierCDataType);
                writer.newLine;
                
                if this.IsOverheadFilterEnabled
                    writer.wComment('Variables to store the estimated overhead of the instrumentation API');
                    writer.writeLine('static %s xsd_xil_timer_overhead_freezing = 0;', ...
                        lTimerType);
                    writer.writeLine('static %s xsd_xil_timer_overhead_task = 0;', ...
                        lTimerType);
                    writer.writeLine('static %s xsd_xil_timer_overhead_function = 0;', ...
                        lTimerType);
                    writer.wComment('Variable to account the total generated overhead since last freezing');
                    writer.writeLine('static %s xsd_xil_timer_overhead_total = 0;', ...
                        lTimerType);
                    writer.newLine;
                end
                
                writer.newLine
            end

            writer.newLine;
        end
        
        function writeSectionUploadProfilingData(this, writer, targetCollectDataFcnName)
            lIdType = this.IdentifierCDataType;
            
            sectionIdVar = 'sectionId';
            timerVar = 'xsd_xil_timer_corrected';
            if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                timerVar = 'timerVal';
                fcnSignature = sprintf('void xilUploadProfilingData(%s %s, %s %s)',...
                                       lIdType,...
                                       sectionIdVar,...
                                       this.ProfilingTimer.getTimerCType,...
                                       timerVar);
            else
                fcnSignature = sprintf('void xilUploadProfilingData(%s %s)', ...
                                       lIdType,...
                                       sectionIdVar);
            end
            
            writer.wBlockStart('%s', fcnSignature);
            
            if this.getIsExtReadStoreManagement
                % this code is executed if AtomicReadStoreFcnName is not empty
                % or XpcMode is active
                if ~this.RealTimeUpload
                    writer.wComment('No implementation required for this method');
                    writer.writeLine('UNUSED_PARAMETER(%s);', sectionIdVar);
                elseif this.getIsXpcMode
                    timerFunc =  this.ProfilingTimer.getReadTimerExpression;
                    % We need to insert the sectionId argument
                    timerFunc = regexprep(timerFunc, '\(\)', ['\(' sectionIdVar '\)']);
                    writer.writeLine('%s;', timerFunc);
                elseif ~isempty(this.GlobalRegistry.AtomicReadStoreFcnName)
                    % based on how this.getIsExtReadStoreManagement is computed, this
                    % code is executed if AtomicReadStoreFcnName is not empty
                    timerFunc = this.GlobalRegistry.AtomicReadStoreFcnName;
                    writer.writeLine('%s(%s);', timerFunc, sectionIdVar);
                else
                    assert(false, 'No possible to generate external management of timer read/store');
                end
            else
                lCodeInstrSizeCDataType = ...
                    coder.profile.ExecTimeConfig.getCodeInstrNumElsCDataType...
                    (this.TargetWordSize);
                writer.writeLine...
                    ('%s((void *)(&%s), (%s)(SIZEOF_TIMER_TYPE), %s);', ...
                     targetCollectDataFcnName, ...
                     timerVar,...
                     lCodeInstrSizeCDataType, ...
                     sectionIdVar);
            end
                        
            writer.wBlockEnd();
            writer.newLine;

        end
        
        function writeSectionProfilingStartEnd(this, isStart, lProbeType, writer, fcnNameStart, fcnNameEnd)
            
            lIdType = this.IdentifierCDataType;
                
            if isStart
                fcnName = fcnNameStart;
            else 
                fcnName = fcnNameEnd;
            end
                
            inlineKeyword = '';
            if this.GlobalRegistry.InlineGeneratedCode
                inlineKeyword = 'static inline';
            end
            
            fcnSignature = sprintf('%s void %s(%s sectionId)', ...
                                   inlineKeyword, fcnName, lIdType);
            
            if isempty(this.ProfilingTimer)
                writer.writeLine('%s {UNUSED_PARAMETER(sectionId);}', fcnSignature);
                writer.newLine;
                return;
            end
            
            writer.wBlockStart('%s', fcnSignature);
            switch (lProbeType)
                case this.TaskProbe
                    this.writeSectionTaskProbeBodyStartEnd(writer, isStart);
                case this.ExecProbe
                    this.writeSectionExecProbeBodyStartEnd(writer, isStart);
                case this.FilterProbe
                    this.writeSectionFilterProbeBodyStartEnd(writer, isStart);
            end
            writer.wBlockEnd();
            writer.newLine;
        end
        
        function writeSectionTaskProbeBodyStartEnd (this, writer, isStart)
            lTimerType = this.ProfilingTimer.getTimerCType;
            lIdType = this.IdentifierCDataType;
            
            if ~isStart && this.RealTimeUpload
                writer.writeLine('%s sectionIdNeg = ~sectionId;', lIdType);
                writer.newLine;
            end
            
            if ~this.getIsExtReadStoreManagement
                if this.SingleThreadTiming
                    timerValueExpr = 'xsd_xil_timer_corrected';
                else
                    timerValueExpr = 'timer_value';
                    writer.wLine('%s %s;', lTimerType, timerValueExpr);
                    writer.newLine;
                end
            end
                        
            if ~isStart
                if this.IsOverheadFilterEnabled
                    writer.wComment('Add task-level overhead to total overhead');
                    writer.writeLine('xsd_xil_timer_overhead_total = xsd_xil_timer_overhead_total + xsd_xil_timer_overhead_task;');
                    writer.newLine;
                end
                if ~this.GlobalRegistry.RequireXCPSupport
                    writer.writeLine('xilProfilingTimerFreezeInternal();');
                    writer.newLine;
                end
            end

            if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                timerFunc =  this.ProfilingTimer.getReadTimerExpression;
                [timer_expr_prefix, timer_expr_suffix] = this.getTimerDirection(writer);
                writer.newLine;
                writer.writeLine('%s = %s(%s) %s%s;', ...
                                 timerValueExpr,...
                                 timer_expr_prefix, ...
                                 lTimerType, timerFunc, timer_expr_suffix);
            end

            if ~this.RealTimeUpload
                if isStart
                    writer.writeLine('%s(sectionId);', ...
                        coder.profile.CaptureProbeTarget.StartInternalMethod);
                else
                    writer.writeLine('%s(sectionId);', ...
                        coder.profile.CaptureProbeTarget.EndInternalMethod);
                end
            else
                if isStart
                    sectionIdVar = 'sectionId';
                else
                    sectionIdVar = 'sectionIdNeg';
                end
                writer.wComment('Send execution profiling data to host');
                if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                    writer.writeLine('xilUploadProfilingData(%s, %s);', sectionIdVar, timerValueExpr);
                elseif this.GlobalRegistry.RequireXCPSupport
                    timerFunc = this.GlobalRegistry.AtomicReadStoreFcnName;
                    if ~this.GlobalRegistry.SplitAtomicReadStoreFcn
                        writer.writeLine('%s(%s);', timerFunc, sectionIdVar);
                    elseif isStart
                        writer.writeLine('%sStart(%s);', timerFunc, sectionIdVar);
                    else
                        writer.writeLine('%sEnd(%s);', timerFunc, sectionIdVar);
                    end
                else
                    writer.writeLine('xilUploadProfilingData(%s);', sectionIdVar);
                end
            end
            writer.newLine;
            
            if isStart && ~this.GlobalRegistry.RequireXCPSupport
                writer.newLine;
                writer.writeLine('xilProfilingTimerUnFreezeInternal();');
                writer.newLine;
            end
        end
        
        function writeSectionExecProbeBodyStartEnd (this, writer, isStart)
            if ~isStart && this.RealTimeUpload
                lIdType = this.IdentifierCDataType;
                writer.newLine;
                writer.writeLine('%s sectionIdNeg = ~sectionId;', lIdType);
            end
            
            if ~this.getIsExtReadStoreManagement
                if this.SingleThreadTiming
                    timerValueExpr = 'xsd_xil_timer_corrected';
                else
                    timerValueExpr = 'timer_value';
                    lTimerType = this.ProfilingTimer.getTimerCType;
                    writer.wLine('%s %s;', lTimerType, timerValueExpr);
                end
            end
            
            if this.IsOverheadFilterEnabled && ~isStart
                writer.wComment('Add function-level overhead to total overhead');
                writer.writeLine('xsd_xil_timer_overhead_total = xsd_xil_timer_overhead_total + xsd_xil_timer_overhead_function;');
                writer.newLine;
            end
            
            writer.newLine;
            if ~this.GlobalRegistry.RequireXCPSupport
                writer.writeLine('xilProfilingTimerFreezeInternal();');
                writer.newLine;
            end
            
            if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                timerFunc =  this.ProfilingTimer.getReadTimerExpression;
                [timer_expr_prefix, timer_expr_suffix] = this.getTimerDirection(writer);
                writer.newLine;
                writer.writeLine('%s = %s(%s) %s%s;', ...
                    timerValueExpr,...
                    timer_expr_prefix, ...
                    lTimerType, timerFunc, timer_expr_suffix);
            end
            
            if isStart || ~this.RealTimeUpload
                sectionIdVar = 'sectionId';
            else
                sectionIdVar = 'sectionIdNeg';
            end

            if ~this.RealTimeUpload
                if isStart
                    writer.writeLine('%s(%s);', ...
                        coder.profile.CaptureProbeTarget.StartInternalMethod, ...
                        sectionIdVar);
                else
                    writer.writeLine('%s(%s);', ...
                        coder.profile.CaptureProbeTarget.EndInternalMethod, ...
                        sectionIdVar);
                end
            else
                writer.wComment('Send execution profiling data to host');
                if ~this.getIsExtReadStoreManagement && ~this.SingleThreadTiming
                    writer.writeLine('xilUploadProfilingData(%s, %s);', sectionIdVar, timerValueExpr);
                elseif this.GlobalRegistry.RequireXCPSupport
                    timerFunc = this.GlobalRegistry.AtomicReadStoreFcnName;
                    if ~this.GlobalRegistry.SplitAtomicReadStoreFcn
                        writer.writeLine('%s(%s);', timerFunc, sectionIdVar);
                    elseif isStart
                        writer.writeLine('%sStart(%s);', timerFunc, sectionIdVar);
                    else
                        writer.writeLine('%sEnd(%s);', timerFunc, sectionIdVar);
                    end
                else
                    writer.writeLine('xilUploadProfilingData(%s);', sectionIdVar);
                end
            end
            writer.newLine;
            
            if ~this.GlobalRegistry.RequireXCPSupport
                writer.writeLine('xilProfilingTimerUnFreezeInternal();');
                writer.newLine;
            end
        end
        
        function writeSectionFilterProbeBodyStartEnd (this, writer, isStart)
            writer.writeLine('UNUSED_PARAMETER(sectionId);');
            if ~this.GlobalRegistry.RequireXCPSupport
                if isStart
                    writer.writeLine('xilProfilingTimerFreezeInternal();');
                    if this.SingleThreadTiming
                        writer.writeLine('xsd_xil_freezing_busy = 1;');
                    end
                else
                    if this.SingleThreadTiming
                        writer.writeLine('xsd_xil_freezing_busy = 0;');
                    end
                    if this.IsOverheadFilterEnabled
                        writer.wComment('Add freezing overhead to total overhead');
                        writer.writeLine('xsd_xil_timer_overhead_total = xsd_xil_timer_overhead_total + xsd_xil_timer_overhead_freezing;');
                    end
                    writer.writeLine('xilProfilingTimerUnFreezeInternal();');
                end
                writer.newLine;
            end
        end
        
        function writeSectionProfilingTimerFreezeFcns(this, writer)
            
            if this.SingleThreadTiming
                
                type = this.ProfilingTimer.getTimerCType;
                
                timerFunc =  this.ProfilingTimer.getReadTimerExpression;
                
                writer.wComment(['The internal freeze and unfreeze methods '...
                                 'cannot be nested. ' ...
                                 'The customer-visible implementation avoids nesting problems']);
                
                % freeze method: update timer_freeze and timer_corrected
                writer.wBlockStart('void xilProfilingTimerFreezeInternal(void)');
                
                if this.IsOverheadFilterEnabled
                    writer.writeLine('%s xsd_xil_timer_corrected_previous = xsd_xil_timer_corrected;', type);
                    writer.newLine;
                end
                
                timer_read_expr = sprintf('((%s)(%s))', type, timerFunc);
                countDirection = this.ProfilingTimer.getCountDirection;
                writer.writeLine('/* Update the value of the corrected timer to exclude time spent in the');
                writer.writeLine(' * instrumentation code.');
                writer.writeLine(' *');
                if strcmp(countDirection,'up')
                    writer.writeLine(' * Using a timer that increments on each tick.'); 
                    timer_expr_prefix = '+ (';
                    timer_expr_suffix = ')';
                else
                    writer.writeLine(' * Using a timer that decrements on each tick.'); 
                    timer_expr_prefix = '- (';
                    timer_expr_suffix = ')';
                end
                writer.writeLine(' */');
                writer.newLine;
                writer.writeLine(['xsd_xil_timer_corrected = xsd_xil_timer_corrected ' ...);
                                  timer_expr_prefix ...
                                  timer_read_expr ' - xsd_xil_timer_unfreeze' ...
                                  timer_expr_suffix ';']);
                
                if this.IsOverheadFilterEnabled
                    writer.wComment('The uploaded sample must be equal to or greater than the previous one');
                    writer.wBlockStart('if ((xsd_xil_timer_corrected - xsd_xil_timer_corrected_previous) > xsd_xil_timer_overhead_total)');
                    writer.writeLine('xsd_xil_timer_corrected = xsd_xil_timer_corrected - xsd_xil_timer_overhead_total;');
                    writer.wBlockMiddle('else');
                    writer.writeLine('xsd_xil_timer_corrected = xsd_xil_timer_corrected_previous;');
                    writer.wBlockEnd();
                    writer.wComment('Reset overhead counter');
                    writer.writeLine('xsd_xil_timer_overhead_total = 0;');
                end
                
                writer.wBlockEnd();
                writer.newLine;

                % unfreeze function
                writer.wBlockStart('void xilProfilingTimerUnFreezeInternal(void)');
                writer.writeLine(...
                    ['xsd_xil_timer_unfreeze = ( ' type ' ) ('  timerFunc ');']);
                writer.wBlockEnd();
                writer.newLine;
                
                writer.wBlockStart('void xilProfilingTimerFreeze(void)');
                writer.wBlockStart('if (xsd_xil_freezing_busy == 0)');
                writer.writeLine('xilProfilingTimerFreezeInternal();');
                writer.wBlockEnd();
                writer.wBlockEnd;
                writer.newLine;

                writer.wBlockStart('void xilProfilingTimerUnFreeze(void)');
                writer.wBlockStart('if (xsd_xil_freezing_busy == 0)');
                writer.writeLine('xilProfilingTimerUnFreezeInternal();');
                writer.wBlockEnd();
                writer.wBlockEnd;
                writer.newLine;
                
            else
                if ~isempty(this.ProfilingTimer)
                    realTimeComment = [...
                        'For real-time, multitasking case this function is '...
                        'stubbed out.'];
                    writer.wComment(realTimeComment);
                end                
                writer.writeLine('void xilProfilingTimerFreezeInternal(void) {}');
                writer.writeLine('void xilProfilingTimerFreeze(void) {}');
                writer.newLine;

                if ~isempty(this.ProfilingTimer)
                    writer.wComment(realTimeComment);
                end
                writer.writeLine('void xilProfilingTimerUnFreezeInternal(void) {}');
                writer.writeLine('void xilProfilingTimerUnFreeze(void) {}');
                writer.newLine;
            end
        end

        function targetWriteSectionFcnsFinal(this, writer, targetCollectDataFcnName, executed)
            if ~executed && ~this.GlobalRegistry.RequireXCPSupport
                if ~isempty(this.ProfilingTimer)
                    this.writeSectionUploadProfilingData(writer, targetCollectDataFcnName);
                end
            
                this.writeSectionProfilingTimerFreezeFcns(writer);
            end
            
            % profiling function for start of regular section
            isStart = true;
            this.writeSectionProfilingStartEnd(isStart, this.ProbeType, writer, ...
                                  this.ProfilingStartFnc, this.ProfilingEndFnc);
            
            % profiling function for end of regular section
            isStart = false;
            this.writeSectionProfilingStartEnd(isStart, this.ProbeType, writer, ...
                                  this.ProfilingStartFnc, this.ProfilingEndFnc);
        end
    end
    
    methods (Access = public, Static)
        function obj = getTaskProbeTarget (lStartFnc, lEndFnc)
            obj = coder.profile.ExecutionTimeProbeTarget(lStartFnc, lEndFnc, ...
                        coder.profile.ExecutionTimeProbeTarget.TaskProbe);
        end

        function obj = getFilterProbeTarget (lStartFnc, lEndFnc)
            obj = coder.profile.ExecutionTimeProbeTarget(lStartFnc, lEndFnc, ...
                        coder.profile.ExecutionTimeProbeTarget.FilterProbe);
        end

        function obj = getExecutionProbeTarget (lStartFnc, lEndFnc)
            obj = coder.profile.ExecutionTimeProbeTarget(lStartFnc, lEndFnc, ...
                        coder.profile.ExecutionTimeProbeTarget.ExecProbe);
        end
    end
   
    methods (Access = public)
        function this = ExecutionTimeProbeTarget(lStartFnc, lEndFnc, lProbeType)
            this.ProfilingStartFnc = lStartFnc;
            this.ProfilingEndFnc = lEndFnc;
            this.ProbeType = lProbeType;
        end
        
        function targetWriteSectionExportedAtomicFcn(this, writer, collect)
            if ~isempty(collect)
                writer.wComment('Atomic function to read timer and upload data point');
                if this.GlobalRegistry.RequireXCPSupport
                    if ~this.GlobalRegistry.SplitAtomicReadStoreFcn
                        writer.writeLine('void %s(%s sectionId);', collect, this.IdentifierCDataType);
                    else
                        writer.writeLine('void %sStart(%s sectionId);', collect, this.IdentifierCDataType);
                        writer.writeLine('void %sEnd(%s sectionId);', collect, this.IdentifierCDataType);
                    end
                else
                    writer.writeLine('void %s(%s sectionId);', collect, this.IdentifierCDataType);
                end
                writer.newLine;
            end
        end
        
        function init (this, lTargetWordSize, lSingleThreadTiming, lProfilingTimer, lGlobalRegistry)
            init@coder.profile.TargetInterface(this, lTargetWordSize, ...
                lSingleThreadTiming, lProfilingTimer, lGlobalRegistry);
            this.IsOverheadFilterEnabled = this.GlobalRegistry.FilterProfilingOverhead;
            this.RealTimeUpload = this.GlobalRegistry.UploadDataInRealTime;
            if this.IsOverheadFilterEnabled
                assert(this.SingleThreadTiming, ...
                    'The overhead is estimated only on Single-Thread models');
            end
        end
        
        % implementation of abstract methods
        function targetWriteProbeExportedFcnPrototypes(this, writer, executed)
            this.targetWriteSectionLocalFcnPrototypes(writer, executed);
            writer.newLine;
            
            if ~executed && ~this.GlobalRegistry.RequireXCPSupport
                writer.wComment('Pause the timer while running code associated with storing and uploading the data.')
                writer.writeLine('void xilProfilingTimerFreeze(void);');
                writer.newLine;
                
                writer.wComment('Restart the timer after a pause')
                writer.writeLine('void xilProfilingTimerUnFreeze(void);');
                writer.newLine;
                
                if this.IsOverheadFilterEnabled && ~this.GlobalRegistry.RunOverheadBenchmark
                    lTimerType = this.ProfilingTimer.getTimerCType;
                    writer.writeLine('void xilProfilingOverheadSetting(%s, %s, %s);', ...
                        lTimerType, lTimerType, lTimerType);
                end
            end
        end
        
        function targetWriteSectionFcns(this, writer, targetCollectDataFcnName, executed)
            if ~executed
                this.targetWriteSectionStorage(writer);

                if this.IsOverheadFilterEnabled && ~this.GlobalRegistry.RunOverheadBenchmark
                    this.targetWriteOverheadSetting(writer);
                end
            end
            this.targetWriteSectionFcnsFinal(writer, targetCollectDataFcnName, executed);
        end
        
        function targetWriteInit(~, ~) 
        end
        
        function targetWriteXCPInitAndValidate(this, eventID)
            this.XCPEventID = eventID;
            assert(~isempty(this.GlobalRegistry.AtomicReadStoreFcnName), ...
                'Profiling via XCP requires that AtomicReadStoreFcnName in ProbeGlobalRegistry is set');
        end
        
        function targetWriteXCPManagementSource(this, writer, cpuIDFcn, threadIDFcn, addHeaders)
            if ~this.RealTimeUpload
                return;
            end
           
            lNumSamples = this.GlobalRegistry.XCPBufferConfig.NumSamples;
            lBufferSize = this.GlobalRegistry.XCPBufferConfig.BufferSizeInBytes;
            lSampleSizeInBytes = this.GlobalRegistry.XCPBufferConfig.SampleSizeInBytes;
            lBufferName = this.GlobalRegistry.XCPBufferConfig.BufferName;

            writer.wLine('#include "rtwtypes.h"');            
            writer.wLine('#include "xcp.h"');
            for i=1:length(addHeaders)
                [~, fname, fext] = fileparts(addHeaders{i});
                writer.wLine('#include "%s%s"', fname, fext);
            end
            writer.wLine('#include "%s"', this.ProfilingTimer.getTimerHeader);
            writer.newLine;

            % define area
            writer.wLine('#define XCP_PROFILING_EVENT %d', this.XCPEventID);
            writer.wLine('#define XCP_PROFILING_NUM_SAMPLES %d', lNumSamples);
            writer.wLine('#define XCP_PROFILING_BUFFER_SIZE %d', lBufferSize);
            writer.wLine('#define XCP_PROFILING_SAMPLE_SIZE %d', lSampleSizeInBytes);
            writer.newLine;
            
            % global variables
            writer.wLine('static XcpErrorCode profilingLastErrorCode = XCP_SUCCESS;');
            writer.wLine('uint8_T %s[XCP_PROFILING_BUFFER_SIZE];', lBufferName);
            writer.wLine('static %s profilingSampleIndex = 0;', this.IdentifierCDataType);
            writer.wLine('static %s profilingBufferIndex = 0;', this.IdentifierCDataType);
            if ~this.GlobalRegistry.SingleTaskModel
                % define a mutex in case of multi-tasking model
                writer.wLine('XCP_MUTEX_DEFINE(profiling_mutex);');
                writer.wLine('static uint8_T profiling_flag_mutex = 0;');
            end
            writer.newLine;
            
            % method to upload the buffer
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('void extmodeEmptyProfilingBufferLockFree(void);');
                writer.wBlockStart('void extmodeEmptyProfilingBuffer(void)');
                writer.wLine('XCP_MUTEX_LOCK(profiling_mutex);');
                writer.wLine('extmodeEmptyProfilingBufferLockFree();')
                writer.wLine('XCP_MUTEX_UNLOCK(profiling_mutex);');
                writer.wBlockEnd;
                writer.wBlockStart('void extmodeEmptyProfilingBufferLockFree(void)');
            else
                writer.wBlockStart('void extmodeEmptyProfilingBuffer(void)');
            end
            writer.wLine('XcpErrorCode errorCode = XCP_SUCCESS;');
            writer.wBlockStart('if (profilingSampleIndex > 0)');
            writer.wLine(['XCP_MEMCPY((void*)(' lBufferName '), (void*)&profilingBufferIndex, sizeof(' ...
                          this.IdentifierCDataType '));']);
            writer.wLine(['XCP_MEMCPY((void*)(' lBufferName ' + sizeof(' this.IdentifierCDataType ...
                          ')), (void*)&profilingSampleIndex, sizeof(' this.IdentifierCDataType '));']);
            writer.wLine('errorCode = xcpEvent(XCP_PROFILING_EVENT);');
            writer.wBlockStart('if (errorCode != XCP_SUCCESS)');
            writer.wLine('profilingLastErrorCode = errorCode;');
            writer.wLine('XCP_PRINTF("xcpEvent error: code %d\n", errorCode);');
            writer.wBlockEnd;
            writer.wLine('profilingBufferIndex = profilingBufferIndex + 1;');
            writer.wLine('profilingSampleIndex = 0;');
            writer.wBlockEnd;
            writer.wBlockEnd;
            
            % method to get the last error
            writer.wBlockStart('XcpErrorCode extmodeGetLastProfilingError (void)');
            writer.wLine('XcpErrorCode old = profilingLastErrorCode;');
            writer.wLine('profilingLastErrorCode = XCP_SUCCESS;');
            writer.wLine('return old;');
            writer.wBlockEnd;
            writer.newLine;

            % method to add a profiling sample in the buffer
            if this.GlobalRegistry.SplitAtomicReadStoreFcn
                this.targetWriteXCPSplitReadStoreSample(writer, cpuIDFcn, threadIDFcn, true);
                this.targetWriteXCPSplitReadStoreSample(writer, cpuIDFcn, threadIDFcn, false);
            else
                this.targetWriteXCPReadStoreSample(writer, cpuIDFcn, threadIDFcn);
            end
        end

        function targetWriteXCPManagementHeader(this, writer)
            if ~this.RealTimeUpload
                return;
            end
            writer.wLine('#ifndef XCP_PROFILING_H');
            writer.wLine('#define XCP_PROFILING_H');
            % this must be removed with new XCP API
            writer.wComment('Include main XCP header with XcpErrorCode type definition');
            writer.wLine('#include "xcp.h"');
            % this file contains the prototypes of the instrumentation API
            writer.wComment('Include main instrumentation header');
            [~, headerFile, extFile] = fileparts(this.GlobalRegistry.HeaderFileTargetInterface);
            writer.wLine('#include "%s%s"', headerFile, extFile);
            % prototypes for the methods implemented in xcp_profiling.c
            writer.wComment('This method flushes the profiling samples in the transmission buffer');
            writer.wLine('extern void extmodeEmptyProfilingBuffer(void);');
            writer.wComment('This method returns the last error happened since the last call of extmodeGetLastProfilingError');
            writer.wLine('extern XcpErrorCode extmodeGetLastProfilingError(void);');
            writer.wLine('#endif');
        end
    end
    
    methods (Access = private)
        
        function targetWriteXCPSplitReadStoreSample(this, writer, cpuIDFcn, threadIDFcn, isStart)
            hasCPUIDField = ~this.GlobalRegistry.SingleTaskModel && ~isempty(cpuIDFcn);
            hasThreadIDField = ~this.GlobalRegistry.SingleTaskModel && ~isempty(threadIDFcn);
            lHeaderSizeInBytes = this.GlobalRegistry.XCPBufferConfig.HeaderSizeInBytes;
            lPaddingInBytes = this.GlobalRegistry.XCPBufferConfig.PaddingSizeInBytes;
            lBufferName = this.GlobalRegistry.XCPBufferConfig.BufferName;
            
            if isStart
                fcnNamePostix = 'Start';
            else
                fcnNamePostix = 'End';
            end
            % method to add a profiling sample in the buffer
            writer.wBlockStart(['void ', this.GlobalRegistry.AtomicReadStoreFcnName, ...
                    fcnNamePostix '(', this.IdentifierCDataType, ' sectionId)']);
            % define local variables
            writer.wLine('%s timerValue;', this.ProfilingTimer.getTimerCType);
            if hasCPUIDField
                writer.wLine('%s cpuId;', this.IdentifierCDataType);
            end
            if hasThreadIDField
                writer.wLine('%s threadId;', this.IdentifierCDataType);
            end
            writer.wLine('%s offset;', this.IdentifierCDataType);
            % check if mutex must be initialized and lock it if required
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wBlockStart('if (profiling_flag_mutex == 0)');
                writer.wLine('XCP_MUTEX_INIT(profiling_mutex);');
                writer.wLine('profiling_flag_mutex = 1;');
                writer.wBlockEnd;
                writer.wLine('XCP_MUTEX_LOCK(profiling_mutex);');
            end
            % check any error in the offset
            writer.wBlockStart('if (profilingSampleIndex >= XCP_PROFILING_NUM_SAMPLES)');
            writer.wLine(['XCP_PRINTF("' ...
                          this.GlobalRegistry.AtomicReadStoreFcnName ...
                          ' error: offset not valid\n");']);
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('XCP_MUTEX_UNLOCK(profiling_mutex);');
            end
            writer.wLine('return;');
            writer.wBlockEnd;
            if isStart
                writer.wBlockStart('if (profilingSampleIndex == (XCP_PROFILING_NUM_SAMPLES-1))');
                if ~this.GlobalRegistry.SingleTaskModel
                    writer.wLine('extmodeEmptyProfilingBufferLockFree();');
                else
                    writer.wLine('extmodeEmptyProfilingBuffer();');
                end
                writer.wBlockEnd;
                
            end
            % compute where to write in the buffer
            writer.wLine('offset = %d + profilingSampleIndex * XCP_PROFILING_SAMPLE_SIZE + %d;', ...
                lHeaderSizeInBytes, lPaddingInBytes);
            % copy section ID
            writer.wLine('XCP_MEMCPY((void*)(%s + offset), (void*)&sectionId, sizeof(%s));', ...
                lBufferName, this.IdentifierCDataType);
            writer.wLine('offset = offset + sizeof(%s);', this.IdentifierCDataType);
            % get time stamp
            [timer_expr_prefix, timer_expr_suffix] = this.getTimerDirection(writer);
            % write timestamp in the buffer
            writer.wLine('timerValue = %s(%s)%s%s;', ...
                timer_expr_prefix, this.ProfilingTimer.getTimerCType, ...
                this.ProfilingTimer.getReadTimerExpression, timer_expr_suffix);
            writer.wLine('XCP_MEMCPY((void*)(%s + offset), (void*)&timerValue, sizeof(%s));', ...
                lBufferName, this.ProfilingTimer.getTimerCType );
            % write CPU and/or thread IDs
            if hasCPUIDField || hasThreadIDField
                if ~(hasCPUIDField && hasThreadIDField) && lPaddingInBytes > 0
                    % if there is only CPU ID or thread ID and padding is
                    % required
                    writer.wLine('offset = offset + sizeof(%s) + %d;', ...
                        this.ProfilingTimer.getTimerCType, lPaddingInBytes);
                else
                    writer.wLine('offset = offset + sizeof(%s);', ...
                        this.ProfilingTimer.getTimerCType );
                end
                if hasCPUIDField
                    writer.wLine('cpuId = %s;', cpuIDFcn);
                    writer.wLine('XCP_MEMCPY((void*)(%s + offset), (void*)&cpuId, sizeof(%s));', ...
                        lBufferName, this.IdentifierCDataType );
                end
                if hasThreadIDField
                    if hasCPUIDField
                        writer.wLine('offset = offset + sizeof(%s);', ...
                            this.IdentifierCDataType);
                    end
                    writer.wLine('threadId = %s;', threadIDFcn);
                    writer.wLine('XCP_MEMCPY((void*)(%s + offset), (void*)&threadId, sizeof(%s));', ...
                        lBufferName, this.IdentifierCDataType );
                end
            end
            % increase counter
            writer.wLine('profilingSampleIndex = profilingSampleIndex + 1;');
            % empty buffer if full
            if ~isStart
                writer.wBlockStart('if (profilingSampleIndex == XCP_PROFILING_NUM_SAMPLES)');
                if ~this.GlobalRegistry.SingleTaskModel
                    writer.wLine('extmodeEmptyProfilingBufferLockFree();');
                else
                    writer.wLine('extmodeEmptyProfilingBuffer();');
                end
                writer.wBlockEnd;
            end
            % release mutex if required
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('XCP_MUTEX_UNLOCK(profiling_mutex);');
            end
            writer.wBlockEnd;
        end
        
        function targetWriteXCPReadStoreSample(this, writer, cpuIDFcn, threadIDFcn)
            hasCPUIDField = ~this.GlobalRegistry.SingleTaskModel && ~isempty(cpuIDFcn);
            hasThreadIDField = ~this.GlobalRegistry.SingleTaskModel && ~isempty(threadIDFcn);
            lHeaderSizeInBytes = this.GlobalRegistry.XCPBufferConfig.HeaderSizeInBytes;
            lPaddingInBytes = this.GlobalRegistry.XCPBufferConfig.PaddingSizeInBytes;
            lBufferName = this.GlobalRegistry.XCPBufferConfig.BufferName;
            
            % method to add a profiling sample in the buffer
            writer.wBlockStart(['void ', this.GlobalRegistry.AtomicReadStoreFcnName, ...
                    '(', this.IdentifierCDataType, ' sectionId)']);
            % define local variables
            writer.wLine('%s timerValue;', this.ProfilingTimer.getTimerCType);
            if hasCPUIDField
                writer.wLine('%s cpuId;', this.IdentifierCDataType);
            end
            if hasThreadIDField
                writer.wLine('%s threadId;', this.IdentifierCDataType);
            end
            writer.wLine('%s offset;', this.IdentifierCDataType);
            % check if mutex must be initialized and lock it if required
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wBlockStart('if (profiling_flag_mutex == 0)');
                writer.wLine('XCP_MUTEX_INIT(profiling_mutex);');
                writer.wLine('profiling_flag_mutex = 1;');
                writer.wBlockEnd;
                writer.wLine('XCP_MUTEX_LOCK(profiling_mutex);');
            end
            % check any error in the offset
            writer.wBlockStart('if (profilingSampleIndex >= XCP_PROFILING_NUM_SAMPLES)');
            writer.wLine(['XCP_PRINTF("' ...
                          this.GlobalRegistry.AtomicReadStoreFcnName ...
                          ' error: offset not valid\n");']);
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('XCP_MUTEX_UNLOCK(profiling_mutex);');
            end
            writer.wLine('return;');
            writer.wBlockEnd;
            % compute where to write in the buffer
            writer.wLine(['offset = ' num2str(lHeaderSizeInBytes) ...
                          ' + profilingSampleIndex * XCP_PROFILING_SAMPLE_SIZE  + ' ...
                          num2str(lPaddingInBytes) ';']);
            % copy section ID
            writer.wLine(['XCP_MEMCPY((void*)(' lBufferName ' + offset), (void*)&sectionId, sizeof(' ...
                this.IdentifierCDataType '));']);
            writer.wLine('offset = offset + sizeof(%s);', this.IdentifierCDataType);
            % get time stamp
            [timer_expr_prefix, timer_expr_suffix] = this.getTimerDirection(writer);
            % write timestamp in the buffer
            writer.wLine(['timerValue = ', timer_expr_prefix, ...
                          '(', this.ProfilingTimer.getTimerCType, ')', ...
                          this.ProfilingTimer.getReadTimerExpression, ...
                          timer_expr_suffix, ';']);
            writer.wLine(['XCP_MEMCPY((void*)(' lBufferName ...
                          ' + offset), (void*)&timerValue, sizeof(' ...
                          this.ProfilingTimer.getTimerCType '));']);
            % write CPU and/or thread IDs
            if hasCPUIDField || hasThreadIDField
                if ~(hasCPUIDField && hasThreadIDField) && lPaddingInBytes > 0
                    % if there is only CPU ID or thread ID and padding is
                    % required
                    writer.wLine(['offset = offset + sizeof(' ...
                                  this.ProfilingTimer.getTimerCType ...
                                  ') + ' num2str(lPaddingInBytes) ';']);
                else
                    writer.wLine('offset = offset + sizeof(%s);', ...
                        this.ProfilingTimer.getTimerCType);
                end
                if hasCPUIDField
                    writer.wLine('cpuId = %s;', cpuIDFcn);
                    writer.wLine(['XCP_MEMCPY((void*)(' lBufferName ...
                                  ' + offset), (void*)&cpuId, sizeof(' ...
                                  this.IdentifierCDataType '));']);
                end
                if hasThreadIDField
                    if hasCPUIDField
                        writer.wLine(['offset = offset + sizeof(' ...
                                      this.IdentifierCDataType ');']);
                    end
                    writer.wLine(['threadId = ', threadIDFcn ';']);
                    writer.wLine(['XCP_MEMCPY((void*)(' lBufferName ...
                                  ' + offset), (void*)&threadId, sizeof(' ...
                                  this.IdentifierCDataType '));']);
                end
            end
            % increase counter
            writer.wLine('profilingSampleIndex = profilingSampleIndex + 1;');
            % empty buffer if full
            writer.wBlockStart('if (profilingSampleIndex == XCP_PROFILING_NUM_SAMPLES)');
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('extmodeEmptyProfilingBufferLockFree();');
            else
                writer.wLine('extmodeEmptyProfilingBuffer();');
            end
            writer.wBlockEnd;
            % release mutex if required
            if ~this.GlobalRegistry.SingleTaskModel
                writer.wLine('XCP_MUTEX_UNLOCK(profiling_mutex);');
            end
            writer.wBlockEnd;
        end
        
    end

end

% LocalWords:  Xpc XCP extmode
