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

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "config.h"
#include "lib.h"
#include "objlist.h"
#include "type.h"
#include "asm_format.h"
#include "asm_code.h"
#include "asm_queue.h"

typedef enum {
  QENTRY_TYPE_NONE = 0,

  /* asm_format_* */

  QENTRY_TYPE_FORMAT_COMMENT,
  QENTRY_TYPE_FORMAT_NEWLINE,
  QENTRY_TYPE_FORMAT_ALIGN,
  QENTRY_TYPE_FORMAT_SPACE,
  QENTRY_TYPE_FORMAT_LABEL,
  QENTRY_TYPE_FORMAT_GLOBL,
  QENTRY_TYPE_FORMAT_TYPE,
  QENTRY_TYPE_FORMAT_SIZE,
  QENTRY_TYPE_FORMAT_DUMMY,
  QENTRY_TYPE_FORMAT_STRING,
  QENTRY_TYPE_FORMAT_VALUE,
  QENTRY_TYPE_FORMAT_SECTION,

  /* asm_code_* */

  QENTRY_TYPE_CODE_GET_VALUE,
  QENTRY_TYPE_CODE_GET_VALUE_R1,
  QENTRY_TYPE_CODE_GET_ADDRESS_STACK,
  QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1,
  QENTRY_TYPE_CODE_GET_ADDRESS,
  QENTRY_TYPE_CODE_ADD_ADDRESS,
  QENTRY_TYPE_CODE_GET_R1,
  QENTRY_TYPE_CODE_SET_R1,
  QENTRY_TYPE_CODE_MEMORY_LOAD,
  QENTRY_TYPE_CODE_MEMORY_STORE,
  QENTRY_TYPE_CODE_STACK_LOAD,
  QENTRY_TYPE_CODE_STACK_STORE,
  QENTRY_TYPE_CODE_STACK_LOAD_R1,
  QENTRY_TYPE_CODE_STACK_STORE_R1,
  QENTRY_TYPE_CODE_STACK_EXPAND,
  QENTRY_TYPE_CODE_STACK_REDUCE,
  QENTRY_TYPE_CODE_FUNCCALL_REG_LOAD,
  QENTRY_TYPE_CODE_FUNCCALL_REG_STORE,
  QENTRY_TYPE_CODE_TMP_REG_LOAD,
  QENTRY_TYPE_CODE_TMP_REG_SAVE,
  QENTRY_TYPE_CODE_TMP_REG_LOAD_R1,
  QENTRY_TYPE_CODE_TMP_REG_SAVE_R1,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_CHAR,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_UCHAR,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_SHORT,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_USHORT,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_INT,
  QENTRY_TYPE_CODE_SIGN_EXTENSION_UINT,
  QENTRY_TYPE_CODE_CALC_INV,
  QENTRY_TYPE_CODE_CALC_MINUS,
  QENTRY_TYPE_CODE_CALC_OP1,
  QENTRY_TYPE_CODE_CALC_ADD,
  QENTRY_TYPE_CODE_CALC_SUB,
  QENTRY_TYPE_CODE_CALC_AND,
  QENTRY_TYPE_CODE_CALC_OR,
  QENTRY_TYPE_CODE_CALC_XOR,
  QENTRY_TYPE_CODE_CALC_MUL,
  QENTRY_TYPE_CODE_CALC_DIV,
  QENTRY_TYPE_CODE_CALC_MOD,
  QENTRY_TYPE_CODE_CALC_LLSHIFT,
  QENTRY_TYPE_CODE_CALC_RASHIFT,
  QENTRY_TYPE_CODE_CALC_RLSHIFT,
  QENTRY_TYPE_CODE_CALC_OP2,
  QENTRY_TYPE_CODE_BRANCH,
  QENTRY_TYPE_CODE_BRANCH_ZERO,
  QENTRY_TYPE_CODE_BRANCH_NZERO,
  QENTRY_TYPE_CODE_BRANCH_CMP_EQ,
  QENTRY_TYPE_CODE_BRANCH_CMP_NE,
  QENTRY_TYPE_CODE_BRANCH_CMP_LT,
  QENTRY_TYPE_CODE_BRANCH_CMP_GT,
  QENTRY_TYPE_CODE_BRANCH_CMP_LE,
  QENTRY_TYPE_CODE_BRANCH_CMP_GE,
  QENTRY_TYPE_CODE_BRANCH_CMP_ULT,
  QENTRY_TYPE_CODE_BRANCH_CMP_UGT,
  QENTRY_TYPE_CODE_BRANCH_CMP_ULE,
  QENTRY_TYPE_CODE_BRANCH_CMP_UGE,
  QENTRY_TYPE_CODE_BRANCH_CMP,
  QENTRY_TYPE_CODE_FUNCTION_CALL,
  QENTRY_TYPE_CODE_FUNCTION_CALL_SET,
  QENTRY_TYPE_CODE_FUNCTION_CALL_POINTER,
  QENTRY_TYPE_CODE_FUNCTION_START,
  QENTRY_TYPE_CODE_FUNCTION_END,
  QENTRY_TYPE_CODE_FUNCTION_REGISTER_SAVE,
  QENTRY_TYPE_CODE_FUNCTION_REGISTER_LOAD,

  QENTRY_TYPE_NUM
} qentry_type_t;

typedef struct qentry_info {
  qentry_type_t type;
#define QENTRY_INFO_FLAG_NONE   0
#define QENTRY_INFO_FLAG_BREAK (1 << 0)
#define QENTRY_INFO_FLAG_R0RD  (1 << 1)
#define QENTRY_INFO_FLAG_R0WR  (1 << 2)
#define QENTRY_INFO_FLAG_R1RD  (1 << 3)
#define QENTRY_INFO_FLAG_R1WR  (1 << 4)
#define QENTRY_INFO_FLAG_SPRD  (1 << 5)
#define QENTRY_INFO_FLAG_SPWR  (1 << 6)
#define QENTRY_INFO_FLAG_JUMP  (1 << 7)
#define QENTRY_INFO_FLAG_EXTN  (1 << 8)
  unsigned int flags;
} *qentry_info_t;

struct qentry_info qentry_infos[] = {
  { QENTRY_TYPE_NONE, QENTRY_INFO_FLAG_NONE },

  /* asm_format_* */

  { QENTRY_TYPE_FORMAT_COMMENT, QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_NEWLINE, QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_ALIGN,   QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_SPACE,   QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_FORMAT_LABEL,   QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_FORMAT_GLOBL,   QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_TYPE,    QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_SIZE,    QENTRY_INFO_FLAG_NONE },
  { QENTRY_TYPE_FORMAT_DUMMY,   QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_FORMAT_STRING,  QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_FORMAT_VALUE,   QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_FORMAT_SECTION, QENTRY_INFO_FLAG_BREAK },

  /* asm_code_* */

