/*
 * 構造体アロケータ
 * Funded by IPA未踏ソフトウェア創造事業 2001 8/15
 *
 * $Id: alloc.c,v 1.12 2002/05/15 11:21:10 yusuke Exp $
 *
 * Copyright (C) 2005 YOSHIDA Yuichi
 * Copyright (C) 2000-2005 TABATA Yusuke, UGAWA Tomoharu
 * Copyright (C) 2002, 2005 NIIBE Yutaka
 *
 * dtor: destructor
 * 
 * ページ中のフリーなchunkは単方向リストに継がれている
 *
 */
/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2 of the License, or (at your option) any later version.

  This library 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
  Lesser General Public License for more details.

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

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

#include <anthy/alloc.h>
#include <anthy/logger.h>

/**/
#define PAGE_MAGIC 0x12345678
#define PAGE_SIZE 2048

/* ページ使用量の合計、デバッグの時等に用いる */
static int nr_pages;

/* page内のオブジェクトを表すオブジェクト */
struct chunk {
  void *storage[1];
};
#define CHUNK_HEADER_SIZE ((size_t)&((struct chunk *)0)->storage)
/* CPUもしくは、OSの種類によって要求されるアライメント */
#define CHUNK_ALIGN (sizeof(double))

/*
 * pageのstorage中には 
 * max_obj = (PAGE_SIZE - PAGE_HEADER_SIZE) / (size + CHUNK_HEADER_SIZE)個の
 * スロットがある。そのうちuse_count個のスロットがfree_listにつながっている、
 * もしくは使用中である。
 */
struct page {
  int magic;
  struct page *prev, *next;
};


#define PAGE_HEADER_SIZE (sizeof(struct page))
#define PAGE_AVAIL(p) ((unsigned char*)p + sizeof(struct page))
#define PAGE_STORAGE(a, p) (((unsigned char *)p) + (a->storage_offset))
#define PAGE_CHUNK(a, p, i) (struct chunk*)(&PAGE_STORAGE(a, p)[((a->size) + CHUNK_HEADER_SIZE) * (i)])


/**/
struct allocator_priv {
  /* 構造体のサイズ */
  int size;
  /* ページ内に入れることができるオブジェクトの数 */
  int max_num;
  /* 
     実際のデータが格納され始める場所のオフセット 
     ページ中のこれより手前には対応する場所のデータが使われているかどうかを0/1で表す
     ビットマップがある
   */
  int storage_offset;
  /* このallocatorが使用しているページのリスト */
  struct page page_list;
  /* allocatorのリスト */
  struct allocator_priv *next;
  /* sfreeした際に呼ばれる */
  void (*dtor)(void *);
};

static struct allocator_priv *allocator_list;

static int bit_test(unsigned char* bits, int pos)
{
  /*
     bit_getとほぼ同じだがbit != 0の時に0以外を返すことしか保証しない
   */
  return bits[pos >> 3] & (1 << (7 - (pos & 0x7)));
}


static int bit_set(unsigned char* bits, int pos, int bit)
{
  unsigned char filter = 1 << (7 - (pos & 0x7));
  if (bit == 0) {
    return bits[pos >> 3] &= ~filter;
  } else {
    return bits[pos >> 3] |= filter;
  }
}


static struct chunk *
get_chunk_address(void *s)
{
  return (struct chunk *)
    ((unsigned long)s - CHUNK_HEADER_SIZE);
}

static struct page *
alloc_page(struct allocator_priv *ator)
{
  struct page *p;
  unsigned char* avail;
    
  p = malloc(PAGE_SIZE);
  if (!p) {
    return NULL;
  }

  p->magic = PAGE_MAGIC;
  avail = PAGE_AVAIL(p);
  memset(avail, 0, (ator->max_num >> 3) + 1);
  return p;
}

static struct chunk *
get_chunk_from_page(allocator a, struct page *p)
{
  int i;

  int num = a->max_num;
  unsigned char* avail = PAGE_AVAIL(p);

  for (i = 0; i < num; ++i) {
    if (bit_test(avail, i) == 0) {
      bit_set(avail, i, 1);
      return PAGE_CHUNK(a, p, i);
    }
  }
  return NULL;  
}

static int
roundup_align(int num)
{
  num = num + (CHUNK_ALIGN - 1);
  num /= CHUNK_ALIGN;
  num *= CHUNK_ALIGN;
  return num;
}

