/* Copyright 1996 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* armprof.c:  Copyright (C) Codemist Ltd., 1988      */
/* Copyright (C) Acorn Computers Ltd., 1988, 1990     */
/* RISCOS-specific profiling support                  */

#include <stdio.h>

/* HIDDEN EXPORTS */
extern void _mapstore(void);
extern void _fmapstore(char *);
void _write_profile(char *);

#ifdef __STDC__
  #error armprof.c MUST be compiled in -pcc mode
#endif

extern unsigned Image$$RO$$Base, Image$$RO$$Limit, Image$$RW$$Limit;

static unsigned *RO_Base  = &Image$$RO$$Base;
static unsigned *RO_Limit = &Image$$RO$$Limit;
static unsigned *RW_Limit = &Image$$RO$$Limit;

extern void _count(void), _count1(void);

typedef union count_position
/* This defines the format of the word that follows on from a call         */
/* to _count1(). The related constant values are related to the way that   */
/* file-name decoding tables are packed away.                              */
{   int i;
    struct s
    {   unsigned int posn:12,
                     line:16,
                     file:4;
    } s;
} count_position;

#define file_name_map_start 0xfff12340  /* Magic number */
#define file_name_map_end   0x31415926  /* Magic number */

#define word_roundup(s) ((char *)(((int)s + 3) & (~3)))

static char *find_file_map(int p)
{
    int i, w;
    char *s;
    while (((w = *(int *)p) & 0xfffffff0) != file_name_map_start)
    {   if (p >= (int)RO_Limit) return "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
        p += 4;
    }
    s = (char *)(p + 4);
    for ( i = 0; i<=(w & 0xf); i++)
    {   s += 1 + strlen(s);
        s = word_roundup(s);
    }
    if (*(int *)s != file_name_map_end) return find_file_map((int)s);
    return (char *)(p + 4);
}

static void _map_store(FILE *map_file)
{
    unsigned count1 = (unsigned)_count1;    /* address of the fn as an int */
    unsigned p, onthisline = 4, w1 = 0, w2 = 0;
    unsigned ro_base  = (unsigned)RO_Base;
    unsigned ro_limit = (unsigned)RO_Limit;
    unsigned rw_limit = (unsigned)RW_Limit;

    fprintf(map_file,
"\nFunction/statement counts from code base = %.6x to code limit = %.6x\n",
        ro_base, ro_limit);

    for (p = ro_base; p < ro_limit; p += 4)
    {   int w = *(int *)p;
        if ((w & 0xff000000) == 0xeb000000) /* Unconditional BL instruction */
        {   unsigned dest = (p + 8 + ((w << 8) >> 6));
            if (dest != count1 && dest >= ro_base && dest < rw_limit)
            {   /* Try for call through (straight) veneer */
                int b = *(int *)dest;
                if ((b & 0xff000000) == 0xea000000) /* Unconditional B */
                    dest = (dest + 8 + ((b << 8) >> 6));
            }
            if (dest == count1)
            {   if (onthisline == 4) onthisline = 0, fputs("\n   ", map_file);
                ++onthisline;
                fprintf(map_file, " %.6u: %-9u",
                        (*(unsigned *)(p + 8) << 4) >> 16,
                        *(unsigned *)(p + 4));
            }
        }
        if (   ((w & 0xff000000) == 0xe9000000) &&
             ( ((w & 0x00ff0000) == 0x002c0000 && w1 == 0xe1a0b00c) ||
               ((w & 0x00ff0000) == 0x002d0000 && w1 == 0xe1a0c00d) ) &&
             /* STMFD sp!, ..., ; MOV ip, sp  @@@@@, either calling sequence */
             (w2 & 0xffff0000) == 0xff000000)
        {   char *name = (char *)(p - 8 - (w2 & 0xffff));
            if (onthisline != 0) fputc('\n', map_file);
            onthisline = 4;
            fprintf(map_file, "%s", name);
        }
        w2 = w1;
        w1 = w;
    }
    if (onthisline != 0) fputc('\n', map_file);
}

