/* -*- Mode: C; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/* vim: set filetype=c tabstop=2 shiftwidth=2 expandtab: */

/*
 * compiler.d - bytecode compiler
 *
 * Copyright (c) 2001 Juan Jose Garcia Ripoll
 *
 * See file 'LICENSE' for the copyright details.
 *
 */

/*  Remarks:

    [1] The virtual machine has a word size of 16 bits. Operands and arguments
    have this very size, so that for instance, a jump

    OP_JMP increment

    takes two words of memory: one for the operator and one for the argument.
    The interpreter is written with this assumption in mind, but it should be
    easily modified, because arguments are retrieved with "next_arg" and
    operators with "next_op".  Parts which will require a careful modification
    are marked with flag [1].
*/
#include <string.h>
#include <ecl/ecl.h>
#include <ecl/ecl-inl.h>
#include <ecl/internal.h>
#include <ecl/bytecodes.h>

/********************* EXPORTS *********************/

/* Flags for the compilation routines: */
/* + Push the output of this form */
#define FLAG_PUSH               1
/* + Set the output of this form in VALUES */
#define FLAG_VALUES             2
/* + Set the output of this form in REG0 */
#define FLAG_REG0               4
/* + Search function binding in the global environment */
#define FLAG_GLOBAL             8
/* + Ignore this form */
#define FLAG_IGNORE             0
#define FLAG_USEFUL             (FLAG_PUSH | FLAG_VALUES | FLAG_REG0)

#define FLAG_EXECUTE            16
#define FLAG_LOAD               32
#define FLAG_COMPILE            64

/********************* PRIVATE ********************/

typedef struct cl_compiler_env *cl_compiler_ptr;

#define asm_begin(env) current_pc(env)
#define current_pc(env) ECL_STACK_INDEX(env)
#define set_pc(env,n) asm_clear(env,n)
#define asm_ref(env,n) (cl_fixnum)((env)->run_stack.org[n])
static void asm_clear(cl_env_ptr env, cl_index h);
static void asm_op(cl_env_ptr env, cl_fixnum op);
static void asm_op2(cl_env_ptr env, int op, int arg);
static cl_object asm_end(cl_env_ptr env, cl_index handle, cl_object definition);
static cl_index asm_jmp(cl_env_ptr env, int op);
static void asm_complete(cl_env_ptr env, int op, cl_index original);

static struct cl_compiler_ref
c_var_ref(cl_env_ptr env, cl_object var, bool allow_sym_mac, bool ensure_def);

void c_sym_ref(cl_env_ptr env, cl_object name);
void c_mac_ref(cl_env_ptr env, cl_object name);

static int c_block(cl_env_ptr env, cl_object args, int flags);
static int c_case(cl_env_ptr env, cl_object args, int flags);
static int c_catch(cl_env_ptr env, cl_object args, int flags);
static int c_compiler_let(cl_env_ptr env, cl_object args, int flags);
static int c_cond(cl_env_ptr env, cl_object args, int flags);
static int c_eval_when(cl_env_ptr env, cl_object args, int flags);
static int c_flet(cl_env_ptr env, cl_object args, int flags);
static int c_funcall(cl_env_ptr env, cl_object args, int flags);
static int c_function(cl_env_ptr env, cl_object args, int flags);
static int c_go(cl_env_ptr env, cl_object args, int flags);
static int c_if(cl_env_ptr env, cl_object args, int flags);
static int c_labels(cl_env_ptr env, cl_object args, int flags);
static int c_let(cl_env_ptr env, cl_object args, int flags);
static int c_leta(cl_env_ptr env, cl_object args, int flags);
static int c_load_time_value(cl_env_ptr env, cl_object args, int flags);
static int c_locally(cl_env_ptr env, cl_object args, int flags);
static int c_macrolet(cl_env_ptr env, cl_object args, int flags);
static int c_multiple_value_bind(cl_env_ptr env, cl_object args, int flags);
static int c_multiple_value_call(cl_env_ptr env, cl_object args, int flags);
static int c_multiple_value_prog1(cl_env_ptr env, cl_object args, int flags);
static int c_multiple_value_setq(cl_env_ptr env, cl_object args, int flags);
static int c_not(cl_env_ptr env, cl_object args, int flags);
static int c_nth_value(cl_env_ptr env, cl_object args, int flags);
static int c_prog1(cl_env_ptr env, cl_object args, int flags);
static int c_progv(cl_env_ptr env, cl_object args, int flags);
static int c_psetq(cl_env_ptr env, cl_object args, int flags);
static int c_quote(cl_env_ptr env, cl_object args, int flags);
static int c_values(cl_env_ptr env, cl_object args, int flags);
static int c_setq(cl_env_ptr env, cl_object args, int flags);
static int c_return(cl_env_ptr env, cl_object args, int flags);
static int c_return_from(cl_env_ptr env, cl_object args, int flags);
static int c_symbol_macrolet(cl_env_ptr env, cl_object args, int flags);
static int c_tagbody(cl_env_ptr env, cl_object args, int flags);
static int c_the(cl_env_ptr env, cl_object args, int flags);
static int c_throw(cl_env_ptr env, cl_object args, int flags);
static int c_unwind_protect(cl_env_ptr env, cl_object args, int flags);
static int c_while(cl_env_ptr env, cl_object args, int flags);
static int c_with_backend(cl_env_ptr env, cl_object args, int flags);
static int c_until(cl_env_ptr env, cl_object args, int flags);
static void eval_form(cl_env_ptr env, cl_object form);
static int execute_each_form(cl_env_ptr env, cl_object body);
static int compile_toplevel_body(cl_env_ptr env, cl_object args, int flags);
static int compile_body(cl_env_ptr env, cl_object args, int flags);
static int compile_form(cl_env_ptr env, cl_object args, int push);
static int compile_with_load_time_forms(cl_env_ptr env, cl_object form, int flags);
static int compile_constant(cl_env_ptr env, cl_object stmt, int flags);
static void maybe_make_load_forms(cl_env_ptr env, cl_object constant);

static int c_cons(cl_env_ptr env, cl_object args, int push);
static int c_endp(cl_env_ptr env, cl_object args, int push);
static int c_car(cl_env_ptr env, cl_object args, int push);
static int c_cdr(cl_env_ptr env, cl_object args, int push);
static int c_list(cl_env_ptr env, cl_object args, int push);
static int c_listA(cl_env_ptr env, cl_object args, int push);
static int c_cons_car(cl_env_ptr env, cl_object args, int push);
static int c_cons_cdr(cl_env_ptr env, cl_object args, int push);

static cl_object ecl_make_lambda(cl_env_ptr env, cl_object name, cl_object lambda);

static void FEill_formed_input(void) ecl_attr_noreturn;

static int asm_function(cl_env_ptr env, cl_object args, int flags);

/* -------------------- SAFE LIST HANDLING -------------------- */
static cl_object
pop(cl_object *l) {
  cl_object head, list = *l;
  unlikely_if (ECL_ATOM(list))
    FEill_formed_input();
  head = ECL_CONS_CAR(list);
  *l = ECL_CONS_CDR(list);
  return head;
}

static cl_object
pop_maybe_nil(cl_object *l) {
  cl_object head, list = *l;
  if (list == ECL_NIL)
    return ECL_NIL;
  unlikely_if (!ECL_LISTP(list))
    FEill_formed_input();
  head = ECL_CONS_CAR(list);
  *l = ECL_CONS_CDR(list);
  return head;
}

static cl_object
push(cl_object v, cl_object *l) {
  cl_object list = *l;
  unlikely_if (!ECL_LISTP(list))
    FEill_formed_input();
  *l = ecl_cons(v, *l);
  return *l;
}

/* ------------------------------ ASSEMBLER ------------------------------ */

static cl_object
asm_end(cl_env_ptr env, cl_index beginning, cl_object definition) {
  const cl_compiler_ptr c_env = env->c_env;
  cl_object output;
  cl_index code_size, i;
  cl_opcode *code;
  cl_object file = ECL_SYM_VAL(env,@'ext::*source-location*'), position;
  if (Null(file)) {
    file = ECL_SYM_VAL(env,@'*load-truename*');
    position = ecl_make_fixnum(0);
  } else {
    position = cl_cdr(file);
    file = cl_car(file);
  }

  /* Save bytecodes from this session in a new vector */
  code_size = current_pc(env) - beginning;
  output = ecl_alloc_object(t_bytecodes);
  output->bytecodes.name = @'si::bytecodes';
  output->bytecodes.definition = definition;
  output->bytecodes.code_size = code_size;
  output->bytecodes.code = ecl_alloc_atomic(code_size * sizeof(cl_opcode));
  output->bytecodes.data = c_env->constants;
  output->bytecodes.flex = ECL_NIL;
  output->bytecodes.nlcl = ecl_make_fixnum(c_env->env_width);
  for (i = 0, code = (cl_opcode *)output->bytecodes.code; i < code_size; i++) {
    code[i] = (cl_opcode)(cl_fixnum)(env->run_stack.org[beginning+i]);
  }
  output->bytecodes.entry =  _ecl_bytecodes_dispatch_vararg;
  ecl_set_function_source_file_info(output, (file == OBJNULL)? ECL_NIL : file,
                                    (file == OBJNULL)? ECL_NIL : position);
  asm_clear(env, beginning);
  return output;
}

#define asm_arg(env,n) asm_op(env,n)

static void
asm_op(cl_env_ptr env, cl_fixnum code) {
  cl_object v = (cl_object)code;
  ECL_STACK_PUSH(env,v);
}

static void
asm_clear(cl_env_ptr env, cl_index h) {
  ECL_STACK_UNWIND(env, h);
}

static void
asm_op2(cl_env_ptr env, int code, int n) {
  if (ecl_unlikely(n < -MAX_OPARG || MAX_OPARG < n))
    FEprogram_error("Argument to bytecode is too large", 0);
  asm_op(env, code);
  asm_arg(env, n);
}

static cl_index
asm_constant(cl_env_ptr env, cl_object c)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object constants = c_env->constants;
  cl_vector_push_extend(2, c, constants);
  return constants->vector.fillp-1;
}

static cl_index
asm_captured(cl_env_ptr env, cl_object c)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object captured = c_env->captured;
  cl_vector_push_extend(2, c, captured);
  return captured->vector.fillp-1;
}

static cl_index
asm_jmp(cl_env_ptr env, int op) {
  cl_index output;
  asm_op(env, op);
  output = current_pc(env);
  asm_arg(env, 0);
  return output;
}

static void
asm_complete(cl_env_ptr env, int op, cl_index pc) {
  cl_fixnum delta = current_pc(env) - pc;  /* [1] */
  if (ecl_unlikely(op && (asm_ref(env, pc-1) != op)))
    FEprogram_error("Non matching codes in ASM-COMPLETE2", 0);
  else if (ecl_unlikely(delta < -MAX_OPARG || delta > MAX_OPARG))
    FEprogram_error("Too large jump", 0);
  else {
    env->run_stack.org[pc] = (cl_object)(cl_fixnum)delta;
  }
}

/* ------------------------------ COMPILER ------------------------------ */

typedef struct {
  void *symbol;
  int (*compiler)(cl_env_ptr, cl_object, int);
  int lexical_increment;
} compiler_record;

static compiler_record database[] = {
  {@'block', c_block, 1},
  {@'case', c_case, 1},
  {@'catch', c_catch, 1},
  {@'ext::compiler-let', c_compiler_let, 0},
  {@'cond', c_cond, 1},
  {@'eval-when', c_eval_when, 0},
  {@'flet', c_flet, 1},
  {@'function', c_function, 1},
  {@'funcall', c_funcall, 1},
  {@'go', c_go, 1},
  {@'if', c_if, 1},
  {@'labels', c_labels, 1},
  {@'let', c_let, 1},
  {@'let*', c_leta, 1},
  {@'locally', c_locally, 0},
  {@'load-time-value', c_load_time_value, 1},
  {@'macrolet', c_macrolet, 0},
  {@'multiple-value-bind', c_multiple_value_bind, 1},
  {@'multiple-value-call', c_multiple_value_call, 1},
  {@'multiple-value-prog1', c_multiple_value_prog1, 1},
  {@'multiple-value-setq', c_multiple_value_setq, 1},
  {@'not', c_not, 1},
  {@'nth-value', c_nth_value, 1},
  {@'null', c_not, 1},
  {@'progn', compile_toplevel_body, 0},
  {@'prog1', c_prog1, 1},
  {@'progv', c_progv, 1},
  {@'psetq', c_psetq, 1},
  {@'quote', c_quote, 1},
  {@'return', c_return, 1},
  {@'return-from', c_return_from, 1},
  {@'setq', c_setq, 1},
  {@'symbol-macrolet', c_symbol_macrolet, 0},
  {@'tagbody', c_tagbody, 1},
  {@'the', c_the, 0},
  {@'ext::truly-the', c_the, 0},
  {@'throw', c_throw, 1},
  {@'unwind-protect', c_unwind_protect, 1},
  {@'values', c_values, 1},
  {@'si::while', c_while, 0},
  {@'ext::with-backend', c_with_backend, 0},
  {@'si::until', c_until, 0},
  /* Inlined functions */
  {@'cons', c_cons, 1},
  {@'car', c_car, 1},
  {@'cdr', c_cdr, 1},
  {@'first', c_car, 1},
  {@'rest', c_cdr, 1},
  {@'list', c_list, 1},
  {@'list*', c_listA, 1},
  {@'endp', c_endp, 1},
  /* Primops */
  {@'si::cons-car', c_cons_car, 1},
  {@'si::cons-cdr', c_cons_cdr, 1},

  {NULL, NULL, 1}
};

/* ----------------- LEXICAL ENVIRONMENT HANDLING -------------------- */

static void
assert_type_symbol(cl_object v)
{
  if (ecl_t_of(v) != t_symbol)
    FEprogram_error("Expected a symbol, found ~S.", 1, v);
}

static void
FEill_formed_input()
{
  FEprogram_error("Syntax error: list with too few elements or improperly terminated.", 0);
}

static int
c_search_constant(cl_env_ptr env, cl_object c)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object p = c_env->constants;
  int n;
  for (n = 0; n < p->vector.fillp; n++) {
    if (ecl_eql(p->vector.self.t[n], c)) {
      return n;
    }
  }
  return -1;
}

static int
c_register_constant(cl_env_ptr env, cl_object c)
{
  int n = c_search_constant(env, c);
  return (n < 0) ? asm_constant(env, c) : n;
}

static void
asm_arg_data(cl_env_ptr env, cl_object o) {
  asm_arg(env, c_register_constant(env, o));
}

static void
asm_op2c(cl_env_ptr env, int code, cl_object o) {
  asm_op2(env, code, c_register_constant(env, o));
}

/* Captured variables */
static int
c_search_captured(cl_env_ptr env, cl_object c)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object p = c_env->captured;
  int n;
  if(Null(p)) {
    p = si_make_vector(ECL_T, ecl_make_fixnum(16),
                       ECL_T, /* Adjustable */
                       ecl_make_fixnum(0), /* Fillp */
                       ECL_NIL, /* displacement */
                       ECL_NIL);
    c_env->captured = p;
  }
  for (n = 0; n < p->vector.fillp; n++) {
    if (ecl_eql(p->vector.self.t[n], c)) {
      return n;
    }
  }
  return -1;
}

static int
c_register_captured(cl_env_ptr env, cl_object c)
{
  int n = c_search_captured(env, c);
  return (n < 0) ? asm_captured(env, c) : n;
}

