/* 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;

/**
 * 致命的なエラーが発生したことを示すダイアログボックスを表示後、
 * プログラムを終了させる
 */
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;
}


//--- 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());
  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;
}