void _mapstore()
{
    _map_store(stderr);
}

void _fmapstore(char *filename)
{
    FILE *map_file = map_file = fopen(filename, "w");
    if (map_file == NULL)
    {   fprintf(stderr, "\nUnable to open %s for execution profile log\n",
                        filename);
        return;
    }
    _map_store(map_file);
    fclose(map_file);
    fprintf(stderr, "\nProfile information written to %s\n", filename);
}

void _write_profile(char *filename)
{
/* Create a (binary) file containing execution profile information for     */
/* the current program. The format is eccentric, and must be kept in step  */
/* with (a) parts of armgen.c that generate code that collects statistics  */
/* and (b) code in misc.c that reads in the binary file created here and   */
/* displays the counts attached to a source listing of the original code.  */
    int count1 = (int)_count1;
    int p, w1 = 0, w2 = 0, pass, nfiles = 0, namebytes = 0, ncounts = 0;
    int global_name_offset[256];
    char *global_file_map[256]; /* Limits total number of files allowed */
    FILE *map_file = fopen(filename, "wb");
    char *file_map;
    if (map_file == NULL)
    {   fprintf(stderr, "\nUnable to open %s for execution profile log\n",
                        filename);
        return;
    }
    for (pass = 1; pass <=2; pass++)
    {
        if (pass == 2)
        /* Write file header indicating size of sub-parts */
        {   fwrite("\xff*COUNTFILE*", 4, 3, map_file);
            fwrite(&namebytes, 4, 1, map_file);
            fwrite(&nfiles,    4, 1, map_file);
            fwrite(&ncounts,   4, 1, map_file);
            for (p = 0; p < nfiles; p++)
            {   char *ss = global_file_map[p];
                int len = 1 + strlen(ss);
                len = ((len + 3) & (~3)) / 4;
                fwrite(ss, 4, len, map_file);
            }
            for (p = 0; p < nfiles; p++)
                fwrite(&global_name_offset[p], 4, 1, map_file);
        }
        file_map = NULL;
        for (p = (int)RO_Base; p < (int)RO_Limit; p += 4)
        {   int w = *(int *)p;
            if ((w & 0xff000000) == 0xeb000000) /* BL instruction */
            {   int dest = (p + 8 + ((w << 8) >> 6));
                if (dest != count1 &&
                    dest >= (int)RO_Base && dest < (int)RO_Limit)
                {   /* Try for call through (straight) veneer */
                    int b = *(int *)dest;
                    if ((b & 0xff000000) == 0xea000000) /* Unconditional B */
                        dest = (dest + 8 + ((b << 8) >> 6));
                }
                if (dest == count1)
                {   count_position k;
                    int i;
                    char *s;
                    if (file_map == NULL ||
                        (int)file_map <= p) file_map = find_file_map(p+12);
                    s = file_map;
                    k.i = *(int *)(p + 8);
                    for (i = 0; i<k.s.file; i++)
                    {   s += 1 + strlen(s);
                        s = word_roundup(s);
                    }
                    if (pass == 1)
                    {   int i;
                        for (i = 0;;i++)
                        {   if (i >= nfiles)
                            {   global_name_offset[nfiles] = namebytes;
                                global_file_map[nfiles++] = s;
                                namebytes += 1 + strlen(s);
                                namebytes = (namebytes + 3) & (~3);
                                break;
                            }
                            else if (strcmp(s,global_file_map[i]) ==0) break;
                        }
                        ncounts++;
                    }
                    else
                    {   int i;
                        for (i = 0; strcmp(s, global_file_map[i]) !=0; i++);
                        fwrite((int *)(p + 4), 4, 1, map_file);
                        i = (k.s.line & 0xffff) | (i << 16);
                        fwrite(&i, 4, 1, map_file);
                    }
                    p += 8;
                }
            }
            w2 = w1;
            w1 = w;
        }
    }
    fwrite("\xff*ENDCOUNT*\n", 4, 3, map_file); /* Trailer data */
    fclose(map_file);
    fprintf(stderr, "\nProfile information written to %s\n", filename);
}