/*
 * Note: the following should match the definitions in cmp/cmpenv.lsp, as
 * well as CMP-ENV-REGISTER-MACROLET (lsp/defmacro.lsp)
 *
 * The compiler environment consists of two lists, one stored in
 * env->variables, the other one stored in env->macros.
 *
 * variable-record =
 *     (:block block-name [used-p | block-object] location) |
 *     (:tag ({tag-name [. tag-id]}*) [used-p | tag-object] location) |
 *     (:function function-name used-p [location]) |
 *     (var-name {:special | nil} bound-p [location]) |
 *     (symbol si::symbol-macro macro-function) |
 *     (:declare type arguments) |
 *     SI:FUNCTION-BOUNDARY |
 *     SI:UNWIND-PROTECT-BOUNDARY
 *     (:declare declaration-arguments*)
 *     (:type type-name [type-definition | expansion-function])
 * macro-record =
 *     (function-name FUNCTION [| function-object]) |
 *     (macro-name si::macro macro-function) |
 *     (:declare name declaration) |
 *     (compiler-macro-name si::compiler-macro macro-function) |
 *     SI:FUNCTION-BOUNDARY |
 *     SI:UNWIND-PROTECT-BOUNDARY
 *
 * A *-NAME is a symbol. A TAG-ID is a number. A MACRO-FUNCTION is a function
 * that provides us with the expansion for that local macro or symbol
 * macro. BOUND-P is true when the variable has been bound by an enclosing form,
 * while it is NIL if the variable-record corresponds just to a special
 * declaration. SI:FUNCTION-BOUNDARY and SI:UNWIND-PROTECT-BOUNDARY denote
 * function and unwind-protect boundaries.
 *
 * The brackets [] denote differences between the bytecodes and C compiler
 * environments, with the first option belonging to the interpreter and the
 * second alternative to the compiler.
 *
 * A LOCATION object is proper to the bytecodes compiler and denotes the
 * position of this variable, block, tag or function, in the lexical
 * environment. Currently, it is a CONS with two integers (DEPTH . ORDER),
 * denoting the depth of the nested environments and the position in the
 * environment (from the beginning, not from the tail).
 *
 * The BLOCK-, TAG- and FUNCTION- objects are proper of the compiler and carry
 * further information.
 *
 * The last variable records are devoted to declarations and are only used by
 * the C compiler. Read cmpenv.lsp for more details on the structure of these
 * declaration forms, as they do not completely match those of Common-Lisp.
 */

static cl_object
c_push_record(const cl_compiler_ptr c_env, cl_object type,
              cl_object arg1, cl_object arg2)
{
  cl_object depth = ecl_make_fixnum(c_env->env_depth);
  cl_object index = ecl_make_fixnum(c_env->env_size++);
  cl_object loc = CONS(depth, index);
  if (c_env->env_width < c_env->env_size)
    c_env->env_width = c_env->env_size;
  return cl_list(4, type, arg1, arg2, loc);
}

static cl_object
c_make_record(const cl_compiler_ptr c_env, cl_object type,
              cl_object arg1, cl_object arg2)
{
  return cl_list(3, type, arg1, arg2);
}

static void
c_register_block(cl_env_ptr env, cl_object name)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object entry = c_push_record(c_env, @':block', name, ECL_NIL);
  c_env->variables = CONS(entry, c_env->variables);
}

static void
c_register_tags(cl_env_ptr env, cl_object all_tags)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object entry = c_push_record(c_env, @':tag', all_tags, ECL_NIL);
  c_env->variables = CONS(entry, c_env->variables);
}

static void
c_register_var(cl_env_ptr env, cl_object var, bool special, bool bound)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object boundp = bound? ECL_T : ECL_NIL;
  cl_object entry = (special
                     ? c_make_record(c_env, var, ECL_T, boundp)
                     : c_push_record(c_env, var, ECL_NIL, boundp));
  c_env->variables = CONS(entry, c_env->variables);
}

static void
c_register_function(cl_env_ptr env, cl_object name)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object entry = c_push_record(c_env, @':function', name, ECL_NIL);
  c_env->variables = CONS(entry, c_env->variables);
  c_env->macros = CONS(cl_list(2, name, @'function'), c_env->macros);
}

static void
c_register_symbol_macro(cl_env_ptr env, cl_object name, cl_object exp_fun)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object entry = c_make_record(c_env, name, @'si::symbol-macro', exp_fun);
  c_env->variables = CONS(entry, c_env->variables);
}

static void
c_register_macro(cl_env_ptr env, cl_object name, cl_object exp_fun)
{
  const cl_compiler_ptr c_env = env->c_env;
  c_env->macros = CONS(cl_list(3, name, @'si::macro', exp_fun), c_env->macros);
}

static void
c_register_boundary(cl_env_ptr env, cl_object type)
{
  const cl_compiler_ptr c_env = env->c_env;
  c_env->variables = CONS(type, c_env->variables);
  c_env->macros = CONS(type, c_env->macros);
}

static cl_object
c_macro_expand1(cl_env_ptr env, cl_object stmt)
{
  const cl_compiler_ptr c_env = env->c_env;
  if(ECL_ATOM(stmt)) {
    if(!ECL_SYMBOLP(stmt)) return stmt;
    c_sym_ref(env, stmt);
  } else {
    cl_object name = ECL_CONS_CAR(stmt);
    if(!ECL_SYMBOLP(name)) return stmt;
    c_mac_ref(env, name);
  }
  return cl_macroexpand_1(2, stmt, CONS(c_env->variables, c_env->macros));
}

static void
import_lexenv(cl_env_ptr env, cl_object lexenv)
{
  if (!ECL_VECTORP(lexenv))
    return;
  /*
   * Given the environment of an interpreted function, we guess a
   * suitable compiler enviroment to compile forms that access the
   * variables and local functions of this interpreted code.
   */
  cl_object *lex = lexenv->vector.self.t;
  cl_index index = lexenv->vector.dim;
  cl_compiler_env_ptr c_env = env->c_env;
  cl_object record;
  while(index>0) {
    index--;
    record = lex[index];
    if (!LISTP(record)) {
      if (ecl_t_of(record) == t_bclosure)
        record = record->bclosure.code;
      c_register_function(env, record->bytecodes.name);
    } else {
      cl_object record0 = ECL_CONS_CAR(record);
      cl_object record1 = ECL_CONS_CDR(record);
      if (ECL_SYMBOLP(record0)) {
        if (record0 == @'si::macro')
          c_register_macro(env, ECL_CONS_CDR(record1), ECL_CONS_CAR(record1));
        else if (record0 == @'si::symbol-macro')
          c_register_symbol_macro(env, ECL_CONS_CDR(record1), ECL_CONS_CAR(record1));
        else
          c_register_var(env, record0, FALSE, TRUE);
      } else if (record1 == ecl_make_fixnum(0)) {
        /* We have lost the information, which tag corresponds to
           the lex-env record. If we are compiling a closure over a
           tag, we will get an error later on. */
      } else {
        c_register_block(env, record1);
      }
    }
  }
  /* INV we register a function boundary so that objects are not looked for in
     the parent locals. -- jd 2025-01-13*/
  c_register_boundary(env, @'si::function-boundary');
  c_env->lex_env = lexenv;
}

static void
import_cmpenv(cl_env_ptr env, cl_object cmpenv)
{
  if (!ECL_CONSP(cmpenv))
    return;
  cl_object variables = ECL_CONS_CAR(cmpenv);
  cl_object functions = ECL_CONS_CDR(cmpenv);
  cl_compiler_env_ptr c_env = env->c_env;
  cl_object record, reg0, reg1;
  c_env->variables = variables;
  c_env->macros = functions;
  loop_for_on_unsafe(variables) {
    record = ECL_CONS_CAR(variables);
    if (ECL_ATOM(record))
      continue;
    reg0 = pop(&record);
    reg1 = pop(&record);
    if (reg0 == @':declare' || reg1 == @'si::symbol-macro') {
      continue;
    } else {
      c_env->lexical_level = 1;
      break;
    }
  } end_loop_for_on_unsafe();
  /* INV we register a function boundary so that objects are not looked for in
     the parent locals. -- jd 2025-01-13*/
  c_register_boundary(env, @'si::function-boundary');
}

static void
c_new_env(cl_env_ptr the_env, cl_compiler_env_ptr new, cl_compiler_env_ptr old)
{
  the_env->c_env = new;
  if (old) {
    *new = *old;
    new->captured = ECL_NIL;
    new->parent_env = old;
    new->env_size = 0;
    new->env_width = 0;
    new->env_depth = old->env_depth + 1;
  } else {
    new->code_walker = ECL_SYM_VAL(the_env, @'si::*code-walker*');
    new->constants = si_make_vector(ECL_T, ecl_make_fixnum(16),
                                    ECL_T, /* Adjustable */
                                    ecl_make_fixnum(0), /* Fillp */
                                    ECL_NIL, /* displacement */
                                    ECL_NIL);
    new->stepping = 0;
    new->lex_env = ECL_NIL;
    new->lexical_level = 0;
    new->load_time_forms = ECL_NIL;
    new->ltf_being_created = ECL_NIL;
    new->ltf_defer_init_until = ECL_T;
    new->ltf_locations = ECL_NIL;
    new->captured = ECL_NIL;
    new->parent_env = NULL;
    new->env_size = 0;
    new->env_width = 0;
    new->env_depth = 0;
    new->macros = ECL_NIL;
    new->variables = ECL_NIL;
    new->mode = FLAG_EXECUTE;
  }
}

static void
c_restore_env(cl_env_ptr the_env, cl_compiler_env_ptr new_c_env, cl_compiler_env_ptr old_c_env)
{
  if (new_c_env->env_depth == 0) {
    /* Clear created constants (they cannot be printed) */
    loop_for_in(new_c_env->ltf_locations) {
      cl_index loc = ecl_fixnum(ECL_CONS_CAR(new_c_env->ltf_locations));
      new_c_env->constants->vector.self.t[loc] = ecl_make_fixnum(0);
    } end_loop_for_in;
  }
  the_env->c_env = old_c_env;
}

/* c_sym_ref and c_mac_ref ensure that symbol macros and macros that are
   referenced across the function boundary are captured. We capture the entry
   verbatim and we don't bind any objects at runtime -- these objects are
   supplied to enable recompilation by CCMP and BCMP. */

static void
close_around_macros(cl_env_ptr env, cl_object mfun)
{
  cl_object lex = mfun->bclosure.lex;
  cl_object *lex_vec = lex->vector.self.t;
  for (cl_index i = 0; i < lex->vector.dim; i++) {
    cl_object reg = lex_vec[i]; /* INV see interpreter.d for lexenv structure */
    cl_object type = CAR(reg);  /* lexenv tag */
    cl_object name = CDDR(reg); /* macro name */
    if (type == @'si::macro') {
      c_mac_ref(env, name);
    } else if (type == @'si::symbol_macro') {
      c_sym_ref(env, name);
    }
  }
}