  { QENTRY_TYPE_CODE_GET_VALUE,            QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_GET_VALUE_R1,         QENTRY_INFO_FLAG_R1WR },
  { QENTRY_TYPE_CODE_GET_ADDRESS_STACK,    QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1, QENTRY_INFO_FLAG_R1WR|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_GET_ADDRESS,          QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_ADD_ADDRESS,          QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_GET_R1,               QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_SET_R1,               QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1WR },
  { QENTRY_TYPE_CODE_MEMORY_LOAD,          QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_MEMORY_STORE,         QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD },
  { QENTRY_TYPE_CODE_STACK_LOAD,           QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_STACK_STORE,          QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_STACK_LOAD_R1,        QENTRY_INFO_FLAG_R1WR|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_STACK_STORE_R1,       QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_SPRD },
  { QENTRY_TYPE_CODE_STACK_EXPAND,         QENTRY_INFO_FLAG_SPRD|QENTRY_INFO_FLAG_SPWR },
  { QENTRY_TYPE_CODE_STACK_REDUCE,         QENTRY_INFO_FLAG_SPRD|QENTRY_INFO_FLAG_SPWR },
  { QENTRY_TYPE_CODE_FUNCCALL_REG_LOAD,    QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCCALL_REG_STORE,   QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_TMP_REG_LOAD,         QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_TMP_REG_SAVE,         QENTRY_INFO_FLAG_R0RD },
  { QENTRY_TYPE_CODE_TMP_REG_LOAD_R1,      QENTRY_INFO_FLAG_R1WR },
  { QENTRY_TYPE_CODE_TMP_REG_SAVE_R1,      QENTRY_INFO_FLAG_R1RD },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_CHAR,   QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_UCHAR,  QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_SHORT,  QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_USHORT, QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_INT,    QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_SIGN_EXTENSION_UINT,   QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR|QENTRY_INFO_FLAG_EXTN },
  { QENTRY_TYPE_CODE_CALC_INV,              QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_MINUS,            QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_OP1,              QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_ADD,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_SUB,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_AND,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_OR,      QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_XOR,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_MUL,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_DIV,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_MOD,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_LLSHIFT, QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_RASHIFT, QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_RLSHIFT, QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_CALC_OP2,     QENTRY_INFO_FLAG_R0RD|QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R0WR },
  { QENTRY_TYPE_CODE_BRANCH,         QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_ZERO,    QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_NZERO,   QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_EQ,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_NE,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_LT,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_GT,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_LE,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_GE,  QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_ULT, QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_UGT, QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_ULE, QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP_UGE, QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_BRANCH_CMP,     QENTRY_INFO_FLAG_BREAK|QENTRY_INFO_FLAG_JUMP },
  { QENTRY_TYPE_CODE_FUNCTION_CALL,          QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_CALL_SET,      QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_CALL_POINTER,  QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_START,         QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_END,           QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_REGISTER_SAVE, QENTRY_INFO_FLAG_BREAK },
  { QENTRY_TYPE_CODE_FUNCTION_REGISTER_LOAD, QENTRY_INFO_FLAG_BREAK },
};

static int enqueue(qentry_type_t type, FILE *out, long number, int value, char *name);
static int enqueue_null(qentry_type_t type, FILE *out);
static int enqueue_number(qentry_type_t type, FILE *out, long number);
static int enqueue_value(qentry_type_t type, FILE *out, int value);
static int enqueue_name(qentry_type_t type, FILE *out, char *name);
static int enqueue_string(qentry_type_t type, FILE *out,
			  int value, char *name, char *string);
static int enqueue_model(qentry_type_t type, FILE *out,
			 int value, char *name, model_t model);
static int enqueue_comment(qentry_type_t type, FILE *out, char *comment,
			   char *param1, char *param2, char *param3);
static int enqueue_args(qentry_type_t type, FILE *out, int value, model_t model,
			model_t model_arg0, model_t model_arg1);

/* asm_format_* */

void asm_queue_format_comment(FILE *out, char *comment, char *param1, char *param2, char *param3)
{
  if (enqueue_comment(QENTRY_TYPE_FORMAT_COMMENT, out, comment, param1, param2, param3) > 0)
    return;
  asm_format_comment(out, comment, param1, param2, param3);
}

void asm_queue_format_newline(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_FORMAT_NEWLINE, out) > 0)
    return;
  asm_format_newline(out);
}

void asm_queue_format_align(FILE *out, int size)
{
  if (enqueue_value(QENTRY_TYPE_FORMAT_ALIGN, out, size) > 0)
    return;
  asm_format_align(out, size);
}

void asm_queue_format_space(FILE *out, int size)
{
  if (enqueue_value(QENTRY_TYPE_FORMAT_SPACE, out, size) > 0)
    return;
  asm_format_space(out, size);
}

void asm_queue_format_label(FILE *out, char *label)
{
  if (enqueue_name(QENTRY_TYPE_FORMAT_LABEL, out, label) > 0)
    return;
  asm_format_label(out, label);
}

void asm_queue_format_globl(FILE *out, char *name)
{
  if (enqueue_name(QENTRY_TYPE_FORMAT_GLOBL, out, name) > 0)
    return;
  asm_format_globl(out, name);
}

void asm_queue_format_type(FILE *out, char *name, char *prefix, asm_format_type_t type)
{
  if (enqueue_string(QENTRY_TYPE_FORMAT_TYPE, out, type, name, prefix) > 0)
    return;
  asm_format_type(out, name, prefix, type);
}

void asm_queue_format_size(FILE *out, char *name, int size)
{
  if (enqueue(QENTRY_TYPE_FORMAT_SIZE, out, 0, size, name) > 0)
    return;
  asm_format_size(out, name, size);
}

void asm_queue_format_dummy(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_FORMAT_DUMMY, out) > 0)
    return;
  asm_format_dummy(out);
}

void asm_queue_format_string(FILE *out, char *string, int size)
{
  if (enqueue_string(QENTRY_TYPE_FORMAT_STRING, out, size, NULL, string) > 0)
    return;
  asm_format_string(out, string, size);
}

void asm_queue_format_value(FILE *out, asm_format_type_t type, long number, char *name)
{
  if (enqueue(QENTRY_TYPE_FORMAT_VALUE, out, number, type, name) > 0)
    return;
  asm_format_value(out, type, number, name);
}

void asm_queue_format_section(FILE *out, asm_format_type_t type)
{
  if (enqueue_value(QENTRY_TYPE_FORMAT_SECTION, out, type) > 0)
    return;
  asm_format_section(out, type);
}

/* asm_code_* */

void asm_queue_code_get_value(FILE *out, long value)
{
  if (enqueue_number(QENTRY_TYPE_CODE_GET_VALUE, out, value) > 0)
    return;
  asm_code_get_value(out, value);
}

void asm_queue_code_get_value_r1(FILE *out, long value)
{
  if (enqueue_number(QENTRY_TYPE_CODE_GET_VALUE_R1, out, value) > 0)
    return;
  asm_code_get_value_r1(out, value);
}

void asm_queue_code_get_address_stack(FILE *out, int offset)
{
  if (enqueue_value(QENTRY_TYPE_CODE_GET_ADDRESS_STACK, out, offset) > 0)
    return;
  asm_code_get_address_stack(out, offset);
}