static int
calc_max_num(int size)
{
  int area, bits;
  /* ビット数で計算
   * 厳密な最適解ではない
   */
  area = (PAGE_SIZE - PAGE_HEADER_SIZE - CHUNK_ALIGN) * 8;
  bits = (size + CHUNK_HEADER_SIZE) * 8 + 1;
  return (int)(area / bits);
}

allocator
anthy_create_allocator(int size, void (*dtor)(void *))
{
  allocator a;
size=roundup_align(size);
  if (size > (int)(PAGE_SIZE - PAGE_HEADER_SIZE - CHUNK_HEADER_SIZE)) {
    anthy_log(0, "Fatal error: too big allocator is requested.\n");
    exit(1);
  }
  a = malloc(sizeof(*a));
  if (!a) {
    anthy_log(0, "Fatal error: Failed to allocate memory.\n");
    exit(1);
  }
  a->size = size;
  a->max_num = calc_max_num(size); 
  a->storage_offset = roundup_align(sizeof(struct page) + a->max_num / 8 + 1);
  /*printf("size=%d max_num=%d offset=%d area=%d\n", size, a->max_num, a->storage_offset, size*a->max_num + a->storage_offset);*/
  a->dtor = dtor;
  a->page_list.next = &a->page_list;
  a->page_list.prev = &a->page_list;
  a->next = allocator_list;
  allocator_list = a;
  return a;
}

static void
anthy_free_allocator_internal(allocator a)
{
  struct page *p, *p_next;

  /* 各ページのメモリを解放する */
  for (p = a->page_list.next; p != &a->page_list; p = p_next) {
    unsigned char* avail = PAGE_AVAIL(p);
    int i;

    p_next = p->next;
    if (a->dtor) {
      for (i = 0; i < a->max_num; i++) {
	if (bit_test(avail, i)) {
	  struct chunk *c;

	  bit_set(avail, i, 0);
	  c = PAGE_CHUNK(a, p, i);
	  a->dtor(c->storage);
	}
      }
    }
    free(p);
    nr_pages--;
  }
  free(a);
}

void
anthy_free_allocator(allocator a)
{
  allocator a0, *a_prev_p;

  /* リストからaの前の要素を見付ける */
  a_prev_p = &allocator_list;
  for (a0 = allocator_list; a0; a0 = a0->next) {
    if (a == a0)
      break;
    else
      a_prev_p = &a0->next;
  }
  /* aをリストから外す */
  *a_prev_p = a->next;

  anthy_free_allocator_internal(a);
}

void *
anthy_smalloc(allocator a)
{
  struct page *p;
  struct chunk *c;

  /* 空いてるページをさがす */
  for (p = a->page_list.next; p != &a->page_list; p = p->next) {
    c = get_chunk_from_page(a, p);
    if (c) {
      return c->storage;
    }
  }
  /* ページを作って、リンクする */
  p = alloc_page(a);
  if (!p) {
    anthy_log(0, "Fatal error: Failed to allocate memory.\n");
    return NULL;
  }
  nr_pages++;

  p->next = a->page_list.next;
  p->prev = &a->page_list;
  a->page_list.next->prev = p;
  a->page_list.next = p;
  /* やり直す */
  return anthy_smalloc(a);
}

void
anthy_sfree(allocator a, void *ptr)
{
  struct chunk *c = get_chunk_address(ptr);
  struct page *p;
  int index;
  /* ポインタの含まれるページを探す */
  for (p = a->page_list.next; p != &a->page_list; p = p->next) {
    if ((unsigned long)p < (unsigned long)c &&
	(unsigned long)c < (unsigned long)p + PAGE_SIZE) {
      break;
    }
  }

  /* sanity check */
  if (!p || p->magic != PAGE_MAGIC) {
    anthy_log(0, "sfree()ing Invalid Object\n");
    abort();
  }

  /* ページ中の何番目のオブジェクトかを求める */
  index = ((unsigned long)c - (unsigned long)PAGE_STORAGE(a, p)) /
    (a->size + CHUNK_HEADER_SIZE);  
  bit_set(PAGE_AVAIL(p), index, 0);

  /* デストラクタを呼ぶ */
  if (a->dtor) {
    a->dtor(ptr);
  }
}

void
anthy_quit_allocator(void)
{
  allocator a, a_next;
  for (a = allocator_list; a; a = a_next) {
    a_next = a->next;
    anthy_free_allocator_internal(a);
  }
  allocator_list = NULL;
}