void
c_sym_ref(cl_env_ptr env, cl_object name)
{
  const cl_compiler_ptr c_env = env->c_env;
  int function_boundary_crossed = 0;
  cl_object l = c_env->variables;
  loop_for_on_unsafe(l) {
    cl_object record = ECL_CONS_CAR(l), reg, type, other;
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if(ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    other = pop(&reg);
    if (type == name) {
      if (other == @'si::symbol-macro') {
        if (function_boundary_crossed) {
          c_register_captured(env, record);
        } else {
          cl_object mfun = ECL_CONS_CAR(reg);
          if (ecl_t_of(mfun) == t_bclosure) {
            close_around_macros(env, mfun);
          }
        }
      }
      return;
    }
  } end_loop_for_on_unsafe(l);
}

/* This looks in c_env->macros so it is unlike other c_*_ref functions. */
void
c_mac_ref(cl_env_ptr env, cl_object name)
{
  const cl_compiler_ptr c_env = env->c_env;
  int function_boundary_crossed = 0;
  cl_object l = c_env->macros;
  loop_for_on_unsafe(l) {
    cl_object record = ECL_CONS_CAR(l), reg, type, other;
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if(ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    other = pop(&reg);
    if (type == name) {
      if(other == @':function')
        return;
      if(other == @'si::macro') {
        if (function_boundary_crossed) {
          c_register_captured(env, record);
        } else {
          cl_object mfun = ECL_CONS_CAR(reg);
          if (ecl_t_of(mfun) == t_bclosure) {
            close_around_macros(env, mfun);
          }
        }
        return;
      }
    }
  } end_loop_for_on_unsafe(l);
}

/* Computes the local index from the function boundary. */
static cl_fixnum
c_lcl_idx(cl_env_ptr env, cl_object entry)
{
  cl_fixnum n = 0, i = -1;
  const cl_compiler_ptr c_env = env->c_env;
  cl_object l = c_env->variables;
  loop_for_on_unsafe(l) {
    cl_object record = ECL_CONS_CAR(l), type;
    if (record == @'si::function-boundary') {
      break;
    }
    if(ECL_ATOM(record))
      continue;
    if(record == entry) {
      i = n;
      continue;
    }
    type = pop(&record);
    if (type == @':block' || type == @':function' || type == @':tag'
        /* type == @'variable' && Null(specialp) */
        || Null(pop(&record))) {
      n++;
    }
  } end_loop_for_on_unsafe(l);
  if (i<0) ecl_miscompilation_error();
  return n-i;
}

/* This function is called after we compile lambda in the parent's
   environment. Its responsibility is to propagate closures. */
static struct cl_compiler_ref
c_any_ref(cl_env_ptr env, cl_object entry)
{
  int function_boundary_crossed = 0;
  struct cl_compiler_ref output = { ECL_CMPREF_UNDEFINED };
  const cl_compiler_ptr c_env = env->c_env;
  cl_object l = c_env->variables;
  loop_for_on_unsafe(l) {
    cl_object record = ECL_CONS_CAR(l);
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if(ECL_ATOM(record))
      continue;
    if(record == entry) {
      if (function_boundary_crossed) {
        output.place = ECL_CMPREF_CLOSE;
        output.index = c_register_captured(env, record);
      } else {
        output.place = ECL_CMPREF_LOCAL;
        output.index = c_lcl_idx(env, record);
      }
      output.entry = record;
      return output;
    }
  } end_loop_for_on_unsafe(l);
  return output;
}

static struct cl_compiler_ref
c_tag_ref(cl_env_ptr env, cl_object the_tag)
{
  cl_object l, reg;
  int function_boundary_crossed = 0;
  struct cl_compiler_ref output = { ECL_CMPREF_UNDEFINED };
  const cl_compiler_ptr c_env = env->c_env;
  for (l = c_env->variables; CONSP(l); l = ECL_CONS_CDR(l)) {
    cl_object type, all_tags, record = ECL_CONS_CAR(l);
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if (ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    all_tags = pop(&reg);
    if (type == @':tag') {
      cl_object label = ecl_assql(the_tag, all_tags);
      if (!Null(label)) {
        /* Mark as used */
        ECL_RPLACA(reg, ECL_T);
        if (function_boundary_crossed) {
          output.place = ECL_CMPREF_CLOSE;
          output.index = c_register_captured(env, record);
        } else {
          output.place = ECL_CMPREF_LOCAL;
          output.index = c_lcl_idx(env, record);
        }
        output.entry = record;
        output.label = ecl_fixnum(ECL_CONS_CDR(label));
        return output;
      }
    }
  }
  return output;
}

static struct cl_compiler_ref
c_blk_ref(cl_env_ptr env, cl_object the_tag)
{
  cl_object l, reg;
  int function_boundary_crossed = 0;
  struct cl_compiler_ref output = { ECL_CMPREF_UNDEFINED };
  const cl_compiler_ptr c_env = env->c_env;
  for (l = c_env->variables; CONSP(l); l = ECL_CONS_CDR(l)) {
    cl_object type, name, record = ECL_CONS_CAR(l);
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if (ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    name = pop(&reg);
    if (type == @':block') {
      if(ecl_eql(name, the_tag)) {
        /* Mark as used */
        ECL_RPLACA(reg, ECL_T);
        if (function_boundary_crossed) {
          output.place = ECL_CMPREF_CLOSE;
          output.index = c_register_captured(env, record);
        } else {
          output.place = ECL_CMPREF_LOCAL;
          output.index = c_lcl_idx(env, record);
        }
        output.entry = record;
        return output;
      }
    }
  }
  return output;
}

static struct cl_compiler_ref
c_fun_ref(cl_env_ptr env, cl_object the_tag)
{
  cl_object l, reg;
  int function_boundary_crossed = 0;
  struct cl_compiler_ref output = { ECL_CMPREF_UNDEFINED };
  const cl_compiler_ptr c_env = env->c_env;
  for (l = c_env->variables; CONSP(l); l = ECL_CONS_CDR(l)) {
    cl_object type, name, record = ECL_CONS_CAR(l);
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if (ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    name = pop(&reg);
    if (type == @':function') {
      /* We compare with EQUAL, because of (SETF fname) */
      if(ecl_equal(name, the_tag)) {
        /* Mark as used */
        ECL_RPLACA(reg, ECL_T);
        if (function_boundary_crossed) {
          output.place = ECL_CMPREF_CLOSE;
          output.index = c_register_captured(env, record);
        } else {
          output.place = ECL_CMPREF_LOCAL;
          output.index = c_lcl_idx(env, record);
        }
        output.entry = record;
        return output;
      }
    }
  }
  return output;
}

ecl_def_ct_base_string(undefined_variable,
                       "Undefined variable referenced in interpreted code"
                       ".~%Name: ~A", 60, static, const);                               

static struct cl_compiler_ref
c_var_ref(cl_env_ptr env, cl_object var, bool allow_sym_mac, bool ensure_def)
{
  cl_object l, reg;
  int function_boundary_crossed = 0;
  struct cl_compiler_ref output;
  output.place = ECL_CMPREF_UNDEFINED;
  output.label = ECL_CMPVAR_UNDEFINED;
  const cl_compiler_ptr c_env = env->c_env;
  for (l = c_env->variables; CONSP(l); l = ECL_CONS_CDR(l)) {
    cl_object type, special, record = ECL_CONS_CAR(l);
    if (record == @'si::function-boundary')
      function_boundary_crossed++;
    if (ECL_ATOM(record))
      continue;
    reg = record;
    type = pop(&reg);
    special = pop(&reg);
    if (type == @':block' || type == @':tag' || type == @':function'
        || type == @':declare' || type == @':type' || type != var) {
      continue;
    } else if (Null(special)) {
      if (function_boundary_crossed) {
        output.place = ECL_CMPREF_CLOSE;
        output.index = c_register_captured(env, record);
      } else {
        output.place = ECL_CMPREF_LOCAL;
        output.index = c_lcl_idx(env, record);
      }
      output.entry = record;
      output.label = ECL_CMPVAR_LEXICAL;
      return output;
    } else if (special == @'si::symbol-macro') {
      if(!allow_sym_mac)
        FEprogram_error("Internal error: symbol macro ~S used as variable", 1, var);
      /* We can only get here when we try to redefine a symbol macro. */
      /* We don't close over symbol macros (but we will). */
      output.place = function_boundary_crossed
        ? ECL_CMPREF_CLOSE
        : ECL_CMPREF_LOCAL;
      output.entry = record;
      output.label = ECL_CMPVAR_SYM_MACRO;
      return output;
    } else {
      /* We don't close over special variables. */
      output.place = function_boundary_crossed
        ? ECL_CMPREF_CLOSE
        : ECL_CMPREF_LOCAL;
      output.index = -1;
      output.entry = record;
      output.label = ECL_CMPVAR_SPECIAL;
      return output;
    }
  }
  if (ensure_def) {
    l = ecl_cmp_symbol_value(env, @'ext::*action-on-undefined-variable*');
    if (l != ECL_NIL) {
      cl_funcall(3, l, undefined_variable, var);
    }
  }
  return output;
}

/* Depending on whether the variable is special, local or closed over, we emit
   different opcodes since they are handled differently. -- jd 2025-01-07*/
static int
c_var_ref_fix_op(struct cl_compiler_ref ref, int op) {
  bool special = (ref.label == ECL_CMPVAR_SPECIAL
                  || ref.label == ECL_CMPVAR_UNDEFINED);
  bool closure = (!special && ref.place == ECL_CMPREF_CLOSE);
  if(!special && !closure) return op;
  switch(op) {
    /* setters */
  case OP_SETQ:  return (special ? OP_SETQS : OP_SETQC);
  case OP_PSETQ: return (special ? OP_PSETQS : OP_PSETQC);
  case OP_VSETQ: return (special ? OP_VSETQS : OP_VSETQC);
    /* getters */
  case OP_VAR:   return (special ? OP_VARS : OP_VARC);
  case OP_PUSHV: return (special ? OP_PUSHVS : OP_PUSHVC);
  default:
    ecl_miscompilation_error();
  }
}

static bool
c_declared_special(cl_object var, cl_object specials)
{
  return ((ecl_symbol_type(var) & ecl_stp_special) || ecl_member_eq(var, specials));
}

static void
c_declare_specials(cl_env_ptr env, cl_object specials)
{
  while (!Null(specials)) {
    cl_object var = pop(&specials);
    struct cl_compiler_ref ref = c_var_ref(env, var, TRUE, FALSE);
    switch(ref.label) {
    case ECL_CMPVAR_UNDEFINED:
    case ECL_CMPVAR_SYM_MACRO:
    case ECL_CMPVAR_LEXICAL:
      c_register_var(env, var, TRUE, FALSE);
      break;
    default:
      break;
    }
  }
}

static cl_object
c_process_declarations(cl_object body)
{
  const cl_env_ptr the_env = ecl_process_env();
  @si::process-declarations(1, body);
  body = ecl_nth_value(the_env, 1);
  return body;
}

static bool
c_pbind(cl_env_ptr env, cl_object var, cl_object specials)
{
  bool special;
  if (!ECL_SYMBOLP(var))
    FEillegal_variable_name(var);
  else if ((special = c_declared_special(var, specials))) {
    c_register_var(env, var, TRUE, TRUE);
    asm_op2c(env, OP_PBINDS, var);
  } else {
    c_register_var(env, var, FALSE, TRUE);
    asm_op2c(env, OP_PBIND, var);
  }
  return special;
}

static bool
c_bind(cl_env_ptr env, cl_object var, cl_object specials)
{
  bool special;
  if (!ECL_SYMBOLP(var))
    FEillegal_variable_name(var);
  else if ((special = c_declared_special(var, specials))) {
    c_register_var(env, var, TRUE, TRUE);
    asm_op2c(env, OP_BINDS, var);
  } else {
    c_register_var(env, var, FALSE, TRUE);
    asm_op2c(env, OP_BIND, var);
  }
  return special;
}

static void
c_undo_bindings(cl_env_ptr the_env, cl_object old_vars, int only_specials)
{
  cl_object env;
  cl_index num_lexical = 0;
  cl_index num_special = 0;
  const cl_compiler_ptr c_env = the_env->c_env;
  for (env = c_env->variables; env != old_vars && !Null(env); env = ECL_CONS_CDR(env))
    {
      cl_object record, name, special, boundp;
      record = ECL_CONS_CAR(env);
      if (ECL_ATOM(record))
        continue;
      name = ECL_CONS_CAR(record);
      record = ECL_CONS_CDR(record);
      special = ECL_CONS_CAR(record);
      if (name == @':block' || name == @':tag') {
        if (!only_specials) num_lexical++;
      } else if (name == @':function' || Null(special)) {
        if (!only_specials) num_lexical++;
      } else if (name == @':declare' || name == @':type') {
        /* Ignored */
      } else if (special != @'si::symbol-macro') {
        /* If (third special) = NIL, the variable was declared
           special, but there is no binding! */
        record = ECL_CONS_CDR(record);
        boundp = ECL_CONS_CAR(record);
        if (!Null(boundp)) {
          num_special++;
        }
      }
    }
  c_env->variables = env;
  if (num_lexical) {
    c_env->env_size -= num_lexical;
    asm_op2(the_env, OP_UNBIND, num_lexical);
  }
  if (num_special) asm_op2(the_env, OP_UNBINDS, num_special);
}

static void
compile_setq(cl_env_ptr env, int op, cl_object var)
{
  cl_index ndx;
  struct cl_compiler_ref ref;
  if (!ECL_SYMBOLP(var))
    FEillegal_variable_name(var);
  ref = c_var_ref(env, var,FALSE,TRUE);
  ndx = ref.index;
  switch(ref.label) {
  case ECL_CMPVAR_SPECIAL:
  case ECL_CMPVAR_UNDEFINED:
    if (ecl_symbol_type(var) & ecl_stp_constant) {
      FEassignment_to_constant(var);
    }
    ndx = c_register_constant(env, var);
    /* fall through */
  default:
    op = c_var_ref_fix_op(ref, op);
    break;
  }
  asm_op2(env, op, ndx);
}

/*
 * This routine is used to change the compilation flags in optimizers
 * that do not want to push values onto the stack.  Its purpose is to
 * keep ignorable forms ignored, while preserving the value of useful
 * forms. Qualitative behavior:
 *      FLAG_PUSH               -> FLAG_VALUES
 *      FLAG_VALUES             -> FLAG_VALUES
 *      FLAG_REG0               -> FLAG_REG0
 *      FLAG_IGNORE             -> FLAG_IGNORE
 */
static int
maybe_values_or_reg0(int flags) {
  if (flags & FLAG_PUSH)
    return (flags | FLAG_VALUES) & ~FLAG_PUSH;
  else
    return flags;
}

/*
 * This routine is used to change the compilation flags in optimizers
 * that do not want to push values onto the stack, but also do not want
 * to use REG0 (maybe because the call a nested ecl_interpret()). Ignorable
 * forms are kept ignored:
 *      FLAG_PUSH               -> FLAG_VALUES
 *      FLAG_VALUES             -> FLAG_VALUES
 *      FLAG_REG0               -> FLAG_VALUES
 *      FLAG_IGNORE             -> FLAG_IGNORE
 */
static int
maybe_values(int flags) {
  if (flags & FLAG_USEFUL)
    return (flags & ~(FLAG_PUSH | FLAG_REG0)) | FLAG_VALUES;
  else
    return flags;
}

/*
 * This routine is used to change the compilation flags in optimizers
 * that do not want to push values onto the stack.  Its purpose is to
 * keep ignorable forms ignored, while preserving the value of useful
 * forms. Qualitative behavior:
 *      FLAG_PUSH               -> FLAG_REG0
 *      FLAG_VALUES             -> FLAG_REG0
 *      FLAG_REG0               -> FLAG_REG0
 *      FLAG_IGNORE             -> FLAG_IGNORE
 */
static int
maybe_reg0(int flags) {
  if (flags & FLAG_USEFUL)
    return (flags & ~(FLAG_VALUES | FLAG_PUSH)) | FLAG_REG0;
  else
    return flags;
}

/* -------------------- THE COMPILER -------------------- */

/*
  The OP_BLOCK operator encloses several forms within a block
  named BLOCK_NAME, thus catching any OP_RETFROM whose argument
  matches BLOCK_NAME. The end of this block is marked both by
  the OP_EXIT operator and the LABELZ which is packed within
  the OP_BLOCK operator.

  [OP_BLOCK + name + labelz]
  ....
  OP_EXIT_FRAME
  labelz: ...
*/

static int
c_block(cl_env_ptr env, cl_object body, int old_flags) {
  struct cl_compiler_env old_env;
  cl_object name = pop(&body);
  cl_object block_record;
  cl_index labelz, pc, constants;
  int flags;

  if (!ECL_SYMBOLP(name))
    FEprogram_error("BLOCK: Not a valid block name, ~S", 1, name);

  old_env = *(env->c_env);
  constants = old_env.constants->vector.fillp;
  pc = current_pc(env);

  flags = maybe_values_or_reg0(old_flags);
  c_register_block(env, name);
  block_record = ECL_CONS_CAR(env->c_env->variables);
  if (Null(name)) {
    asm_op(env, OP_DO);
  } else {
    asm_op2c(env, OP_BLOCK, name);
  }
  labelz = asm_jmp(env, OP_FRAME);
  compile_body(env, body, flags);
  if (CADDR(block_record) == ECL_NIL) {
    /* Block unused. We remove the enclosing OP_BLOCK/OP_DO */
    /* We also have to remove the constants we compiled, because */
    /* some of them might be from load-time-value */
    old_env.constants->vector.fillp = constants;
    *(env->c_env) = old_env;
    set_pc(env, pc);
    return compile_body(env, body, old_flags);
  } else {
    c_undo_bindings(env, old_env.variables, 0);
    asm_op(env, OP_EXIT_FRAME);
    asm_complete(env, 0, labelz);
    return flags;
  }
}

/*
  There are several ways to invoke functions and to handle the
  output arguments. These are

  [OP_CALL + nargs]
  function_name

  [OP_FCALL + nargs]

  OP_CALL and OP_FCALL leave all arguments in the VALUES() array,
  while OP_PCALL and OP_PFCALL leave the first argument in the
  stack.

  OP_CALL and OP_PCALL use the value in VALUES(0) to retrieve the
  function, while OP_FCALL and OP_PFCALL use a value from the
  stack.
*/
static int
c_arguments(cl_env_ptr env, cl_object args) {
  cl_index nargs;
  for (nargs = 0; !Null(args); nargs++) {
    compile_form(env, pop(&args), FLAG_PUSH);
  }
  return nargs;
}

static int
c_call(cl_env_ptr env, cl_object args, int flags) {
  cl_object name;
  cl_index nargs;

  name = pop(&args);
  if (name >= (cl_object)cl_symbols
      && name < (cl_object)(cl_symbols + cl_num_symbols_in_core))
    {
      cl_object f = ECL_SYM_FUN(name);
      cl_type t = (!ECL_FBOUNDP(name))? t_other : ecl_t_of(f);
      if (t == t_cfunfixed) {
        cl_index n = ecl_length(args);
        if (f->cfun.narg == 1 && n == 1) {
          compile_form(env, ECL_CONS_CAR(args), FLAG_REG0);
          asm_op2c(env, OP_CALLG1, name);
          return FLAG_VALUES;
        } else if (f->cfun.narg == 2 && n == 2) {
          compile_form(env, ECL_CONS_CAR(args), FLAG_PUSH);
          args = ECL_CONS_CDR(args);
          compile_form(env, ECL_CONS_CAR(args), FLAG_REG0);
          asm_op2c(env, OP_CALLG2, name);
          return FLAG_VALUES;
        }
      }
    }
  nargs = c_arguments(env, args);
  if (env->c_env->stepping) {
    /* When stepping, we only have one opcode to do function
     * calls: OP_STEPFCALL. */
    asm_function(env, name, (flags & FLAG_GLOBAL) | FLAG_REG0);
    asm_op2(env, OP_STEPCALL, nargs);
    flags = FLAG_VALUES;
  } else if (ECL_SYMBOLP(name) &&
             ((flags & FLAG_GLOBAL) ||
              c_fun_ref(env, name).place == ECL_CMPREF_UNDEFINED))
  {
    asm_op2(env, OP_CALLG, nargs);
    asm_arg_data(env, name);
    flags = FLAG_VALUES;
  } else {
    /* Fixme!! We can optimize the case of global functions! */
    asm_function(env, name, (flags & FLAG_GLOBAL) | FLAG_REG0);
    asm_op2(env, OP_CALL, nargs);
    flags = FLAG_VALUES;
  }
  return flags;
}

static int
c_funcall(cl_env_ptr env, cl_object args, int flags) {
  cl_object name;
  cl_index nargs;

  name = pop(&args);
  if (CONSP(name)) {
    cl_object kind = ECL_CONS_CAR(name);
    if (kind == @'function') {
      if (cl_list_length(name) != ecl_make_fixnum(2))
        FEprogram_error("FUNCALL: Invalid function name ~S.", 1, name);
      return c_call(env, CONS(CADR(name), args), flags);
    }
    if (kind == @'quote') {
      if (cl_list_length(name) != ecl_make_fixnum(2))
        FEprogram_error("FUNCALL: Invalid function name ~S.", 1, name);
      return c_call(env, CONS(CADR(name), args), flags | FLAG_GLOBAL);
    }
  }
  compile_form(env, name, FLAG_PUSH);
  nargs = c_arguments(env, args);
  if (env->c_env->stepping) {
    asm_op2(env, OP_STEPCALL, nargs);
    flags = FLAG_VALUES;
  } else {
    asm_op2(env, OP_FCALL, nargs);
    flags = FLAG_VALUES;
  }
  asm_op(env, OP_POP1);
  return flags;
}

static int
perform_c_case(cl_env_ptr env, cl_object args, int flags) {
  cl_object test, clause;

  do {
    if (Null(args))
      return compile_body(env, ECL_NIL, flags);
    clause = pop(&args);
    if (ECL_ATOM(clause))
      FEprogram_error("CASE: Illegal clause ~S.",1,clause);
    test = pop(&clause);
  } while (test == ECL_NIL);

  if (@'otherwise' == test || test == ECL_T) {
    unlikely_if (args != ECL_NIL) {
      FEprogram_error("CASE: The selector ~A can only appear at the last position.",
                               1, test);
    }
    compile_body(env, clause, flags);
  } else {
    cl_index labeln, labelz;
    if (CONSP(test)) {
      cl_index n = ecl_length(test);
      while (n-- > 1) {
        cl_object v = pop(&test);
        asm_op(env, OP_JEQL);
        maybe_make_load_forms(env, v);
        asm_arg_data(env, v);
        asm_arg(env, n * (OPCODE_SIZE + OPARG_SIZE * 2)
                + OPARG_SIZE);
      }
      test = ECL_CONS_CAR(test);
    }
    asm_op(env, OP_JNEQL);
    maybe_make_load_forms(env, test);
    asm_arg_data(env, test);
    labeln = current_pc(env);
    asm_arg(env, 0);
    compile_body(env, clause, flags);
    if (Null(args) && !(flags & FLAG_USEFUL)) {
      /* Ther is no otherwise. The test has failed and
         we need no output value. We simply close jumps. */
      asm_complete(env, 0 & OP_JNEQL, labeln);
    } else {
      labelz = asm_jmp(env, OP_JMP);
      asm_complete(env, 0 & OP_JNEQL, labeln);
      perform_c_case(env, args, flags);
      asm_complete(env, OP_JMP, labelz);
    }
  }
  return flags;
}

static int
c_case(cl_env_ptr env, cl_object clause, int flags) {
  compile_form(env, pop(&clause), FLAG_REG0);
  return perform_c_case(env, clause, maybe_values_or_reg0(flags));
}

/*
  The OP_CATCH takes the object in VALUES(0) and uses it to catch
  any OP_THROW operation which uses that value as argument. If a
  catch occurs, or when all forms have been properly executed, it
  jumps to LABELZ. LABELZ is packed within the OP_CATCH operator.
  [OP_CATCH + labelz]
  ...
  "forms to be caught"
  ...
  OP_EXIT_FRAME
  labelz: ...
*/

static int
c_catch(cl_env_ptr env, cl_object args, int flags) {
  cl_index labelz;
  cl_object old_env;

  /* Compile evaluation of tag */
  compile_form(env, pop(&args), FLAG_REG0);

  /* Compile binding of tag */
  old_env = env->c_env->variables;
  c_register_block(env, ecl_make_fixnum(0));
  asm_op(env, OP_CATCH);

  /* Compile jump point */
  labelz = asm_jmp(env, OP_FRAME);

  /* Compile body of CATCH */
  compile_body(env, args, FLAG_VALUES);

  c_undo_bindings(env, old_env, 0);
  asm_op(env, OP_EXIT_FRAME);
  asm_complete(env, 0, labelz);

  return FLAG_VALUES;
}

static int
c_compiler_let(cl_env_ptr env, cl_object args, int flags) {
  cl_object bindings;
  cl_index old_bds_ndx = env->bds_stack.top - env->bds_stack.org;

  for (bindings = pop(&args); !Null(bindings); ) {
    cl_object form = pop(&bindings);
    cl_object var = pop(&form);
    cl_object value = pop_maybe_nil(&form);
    ecl_bds_bind(env, var, value);
  }
  flags = compile_toplevel_body(env, args, flags);
  ecl_bds_unwind(env, old_bds_ndx);
  return flags;
}

/*
  There are three operators which perform explicit jumps, but
  almost all other operators use labels in one way or
  another.

  1) Jumps are always relative to the place where the jump label
  is retrieved so that if the label is in vector[0], then the
  destination is roughly vector + vector[0].

  2) The three jump forms are

  [OP_JMP + label]        ; Unconditional jump
  [OP_JNIL + label]       ; Jump if VALUES(0) == ECL_NIL
  [OP_JT + label]         ; Jump if VALUES(0) != ECL_NIL

  It is important to remark that both OP_JNIL and OP_JT truncate
  the values stack, so that always NVALUES = 1 after performing
  any of these operations.
*/
static int
c_cond(cl_env_ptr env, cl_object args, int flags) {
  cl_object test, clause;
  cl_index label_nil, label_exit;

  if (Null(args))
    return compile_form(env, ECL_NIL, flags);
  clause = pop(&args);
  if (ECL_ATOM(clause))
    FEprogram_error("COND: Illegal clause ~S.",1,clause);
  test = pop(&clause);
  flags = maybe_values_or_reg0(flags);
  if (ECL_T == test) {
    /* Default sentence. If no forms, just output T. */
    if (Null(clause))
      compile_form(env, ECL_T, flags);
    else
      compile_body(env, clause, flags);
  } else {
    /* Compile the test. If no more forms, just output
       the first value (this is guaranteed by OP_JT), but make
       sure it is stored in the appropriate place. */
    if (Null(args)) {
      if (Null(clause)) {
        c_values(env, cl_list(1,test), flags);
      } else {
        compile_form(env, test, FLAG_REG0);
        if (flags & FLAG_VALUES) asm_op(env, OP_VALUEREG0);
        label_nil = asm_jmp(env, OP_JNIL);
        compile_body(env, clause, flags);
        asm_complete(env, OP_JNIL, label_nil);
      }
    } else if (Null(clause)) {
      compile_form(env, test, FLAG_REG0);
      if (flags & FLAG_VALUES) asm_op(env, OP_VALUEREG0);
      label_exit = asm_jmp(env, OP_JT);
      c_cond(env, args, flags);
      asm_complete(env, OP_JT, label_exit);
    } else {
      compile_form(env, test, FLAG_REG0);
      label_nil = asm_jmp(env, OP_JNIL);
      compile_body(env, clause, flags);
      label_exit = asm_jmp(env, OP_JMP);
      asm_complete(env, OP_JNIL, label_nil);
      c_cond(env, args, flags);
      asm_complete(env, OP_JMP, label_exit);
    }
  }
  return flags;
}

/*      The OP_DO operator saves the lexical environment and establishes
        a NIL block to execute the enclosed forms, which are typically
        like the ones shown below. At the exit of the block, either by
        means of a OP_RETFROM jump or because of normal termination,
        the lexical environment is restored, and all bindings undone.

        [OP_DO + labelz]
        ...     ; bindings
        [JMP + labelt]
        labelb: ...     ; body
        ...     ; stepping forms
        labelt: ...     ; test form
        [JNIL + label]
        ...     ; output form
        OP_EXIT_FRAME
        labelz:

*/
static int
c_while_until(cl_env_ptr env, cl_object body, int flags, bool is_while) {
  cl_object test = pop(&body);
  cl_index labelt, labelb;

  flags = maybe_reg0(flags);

  /* Jump to test */
  labelt = asm_jmp(env, OP_JMP);

  /* Compile body */
  labelb = current_pc(env);
  c_tagbody(env, body, flags);

  /* Compile test */
  asm_complete(env, OP_JMP, labelt);
  compile_form(env, test, FLAG_REG0);
  asm_op(env, is_while? OP_JT : OP_JNIL);
  asm_arg(env, labelb - current_pc(env));

  return flags;
}

static int
c_while(cl_env_ptr env, cl_object body, int flags) {
  return c_while_until(env, body, flags, 1);
}

static int
c_until(cl_env_ptr env, cl_object body, int flags) {
  return c_while_until(env, body, flags, 0);
}

static int
c_with_backend(cl_env_ptr env, cl_object args, int flags)
{
  cl_object forms = ECL_NIL;
  while (!Null(args)) {
    cl_object tag = pop(&args);
    cl_object form = pop(&args);
    if (tag == @':bytecodes')
      forms = CONS(form, forms);
  }
  return compile_toplevel_body(env, forms, flags);
}

static int
eval_when_flags(cl_object situation)
{
  int code = 0;
  cl_object p;
  for (p = situation; p != ECL_NIL; p = ECL_CONS_CDR(p)) {
    cl_object keyword;
    unlikely_if (!ECL_LISTP(p))
      FEtype_error_proper_list(situation);
    keyword = ECL_CONS_CAR(p);
    if (keyword == @'load')
      code |= FLAG_LOAD;
    else if (keyword == @':load-toplevel')
      code |= FLAG_LOAD;
    else if (keyword == @'compile')
      code |= FLAG_COMPILE;
    else if (keyword == @':compile-toplevel')
      code |= FLAG_COMPILE;
    else if (keyword == @'eval')
      code |= FLAG_EXECUTE;
    else if (keyword == @':execute')
      code |= FLAG_EXECUTE;
  }
  return code;
}

#define when_load_p(s) ((s) & FLAG_LOAD)
#define when_compile_p(s) ((s) & FLAG_COMPILE)
#define when_execute_p(s) ((s) & FLAG_EXECUTE)

static int
c_eval_when(cl_env_ptr env, cl_object args, int flags) {
  cl_object situation_list = pop(&args);
  int situation = eval_when_flags(situation_list);
  const cl_compiler_ptr c_env = env->c_env;
  int mode = c_env->mode;
  if (c_env->lexical_level || mode == FLAG_EXECUTE) {
    if (!when_execute_p(situation))
      args = ECL_NIL;
  } else if (when_load_p(situation)) {
    if (when_compile_p(situation)) {
      int current_mode = c_env->mode;
      c_env->mode = FLAG_EXECUTE;
      execute_each_form(env, args);
      c_env->mode = current_mode;
    }
  } else if (when_compile_p(situation)) {
    int current_mode = c_env->mode;
    c_env->mode = FLAG_EXECUTE;
    execute_each_form(env, args);
    c_env->mode = current_mode;
    args = ECL_NIL;
  } else {
    args = ECL_NIL;
  }
  return compile_toplevel_body(env, args, flags);
}


/*
  The OP_FLET/OP_FLABELS operators change the lexical environment
  to add a few local functions.

  [OP_FLET/OP_FLABELS + nfun + fun1]
  ...
  OP_UNBIND nfun
  labelz:
*/
static cl_index
c_register_functions(cl_env_ptr env, cl_object l)
{
  cl_index nfun;
  for (nfun = 0; !Null(l); nfun++) {
    cl_object definition = pop(&l);
    cl_object name = pop(&definition);
    c_register_function(env, name);
  }
  return nfun;
}

static int
c_labels_flet(cl_env_ptr env, int op, cl_object args, int flags) {
#define push_back(v,l) { cl_object c = *l = CONS(v, *l); l = &ECL_CONS_CDR(c); }
  const cl_compiler_ptr c_env = env->c_env;
  cl_object l, def_list = pop(&args);
  cl_object old_vars = c_env->variables;
  cl_object old_funs = c_env->macros;
  cl_object fnames = ECL_NIL;
  cl_object v, *f = &fnames;
  cl_index nfun, lex_idx;

  if (def_list == ECL_NIL) {
    return c_locally(env, args, flags);
  }

  /* ANSI doesn't specify what should happen if we define
     multiple functions of the same name in the flet/labels
     block – ECL treats this undefined behavior as an error */
  for (l = def_list, nfun = 0; !Null(l); nfun++) {
    v = CAR(pop(&l));
    if (ecl_member_eq(v, fnames))
      FEprogram_error
        ("~s: The function ~s was already defined.",
         2, (op == OP_LABELS ? @'LABELS' : @'FLET'), v);
    push_back(v, f);
  }

  /* If compiling a LABELS form, add the function names to the lexical
     environment before compiling the functions */
  if (op == OP_LABELS)
    c_register_functions(env, def_list);

  /* Push the operator (OP_LABELS/OP_FLET) with the number of functions */
  asm_op2(env, op, nfun);

  /* Compile the local functions now. */
  for (l = def_list; !Null(l); ) {
    cl_object definition = pop(&l);
    cl_object name = pop(&definition);
    cl_object lambda = ecl_make_lambda(env, name, definition);
    lex_idx = c_register_constant(env, lambda);
    asm_arg(env, lex_idx);
  }

  /* If compiling a FLET form, add the function names to the lexical
     environment after compiling the functions */
  if (op == OP_FLET)
    c_register_functions(env, def_list);

  /* Compile the body of the form with the local functions in the lexical
     environment. */
  flags = c_locally(env, args, flags);

  /* Restore and return */
  c_undo_bindings(env, old_vars, 0);
  env->c_env->macros = old_funs;

  return flags;
#undef push
}


static int
c_flet(cl_env_ptr env, cl_object args, int flags) {
  return c_labels_flet(env, OP_FLET, args, flags);
}


/*
  There are two operators that produce functions. The first one is
  [OP_FUNCTION + name] which takes the function binding of SYMBOL.

  The second one is OP_CLOSE interpreted which encloses the INTERPRETED
  function in the current lexical environment.
*/
static int
c_function(cl_env_ptr env, cl_object args, int flags) {
  cl_object function = pop(&args);
  if (!Null(args))
    FEprogram_error("FUNCTION: Too many arguments.", 0);
  return asm_function(env, function, flags);
}

static int                      /* XXX: here we look for function in cmpenv */
asm_function(cl_env_ptr env, cl_object function, int flags) {
  if (!Null(si_valid_function_name_p(function))) {
    struct cl_compiler_ref ref = c_fun_ref(env, function);
    switch(ref.place) {
    case ECL_CMPREF_UNDEFINED:
      /* Globally defined function */
      asm_op2c(env, OP_FUNCTION, function);
      return FLAG_REG0;
    case ECL_CMPREF_LOCAL:
      /* Function from a FLET/LABELS form */
      asm_op2(env, OP_LFUNCTION, ref.index);
      return FLAG_REG0;
    case ECL_CMPREF_CLOSE:
      /* Function from a FLET/LABELS form (cfb) */
      asm_op2(env, OP_CFUNCTION, ref.index);
      return FLAG_REG0;
    default:
      ecl_miscompilation_error();
    }
  }
  if (CONSP(function)) {
    cl_object kind = ECL_CONS_CAR(function);
    cl_object body = ECL_CONS_CDR(function);
    cl_object name;
    if (kind == @'lambda') {
      name = ECL_NIL;
    } else if (kind == @'ext::lambda-block') {
      name = ECL_CONS_CAR(body);
      body = ECL_CONS_CDR(body);
    } else {
      goto ERROR;
    }

    cl_object lambda = ecl_make_lambda(env, name, body);
    cl_object cfb = ecl_nth_value(env, 1);
    if (Null(cfb)) {
      /* No closure */
      asm_op2c(env, OP_QUOTE, lambda);
    } else {
      /* Close around referenced objects */
      asm_op2c(env, OP_CLOSE, lambda);
    }
    return FLAG_REG0;
  }
 ERROR:
  FEprogram_error("FUNCTION: Not a valid argument ~S.", 1, function);
}

static int
c_go(cl_env_ptr env, cl_object args, int flags) {
  cl_object tag = pop(&args);
  if (!Null(args)) {
    FEprogram_error("GO: Too many arguments.",0);
  }
  if (Null(tag)) {
    tag = ECL_NIL_SYMBOL;
  }
  struct cl_compiler_ref ref = c_tag_ref(env, tag);
  switch(ref.place) {
  case ECL_CMPREF_UNDEFINED:
    FEprogram_error("GO: Unknown tag ~S.", 1, tag);
  case ECL_CMPREF_LOCAL:
    asm_op(env, OP_GO);
    asm_arg(env, ref.index);
    asm_arg(env, ref.label);
    break;
  case ECL_CMPREF_CLOSE:
    asm_op(env, OP_GO_CFB);
    asm_arg(env, ref.index);
    asm_arg(env, ref.label);
    break;
  default:
    ecl_miscompilation_error();
  }
  return flags;
}

/*
  (if a b) -> (cond (a b))
  (if a b c) -> (cond (a b) (t c))
*/
static int
c_if(cl_env_ptr env, cl_object form, int flags) {
  cl_object test = pop(&form);
  cl_object then = pop(&form);
  then = cl_list(2, test, then);
  if (Null(form)) {
    return c_cond(env, ecl_list1(then), flags);
  } else {
    return c_cond(env, cl_list(2, then, CONS(ECL_T, form)), flags);
  }
}


static int
c_labels(cl_env_ptr env, cl_object args, int flags) {
  return c_labels_flet(env, OP_LABELS, args, flags);
}


/*
  The OP_PUSHENV saves the current lexical environment to allow
  several bindings.
  OP_PUSHENV
  ...             ; binding forms
  ...             ; body
  OP_EXIT

  There are four forms which perform bindings
  OP_PBIND name   ; Bind NAME in the lexical env. using
  ; a value from the stack
  OP_PBINDS name  ; Bind NAME as special variable using
  ; a value from the stack
  OP_BIND name    ; Bind NAME in the lexical env. using
  ; VALUES(0)
  OP_BINDS name   ; Bind NAME as special variable using
  ; VALUES(0)

  After a variable has been bound, there are several ways to
  refer to it.

  1) Refer to the n-th variable in the lexical environment
  [SYMVAL + n]

  2) Refer to the value of a special variable or constant
  SYMVALS
  name

  3) Push the value of the n-th variable of the lexical environment
  [PUSHV + n]

  4) Push the value of a special variable or constant
  PUSHVS
  name
*/

static int
c_let_leta(cl_env_ptr env, int op, cl_object args, int flags) {
  cl_object bindings, specials, body, l, vars;
  cl_object old_variables = env->c_env->variables;

  bindings = cl_car(args);
  body = c_process_declarations(ECL_CONS_CDR(args));
  specials = env->values[3];

  /* Optimize some common cases */
  if (bindings == ECL_NIL)
    return c_locally(env, CDR(args), flags);
  if (ECL_CONS_CDR(bindings) == ECL_NIL)
    op = OP_BIND;

  for (vars=ECL_NIL, l=bindings; !Null(l); ) {
    cl_object aux = pop(&l);
    cl_object var, value;
    if (ECL_ATOM(aux)) {
      var = aux;
      value = ECL_NIL;
    } else {
      var = pop(&aux);
      value = pop_maybe_nil(&aux);
      if (!Null(aux))
        FEprogram_error("LET: Ill formed declaration.",0);
    }
    if (!ECL_SYMBOLP(var))
      FEillegal_variable_name(var);
    if (ecl_symbol_type(var) & ecl_stp_constant)
      FEbinding_a_constant(var);
    if (op == OP_PBIND) {
      compile_form(env, value, FLAG_PUSH);
      if (ecl_member_eq(var, vars))
        FEprogram_error
          ("LET: The variable ~s occurs more than "
           "once in the LET.", 1, var);
      vars = CONS(var, vars);
    } else {
      compile_form(env, value, FLAG_REG0);
      c_bind(env, var, specials);
    }
  }
  while (!Null(vars))
    c_pbind(env, pop(&vars), specials);

  /* We have to register all specials, because in the list
   * there might be some variable that is not bound by this LET form
   */
  c_declare_specials(env, specials);

  flags = compile_body(env, body, flags);

  c_undo_bindings(env, old_variables, 0);
  return flags;
}

static int
c_let(cl_env_ptr env, cl_object args, int flags) {
  return c_let_leta(env, OP_PBIND, args, flags);
}

static int
c_leta(cl_env_ptr env, cl_object args, int flags) {
  return c_let_leta(env, OP_BIND, args, flags);
}

static int
c_load_time_value(cl_env_ptr env, cl_object args, int flags)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object value;
  unlikely_if (Null(args) || cl_cddr(args) != ECL_NIL)
    FEprogram_error("LOAD-TIME-VALUE: Wrong number of arguments.", 0);
  value = ECL_CONS_CAR(args);
  if (c_env->mode == FLAG_EXECUTE) {
    value = si_eval_with_env(1, value);
  } else if (ECL_SYMBOLP(value) || ECL_LISTP(value)) {
    /* Using the form as constant, we force the system to coalesce multiple
     * copies of the same load-time-value form */
    push(cl_list(3, args, value, ECL_NIL), &c_env->load_time_forms);
    value = args;
  }
  return compile_constant(env, value, flags);
}

static int
c_locally(cl_env_ptr env, cl_object args, int flags) {
  cl_object old_env = env->c_env->variables;

  /* First use declarations by declaring special variables... */
  args = c_process_declarations(args);
  c_declare_specials(env, env->values[3]);

  /* ...and then process body */
  flags = compile_toplevel_body(env, args, flags);

  c_undo_bindings(env, old_env, 0);

  return flags;
}

/*
  MACROLET

  The current lexical environment is saved. A new one is prepared with the
  definitions of these macros, and this environment is used to compile the body.
*/
static int
c_macrolet(cl_env_ptr the_env, cl_object args, int flags)
{
  const cl_compiler_ptr c_env = the_env->c_env;
  cl_object old_env = c_env->macros;
  cl_object env = funcall(3, @'si::cmp-env-register-macrolet', pop(&args),
                          CONS(c_env->variables, c_env->macros));
  c_env->macros = CDR(env);
  flags = c_locally(the_env, args, flags);
  c_env->macros = old_env;
  return flags;
}

static void
c_vbind(cl_env_ptr env, cl_object var, int n, cl_object specials)
{
  if (c_declared_special(var, specials)) {
    c_register_var(env, var, FLAG_PUSH, TRUE);
    if (n) {
      asm_op2(env, OP_VBINDS, n);
    } else {
      asm_op(env, OP_BINDS);
    }
  } else {
    c_register_var(env, var, FALSE, TRUE);
    if (n) {
      asm_op2(env, OP_VBIND, n);
    } else {
      asm_op(env, OP_BIND);
    }
  }
  asm_arg_data(env, var);
}

static int
c_multiple_value_bind(cl_env_ptr env, cl_object args, int flags)
{
  cl_object vars = pop(&args);
  int n = ecl_length(vars);
  switch (n) {
  case 0:
    compile_form(env, pop(&args), FLAG_IGNORE);
    return c_locally(env, args, flags);
  case 1:
    vars = ECL_CONS_CAR(vars);
    vars = ecl_list1(cl_list(2, vars, pop(&args)));
    return c_leta(env, cl_listX(2, vars, args), flags);
  default: {
    cl_object value = pop(&args);
    cl_object old_variables = env->c_env->variables;
    cl_object body = c_process_declarations(args);
    cl_object specials = env->values[3];
    compile_form(env, value, FLAG_VALUES);
    for (vars=cl_reverse(vars); n--; ) {
      cl_object var = pop(&vars);
      if (!ECL_SYMBOLP(var))
        FEillegal_variable_name(var);
      if (ecl_symbol_type(var) & ecl_stp_constant)
        FEbinding_a_constant(var);
      c_vbind(env, var, n, specials);
    }
    c_declare_specials(env, specials);
    flags = compile_body(env, body, flags);
    c_undo_bindings(env, old_variables, 0);
    return flags;
  }}
}


static int
c_multiple_value_call(cl_env_ptr env, cl_object args, int flags) {
  cl_object name;
  int op;

  name = pop(&args);
  if (Null(args)) {
    /* If no arguments, just use ordinary call */
    return c_funcall(env, cl_list(1, name), flags);
  }
  compile_form(env, name, FLAG_PUSH);
  for (op = OP_PUSHVALUES; !Null(args); op = OP_PUSHMOREVALUES) {
    compile_form(env, pop(&args), FLAG_VALUES);
    asm_op(env, op);
  }
  asm_op(env, OP_MCALL);
  asm_op(env, OP_POP1);

  return FLAG_VALUES;
}


static int
c_multiple_value_prog1(cl_env_ptr env, cl_object args, int flags) {
  compile_form(env, pop(&args), FLAG_VALUES);
  if (!Null(args)) {
    asm_op(env, OP_PUSHVALUES);
    compile_body(env, args, FLAG_IGNORE);
    asm_op(env, OP_POPVALUES);
  }
  return FLAG_VALUES;
}


static int
c_multiple_value_setq(cl_env_ptr env, cl_object orig_args, int flags) {
  cl_object args = orig_args;
  cl_object orig_vars;
  cl_object vars = ECL_NIL, values;
  cl_object old_variables = env->c_env->variables;
  cl_index nvars = 0;

  /* Look for symbol macros, building the list of variables
     and the list of late assignments. */
  for (orig_vars = pop(&args); !Null(orig_vars); ) {
    cl_object v = pop(&orig_vars);
    if (!ECL_SYMBOLP(v))
      FEillegal_variable_name(v);
    v = c_macro_expand1(env, v);
    if (!ECL_SYMBOLP(v)) {
      /* If any of the places to be set is not a variable,
       * transform MULTIPLE-VALUE-SETQ into (SETF (VALUES ...))
       */
      args = orig_args;
      return compile_form(env, cl_listX(3, @'setf',
                                        CONS(@'values', CAR(args)),
                                        CDR(args)),
                          flags);
    }
    vars = CONS(v, vars);
    nvars++;
  }

  /* Compile values */
  values = pop(&args);
  if (args != ECL_NIL)
    FEprogram_error("MULTIPLE-VALUE-SETQ: Too many arguments.", 0);
  if (nvars == 0) {
    /* No variables */
    return compile_form(env, cl_list(2, @'values', values), flags);
  }
  compile_form(env, values, FLAG_VALUES);

  /* Compile variables */
  for (nvars = 0, vars = cl_nreverse(vars); vars != ECL_NIL; nvars++, vars = ECL_CONS_CDR(vars)) {
    if (nvars) {
      compile_setq(env, OP_VSETQ, ECL_CONS_CAR(vars));
      asm_arg(env, nvars);
    } else {
      compile_setq(env, OP_SETQ, ECL_CONS_CAR(vars));
    }
  }

  c_undo_bindings(env, old_variables, 0);

  return FLAG_REG0;
}

/*
  The OP_NOT operator reverses the boolean value of VALUES(0).
*/
static int
c_not(cl_env_ptr env, cl_object args, int flags) {
  flags = maybe_reg0(flags);
  if (flags & FLAG_USEFUL) {
    /* The value is useful */
    compile_form(env, pop(&args), FLAG_REG0);
    asm_op(env, OP_NOT);
  } else {
    /* The value may be ignored. */
    flags = compile_form(env, pop(&args), flags);
  }
  if (!Null(args))
    FEprogram_error("NOT/NULL: Too many arguments.", 0);
  return flags;
}

/*
  The OP_NTHVAL operator moves a value from VALUES(ndx) to
  VALUES(0). The index NDX is taken from the stack.

  OP_NTHVAL
*/
static int
c_nth_value(cl_env_ptr env, cl_object args, int flags) {
  compile_form(env, pop(&args), FLAG_PUSH);       /* INDEX */
  compile_form(env, pop(&args), FLAG_VALUES);     /* VALUES */
  if (args != ECL_NIL)
    FEprogram_error("NTH-VALUE: Too many arguments.",0);
  asm_op(env, OP_NTHVAL);
  return FLAG_REG0;
}


static int
c_prog1(cl_env_ptr env, cl_object args, int flags) {
  cl_object form = pop(&args);
  if (!(flags & FLAG_USEFUL) || (flags & FLAG_PUSH)) {
    flags = compile_form(env, form, flags);
    compile_body(env, args, FLAG_IGNORE);
  } else {
    flags = FLAG_REG0;
    compile_form(env, form, FLAG_PUSH);
    compile_body(env, args, FLAG_IGNORE);
    asm_op(env, OP_POP);
  }
  return flags;
}


/*
  The OP_PROGV operator exectures a set of statements in a lexical
  environment that has been extended with special variables. The
  list of special variables is taken from the top of the stack,
  while the list of values is in VALUES(0).

  ...             ; list of variables
  OP_PUSH
  ...             ; list of values
  OP_PROGV
  ...             ; body of progv
  OP_EXIT
*/
static int
c_progv(cl_env_ptr env, cl_object args, int flags) {
  cl_object vars = pop(&args);
  cl_object values = pop(&args);

  /* The list of variables is in the stack */
  compile_form(env, vars, FLAG_PUSH);

  /* The list of values is in reg0 */
  compile_form(env, values, FLAG_REG0);

  /* The body is interpreted within an extended lexical
     environment. However, as all the new variables are
     special, the compiler need not take care of them
  */
  asm_op(env, OP_PROGV);
  flags = compile_body(env, args, FLAG_VALUES);
  asm_op(env, OP_EXIT_PROGV);

  return flags;
}


/*
  There are four assignment operators. They are

  1) Assign VALUES(0) to the lexical variable which occupies the
  N-th position
  [OP_SETQ + n]

  2) Assign VALUES(0) to the special variable NAME
  [OP_SETQS + name]

  3) Pop a value from the stack and assign it to the lexical
  variable in the N-th position.
  [OP_PSETQ + n]

  4) Pop a value from the stack and assign it to the special
  variable denoted by NAME
  [OP_PSETQS + name]
*/
static int
c_psetq(cl_env_ptr env, cl_object old_args, int flags) {
  cl_object args = ECL_NIL, vars = ECL_NIL;
  bool use_psetf = FALSE;
  cl_index nvars = 0;

  if (Null(old_args))
    return compile_body(env, ECL_NIL, flags);
  /* We have to make sure that non of the variables which
     are to be assigned is actually a symbol macro. If that
     is the case, we invoke (PSETF ...) to handle the
     macro expansions.
  */
  do {
    cl_object var = pop(&old_args);
    cl_object value = pop(&old_args);
    if (!ECL_SYMBOLP(var))
      FEillegal_variable_name(var);
    var = c_macro_expand1(env, var);
    if (!ECL_SYMBOLP(var))
      use_psetf = TRUE;
    args = ecl_nconc(args, cl_list(2, var, value));
    nvars++;
  } while (!Null(old_args));
  if (use_psetf) {
    return compile_form(env, CONS(@'psetf', args), flags);
  }
  do {
    cl_object var = pop(&args);
    cl_object value = pop(&args);
    vars = CONS(var, vars);
    compile_form(env, value, FLAG_PUSH);
  } while (!Null(args));
  do {
    compile_setq(env, OP_PSETQ, pop(&vars));
  } while (!Null(vars));
  return compile_form(env, ECL_NIL, flags);
}


/*
  The OP_RETURN operator returns from a block putting result in VALUES().

  ...             ; output form
  OP_RETURN | OP_RETURN_CFB
  idx             ; index of the output block
*/
static int
c_return_aux(cl_env_ptr env, cl_object name, cl_object args, int flags)
{
  struct cl_compiler_ref ref = c_blk_ref(env, name);
  cl_object output = pop_maybe_nil(&args);
  if (!Null(args))
    FEprogram_error("RETURN-FROM: Too many arguments.", 0);
  switch(ref.place) {
  case ECL_CMPREF_UNDEFINED:
    FEprogram_error("RETURN-FROM: Unknown block name ~S.", 1, name);
  case ECL_CMPREF_LOCAL:
    compile_form(env, output, FLAG_VALUES);
    asm_op2(env, OP_RETURN, ref.index);
    break;
  case ECL_CMPREF_CLOSE:
    compile_form(env, output, FLAG_VALUES);
    asm_op2(env, OP_RETURN_CFB, ref.index);
    break;
  default:
    ecl_miscompilation_error();
  }
  return FLAG_VALUES;
}

static int
c_return(cl_env_ptr env, cl_object stmt, int flags) {
  return c_return_aux(env, ECL_NIL, stmt, flags);
}


static int
c_return_from(cl_env_ptr env, cl_object stmt, int flags) {
  cl_object name = pop(&stmt);
  return c_return_aux(env, name, stmt, flags);
}


static int
c_setq(cl_env_ptr env, cl_object args, int flags) {
  if (Null(args))
    return compile_form(env, ECL_NIL, flags);
  do {
    cl_object var = pop(&args);
    cl_object value = pop(&args);
    if (!ECL_SYMBOLP(var))
      FEillegal_variable_name(var);
    var = c_macro_expand1(env, var);
    if (ECL_SYMBOLP(var)) {
      flags = FLAG_REG0;
      compile_form(env, value, FLAG_REG0);
      compile_setq(env, OP_SETQ, var);
    } else {
      flags = ecl_endp(args)? FLAG_VALUES : FLAG_REG0;
      compile_form(env, cl_list(3, @'setf', var, value), flags);
    }
  } while (!Null(args));
  return flags;
}


static int
c_symbol_macrolet(cl_env_ptr env, cl_object args, int flags)
{
  cl_object def_list, specials, body;
  cl_object old_variables = env->c_env->variables;

  def_list = pop(&args);
  body = c_process_declarations(args);
  specials = env->values[3];

  /* Scan the list of definitions */
  while (!Null(def_list)) {
    cl_object definition = pop(&def_list);
    cl_object name = pop(&definition);
    cl_object expansion = pop(&definition);
    cl_object arglist = cl_list(2, @gensym(0), @gensym(0));
    cl_object function;
    if ((ecl_symbol_type(name) & (ecl_stp_constant|ecl_stp_special)) ||
        ecl_member_eq(name, specials))
      {
        FEprogram_error("SYMBOL-MACROLET: Symbol ~A cannot be \
declared special and appear in a symbol-macrolet.", 1, name);
      }
    definition = cl_list(2, arglist, cl_list(2, @'quote', expansion));
    function = ecl_make_lambda(env, name, definition);
    c_register_symbol_macro(env, name, function);
  }
  c_declare_specials(env, specials);
  flags = compile_toplevel_body(env, body, flags);
  c_undo_bindings(env, old_variables, 0);
  return flags;
}

static int
c_tagbody(cl_env_ptr env, cl_object args, int flags)
{
  cl_object old_env = env->c_env->variables;
  cl_index tag_base;
  cl_object labels = ECL_NIL, label, body;
  cl_type item_type;
  int nt, i;

  /* count the tags */
  for (nt = 0, body = args; !Null(body); ) {
    label = pop(&body);
    if (Null(label)) {
      label = ECL_NIL_SYMBOL;
    }
    item_type = ecl_t_of(label);
    if (item_type == t_symbol || item_type == t_fixnum ||
        item_type == t_bignum) {
      labels = CONS(CONS(label,ecl_make_fixnum(nt)), labels);
      nt += 1;
    }
  }
  if (nt == 0) {
    compile_body(env, args, 0);
    return compile_form(env, ECL_NIL, flags);
  }
  asm_op2c(env, OP_BLOCK, ecl_make_fixnum(0));
  c_register_tags(env, labels);
  asm_op2(env, OP_TAGBODY, nt);
  tag_base = current_pc(env);
  for (i = nt; i; i--)
    asm_arg(env, 0);

  for (body = args; !Null(body); ) {
    label = pop(&body);
    if (Null(label)) {
      label = ECL_NIL_SYMBOL;
    }
    item_type = ecl_t_of(label);
    if (item_type == t_symbol || item_type == t_fixnum ||
        item_type == t_bignum) {
      asm_complete(env, 0, tag_base);
      tag_base += OPARG_SIZE;
    } else {
      compile_form(env, label, FLAG_IGNORE);
    }
  }
  asm_op(env, OP_EXIT_TAGBODY);
  c_undo_bindings(env, old_env, 0);
  return FLAG_REG0;
}

static int
c_the(cl_env_ptr env, cl_object stmt, int flags) {
  cl_object value;
  /* ignore first element */
  pop(&stmt);
  value = pop(&stmt);
  if (stmt != ECL_NIL) {
    FEprogram_error("THE: Too many arguments",0);
  }
  return compile_form(env, value, flags);
}

/*
  The OP_THROW jumps to an enclosing OP_CATCH whose tag
  matches the one of the throw. The tag is taken from the
  stack, while the output values are left in VALUES().
*/
static int
c_throw(cl_env_ptr env, cl_object stmt, int flags) {
  cl_object tag = pop(&stmt);
  cl_object form = pop(&stmt);
  if (stmt != ECL_NIL)
    FEprogram_error("THROW: Too many arguments.",0);
  compile_form(env, tag, FLAG_PUSH);
  compile_form(env, form, FLAG_VALUES);
  asm_op(env, OP_THROW);
  return flags;
}


static int
c_unwind_protect(cl_env_ptr env, cl_object args, int flags) {
  cl_index label = asm_jmp(env, OP_PROTECT);

  /* We register unwind-protect boundary. This mark is not used in bytecode
     compiler but we do it anyway to have better compilation environment. */
  c_register_boundary(env, @'si::unwind-protect-boundary');

  flags = maybe_values(flags);

  /* Compile form to be protected */
  flags = compile_form(env, pop(&args), flags);
  asm_op(env, OP_PROTECT_NORMAL);

  /* Compile exit clause */
  asm_complete(env, OP_PROTECT, label);
  compile_body(env, args, FLAG_IGNORE);
  asm_op(env, OP_PROTECT_EXIT);

  return flags;
}


/*
  The OP_VALUES moves N values from the stack to VALUES().

  [OP_VALUES + n]
*/
static int
c_values(cl_env_ptr env, cl_object args, int flags) {
  if (!(flags & FLAG_USEFUL)) {
    /* This value will be discarded. We do not care to
       push it or to save it in VALUES */
    if (Null(args))
      return flags;
    return compile_body(env, args, flags);
  } else if (flags & FLAG_PUSH) {
    /* We only need the first value. However, the rest
       of arguments HAVE to be be evaluated */
    if (Null(args))
      return compile_form(env, ECL_NIL, flags);
    flags = compile_form(env, pop(&args), FLAG_PUSH);
    compile_body(env, args, FLAG_IGNORE);
    return flags;
  } else if (Null(args)) {
    asm_op(env, OP_NOP);
  } else {
    int n = 0;
    while (!Null(args)) {
      compile_form(env, pop_maybe_nil(&args), FLAG_PUSH);
      n++;
    }
    asm_op2(env, OP_VALUES, n);
  }
  return FLAG_VALUES;
}

static void
defer_load_object(cl_env_ptr env, cl_object place, cl_object created)
{
  const cl_compiler_ptr c_env = env->c_env;
  if (c_env->ltf_defer_init_until == ECL_T) {
    FEerror("Circular dependency in load time forms involving ~S.", 1, ECL_CONS_CAR(place));
  }
  if (c_env->ltf_defer_init_until != ECL_NIL
      && ecl_member_eq(c_env->ltf_defer_init_until, created)) {
    /* We are already deferring the init form long enough, nothing to do. */
    return;
  }
  c_env->ltf_defer_init_until = place;
}

static void
maybe_make_load_forms(cl_env_ptr env, cl_object constant)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object init, make, created;
  if ((c_env->mode != FLAG_LOAD)
      || (si_need_to_make_load_form_p(constant) == ECL_NIL))
    return;
  created = c_env->ltf_being_created;
  /* If we are compiling a creation form for another load time form, defer the
   * init form until after this creation form has been compiled. */
  loop_for_in(created) {
    cl_object place = ECL_CONS_CAR(created);
    if (ECL_CONS_CAR(place) == constant) {
      defer_load_object(env, place, created);
      return;
    }
  } end_loop_for_in;
  make = _ecl_funcall2(@'make-load-form', constant);
  init = (env->nvalues > 1)? env->values[1] : ECL_NIL;
  push(cl_list(3, constant, make, init), &c_env->load_time_forms);
}

static int
compile_constant(cl_env_ptr env, cl_object stmt, int flags)
{
  if (flags & FLAG_USEFUL) {
    bool push = flags & FLAG_PUSH;
    cl_fixnum n;
    maybe_make_load_forms(env, stmt);
    if (stmt == ECL_NIL) {
      asm_op(env, push? OP_PUSHNIL : OP_NIL);
    } else if (ECL_FIXNUMP(stmt) && (n = ecl_fixnum(stmt)) <= MAX_OPARG
               && n >= -MAX_OPARG) {
      asm_op2(env, push? OP_PINT : OP_INT, n);
    } else {
      asm_op2c(env, push? OP_PUSHQ : OP_QUOTE, stmt);
    }
    if (flags & FLAG_VALUES)
      flags = (flags & ~FLAG_VALUES) | FLAG_REG0;
  }
  return flags;
}

static int
c_quote(cl_env_ptr env, cl_object args, int flags)
{
  if (ECL_ATOM(args) || ECL_CONS_CDR(args) != ECL_NIL)
    FEill_formed_input();
  return compile_constant(env, ECL_CONS_CAR(args), flags);
}

static int
compile_symbol(cl_env_ptr env, cl_object stmt, int flags)
{
  cl_object stmt1 = c_macro_expand1(env, stmt);
  if (stmt1 != stmt) {
    return compile_form(env, stmt1, flags);
  } else {
    struct cl_compiler_ref ref = c_var_ref(env, stmt, FALSE, FALSE);
    bool push = flags & FLAG_PUSH;
    int op = c_var_ref_fix_op(ref, push ? OP_PUSHV : OP_VAR);
    switch (ref.label) {
    case ECL_CMPVAR_LEXICAL:
      asm_op2(env, op, ref.index);
      break;
    case ECL_CMPVAR_SPECIAL:
    case ECL_CMPVAR_UNDEFINED:
      asm_op2c(env, op, stmt);
      break;
    default:
      ecl_miscompilation_error();
    }
    if (flags & FLAG_VALUES)
      return (flags & ~FLAG_VALUES) | FLAG_REG0;
    else
      return flags;
  }
}

static int
compile_form(cl_env_ptr env, cl_object stmt, int flags) {
  const cl_compiler_ptr c_env = env->c_env;
  cl_object function;
  int new_flags;

  ecl_bds_bind(env, @'si::*current-form*', stmt);
 BEGIN:
  if (c_env->code_walker != OBJNULL) {
    stmt = funcall(3, c_env->code_walker, stmt,
                   CONS(c_env->variables, c_env->macros));
  }
  /*
   * First try with variable references and quoted constants
   */
  if (Null(stmt)) {
    new_flags = compile_constant(env, stmt, flags);
    goto OUTPUT;
  }
  if (!ECL_LISTP(stmt)) {
    if (ECL_SYMBOLP(stmt)) {
      new_flags = compile_symbol(env, stmt, flags);
    } else {
      new_flags = compile_constant(env, stmt, flags);
    }
    goto OUTPUT;
  }
  /*
   * Next try with special forms.
   */
  function = ECL_CONS_CAR(stmt);
  if (ECL_SYMBOLP(function)) {
    cl_object index = ecl_gethash(function, cl_core.compiler_dispatch);
    if (index != OBJNULL) {
      compiler_record *l = database + ecl_fixnum(index);
      c_env->lexical_level += l->lexical_increment;
      if (c_env->stepping && function != @'function' && c_env->lexical_level)
        asm_op2c(env, OP_STEPIN, stmt);
      new_flags = (*(l->compiler))(env, ECL_CONS_CDR(stmt), flags);
      if (c_env->stepping && function != @'function' && c_env->lexical_level)
        asm_op(env, OP_STEPOUT);
      c_env->lexical_level -= l->lexical_increment;
      goto OUTPUT;
    }
    /*
     * Next try to macroexpand
     */
    cl_object new_stmt = c_macro_expand1(env, stmt);
    if (new_stmt != stmt) {
      stmt = new_stmt;
      goto BEGIN;
    }
  }
  /*
   * Finally resort to ordinary function calls.
   */
  if (c_env->stepping) {
    asm_op2c(env, OP_STEPIN, stmt);
  }
  c_env->lexical_level++;
  new_flags = c_call(env, stmt, flags);
  c_env->lexical_level--;
 OUTPUT:
  /*
    flags           new_flags               action
    PUSH            PUSH                    ---
    PUSH            VALUES                  OP_PUSH
    PUSH            REG0                    OP_PUSH
    VALUES          PUSH                    Impossible
    VALUES          VALUES                  ---
    VALUES          REG0                    OP_VALUEREG0
    REG0            PUSH                    Impossible
    REG0            VALUES                  ---
    REG0            REG0                    ---
  */
  if (flags & FLAG_PUSH) {
    if (new_flags & (FLAG_REG0 | FLAG_VALUES))
      asm_op(env, OP_PUSH);
  } else if (flags & FLAG_VALUES) {
    if (new_flags & FLAG_REG0) {
      asm_op(env, OP_VALUEREG0);
    } else if (new_flags & FLAG_PUSH) {
      FEerror("Internal error in bytecodes compiler", 0);
    }
  } else if (new_flags & FLAG_PUSH) {
    FEerror("Internal error in bytecodes compiler", 0);
  }
  ecl_bds_unwind1(env);
  return flags;
}

static void
eval_nontrivial_form(cl_env_ptr env, cl_object form) {
  const cl_compiler_ptr old_c_env = env->c_env;
  struct cl_compiler_env new_c_env = *old_c_env;
  cl_index handle;
  cl_object bytecodes;
  struct ecl_stack_frame frame;
  frame.t = t_frame;
  frame.opened = 0;
  frame.base = 0;
  frame.size = 0;
  frame.sp = 0;
  frame.env = env;
  env->nvalues = 0;
  env->values[0] = ECL_NIL;
  new_c_env.constants = si_make_vector(ECL_T, ecl_make_fixnum(16),
                                       ECL_T, /* Adjustable */
                                       ecl_make_fixnum(0), /* Fillp */
                                       ECL_NIL, /* displacement */
                                       ECL_NIL);
  new_c_env.load_time_forms = ECL_NIL;
  new_c_env.ltf_being_created = ECL_NIL;
  new_c_env.ltf_defer_init_until = ECL_T;
  new_c_env.ltf_locations = ECL_NIL;
  new_c_env.captured = ECL_NIL;
  new_c_env.parent_env = NULL;
  new_c_env.env_depth = 0;
  new_c_env.env_width = 0;
  new_c_env.env_size = 0;
  env->c_env = &new_c_env;
  handle = asm_begin(env);
  compile_with_load_time_forms(env, form, FLAG_VALUES);
  if (current_pc(env) != handle) {
    asm_op(env, OP_EXIT);
    bytecodes = asm_end(env, handle, form);
    env->values[0] = ecl_interpret((cl_object)&frame,
                                   new_c_env.lex_env,
                                   bytecodes);
#ifdef GBC_BOEHM
    GC_FREE(bytecodes->bytecodes.code);
    GC_FREE(bytecodes);
#endif
  }
  env->c_env = old_c_env;
}

static void
eval_form(cl_env_ptr env, cl_object form) {
  if (ECL_LISTP(form) || ECL_SYMBOLP(form)) {
    eval_nontrivial_form(env, form);
  } else {
    env->values[0] = form;
    env->nvalues = 1;
  }
}

static int
execute_each_form(cl_env_ptr env, cl_object body)
{
  cl_object form = ECL_NIL, next_form;
  for (form = ECL_NIL; !Null(body); form = next_form) {
    unlikely_if (!ECL_LISTP(body))
      FEtype_error_proper_list(body);
    next_form = ECL_CONS_CAR(body);
    body = ECL_CONS_CDR(body);
    eval_form(env, form);
  }
  eval_form(env, form);
  return FLAG_VALUES;
}

static cl_object
save_bytecodes(cl_env_ptr env, cl_index start, cl_index end)
{
  cl_index l = end - start;
  cl_object bytecodes = ecl_alloc_simple_vector(l, ecl_aet_index);
  cl_index *p;
  for (p = bytecodes->vector.self.index; end > start; end--, p++) {
    *p = (cl_index)ECL_STACK_POP_UNSAFE(env);
  }
  return bytecodes;
}

static void
restore_bytecodes(cl_env_ptr env, cl_object bytecodes)
{
  cl_index *p = bytecodes->vector.self.index;
  cl_index l;
  for (l = bytecodes->vector.dim; l; l--) {
    ECL_STACK_PUSH(env, (cl_object)p[l-1]);
  }
  ecl_dealloc(bytecodes);
}

static cl_index
add_load_form(cl_env_ptr env, cl_object object)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_object constant = pop(&object);
  cl_object make_form = pop(&object);
  cl_object init_form = pop(&object);
  cl_object deferred_init_forms;
  cl_index loc = c_register_constant(env, constant);
  {
    cl_object previous_locs = c_env->ltf_locations;
    loop_for_in(previous_locs) {
      if (ecl_fixnum(ECL_CONS_CAR(previous_locs)) == loc) {
        /* We already compiled this load time form, nothing to do */
        return loc;
      }
    } end_loop_for_in;
  }
  /* compile the MAKE-FORM */
  /* c_env->ltf_being_created holds a list with the constant whose
   * creation form is being compiled as first element... */
  push(ecl_list1(constant), &c_env->ltf_being_created);
  compile_with_load_time_forms(env, make_form, FLAG_REG0);
  asm_op2(env, OP_CSET, loc);
  /* ... and bytecodes for init forms which need to be deferred
   * until the creation form has been evaluated in the following
   * elements */
  deferred_init_forms = ECL_CONS_CDR(pop(&c_env->ltf_being_created));
  /* save the location of the created constant. This also serves as an
   * indicator that we already compiled the load form for constant and
   * don't need to do that again if we encouter constant in any other
   * load time forms. */
  push(ecl_make_fixnum(loc), &c_env->ltf_locations);
  /* compile the INIT-FORM ... */
  if (init_form != ECL_NIL) {
    cl_index handle_init = current_pc(env);
    cl_object old_init_until = c_env->ltf_defer_init_until;
    c_env->ltf_defer_init_until = ECL_NIL;
    compile_with_load_time_forms(env, init_form, FLAG_IGNORE);
    /* ... and if it needs to be deferred, add it to c_env->ltf_being_created */
    if (c_env->ltf_defer_init_until != ECL_NIL
        && c_env->ltf_defer_init_until != object) {
      cl_object bytecodes_init = save_bytecodes(env, handle_init, current_pc(env));
      cl_object l = si_memq(c_env->ltf_defer_init_until, c_env->ltf_being_created);
      if (l != ECL_NIL) {
        cl_object constant_and_inits = ECL_CONS_CAR(l);
        ECL_RPLACD(constant_and_inits,
                   CONS(bytecodes_init, ECL_CONS_CDR(constant_and_inits)));
      }
    }
    c_env->ltf_defer_init_until = old_init_until;
  }
  /* restore bytecodes for deferred init-forms. This comes after
   * compiling the init form for constant since we are required to
   * evaluate init forms as soon as possible. */
  loop_for_in(deferred_init_forms) {
    restore_bytecodes(env, ECL_CONS_CAR(deferred_init_forms));
  } end_loop_for_in;
  return loc;
}


/* First we compile the form as usual. If some constants need to be built,
 * insert the code _before_ the actual forms; to do that we first save the
 * bytecodes for the form, and then we compile forms that build constants;
 * only after that we restore bytecodes of the compiled form. */
static int
compile_with_load_time_forms(cl_env_ptr env, cl_object form, int flags)
{
  const cl_compiler_ptr c_env = env->c_env;
  cl_index handle = asm_begin(env);
  int output_flags = compile_form(env, form, flags);
  if (c_env->load_time_forms != ECL_NIL) {
    /* load_time_forms are collected in a reverse order, so we need to reverse
       the list. Forms should not be compiled as top-level forms - to ensure
       that we increment the lexical_level. */
    cl_object bytecodes = save_bytecodes(env, handle, current_pc(env));
    cl_object p = cl_nreverse(c_env->load_time_forms);
    c_env->load_time_forms = ECL_NIL;
    c_env->lexical_level++;
    loop_for_in(p) {
      add_load_form(env, ECL_CONS_CAR(p));
    } end_loop_for_in;
    c_env->lexical_level--;
    restore_bytecodes(env, bytecodes);
  }
  return output_flags;
}

static int
compile_each_form(cl_env_ptr env, cl_object body, int flags)
{
  cl_object form = ECL_NIL, next_form;
  for (form = ECL_NIL; !Null(body); form = next_form) {
    unlikely_if (!ECL_LISTP(body))
      FEtype_error_proper_list(body);
    next_form = ECL_CONS_CAR(body);
    body = ECL_CONS_CDR(body);
    compile_with_load_time_forms(env, form, FLAG_IGNORE);
  }
  return compile_with_load_time_forms(env, form, flags);
}

static int
compile_toplevel_body(cl_env_ptr env, cl_object body, int flags)
{
  const cl_compiler_ptr c_env = env->c_env;
  if (!c_env->lexical_level) {
    if (c_env->mode == FLAG_EXECUTE)
      return execute_each_form(env, body);
    else
      return compile_each_form(env, body, flags);
  } else {
    return compile_body(env, body, flags);
  }
}

static int
compile_body(cl_env_ptr env, cl_object body, int flags)
{
  cl_object form = ECL_NIL, next_form;
  for (form = ECL_NIL; !Null(body); form = next_form) {
    unlikely_if (!ECL_LISTP(body))
      FEtype_error_proper_list(body);
    next_form = ECL_CONS_CAR(body);
    body = ECL_CONS_CDR(body);
    compile_form(env, form, FLAG_IGNORE);
  }
  return compile_form(env, form, flags);
}

/* ------------------------ INLINED FUNCTIONS -------------------------------- */

static int
c_cons(cl_env_ptr env, cl_object args, int flags)
{
  if (ecl_length(args) != 2) {
    FEprogram_error("CONS: Wrong number of arguments", 0);
  }
  compile_form(env, cl_first(args), FLAG_PUSH);
  compile_form(env, cl_second(args), FLAG_REG0);
  asm_op(env, OP_CONS);
  return FLAG_REG0;
}

static int
c_endp(cl_env_ptr env, cl_object args, int flags)
{
  cl_object list = pop(&args);
  if (args != ECL_NIL) {
    FEprogram_error("ENDP: Too many arguments", 0);
  }
  compile_form(env, list, FLAG_REG0);
  asm_op(env, OP_ENDP);
  return FLAG_REG0;
}

static int
c_car(cl_env_ptr env, cl_object args, int flags)
{
  cl_object list = pop(&args);
  if (args != ECL_NIL) {
    FEprogram_error("CAR: Too many arguments", 0);
  }
  compile_form(env, list, FLAG_REG0);
  asm_op(env, OP_CAR);
  return FLAG_REG0;
}

static int
c_cdr(cl_env_ptr env, cl_object args, int flags)
{
  cl_object list = pop(&args);
  if (args != ECL_NIL) {
    FEprogram_error("CDR: Too many arguments", 0);
  }
  compile_form(env, list, FLAG_REG0);
  asm_op(env, OP_CDR);
  return FLAG_REG0;
}

static int
c_list_listA(cl_env_ptr env, cl_object args, int flags, int op)
{
  cl_index n = ecl_length(args);
  if (n == 0) {
    return compile_form(env, ECL_NIL, flags);
  } else {
    while (ECL_CONS_CDR(args) != ECL_NIL) {
      compile_form(env, ECL_CONS_CAR(args), FLAG_PUSH);
      args = ECL_CONS_CDR(args);
    }
    compile_form(env, ECL_CONS_CAR(args), FLAG_REG0);
    asm_op2(env, op, n);
    return FLAG_REG0;
  }
}

static int
c_list(cl_env_ptr env, cl_object args, int flags)
{
  return c_list_listA(env, args, flags, OP_LIST);
}

static int
c_listA(cl_env_ptr env, cl_object args, int flags)
{
  return c_list_listA(env, args, flags, OP_LISTA);
}

/* -- Primops --------------------------------------------------------------- */

static int
c_cons_car(cl_env_ptr env, cl_object args, int flags)
{
  cl_object list = pop(&args);
  if (args != ECL_NIL) {
    FEprogram_error("CAR: Too many arguments", 0);
  }
  compile_form(env, list, FLAG_REG0);
  asm_op(env, OP_CONS_CAR);
  return FLAG_REG0;
}

static int
c_cons_cdr(cl_env_ptr env, cl_object args, int flags)
{
  cl_object list = pop(&args);
  if (args != ECL_NIL) {
    FEprogram_error("CDR: Too many arguments", 0);
  }
  compile_form(env, list, FLAG_REG0);
  asm_op(env, OP_CONS_CDR);
  return FLAG_REG0;
}

/* ----------------------------- PUBLIC INTERFACE ---------------------------- */

/* ------------------------------------------------------------
   LAMBDA OBJECTS: An interpreted function is a vector made of
   the following components

   #(LAMBDA
   {block-name | NIL}
   {variable-env | NIL}
   {function-env | NIL}
   {block-env | NIL}
   (list of variables declared special)
   Nreq {var}*                     ; required arguments
   Nopt {var value flag}*          ; optional arguments
   {rest-var NIL}                  ; rest variable
   {T | NIL}                       ; allow other keys?
   Nkey {key var value flag}*      ; keyword arguments
   Naux {var init}                 ; auxiliary variables
   documentation-string
   list-of-declarations
   {form}*                         ; body)

   ------------------------------------------------------------ */

/*
  Determine whether the form can be externalized using the lisp
  printer or we should rather use MAKE-LOAD-FORM.
*/
cl_object
si_need_to_make_load_form_p(cl_object object)
{
  cl_object load_form_cache = cl__make_hash_table(@'eq',
                                                  ecl_make_fixnum(16),
                                                  cl_core.rehash_size,
                                                  cl_core.rehash_threshold);
  cl_object waiting_objects = ecl_list1(object);
  cl_type type = t_start;

 loop:
  if (waiting_objects == ECL_NIL)
    return ECL_NIL;
  object = pop(&waiting_objects);
  type = ecl_t_of(object);
  /* For simple, atomic objects we just return NIL. There is no need
     to call MAKE-LOAD-FORM on them. */
  switch (type) {
  case t_character:
  case t_fixnum:
  case t_bignum:
  case t_ratio:
  case t_singlefloat:
  case t_doublefloat:
  case t_longfloat:
  case t_complex:
#ifdef ECL_COMPLEX_FLOAT
  case t_csfloat:
  case t_cdfloat:
  case t_clfloat:
#endif
#ifdef ECL_SSE2
  case t_sse_pack:
#endif
  case t_symbol:
  case t_pathname:
#ifdef ECL_UNICODE
  case t_string:
#endif
  case t_base_string:
  case t_bitvector:
    goto loop;
  case t_list:
    if (Null(object)) goto loop;
  default:
    ;
  }
  /* For compound objects we set up a cache and run through the objects content
     looking for components that require MAKE-LOAD-FORM to be externalized. The
     cache is used to solve the problem of circularity and of EQ references. */
  if(ecl_gethash(object, load_form_cache))
    goto loop;
  ecl_sethash(object, load_form_cache, ECL_T);
  switch (type) {
  case t_array:
  case t_vector:
    if (object->array.elttype == ecl_aet_object) {
      cl_index i = 0;
      for(; i<object->array.dim; i++) {
        push(object->array.self.t[i], &waiting_objects);
      }
    }
    goto loop;
  case t_list:
    push(ECL_CONS_CDR(object), &waiting_objects);
    push(ECL_CONS_CAR(object), &waiting_objects);
    goto loop;
  case t_bclosure: {
    cl_object bc = object->bclosure.code;
    push(object->bclosure.lex, &waiting_objects);
    push(bc->bytecodes.data, &waiting_objects);
    push(bc->bytecodes.flex, &waiting_objects);
    push(bc->bytecodes.name, &waiting_objects);
    goto loop;
  }
  case t_bytecodes:
    push(object->bytecodes.data, &waiting_objects);
    push(object->bytecodes.flex, &waiting_objects);
    push(object->bytecodes.name, &waiting_objects);
    goto loop;
  default:
    return ECL_T;
  }
  _ecl_unexpected_return();
}

/*
  Handles special declarations, removes declarations from body
*/
@(defun si::process-declarations (body &optional doc)
  cl_object documentation = ECL_NIL, declarations = ECL_NIL, specials = ECL_NIL;
@
  for (; !Null(body); body = ECL_CONS_CDR(body)) {
    cl_object form;
    unlikely_if (!ECL_LISTP(body))
      FEill_formed_input();
    form = ECL_CONS_CAR(body);
    if (!Null(doc) && ecl_stringp(form) && !Null(ECL_CONS_CDR(body))) {
      if (documentation != ECL_NIL)
        break;
      documentation = form;
      continue;
    }
    if (ECL_ATOM(form) || (ECL_CONS_CAR(form) != @'declare')) {
      break;
    }
    for (form = ECL_CONS_CDR(form); !Null(form); ) {
      cl_object sentence = pop(&form);
      push(sentence, &declarations);
      if (pop(&sentence) == @'special') {
        while (!Null(sentence)) {
          cl_object v = pop(&sentence);
          assert_type_symbol(v);
          specials = push(v, &specials);
        }
      }
    }
  }
  @(return cl_nreverse(declarations) body documentation specials);
@)

cl_object
si_process_lambda(cl_object lambda)
{
  cl_object documentation, declarations, specials;
  cl_object lambda_list, body;
  const cl_env_ptr env = ecl_process_env();
  unlikely_if (ECL_ATOM(lambda))
    FEprogram_error("LAMBDA: No lambda list.", 0);

  lambda_list = ECL_CONS_CAR(lambda);
  body = ECL_CONS_CDR(lambda);
  declarations = @si::process-declarations(2, body, ECL_T);
  body = env->values[1];
  documentation = env->values[2];
  specials = env->values[3];

  lambda_list = si_process_lambda_list(lambda_list, @'function');
  {
    cl_index n = env->nvalues;
    env->values[0] = lambda_list;
    env->values[n++] = documentation;
    env->values[n++] = specials;
    env->values[n++] = declarations;
    env->values[n++] = body;
    env->nvalues = n;
  }
  return lambda_list;
}

/*
 * (si::process-lambda-list lambda-list context)
 *
 * Parses different types of lambda lists. CONTEXT may be MACRO,
 * FTYPE, FUNCTION, METHOD or DESTRUCTURING-BIND, and determines the
 * valid sytax. The output is made of several values:
 *
 * VALUES(0) = (N req1 ... )                    ; required values
 * VALUES(1) = (N opt1 init1 flag1 ... )        ; optional values
 * VALUES(2) = rest-var                         ; rest-variable, if any
 * VALUES(3) = key-flag                         ; T if &key was supplied
 * VALUES(4) = (N key1 var1 init1 flag1 ... )   ; keyword arguments
 * VALUES(5) = allow-other-keys                 ; flag &allow-other-keys
 * VALUES(6) = (N aux1 init1 ... )              ; auxiliary variables
 *
 * 1) The prefix "N" is an integer value denoting the number of variables
 * which are declared within this section of the lambda list.
 *
 * 2) The INIT* arguments are lisp forms which are evaluated when no value is
 * provided.
 *
 * 3) The FLAG* arguments is the name of a variable which holds a boolean
 * value in case an optional or keyword argument was provided. If it is NIL,
 * no such variable exists.
 */

cl_object
si_process_lambda_list(cl_object org_lambda_list, cl_object context)
{
#define push(v,l) { cl_object c = *l = CONS(v, *l); l = &ECL_CONS_CDR(c); }
#define assert_var_name(v)                              \
  if (context == @'function') {                         \
    unlikely_if (ecl_symbol_type(v) & ecl_stp_constant) \
      FEillegal_variable_name(v); }
  cl_object lists[4] = {ECL_NIL, ECL_NIL, ECL_NIL, ECL_NIL};
  cl_object *reqs = lists, *opts = lists+1, *keys = lists+2, *auxs = lists+3;
  cl_object v, rest = ECL_NIL, lambda_list = org_lambda_list;
  int nreq = 0, nopt = 0, nkey = 0, naux = 0;
  cl_object allow_other_keys = ECL_NIL;
  cl_object key_flag = ECL_NIL;
  enum {  AT_REQUIREDS,
          AT_OPTIONALS,
          AT_REST,
          AT_KEYS,
          AT_OTHER_KEYS,
          AT_AUXS
  } stage = AT_REQUIREDS;

  if (!ECL_LISTP(lambda_list))
    goto ILLEGAL_LAMBDA;
 LOOP:
  if (Null(lambda_list))
    goto OUTPUT;
  if (!ECL_LISTP(lambda_list)) {
    unlikely_if (context == @'function' || context == @'ftype')
      goto ILLEGAL_LAMBDA;
    v = lambda_list;
    lambda_list = ECL_NIL;
    goto REST;
  }
  v = ECL_CONS_CAR(lambda_list);
  lambda_list = ECL_CONS_CDR(lambda_list);
  if (v == @'&optional') {
    unlikely_if (stage >= AT_OPTIONALS)
      goto ILLEGAL_LAMBDA;
    stage = AT_OPTIONALS;
    goto LOOP;
  }
  if (v == @'&rest'
      || (v == @'&body'
          && (context == @'si::macro' || context == @'destructuring-bind'))) {
    unlikely_if (ECL_ATOM(lambda_list))
      goto ILLEGAL_LAMBDA;
    v = ECL_CONS_CAR(lambda_list);
    lambda_list = ECL_CONS_CDR(lambda_list);
  REST:           unlikely_if (stage >= AT_REST)
      goto ILLEGAL_LAMBDA;
    stage = AT_REST;
    rest = v;
    goto LOOP;
  }
  if (v == @'&key') {
    unlikely_if (stage >= AT_KEYS)
      goto ILLEGAL_LAMBDA;
    key_flag = ECL_T;
    stage = AT_KEYS;
    goto LOOP;
  }
  if (v == @'&aux') {
    unlikely_if (stage >= AT_AUXS)
      goto ILLEGAL_LAMBDA;
    stage = AT_AUXS;
    goto LOOP;
  }
  if (v == @'&allow-other-keys') {
    allow_other_keys = ECL_T;
    unlikely_if (stage != AT_KEYS)
      goto ILLEGAL_LAMBDA;
    stage = AT_OTHER_KEYS;
    goto LOOP;
  }
  switch (stage) {
  case AT_REQUIREDS:
    nreq++;
    assert_var_name(v);
    if (context == @'function' && ecl_member_eq(v, lists[0]))
      /* note: ftype isn't valid context for this check */
      FEprogram_error
        ("The variable ~s occurs more than once as the "
         "required parameter in the lambda list.", 1, v);
    push(v, reqs);
    break;
  case AT_OPTIONALS: {
    cl_object spp = ECL_NIL;
    cl_object init = ECL_NIL;
    if (!ECL_ATOM(v) && (context != @'ftype')) {
      cl_object x = v;
      unlikely_if (!ECL_LISTP(x)) goto ILLEGAL_LAMBDA;
      v = ECL_CONS_CAR(x);
      x = ECL_CONS_CDR(x);
      if (!Null(x)) {
        unlikely_if (!ECL_LISTP(x)) goto ILLEGAL_LAMBDA;
        init = ECL_CONS_CAR(x);
        x = ECL_CONS_CDR(x);
        if (!Null(x)) {
          unlikely_if (!ECL_LISTP(x)) goto ILLEGAL_LAMBDA;
          spp = ECL_CONS_CAR(x);
          x = ECL_CONS_CDR(x);
          if (spp != ECL_NIL) assert_var_name(spp);
          unlikely_if (!Null(x))
            goto ILLEGAL_LAMBDA;
        }
      }
    }
    nopt++;
    assert_var_name(v);
    push(v, opts);
    push(init, opts);
    push(spp, opts);
    break;
  }
  case AT_REST:
    /* If we get here, the user has declared more than one
     * &rest variable, as in (lambda (&rest x y) ...) */
    goto ILLEGAL_LAMBDA;
  case AT_KEYS: {
    cl_object init = ECL_NIL;
    cl_object spp = ECL_NIL;
    cl_object key;
    if (context == @'ftype') {
      unlikely_if (ECL_ATOM(v))
        goto ILLEGAL_LAMBDA;
      key = ECL_CONS_CAR(v);
      v = CADR(v);
      goto KEY_PUSH;
    }
    if (!ECL_ATOM(v)) {
      cl_object x = v;
      v = ECL_CONS_CAR(x);
      x = ECL_CONS_CDR(x);
      if (!Null(x)) {
        unlikely_if (!ECL_LISTP(x)) goto ILLEGAL_LAMBDA;
        init = ECL_CONS_CAR(x);
        x = ECL_CONS_CDR(x);
        if (!Null(x)) {
          unlikely_if (!ECL_LISTP(x)) goto ILLEGAL_LAMBDA;
          spp = ECL_CONS_CAR(x);
          x = ECL_CONS_CDR(x);
          unlikely_if (!Null(x))
            goto ILLEGAL_LAMBDA;
          if (spp != ECL_NIL) assert_var_name(spp);
        }
      }
    }
    if (CONSP(v)) {
      key = ECL_CONS_CAR(v);
      v = ECL_CONS_CDR(v);
      unlikely_if (ECL_ATOM(v) || !Null(ECL_CONS_CDR(v)))
        goto ILLEGAL_LAMBDA;
      v = ECL_CONS_CAR(v);
      if (context == @'function')
        assert_type_symbol(v);
      assert_type_symbol(key);
    } else {
      int intern_flag;
      key = ecl_intern(ecl_symbol_name(v), cl_core.keyword_package,
                       &intern_flag);
    }
    KEY_PUSH:
    nkey++;
    push(key, keys);
    assert_var_name(v);
    push(v, keys);
    push(init, keys);
    push(spp, keys);
    break;
  }
  default: {
    cl_object init;
    if (ECL_ATOM(v)) {
      init = ECL_NIL;
    } else if (Null(CDDR(v))) {
      cl_object x = v;
      v = ECL_CONS_CAR(x);
      init = CADR(x);
    } else
      goto ILLEGAL_LAMBDA;
    naux++;
    assert_var_name(v);
    push(v, auxs);
    push(init, auxs);
  }
  }
  goto LOOP;

 OUTPUT:
  if ((nreq+nopt+(!Null(rest))+nkey) >= ECL_CALL_ARGUMENTS_LIMIT)
    FEprogram_error("LAMBDA: Argument list is too long, ~S.", 1, org_lambda_list);

  @(return
    CONS(ecl_make_fixnum(nreq), lists[0])
    CONS(ecl_make_fixnum(nopt), lists[1])
    rest
    key_flag
    CONS(ecl_make_fixnum(nkey), lists[2])
    allow_other_keys
    CONS(ecl_make_fixnum(naux), lists[3]))

 ILLEGAL_LAMBDA:
  FEprogram_error("LAMBDA: Illegal lambda list ~S.", 1, org_lambda_list);

#undef push
#undef assert_var_name
}

static void
c_default(cl_env_ptr env, cl_object var, cl_object stmt, cl_object flag, cl_object specials)
{
  /* Flag is in REG0, value, if it exists, in stack */
  cl_index label;
  label = asm_jmp(env, OP_JT);
  compile_form(env, stmt, FLAG_PUSH);
  if (Null(flag)) {
    asm_complete(env, OP_JT, label);
  } else {
    compile_form(env, ECL_NIL, FLAG_REG0);
    asm_complete(env, OP_JT, label);
    c_bind(env, flag, specials);
  }
  c_pbind(env, var, specials);
}

static cl_object
fix_macro_to_lexenv(cl_env_ptr env, cl_object record) {
  cl_object arg1 = pop(&record);
  cl_object arg2 = pop(&record);
  cl_object arg3 = pop(&record);
  if (arg2 == @'si::macro') {
    c_mac_ref(env, arg1);
    return CONS(arg2, CONS(arg3, arg1));
  } else if (arg2 == @'si::symbol-macro') {
    c_sym_ref(env, arg1);
    return CONS(arg2, CONS(arg3, arg1));
  } else {
    return ECL_NIL;
  }
}

cl_object
ecl_make_lambda(cl_env_ptr env, cl_object name, cl_object lambda) {
  cl_object reqs, opts, rest, key, keys, auxs, allow_other_keys;
  cl_object specials, decl, body, output;
  cl_object cfb = ECL_NIL, captured;
  cl_index handle;
  struct cl_compiler_env *old_c_env, new_c_env[1];

  ecl_bds_bind(env, @'si::*current-form*',
               @list*(3, @'ext::lambda-block', name, lambda));

  old_c_env = env->c_env;
  c_new_env(env, new_c_env, old_c_env);
  new_c_env->lexical_level++;
  reqs = si_process_lambda(lambda);
  opts = env->values[1];
  rest = env->values[2];
  key  = env->values[3];
  keys = env->values[4];
  allow_other_keys = env->values[5];
  auxs = env->values[6];
  /* doc  = env->values[7]; unused */;
  specials = env->values[8];
  decl = env->values[9];
  body = env->values[10];

  handle = asm_begin(env);

  /* Transform (SETF fname) => fname */
  if (!Null(name) && Null(si_valid_function_name_p(name)))
    FEprogram_error("LAMBDA: Not a valid function name ~S.",1,name);

  /* We register the function boundary. We use this mark in both variables and
   * macros for code-walking. */
  c_register_boundary(env, @'si::function-boundary');

  reqs = ECL_CONS_CDR(reqs);              /* Required arguments */
  while (!Null(reqs)) {
    cl_object var = pop(&reqs);
    asm_op(env, OP_POPREQ);
    c_bind(env, var, specials);
  }
  opts = ECL_CONS_CDR(opts);
  while (!Null(opts)) {                   /* Optional arguments */
    cl_object var = pop(&opts);
    cl_object stmt = pop(&opts);
    cl_object flag = pop(&opts);
    asm_op(env, OP_POPOPT);
    c_default(env, var, stmt, flag, specials);
  }
  if (Null(rest) && Null(key)) {          /* Check no excess arguments */
    asm_op(env, OP_NOMORE);
  }
  if (!Null(rest)) {                      /* &rest argument */
    asm_op(env, OP_POPREST);
    c_bind(env, rest, specials);
  }
  if (!Null(key)) {
    cl_object aux = CONS(allow_other_keys,ECL_NIL);
    cl_object names = ECL_NIL;
    asm_op2c(env, OP_PUSHKEYS, aux);
    keys = ECL_CONS_CDR(keys);
    while (!Null(keys)) {
      cl_object name = pop(&keys);
      cl_object var = pop(&keys);
      cl_object stmt = pop(&keys);
      cl_object flag = pop(&keys);
      names = CONS(name, names);
      asm_op(env, OP_POP);
      c_default(env, var, stmt, flag, specials);
    }
    ECL_RPLACD(aux, names);
  }
  auxs = ECL_CONS_CDR(auxs);
  while (!Null(auxs)) {                   /* Local bindings */
    cl_object var = pop(&auxs);
    cl_object value = pop(&auxs);
    compile_form(env, value, FLAG_REG0);
    c_bind(env, var, specials);
  }
  c_declare_specials(env, specials);

  if (!Null(name)) {
    compile_form(env, @list*(3, @'block', si_function_block_name(name),
                             body), FLAG_VALUES);
  } else {
    while (!Null(decl)) {
      cl_object l = ECL_CONS_CAR(decl);
      if (ECL_CONSP(l) && ECL_CONS_CAR(l) == @'si::function-block-name') {
        name = ECL_CONS_CAR(ECL_CONS_CDR(l));
        break;
      }
      decl = ECL_CONS_CDR(decl);
    }
    compile_body(env, body, FLAG_VALUES);
  }

  /* Only undo special bindings */
  c_undo_bindings(env, old_c_env->variables, 1);
  asm_op(env, OP_EXIT);

  if (Null(ecl_cmp_symbol_value(env, @'si::*keep-definitions*')))
    lambda = ECL_NIL;
  output = asm_end(env, handle, lambda);
  output->bytecodes.name = name;
  output->bytecodes.flex = ECL_NIL;
  output->bytecodes.nlcl = ecl_make_fixnum(new_c_env->env_width);

  old_c_env->load_time_forms = new_c_env->load_time_forms;

  c_restore_env(env, new_c_env, old_c_env);
  ecl_bds_unwind1(env);         /* @'si::*current-form*', */

  /* Process closed over entries. */
  captured = new_c_env->captured;
  if (!Null(captured)) {
    cl_object flex, entry, macro_entry;
    struct cl_compiler_ref ref;
    int i, n;
    n = captured->vector.fillp;
    flex = si_make_vector(ECL_T, ecl_make_fixnum(n),
                          ECL_NIL, ECL_NIL, ECL_NIL, ECL_NIL);
    output->bytecodes.flex = flex;
    for (i = 0; i < n; i++) {
      entry = captured->vector.self.t[i];
      captured->vector.self.t[i] = ECL_NIL;
      macro_entry = fix_macro_to_lexenv(env, entry);
      if(!Null(macro_entry)) {
        flex->vector.self.t[i] = macro_entry;
        continue;
      }
      ref = c_any_ref(env, entry);
      switch(ref.place) {
      case ECL_CMPREF_LOCAL:
        flex->vector.self.t[i] = ecl_make_fixnum(-ref.index-1);
        break;
      case ECL_CMPREF_CLOSE:
        flex->vector.self.t[i] = ecl_make_fixnum(ref.index);
        break;
      default:
        ecl_miscompilation_error();
      }
    }
    cfb = ECL_T;
  }
  ecl_return2(env, output, cfb);
}

static cl_object
ecl_function_block_name(cl_object name)
{
  if (ECL_SYMBOLP(name)) {
    return name;
  } else if (CONSP(name) && ECL_CONS_CAR(name) == @'setf') {
    name = ECL_CONS_CDR(name);
    if (CONSP(name)) {
      cl_object output = ECL_CONS_CAR(name);
      if (ECL_SYMBOLP(output) && Null(ECL_CONS_CDR(name)))
        return output;
    }
  }
  return NULL;
}

cl_object
si_function_block_name(cl_object name)
{
  cl_object output = ecl_function_block_name(name);
  if (!output)
    FEinvalid_function_name(name);
  @(return output);
}

cl_object
si_valid_function_name_p(cl_object name)
{
  name = ecl_function_block_name(name);
  @(return (name? ECL_T : ECL_NIL));
}

cl_object
si_make_lambda(cl_object name, cl_object rest)
{
  cl_object lambda;
  const cl_env_ptr the_env = ecl_process_env();
  cl_compiler_env_ptr old_c_env = the_env->c_env;
  struct cl_compiler_env new_c_env;

  c_new_env(the_env, &new_c_env, NULL);
  ECL_UNWIND_PROTECT_BEGIN(the_env) {
    lambda = ecl_make_lambda(the_env, name, rest);
  } ECL_UNWIND_PROTECT_EXIT {
    c_restore_env(the_env, &new_c_env, old_c_env);
  } ECL_UNWIND_PROTECT_END;
  @(return lambda);
}

cl_object
si_bc_compile_from_stream(cl_object input)
{
  /* Compile all forms read from input stream to bytecodes */
  cl_env_ptr the_env = ecl_process_env();
  cl_compiler_env_ptr old_c_env;
  struct cl_compiler_env new_c_env;
  cl_object bytecodes = ECL_NIL;
  old_c_env = the_env->c_env;
  c_new_env(the_env, &new_c_env, NULL);
  new_c_env.mode = FLAG_LOAD;

  ECL_UNWIND_PROTECT_BEGIN(the_env) {
    while (TRUE) {
      cl_object position, form, source_location;
      cl_index handle;
      position = ecl_file_position(input);
      form = cl_read(3, input, ECL_NIL, @':eof');
      if (form == @':eof')
        break;
      source_location = ECL_SYM_VAL(the_env, @'ext::*source-location*');
      if (source_location != ECL_NIL)
        cl_rplacd(source_location, position);

      handle = asm_begin(the_env);
      compile_with_load_time_forms(the_env, form, FLAG_VALUES);
      asm_op(the_env, OP_EXIT);
      push(asm_end(the_env, handle, form), &bytecodes);
    }
  } ECL_UNWIND_PROTECT_EXIT {
    c_restore_env(the_env, &new_c_env, old_c_env);
  } ECL_UNWIND_PROTECT_END;

  return cl_nreverse(bytecodes);
}

@(defun si::eval-with-env (form &optional (env ECL_NIL) (stepping ECL_NIL)
                           (compiler_env_p ECL_NIL) (mode @':execute'))
  cl_compiler_env_ptr old_c_env;
  struct cl_compiler_env new_c_env;
@
  /*
   * Compile to bytecodes.
   * Parameter mode is interpreted as follows:
   * - execute: Execute the compiled form
   * - load-toplevel: Compile the form without executing. Calls
   *   make-load-form for literal objects encountered during
   *   compilation.
   * - compile-toplevel: Compile the form without executing, do not
   *   call make-load-form. Useful for code walking.
   */
  if (!(mode == @':execute' || mode == @':load-toplevel' || mode == @':compile-toplevel')) {
    FEerror("Invalid mode in SI:EVAL-WITH-ENV", 0);
  }
  old_c_env = the_env->c_env;
  c_new_env(the_env, &new_c_env, NULL);
  (Null(compiler_env_p)
   ? import_lexenv(the_env, env)
   : import_cmpenv(the_env, env));
  new_c_env.stepping = stepping != ECL_NIL;
  the_env->stepper = @'si::stepper-hook';
  ECL_UNWIND_PROTECT_BEGIN(the_env) {
    if (mode == @':execute') {
      eval_form(the_env, form);
    } else {
      cl_index handle = asm_begin(the_env);
      new_c_env.mode = (mode == @':load-toplevel') ? FLAG_LOAD : FLAG_COMPILE;
      compile_with_load_time_forms(the_env, form, FLAG_VALUES);
      asm_op(the_env, OP_EXIT);
      the_env->values[0] = asm_end(the_env, handle, form);
      the_env->nvalues = 1;
    }
  } ECL_UNWIND_PROTECT_EXIT {
    c_restore_env(the_env, &new_c_env, old_c_env);
  } ECL_UNWIND_PROTECT_END;
  return the_env->values[0];
@)

void
init_compiler()
{
  cl_object dispatch_table =
    cl_core.compiler_dispatch =
    cl__make_hash_table(@'eq', ecl_make_fixnum(128), /* size */
                        cl_core.rehash_size,
                        cl_core.rehash_threshold);
  int i;
  for (i = 0; database[i].symbol; i++) {
    ecl_sethash(database[i].symbol, dispatch_table, ecl_make_fixnum(i));
  }
}
