/* common_funcs.cpp
   Copyright (C) 2005 Free Software Foundation, Inc.

This file is part of Mysaifu JVM

Mysaifu JVM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

Mysaifu JVM is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
*/

#include "StdAfx.h"
#include "common_funcs.h"

/**
 * JVMのバージョン
 */
static LARGE_INTEGER g_jvm_version;

/**
 * Temporary directory.
 */
static _TCHAR g_temp_dir[MAX_PATH + 1];

/**
 * 致命的なエラーが発生したことを示すダイアログボックスを表示後、
 * プログラムを終了させる
 */
void
fatal_error (const _TCHAR * msg)
{
  if (msg == FATAL_ERROR_NO_MEMORY)
    {
      SHShowOutOfMemory (NULL, 0);
    }
  else
    {
      MessageBox (NULL, msg, _T ("Mysaifu JVM fatal error"), MB_OK);
    }
  exit (1);
}

/**
 * JVMのバージョンを返す
 */
LARGE_INTEGER
get_jvm_version ()
{
  if (!g_jvm_version.QuadPart)
    {
      // JVMの正確なバージョンを取得する
      HMODULE hModule = GetModuleHandle (NULL);
      _TCHAR filename[MAX_PATH + 1];
      GetModuleFileName (hModule, filename, MAX_PATH);
      DWORD cbBlock = GetFileVersionInfoSize (filename, 0);
      BYTE *pbBlock = (BYTE *) malloc (sizeof (BYTE) * cbBlock);
      if (pbBlock)
	{
	  GetFileVersionInfo (filename, 0, cbBlock, pbBlock);

	  LPVOID pvBuf = NULL;
	  UINT uLen = 0;
	  VerQueryValue ((const LPVOID) pbBlock, _T ("\\"), &pvBuf, &uLen);

	  VS_FIXEDFILEINFO *vfi = (VS_FIXEDFILEINFO *) pvBuf;

	  if (vfi->dwSignature == 0xfeef04bd)
	    {
	      g_jvm_version.HighPart = vfi->dwProductVersionMS;
	      g_jvm_version.LowPart = vfi->dwProductVersionLS;
	    }
	  free (pbBlock);
	}
    }
  return g_jvm_version;
}

/**
 * テンポラリディレクトリを返す
 */
const _TCHAR* get_temp_dir()
{
  if (g_temp_dir[0] == _T('\0'))
  {
    // Note : first call is not thread-safe.
    _TCHAR dir[MAX_PATH + 1];
    if (! GetTempPath (MAX_PATH, dir))
    {
      _tcscpy(g_temp_dir, _T("\\"));
    }
    else
    {
      _tcscpy(g_temp_dir, dir);
      int len = _tcslen (dir);
      if (len && dir[len - 1] != _T ('\\'))
      {
        g_temp_dir[len - 1] = _T ('\\');
      }
      _tcscat (g_temp_dir, _T ("Mysaifu JVM"));
      LONG attr = GetFileAttributes(g_temp_dir);
      if (-1 == attr || (! (attr & FILE_ATTRIBUTE_DIRECTORY)))
      {
        // create a directory
        if (! CreateDirectory (g_temp_dir, NULL))
        {
          _tcscpy(g_temp_dir, dir);
        }
      }
    }
  }
  return g_temp_dir;
}

//--- PermanentHeap
/**
 * メモリ管理用構造体
 *
 * permanent_heap_block
 *
   +-------+
   | next  |--------> +----+ 
   +-------+          |next| --> ...
   | limit |-----+    +----+
   +-------+     |    | 
   |current|-+   |
   +-------+ |   |
   |elem1  | |   |
   +-------+ |   |
   |elem2  | |   |
   +-------+ |   |
   | 空き  <-+   |
   |       |     |
   +-------+<----+

 * 基本的に header は1つしか存在せず、空き領域が順番に埋められていくが、空き領域に入らなかった場合は
 * 新たに header が作られ、チェーンされる。
 * チェーンが長くなるのを防ぐため、ブロックサイズ以上のサイズが要求された場合は、malloc()で確保した結果をそのまま返す
 * のみの確保を行い、チェーンしない。
 * また、空き領域がsetMinimumAllocationSize()で指定した値以下になったブロックは、検索対象からはずされる。
 * (もうその領域からメモリは確保しない）
 */
struct permanent_heap_block
{
  permanent_heap_block *next;
  char *limit;
  char *current;
  // この直後にデータが格納されていく
};

/**
 * ０バイトのサイズが指定された場合に返すアドレス用変数
 */
static char g_zero_size_data;

/**
 * 切り上げ
 */
#define ALIGN_UP(p, s) ((((p) % (s)) == 0) ? (p) : ((p) + ((s) - ((p) % (s)))))

/**
 * ブロック内の空きサイズを返す
 * （負数になる可能性があることに注意）
 */
#define REMAINS_IN_BLOCK(b) ((int) (b->limit - b->current + 1))

PermanentHeap::PermanentHeap (size_t blockSize):head (NULL),
minimumAllocationSize (0)
{
  this->blockSize = blockSize;
}

PermanentHeap::~PermanentHeap ()
{
  // PermanentHeapは開放できない
}

/**
 * プール用のブロックを確保する
 */
permanent_heap_block *
PermanentHeap::allocateBlock ()
{
  permanent_heap_block *block =
    (permanent_heap_block *) calloc (1, this->getBlockSize ());
  if (block)
  {
    block->next = NULL;
    block->limit = ((char *) block) + getBlockSize () - 1;
    block->current = (char *) (block + 1);
  }
  return block;
}

/**
 * 指定されたサイズのメモリを確保する
 */
void *
PermanentHeap::allocate (size_t size)
{
  char *result = NULL;

  if (size == 0)
    {
      // 0バイト確保の場合には常に同一のポインタが返されるが
      // その後開放処理は行わないため問題はない（はず）
      return &g_zero_size_data;
    }
  if (size >= getBlockSize ())
    {
      // サイズが大きいので、malloc()で直接確保する
      // （ブロックの確保は行わない）
      result = (char *) malloc (size);
    }
  else
    {
      // 空き領域を探していく
      if (!this->head)
	{
	  this->head = allocateBlock ();
	}
      permanent_heap_block *block = this->head;
      permanent_heap_block *prev_block = NULL;
      while (true)
	{
	  if (REMAINS_IN_BLOCK (block) >= (int) size)
	    {
	      // ブロックから確保する
	      result = block->current;
	      block->current += ALIGN_UP (size, 8);	// 8バイト単位に切り上げ
	      if (REMAINS_IN_BLOCK (block) <
		  ((int) getMinimumAllocationSize ()))
		{
		  // 符号付比較を行うため、size_t を int にキャストしている
		  // 空き領域が足りないので、検索対象外にする
		  if (block == this->head)
		    {
		      // 先頭ブロックの場合
		      this->head = block->next;
		    }
		  else
		    {
		      assert (prev_block);
		      prev_block->next = block->next;
		    }

		}
	      break;
	    }
	  else
	    {
	      permanent_heap_block *next = block->next;
	      if (!next)
		{
		  // 新しいブロックを確保
		  next = allocateBlock ();
		  if (!next)
		    {
		      break;
		    }
		  block->next = next;
		}
	      prev_block = block;
	      block = next;
	    }
	}
    }
  return result;
}