int asm_queue_code_get_address_stack_r1(FILE *out, int offset)
{
  if (asm_code_get_address_stack_r1(NULL, offset) < 0)
    return -1;
  if (enqueue_value(QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1, out, offset) > 0)
    return 0;
  return asm_code_get_address_stack_r1(out, offset);
}

void asm_queue_code_get_address(FILE *out, char *label)
{
  if (enqueue_name(QENTRY_TYPE_CODE_GET_ADDRESS, out, label) > 0)
    return;
  asm_code_get_address(out, label);
}

void asm_queue_code_add_address(FILE *out, int offset)
{
  if (enqueue_value(QENTRY_TYPE_CODE_ADD_ADDRESS, out, offset) > 0)
    return;
  asm_code_add_address(out, offset);
}

void asm_queue_code_get_r1(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_GET_R1, out) > 0)
    return;
  asm_code_get_r1(out);
}

void asm_queue_code_set_r1(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_SET_R1, out) > 0)
    return;
  asm_code_set_r1(out);
}

void asm_queue_code_memory_load(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_MEMORY_LOAD, out, offset, NULL, model) > 0)
    return;
  asm_code_memory_load(out, offset, model);
}

void asm_queue_code_memory_store(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_MEMORY_STORE, out, offset, NULL, model) > 0)
    return;
  asm_code_memory_store(out, offset, model);
}

void asm_queue_code_stack_load(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_STACK_LOAD, out, offset, NULL, model) > 0)
    return;
  asm_code_stack_load(out, offset, model);
}

void asm_queue_code_stack_store(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_STACK_STORE, out, offset, NULL, model) > 0)
    return;
  asm_code_stack_store(out, offset, model);
}

void asm_queue_code_stack_load_r1(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_STACK_LOAD_R1, out, offset, NULL, model) > 0)
    return;
  asm_code_stack_load_r1(out, offset, model);
}

void asm_queue_code_stack_store_r1(FILE *out, int offset, model_t model)
{
  if (enqueue_model(QENTRY_TYPE_CODE_STACK_STORE_R1, out, offset, NULL, model) > 0)
    return;
  asm_code_stack_store_r1(out, offset, model);
}

void asm_queue_code_stack_expand(FILE *out, int size)
{
  if (enqueue_value(QENTRY_TYPE_CODE_STACK_EXPAND, out, size) > 0)
    return;
  asm_code_stack_expand(out, size);
}

void asm_queue_code_stack_reduce(FILE *out, int size)
{
  if (enqueue_value(QENTRY_TYPE_CODE_STACK_REDUCE, out, size) > 0)
    return;
  asm_code_stack_reduce(out, size);
}

void asm_queue_code_funccall_reg_load(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_FUNCCALL_REG_LOAD, out, number) > 0)
    return;
  asm_code_funccall_reg_load(out, number);
}

void asm_queue_code_funccall_reg_store(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_FUNCCALL_REG_STORE, out, number) > 0)
    return;
  asm_code_funccall_reg_store(out, number);
}

void asm_queue_code_tmp_reg_load(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_TMP_REG_LOAD, out, number) > 0)
    return;
  asm_code_tmp_reg_load(out, number);
}

void asm_queue_code_tmp_reg_save(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_TMP_REG_SAVE, out, number) > 0)
    return;
  asm_code_tmp_reg_save(out, number);
}

void asm_queue_code_tmp_reg_load_r1(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_TMP_REG_LOAD_R1, out, number) > 0)
    return;
  asm_code_tmp_reg_load_r1(out, number);
}

void asm_queue_code_tmp_reg_save_r1(FILE *out, int number)
{
  if (enqueue_value(QENTRY_TYPE_CODE_TMP_REG_SAVE_R1, out, number) > 0)
    return;
  asm_code_tmp_reg_save_r1(out, number);
}

int asm_queue_code_sign_extension_char(FILE *out)
{
  if (asm_code_sign_extension_char(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_CHAR, out) > 0)
    return 0;
  return asm_code_sign_extension_char(out);
}

int asm_queue_code_sign_extension_uchar(FILE *out)
{
  if (asm_code_sign_extension_uchar(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_UCHAR, out) > 0)
    return 0;
  return asm_code_sign_extension_uchar(out);
}

int asm_queue_code_sign_extension_short(FILE *out)
{
  if (asm_code_sign_extension_short(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_SHORT, out) > 0)
    return 0;
  return asm_code_sign_extension_short(out);
}

int asm_queue_code_sign_extension_ushort(FILE *out)
{
  if (asm_code_sign_extension_ushort(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_USHORT, out) > 0)
    return 0;
  return asm_code_sign_extension_ushort(out);
}

int asm_queue_code_sign_extension_int(FILE *out)
{
  if (asm_code_sign_extension_int(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_INT, out) > 0)
    return 0;
  return asm_code_sign_extension_int(out);
}

int asm_queue_code_sign_extension_uint(FILE *out)
{
  if (asm_code_sign_extension_uint(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_SIGN_EXTENSION_UINT, out) > 0)
    return 0;
  return asm_code_sign_extension_uint(out);
}

int asm_queue_code_calc_inv(FILE *out, model_t model, model_t model_arg0)
{
  if (asm_code_calc_inv(NULL, model, model_arg0) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_INV, out, 0, model, model_arg0, NULL) > 0)
    return 0;
  return asm_code_calc_inv(out, model, model_arg0);
}

int asm_queue_code_calc_minus(FILE *out, model_t model, model_t model_arg0)
{
  if (asm_code_calc_minus(NULL, model, model_arg0) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_MINUS, out, 0, model, model_arg0, NULL) > 0)
    return 0;
  return asm_code_calc_minus(out, model, model_arg0);
}

int asm_queue_code_calc_op1(FILE *out, c_type_t type, model_t model, model_t model_arg0)
{
  if (asm_code_calc_op1(NULL, type, model, model_arg0) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_OP1, out, type, model, model_arg0, NULL) > 0)
    return 0;
  return asm_code_calc_op1(out, type, model, model_arg0);
}

void asm_queue_code_calc_add(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_ADD, out, 0, model, model_arg0, model_arg1) > 0)
    return;
  asm_code_calc_add(out, model, model_arg0, model_arg1);
}

void asm_queue_code_calc_sub(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_SUB, out, 0, model, model_arg0, model_arg1) > 0)
    return;
  asm_code_calc_sub(out, model, model_arg0, model_arg1);
}

void asm_queue_code_calc_and(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_AND, out, 0, model, model_arg0, model_arg1) > 0)
    return;
  asm_code_calc_and(out, model, model_arg0, model_arg1);
}

