/*===- InstrProfilingFile.c - Write instrumentation to a file -------------===*\
|*
|*                     The LLVM Compiler Infrastructure
|*
|* This file is distributed under the University of Illinois Open Source
|* License. See LICENSE.TXT for details.
|*
\*===----------------------------------------------------------------------===*/

#include "InstrProfiling.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int writeFile(FILE *File) {
  /* Match logic in __llvm_profile_write_buffer(). */
  const __llvm_profile_data *DataBegin = __llvm_profile_data_begin();
  const __llvm_profile_data *DataEnd = __llvm_profile_data_end();
  const uint64_t *CountersBegin = __llvm_profile_counters_begin();
  const uint64_t *CountersEnd   = __llvm_profile_counters_end();
  const char *NamesBegin = __llvm_profile_names_begin();
  const char *NamesEnd   = __llvm_profile_names_end();

  /* Calculate size of sections. */
  const uint64_t DataSize = DataEnd - DataBegin;
  const uint64_t CountersSize = CountersEnd - CountersBegin;
  const uint64_t NamesSize = NamesEnd - NamesBegin;

  /* Create the header. */
  uint64_t Header[PROFILE_HEADER_SIZE] = {
    __llvm_profile_get_magic(),
    __llvm_profile_get_version(),
    DataSize,
    CountersSize,
    NamesSize,
    (uintptr_t)CountersBegin,
    (uintptr_t)NamesBegin
  };

  /* Write the data. */
#define CHECK_fwrite(Data, Size, Length, File) \
  do { if (fwrite(Data, Size, Length, File) != Length) return -1; } while (0)
  CHECK_fwrite(Header,        sizeof(uint64_t), PROFILE_HEADER_SIZE, File);
  CHECK_fwrite(DataBegin,     sizeof(__llvm_profile_data), DataSize, File);
  CHECK_fwrite(CountersBegin, sizeof(uint64_t), CountersSize, File);
  CHECK_fwrite(NamesBegin,    sizeof(char), NamesSize, File);
#undef CHECK_fwrite

   return 0;
}

static int writeFileWithName(const char *OutputName) {
  int RetVal;
  FILE *OutputFile;
  if (!OutputName || !OutputName[0])
    return -1;
  OutputFile = fopen(OutputName, "w");
  if (!OutputFile)
    return -1;

  RetVal = writeFile(OutputFile);

  fclose(OutputFile);
  return RetVal;
}

static const char *CurrentFilename = NULL;
void __llvm_profile_set_filename(const char *Filename) {
  CurrentFilename = Filename;
}

int getpid(void);
int __llvm_profile_write_file(void) {
  char *AllocatedFilename = NULL;
  int I, J;
  int RetVal;

#define MAX_PID_SIZE 16
  char PidChars[MAX_PID_SIZE] = { 0 };
  int PidLength = 0;
  int NumPids = 0;

  /* Get the filename. */
  const char *Filename = CurrentFilename;
#define UPDATE_FILENAME(NextFilename) \
  if (!Filename || !Filename[0]) Filename = NextFilename
  UPDATE_FILENAME(getenv("LLVM_PROFILE_FILE"));
  UPDATE_FILENAME("default.profraw");
#undef UPDATE_FILENAME

  /* Check the filename for "%p", which indicates a pid-substitution. */
  for (I = 0; Filename[I]; ++I)
    if (Filename[I] == '%' && Filename[++I] == 'p')
      if (!NumPids++) {
        PidLength = snprintf(PidChars, MAX_PID_SIZE, "%d", getpid());
        if (PidLength <= 0)
          return -1;
      }
  if (NumPids) {
    /* Allocate enough space for the substituted filename. */
    AllocatedFilename = (char*)malloc(I + NumPids*(PidLength - 2) + 1);
    if (!AllocatedFilename)
      return -1;

    /* Construct the new filename. */
    for (I = 0, J = 0; Filename[I]; ++I)
      if (Filename[I] == '%') {
        if (Filename[++I] == 'p') {
          memcpy(AllocatedFilename + J, PidChars, PidLength);
          J += PidLength;
        }
        /* Drop any unknown substitutions. */
      } else
        AllocatedFilename[J++] = Filename[I];
    AllocatedFilename[J] = 0;

    /* Actually use the computed name. */
    Filename = AllocatedFilename;
  }

  /* Write the file. */
  RetVal = writeFileWithName(Filename);

  /* Free the filename. */
  if (AllocatedFilename)
    free(AllocatedFilename);

  return RetVal;
}

static void writeFileWithoutReturn(void) {
  __llvm_profile_write_file();
}

int __llvm_profile_register_write_file_atexit(void) {
  static int HasBeenRegistered = 0;

  if (HasBeenRegistered)
    return 0;

  HasBeenRegistered = 1;
  return atexit(writeFileWithoutReturn);
}