void asm_queue_code_calc_or(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_OR, out, 0, model, model_arg0, model_arg1) > 0)
    return;
  asm_code_calc_or(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_xor(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_xor(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_XOR, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_xor(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_mul(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_mul(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_MUL, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_mul(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_div(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_div(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_DIV, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_div(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_mod(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_mod(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_MOD, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_mod(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_llshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_llshift(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_LLSHIFT, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_llshift(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_rashift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_rashift(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_RASHIFT, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_rashift(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_rlshift(FILE *out, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_rlshift(NULL, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_RLSHIFT, out, 0, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_rlshift(out, model, model_arg0, model_arg1);
}

int asm_queue_code_calc_op2(FILE *out, c_type_t type, model_t model, model_t model_arg0, model_t model_arg1)
{
  if (asm_code_calc_op2(NULL, type, model, model_arg0, model_arg1) < 0)
    return -1;
  if (enqueue_args(QENTRY_TYPE_CODE_CALC_OP2, out, type, model, model_arg0, model_arg1) > 0)
    return 0;
  return asm_code_calc_op2(out, type, model, model_arg0, model_arg1);
}

void asm_queue_code_branch(FILE *out, char *label)
{
  if (enqueue_name(QENTRY_TYPE_CODE_BRANCH, out, label) > 0)
    return;
  asm_code_branch(out, label);
}

int asm_queue_code_branch_zero(FILE *out, char *label)
{
  if (asm_code_branch_zero(NULL, label) < 0)
    return -1;
  if (enqueue_name(QENTRY_TYPE_CODE_BRANCH_ZERO, out, label) > 0)
    return 0;
  return asm_code_branch_zero(out, label);
}

int asm_queue_code_branch_nzero(FILE *out, char *label)
{
  if (asm_code_branch_nzero(NULL, label) < 0)
    return -1;
  if (enqueue_name(QENTRY_TYPE_CODE_BRANCH_NZERO, out, label) > 0)
    return 0;
  return asm_code_branch_nzero(out, label);
}

int asm_queue_code_branch_cmp_eq(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_eq(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_EQ, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_eq(out, label, model);
}

int asm_queue_code_branch_cmp_ne(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_ne(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_NE, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_ne(out, label, model);
}

int asm_queue_code_branch_cmp_lt(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_lt(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_LT, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_lt(out, label, model);
}

int asm_queue_code_branch_cmp_gt(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_gt(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_GT, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_gt(out, label, model);
}

int asm_queue_code_branch_cmp_le(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_le(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_LE, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_le(out, label, model);
}

int asm_queue_code_branch_cmp_ge(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_ge(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_GE, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_ge(out, label, model);
}

int asm_queue_code_branch_cmp_ult(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_ult(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_ULT, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_ult(out, label, model);
}

int asm_queue_code_branch_cmp_ugt(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_ugt(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_UGT, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_ugt(out, label, model);
}

int asm_queue_code_branch_cmp_ule(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_ule(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_ULE, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_ule(out, label, model);
}

int asm_queue_code_branch_cmp_uge(FILE *out, char *label, model_t model)
{
  if (asm_code_branch_cmp_uge(NULL, label, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP_UGE, out, 0, label, model) > 0)
    return 0;
  return asm_code_branch_cmp_uge(out, label, model);
}

int asm_queue_code_branch_cmp(FILE *out, char *label, c_type_t type, model_t model)
{
  if (asm_code_branch_cmp(NULL, label, type, model) < 0)
    return -1;
  if (enqueue_model(QENTRY_TYPE_CODE_BRANCH_CMP, out, type, label, model) > 0)
    return 0;
  return asm_code_branch_cmp(out, label, type, model);
}

int asm_queue_code_function_call(FILE *out, char *label)
{
  if (asm_code_function_call(NULL, label) < 0)
    return -1;
  if (enqueue_name(QENTRY_TYPE_CODE_FUNCTION_CALL, out, label) > 0)
    return 0;
  return asm_code_function_call(out, label);
}

int asm_queue_code_function_call_set(FILE *out)
{
  if (asm_code_function_call_set(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_CALL_SET, out) > 0)
    return 0;
  return asm_code_function_call_set(out);
}

int asm_queue_code_function_call_pointer(FILE *out)
{
  if (asm_code_function_call_pointer(NULL) < 0)
    return -1;
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_CALL_POINTER, out) > 0)
    return 0;
  return asm_code_function_call_pointer(out);
}

void asm_queue_code_function_start(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_START, out) > 0)
    return;
  asm_code_function_start(out);
}

void asm_queue_code_function_end(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_END, out) > 0)
    return;
  asm_code_function_end(out);
}

void asm_queue_code_function_register_save(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_REGISTER_SAVE, out) > 0)
    return;
  asm_code_function_register_save(out);
}

void asm_queue_code_function_register_load(FILE *out)
{
  if (enqueue_null(QENTRY_TYPE_CODE_FUNCTION_REGISTER_LOAD, out) > 0)
    return;
  asm_code_function_register_load(out);
}

/* asm queue service */

typedef enum {
  QENTRY_OPTION_TYPE_NONE = 0,
  QENTRY_OPTION_TYPE_COMMENT,
  QENTRY_OPTION_TYPE_ARGS,
} qentry_option_type_t;

typedef union _qentry_option {
  struct {
    char *param1;
    char *param2;
    char *param3;
  } comment;
  struct {
    model_t arg0;
    model_t arg1;
  } args;
} *qentry_option_t;

typedef struct _qentry {
  struct objentry entry;
  qentry_type_t type;
  qentry_option_type_t option_type;

  FILE *out;

  long number;
  int value;
  char *name;
  char *string;
  model_t model;

  qentry_option_t option;
} *qentry_t;

static int asm_queue_level = 0;
static objlist_t asm_queue = NULL;

int asm_queue_set_level(int level)
{
  int old;

  if (level && (asm_queue == NULL))
    asm_queue = objlist_create(NULL);

  old = asm_queue_level;
  asm_queue_level = level;

  return old;
}

static qentry_t qentry_destroy(qentry_t qentry)
{
  if (qentry != NULL) {
    if (qentry->name != NULL)
      free(qentry->name);
    if (qentry->string != NULL)
      free(qentry->string);
    if (qentry->model)
      model_destroy(qentry->model);

    if (qentry->option != NULL) {
      switch (qentry->option_type) {
      case QENTRY_OPTION_TYPE_COMMENT:
	if (qentry->option->comment.param1 != NULL) free(qentry->option->comment.param1);
	if (qentry->option->comment.param2 != NULL) free(qentry->option->comment.param2);
	if (qentry->option->comment.param3 != NULL) free(qentry->option->comment.param3);
	break;

      case QENTRY_OPTION_TYPE_ARGS:
	if (qentry->option->args.arg0) model_destroy(qentry->option->args.arg0);
	if (qentry->option->args.arg1) model_destroy(qentry->option->args.arg1);
	break;

      case QENTRY_OPTION_TYPE_NONE:
      default:
	break;
      }
    }

    objentry_set_free_func(&qentry->entry, NULL);
    if (objentry_done(&qentry->entry) != NULL) {
      /* fail to done */
    }
    memset(qentry, 0, sizeof(*qentry));
    free(qentry);
  }

  return NULL;
}

static void qentry_free(void *p)
{
  qentry_destroy((qentry_t)p);
}

static qentry_t qentry_create(qentry_type_t type, FILE *out,
			      long number, int value, char *name, char *string, model_t model,
			      qentry_option_type_t option_type, qentry_option_t option)
{
  qentry_t qentry;
  int s;

  s = sizeof(*qentry);
  s += option ? sizeof(*option) : 0;

  qentry = malloc(s);
  if (qentry == NULL)
    goto err;
  memset(qentry, 0, s);

  if (objentry_init(&qentry->entry, qentry, 0, qentry_free) == NULL)
    goto err;

  if (option != NULL) {
    qentry->option = (qentry_option_t)(qentry + 1);
  } else {
    qentry->option = NULL;
  }

  if (name != NULL) {
    qentry->name = strdup(name);
    if (qentry->name == NULL)
      goto err;
  } else {
    qentry->name = NULL;
  }

  if (string != NULL) {
    qentry->string = strdup(string);
    if (qentry->string == NULL)
      goto err;
  } else {
    qentry->string = NULL;
  }

  if (model != NULL) {
    qentry->model = model_copy(model);
    if (qentry->model == NULL)
      goto err;
  } else {
    qentry->model = NULL;
  }

  qentry->type        = type;
  qentry->option_type = option_type;

  qentry->out    = out;
  qentry->number = number;
  qentry->value  = value;

  if (option != NULL) {
    switch (option_type) {
    case QENTRY_OPTION_TYPE_COMMENT:
      if (option->comment.param1 != NULL) {
	qentry->option->comment.param1 = strdup(option->comment.param1);
	if (qentry->option->comment.param1 == NULL)
	  goto err;
      } else {
	qentry->option->comment.param1 = NULL;
      }

      if (option->comment.param2 != NULL) {
	qentry->option->comment.param2 = strdup(option->comment.param2);
	if (qentry->option->comment.param2 == NULL)
	  goto err;
      } else {
	qentry->option->comment.param2 = NULL;
      }

      if (option->comment.param3 != NULL) {
	qentry->option->comment.param3 = strdup(option->comment.param3);
	if (qentry->option->comment.param3 == NULL)
	  goto err;
      } else {
	qentry->option->comment.param3 = NULL;
      }
      break;

    case QENTRY_OPTION_TYPE_ARGS:
      if (option->args.arg0 != NULL) {
	qentry->option->args.arg0 = model_copy(option->args.arg0);
	if (qentry->option->args.arg0 == NULL)
	  goto err;
      } else {
	qentry->option->args.arg0 = NULL;
      }

      if (option->args.arg1 != NULL) {
	qentry->option->args.arg1 = model_copy(option->args.arg1);
	if (qentry->option->args.arg1 == NULL)
	  goto err;
      } else {
	qentry->option->args.arg1 = NULL;
      }
      break;

    case QENTRY_OPTION_TYPE_NONE:
    default:
      break;
    }
  }

  return qentry;

err:
  if (qentry != NULL)
    qentry_destroy(qentry);
  return NULL;
}

static int _enqueue(qentry_type_t type, FILE *out,
		    long number, int value, char *name, char *string, model_t model,
		    qentry_option_type_t option_type, qentry_option_t option)
{
  qentry_t qentry;

  if (!asm_queue_level)
    return 0;

  qentry = qentry_create(type, out, number, value, name, string, model,
			 option_type, option);
  if (qentry == NULL)
    goto err;

  objlist_insert_tail(asm_queue, &qentry->entry);

  return 1;

err:
  return -1;
}

static int enqueue(qentry_type_t type, FILE *out, long number, int value, char *name)
{
  return _enqueue(type, out, number, value, name, NULL, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_null(qentry_type_t type, FILE *out)
{
  return _enqueue(type, out, 0, 0, NULL, NULL, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_number(qentry_type_t type, FILE *out, long number)
{
  return _enqueue(type, out, number, 0, NULL, NULL, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_value(qentry_type_t type, FILE *out, int value)
{
  return _enqueue(type, out, 0, value, NULL, NULL, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_name(qentry_type_t type, FILE *out, char *name)
{
  return _enqueue(type, out, 0, 0, name, NULL, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_string(qentry_type_t type, FILE *out,
			  int value, char *name, char *string)
{
  return _enqueue(type, out, 0, value, name, string, NULL,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_model(qentry_type_t type, FILE *out,
			 int value, char *name, model_t model)
{
  return _enqueue(type, out, 0, value, name, NULL, model,
		  QENTRY_OPTION_TYPE_NONE, NULL);
}

static int enqueue_comment(qentry_type_t type, FILE *out, char *comment,
			   char *param1, char *param2, char *param3)
{
  union _qentry_option option;
  memset(&option, 0, sizeof(option));
  option.comment.param1 = param1;
  option.comment.param2 = param2;
  option.comment.param3 = param3;
  return _enqueue(type, out, 0, 0, NULL, comment, NULL,
		  QENTRY_OPTION_TYPE_COMMENT, &option);
}

static int enqueue_args(qentry_type_t type, FILE *out, int value, model_t model,
			model_t model_arg0, model_t model_arg1)
{
  union _qentry_option option;
  memset(&option, 0, sizeof(option));
  option.args.arg0 = model_arg0;
  option.args.arg1 = model_arg1;
  return _enqueue(type, out, 0, value, NULL, NULL, model,
		  QENTRY_OPTION_TYPE_ARGS, &option);
}

static qentry_t dequeue(void)
{
  objentry_t entry;

  entry = objlist_extract_head(asm_queue);
  if (entry == NULL)
    return NULL;

  return (qentry_t)objentry_get_obj(entry);
}

static int optimize_delete_comment(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if (qentry->type == QENTRY_TYPE_FORMAT_COMMENT) {
      objlist_extract(queue, entry);
      qentry_destroy(qentry);
      opt = 1;
      continue;
    }
  }

  return opt;
}

static int optimize_delete_same_section(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry;
  asm_format_type_t type = ASM_FORMAT_TYPE_NONE;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if (qentry->type == QENTRY_TYPE_FORMAT_SECTION) {
      if (qentry->value == type) {
	objlist_extract(queue, entry);
	qentry_destroy(qentry);
	opt = 1;
	continue;
      } else {
	type = qentry->value;
      }
    }
  }

  return opt;
}

static int optimize_delete_waste_align(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry;
  int size = 0, s, opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if (qentry->type == QENTRY_TYPE_FORMAT_ALIGN) {
      if ((size > 0) && (size == qentry->value)) {
	objlist_extract(queue, entry);
	qentry_destroy(qentry);
	opt = 1;
	continue;
      }
      size = qentry->value;
    } else if (qentry->type == QENTRY_TYPE_FORMAT_VALUE) {
      switch (qentry->value) {
      case ASM_FORMAT_TYPE_VALUE_BYTE:  s = 1; break;
      case ASM_FORMAT_TYPE_VALUE_SHORT: s = 2; break;
      case ASM_FORMAT_TYPE_VALUE_INT:   s = 4; break;
      case ASM_FORMAT_TYPE_VALUE_QUAD:  s = 8; break;
      default:                          s = 0; break;
      }
      if (size != s)
	size = 0;
    } else {
      size = 0;
    }
  }

  return opt;
}

static int optimize_delete_nop(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if ((qentry->type == QENTRY_TYPE_CODE_ADD_ADDRESS) &&
	(qentry->value == 0))
      goto delete;

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry);
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_exchange_op(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);
    if (objlist_is_end(queue, nentry))
      continue;

    qentry1 = objentry_get_obj(nentry);
    info1 = &qentry_infos[qentry1->type];

    if ((info0->flags | info1->flags) & QENTRY_INFO_FLAG_BREAK)
      continue;

    if ((qentry0->type == QENTRY_TYPE_CODE_GET_ADDRESS_STACK) &&
	(qentry1->type == QENTRY_TYPE_CODE_SET_R1)) {
      qentry0->type = QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1;
      qentry1->type = QENTRY_TYPE_CODE_GET_R1;
      opt = 1;
      continue;
    }
  }

  return opt;
}

static int optimize_delete_waste_label(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry;
  int opt = 0, n, max = -1, len;
  char *label;
  unsigned int *found;

  len = strlen(DEFAULT_LABEL_PREFIX);

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if ((qentry->type == QENTRY_TYPE_FORMAT_LABEL) &&
	!strncmp(qentry->name, DEFAULT_LABEL_PREFIX, len)) {
      n = atoi(qentry->name + len);
      if ((max < 0) || (max < n))
	max = n;
    }
  }

  if (max < 0)
    return 0;

  n = ((max + 1) + sizeof(*found) - 1) / sizeof(*found);
  found = malloc(sizeof(*found) * n);
  memset(found, 0, sizeof(*found) * n);

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    switch (qentry->type) {
    case QENTRY_TYPE_FORMAT_GLOBL:        label = qentry->name; break;
#if 1
    case QENTRY_TYPE_FORMAT_TYPE:         label = qentry->name; break;
    case QENTRY_TYPE_FORMAT_SIZE:         label = qentry->name; break;
#endif
    case QENTRY_TYPE_CODE_GET_ADDRESS:    label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH:         label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_ZERO:    label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_NZERO:   label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_EQ:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_NE:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_LT:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_GT:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_LE:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_GE:  label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_ULT: label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_UGT: label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_ULE: label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP_UGE: label = qentry->name; break;
    case QENTRY_TYPE_CODE_BRANCH_CMP:     label = qentry->name; break;
    case QENTRY_TYPE_CODE_FUNCTION_CALL:  label = qentry->name; break;
    case QENTRY_TYPE_FORMAT_LABEL:
    default:
      label = NULL;
      break;
    }

    if (label && !strncmp(label, DEFAULT_LABEL_PREFIX, len)) {
      n = atoi(label + len);
      found[n / sizeof(*found)] |= (1 << (n % sizeof(*found)));
    }
  }

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry = objentry_get_obj(entry);
    nentry = objentry_get_next(entry);

    if ((qentry->type == QENTRY_TYPE_FORMAT_LABEL) &&
	!strncmp(qentry->name, DEFAULT_LABEL_PREFIX, len)) {
      n = atoi(qentry->name + len);
      if (!(found[n / sizeof(*found)] & (1 << (n % sizeof(*found))))) {
	objlist_extract(queue, entry);
	qentry_destroy(qentry);
	opt = 1;
	continue;
      }
    }
  }

  free(found);

  return opt;
}

static int optimize_delete_waste_code(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);
    if (objlist_is_end(queue, nentry))
      continue;

    qentry1 = objentry_get_obj(nentry);
    info1 = &qentry_infos[qentry1->type];

    if ((info0->flags | info1->flags) & QENTRY_INFO_FLAG_BREAK)
      continue;

    if (((qentry0->type == QENTRY_TYPE_CODE_MEMORY_LOAD) ||
	 (qentry0->type == QENTRY_TYPE_CODE_MEMORY_STORE)) &&
	((qentry1->type == QENTRY_TYPE_CODE_MEMORY_LOAD) ||
	 (qentry1->type == QENTRY_TYPE_CODE_MEMORY_STORE)) &&
	(qentry0->value == qentry1->value) &&
	(qentry0->model->type == qentry1->model->type))
      goto delete_next;

    if ((qentry0->type == QENTRY_TYPE_CODE_ADD_ADDRESS) &&
	(qentry1->type == QENTRY_TYPE_CODE_ADD_ADDRESS)) {
      qentry0->value += qentry1->value;
      goto delete_next;
    }

    if (qentry0->type == QENTRY_TYPE_CODE_GET_VALUE) {
      if (info1->flags & QENTRY_INFO_FLAG_EXTN) {
	if ((qentry0->number >= 0) && (qentry0->number < 0x80))
	  goto delete_next;
      }
    }

    continue;

delete_next:
    objlist_extract(queue, nentry);
    qentry_destroy(qentry1);
    nentry = entry;
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_code2(objlist_t queue)
{
  objentry_t entry, nentry, n2entry;
  qentry_t qentry0, qentry1, qentry2;
  qentry_info_t info0, info1, info2;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);
    if (objlist_is_end(queue, nentry))
      continue;

    qentry1 = objentry_get_obj(nentry);
    info1 = &qentry_infos[qentry1->type];

    if ((info0->flags | info1->flags) & QENTRY_INFO_FLAG_BREAK)
      continue;

    n2entry = objentry_get_next(nentry);
    if (objlist_is_end(queue, n2entry))
      continue;

    qentry2 = objentry_get_obj(n2entry);
    info2 = &qentry_infos[qentry2->type];

    if ((qentry0->type == QENTRY_TYPE_CODE_BRANCH_ZERO) &&
	(qentry1->type == QENTRY_TYPE_CODE_BRANCH) &&
	(qentry2->type == QENTRY_TYPE_FORMAT_LABEL) &&
	!strcmp(qentry0->name, qentry2->name)) {
      qentry1->type = QENTRY_TYPE_CODE_BRANCH_NZERO;
      goto delete;
    }

    if (info2->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if ((qentry0->type == QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1) &&
	!(info1->flags & QENTRY_INFO_FLAG_R1WR) &&
	!(info1->flags & (QENTRY_INFO_FLAG_SPRD|QENTRY_INFO_FLAG_SPWR)) &&
	(qentry2->type == QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1) &&
	(qentry0->value == qentry2->value))
      goto delete_next2;

    if ((qentry0->type == QENTRY_TYPE_CODE_STACK_LOAD) &&
	(qentry1->type == QENTRY_TYPE_CODE_SET_R1) &&
	(info2->flags & QENTRY_INFO_FLAG_R0WR)) {
      qentry0->type = QENTRY_TYPE_CODE_STACK_LOAD_R1;
      goto delete_next;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;

delete_next:
    objlist_extract(queue, nentry);
    qentry_destroy(qentry1);
    nentry = entry;
    opt = 1;
    continue;

delete_next2:
    objlist_extract(queue, n2entry);
    qentry_destroy(qentry2);
    nentry = entry;
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_jump(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (!(info0->flags & QENTRY_INFO_FLAG_JUMP))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);

      if (qentry1->type != QENTRY_TYPE_FORMAT_LABEL)
	break;
      if (!strcmp(qentry0->name, qentry1->name))
	goto delete;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_write_r0(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (info0->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if (!(info0->flags & QENTRY_INFO_FLAG_R0WR))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);
      info1 = &qentry_infos[qentry1->type];

      if (info1->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (info1->flags & QENTRY_INFO_FLAG_R0RD)
	break;
      if (info1->flags & QENTRY_INFO_FLAG_R0WR)
	goto delete;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_write_r1(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (info0->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if (!(info0->flags & QENTRY_INFO_FLAG_R1WR))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);
      info1 = &qentry_infos[qentry1->type];

      if (info1->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (info1->flags & QENTRY_INFO_FLAG_R1RD)
	break;
      if (info1->flags & QENTRY_INFO_FLAG_R1WR)
	goto delete;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_access_tmp(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (info0->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if ((qentry0->type != QENTRY_TYPE_CODE_TMP_REG_LOAD) &&
	(qentry0->type != QENTRY_TYPE_CODE_TMP_REG_SAVE))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);
      info1 = &qentry_infos[qentry1->type];

      if (info1->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (((qentry1->type == QENTRY_TYPE_CODE_TMP_REG_LOAD) ||
	   (qentry1->type == QENTRY_TYPE_CODE_TMP_REG_SAVE)) &&
	  (qentry0->value == qentry1->value))
	goto delete_next;
      if (info1->flags & QENTRY_INFO_FLAG_R0WR)
	break;
    }

    continue;

delete_next:
    objlist_extract(queue, nentry);
    qentry_destroy(qentry1);
    nentry = entry;
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_write_tmp(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (info0->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if ((qentry0->type != QENTRY_TYPE_CODE_TMP_REG_SAVE) &&
	(qentry0->type != QENTRY_TYPE_CODE_TMP_REG_SAVE_R1))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);
      info1 = &qentry_infos[qentry1->type];

      if (info1->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (((qentry1->type == QENTRY_TYPE_CODE_TMP_REG_LOAD) ||
	   (qentry1->type == QENTRY_TYPE_CODE_TMP_REG_LOAD_R1)) &&
	  (qentry0->value == qentry1->value))
	break;
      if (((qentry1->type == QENTRY_TYPE_CODE_TMP_REG_SAVE) ||
	   (qentry1->type == QENTRY_TYPE_CODE_TMP_REG_SAVE_R1)) &&
	  (qentry0->value == qentry1->value))
	goto delete;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_extension(objlist_t queue)
{
  objentry_t entry, nentry;
  qentry_t qentry0, qentry1;
  qentry_info_t info0, info1;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);

    if (info0->flags & QENTRY_INFO_FLAG_BREAK)
      continue;

    if (!(info0->flags & QENTRY_INFO_FLAG_EXTN))
      continue;

    for (; !objlist_is_end(queue, nentry);
	 nentry = objentry_get_next(nentry)) {
      qentry1 = objentry_get_obj(nentry);
      info1 = &qentry_infos[qentry1->type];

      if (info1->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (qentry1->type == qentry0->type)
	goto delete_next;
      if (info1->flags & QENTRY_INFO_FLAG_R0WR)
	break;
    }

    continue;

delete_next:
    objlist_extract(queue, nentry);
    qentry_destroy(qentry1);
    nentry = entry;
    opt = 1;
    continue;
  }

  return opt;
}

static int optimize_delete_waste_load_store(objlist_t queue)
{
  objentry_t entry, nentry, n2entry;
  qentry_t qentry0, qentry1, qentry2;
  qentry_info_t info0, info1, info2;
  int opt = 0;

  for (entry = objlist_get_head(queue);
       !objlist_is_end(queue, entry);
       entry = nentry) {
    qentry0 = objentry_get_obj(entry);
    info0 = &qentry_infos[qentry0->type];

    nentry = objentry_get_next(entry);
    if (objlist_is_end(queue, nentry))
      continue;

    qentry1 = objentry_get_obj(nentry);
    info1 = &qentry_infos[qentry1->type];

    if ((info0->flags | info1->flags) & QENTRY_INFO_FLAG_BREAK)
      continue;

    if ((qentry0->type != QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1) ||
	((qentry1->type != QENTRY_TYPE_CODE_MEMORY_LOAD) &&
	 (qentry1->type != QENTRY_TYPE_CODE_MEMORY_STORE)) ||
	(qentry1->value != 0))
      continue;

    for (n2entry = objentry_get_next(nentry);
	 !objlist_is_end(queue, n2entry);
	 n2entry = objentry_get_next(n2entry)) {
      qentry2 = objentry_get_obj(n2entry);
      info2 = &qentry_infos[qentry2->type];

      if (info2->flags & QENTRY_INFO_FLAG_BREAK)
	break;
      if (!(info2->flags & QENTRY_INFO_FLAG_R1RD) &&
	  (info2->flags & QENTRY_INFO_FLAG_R1WR)) {
	switch (qentry1->type) {
	case QENTRY_TYPE_CODE_MEMORY_LOAD:
	  qentry1->type = QENTRY_TYPE_CODE_STACK_LOAD;
	  break;
	case QENTRY_TYPE_CODE_MEMORY_STORE:
	  qentry1->type = QENTRY_TYPE_CODE_STACK_STORE;
	  break;
	default:
	  ERREXIT(1);
	}
	qentry1->value = qentry0->value;
	goto delete;
      }
      if (info2->flags & (QENTRY_INFO_FLAG_R1RD|QENTRY_INFO_FLAG_R1WR))
	break;
    }

    continue;

delete:
    objlist_extract(queue, entry);
    qentry_destroy(qentry0);
    opt = 1;
    continue;
  }

  return opt;
}

static int type_check()
{
  qentry_type_t type;
  qentry_info_t info;

  for (type = 0; type < QENTRY_TYPE_NUM; type++) {
    info = &qentry_infos[type];
    if (info->type != type)
      return -1;
  }

  return 0;
}

static int optimize(objlist_t queue)
{
  if (type_check() < 0)
    ERREXIT(1);

  if (asm_queue_level > 0) {
    optimize_delete_comment(queue);
    optimize_delete_same_section(queue);
    optimize_delete_waste_align(queue);
  }

  if (asm_queue_level > 1) {
    while (1) {
      if (optimize_delete_nop(queue))
	continue;
      if (optimize_exchange_op(queue))
	continue;
      if (optimize_delete_waste_label(queue))
	continue;
      if (optimize_delete_waste_code(queue))
	continue;
      if (optimize_delete_waste_code2(queue))
	continue;
      if (optimize_delete_waste_jump(queue))
	continue;
      if (optimize_delete_waste_write_r0(queue))
	continue;
      if (optimize_delete_waste_write_r1(queue))
	continue;
      if (optimize_delete_waste_access_tmp(queue))
	continue;
      if (optimize_delete_waste_write_tmp(queue))
	continue;
      if (optimize_delete_waste_extension(queue))
	continue;
      if (optimize_delete_waste_load_store(queue))
	continue;
      break;
    }
  }

  return 0;
}

int asm_queue_flush(void)
{
  qentry_t qentry;
  int level;

  if (asm_queue == NULL)
    return -1;

  optimize(asm_queue);

  level = asm_queue_set_level(0);

  while ((qentry = dequeue()) != NULL) {
    switch (qentry->type) {

    /* asm_format_* */

    case QENTRY_TYPE_FORMAT_COMMENT:
      asm_queue_format_comment(qentry->out, qentry->string,
			       qentry->option->comment.param1,
			       qentry->option->comment.param2,
			       qentry->option->comment.param3);
      break;

    case QENTRY_TYPE_FORMAT_NEWLINE:
      asm_queue_format_newline(qentry->out);
      break;

    case QENTRY_TYPE_FORMAT_ALIGN:
      asm_queue_format_align(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_FORMAT_SPACE:
      asm_queue_format_space(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_FORMAT_LABEL:
      asm_queue_format_label(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_FORMAT_GLOBL:
      asm_queue_format_globl(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_FORMAT_TYPE:
      asm_queue_format_type(qentry->out, qentry->name, qentry->string, qentry->value);
      break;

    case QENTRY_TYPE_FORMAT_SIZE:
      asm_queue_format_size(qentry->out, qentry->name, qentry->value);
      break;

    case QENTRY_TYPE_FORMAT_DUMMY:
      asm_queue_format_dummy(qentry->out);
      break;

    case QENTRY_TYPE_FORMAT_STRING:
      asm_queue_format_string(qentry->out, qentry->string, qentry->value);
      break;

    case QENTRY_TYPE_FORMAT_VALUE:
      asm_queue_format_value(qentry->out, qentry->value, qentry->number, qentry->name);
      break;

    case QENTRY_TYPE_FORMAT_SECTION:
      asm_queue_format_section(qentry->out, qentry->value);
      break;

    /* asm_code_* */

    case QENTRY_TYPE_CODE_GET_VALUE:
      asm_queue_code_get_value(qentry->out, qentry->number);
      break;

    case QENTRY_TYPE_CODE_GET_VALUE_R1:
      asm_queue_code_get_value_r1(qentry->out, qentry->number);
      break;

    case QENTRY_TYPE_CODE_GET_ADDRESS_STACK:
      asm_queue_code_get_address_stack(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_GET_ADDRESS_STACK_R1:
      asm_queue_code_get_address_stack_r1(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_GET_ADDRESS:
      asm_queue_code_get_address(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_CODE_ADD_ADDRESS:
      asm_queue_code_add_address(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_GET_R1:
      asm_queue_code_get_r1(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SET_R1:
      asm_queue_code_set_r1(qentry->out);
      break;

    case QENTRY_TYPE_CODE_MEMORY_LOAD:
      asm_queue_code_memory_load(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_MEMORY_STORE:
      asm_queue_code_memory_store(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_STACK_LOAD:
      asm_queue_code_stack_load(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_STACK_STORE:
      asm_queue_code_stack_store(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_STACK_LOAD_R1:
      asm_queue_code_stack_load_r1(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_STACK_STORE_R1:
      asm_queue_code_stack_store_r1(qentry->out, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_STACK_EXPAND:
      asm_queue_code_stack_expand(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_STACK_REDUCE:
      asm_queue_code_stack_reduce(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_FUNCCALL_REG_LOAD:
      asm_queue_code_funccall_reg_load(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_FUNCCALL_REG_STORE:
      asm_queue_code_funccall_reg_store(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_TMP_REG_LOAD:
      asm_queue_code_tmp_reg_load(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_TMP_REG_SAVE:
      asm_queue_code_tmp_reg_save(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_TMP_REG_LOAD_R1:
      asm_queue_code_tmp_reg_load_r1(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_TMP_REG_SAVE_R1:
      asm_queue_code_tmp_reg_save_r1(qentry->out, qentry->value);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_CHAR:
      asm_queue_code_sign_extension_char(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_UCHAR:
      asm_queue_code_sign_extension_uchar(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_SHORT:
      asm_queue_code_sign_extension_short(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_USHORT:
      asm_queue_code_sign_extension_ushort(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_INT:
      asm_queue_code_sign_extension_int(qentry->out);
      break;

    case QENTRY_TYPE_CODE_SIGN_EXTENSION_UINT:
      asm_queue_code_sign_extension_uint(qentry->out);
      break;

    case QENTRY_TYPE_CODE_CALC_INV:
      asm_queue_code_calc_inv(qentry->out, qentry->model, qentry->option->args.arg0);
      break;

    case QENTRY_TYPE_CODE_CALC_MINUS:
      asm_queue_code_calc_minus(qentry->out, qentry->model, qentry->option->args.arg0);
      break;

    case QENTRY_TYPE_CODE_CALC_OP1:
      asm_queue_code_calc_op1(qentry->out, qentry->value, qentry->model, qentry->option->args.arg0);
      break;

    case QENTRY_TYPE_CODE_CALC_ADD:
      asm_queue_code_calc_add(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_SUB:
      asm_queue_code_calc_sub(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_AND:
      asm_queue_code_calc_and(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_OR:
      asm_queue_code_calc_or(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_XOR:
      asm_queue_code_calc_xor(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_MUL:
      asm_queue_code_calc_mul(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_DIV:
      asm_queue_code_calc_div(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_MOD:
      asm_queue_code_calc_mod(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_LLSHIFT:
      asm_queue_code_calc_llshift(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_RASHIFT:
      asm_queue_code_calc_rashift(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_RLSHIFT:
      asm_queue_code_calc_rlshift(qentry->out, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_CALC_OP2:
      asm_queue_code_calc_op2(qentry->out, qentry->value, qentry->model, qentry->option->args.arg0, qentry->option->args.arg1);
      break;

    case QENTRY_TYPE_CODE_BRANCH:
      asm_queue_code_branch(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_CODE_BRANCH_ZERO:
      asm_queue_code_branch_zero(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_CODE_BRANCH_NZERO:
      asm_queue_code_branch_nzero(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_EQ:
      asm_queue_code_branch_cmp_eq(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_NE:
      asm_queue_code_branch_cmp_ne(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_LT:
      asm_queue_code_branch_cmp_lt(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_GT:
      asm_queue_code_branch_cmp_gt(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_LE:
      asm_queue_code_branch_cmp_le(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_GE:
      asm_queue_code_branch_cmp_ge(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_ULT:
      asm_queue_code_branch_cmp_ult(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_UGT:
      asm_queue_code_branch_cmp_ugt(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_ULE:
      asm_queue_code_branch_cmp_ule(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP_UGE:
      asm_queue_code_branch_cmp_uge(qentry->out, qentry->name, qentry->model);
      break;

    case QENTRY_TYPE_CODE_BRANCH_CMP:
      asm_queue_code_branch_cmp(qentry->out, qentry->name, qentry->value, qentry->model);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_CALL:
      asm_queue_code_function_call(qentry->out, qentry->name);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_CALL_SET:
      asm_queue_code_function_call_set(qentry->out);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_CALL_POINTER:
      asm_queue_code_function_call_pointer(qentry->out);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_START:
      asm_queue_code_function_start(qentry->out);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_END:
      asm_queue_code_function_end(qentry->out);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_REGISTER_SAVE:
      asm_queue_code_function_register_save(qentry->out);
      break;

    case QENTRY_TYPE_CODE_FUNCTION_REGISTER_LOAD:
      asm_queue_code_function_register_load(qentry->out);
      break;

    case QENTRY_TYPE_NONE:
    default:
      break;
    }

    qentry_destroy(qentry);
  }

  asm_queue_set_level(level);

  return 0;
}
