/* pkl-gen.c - Code generation phase for the poke compiler.  */

/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Jose E.
 * Marchesi */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#include "pkl.h"
#include "pkl-ast.h"
#include "pkl-env.h"
#include "pkl-pass.h"
#include "pkl-asm.h"
#include "pvm-alloc.h" /* For pvm_alloc.  */
#include "pvm.h"
#include "pk-utils.h"

/* It would be very unlikely to have more than 24 declarations nested
   in a poke program... if it ever happens, we will just increase
   this, that's a promise :P (don't worry, there is an assertion that
   will fire if this limit is surpassed.) */

#define PKL_GEN_MAX_PASM 25

/* Likewise for the contexts.  If this hard limit is exceeded it is
   most likely due to a bug in the gen pass.  */

#define PKL_GEN_MAX_CTX 25

/* The following struct defines the payload of the code generation
   phase.

   COMPILER is the Pkl compiler driving the compilation.

   PASM is a stack of macro-assemblers, which are used to asembly the
   main program, struct mappers, functions, etc.

   ENDIAN is the endianness to be used when mapping and writing
   integral types.

   CUR_PASM is a pointer to the top of PASM.

   PROGRAM is the main PVM program being compiled.  When the phase is
   completed, the program is "finished" (in PVM parlance) and ready
   to be used.

   CONTEXT is an array implementing a stack of contexts.  Each context
   is a bitmap.  See the PKL_GEN_CTX_* constants below for the
   significance of each bit.  The initial context is initialized to 0.

   CUR_CONTEXT is the index to CONTEXT and marks the top of the stack
   of contexts.  Initially 0.

   MAPPER_DEPTH and CONSTRUCTOR_DEPTH are used in the array mapper and
   constructor generation handlers.

   IN_FILE_P indicates whether the current source is a file, as
   opposed to stdin (i.e. as opposed of compiling a statement.)

   FILENAME is the current active filename (it can change because
   of PKL_AST_SRC nodes).  */


struct pkl_gen_payload
{
  pkl_compiler compiler;
  pkl_asm pasm[PKL_GEN_MAX_PASM];
  uint32_t context[PKL_GEN_MAX_CTX];
  enum pkl_ast_endian endian;
  int cur_pasm;
  int cur_context;
  pvm_program program;
  int constructor_depth;
  int mapper_depth;
  int in_file_p;
  char *filename;
  pkl_env env;
};

typedef struct pkl_gen_payload *pkl_gen_payload;

/* Transformation phases initializer and finalizer.  */

static void *
pkl_gen_initialize (pkl_compiler compiler, pkl_env env)
{
  struct pkl_gen_payload *payload = pvm_alloc (sizeof (struct pkl_gen_payload));

  if (!payload)
    return NULL;

  /* Note that we are using pvm_alloc for this phase's payload because
     it contains a pvm_program that indirectly contains PVM
     values.  */

  memset (payload, 0, sizeof (struct pkl_gen_payload));
  payload->compiler = compiler;
  payload->env = env;
  return payload;
}

static void
pkl_gen_finalize (void *payload)
{
}

/* At any given time the code generation phase can be in a set of
   several possible contexts, which are generally mutually inclusive.
   This set of contexts in maintained in a 32-bit long bitmap.  The
   following constants identify the supported contexts and their
   corresponding bits in the bitmap.  */

#define PKL_GEN_CTX_IN_STRUCT_DECL  0x01
#define PKL_GEN_CTX_IN_MAPPER       0x02
#define PKL_GEN_CTX_IN_CONSTRUCTOR  0x04
#define PKL_GEN_CTX_IN_WRITER       0x08
#define PKL_GEN_CTX_IN_LVALUE       0x10
#define PKL_GEN_CTX_IN_COMPARATOR   0x20
#define PKL_GEN_CTX_IN_PRINTER      0x40
#define PKL_GEN_CTX_IN_ARRAY_BOUNDER 0x80
#define PKL_GEN_CTX_IN_FUNCALL      0x200
#define PKL_GEN_CTX_IN_TYPE         0x400
#define PKL_GEN_CTX_IN_FORMATER     0x800
#define PKL_GEN_CTX_IN_INTEGRATOR   0x1000
#define PKL_GEN_CTX_IN_DEINTEGRATOR 0x2000
#define PKL_GEN_CTX_IN_TYPIFIER     0x4000

/* The following macros are used in the rules below, to reduce
   verbosity.  */

#define PKL_GEN_PAYLOAD ((pkl_gen_payload) PKL_PASS_PAYLOAD)

#define PKL_GEN_AN_ASM(ASM)                             \
  (PKL_GEN_PAYLOAD->ASM[PKL_GEN_PAYLOAD->cur_##ASM])

#define PKL_GEN_ASM  PKL_GEN_AN_ASM(pasm)

#define PKL_GEN_PUSH_AN_ASM(ASM,new_pasm)                               \
  do                                                                    \
    {                                                                   \
      assert (PKL_GEN_PAYLOAD->cur_##ASM < PKL_GEN_MAX_PASM);           \
      PKL_GEN_PAYLOAD->ASM[++(PKL_GEN_PAYLOAD->cur_##ASM)] = (new_pasm); \
    }                                                                   \
  while (0)

#define PKL_GEN_PUSH_ASM(new_pasm)  PKL_GEN_PUSH_AN_ASM(pasm,new_pasm)

#define PKL_GEN_POP_AN_ASM(ASM)                  \
  do                                             \
    {                                            \
      assert (PKL_GEN_PAYLOAD->cur_##ASM > 0);   \
      PKL_GEN_PAYLOAD->cur_##ASM -= 1;           \
    }                                            \
  while (0)

#define PKL_GEN_POP_ASM  PKL_GEN_POP_AN_ASM(pasm)

#define PKL_GEN_IN_CTX_P(CTX)                                           \
  (PKL_GEN_PAYLOAD->context[PKL_GEN_PAYLOAD->cur_context] & (CTX))

#define PKL_GEN_PUSH_CONTEXT                                            \
  do                                                                    \
    {                                                                   \
      if (PKL_GEN_PAYLOAD->cur_context >= PKL_GEN_MAX_CTX)              \
        {                                                               \
          PKL_ERROR (PKL_AST_NOLOC,                                     \
                     "maximum code nesting level reached");             \
          PKL_PASS_ERROR;                                               \
        }                                                               \
      PKL_GEN_PAYLOAD->context[PKL_GEN_PAYLOAD->cur_context + 1]        \
        = 0;                                                            \
      PKL_GEN_PAYLOAD->cur_context++;                                   \
    }                                                                   \
  while (0)

#define PKL_GEN_POP_CONTEXT                             \
  do                                                    \
    {                                                   \
      assert (PKL_GEN_PAYLOAD->cur_context > 0);        \
      PKL_GEN_PAYLOAD->cur_context--;                   \
    }                                                   \
  while (0)

#define PKL_GEN_SET_CONTEXT(CTX)                                        \
  do                                                                    \
    {                                                                   \
      PKL_GEN_PAYLOAD->context[PKL_GEN_PAYLOAD->cur_context] |= (CTX);  \
    }                                                                   \
  while (0)

#define PKL_GEN_CLEAR_CONTEXT(CTX)                                      \
  do                                                                    \
    {                                                                   \
      PKL_GEN_PAYLOAD->context[PKL_GEN_PAYLOAD->cur_context] &= ~(CTX); \
    }                                                                   \
  while (0)

#define PKL_GEN_PUSH_SET_CONTEXT(CTX)           \
  do                                            \
    {                                           \
      PKL_GEN_PUSH_CONTEXT;                     \
      PKL_GEN_SET_CONTEXT (CTX);                \
    }                                           \
  while (0)

/* Code generated by RAS is used in the handlers below.  Configure it
   to use the main assembler in the GEN payload.  Then just include
   the assembled macros in this file.  */
#define RAS_ASM PKL_GEN_ASM
#define RAS_PUSH_ASM PKL_GEN_PUSH_ASM
#define RAS_POP_ASM PKL_GEN_POP_ASM
#define RAS_COMPILER (PKL_GEN_PAYLOAD->compiler)
#define RAS_COMP_ENV (PKL_GEN_PAYLOAD->env)

/* This macro is used in the `typeof' RAS macro in pkl-gen.pkc.  */
#define PK_TYPE_CODE(TYPE)                                              \
  ({                                                                    \
    pkl_ast_node initial = pkl_env_lookup_var (pkl_get_env (PKL_PASS_COMPILER), \
                                               (TYPE));                 \
    assert (PKL_AST_CODE (initial) == PKL_AST_INTEGER);                 \
    PKL_AST_INTEGER_VALUE (initial);                                    \
  })

#include "pkl-gen.pkc"
#include "pkl-gen-attrs.pkc"

/*
 * SRC
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_src)
{
  PKL_GEN_PAYLOAD->in_file_p
      = PKL_AST_SRC_FILENAME (PKL_PASS_NODE)
        && STRNEQ (PKL_AST_SRC_FILENAME (PKL_PASS_NODE), "<stdin>");
  PKL_GEN_PAYLOAD->filename = PKL_AST_SRC_FILENAME (PKL_PASS_NODE);
}
PKL_PHASE_END_HANDLER

/*
 * PROGRAM
 * | PROGRAM_ELEM
 * | ...
 *
 * This function initializes the payload and also generates the
 * standard prologue.
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_program)
{
  PKL_GEN_ASM = pkl_asm_new (PKL_PASS_AST,
                             PKL_GEN_PAYLOAD->compiler,
                             1 /* prologue */);

  PKL_GEN_PAYLOAD->in_file_p
    = (!pkl_compiling_statement_p (PKL_GEN_PAYLOAD->compiler)
       && !pkl_compiling_expression_p (PKL_GEN_PAYLOAD->compiler));
  PKL_GEN_PAYLOAD->filename = PKL_PASS_AST->filename;
}
PKL_PHASE_END_HANDLER

/*
 * | PROGRAM_ELEM
 * | ...
 * PROGRAM
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_program)
{
  /* Make sure there is always some value returned in the stack, since
     that is expected in the PVM.  */
  if (!pkl_compiling_expression_p (PKL_GEN_PAYLOAD->compiler)
      && !(pkl_compiling_statement_p (PKL_GEN_PAYLOAD->compiler)
           && PKL_AST_PROGRAM_ELEMS (PKL_PASS_NODE)
           && PKL_AST_CODE (PKL_AST_PROGRAM_ELEMS (PKL_PASS_NODE)) == PKL_AST_EXP_STMT))
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

  PKL_PASS_AST->payload = pkl_asm_finish (PKL_GEN_ASM, 1 /* epilogue */);
}
PKL_PHASE_END_HANDLER

/*
 * DECL
 * | INITIAL
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_decl)
{
  pkl_ast_node decl = PKL_PASS_NODE;
  pkl_ast_node initial = PKL_AST_DECL_INITIAL (decl);
  pkl_ast_node type_name = PKL_AST_DECL_NAME (decl);

  /* mktysct only gets information from regular struct fields.
     Therefore, we do not need to process declarations of variables,
     types and methods inside struct types.  */
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    PKL_PASS_BREAK;

  switch (PKL_AST_DECL_KIND (decl))
    {
    case PKL_AST_DECL_KIND_UNIT:
      /* Nothing to do with units at run-time, for now.  */
      PKL_PASS_BREAK;
      break;
    case PKL_AST_DECL_KIND_TYPE:
      PKL_AST_TYPE_NAMED_P (initial) = 1;
      switch (PKL_AST_TYPE_CODE (initial))
        {
        case PKL_TYPE_FUNCTION:
          {
            /* Install bounders in the array types that may appear in
               the function type, be it as the type of an argument, or
               the return type.  */

            pkl_ast_node type_function = initial;

            PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
            PKL_PASS_SUBPASS (type_function);
            PKL_GEN_POP_CONTEXT;
            break;
          }
        case PKL_TYPE_STRUCT:
          {
            pkl_ast_node type_struct = initial;
            int is_type_alias_p;

            is_type_alias_p = PKL_AST_TYPE_CODE2 (initial) == PKL_TYPE_ALIAS;

            /* Compile the struct closures and complete them using the
               current environment.  */

            if (PKL_AST_TYPE_S_WRITER (type_struct) == PVM_NULL
                || is_type_alias_p)
              {
                pvm_val writer_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
                {
                  if (PKL_AST_TYPE_S_UNION_P (type_struct))
                    RAS_FUNCTION_UNION_WRITER (writer_closure, type_struct);
                  else
                    RAS_FUNCTION_STRUCT_WRITER (writer_closure, type_struct);
                }
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_S_WRITER (type_struct) = writer_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_S_WRITER (type_struct)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);           /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);          /* _ */

            /* Note that it is important for the constructor to be
               compiled before the mapper, because the first has to be
               installed in the PVM struct type built by the second.
              */
            if (PKL_AST_TYPE_S_CONSTRUCTOR (type_struct) == PVM_NULL
                || is_type_alias_p)
              {
                pvm_val constructor_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
                RAS_FUNCTION_STRUCT_CONSTRUCTOR (constructor_closure,
                                                 type_struct, type_name);
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_S_CONSTRUCTOR (type_struct) = constructor_closure;
              }
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_S_CONSTRUCTOR (type_struct)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);               /* _ */

            if (PKL_AST_TYPE_S_MAPPER (type_struct) == PVM_NULL
                || is_type_alias_p)
              {
                pvm_val mapper_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_MAPPER);
                RAS_FUNCTION_STRUCT_MAPPER (mapper_closure, type_struct, type_name);
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_S_MAPPER (type_struct) = mapper_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_S_MAPPER (type_struct)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);           /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);          /* _ */

            if (PKL_AST_TYPE_S_COMPARATOR (type_struct) == PVM_NULL
                || is_type_alias_p)
              {
                pvm_val comparator_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_COMPARATOR);
                if (PKL_AST_TYPE_S_UNION_P (type_struct))
                  RAS_FUNCTION_UNION_COMPARATOR (comparator_closure,
                                                 type_struct);
                else
                  RAS_FUNCTION_STRUCT_COMPARATOR (comparator_closure,
                                                  type_struct);
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_S_COMPARATOR (type_struct) = comparator_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_S_COMPARATOR (type_struct)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);               /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);              /* _ */

            if (PKL_AST_TYPE_S_ITYPE (type_struct))
              {
                if (PKL_AST_TYPE_S_INTEGRATOR (type_struct) == PVM_NULL
                    || is_type_alias_p)
                  {
                    pvm_val integrator_closure;

                    PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_INTEGRATOR);
                    if (PKL_AST_TYPE_S_UNION_P (type_struct))
                      RAS_FUNCTION_UNION_INTEGRATOR (integrator_closure,
                                                     type_struct);
                    else
                      RAS_FUNCTION_STRUCT_INTEGRATOR (integrator_closure,
                                                      type_struct);
                    PKL_AST_TYPE_S_INTEGRATOR (type_struct) = integrator_closure;
                    PKL_GEN_POP_CONTEXT;
                  }

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                              PKL_AST_TYPE_S_INTEGRATOR (type_struct)); /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);               /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);              /* _ */

                if (PKL_AST_TYPE_S_DEINTEGRATOR (type_struct) == PVM_NULL
                    || is_type_alias_p)
                  {
                    pvm_val deintegrator_closure;

                    PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_DEINTEGRATOR);
                    if (PKL_AST_TYPE_S_UNION_P (type_struct))
                      RAS_FUNCTION_UNION_DEINTEGRATOR (deintegrator_closure,
                                                       type_struct, type_name);
                    else
                      RAS_FUNCTION_STRUCT_DEINTEGRATOR (deintegrator_closure,
                                                        type_struct);
                    PKL_GEN_POP_CONTEXT;
                    PKL_AST_TYPE_S_DEINTEGRATOR (type_struct) = deintegrator_closure;
                  }

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                              PKL_AST_TYPE_S_DEINTEGRATOR (type_struct)); /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                 /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);                /* _ */
              }

            /* Note that the typifier must be processed last, as it
               uses the closures installed above.  */
            if (PKL_AST_TYPE_S_TYPIFIER (type_struct) == PVM_NULL
                || is_type_alias_p)
              {
                pvm_val typifier_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPIFIER);
                RAS_FUNCTION_STRUCT_TYPIFIER (typifier_closure,
                                              type_struct, type_name);
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_S_TYPIFIER (type_struct) = typifier_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_S_TYPIFIER (type_struct)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);             /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);            /* _ */

            PKL_PASS_BREAK;
            break;
          }
        case PKL_TYPE_ARRAY:
          {
            pkl_ast_node array_type = initial;

            /* Compile the arrays closures and complete them using the
               current environment.  */

            /* The bounder closures for this array and possibly
               contained sub-arrays are installed in the
               pkl_gen_pr_type_array handler.  This should be done
               before compiling the rest of the closures below, to
               assure the bounder closures capture the right lexical
               context!  This makes the calls to in_array_bounder in
               array mappers/constructors to only happen for anonymous
               array types.  */
            PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
            PKL_PASS_SUBPASS (array_type);
            PKL_GEN_POP_CONTEXT;

            if (PKL_AST_TYPE_A_WRITER (array_type) == PVM_NULL)
              {
                pvm_val writer_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
                RAS_FUNCTION_ARRAY_WRITER (writer_closure, array_type);
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_A_WRITER (array_type) = writer_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_A_WRITER (array_type)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);          /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);         /* _ */

            if (PKL_AST_TYPE_A_MAPPER (array_type) == PVM_NULL)
              {
                pvm_val mapper_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_MAPPER);
                PKL_GEN_PAYLOAD->mapper_depth = 1;
                RAS_FUNCTION_ARRAY_MAPPER (mapper_closure, array_type);
                assert (PKL_GEN_PAYLOAD->mapper_depth == 1);
                PKL_GEN_PAYLOAD->mapper_depth = 0;
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_A_MAPPER (array_type) = mapper_closure;
              }

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_A_MAPPER (array_type)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);          /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);         /* _ */

            if (PKL_AST_TYPE_A_CONSTRUCTOR (array_type) == PVM_NULL)
              {
                pvm_val constructor_closure;

                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
                PKL_GEN_PAYLOAD->constructor_depth = 1;
                RAS_FUNCTION_ARRAY_CONSTRUCTOR (constructor_closure,
                                                array_type);
                assert (PKL_GEN_PAYLOAD->constructor_depth == 1);
                PKL_GEN_PAYLOAD->constructor_depth = 0;
                PKL_GEN_POP_CONTEXT;
                PKL_AST_TYPE_A_CONSTRUCTOR (array_type) = constructor_closure;
              }
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          PKL_AST_TYPE_A_CONSTRUCTOR (array_type)); /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);               /* CLS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);              /* _ */

            if (pkl_ast_type_integrable_p (array_type))
              {
                if (PKL_AST_TYPE_A_INTEGRATOR (array_type) == PVM_NULL)
                  {
                    pvm_val integrator_closure;

                    PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_INTEGRATOR);
                    RAS_FUNCTION_ARRAY_INTEGRATOR (integrator_closure,
                                                   array_type);
                    PKL_GEN_POP_CONTEXT;
                    PKL_AST_TYPE_A_INTEGRATOR (array_type) = integrator_closure;
                  }
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                              PKL_AST_TYPE_A_INTEGRATOR (array_type)); /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);              /* CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);             /* _ */
              }

            PKL_PASS_BREAK;
            break;
          }
        default:
          break;
        }
      break;
    case PKL_AST_DECL_KIND_FUNC:
      {
        pvm_program program;
        pvm_val closure;
        char *program_name = PKL_AST_FUNC_NAME (initial);

        if (PKL_AST_FUNC_PROGRAM (initial))
          program = PKL_AST_FUNC_PROGRAM (initial);
        else
          {
            /* INITIAL is a PKL_AST_FUNC, that will compile into a
               program containing the function code.  Push a new
               assembler to the stack of assemblers in the payload and
               use it to process INITIAL.  */
            PKL_GEN_PUSH_ASM (pkl_asm_new (PKL_PASS_AST,
                                           PKL_GEN_PAYLOAD->compiler,
                                           0 /* prologue */));

            PKL_PASS_SUBPASS (initial);

            /* At this point the code for the function specification
               INITIAL has been assembled in the current
               macroassembler.  Finalize the program and put it in a
               PVM closure, along with the current environment.  */

            program = pkl_asm_finish (PKL_GEN_ASM,
                                      0 /* epilogue */);
            PKL_GEN_POP_ASM;
            pvm_program_make_executable (program);

            /* XXX */
            //            pvm_disassemble_program (program);
            PKL_AST_FUNC_PROGRAM (initial) = program;
          }

        closure = pvm_make_cls (program,
                                program_name ? pvm_make_string (program_name) : PVM_NULL);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, closure);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUC);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REGVAR);

        PKL_PASS_BREAK;
        break;
      }
    default:
      break;
    }
}
PKL_PHASE_END_HANDLER

/*
 * | INITIAL
 * DECL
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_decl)
{
  pkl_ast_node decl = PKL_PASS_NODE;
  pkl_ast_node initial = PKL_AST_DECL_INITIAL (decl);

  switch (PKL_AST_DECL_KIND (decl))
    {
    case PKL_AST_DECL_KIND_VAR:
      /* The value is in the stack.  Just register the variable.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REGVAR);
      break;
    case PKL_AST_DECL_KIND_TYPE:
      if (PKL_AST_TYPE_CODE (initial) == PKL_TYPE_STRUCT
          || PKL_AST_TYPE_CODE (initial) == PKL_TYPE_ARRAY)
        PK_UNREACHABLE ();
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

/*
 * VAR
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_var)
{
  pkl_ast_node var = PKL_PASS_NODE;

  if (PKL_PASS_PARENT == NULL
      && PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_LVALUE))
    {
      /* This is a l-value in an assignment.  Generate nothing, as
         this node is only used as a recipient for the lexical address
         of the variable.  */
    }
  else
    {
      pkl_ast_node var_decl = PKL_AST_VAR_DECL (var);
      pkl_ast_node var_type = PKL_AST_TYPE (var);
      pkl_ast_node var_function = PKL_AST_VAR_FUNCTION (var);

      /* If the declaration associated with the variable is in a
         struct _and_ we are in a method body.

         Instead of accessing the variable in the lexical environment,
         we push the implicit struct and sref it with the name of the
         variable.  The implicit struct is the first argument passed
         to the current function.  */
      if (var_function
          && PKL_AST_FUNC_METHOD_P (var_function)
          && (PKL_AST_DECL_STRUCT_FIELD_P (var_decl)
              || (PKL_AST_DECL_KIND (var_decl) == PKL_AST_DECL_KIND_FUNC
                  && PKL_AST_FUNC_METHOD_P (PKL_AST_DECL_INITIAL (var_decl)))))
        {
          pkl_ast_node var_name = PKL_AST_VAR_NAME (var);
          int var_function_back = PKL_AST_VAR_FUNCTION_BACK (var);

          assert (var_name != NULL);

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHVAR,
                        var_function_back, 0);              /* SCT */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_string (PKL_AST_IDENTIFIER_POINTER (var_name)));
                                                            /* SCT STR */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREF);        /* SCT STR VAL */

          if (PKL_AST_DECL_KIND (var_decl) == PKL_AST_DECL_KIND_FUNC)
            /* Method call: leave the implicit struct so it is passed
               as the last argument to the method.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
          else
            /* Normal field.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
        }
      else
        {
          /* Normal variable.  */
          if (PKL_AST_VAR_BACK (var) == -1
              || PKL_AST_VAR_OVER (var) == -1)
            {
              pkl_ast_node var_name = PKL_AST_VAR_NAME (var);

              PKL_ICE (PKL_AST_LOC (var),
                       "gen: found VAR node for `%s' without lexical address",
                       PKL_AST_IDENTIFIER_POINTER (var_name));
              PKL_PASS_ERROR;
            }
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHVAR,
                        PKL_AST_VAR_BACK (var), PKL_AST_VAR_OVER (var));
        }

      /* If the declaration associated with the variable is in a
         struct and we are not in a method, i.e. we are in a context
         like a constraint expression or an optional field condition,
         then raise E_elem if the value is null.  */
      if (PKL_AST_DECL_STRUCT_FIELD_P (var_decl)
          && (!var_function || !PKL_AST_FUNC_METHOD_P (var_function)))
        {
          pvm_program_label label
            = pkl_asm_fresh_label (PKL_GEN_ASM);

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNN, label);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_exception (PVM_E_ELEM, PVM_E_ELEM_NAME,
                                            PVM_E_ELEM_ESTATUS, NULL, NULL));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
          pkl_asm_label (PKL_GEN_ASM, label);
        }

      /* If the value holds a value that could be mapped, and the IO
         space is volatile, then use have to use AREMAP instruction to
         make sure the value is fresh.

         However, if the value is an array, its type is complete and
         an array or struct, and it is used as the entity in an
         indexer, the AREMAP is _not_ necessary.  */
      if ((PKL_AST_TYPE_CODE (var_type) == PKL_TYPE_ARRAY
           && !(PKL_AST_VAR_IS_INDEXED (var)
                && (PKL_AST_TYPE_CODE (PKL_AST_TYPE_A_ETYPE (var_type))
                    == PKL_TYPE_ARRAY
                    || PKL_AST_TYPE_CODE (PKL_AST_TYPE_A_ETYPE (var_type))
                    == PKL_TYPE_STRUCT)
                && (PKL_AST_TYPE_COMPLETE (PKL_AST_TYPE_A_ETYPE (var_type))
                    == PKL_AST_TYPE_COMPLETE_YES)))
          || PKL_AST_TYPE_CODE (var_type) == PKL_TYPE_STRUCT)
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREMAP); /* NVAL REMAPPED_P */

          pvm_program_label done
            = pkl_asm_fresh_label (PKL_GEN_ASM);

          /* If the value was remapped, replace the old value of the
             variable with the new in the corresponding frame.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, done);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* NVAL */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP);  /* NVAL NVAL */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPVAR,
                        PKL_AST_VAR_BACK (var),
                        PKL_AST_VAR_OVER (var));     /* NVAL */

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL); /* NVAL null */
          pkl_asm_label (PKL_GEN_ASM, done);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* NVAL */
        }
    }
}
PKL_PHASE_END_HANDLER

/*
 * LAMBDA
 * | FUNCTION
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_lambda)
{
  /* FUNCTION is a PKL_AST_FUNC, that will compile into a program
     containing the function code.  Push a new assembler.  */
  PKL_GEN_PUSH_ASM (pkl_asm_new (PKL_PASS_AST,
                                 PKL_GEN_PAYLOAD->compiler,
                                 0 /* prologue */));
}
PKL_PHASE_END_HANDLER

/*
 * | FUNCTION
 * LAMBDA
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_lambda)
{
  /* At this point the code for FUNCTION has been assembled in the
     current macroassembler.  Finalize the program and put it in a PVM
     closure, along with the current environment.  */

  pvm_program program = pkl_asm_finish (PKL_GEN_ASM,
                                        0 /* epilogue */);
  pvm_val closure;

  PKL_GEN_POP_ASM;
  pvm_program_make_executable (program);
  closure = pvm_make_cls (program, PVM_NULL /* name */);

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, closure);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUC);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);
}
PKL_PHASE_END_HANDLER

/*
 * NULL_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_null_stmt)
{
  /* Null is nothing, nada.  */
}
PKL_PHASE_END_HANDLER

/*
 * COMP_STMT
 * | (STMT | DECL)
 * | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_comp_stmt)
{
  pkl_ast_node comp_stmt = PKL_PASS_NODE;

  /* If the compound statement is empty, do not generate anything.  */
  if (PKL_AST_COMP_STMT_STMTS (comp_stmt) == NULL)
    PKL_PASS_BREAK;

  if (!PKL_AST_COMP_STMT_FRAMELESS_P (comp_stmt))
    {
      /* Push a frame into the environment.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHF,
                    PKL_AST_COMP_STMT_NUMVARS (comp_stmt));
    }
}
PKL_PHASE_END_HANDLER

/*
 * | (STMT | DECL)
 * | ...
 * COMP_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_comp_stmt)
{
  pkl_ast_node comp_stmt = PKL_PASS_NODE;

  if (!PKL_AST_COMP_STMT_FRAMELESS_P (comp_stmt))
    {
      /* Pop the lexical frame created by the compound
         statement.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPF, 1);
    }
}
PKL_PHASE_END_HANDLER

/*
 * INCRDECR
 * | EXP
 * | ASS_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_incrdecr)
{
  pkl_ast_node incrdecr = PKL_PASS_NODE;
  pkl_ast_node incrdecr_exp = PKL_AST_INCRDECR_EXP (incrdecr);
  pkl_ast_node incrdecr_ass_stmt = PKL_AST_INCRDECR_ASS_STMT (incrdecr);
  int incrdecr_order = PKL_AST_INCRDECR_ORDER (incrdecr);

  if (incrdecr_order == PKL_AST_ORDER_PRE)
    PKL_PASS_SUBPASS (incrdecr_ass_stmt);

  PKL_PASS_SUBPASS (incrdecr_exp);

  if (incrdecr_order == PKL_AST_ORDER_POST)
    PKL_PASS_SUBPASS (incrdecr_ass_stmt);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * ASS_STMT
 * | [EXP]
 * | LVALUE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_ass_stmt)
{
#define LMAP(TYPE)                                                      \
  do                                                                    \
    {                                                                   \
      int lvalue_type_code = PKL_AST_TYPE_CODE ((TYPE));                \
                                                                        \
      switch (lvalue_type_code)                                         \
        {                                                               \
        case PKL_TYPE_ARRAY:                                            \
        case PKL_TYPE_STRUCT:                                           \
          {                                                             \
            pvm_val writer =                                            \
              (lvalue_type_code == PKL_TYPE_ARRAY                       \
               ? PKL_AST_TYPE_A_WRITER ((TYPE))                         \
               : PKL_AST_TYPE_S_WRITER ((TYPE)));                       \
                                                                        \
            /* If the type is anonymous it wont' have a compiled */     \
            /* writer.  */                                              \
            if (!PKL_AST_TYPE_NAMED_P ((TYPE)))                           \
              {                                                         \
                assert (writer == PVM_NULL);                            \
                PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);       \
                {                                                       \
                  if (lvalue_type_code == PKL_TYPE_ARRAY)               \
                    RAS_FUNCTION_ARRAY_WRITER (writer, (TYPE));         \
                  else if (PKL_AST_TYPE_S_UNION_P ((TYPE)))             \
                    RAS_FUNCTION_UNION_WRITER (writer, (TYPE));         \
                  else                                                  \
                    RAS_FUNCTION_STRUCT_WRITER (writer, (TYPE));        \
                }                                                       \
                PKL_GEN_POP_CONTEXT;                                    \
                                                                        \
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, writer);      \
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);               \
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);              \
            }                                                           \
                                                                        \
            /* VAL IOS BOFF */                                          \
            RAS_MACRO_COMPLEX_LMAP ((TYPE), writer); /* _ */            \
            break;                                                      \
          }                                                             \
        default:                                                        \
          /* The map at the l-value is of a simple type, i.e. of */     \
          /* types whose values cannot be mapped (integers, offsets, */ \
          /* strings, etc).  The strategy here is simple: we just */    \
          /*  generate a writer for the type.  */                       \
                                                                        \
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* IOS BOFF VAL */  \
                                                                        \
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);             \
          PKL_PASS_SUBPASS ((TYPE));                                    \
          PKL_GEN_POP_CONTEXT;                                          \
          break;                                                        \
        }                                                               \
    }                                                                   \
  while (0)

  pkl_ast_node ass_stmt = PKL_PASS_NODE;
  pkl_ast_node lvalue = PKL_AST_ASS_STMT_LVALUE (ass_stmt);
  pkl_ast_node lvalue_type = PKL_AST_TYPE (lvalue);
  pkl_ast_node exp = PKL_AST_ASS_STMT_EXP (ass_stmt);
  pvm_program_label done = pkl_asm_fresh_label (PKL_GEN_ASM);
  int assigning_computed_field_p = 0;
  int assigning_to_indirection_p = 0;
  const char *computed_field_name = NULL;

  if (exp)
    PKL_PASS_SUBPASS (exp);

  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_LVALUE);
  PKL_PASS_SUBPASS (lvalue);
  PKL_GEN_POP_CONTEXT;

  /* At this point the r-value, generated from executing EXP, is in
     the stack.  */

  /* Determine whether we are assigning to an indirection.  */
  assigning_to_indirection_p = (PKL_AST_CODE (lvalue) == PKL_AST_STRUCT_REF
                                && PKL_AST_STRUCT_REF_INDIRECTION_P (lvalue));

  /* Determine whether we are assigning to a computed field in a
     struct.  */
  if (PKL_AST_CODE (lvalue) == PKL_AST_STRUCT_REF)
    {
      pkl_ast_node sct = PKL_AST_STRUCT_REF_STRUCT (lvalue);
      pkl_ast_node struct_type = PKL_AST_TYPE (sct);
      pkl_ast_node field_name = PKL_AST_STRUCT_REF_IDENTIFIER (lvalue);
      const char *field_name_str = PKL_AST_IDENTIFIER_POINTER (field_name);
      pkl_ast_node elem = NULL;

      for (elem = PKL_AST_TYPE_S_ELEMS (struct_type);
           elem;
           elem = PKL_AST_CHAIN (elem))
        {
          if (PKL_AST_CODE (elem) == PKL_AST_STRUCT_TYPE_FIELD)
            {
              pkl_ast_node elem_name = PKL_AST_STRUCT_TYPE_FIELD_NAME (elem);

              if (elem_name
                  && STREQ (PKL_AST_IDENTIFIER_POINTER (elem_name),
                            field_name_str))
                {
                  assigning_computed_field_p
                    = PKL_AST_STRUCT_TYPE_FIELD_COMPUTED_P (elem);
                  if (assigning_computed_field_p)
                    computed_field_name = field_name_str;
                }
            }
        }
    }

  /* If the base array to the indexer, or the referred struct, are
     mapped, and the assigned value is a complex value, then we have
     to reflect the effect of the assignment in the corresponding IO
     space.  */
  if (!assigning_computed_field_p
      && !assigning_to_indirection_p
      && (PKL_AST_CODE (lvalue) == PKL_AST_INDEXER
          || PKL_AST_CODE (lvalue) == PKL_AST_STRUCT_REF)
      && (PKL_AST_TYPE_CODE (lvalue_type) == PKL_TYPE_ARRAY
          || PKL_AST_TYPE_CODE (lvalue_type) == PKL_TYPE_STRUCT))
    {
      pvm_program_label not_mapped = pkl_asm_fresh_label (PKL_GEN_ASM);

      /* Stack: VAL (SCT|ARR) (ID|IDX) */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL ID SCT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);  /* VAL ID SCT MAPPED_P */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, not_mapped);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL ID SCT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL SCT ID */

      if (PKL_AST_CODE (lvalue) == PKL_AST_INDEXER)
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREFO); /* VAL SCT ID BOFF */
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREFO); /* VAL SCT ID BOFF */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);     /* VAL SCT BOFF */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);    /* VAL BOFF SCT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETIOS); /* VAL BOFF SCT IOS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);     /* VAL BOFF IOS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);    /* VAL IOS BOFF */

      /* VAL IOS OFF */
      LMAP (PKL_AST_TYPE (lvalue));

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, done);
      pkl_asm_label (PKL_GEN_ASM, not_mapped);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL ID SCT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL SCT ID */
    }

  /* All right, now assign the resulting rvalue to the lvalue.  */
  switch (PKL_AST_CODE (lvalue))
    {
    case PKL_AST_VAR:
      {
        /* Stack: VAL */

        pkl_ast_node var = lvalue;
        pkl_ast_node var_decl = PKL_AST_VAR_DECL (var);
        pkl_ast_node var_function = PKL_AST_VAR_FUNCTION (var);

        /* If the declaration associated with the variable is in a
           struct _and_ we are in a method body, we update the
           implicit struct argument.  */
        if (var_function
            && PKL_AST_FUNC_METHOD_P (var_function)
            && (PKL_AST_DECL_STRUCT_FIELD_P (var_decl)
                || (PKL_AST_DECL_KIND (var_decl) == PKL_AST_DECL_KIND_FUNC
                    && PKL_AST_FUNC_METHOD_P (PKL_AST_DECL_INITIAL (var_decl)))))
          {
            pkl_ast_node var_name = PKL_AST_VAR_NAME (var);
            int var_function_back = PKL_AST_VAR_FUNCTION_BACK (var);

            assert (var_name != NULL);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHVAR,
                          var_function_back, 0);              /* VAL SCT */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          pvm_make_string (PKL_AST_IDENTIFIER_POINTER (var_name)));
                                                              /* VAL SCT STR */

            /* Set the value in the struct using SETC if the struct is
               mapped in non-strict mode. Otherwise, use SSET.  */
            {
              pvm_program_label use_sset = pkl_asm_fresh_label (PKL_GEN_ASM);
              pvm_program_label use_ssetc = pkl_asm_fresh_label (PKL_GEN_ASM);
              pvm_program_label done = pkl_asm_fresh_label (PKL_GEN_ASM);

              /* If the struct is not mapped, always check integrity,
                 since there is not such a thing as non-strict
                 construction (yet).  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT); /* STR VAL SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);  /* STR VAL SCT MAPPED_P */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, use_ssetc);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* STR VAL SCT */

              /* The value is mapped.  Check the strictness of the mapping.  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETS); /* STR VAL SCT STRICT_P */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, use_ssetc);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* STR VAL SCT */

              /* Set without integrity checks.  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
              pkl_asm_label (PKL_GEN_ASM, use_sset);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* STR VAL SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* STR VAL [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);  /* STR VAL STR [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);  /* STR VAL STR VAL [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* STR VAL STR VAL SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* STR VAL SCT STR VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET);  /* SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, done);

              /* Set with integrity checks.  */
              pkl_asm_label (PKL_GEN_ASM, use_ssetc);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* STR VAL SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* STR VAL [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);  /* STR VAL STR [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);  /* STR VAL STR VAL [SCT] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* STR VAL STR VAL SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* STR VAL SCT STR VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSETC, NULL);
                                                          /* STR VAL SCT */

              pkl_asm_label (PKL_GEN_ASM, done);
            }

            /* Do a L-MAP if the struct is mapped.  */
            {
              pvm_program_label not_mapped = pkl_asm_fresh_label (PKL_GEN_ASM);

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_QUAKE); /* VAL STR SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);  /* VAL STR SCT MAPPED_P */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, not_mapped);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL STR SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL SCT STR */

              /* Mapped  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);   /* SCT STR VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* SCT STR [VAL] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREFO); /* SCT STR BOFF [VAL] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* SCT STR [VAL BOFF] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* SCT [VAL BOFF STR] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETIOS); /* SCT IOS [VAL BOFF STR] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* SCT IOS STR [VAL BOFF] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* SCT STR IOS [VAL BOFF] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* SCT STR IOS BOFF [VAL] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* SCT STR IOS BOFF VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* SCT STR VAL IOS BOFF */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OOVER); /* SCT STR VAL IOS BOFF VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* SCT STR VAL VAL IOS BOFF */
              LMAP (PKL_AST_TYPE (lvalue)); /* SCT STR VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REVN, 3); /* VAL STR SCT */

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
              pkl_asm_label (PKL_GEN_ASM, not_mapped);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL STR SCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REVN, 3); /* SCT STR VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP2); /* SCT */
            }

            /* In case the struct is mapped.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE); /* SCT */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* _ */
          }
        else
          /* Normal variable.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPVAR,
                        PKL_AST_VAR_BACK (lvalue), PKL_AST_VAR_OVER (lvalue));
        break;
      }
    case PKL_AST_INDEXER:
      {
        /* Stack: VAL ARRAY INDEX */

        pkl_ast_node array = PKL_AST_INDEXER_ENTITY (lvalue);
        pkl_ast_node array_type = PKL_AST_TYPE (array);
        pkl_ast_node etype = PKL_AST_TYPE_A_ETYPE (array_type);

        /* If the type of the array is ANY[], then check at runtime
           that the type of the value matches the type of the elements
           in the array.  */
        if (PKL_AST_TYPE_CODE (etype) == PKL_TYPE_ANY)
          {
            pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);
            pvm_program_label array_of_any = pkl_asm_fresh_label (PKL_GEN_ASM);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* INDEX VAL ARRAY */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYPOF); /* INDEX VAL ARRAY ATYPE */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYAGETT); /* INDEX VAL ARRAY ATYPE ETYPE */
            /* If this is an array of `any', it can contain
               any value.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, array_of_any);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);   /* INDEX VAL ARRAY ETYPE */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);   /* INDEX ARRAY ETYPE VAL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* INDEX ARRAY VAL ETYPE */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ISA);   /* INDEX ARRAY VAL ETYPE BOOL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);   /* INDEX ARRAY VAL BOOL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, label);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          pvm_make_exception (PVM_E_CONV, PVM_E_CONV_NAME,
                                              PVM_E_CONV_ESTATUS, NULL, NULL));
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);

            pkl_asm_label (PKL_GEN_ASM, array_of_any);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* INDEX VAL ARRAY ATYPE */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_QUAKE);  /* INDEX ARRAY VAL ATYPE */

            pkl_asm_label (PKL_GEN_ASM, label);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* INDEX ARRAY VAL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* INDEX VAL ARRAY */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);   /* VAL ARRAY INDEX */
          }

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);   /* ARRAY INDEX VAL */
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ASETC, array_type); /* ARRAY */
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE); /* ARRAY */
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* The array
                                                       value.  */
        break;
      }
    case PKL_AST_STRUCT_REF:
      {
        pkl_ast_node sct = PKL_AST_STRUCT_REF_STRUCT (lvalue);
        pkl_ast_node struct_type = PKL_AST_TYPE (sct);

        if (assigning_computed_field_p)
          {
            /* Assigning to a computed field.

               We should SREF the method set_FOO from the struct and
               pass the struct and the r-value to it.  Note that since
               this is the calling of a method, the struct value
               should be passed _last_.  */

            /* Stack: VAL SCT ID */

            char *setter = pk_str_concat ("set_",
                                          computed_field_name,
                                          NULL);
            pkl_ast_node setter_decl
              = pkl_ast_get_struct_type_method (struct_type, setter);

            if (setter_decl)
              {
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                              pvm_make_string (setter));   /* VAL SCT SETTER_NAME */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREFMNT);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);   /* VAL SCT CLS */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);  /* NULL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* _ */
              }
            else
              {
                /* No setter defined for this computer field.  */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                              pvm_make_exception (PVM_E_ELEM, PVM_E_ELEM_NAME,
                                                  PVM_E_ELEM_ESTATUS, NULL, NULL));
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
              }

            free (setter);
          }
        else if (assigning_to_indirection_p)
          {
            /* SCT->ID = VAL */
            /* Stack: VAL SCT ID */

            pvm_program_label mapped = pkl_asm_fresh_label (PKL_GEN_ASM);

            /* The referred field contains the offset where to write
               VAL.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREF);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* VAL SCT OFF */

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL OFF SCT */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);   /* VAL OFF SCT MAPPED_P */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, mapped);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                          pvm_make_exception (PVM_E_MAP, PVM_E_MAP_NAME,
                                              PVM_E_MAP_ESTATUS, NULL, NULL));
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);

            pkl_asm_label (PKL_GEN_ASM, mapped);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL OFF SCT */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETIOS); /* VAL OFF SCT IOS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* VAL OFF IOS */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL IOS OFF */

            /* We need a bit-offset.  */
            {
              pkl_ast_node ref_orig_type = PKL_AST_STRUCT_REF_ORIG_TYPE (lvalue);
              pkl_ast_node u64t = pkl_ast_make_integral_type (PKL_PASS_AST, 64, 0);

              assert (PKL_AST_TYPE_CODE (ref_orig_type) == PKL_TYPE_OFFSET);

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM); /* ... OFF MAGNITUDE */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NTON,
                            PKL_AST_TYPE_O_BASE_TYPE (ref_orig_type),
                            u64t);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* ... OFF NMAGNITUDE */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* ... NMAGNITUDE OFF */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETU);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* ... NMAGNITUDE UNIT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MULLU);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* (NMAGNITUDE*UNIT) */
              u64t = ASTREF (u64t); pkl_ast_node_free (u64t);
            }

            /* VAL IOS BOFF */
            LMAP (PKL_AST_TYPE (lvalue)); /* _ */
          }
        else
          {
            /* Assigning to a regular struct field.  */
            /* Stack: VAL SCT ID */

            pvm_program_label label1 = pkl_asm_fresh_label (PKL_GEN_ASM);
            pvm_program_label label2 = pkl_asm_fresh_label (PKL_GEN_ASM);

            assert (PKL_AST_TYPE_S_CONSTRUCTOR (struct_type) != PVM_NULL);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* VAL SCT ID SCT */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETS); /* VAL SCT ID SCT STRICT_P */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);  /* VAL SCT ID STRICT_P */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, label1);

            /* Strict value: set with integrity.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* SCT ID VAL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSETC, struct_type);

            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, label2);
            pkl_asm_label (PKL_GEN_ASM, label1);

            /* Non-strict value: set with no integrity.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* SCT ID VAL */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET, struct_type);

            pkl_asm_label (PKL_GEN_ASM, label2);
            /* For unmapped integral structs (from deintegration), propagate
               the write up through the parent chain until we find a mapped
               ancestor, then integrate and poke directly.  */
            if (PKL_AST_TYPE_S_ITYPE (struct_type))
              {
                pvm_program_label mapped_label = pkl_asm_fresh_label (PKL_GEN_ASM);
                pvm_program_label write_done = pkl_asm_fresh_label (PKL_GEN_ASM);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP); /* SCT SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);  /* SCT SCT MAPPED_P */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, mapped_label);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* SCT SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* SCT */

                {
                  pkl_ast_node cur = sct, parent_exp;

                  for (; cur && PKL_AST_CODE (cur) == PKL_AST_STRUCT_REF;
                       cur = parent_exp)
                    {
                      pkl_ast_node parent_type;
                      pkl_ast_node field_id;
                      const char *field_name;
                      pvm_program_label parent_not_mapped_label;

                      parent_exp = PKL_AST_STRUCT_REF_STRUCT (cur);
                      parent_type = PKL_AST_TYPE (parent_exp);

                      if (PKL_AST_TYPE_CODE (parent_type) != PKL_TYPE_STRUCT
                          || !PKL_AST_TYPE_S_ITYPE (parent_type))
                        break;

                      field_id = PKL_AST_STRUCT_REF_IDENTIFIER (cur);
                      field_name = PKL_AST_IDENTIFIER_POINTER (field_id);
                      parent_not_mapped_label
                        = pkl_asm_fresh_label (PKL_GEN_ASM);

                      /* Fetch parent.  Use PUSHVAR directly for variables to
                         avoid AREMAP reading a fresh copy from IO.  */
                      if (PKL_AST_CODE (parent_exp) == PKL_AST_VAR)
                        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHVAR,
                                      PKL_AST_VAR_BACK (parent_exp),
                                      PKL_AST_VAR_OVER (parent_exp));
                      else
                        {
                          PKL_GEN_PUSH_CONTEXT;
                          PKL_PASS_SUBPASS (parent_exp);
                          PKL_GEN_POP_CONTEXT;
                        }

                      /* SSET field in parent: SCT PARENT -> PARENT */
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                                    pvm_make_string (field_name)); /* SCT PARENT STR */
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* PARENT STR SCT */
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET);

                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, parent_not_mapped_label);

                      /* Parent is mapped.  Integrate and poke directly since
                         WRITE would skip unmapped child fields.  */
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);

                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETIOS);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETO);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);

                      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_INTEGRATOR);
                      PKL_PASS_SUBPASS (parent_type);
                      PKL_GEN_POP_CONTEXT;

                      {
                        pkl_ast_node parent_itype = PKL_AST_TYPE_S_ITYPE (parent_type);
                        PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
                        PKL_PASS_SUBPASS (parent_itype);
                        PKL_GEN_POP_CONTEXT;
                      }

                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, write_done);

                      pkl_asm_label (PKL_GEN_ASM, parent_not_mapped_label);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
                      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
                    }
                                                             /* SCT */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
                }

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, write_done);

                pkl_asm_label (PKL_GEN_ASM, mapped_label);  /* SCT SCT MAPPED_P */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* SCT SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE); /* SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);

                pkl_asm_label (PKL_GEN_ASM, write_done);
              }
            else
              {
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE); /* SCT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
              }
          }
        break;
      }
    case PKL_AST_MAP:
      /* Stack: VAL IOS OFF */
      /* Convert the offset to a bit-offset.  The offset is */
      /* guaranteed to be ulong<64> with unit bits, as per  */
      /* promo.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* VAL IOS BOFF */
      LMAP (PKL_AST_TYPE (lvalue));
      break;
    default:
      break;
    }

  if (PKL_AST_CODE (lvalue) == PKL_AST_INDEXER
      || PKL_AST_CODE (lvalue) == PKL_AST_STRUCT_REF)
    pkl_asm_label (PKL_GEN_ASM, done);

  PKL_PASS_BREAK;

#undef LMAP
}
PKL_PHASE_END_HANDLER

/*
 * IF_STMT
 * | EXP
 * | THEN_STMT
 * | [ELSE_STMT]
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_if_stmt)
{
  pkl_ast_node if_stmt = PKL_PASS_NODE;
  pkl_ast_node if_exp = PKL_AST_IF_STMT_EXP (if_stmt);
  pkl_ast_node if_then_stmt = PKL_AST_IF_STMT_THEN_STMT (if_stmt);
  pkl_ast_node if_else_stmt = PKL_AST_IF_STMT_ELSE_STMT (if_stmt);

  if (PKL_AST_CODE (if_exp) == PKL_AST_INTEGER)
    {
      uint64_t exp_value = PKL_AST_INTEGER_VALUE (if_exp);

      if (exp_value == 0)
        {
          if (if_else_stmt)
            PKL_PASS_SUBPASS (if_else_stmt);
        }
      else
        {
          PKL_PASS_SUBPASS (if_then_stmt);
        }

      PKL_PASS_BREAK;
    }

  pkl_asm_if (PKL_GEN_ASM, if_exp);
  {
    PKL_PASS_SUBPASS (if_exp);
  }
  pkl_asm_then (PKL_GEN_ASM);
  {
    PKL_PASS_SUBPASS (if_then_stmt);
  }
  pkl_asm_else (PKL_GEN_ASM);
  {
    if (if_else_stmt)
      PKL_PASS_SUBPASS (if_else_stmt);
  }
  pkl_asm_endif (PKL_GEN_ASM);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * BREAK_CONTINUE_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_break_continue_stmt)
{
  pkl_ast_node stmt = PKL_PASS_NODE;
  int kind = PKL_AST_BREAK_CONTINUE_STMT_KIND (stmt);
  int nframes = PKL_AST_BREAK_CONTINUE_STMT_NFRAMES (PKL_PASS_NODE);
  int npopes = PKL_AST_BREAK_CONTINUE_STMT_NPOPES (PKL_PASS_NODE);

  if (nframes > 0)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPF, nframes);
  for (; npopes; --npopes)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPE);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA,
                kind == PKL_AST_BREAK_CONTINUE_STMT_KIND_BREAK
                ? pkl_asm_break_label (PKL_GEN_ASM)
                : pkl_asm_continue_label (PKL_GEN_ASM));
}
PKL_PHASE_END_HANDLER

/*
 * LOOP_STMT
 * | PARAMS
 * | BODY
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_loop_stmt)
{
  pkl_ast_node loop_stmt = PKL_PASS_NODE;
  int loop_stmt_kind = PKL_AST_LOOP_STMT_KIND (loop_stmt);
  pkl_ast_node body = PKL_AST_LOOP_STMT_BODY (loop_stmt);

  switch (loop_stmt_kind)
    {
    case PKL_AST_LOOP_STMT_KIND_WHILE:
      {
        pkl_ast_node condition
          = PKL_AST_LOOP_STMT_CONDITION (loop_stmt);

        assert (condition && body);

        if (PKL_AST_CODE (condition) == PKL_AST_INTEGER)
          {
            if (PKL_AST_INTEGER_VALUE (condition) == 0)
              ; /* while (0) is optimized away.  */
            else
              {
                pkl_asm_loop (PKL_GEN_ASM);
                PKL_PASS_SUBPASS (body);
                pkl_asm_endloop (PKL_GEN_ASM);
              }
          }
        else
          {
            pkl_asm_while (PKL_GEN_ASM);
            {
              PKL_PASS_SUBPASS (condition);
            }
            pkl_asm_while_loop (PKL_GEN_ASM, PKL_AST_TYPE (condition));
            {
              PKL_PASS_SUBPASS (body);
            }
            pkl_asm_while_endloop (PKL_GEN_ASM);
          }

      break;
      }
    case PKL_AST_LOOP_STMT_KIND_FOR:
      {
        pkl_ast_node head = PKL_AST_LOOP_STMT_HEAD (loop_stmt);
        pkl_ast_node condition = PKL_AST_LOOP_STMT_CONDITION (loop_stmt);

        pkl_asm_for (PKL_GEN_ASM, head);
        {
          for (; head; head = PKL_AST_CHAIN (head))
            PKL_PASS_SUBPASS (head);
        }
        pkl_asm_for_condition (PKL_GEN_ASM);
        {
          if (condition)
            PKL_PASS_SUBPASS (condition);
          else
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_int (1, 32));
        }
        pkl_asm_for_loop (PKL_GEN_ASM,
                          condition ? PKL_AST_TYPE (condition) : NULL);
        {
          PKL_PASS_SUBPASS (body);
        }
        pkl_asm_for_tail (PKL_GEN_ASM);
        {
          pkl_ast_node tail = PKL_AST_LOOP_STMT_TAIL (loop_stmt);

          for (; tail; tail = PKL_AST_CHAIN (tail))
            PKL_PASS_SUBPASS (tail);
        }
        pkl_asm_for_endloop (PKL_GEN_ASM);

        break;
      }
    case PKL_AST_LOOP_STMT_KIND_FOR_IN:
      {
        pkl_ast_node condition
          = PKL_AST_LOOP_STMT_CONDITION (loop_stmt);
        pkl_ast_node loop_stmt_iterator
          = PKL_AST_LOOP_STMT_ITERATOR (loop_stmt);
        pkl_ast_node container = NULL;
        pkl_ast_node container_type = NULL;

        assert (loop_stmt_iterator);

        container = PKL_AST_LOOP_STMT_ITERATOR_CONTAINER (loop_stmt_iterator);
        container_type = PKL_AST_TYPE (container);

        pkl_asm_for_in (PKL_GEN_ASM,
                        PKL_AST_TYPE_CODE (container_type),
                        condition);
        {
          PKL_PASS_SUBPASS (container);
        }
        pkl_asm_for_in_where (PKL_GEN_ASM);
        {
          if (condition)
            PKL_PASS_SUBPASS (condition);
        }
        pkl_asm_for_in_loop (PKL_GEN_ASM);
        {
          PKL_PASS_SUBPASS (body);
        }
        pkl_asm_for_in_endloop (PKL_GEN_ASM);

        break;
      }
    default:
      PK_UNREACHABLE ();
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * RETURN
 * | EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_return_stmt)
{
  /* Clean the stack before returning.  */
  size_t i;

  for (i = 0; i < PKL_AST_RETURN_STMT_NDROPS (PKL_PASS_NODE); ++i)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
}
PKL_PHASE_END_HANDLER

/*
 * | EXP
 * RETURN
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_return_stmt)
{
  /* Return from the function: pop exceptions and generate a return
     instruction.  */

  pkl_ast_node return_stmt = PKL_PASS_NODE;
  pkl_ast_node function = PKL_AST_RETURN_STMT_FUNCTION (return_stmt);
  pkl_ast_node function_type = PKL_AST_TYPE (function);
  int i;

  for (i = 0; i < PKL_AST_RETURN_STMT_NPOPES (PKL_PASS_NODE); ++i)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPE);

  /* In a void function, return PVM_NULL in the stack.  */
  if (PKL_AST_TYPE_CODE (PKL_AST_TYPE_F_RTYPE (function_type))
      == PKL_TYPE_VOID)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RETURN);
}
PKL_PHASE_END_HANDLER

/*
 * | EXP
 * EXP_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_exp_stmt)
{
  /* Drop the expression from the stack, but not if we are compiling a
     single statement.  */
  if (!(pkl_compiling_statement_p (PKL_GEN_PAYLOAD->compiler)
        && PKL_PASS_PARENT
        && PKL_AST_CODE (PKL_PASS_PARENT) == PKL_AST_PROGRAM)
      || PKL_GEN_PAYLOAD->in_file_p)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
}
PKL_PHASE_END_HANDLER

/*
 * FORMAT
 * | ARG
 * | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_format)
{
  pkl_ast_node format = PKL_PASS_NODE;
  pkl_ast_node format_args = PKL_AST_FORMAT_ARGS (format);
  pkl_ast_node arg;
  char *prefix = PKL_AST_FORMAT_PREFIX (format);
  int nstr = 0;
  /* XXX this hard limit should go away.
     See pkl-tran.c:pkl_trans1_ps_format.  */
#define MAX_CLASS_TAGS 32
  int nclasses = 0;
  struct
  {
    char *name;
    int index;
  } classes[MAX_CLASS_TAGS];

  /* Save all the intermediate strings in an array of strings and at the
     end, concatenate all of elements into a single string on the stack.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string_type ());
  pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env, "_pkl_mkclsn");
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYA);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_ulong (0, 64)); /* FIXME use better hint */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKA);

  if (prefix)
    {
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (0, 64));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (prefix));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS);
      ++nstr;
    }

  for (arg = format_args; arg; arg = PKL_AST_CHAIN (arg))
    {
      pkl_ast_node exp = PKL_AST_FORMAT_ARG_EXP (arg);
      char *begin_sc = PKL_AST_FORMAT_ARG_BEGIN_SC (arg);
      char *end_sc = PKL_AST_FORMAT_ARG_END_SC (arg);
      char *suffix = PKL_AST_FORMAT_ARG_SUFFIX (arg);
      int base = PKL_AST_FORMAT_ARG_BASE (arg);
      pkl_ast_node exp_type;
      int arg_omode;
      int arg_odepth;

      if (begin_sc)
        {
          assert (nclasses < MAX_CLASS_TAGS);
          classes[nclasses].name = begin_sc;
          classes[nclasses].index = nstr;
          ++nclasses;
        }

      if (end_sc)
        {
          char* cname;
          int cindex, n;
          pvm_val idx;

          assert (nclasses > 0);
          --nclasses;
          cname = classes[nclasses].name;
          cindex = classes[nclasses].index;

          assert (STREQ (cname, end_sc));

          n = nstr - cindex;
          if (n > 0)
            {
              nstr = cindex + 1;
              idx = pvm_make_ulong (cindex, 64);

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP);       /* ARR ARR */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, idx); /* ARR ARR IDX */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_ulong (n, 64));         /* ARR ARR IDX LEN */
              pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env,
                            "_pkl_reduce_string_array"); /* ARR STR */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);     /* STR ARR */

              for (; n > 1; --n)
                {
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, idx);
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREM);
                }

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, idx); /* STR ARR IDX */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);       /* ARR IDX STR */

              /* Set the string style property.  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SEL);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_ulong (0, 64));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* STR 0UL LEN */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_string (cname)); /* STR 0UL LEN CLASS */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SPROPS); /* ARR STR */

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ASET); /* ARR */
            }
        }

      if (!exp)
        goto fmt_suffix;

      /* Generate code to put the value on the stack.  */
      PKL_PASS_SUBPASS (exp);

      if (PKL_AST_FORMAT_ARG_VALUE_P (arg))
        {
          /* Generate code to format the literal value (%v).  */
          exp_type = PKL_AST_TYPE (exp);
          arg_omode = PKL_AST_FORMAT_ARG_FORMAT_MODE (arg);
          arg_odepth = PKL_AST_FORMAT_ARG_FORMAT_DEPTH (arg);

          /* Set the argument's own omode and odepth, saving
             the VM's own.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHOM); /* OMODE */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_int (arg_omode, 32)); /* OMODE NOMODE */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOM);  /* OMODE */

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHOD); /* OMODE ODEPTH */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_int (arg_odepth, 32)); /* OMODE ODEPTH NODEPTH */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOD);   /* OMODE ODEPTH */

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* OMODE ODEPTH EXP */

          /* Format the value.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_int (0, 32)); /* OMODE ODEPTH EXP DEPTH */
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_FORMATER);
          PKL_PASS_SUBPASS (exp_type); /* OMODE ODEPTH STR */
          PKL_GEN_POP_CONTEXT;

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT); /* STR OMODE ODEPTH */

          /* Restore the current omode and odepth in the VM.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOD); /* ARR STR OMODE */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOM); /* ARR STR */
        }
      else if (PKL_AST_FORMAT_ARG_FLOATING_POINT_P (arg))
        {
          enum pkl_asm_insn insn;
          pvm_val prec
              = pvm_make_uint (PKL_AST_FORMAT_ARG_FLOATING_POINT_PREC (arg),
                               32);

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, prec); /* ARR EXP PREC */

          switch (PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH (arg))
            {
            case PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH_SINGLE:
              insn = PKL_INSN_FORMATF32;
              break;
            case PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH_DOUBLE:
              insn = PKL_INSN_FORMATF64;
              break;
            default:
              assert (0 && "unreachable reached");
            }
          switch (PKL_AST_FORMAT_ARG_FLOATING_POINT_STYLE (arg))
            {
            case 'f':
              pkl_asm_insn (PKL_GEN_ASM, insn, 0);  /* ARR STR */
              break;
            case 'e':
              pkl_asm_insn (PKL_GEN_ASM, insn, 1);  /* ARR STR */
              break;
            case 'g':
              pkl_asm_insn (PKL_GEN_ASM, insn, 2);  /* ARR STR */
              break;
            default:
              assert (0 && "unreachable reached");
            }
        }
      else
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        base ? pvm_make_int (base, 32) : PVM_NULL);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FORMAT,
                        PKL_AST_TYPE (exp));  /* ARR STR */
        }

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_ulong (nstr++, 64)); /* ARR STR IDX */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* ARR IDX STR */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS);  /* ARR */

    fmt_suffix:
      if (suffix)
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (nstr++, 64));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (suffix));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS);
        }
    }

  if (nstr)
    {
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUP);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (0, 64));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (nstr, 64));
      pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env, "_pkl_reduce_string_array");
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
    }

  PKL_PASS_BREAK;

#undef MAX_CLASS_TAGS
}
PKL_PHASE_END_HANDLER

/*
 * PRINT_STMT
 * | ARG
 * | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_print_stmt)
{
  pkl_ast_node print_stmt = PKL_PASS_NODE;
  pkl_ast_node print_stmt_str_exp = PKL_AST_PRINT_STMT_STR_EXP (print_stmt);

  if (print_stmt_str_exp) /* print statement */
    {
      PKL_PASS_SUBPASS (print_stmt_str_exp);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINTS);
    }
  else /* printf statement */
    {
      pkl_ast_node print_stmt_fmt = PKL_AST_PRINT_STMT_FORMAT (print_stmt);
      pkl_ast_node print_stmt_args = PKL_AST_FORMAT_ARGS (print_stmt_fmt);
      pkl_ast_node arg;
      char *prefix = PKL_AST_FORMAT_PREFIX (print_stmt_fmt);
      int nexp;

      /* First, compute the arguments and push them to the stack.  */

      for (nexp = 0, arg = print_stmt_args;
           arg;
           arg = PKL_AST_CHAIN (arg))
        {
          pkl_ast_node exp = PKL_AST_FORMAT_ARG_EXP (arg);

          if (exp)
            {
              PKL_PASS_SUBPASS (exp);
              nexp++;
            }
        }

      /* Reverse the arguments in the stack so we can print it in the
         right order.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REV, nexp);

      /* Now print out the stuff.  */
      if (prefix)
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (prefix));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINTS);
        }

      for (arg = print_stmt_args; arg; arg = PKL_AST_CHAIN (arg))
        {
          /* Handle the argument.  */

          pkl_ast_node exp = PKL_AST_FORMAT_ARG_EXP (arg);
          char *begin_sc = PKL_AST_FORMAT_ARG_BEGIN_SC (arg);
          char *end_sc = PKL_AST_FORMAT_ARG_END_SC (arg);
          char *suffix = PKL_AST_FORMAT_ARG_SUFFIX (arg);
          int base = PKL_AST_FORMAT_ARG_BASE (arg);

          if (begin_sc)
            {
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_string (begin_sc));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BEGINSC);
            }

          if (end_sc)
            {
              pvm_program_label label
                = pkl_asm_fresh_label (PKL_GEN_ASM);

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_string (end_sc));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ENDSC);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, label);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_exception (PVM_E_GENERIC,
                                                "invalid class",
                                                PVM_E_GENERIC_ESTATUS, NULL, NULL));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
              pkl_asm_label (PKL_GEN_ASM, label);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
            }

          if (exp)
            {
              if (PKL_AST_FORMAT_ARG_VALUE_P (arg))
                {
                  /* Generate code to print the value.  */
                  pkl_ast_node exp_type = PKL_AST_TYPE (exp);
                  int arg_omode = PKL_AST_FORMAT_ARG_FORMAT_MODE (arg);
                  int arg_odepth = PKL_AST_FORMAT_ARG_FORMAT_DEPTH (arg);

                  /* Set the argument's own omode and odepth, saving
                     the VM's own.  */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHOM); /* OMODE */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                                pvm_make_int (arg_omode, 32)); /* OMODE NOMODE */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOM); /* OMODE */

                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHOD); /* OMODE ODEPTH */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                                pvm_make_int (arg_odepth, 32)); /* OMODE ODEPTH NODEPTH */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOD); /* OMODE ODEPTH */

                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* OMODE ODEPTH EXP */

                  /* Print out the value.  */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                                pvm_make_int (0, 32)); /* OMODE ODEPTH EXP DEPTH */
                  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_PRINTER);
                  PKL_PASS_SUBPASS (exp_type);
                  PKL_GEN_POP_CONTEXT;

                  /* Restore the current omode and odepth in the VM.  */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOD); /* OMODE */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_POPOM); /* _ */
                }
              else if (PKL_AST_FORMAT_ARG_FLOATING_POINT_P (arg))
                {
                  enum pkl_asm_insn insn;
                  pvm_val prec = pvm_make_uint (
                      PKL_AST_FORMAT_ARG_FLOATING_POINT_PREC (arg), 32);

                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, prec); /* EXP PREC */

                  switch (PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH (arg))
                    {
                    case PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH_SINGLE:
                      insn = PKL_INSN_FORMATF32;
                      break;
                    case PKL_AST_FORMAT_ARG_FLOATING_POINT_WIDTH_DOUBLE:
                      insn = PKL_INSN_FORMATF64;
                      break;
                    default:
                      assert (0 && "unreachable reached");
                    }
                  switch (PKL_AST_FORMAT_ARG_FLOATING_POINT_STYLE (arg))
                    {
                    case 'f':
                      pkl_asm_insn (PKL_GEN_ASM, insn, 0);  /* STR */
                      break;
                    case 'e':
                      pkl_asm_insn (PKL_GEN_ASM, insn, 1);  /* STR */
                      break;
                    case 'g':
                      pkl_asm_insn (PKL_GEN_ASM, insn, 2);  /* STR */
                      break;
                    default:
                      assert (0 && "unreachable reached");
                    }
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINTS);
                }
              else
                {
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                                base ? pvm_make_int (base, 32) : PVM_NULL);
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINT, PKL_AST_TYPE (exp));
                }
            }

          if (suffix)
            {
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (suffix));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINTS);
            }
        }
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | [EXP]
 * RAISE_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_raise_stmt)
{
  pkl_ast_node raise_stmt = PKL_PASS_NODE;

  /* If the `raise' statement was anonymous, then we need to push the
     exception to raise, which by default, is 0.  */
  if (PKL_AST_RAISE_STMT_EXP (raise_stmt) == NULL)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                  pvm_make_exception (PVM_E_GENERIC, PVM_E_GENERIC_NAME,
                                      PVM_E_GENERIC_ESTATUS, NULL, NULL));

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
}
PKL_PHASE_END_HANDLER

/*
 * | CODE
 * TRY_STMT_BODY
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_try_stmt_body)
{
  /* Nothing to do here.  */
}
PKL_PHASE_END_HANDLER

/*
 * | CODE
 * TRY_STMT_HANDLER
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_try_stmt_handler)
{
  /* Nothing to do here.  */
}
PKL_PHASE_END_HANDLER

/*
 * | BODY
 * | [HANDLER]
 * | [ARG]
 * | [EXP]
 * TRY_STMT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_try_stmt)
{
  pkl_ast_node try_stmt = PKL_PASS_NODE;

  if (PKL_AST_TRY_STMT_KIND (try_stmt) == PKL_AST_TRY_STMT_KIND_CATCH)
    {
      /* try-catch statement.  */
      pkl_ast_node body = PKL_AST_TRY_STMT_BODY (try_stmt);
      pkl_ast_node handler = PKL_AST_TRY_STMT_HANDLER (try_stmt);
      pkl_ast_node arg = PKL_AST_TRY_STMT_ARG (try_stmt);
      pkl_ast_node exp = PKL_AST_TRY_STMT_EXP (try_stmt);

      /* Push the exception that will be caught by the sentence.  This
         is EXP if it is defined, or E_generic if it isn't.  */
      if (exp)
        PKL_PASS_SUBPASS (exp);
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                      pvm_make_exception (PVM_E_GENERIC, PVM_E_GENERIC_NAME,
                                          PVM_E_GENERIC_ESTATUS, NULL, NULL));

      pkl_asm_try (PKL_GEN_ASM, arg);
      {
        PKL_PASS_SUBPASS (body);
      }
      pkl_asm_catch (PKL_GEN_ASM);
      {
        PKL_PASS_SUBPASS (handler);
      }
      pkl_asm_endtry (PKL_GEN_ASM);
    }
  else
    {
      /* try-until statement.  */
      pkl_ast_node body = PKL_AST_TRY_STMT_BODY (try_stmt);
      pkl_ast_node exp = PKL_AST_TRY_STMT_EXP (try_stmt);

      /* Push the exception to catch.  */
      PKL_PASS_SUBPASS (exp);
      pkl_asm_try (PKL_GEN_ASM, NULL);
      {
        pkl_asm_loop (PKL_GEN_ASM);
        PKL_PASS_SUBPASS (body);
        pkl_asm_endloop (PKL_GEN_ASM);
      }
      pkl_asm_catch (PKL_GEN_ASM);
      {
      }
      pkl_asm_endtry (PKL_GEN_ASM);
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | EXP
 * FUNCALL_ARG
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_funcall_arg)
{
  /* No extra action is required here.  */
}
PKL_PHASE_END_HANDLER


/* FUNCALL
 * | [ARG]
 * | ...
 * | FUNCTION
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_funcall)
{
  pkl_ast_node funcall = PKL_PASS_NODE;
  pkl_ast_node function = PKL_AST_FUNCALL_FUNCTION (funcall);
  pkl_ast_node function_type = PKL_AST_TYPE (function);
  int vararg = PKL_AST_TYPE_F_VARARG (function_type);
  int i, aindex = 0, vararg_actual = 0, optionals_specified = 0;
  pkl_ast_node aa;

  /* Push the actuals to the stack. */
  for (aa = PKL_AST_FUNCALL_ARGS (funcall); aa; aa = PKL_AST_CHAIN (aa))
    {
      if (PKL_AST_FUNCALL_ARG_FIRST_VARARG (aa))
        vararg_actual = 1;

      if (vararg_actual)
        aindex++;

      if (!PKL_AST_FUNCALL_ARG_EXP (aa))
        {
          /* This is a non-specified actual for a formal having a
             default value.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
          optionals_specified++;
        }
      else
        PKL_PASS_SUBPASS (aa);
    }

  if (vararg)
    {
      /* The actuals are stored in the stack in reverse order.
         Reverse them.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REV, aindex);

      /* Create the array of variable arguments.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
      pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env, "_pkl_mkclsn");
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYA); /* ANY[] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_ulong (aindex, 64));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKA);

      /* Insert the elements in the array.  */
      for (i = 0; i < aindex; ++i)
        {
                                                     /* ... ELEM ARR */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_ulong (i, 64));     /* ... ELEM ARR IDX */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT);  /* ... ARR IDX ELEM */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS); /* ... ARR */
        }
    }

  /* Complete non-specified actuals for formals having default values.
     For these, we should push nulls.  But beware the vararg!  */
  {
    int non_specified
      = (PKL_AST_TYPE_F_NARG (function_type)
         - PKL_AST_FUNCALL_NARG (funcall)
         - PKL_AST_TYPE_F_VARARG (function_type)
         - optionals_specified);

    for (i = 0; i < non_specified; ++i)
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
  }

  /* Push the closure for FUNCTION and call the bloody function.  */
  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_FUNCALL);
  PKL_PASS_SUBPASS (PKL_AST_FUNCALL_FUNCTION (funcall));
  PKL_GEN_POP_CONTEXT;
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * FUNC
 * | [TYPE]
 * | [FUNC_ARG]
 * | ...
 * | BODY
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_func)
{
  pkl_ast_node function = PKL_PASS_NODE;
  int nargs = PKL_AST_FUNC_NARGS (function);
  int method_p = PKL_AST_FUNC_METHOD_P (PKL_PASS_NODE);

  /* This is a function prologue.  */
  if (PKL_AST_FUNC_NAME (function))
    pkl_asm_note (PKL_GEN_ASM,
                  PKL_AST_FUNC_NAME (function));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PROLOG);

  if (nargs > 1)
    {
      /* Reverse the arguments.

         Note that in methods the implicit struct argument is passed
         as the last actual.  However, we have to process it as the
         _first_ formal.  We achieve this by not reversing it, saving
         it in the return stack temporarily.  */

      if (method_p)
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REV, nargs);

      if (method_p)
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);
    }

  /* If the function's return type is an array type, make sure it has
     a bounder.  If it hasn't one, then compute it in this
     environment.  */
  {
    pkl_ast_node rtype = PKL_AST_FUNC_RET_TYPE (function);

    if (PKL_AST_TYPE_CODE (rtype) == PKL_TYPE_ARRAY
        && PKL_AST_TYPE_A_BOUNDER (rtype) == PVM_NULL)
      {
        assert (!PKL_AST_TYPE_NAMED_P (rtype));
        PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
        PKL_PASS_SUBPASS (rtype);
        PKL_GEN_POP_CONTEXT;
      }
  }

  /* Push the function environment, for the arguments.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSHF,
                method_p ? nargs + 1 : nargs);

  /* If in a method, register the implicit argument.  */
  if (method_p)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REGVAR);
}
PKL_PHASE_END_HANDLER

/*
 * FUNC_ARG
 * | TYPE
 * | INITIAL
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_func_arg)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node func_arg = PKL_PASS_NODE;
  pkl_ast_node func_arg_initial = PKL_AST_FUNC_ARG_INITIAL (func_arg);
  pkl_ast_node func_arg_type = PKL_AST_FUNC_ARG_TYPE (func_arg);
  pvm_program_label after_conv_label = pkl_asm_fresh_label (PKL_GEN_ASM);

  /* Traverse the argument type in normal context.  */
  PKL_GEN_PUSH_CONTEXT;
  PKL_PASS_SUBPASS (func_arg_type); /* _ */
  PKL_GEN_POP_CONTEXT;

  if (func_arg_initial)
    {
      pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

      /* If the value on the stack is `null', that means we need to
         use the default value for the argument.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNN, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* Drop the null */
      PKL_PASS_SUBPASS (func_arg_initial);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, after_conv_label);
      pkl_asm_label (PKL_GEN_ASM, label);
    }

  /* If the argument is an array, check/cast to its type, in order to
     perform whatever needed run-time checks.  This is done here and
     not in a cast at funcall time because the argument's type is
     evaluated in the function's lexical environment.  As per promo,
     we know that the value on the stack is an array with the same
     base type, but possibly different bounding.

     Note that if the initial argument is used, then the flow jumps to
     `after_conv_label' and therefore the code below is not executed,
     as promo already performed a cast if needed.  */
  if (PKL_AST_TYPE_CODE (func_arg_type) == PKL_TYPE_ARRAY)
    {
      /* Make sure the cast type has a bounder.  If it doesn't,
         compile and install one.  */

      if (PKL_AST_TYPE_A_BOUNDER (func_arg_type) == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (func_arg_type));
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (func_arg_type);
          PKL_GEN_POP_CONTEXT;
        }

      pkl_asm_insn (pasm, PKL_INSN_ATOA,
                    NULL /* from_type */, func_arg_type);
    }

  pkl_asm_label (PKL_GEN_ASM, after_conv_label);

  /* Pop the actual argument from the stack and put it in the current
     environment.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REGVAR);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | [TYPE]
 * | [FUNC_ARG]
 * | ...
 * | BODY
 * FUNC
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_func)
{
  /* Function epilogue.  */

  pkl_ast_node function = PKL_PASS_NODE;
  pkl_ast_node function_type = PKL_AST_TYPE (function);

  /* In a void function, return PVM_NULL in the stack.  Otherwise, it
     is a run-time error to reach this point.  */
  if (PKL_AST_TYPE_CODE (PKL_AST_TYPE_F_RTYPE (function_type))
      == PKL_TYPE_VOID)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
  else
    {
      char *function_name = PKL_AST_FUNC_NAME (function);
      pkl_ast_loc function_location = PKL_AST_LOC (function);
      char *msg = NULL, *locstr = NULL;

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_exception (PVM_E_NO_RETURN, PVM_E_NO_RETURN_NAME,
                                        PVM_E_NO_RETURN_ESTATUS, NULL, NULL));

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string ("msg"));
      if (PKL_AST_LOC_VALID (function_location))
        locstr = pkl_ast_format_loc (PKL_GEN_PAYLOAD->filename,
                                     function_location);
      msg = pk_str_concat (locstr == NULL ? "" : locstr,
                           locstr == NULL ? "" : ": ",
                           "in function ",
                           function_name == NULL ? "lambda" : function_name,
                           NULL);
      if (msg == NULL)
        {
          PKL_ICE (PKL_AST_LOC (function), "out of memory");
          PKL_PASS_ERROR;
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (msg));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
      free (locstr);
      free (msg);
    }

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RETURN);
}
PKL_PHASE_END_HANDLER

/*
 * INTEGER
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_integer)
{
  pkl_ast_node integer = PKL_PASS_NODE;
  pkl_ast_node type;
  pvm_val val;
  int size;
  uint64_t value;

  type = PKL_AST_TYPE (integer);
  assert (type != NULL
          && PKL_AST_TYPE_CODE (type) == PKL_TYPE_INTEGRAL);

  size = PKL_AST_TYPE_I_SIZE (type);
  value = PKL_AST_INTEGER_VALUE (integer);

  if ((size - 1) & ~0x1f)
    {
      if (PKL_AST_TYPE_I_SIGNED_P (type))
        val = pvm_make_long (value, size);
      else
        val = pvm_make_ulong (value, size);
    }
  else
    {
      if (PKL_AST_TYPE_I_SIGNED_P (type))
        val = pvm_make_int (value, size);
      else
        val = pvm_make_uint (value, size);
    }

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, val);
}
PKL_PHASE_END_HANDLER

/*
 * IDENTIFIER
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_identifier)
{
  pkl_ast_node identifier = PKL_PASS_NODE;
  pvm_val val
    = pvm_make_string (PKL_AST_IDENTIFIER_POINTER (identifier));

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, val);
}
PKL_PHASE_END_HANDLER

/*
 * STRING
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_string)
{
  pkl_ast_node string = PKL_PASS_NODE;
  pvm_val val
    = pvm_make_string (PKL_AST_STRING_POINTER (string));

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, val);
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_OFFSET
 * | BASE_TYPE
 * | UNIT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_type_offset)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      /* Stack: IOS BOFF VAL */
      /* The offset to poke is stored in the TOS.  Replace the offset
         at the TOS with the magnitude of the offset and let the
         BASE_TYPE handler to tackle it.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM); /* IOS BOFF VAL VMAG */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);   /* IOS BOFF VMAG */

      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (PKL_PASS_NODE)); /* _ */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER | PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (PKL_PASS_NODE)); /* VAL */

      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (PKL_PASS_NODE)); /* VAL BASE_TYPE */
      PKL_GEN_POP_CONTEXT;
      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_UNIT (PKL_PASS_NODE));      /* VAL BASE_TYPE UNIT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYO);                  /* VAL TYPE */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKO);                    /* OFF */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
                                                /* VAL DEPTH */
      RAS_MACRO_ANY_PRINTER;                    /* _ */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
                                                /* VAL DEPTH */
      RAS_MACRO_ANY_FORMATER;                   /* STR */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      RAS_MACRO_OFFSET_TYPIFIER (PKL_PASS_NODE); /* SCT */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
    /* Just build an offset type.  */
      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (PKL_PASS_NODE)); /* BASE_TYPE */
      PKL_PASS_SUBPASS (PKL_AST_TYPE_O_UNIT (PKL_PASS_NODE));      /* BASE_TYPE UNIT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYO);                  /* TYPE */
      if (PKL_AST_TYPE_O_REF_TYPE (PKL_PASS_NODE))
        {
          PKL_PASS_SUBPASS (PKL_AST_TYPE_O_REF_TYPE (PKL_PASS_NODE));  /* TYPE REF_TYPE */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYOSETRT);               /* TYPE */
        }

      PKL_PASS_BREAK;
    }

  /* We are in the normal context.  Process the base type, but not the
     unit as we don't need it.  */
  PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (PKL_PASS_NODE));
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | MAGNITUDE
 * | UNIT
 * OFFSET
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_offset)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node offset_type = PKL_AST_TYPE (PKL_PASS_NODE);

  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
  PKL_PASS_SUBPASS (PKL_AST_TYPE_O_BASE_TYPE (offset_type)); /* MAGNITUDE UNIT BASE_TYPE */
  PKL_GEN_POP_CONTEXT;
  pkl_asm_insn (pasm, PKL_INSN_SWAP); /* MAGNITUDE BASE_TYPE UNIT */
  pkl_asm_insn (pasm, PKL_INSN_MKTYO); /* MAGNITUDE TYPE */
  pkl_asm_insn (pasm, PKL_INSN_MKO);
}
PKL_PHASE_END_HANDLER

/*
 * | TYPE
 * | EXP
 * ISA
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_isa)
{
  pkl_ast_node isa = PKL_PASS_NODE;
  pkl_ast_node isa_type = PKL_AST_ISA_TYPE (isa);

  if (PKL_AST_TYPE_CODE (isa_type) == PKL_TYPE_ARRAY
      && PKL_AST_TYPE_A_BOUNDER (isa_type) == PVM_NULL)
    {
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
      PKL_PASS_SUBPASS (isa_type);
      PKL_GEN_POP_CONTEXT;
    }

  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
  PKL_PASS_SUBPASS (isa_type);
  PKL_GEN_POP_CONTEXT;

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ISA);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

/*
 * CAST
 * | TYPE
 * | EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_cast)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node node = PKL_PASS_NODE;

  pkl_ast_node exp;
  pkl_ast_node to_type;
  pkl_ast_node from_type;

  exp = PKL_AST_CAST_EXP (node);

  to_type = PKL_AST_CAST_TYPE (node);
  from_type = PKL_AST_TYPE (exp);

  /* Traverse the type and expression in normal context.  */
  PKL_GEN_PUSH_CONTEXT;
  PKL_PASS_SUBPASS (to_type);  /* _ */
  PKL_PASS_SUBPASS (exp);      /* EXP */
  PKL_GEN_POP_CONTEXT;

  /* And finally generate code for the cast operation.  */
  if (PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_ANY)
    {
      pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

      if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_ARRAY
          && PKL_AST_TYPE_A_BOUNDER (to_type) == PVM_NULL)
        {
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (to_type);
          PKL_GEN_POP_CONTEXT;
        }

      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
      PKL_PASS_SUBPASS (to_type);
      PKL_GEN_POP_CONTEXT;

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ISA);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_exception (PVM_E_CONV, PVM_E_CONV_NAME,
                                        PVM_E_CONV_ESTATUS, NULL, NULL));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
      pkl_asm_label (PKL_GEN_ASM, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
    }
  else if (PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_INTEGRAL
           && PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_INTEGRAL)
    {
      pkl_asm_insn (pasm, PKL_INSN_NTON,
                    from_type, to_type);
      pkl_asm_insn (pasm, PKL_INSN_NIP);
    }
  else if (PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_OFFSET
           && PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_OFFSET)
    {
      pkl_ast_node to_unit = PKL_AST_TYPE_O_UNIT (to_type);

      pkl_asm_insn (pasm, PKL_INSN_PUSH,
                    pvm_make_ulong (PKL_AST_INTEGER_VALUE (to_unit), 64));
      RAS_MACRO_OFFSET_CAST (from_type, to_type);
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_STRING)
    {
      pkl_asm_insn (pasm, PKL_INSN_CTOS);
      pkl_asm_insn (pasm, PKL_INSN_NIP);
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_ARRAY
           && PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_ARRAY)
    {
      /* Make sure the cast type have a bounder.  If it doesn't,
         compile and install one.  */

      if (PKL_AST_TYPE_A_BOUNDER (to_type) == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (to_type));
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (to_type);
          PKL_GEN_POP_CONTEXT;
        }

      pkl_asm_insn (pasm, PKL_INSN_ATOA, from_type, to_type);
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_STRUCT
           && PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_STRUCT)
    {
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
      PKL_PASS_SUBPASS (to_type);
      PKL_GEN_POP_CONTEXT;
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_STRUCT
           && PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_INTEGRAL)
    {
      pkl_ast_node itype = PKL_AST_TYPE_S_ITYPE (to_type);

      /* This is guaranteed as per typify.  */
      assert (itype);

      pkl_asm_insn (pasm, PKL_INSN_NTON, from_type, itype);
      pkl_asm_insn (pasm, PKL_INSN_NIP);
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_DEINTEGRATOR);
      PKL_PASS_SUBPASS (to_type);
      PKL_GEN_POP_CONTEXT;
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_INTEGRAL
           && PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_STRUCT)
    {
      pkl_ast_node itype = PKL_AST_TYPE_S_ITYPE (from_type);

      /* This is guaranteed as per typify.  */
      assert (itype);

      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_INTEGRATOR);
      PKL_PASS_SUBPASS (from_type);
      PKL_GEN_POP_CONTEXT;

      pkl_asm_insn (pasm, PKL_INSN_NTON, itype, to_type);
      pkl_asm_insn (pasm, PKL_INSN_NIP);
    }
  else if (PKL_AST_TYPE_CODE (from_type) == PKL_TYPE_ARRAY
           && PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_INTEGRAL)
    {
      pkl_ast_node u64t = pkl_ast_make_integral_type (PKL_PASS_AST, 64, 0);

      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_INTEGRATOR);
      PKL_PASS_SUBPASS (from_type);
      PKL_GEN_POP_CONTEXT;

                                          /* IVAL(ULONG) WIDTH(UINT) */
      pkl_asm_insn (pasm, PKL_INSN_DROP); /* IVAL(ULONG) */
      pkl_asm_insn (pasm, PKL_INSN_NTON, u64t, to_type);
                                          /* IVAL(ULONG) IVAL */
      pkl_asm_insn (pasm, PKL_INSN_NIP);  /* IVAL */
      u64t = ASTREF (u64t); pkl_ast_node_free (u64t);
    }
  else if (PKL_AST_TYPE_CODE (to_type) == PKL_TYPE_ANY)
    /* Do nothing in casts to `any'.  */;
  else
    PK_UNREACHABLE ();

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | CONS_TYPE
 * | [CONS_VALUE]
 * CONS
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_cons)
{
  pkl_ast_node cons = PKL_PASS_NODE;
  int cons_kind = PKL_AST_CONS_KIND (cons);
  pkl_ast_node cons_type = PKL_AST_CONS_TYPE (cons);

  switch (cons_kind)
    {
    case PKL_AST_CONS_KIND_ARRAY:
      /* Build an array with the given initial value.  */
      if (!PKL_AST_CONS_VALUE (cons))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
      PKL_PASS_SUBPASS (cons_type);
      PKL_GEN_POP_CONTEXT;
      break;
    case PKL_AST_CONS_KIND_STRUCT:
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
      PKL_PASS_SUBPASS (cons_type);
      PKL_GEN_POP_CONTEXT;
      break;
    default:
      PK_UNREACHABLE ();
    }
}
PKL_PHASE_END_HANDLER

/*
 * MAP
 * | [MAP_IOS]
 * | MAP_OFFSET
 * | MAP_TYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_map)
{
  pkl_ast_node map = PKL_PASS_NODE;
  pkl_ast_node map_offset = PKL_AST_MAP_OFFSET (map);
  pkl_ast_node map_ios = PKL_AST_MAP_IOS (map);
  pkl_ast_node map_type = PKL_AST_MAP_TYPE (map);

  /* Traverse the map type in normal context.  */
  PKL_GEN_PUSH_CONTEXT;
  PKL_PASS_SUBPASS (map_type);
  PKL_GEN_POP_CONTEXT;

  if (PKL_PASS_PARENT == NULL
      && PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_LVALUE))
    {
      /* This is an l-value in an assignment.  Generate code for the
         offset, which is expected by the ass_stmt PS handler.  */
      if (map_ios)
        {
          PKL_GEN_PUSH_CONTEXT;
          PKL_PASS_SUBPASS (map_ios);
          PKL_GEN_POP_CONTEXT;
        }
      else
        RAS_MACRO_PUSHIOS;

      PKL_GEN_PUSH_CONTEXT;
      PKL_PASS_SUBPASS (map_offset);
      PKL_GEN_POP_CONTEXT;
    }
  else
    {
      pkl_ast_node map_offset_magnitude = NULL;

      /* Push the strictness to use for the map.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_int (PKL_AST_MAP_STRICT_P (map), 32));

      /* Push the IOS of the map.  */
      if (map_ios)
        PKL_PASS_SUBPASS (map_ios);
      else
        RAS_MACRO_PUSHIOS;

      /* Push the offset of the map and convert to a bit-offset.  Note
         that the offset is guaranteed to be an ulong<64> with unit
         bits, as per promo.

         But optimize for offsets whose magnitude is an integer node,
         transforming to bit offsets at compile time.  */
      if (PKL_AST_CODE (map_offset) == PKL_AST_OFFSET)
        map_offset_magnitude = PKL_AST_OFFSET_MAGNITUDE (map_offset);

      if (map_offset_magnitude
          && PKL_AST_CODE (map_offset_magnitude) == PKL_AST_INTEGER)
        {
          uint64_t magnitude
            = PKL_AST_INTEGER_VALUE (map_offset_magnitude);

          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        pvm_make_ulong (magnitude, 64));
        }
      else
        {
          PKL_PASS_SUBPASS (map_offset);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
        }

      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_MAPPER);
      PKL_PASS_SUBPASS (map_type);
      PKL_GEN_POP_CONTEXT;
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * ARRAY_INITIALIZER
 * | ARRAY_INITIALIZER_INDEX
 * | ARRAY_INITIALIZER_EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_array_initializer)
{
  /* Do nothing.  */
}
PKL_PHASE_END_HANDLER

/*
 * | ARRAY_INITIALIZER_INDEX
 * | ARRAY_INITIALIZER_EXP
 * ARRAY_INITIALIZER
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_array_initializer)
{
  /* Insert this initializer in the array.  */
                                             /* ARR IDX EXP */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS); /* ARR */
}
PKL_PHASE_END_HANDLER

/*
 *  ARRAY
 *  | ARRAY_INITIALIZER
 *  | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_array)
{
  pkl_ast_node array = PKL_PASS_NODE;
  pkl_ast_node array_type = PKL_AST_TYPE (array);
  pvm_val array_type_writer = PVM_NULL;

  /* Create a new empty array of the right type, having the right
     number of elements.  Note that the have to compute the array
     type's bounder here. */

  if (PKL_AST_TYPE_A_BOUNDER (array_type) == PVM_NULL)
    {
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
      PKL_PASS_SUBPASS (array_type);
      PKL_GEN_POP_CONTEXT;
    }

  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
  PKL_PASS_SUBPASS (array_type);             /* TYP */
  PKL_GEN_POP_CONTEXT;

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_ulong (PKL_AST_ARRAY_NELEM (array), 64));
                                            /* TYP NELEM */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKA); /* ARR */

  /* Install a writer in the array.  */
  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
  RAS_FUNCTION_ARRAY_WRITER (array_type_writer, array_type);
  PKL_GEN_POP_CONTEXT;
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_type_writer); /* CLS */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                     /* CLS */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETW);                   /* ARR */
}
PKL_PHASE_END_HANDLER

/*
 * | ARRAY_TYPE
 * | ARRAY_INITIALIZER
 * | ...
 * ARRAY
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_array)
{
  /* Nothing to do here.  */
}
PKL_PHASE_END_HANDLER

/*
 * TRIMMER
 * | ENTITY
 * | FROM
 * | TO
 * | ADDEND
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_trimmer)
{
  pkl_ast_node trimmer = PKL_PASS_NODE;
  pkl_ast_node trimmer_type = PKL_AST_TYPE (trimmer);
  pkl_ast_node trimmer_entity = PKL_AST_TRIMMER_ENTITY (trimmer);
  pkl_ast_node trimmer_from = PKL_AST_TRIMMER_FROM (trimmer);
  pkl_ast_node trimmer_to = PKL_AST_TRIMMER_TO (trimmer);

  PKL_PASS_SUBPASS (trimmer_entity);
  PKL_PASS_SUBPASS (trimmer_from);
  PKL_PASS_SUBPASS (trimmer_to);

  switch (PKL_AST_TYPE_CODE (trimmer_type))
    {
    case PKL_TYPE_STRING:
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SUBSTR);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
      break;
    case PKL_TYPE_ARRAY:
      RAS_MACRO_ATRIM (trimmer_type);
      break;
    default:
      PK_UNREACHABLE ();
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * INDEXER
 * | INDEXER_ENTITY
 * | INDEXER_INDEX
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_indexer)
{
  pkl_ast_node indexer = PKL_PASS_NODE;
  pkl_ast_node indexer_entity = PKL_AST_INDEXER_ENTITY (indexer);
  pkl_ast_node indexer_index = PKL_AST_INDEXER_INDEX (indexer);

  /* Traverse the entity and indexer in normal context.  */
  PKL_GEN_PUSH_CONTEXT;
  PKL_PASS_SUBPASS (indexer_entity);
  PKL_PASS_SUBPASS (indexer_index);
  PKL_GEN_POP_CONTEXT;

  if (PKL_PASS_PARENT == NULL
      && PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_LVALUE))
    {
      /* This is a l-value in an assignment.  The array and the index
         are pushed to the stack for the ass_stmt PR handler.  Nothing
         else to do here.  Note that analf guarantees that the entity
         in this indexer is an array, not a string.  */
    }
  else
    {
      pkl_ast_node indexer_type = PKL_AST_TYPE (indexer);
      pkl_ast_node container = PKL_AST_INDEXER_ENTITY (indexer);
      pkl_ast_node container_type = PKL_AST_TYPE (container);
      pkl_ast_node index = PKL_AST_INDEXER_INDEX (indexer);
      pkl_ast_node index_type = PKL_AST_TYPE (index);

      switch (PKL_AST_TYPE_CODE (container_type))
        {
        case PKL_TYPE_ARRAY:
          if (PKL_AST_TYPE_CODE (index_type) == PKL_TYPE_INTEGRAL)
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREF); /* ARR IDX VAL */
          else
            {
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* ARR IDX ARR */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* ARR IDX ARR IDX */
              RAS_MACRO_AOREF (container_type, index_type); /* ARR IDX VAL */
            }
          /* To cover cases where the referenced array is not mapped, but
             the value stored in it is a mapped value, we issue a
             REMAP.  */
          switch (PKL_AST_TYPE_CODE (indexer_type))
            {
            case PKL_TYPE_ARRAY:
              /* However, if the entity indexed is an array, its type
                 is complete and an array or struct, and it is used as
                 the entity in an indexer, the REMAP is _not_
                 necessary.  */
              if (PKL_AST_INDEXER_IS_INDEXED (indexer)
                  && (PKL_AST_TYPE_COMPLETE (indexer_type)
                      == PKL_AST_TYPE_COMPLETE_YES))
                {
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
                  break;
                }
            case PKL_TYPE_STRUCT:
              {
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREMAP); /* ARR IDX NVAL REMAPPED_P */

                /* ARR IDX NVAL REMAPPED_P */
                pvm_program_label done
                  = pkl_asm_fresh_label (PKL_GEN_ASM);

                /* If the value was remapped, replace the old value of the
                   variable with the new in the corresponding frame.  */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, done);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* ARR IDX NVAL */

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR); /* ARR IDX (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ATR); /* ARR IDX NVAL (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT); /* NVAL ARR IDX (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* NVAL ARR IDX NVAL */

                /* target: NVAL ARR IDX NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ASET); /* NVAL ARR */

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL); /* NVAL null*/
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* null NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* null NVAL null */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* null null NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* null null NVAL null */
                pkl_asm_label (PKL_GEN_ASM, done);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* ARR IDX NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* NVAL */
              }
              break;
            default:
              /* ARR IDX VAL */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* VAL */
              break;
            }
          break;
        case PKL_TYPE_STRING:
          if (PKL_AST_TYPE_CODE (index_type) == PKL_TYPE_INTEGRAL)
            {
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_STRREF);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
            }
          else
            RAS_MACRO_STROREF (index_type);
          break;
        default:
          PK_UNREACHABLE ();
        }
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * STRUCT
 * | STRUCT_FIELD
 * | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_struct)
{
  /* The offset of the new struct, which should be PVM_NULL, as it is
     not mapped.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
}
PKL_PHASE_END_HANDLER

/*
 *  | STRUCT_FIELD
 *  | ...
 *  STRUCT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_struct)
{
  pkl_ast_node sct = PKL_PASS_NODE;
  pkl_ast_node sct_type = PKL_AST_TYPE (sct);

  /* No methods in struct literals.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (0, 64));
  /* The number of elements in struct literals corresponds to the
     number of fields, since there are no declarations in them.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_ulong (PKL_AST_STRUCT_NELEM (sct), 64));

  PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_TYPE);
  PKL_PASS_SUBPASS (sct_type); /* TYP */
  PKL_GEN_POP_CONTEXT;

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKSCT);
}
PKL_PHASE_END_HANDLER

/*
 *  STRUCT_FIELD
 *  | [STRUCT_FIELD_NAME]
 *  | STRUCT_FIELD_EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_struct_field)
{
  pkl_ast_node struct_field = PKL_PASS_NODE;
  pkl_ast_node struct_field_name
    = PKL_AST_STRUCT_FIELD_NAME (struct_field);

  /* Element's offset.  PVM_NULL means use the "natural" offset.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

  /* If the struct initializer doesn't include a name, generate a null
     value as expected by the mksct instruction.  */
  if (!struct_field_name)
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
}
PKL_PHASE_END_HANDLER

/*
 * | STRUCT
 * | IDENTIFIER
 * STRUCT_REF
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_struct_ref)
{
  if (PKL_PASS_PARENT == NULL
      && PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_LVALUE))
    {
      /* This is a l-value in an assignment.  The struct and the
         identifier are pushed to the stack for the ass_stmt PR
         handler.  Nothing else to do here.  */
    }
  else
    {
      pkl_ast_node struct_ref = PKL_PASS_NODE;
      pkl_ast_node struct_ref_type = PKL_AST_TYPE (struct_ref);
      pkl_ast_node struct_ref_struct
        = PKL_AST_STRUCT_REF_STRUCT (struct_ref);
      pkl_ast_node struct_ref_identifier
        = PKL_AST_STRUCT_REF_IDENTIFIER (struct_ref);
      pkl_ast_node struct_ref_struct_type = PKL_AST_TYPE (struct_ref_struct);
      pkl_ast_node elem;
      int is_field_p = 0;
      int is_computed_p = 0;
      pkl_ast_node computed_field_name = NULL;

      /* Determine whether the referred struct element is a field or a
         declaration.  */
      for (elem = PKL_AST_TYPE_S_ELEMS (struct_ref_struct_type);
           elem;
           elem = PKL_AST_CHAIN (elem))
        {
          if (PKL_AST_CODE (elem) == PKL_AST_STRUCT_TYPE_FIELD)
            {
              pkl_ast_node field_name
                = PKL_AST_STRUCT_TYPE_FIELD_NAME (elem);

              if (field_name != NULL
                  && strcmp (PKL_AST_IDENTIFIER_POINTER (field_name),
                             PKL_AST_IDENTIFIER_POINTER (struct_ref_identifier)) == 0)
                {
                  is_field_p = 1;
                  /* Note that computed fields always have names.  */
                  is_computed_p = PKL_AST_STRUCT_TYPE_FIELD_COMPUTED_P (elem);
                  if (is_computed_p)
                    computed_field_name = field_name;
                  break;
                }
            }
        }

      if (is_computed_p)
        {
          /* This is a computed field.

             We should SREF the get_FOO method of the struct and
             call it to get the value.  */

          char *getter = pk_str_concat ("get_",
                                        PKL_AST_IDENTIFIER_POINTER (computed_field_name),
                                        NULL);
          pkl_ast_node getter_decl
            = pkl_ast_get_struct_type_method (struct_ref_struct_type, getter);

          if (getter_decl)
            {
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* STRUCT */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_string (getter));   /* STRUCT GETTER_NAME */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREFMNT); /* STRUCT GETTER_NAME CLS */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);  /* STRUCT CLS */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL); /* VAL */
            }
          else
            {
              /* No getter defined for this computer field.  */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_exception (PVM_E_ELEM, PVM_E_ELEM_NAME,
                                                PVM_E_ELEM_ESTATUS, NULL, NULL));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
            }

          free (getter);
        }
      else
        {
          /* This is either a field or a method.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SREF); /* STRUCT STR ELEM */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_QUAKE); /* STR STRUCT ELEM */

          /* If this is an indirecting struct_ref, now we have to map
             the value using the offset that is on top of the stack,
             using the mapping attributes of the containing struct.
             If the struct value is not mapped, then we raise
             E_no_map.  */
          if (PKL_AST_STRUCT_REF_INDIRECTION_P (struct_ref))
            {
              pvm_program_label mapped = pkl_asm_fresh_label (PKL_GEN_ASM);

                                                         /* STR STRUCT ELEM */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);  /* STR STRUCT [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MM);   /* STR STRUCT MAPPED_P [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZI, mapped);

              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                            pvm_make_exception (PVM_E_MAP, PVM_E_MAP_NAME,
                                                PVM_E_MAP_ESTATUS, NULL, NULL));
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);

              pkl_asm_label (PKL_GEN_ASM, mapped);
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* STR STRUCT [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETS); /* STR STRUCT STRICT [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* STR STRICT STRUCT [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MGETIOS); /* STR STRICT STRUCT IOS [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* STR STRICT IOS [ELEM] */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* STR STRICT IOS ELEM */
              /* We need a bit offset.  */
              {
                pkl_ast_node ref_orig_type = PKL_AST_STRUCT_REF_ORIG_TYPE (struct_ref);
                pkl_ast_node u64t = pkl_ast_make_integral_type (PKL_PASS_AST, 64, 0);

                assert (PKL_AST_TYPE_CODE (ref_orig_type) == PKL_TYPE_OFFSET);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM); /* STR STRICT IOS ELEM MAGNITUDE */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NTON,
                              PKL_AST_TYPE_O_BASE_TYPE (ref_orig_type),
                              u64t);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* STR STRICT IOS ELEM NMAGNITUDE */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* STR STRICT IOS NMAGNITUDE ELEM */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETU);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* STR STRICT IOS NMAGNITUDE UNIT */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MULLU);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* STR STRICT IOS (NMAGNITUDE*UNIT) */
                u64t = ASTREF (u64t); pkl_ast_node_free (u64t);
              }
              /* Ok, map.  */
              PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_MAPPER);
              PKL_PASS_SUBPASS (struct_ref_type);
              PKL_GEN_POP_CONTEXT; /* STR ELEM */
            }
        }
      /* STR [SCT iff !indirect] ELEM */

      /* To cover cases where the referenced struct is not mapped, but
         the value stored in it is a mapped value, we issue a
         REMAP.  */
      switch (PKL_AST_TYPE_CODE (struct_ref_type))
        {
        case PKL_TYPE_ARRAY:
        case PKL_TYPE_STRUCT:
          {
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREMAP); /* STR [SCT] NVAL REMAPPED_P */

            if (!PKL_AST_STRUCT_REF_INDIRECTION_P (struct_ref))
              {
                /* STR SCT NVAL REMAPPED_P */
                pvm_program_label done
                  = pkl_asm_fresh_label (PKL_GEN_ASM);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, done);
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* STR SCT NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_QUAKE); /* SCT STR NVAL */

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR); /* SCT STR (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ATR); /* SCT STR NVAL (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT); /* NVAL SCT STR (NVAL) */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* NVAL SCT STR NVAL */

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET); /* NVAL SCT */

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL); /* NVAL null*/
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* null NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* null NVAL null */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* null null NVAL */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER); /* null null NVAL null */
                pkl_asm_label (PKL_GEN_ASM, done);

                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* STR SCT NVAL */
              }
            else
              {
                /* STR NVAL REMAPPED_P */
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* STR NVAL */
              }
          }
          break;
        default:
          break;
        }

      /* STR [SCT] ELEM */
      if (!is_computed_p)
        {
          if (PKL_AST_STRUCT_REF_INDIRECTION_P (struct_ref))
            {
              /* STR ELEM */
              pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* ELEM */
            }
          else
            {
              /* STR SCT ELEM */
              if (!(PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FUNCALL)
                    && !PKL_PASS_PARENT && !is_field_p))
                pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* ELEM */
              else
                {
                  /* If the parent is a funcall and the referred field was
                     a struct method, then leave both the struct and the
                     closure.
                     i.e. this is a struct method being called, preserve
                     the struct because it is the implicit first arg.  */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* SCT ELEM STR */
                  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* SCT ELEM */
                }
            }
        }
    }
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_ALIAS
 * | NAME
 * | TYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_type_alias)
{
  pkl_ast_node alias = PKL_PASS_NODE;
  pkl_ast_node alias_type = PKL_AST_TYPE_L_TYPE (alias);
  pkl_ast_node alias_name = PKL_AST_TYPE_L_NAME (alias);

  PKL_PASS_SUBPASS (alias_type);

  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      if (PKL_AST_TYPE_CODE (alias_type) == PKL_TYPE_STRUCT)
        {
          PKL_PASS_SUBPASS (alias_name); /* TYP STR */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYSCTSETN); /* TYP */
        }
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR)
           || PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER)
           || PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_DEINTEGRATOR))
    {
      /* The type of the constructed/mapped/deintegrated struct always
         anonymous.  Set its name here.  The types of other PVM values
         do not have names at run-time.  */
      if (PKL_AST_TYPE_CODE (PKL_PASS_NODE) == PKL_TYPE_STRUCT)
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYPOF); /* VAL TYP */
          PKL_PASS_SUBPASS (alias_name); /* VAL TYP STR */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYSCTSETN); /* VAL TYP */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL */
        }
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      /* The stack now contains a Pk_Type with an empty name field.
         Set the name field to the name of the aliased type.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string ("name"));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_string (PKL_AST_IDENTIFIER_POINTER (alias_name)));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SSET);
    }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_VOID
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_void)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYV);
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_ANY
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_any)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    ; /* Do nothing here.  */
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
                              /* VAL DEPTH */
      RAS_MACRO_ANY_PRINTER;  /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
                              /* VAL DEPTH */
      RAS_MACRO_ANY_FORMATER; /* STR */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* This value is arbitrary... literally `any' value that is
         valid in a Poke program.  This excludes PVM_NULL.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_int (0, 32));
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_INTEGRAL
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_integral)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node integral_type = PKL_PASS_NODE;

  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

      /* Stack: IOS BOFF VAL */
      switch (PKL_GEN_PAYLOAD->endian)
        {
        case PKL_AST_ENDIAN_DFL:
          pkl_asm_insn (pasm, PKL_INSN_POKED, integral_type);
          break;
        case PKL_AST_ENDIAN_LSB:
          pkl_asm_insn (pasm, PKL_INSN_POKE, integral_type,
                        IOS_NENC_2, IOS_ENDIAN_LSB);
          break;
        case PKL_AST_ENDIAN_MSB:
          pkl_asm_insn (pasm, PKL_INSN_POKE, integral_type,
                        IOS_NENC_2, IOS_ENDIAN_MSB);
          break;
        default:
          PK_UNREACHABLE ();
        }

      /* Stack: EXCEPTION|null */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
      pkl_asm_label (PKL_GEN_ASM, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
    {
      pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

      /* Stack: STRICT IOS BOFF */
      switch (PKL_GEN_PAYLOAD->endian)
        {
        case PKL_AST_ENDIAN_DFL:
          pkl_asm_insn (pasm, PKL_INSN_PEEKD, integral_type);
          break;
        case PKL_AST_ENDIAN_LSB:
          pkl_asm_insn (pasm, PKL_INSN_PEEK, integral_type,
                        IOS_NENC_2, IOS_ENDIAN_LSB);
          break;
        case PKL_AST_ENDIAN_MSB:
          pkl_asm_insn (pasm, PKL_INSN_PEEK, integral_type,
                        IOS_NENC_2, IOS_ENDIAN_MSB);
          break;
        default:
          PK_UNREACHABLE ();
        }

      /* Stack: EXCEPTION|null */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
      pkl_asm_label (PKL_GEN_ASM, label);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);

      pkl_asm_insn (pasm, PKL_INSN_NIP); /* STRICT is not used.  */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* Stack: NULL */
      int size = PKL_AST_TYPE_I_SIZE (integral_type);
      pvm_val zero;

      if (PKL_AST_TYPE_I_SIGNED_P (integral_type))
        {
          if (size <= 32)
            zero = pvm_make_int (0, size);
          else
            zero = pvm_make_long (0, size);
        }
      else
        {
          if (size <= 32)
            zero = pvm_make_uint (0, size);
          else
            zero = pvm_make_ulong (0, size);
        }

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The NULL */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, zero);
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
                             /* VAL DEPTH */
      RAS_MACRO_ANY_PRINTER; /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
                              /* VAL DEPTH */
      RAS_MACRO_ANY_FORMATER; /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      RAS_MACRO_INTEGRAL_TYPIFIER (PKL_PASS_NODE); /* SCT */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      pkl_asm_insn (pasm, PKL_INSN_PUSH,
                    pvm_make_ulong (PKL_AST_TYPE_I_SIZE (integral_type),
                                    64));

      pkl_asm_insn (pasm, PKL_INSN_PUSH,
                    pvm_make_int (PKL_AST_TYPE_I_SIGNED_P (integral_type),
                                  32));

      pkl_asm_insn (pasm, PKL_INSN_MKTYI);
    }
}
PKL_PHASE_END_HANDLER

/*
 * FUNC_TYPE_ARG
 * | FUNC_TYPE_ARG_TYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_func_type_arg)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_ARRAY_BOUNDER))
    {
      pkl_ast_node func_type_arg = PKL_PASS_NODE;
      pkl_ast_node arg_type = PKL_AST_FUNC_TYPE_ARG_TYPE (func_type_arg);

      if (PKL_AST_TYPE_CODE (arg_type) == PKL_TYPE_ARRAY
          && PKL_AST_TYPE_A_BOUNDER (arg_type) == PVM_NULL)
        PKL_PASS_SUBPASS (arg_type);

      PKL_PASS_BREAK;
    }

  /* Nothing to do.  */
}
PKL_PHASE_END_HANDLER

/* TYPE_FUNCTION
 * | FUNC_TYPE_ARG
 * | ...
 * | FUNC_TYPE_RTYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_type_function)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      /* Writing a function value is a NOP.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The VAL */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The BOFF */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The IOS */

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER | PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* We construct the same function value for mappings and
         constructions.  */

      pkl_ast_node function_type = PKL_PASS_NODE;
      pkl_ast_node function_rtype = PKL_AST_TYPE_F_RTYPE (function_type);
      pvm_program program;

      /* Compile the body for the function value.  */
      PKL_GEN_PUSH_ASM (pkl_asm_new (PKL_PASS_AST,
                                     PKL_GEN_PAYLOAD->compiler,
                                     0 /* prologue */));
      {
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PROLOG);
        int i;

        /* Discard arguments.  */
        for (i = 0; i < PKL_AST_TYPE_F_NARG (function_type); ++i)
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);

        /* If the function returns a value, construct it.  */
        if (PKL_AST_TYPE_CODE (function_rtype) == PKL_TYPE_VOID)
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
        else
          {
            /* Constructor argument.  */
            pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

            PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
            PKL_PASS_SUBPASS (function_rtype);
            PKL_GEN_POP_CONTEXT;
          }

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RETURN);
      }

      program = pkl_asm_finish (PKL_GEN_ASM, 0 /* epilogue */);
      PKL_GEN_POP_ASM;

      pvm_program_make_executable (program);

      /* Discard constructor/mapper arguments.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
      if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);

      /* Push the constructed closure and install the current lexical
         environment.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_cls (program,
                                                              PVM_NULL));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DUC);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      /* If in a mapper, get rid of the unused STRICT.  */
      if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
      /* Stack: VAL DEPTH  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* DEPTH is not used.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL is not used.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_string ("#<closure>"));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PRINTS);
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
      /* Stack: VAL DEPTH  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* DEPTH is not used.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* VAL is not used.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_string ("#<closure>"));
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      RAS_MACRO_FUNCTION_TYPIFIER (PKL_PASS_NODE); /* SCT */
      PKL_PASS_BREAK;
    }
}
PKL_PHASE_END_HANDLER

/*
 * | FUNC_TYPE_ARG
 * | ...
 * | FUNC_TYPE_RTYPE
 * TYPE_FUNCTION
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_function)
{
  pkl_ast_node ftype = PKL_PASS_NODE;

  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_ulong (PKL_AST_TYPE_F_NARG (ftype), 64));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYC);
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_ARRAY_BOUNDER))
    {
      pkl_ast_node rtype = PKL_AST_TYPE_F_RTYPE (ftype);

      if (PKL_AST_TYPE_CODE (rtype) == PKL_TYPE_ARRAY
          && PKL_AST_TYPE_A_BOUNDER (rtype) == PVM_NULL)
        PKL_PASS_SUBPASS (rtype);
    }
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_ARRAY
 * | ETYPE
 * | NELEM
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_type_array)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      /* Stack: IOS OFF ARR */

      /* Note that we don't use the offset, nor the IOS, since these
         are attributes of the mapped value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The array.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The offset. */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The ios.  */

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_ARRAY_BOUNDER))
    {
      pkl_ast_node array_type = PKL_PASS_NODE;
      pkl_ast_node etype = PKL_AST_TYPE_A_ETYPE (array_type);
      pvm_val bounder_closure = PKL_AST_TYPE_A_BOUNDER (array_type);

      if (PKL_AST_TYPE_CODE (etype) == PKL_TYPE_ARRAY
          && !PKL_AST_TYPE_NAME (etype))
        PKL_PASS_SUBPASS (etype);

      if (PKL_AST_TYPE_A_BOUNDER (array_type) == PVM_NULL)
        RAS_FUNCTION_ARRAY_BOUNDER (bounder_closure, array_type);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, bounder_closure); /* CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                   /* CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);                  /* _ */
      PKL_AST_TYPE_A_BOUNDER (array_type) = bounder_closure;

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
    {
      pkl_ast_node array_type = PKL_PASS_NODE;
      pkl_ast_node array_type_bound = PKL_AST_TYPE_A_BOUND (array_type);

      pvm_val array_type_mapper = PKL_AST_TYPE_A_MAPPER (array_type);
      pvm_val array_type_writer = PKL_AST_TYPE_A_WRITER (array_type);

      /* Make a copy of the IOS.  We will need to install it in the
         resulting value later.  */
                                                 /* STRICT IOS OFF */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);  /* STRICT IOS [OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);/* STRICT IOS STRICT IOS OFF */

      /* Make sure the array type has a bounder.  Note that this
         should be done before compiling mapper, writer, constructor,
         etc functions, in order to make sure the bounder closures are
         compiled in the right environment.  */

      PKL_GEN_PAYLOAD->mapper_depth++;

      if (PKL_GEN_PAYLOAD->mapper_depth == 1 && !PKL_AST_TYPE_NAMED_P (array_type))
        {
          /* Note that this only happens at the top-level of an
             anonymous array type, and compiles a bounder for it.
             Named array types have their bounder compiled in
             pkl_gen_pr_decl.  */
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (array_type);
          PKL_GEN_POP_CONTEXT;
        }

      if (array_type_mapper == PVM_NULL)
        RAS_FUNCTION_ARRAY_MAPPER (array_type_mapper, array_type);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_type_mapper);
      if (!PKL_AST_TYPE_NAMED_P (array_type))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);       /* ... STRICT IOS OFF CLS */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);       /* ... STRICT IOS OFF [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ATR);       /* ... STRICT IOS OFF CLS [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);      /* ... STRICT CLS IOS OFF [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);       /* ... STRICT CLS IOS [CLS OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);       /* ... STRICT CLS [CLS OFF IOS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);      /* ... CLS STRICT [CLS OFF IOS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);     /* ... CLS STRICT IOS [CLS OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);     /* ... CLS STRICT IOS OFF [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);     /* ... CLS STRICT IOS OFF CLS */

      /* Build the arguments and call the mapper to get a mapped array
         value.  Whether the mapping is bounded, and exactly how, is
         determined from the array type.  */
      if (array_type_bound
          && (PKL_AST_TYPE_CODE (PKL_AST_TYPE (array_type_bound))
              == PKL_TYPE_INTEGRAL))
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        PKL_AST_TYPE_A_BOUNDER (array_type));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
        }
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
                                                  /* ... CLS STRICT IOS OFF CLS EBOUND */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* ... CLS STRICT IOS OFF EBOUND CLS */

      if (array_type_bound
          && (PKL_AST_TYPE_CODE (PKL_AST_TYPE (array_type_bound))
              == PKL_TYPE_OFFSET))
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        PKL_AST_TYPE_A_BOUNDER (array_type));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);

          /* Convert to bit-offset.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
        }
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
                                               /* ... CLS STRICT IOS OFF EBOUND CLS SBOUND */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);
                                            /* ... CLS STRICT IOS OFF EBOUND SBOUND CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);  /* STRICT IOS CLS VAL */

      /* Install the mapper into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* STRICT IOS VAL CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETM); /* STRICT IOS VAL */

      /* Install the IOS into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);    /* STRICT VAL IOS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CMSETIOS); /* STRICT VAL */

      /* Install the strictness attribute of the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* VAL STRICT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETS); /* VAL */

      if (array_type_writer == PVM_NULL)
        {
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
          RAS_FUNCTION_ARRAY_WRITER (array_type_writer, array_type);
          PKL_GEN_POP_CONTEXT;
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_type_writer);
      if (!PKL_AST_TYPE_NAMED_P (array_type))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC); /* VAL CLS */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETW);                /* VAL */
      /* Yay!, we are done ;) */

      if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
        PKL_GEN_PAYLOAD->mapper_depth--;

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
      /* Stack: ARR DEPTH */
      RAS_MACRO_ANY_PRINTER;
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
      /* Stack: ARR DEPTH */
      RAS_MACRO_ANY_FORMATER;
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_INTEGRATOR))
    {
      /* Stack: ARR */

      pkl_ast_node array_type = PKL_PASS_NODE;
      pvm_val integrator_closure = PKL_AST_TYPE_A_INTEGRATOR (array_type);

      /* If the array type doesn't have a integrator, compile one.  */
      if (integrator_closure == PVM_NULL)
        RAS_FUNCTION_ARRAY_INTEGRATOR (integrator_closure, array_type);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, integrator_closure); /* CLS */
      if (!PKL_AST_TYPE_NAMED_P (array_type))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                    /* CLS */

      /* Invoke the integrator.  IVAL is either ULONG or an offset with
         ULONG as the base type.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL); /* IVAL(ULONG) WIDTH(UINT) */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* Stack: initval */
      pkl_ast_node array_type = PKL_PASS_NODE;
      pkl_ast_node array_type_bound = PKL_AST_TYPE_A_BOUND (array_type);
      pvm_val array_type_constructor = PKL_AST_TYPE_A_CONSTRUCTOR (array_type);

      PKL_GEN_PAYLOAD->constructor_depth++;

      /* Make sure the array type has a bounder.  */
      if (PKL_GEN_PAYLOAD->constructor_depth == 1 && !PKL_AST_TYPE_NAMED_P (array_type))
        {
          /* Note that this only happens at the top-level of an
             anonymous array type, and compiles a bounder for it.
             Named array types have their bounder compiled in
             pkl_gen_pr_decl.  */
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (array_type);
          PKL_GEN_POP_CONTEXT;
        }

      /* Push the EBOUND argument for the constructor.  */
      if (array_type_bound
          && (PKL_AST_TYPE_CODE (PKL_AST_TYPE (array_type_bound))
              == PKL_TYPE_INTEGRAL))
        {
          pkl_asm_note (PKL_GEN_ASM, "bounder");
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        PKL_AST_TYPE_A_BOUNDER (array_type));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
        }
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

      /* Push the SBOUND argument for the constructor, converted to a
         bit-offset.  */
      if (array_type_bound
          && (PKL_AST_TYPE_CODE (PKL_AST_TYPE (array_type_bound))
              == PKL_TYPE_OFFSET))
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                        PKL_AST_TYPE_A_BOUNDER (array_type));
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);

          /* Convert to bit-offset.  */
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
        }
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

      /* Make sure the array type has a constructor, and call it.  */
      if (array_type_constructor == PVM_NULL)
        RAS_FUNCTION_ARRAY_CONSTRUCTOR (array_type_constructor, array_type);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_type_constructor);
      if (!PKL_AST_TYPE_NAMED_P (array_type))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);          /* ARR */
      PKL_GEN_PAYLOAD->constructor_depth--;

      /* Install a writer in the array.  */
      {
        pvm_val array_type_writer = PVM_NULL;

        PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
        RAS_FUNCTION_ARRAY_WRITER (array_type_writer, array_type);
        PKL_GEN_POP_CONTEXT;
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_type_writer); /* CLS */
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                     /* CLS */
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETW);                   /* ARR */
      }

      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      RAS_MACRO_ARRAY_TYPIFIER (PKL_PASS_NODE); /* SCT */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      /* Generating a PVM array type.  */

      pkl_ast_node array_type = PKL_PASS_NODE;
      pkl_ast_node etype = PKL_AST_TYPE_A_ETYPE (array_type);
      pvm_val array_bounder = PKL_AST_TYPE_A_BOUNDER (array_type);

      PKL_PASS_SUBPASS (etype);

      /* Make sure the array type has a bounder.  */
      if (array_bounder == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (array_type));
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_ARRAY_BOUNDER);
          PKL_PASS_SUBPASS (array_type);
          PKL_GEN_POP_CONTEXT;

          array_bounder = PKL_AST_TYPE_A_BOUNDER (array_type);
        }

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, array_bounder);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYA);
      PKL_PASS_BREAK;
    }

  /* In normal context, just subpass on the type of the elements,
     ignoring the number of elements.  */
  PKL_PASS_SUBPASS (PKL_AST_TYPE_A_ETYPE (PKL_PASS_NODE));
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_STRING
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_string)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      /* Stack: IOS BOFF STR */
      pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env,
                    "_pkl_poke_string"); /* NULL */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
    {
      /* Stack: STRICT IOS BOFF */
      pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env,
                    "_pkl_peek_string");        /* STRICT STR */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP); /* STR */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* Stack: NULL */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_string (""));
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
      /* Stack: VAL DEPTH */
      RAS_MACRO_ANY_PRINTER; /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
      /* Stack: VAL DEPTH */
      RAS_MACRO_ANY_FORMATER; /* _ */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      RAS_MACRO_STRING_TYPIFIER (PKL_PASS_NODE); /* SCT */
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYS);
}
PKL_PHASE_END_HANDLER

/*
 * TYPE_STRUCT
 * | (STRUCT_TYPE_FIELD|DECL)
 * | ...
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_type_struct)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER))
    {
      /* Stack: IOS OFF SCT */

      /* Note that we don't use the offset, nor the IOS, because these
         are attributes of the mapped value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_WRITE);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The struct.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The offset. */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The IOS.  */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER))
    {
      /* Stack: STRICT IOS OFF */
      pkl_ast_node type_struct = PKL_PASS_NODE;

      pvm_val type_struct_mapper = PKL_AST_TYPE_S_MAPPER (type_struct);
      pvm_val type_struct_writer = PKL_AST_TYPE_S_WRITER (type_struct);
      pvm_val type_struct_constructor = PKL_AST_TYPE_S_CONSTRUCTOR (type_struct);

      /* Make a copy of the IOS and STRICT.  We will need to install
         them in the resulting value later.  */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);  /* STRICT IOS [OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* STRICT IOS STRICT IOS OFF */

      /* Compile a mapper function and complete it using the current
         environment.  */
      if (type_struct_mapper == PVM_NULL)
        RAS_FUNCTION_STRUCT_MAPPER (type_struct_mapper, type_struct,
                                    (pkl_ast_node) NULL /* type_name */);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, type_struct_mapper);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC); /* ... STRICT IOS OFF CLS */

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* ... STRICT IOS OFF [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ATR);   /* ... STRICT IOS OFF CLS [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT);  /* ... STRICT CLS IOS OFF [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* ... STRICT CLS IOS [CLS OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TOR);   /* ... STRICT CLS [CLS OFF IOS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* ... CLS STRICT [CLS OFF IOS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* ... CLS STRICT IOS [CLS OFF] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR); /* ... CLS STRICT IOS OFF [CLS] */

      /* Build the arguments and call the mapper to get a struct
         value.  For structs, both EBOUND and SBOUND are always
         null.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
                          /* ... CLS STRICT IOS OFF EBOUND [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
                          /* ... CLS STRICT IOS OFF EBOUND SBOUND [CLS] */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_FROMR);
                          /* ... CLS STRICT IOS OFF EBOUND SBOUND CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
                          /* STRICT IOS CLS VAL */

      /* Install the mapper into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* STRICT IOS VAL CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETM); /* STRICT IOS VAL */

      /* Install the ios into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);    /* STRICT VAL IOS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CMSETIOS); /* STRICT VAL */

      /* Install the strictness property into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);  /* VAL STRICT */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETS); /* VAL */

      /* Compile a constructor function and complete it using the
         current environment.  */
      if (type_struct_constructor == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (type_struct));
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_CONSTRUCTOR);
          RAS_FUNCTION_STRUCT_CONSTRUCTOR (type_struct_constructor, type_struct,
                                           (pkl_ast_node) NULL /* type_name */);
          PKL_GEN_POP_CONTEXT;
          /* We normally do not install closures in anonymous types,
             but this one is needed by ssetc.  */
          PKL_AST_TYPE_S_CONSTRUCTOR (type_struct) = type_struct_constructor;
        }

      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        {
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, type_struct_constructor);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
        }

      /* Compile a writer function and complete it using the current
         environment.  */
      if (type_struct_writer == PVM_NULL)
        {
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
          if (PKL_AST_TYPE_S_UNION_P (type_struct))
            RAS_FUNCTION_UNION_WRITER (type_struct_writer, type_struct);
          else
            RAS_FUNCTION_STRUCT_WRITER (type_struct_writer, type_struct);
          PKL_GEN_POP_CONTEXT;
        }

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, type_struct_writer); /* VAL CLS */
        if (!PKL_AST_TYPE_NAMED_P (type_struct))
          pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);                  /* VAL CLS */

      /* Install the writer into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETW);                /* VAL */

      /* And we are done.  */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR))
    {
      /* Stack: SCT */
      pkl_ast_node type_struct = PKL_PASS_NODE;
      pvm_val type_struct_constructor = PKL_AST_TYPE_S_CONSTRUCTOR (type_struct);
      pvm_val type_struct_writer = PKL_AST_TYPE_S_WRITER (type_struct);

      /* If the given structure is null, then create an empty AST
         struct of the right type.  */
      {
        pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);
        pkl_ast_node s = pkl_ast_make_struct (PKL_PASS_AST,
                                              0, NULL);

        PKL_AST_TYPE (s) = ASTREF (type_struct);

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNN, label);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* The null */
        PKL_GEN_PUSH_CONTEXT;
        PKL_PASS_SUBPASS (s);
        PKL_GEN_POP_CONTEXT;

        pkl_asm_label (PKL_GEN_ASM, label);
        s = ASTREF(s); pkl_ast_node_free (s);
      }

      if (type_struct_constructor == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (type_struct));
          RAS_FUNCTION_STRUCT_CONSTRUCTOR (type_struct_constructor, type_struct,
                                           (pkl_ast_node) NULL /* type_name */);
          /* We normally do not install closures in anonymous types,
             but this one is needed by ssetc.  */
          PKL_AST_TYPE_S_CONSTRUCTOR (type_struct) = type_struct_constructor;
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, type_struct_constructor);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC); /* SCT CLS */

      /* Call the constructor to get a new struct.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);    /* NSCT */

      /* Make sure the type of the constructed struct has a constructor at
         runtime.  This is required by SSETC.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYPOF); /* NSCT TYP */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    PKL_AST_TYPE_S_CONSTRUCTOR (type_struct)); /* NSCT TYP CLS */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYSCTSETC); /* NSCT TYP */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* NSCT */

      /* Compile a writer function and complete it using the current
         environment.  */
      if (type_struct_writer == PVM_NULL)
        {
          assert (!PKL_AST_TYPE_NAMED_P (type_struct));
          PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
          {
            if (PKL_AST_TYPE_S_UNION_P (type_struct))
              RAS_FUNCTION_UNION_WRITER (type_struct_writer, type_struct);
            else
              RAS_FUNCTION_STRUCT_WRITER (type_struct_writer, type_struct);
          }
          PKL_GEN_POP_CONTEXT;
        }

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, type_struct_writer);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC); /* NCSCT CLS */

      /* Install the writer into the value.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MSETW); /* NCSCT */

      /* And we are done.  */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_COMPARATOR))
    {
      /* Stack: SCT1 SCT2 */

      pkl_ast_node type_struct = PKL_PASS_NODE;
      pvm_val comparator_closure
        = PKL_AST_TYPE_S_COMPARATOR (type_struct);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OVER);

      if (comparator_closure == PVM_NULL)
        {
          if (PKL_AST_TYPE_S_UNION_P (type_struct))
            RAS_FUNCTION_UNION_COMPARATOR (comparator_closure, type_struct);
          else
            RAS_FUNCTION_STRUCT_COMPARATOR (comparator_closure, type_struct);
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, comparator_closure);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      /* Call the comparator.  */
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL); /* SCT1 SCT2 INT */
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_PRINTER))
    {
      /* Stack: SCT DEPTH */
      RAS_MACRO_ANY_PRINTER;
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_FORMATER))
    {
      /* Stack: SCT DEPTH */
      RAS_MACRO_ANY_FORMATER;
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_INTEGRATOR))
    {
      pkl_ast_node type_struct = PKL_PASS_NODE;
      pvm_val integrator_closure = PKL_AST_TYPE_S_INTEGRATOR (type_struct);

      if (integrator_closure == PVM_NULL)
        {
          if (PKL_AST_TYPE_S_UNION_P (type_struct))
            RAS_FUNCTION_UNION_INTEGRATOR (integrator_closure, type_struct);
          else
            RAS_FUNCTION_STRUCT_INTEGRATOR (integrator_closure, type_struct);
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, integrator_closure);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_DEINTEGRATOR))
    {
      pkl_ast_node type_struct = PKL_PASS_NODE;
      pvm_val deintegrator_closure = PKL_AST_TYPE_S_DEINTEGRATOR (type_struct);

      if (deintegrator_closure == PVM_NULL)
        {
          if (PKL_AST_TYPE_S_UNION_P (type_struct))
            RAS_FUNCTION_UNION_DEINTEGRATOR (deintegrator_closure, type_struct,
                                             (pkl_ast_node) NULL /* type_struct_name */);
          else
            RAS_FUNCTION_STRUCT_DEINTEGRATOR (deintegrator_closure, type_struct);
        }
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, deintegrator_closure);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPIFIER))
    {
      pkl_ast_node type_struct = PKL_PASS_NODE;
      pvm_val typifier_closure = PKL_AST_TYPE_S_TYPIFIER (type_struct);

      if (typifier_closure == PVM_NULL)
        RAS_FUNCTION_STRUCT_TYPIFIER (typifier_closure, type_struct,
                                      (pkl_ast_node) NULL /* type_name */);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, typifier_closure);
      if (!PKL_AST_TYPE_NAMED_P (type_struct))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PEC);

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_CALL);
      PKL_PASS_BREAK;
    }
  else if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      /* Do nothing.  See PS hook.  */
    }
  else
    {
      /* In normal context, process the fields of the struct, but not
         the declarations contained within it.  */

      pkl_ast_node elem;

      for (elem = PKL_AST_TYPE_S_ELEMS (PKL_PASS_NODE);
           elem;
           elem = PKL_AST_CHAIN (elem))
        {
          if (PKL_AST_CODE (elem) == PKL_AST_STRUCT_TYPE_FIELD)
            PKL_PASS_SUBPASS (elem);
        }

      PKL_PASS_BREAK;
    }
}
PKL_PHASE_END_HANDLER

/*
 * | STRUCT_TYPE_FIELD
 * | ...
 * TYPE_STRUCT
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_type_struct)
{
  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      /* We are generating a PVM struct type.  */

      pkl_ast_node struct_type = PKL_PASS_NODE;

      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                    pvm_make_ulong (PKL_AST_TYPE_S_NFIELD (struct_type), 64));
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MKTYSCT);

      /* Install the constructor closure of the struct type.  This may
         require to compile one.  */
      {
        pvm_val constructor = PKL_AST_TYPE_S_CONSTRUCTOR (PKL_PASS_NODE);

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, constructor);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_TYSCTSETC);
      }
    }
}
PKL_PHASE_END_HANDLER

/*
 * STRUCT_TYPE_FIELD
 * | [STRUCT_TYPE_FIELD_NAME]
 * | STRUCT_TYPE_FIELD_TYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_struct_type_field)
{
  assert (!PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_MAPPER));
  assert (!PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_WRITER));
  assert (!PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_CONSTRUCTOR));

  if (PKL_GEN_IN_CTX_P (PKL_GEN_CTX_IN_TYPE))
    {
      /* We are generating a PVM struct type.  */

      /* Do not include computed fields.  */
      if (PKL_AST_STRUCT_TYPE_FIELD_COMPUTED_P (PKL_PASS_NODE))
        PKL_PASS_BREAK;

      /* If the struct type element doesn't include a name, generate a
         null value as expected by the mktysct instruction.  */
      if (!PKL_AST_STRUCT_TYPE_FIELD_NAME (PKL_PASS_NODE))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
      else
        PKL_PASS_SUBPASS (PKL_AST_STRUCT_TYPE_FIELD_NAME (PKL_PASS_NODE));
      PKL_PASS_SUBPASS (PKL_AST_STRUCT_TYPE_FIELD_TYPE (PKL_PASS_NODE));

      PKL_PASS_BREAK;
    }

  /* In normal context, subpass on the field type and ignore the
     name.  */
  PKL_PASS_SUBPASS (PKL_AST_STRUCT_TYPE_FIELD_TYPE (PKL_PASS_NODE));
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * Expression handlers.
 *
 * | OPERAND1
 * | [OPERAND2]
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_add)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node node = PKL_PASS_NODE;
  pkl_ast_node type = PKL_AST_TYPE (node);
  pvm_val array_type_writer = PVM_NULL;

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_INTEGRAL:
      pkl_asm_insn (pasm, PKL_INSN_ADD, type);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_STRING:
      pkl_asm_insn (pasm, PKL_INSN_SCONC);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_ARRAY:
      pkl_asm_insn (pasm, PKL_INSN_ACONC);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);

      /* Install a writer in the array.  */
      PKL_GEN_PUSH_SET_CONTEXT (PKL_GEN_CTX_IN_WRITER);
      RAS_FUNCTION_ARRAY_WRITER (array_type_writer, type);
      PKL_GEN_POP_CONTEXT;
      pkl_asm_insn (pasm, PKL_INSN_PUSH, array_type_writer);
      pkl_asm_insn (pasm, PKL_INSN_PEC);
      pkl_asm_insn (pasm, PKL_INSN_MSETW);
      break;
    case PKL_TYPE_OFFSET:
      {
        pkl_ast_node base_type = PKL_AST_TYPE_O_BASE_TYPE (type);
        pkl_ast_node unit = PKL_AST_TYPE_O_UNIT (type);

        pkl_asm_insn (pasm, PKL_INSN_ADDO, base_type, unit);
        pkl_asm_insn (pasm, PKL_INSN_NIP2);
      }
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_sub)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node node = PKL_PASS_NODE;
  pkl_ast_node type = PKL_AST_TYPE (node);

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_INTEGRAL:
      pkl_asm_insn (pasm, PKL_INSN_SUB, type);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_OFFSET:
      {
        pkl_ast_node base_type = PKL_AST_TYPE_O_BASE_TYPE (type);
        pkl_ast_node unit = PKL_AST_TYPE_O_UNIT (type);

        pkl_asm_insn (pasm, PKL_INSN_SUBO, base_type, unit);
        pkl_asm_insn (pasm, PKL_INSN_NIP2);
      }
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_mul)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node node = PKL_PASS_NODE;
  pkl_ast_node type = PKL_AST_TYPE (node);

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_INTEGRAL:
      pkl_asm_insn (pasm, PKL_INSN_MUL, type);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_OFFSET:
      {
        pkl_ast_node op1 = PKL_AST_EXP_OPERAND (node, 0);
        pkl_ast_node op2 = PKL_AST_EXP_OPERAND (node, 1);
        pkl_ast_node op1_type = PKL_AST_TYPE (op1);
        pkl_ast_node op2_type = PKL_AST_TYPE (op2);
        pkl_ast_node base_type;

        if (PKL_AST_TYPE_CODE (op1_type) == PKL_TYPE_OFFSET)
          base_type = PKL_AST_TYPE_O_BASE_TYPE (op1_type);
        else
          {
            base_type = PKL_AST_TYPE_O_BASE_TYPE (op2_type);
            pkl_asm_insn (pasm, PKL_INSN_SWAP);
          }

        pkl_asm_insn (pasm, PKL_INSN_MULO, base_type);
        pkl_asm_insn (pasm, PKL_INSN_NIP2);
      }
      break;
    case PKL_TYPE_STRING:
      {
        pkl_ast_node op2 = PKL_AST_EXP_OPERAND (node, 1);
        pkl_ast_node op2_type = PKL_AST_TYPE (op2);

        if (PKL_AST_TYPE_CODE (op2_type) == PKL_TYPE_STRING)
          pkl_asm_insn (pasm, PKL_INSN_SWAP);

        pkl_asm_insn (pasm, PKL_INSN_MULS);
        pkl_asm_insn (pasm, PKL_INSN_NIP2);
      }
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

/*
 * | OP1
 * | OP2
 * DIV
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_div)
{
  pkl_ast_node node = PKL_PASS_NODE;
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node type = PKL_AST_TYPE (node);
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (node, 0);
  pkl_ast_node op1_type = PKL_AST_TYPE (op1);
  int div_insn, offset_div_insn;

  if (PKL_AST_EXP_CODE (node) == PKL_AST_OP_DIV)
    {
      div_insn = PKL_INSN_DIV;
      offset_div_insn = PKL_INSN_DIVO;
    }
  else
    {
      div_insn = PKL_INSN_CDIV;
      offset_div_insn = PKL_INSN_CDIVO;
    }

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_INTEGRAL:
      {
        if (PKL_AST_TYPE_CODE (op1_type) == PKL_TYPE_OFFSET)
          {
            /* This is O / O -> I */
            pkl_asm_insn (pasm, offset_div_insn,
                          PKL_AST_TYPE_O_BASE_TYPE (op1_type));
            pkl_asm_insn (pasm, PKL_INSN_NIP2);
          }
        else
          {
            /* This is I / I -> I */
            pkl_asm_insn (pasm, div_insn, type);
            pkl_asm_insn (pasm, PKL_INSN_NIP2);
          }
        break;
      }
    case PKL_TYPE_OFFSET:
      {
        /* This is O / I -> O */
        pkl_ast_node op2 = PKL_AST_EXP_OPERAND (node, 1);
        pkl_ast_node op2_type = PKL_AST_TYPE (op2);

        pkl_asm_insn (pasm, PKL_INSN_SWAP); /* OP2 OP1 */
        pkl_asm_insn (pasm, PKL_INSN_OGETM); /* OP2 OP1 OMAG1 */
        pkl_asm_insn (pasm, PKL_INSN_SWAP);
        pkl_asm_insn (pasm, PKL_INSN_TYPOF);
        pkl_asm_insn (pasm, PKL_INSN_NIP); /* OP2 OMAG1 TYP */
        pkl_asm_insn (pasm, PKL_INSN_NROT); /* TYP OP2 OMAG1 */
        pkl_asm_insn (pasm, PKL_INSN_SWAP); /* TYP OMAG1 OP2 */
        pkl_asm_insn (pasm, div_insn, op2_type);
        pkl_asm_insn (pasm, PKL_INSN_NIP2); /* TYP (OMAG1/OP2) */
        pkl_asm_insn (pasm, PKL_INSN_SWAP);
        pkl_asm_insn (pasm, PKL_INSN_MKO);
        break;
      }
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_mod)
{
  pkl_ast_node node = PKL_PASS_NODE;

  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node type = PKL_AST_TYPE (node);

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_INTEGRAL:
      pkl_asm_insn (pasm, PKL_INSN_MOD, type);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_OFFSET:
      {
        pkl_ast_node base_type = PKL_AST_TYPE_O_BASE_TYPE (type);
        pkl_ast_node unit = PKL_AST_TYPE_O_UNIT (type);

        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_MODO, base_type, unit);
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
        break;
      }
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_binexp)
{
  pkl_asm pasm = PKL_GEN_ASM;

  pkl_ast_node node = PKL_PASS_NODE;
  pkl_ast_node type = PKL_AST_TYPE (node);

  enum pkl_asm_insn insn;

  if (PKL_AST_EXP_CODE (node) == PKL_AST_OP_POS)
    /* POS in both integers and offsets is basically a nop.  */
    PKL_PASS_DONE;

  switch (PKL_AST_EXP_CODE (node))
    {
    case PKL_AST_OP_BAND: insn = PKL_INSN_BAND; break;
    case PKL_AST_OP_BNOT: insn = PKL_INSN_BNOT; break;
    case PKL_AST_OP_NEG: insn = PKL_INSN_NEG; break;
    case PKL_AST_OP_NEGF: insn = PKL_INSN_NEGF; break;
    case PKL_AST_OP_IOR: insn = PKL_INSN_BOR; break;
    case PKL_AST_OP_XOR: insn = PKL_INSN_BXOR; break;
    case PKL_AST_OP_SL: insn = PKL_INSN_SL; break;
    case PKL_AST_OP_SR: insn = PKL_INSN_SR; break;
    case PKL_AST_OP_POW: insn = PKL_INSN_POW; break;
    default:
      PK_UNREACHABLE ();
      break;
    }

  switch (PKL_AST_TYPE_CODE (type))
    {
    case PKL_TYPE_OFFSET:
      /* Fallthrough.  */
    case PKL_TYPE_INTEGRAL:
      pkl_asm_insn (pasm, insn, type);
      pkl_asm_insn (pasm, PKL_INSN_NIP);
      if (insn != PKL_INSN_NEG
          && insn != PKL_INSN_NEGF
          && insn != PKL_INSN_BNOT)
        pkl_asm_insn (pasm, PKL_INSN_NIP);
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_op_and)
{
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 0);
  pkl_ast_node op2 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 1);
  /* `op1' and `op2' are expected to be of the same type.  */
  pkl_ast_node op_type = PKL_AST_TYPE (op1);

  pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

  PKL_PASS_SUBPASS (op1);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZ, op_type, label);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  PKL_PASS_SUBPASS (op2);
  pkl_asm_label (PKL_GEN_ASM, label);

  assert (PKL_AST_TYPE_CODE (op_type) == PKL_TYPE_INTEGRAL);

  /* Normalize the result to 0 or 1 by computing `TOP != 0'.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_integral (0, PKL_AST_TYPE_I_SIZE (op_type),
                                   PKL_AST_TYPE_I_SIGNED_P (op_type)));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NE, op_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_op_or)
{
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 0);
  pkl_ast_node op2 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 1);
  /* `op1' and `op2' are expected to be of the same type.  */
  pkl_ast_node op_type = PKL_AST_TYPE (op1);

  pvm_program_label label = pkl_asm_fresh_label (PKL_GEN_ASM);

  PKL_PASS_SUBPASS (op1);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZ, op_type, label);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  PKL_PASS_SUBPASS (op2);
  pkl_asm_label (PKL_GEN_ASM, label);

  /* Normalize the result to 0 or 1 by computing `TOP != 0'.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_integral (0, PKL_AST_TYPE_I_SIZE (op_type),
                                   PKL_AST_TYPE_I_SIGNED_P (op_type)));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NE, op_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_not)
{
  pkl_ast_node op = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 0);
  pkl_ast_node op_type = PKL_AST_TYPE (op);

  /* Normalize the result to 0 or 1 by computing `TOP == 0'.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_integral (0, PKL_AST_TYPE_I_SIZE (op_type),
                                   PKL_AST_TYPE_I_SIGNED_P (op_type)));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_EQ, op_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_op_impl)
{
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 0);
  pkl_ast_node op2 = PKL_AST_EXP_OPERAND (PKL_PASS_NODE, 1);
  /* `op1' and `op2' are expected to be of the same type.  */
  pkl_ast_node op_type = PKL_AST_TYPE (op1);

  pvm_program_label label1 = pkl_asm_fresh_label (PKL_GEN_ASM);
  pvm_program_label label2 = pkl_asm_fresh_label (PKL_GEN_ASM);

  PKL_PASS_SUBPASS (op1);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BNZ, op_type, label1);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_int (1, 32));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, label2);

  pkl_asm_label (PKL_GEN_ASM, label1);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  PKL_PASS_SUBPASS (op2);
  /* Normalize the result to 0 or 1 by computing `TOP != 0'.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                pvm_make_integral (0, PKL_AST_TYPE_I_SIZE (op_type),
                                   PKL_AST_TYPE_I_SIGNED_P (op_type)));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NE, op_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);

  pkl_asm_label (PKL_GEN_ASM, label2);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * | ARR
 * | VAL
 * APUSH
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_apush)
{
  /* Stack: ARR VAL */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP); /* VAL ARR */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SEL); /* VAL ARR SEL */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_ROT); /* ARR SEL VAL */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AINS); /* ARR */
}
PKL_PHASE_END_HANDLER

/*
 * | ARR
 * APOP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_apop)
{
  /* Stack: ARR */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SEL); /* ARR SEL */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_ulong (1, 64));
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SUBLU);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2); /* ARR (SEL-1) */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREF); /* ARR (SEL-1) ELEM */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NROT); /* ELEM ARR (SEL-1) */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AREM); /* ELEM ARR */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP); /* ELEM */
}
PKL_PHASE_END_HANDLER

/*
 * EXCOND
 * | EXP|STMT
 * | EXCEPTION_ID
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_op_excond)
{
  pkl_asm pasm = PKL_GEN_ASM;

  pkl_ast_node exp = PKL_PASS_NODE;
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node op2 = PKL_AST_EXP_OPERAND (exp, 1);

  pvm_program_label exception_handler = pkl_asm_fresh_label (PKL_GEN_ASM);
  pvm_program_label done = pkl_asm_fresh_label (PKL_GEN_ASM);

  /* Install a handler for the exception specified in the second
     operand.  */
  PKL_PASS_SUBPASS (op2);
  pkl_asm_insn (pasm, PKL_INSN_PUSHE, exception_handler);

  /* Execute the expression or statement in `op1'.  If it is an
     expression, discard the result value.  */
  PKL_PASS_SUBPASS (op1);
  if (PKL_AST_IS_EXP (op1))
    pkl_asm_insn (pasm, PKL_INSN_DROP);

  pkl_asm_insn (pasm, PKL_INSN_POPE);

  /* Push the result of the operation, which is `false'.  */
  pkl_asm_insn (pasm, PKL_INSN_PUSH, pvm_make_int (0, 32));

  pkl_asm_insn (pasm, PKL_INSN_BA, done);

  /* The exception handler just drops the raised exception and
     pushes `true' to reflect the exception was raised. */
  pkl_asm_label (pasm, exception_handler);
  pkl_asm_insn (pasm, PKL_INSN_DROP); /* The exception.  */
  pkl_asm_insn (pasm, PKL_INSN_PUSH, pvm_make_int (1, 32));

  pkl_asm_label (pasm, done);
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * TYPEOF
 * | EXPR|TYPE
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_op_typeof)
{
  pkl_ast_node exp = PKL_PASS_NODE;
  pkl_ast_node op = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node op_type = (PKL_AST_CODE (op) == PKL_AST_TYPE
                          ? op : PKL_AST_TYPE (op));

  RAS_MACRO_TYPEOF (op_type);
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_rela)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node exp = PKL_PASS_NODE;
  int exp_code = PKL_AST_EXP_CODE (exp);
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node op1_type = PKL_AST_TYPE (op1);

  enum pkl_asm_insn rela_insn;

  switch (exp_code)
    {
    case PKL_AST_OP_EQ: rela_insn = PKL_INSN_EQ; break;
    case PKL_AST_OP_NE: rela_insn = PKL_INSN_NE; break;
    case PKL_AST_OP_LT: rela_insn = PKL_INSN_LT; break;
    case PKL_AST_OP_GT: rela_insn = PKL_INSN_GT; break;
    case PKL_AST_OP_LE: rela_insn = PKL_INSN_LE; break;
    case PKL_AST_OP_GE: rela_insn = PKL_INSN_GE; break;
    default:
      PK_UNREACHABLE ();
      break;
    }

  switch (PKL_AST_TYPE_CODE (op1_type))
    {
    case PKL_TYPE_ARRAY:
      /* Fallthrough.  */
    case PKL_TYPE_STRUCT:
      /* Fallthrough.  */
    case PKL_TYPE_FUNCTION:
      assert (exp_code == PKL_AST_OP_EQ
              || exp_code == PKL_AST_OP_NE);
      /* Fallthrough.  */
    case PKL_TYPE_INTEGRAL:
    case PKL_TYPE_OFFSET:
    case PKL_TYPE_STRING:
      pkl_asm_insn (pasm, rela_insn, op1_type);
      pkl_asm_insn (pasm, PKL_INSN_NIP2);
      break;
    case PKL_TYPE_ANY:
      assert (exp_code == PKL_AST_OP_EQ
              || exp_code == PKL_AST_OP_NE);

      pkl_asm_call (PKL_GEN_ASM, PKL_GEN_PAYLOAD->env, "_pkl_eq_any");
      if (exp_code == PKL_AST_OP_NE)
        {
          pkl_asm_insn (pasm, PKL_INSN_NOT);
          pkl_asm_insn (pasm, PKL_INSN_NIP);
        }
      break;
    default:
      PK_UNREACHABLE ();
      break;
    }
}
PKL_PHASE_END_HANDLER

/*
 * | OPERAND1
 * | [OPERAND2]
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_attr)
{
  pkl_ast_node exp = PKL_PASS_NODE;
  pkl_ast_node operand = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node operand_type = PKL_AST_TYPE (operand);
  enum pkl_ast_attr attr = PKL_AST_EXP_ATTR (exp);

  switch (attr)
    {
    case PKL_AST_ATTR_SIZE:
      RAS_MACRO_ATTR_SIZE (operand_type);
      break;
    case PKL_AST_ATTR_MAGNITUDE:
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETM);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
      break;
    case PKL_AST_ATTR_UNIT:
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_OGETU);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
      break;
    case PKL_AST_ATTR_SIGNED:
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
      if (PKL_AST_TYPE_I_SIGNED_P (operand_type))
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_int (1, 32));
      else
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, pvm_make_int (0, 32));
      break;
    case PKL_AST_ATTR_LENGTH:
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SEL);
      pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);
      break;
    case PKL_AST_ATTR_OFFSET:
      RAS_MACRO_ATTR_OFFSET (operand_type);
      break;
    case PKL_AST_ATTR_IOS:
      RAS_MACRO_ATTR_IOS (operand_type);
      break;
    case PKL_AST_ATTR_MAPPED:
      RAS_MACRO_ATTR_MAPPED (operand_type);
      break;
    case PKL_AST_ATTR_STRICT:
      RAS_MACRO_ATTR_STRICT (operand_type);
      break;
    case PKL_AST_ATTR_REF:
      assert (PKL_AST_TYPE_CODE (operand_type) == PKL_TYPE_OFFSET
              && PKL_AST_TYPE_O_REF_TYPE (operand_type) != NULL);
      if (PKL_AST_EXP_NUMOPS (exp) == 1)
        pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);
      RAS_MACRO_ATTR_REF (exp);
      break;
    case PKL_AST_ATTR_ELEM:
      RAS_MACRO_ATTR_ELEM;
      break;
    case PKL_AST_ATTR_EOFFSET:
      RAS_MACRO_ATTR_EOFFSET;
      break;
    case PKL_AST_ATTR_ESIZE:
      RAS_MACRO_ATTR_ESIZE;
      break;
    case PKL_AST_ATTR_ENAME:
      RAS_MACRO_ATTR_ENAME;
      break;
    default:
      PKL_ICE (PKL_AST_LOC (exp),
               "unhandled attribute expression code #%d in code generator",
               attr);
      PKL_PASS_ERROR;
      break;
    }
}
PKL_PHASE_END_HANDLER

/* | OPERAND1
 * | OPERAND2
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_bconc)
{
  pkl_ast_node exp = PKL_PASS_NODE;
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node op2 = PKL_AST_EXP_OPERAND (exp, 1);

  pkl_ast_node op1_type = PKL_AST_TYPE (op1);
  pkl_ast_node op2_type = PKL_AST_TYPE (op2);
  pkl_ast_node exp_type = PKL_AST_TYPE (exp);

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BCONC,
                op1_type, op2_type, exp_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

/*
 * | OPERAND1
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_unmap)
{
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_UNMAP);
}
PKL_PHASE_END_HANDLER

/*
 * | OPERAND1
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_remap)
{
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_REMAP); /* NVAL REMAPPED_P */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);  /* NVAL */
}
PKL_PHASE_END_HANDLER

/*
 * | OPERAND1
 * | OPERAND2
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_in)
{
  pkl_ast_node exp = PKL_PASS_NODE;
  //  pkl_ast_node elem = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node container = PKL_AST_EXP_OPERAND (exp, 1);
  pkl_ast_node container_type = PKL_AST_TYPE (container);
  //  pkl_ast_node elem_type = PKL_AST_TYPE (elem);

  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_AIS, container_type);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

/* The handler below generates and ICE if a given node isn't handled
   by the code generator.  */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_noimpl)
{
  pkl_ast_node node = PKL_PASS_NODE;

  if (PKL_AST_CODE (node) == PKL_AST_EXP)
    {
      PKL_ICE (PKL_AST_LOC (node),
               "unhandled node #%" PRIu64 " with code %d opcode %d in code generator",
               PKL_AST_UID (node), PKL_AST_CODE (node), PKL_AST_EXP_CODE (node));
    }
  else if (PKL_AST_CODE (node) == PKL_AST_TYPE)
    {
      PKL_ICE (PKL_AST_LOC (node),
               "unhandled node #%" PRIu64 " with code %d typecode %d in code generator",
               PKL_AST_UID (node), PKL_AST_CODE (node), PKL_AST_TYPE_CODE (node));
    }
  else
    PKL_ICE (PKL_AST_LOC (node),
             "unhandled node #%" PRIu64 " with code %d in code generator",
             PKL_AST_UID (node), PKL_AST_CODE (node));

  PKL_PASS_ERROR;
}
PKL_PHASE_END_HANDLER

/*
 * COND_EXP
 * | COND
 * | THENEXP
 * | ELSEEXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_cond_exp)
{
  pkl_ast_node cond_exp = PKL_PASS_NODE;
  pkl_ast_node cond = PKL_AST_COND_EXP_COND (cond_exp);
  pkl_ast_node thenexp = PKL_AST_COND_EXP_THENEXP (cond_exp);
  pkl_ast_node elseexp = PKL_AST_COND_EXP_ELSEEXP (cond_exp);

  pvm_program_label label1 = pkl_asm_fresh_label (PKL_GEN_ASM);
  pvm_program_label label2 = pkl_asm_fresh_label (PKL_GEN_ASM);

  PKL_PASS_SUBPASS (cond);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BZI, label1);
  PKL_PASS_SUBPASS (thenexp);
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BA, label2);
  pkl_asm_label (PKL_GEN_ASM, label1);
  PKL_PASS_SUBPASS (elseexp);
  pkl_asm_label (PKL_GEN_ASM, label2);

  /* Get rid fo the condition expression.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_NIP);

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * ASM_STMT
 * | TEMPLATE
 * | INPUTS
 * | OUTPUTS
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_asm_stmt)
{
  pkl_ast_node asm_stmt = PKL_PASS_NODE;
  pkl_ast_node input, output;
  char *asm_ret;

  /* Push a canary to the stack.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

  /* Push the inputs on the stack.  */
  for (input = PKL_AST_ASM_STMT_INPUTS (asm_stmt);
       input;
       input = PKL_AST_CHAIN (input))
    {
      PKL_PASS_SUBPASS (input);
    }

  /* Assembly the asm template.  */
  asm_ret = pkl_asm_from_string (PKL_GEN_ASM,
                                 PKL_AST_IDENTIFIER_POINTER (PKL_AST_ASM_STMT_TEMPLATE (asm_stmt)));
  if (asm_ret != NULL)
    {
      PKL_ERROR (PKL_AST_LOC (PKL_AST_ASM_STMT_TEMPLATE (asm_stmt)),
                 "error parsing asm template near %s", asm_ret);
      free (asm_ret);
      PKL_PASS_ERROR;
    }

  /* Generate the output assignments.  */
  for (output = PKL_AST_ASM_STMT_OUTPUTS (asm_stmt);
       output;
       output = PKL_AST_CHAIN (output))
    {
      PKL_PASS_SUBPASS (output);
    }

  /* Check and drop the canary.  */
  {
    pvm_program_label canary_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);

    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, canary_ok_label);
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                  pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
                                      PVM_E_STACK_ESTATUS, NULL,
                                      "stack overflow or underflow in asm statement"));
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
    pkl_asm_label (PKL_GEN_ASM, canary_ok_label);
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  }

  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * ASM_EXP
 * | TYPE
 * | TEMPLATE
 * | INPUTS  */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_pr_asm_exp)
{
  pkl_ast_node asm_exp = PKL_PASS_NODE;
  pkl_ast_node input;
  char *asm_ret;

  /* Push a canary to the stack.  */
  pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH, PVM_NULL);

  /* Push the inputs on the stack.  */
  for (input = PKL_AST_ASM_EXP_INPUTS (asm_exp);
       input;
       input = PKL_AST_CHAIN (input))
    {
      PKL_PASS_SUBPASS (input);
    }

  /* Assembly the asm template.  */
  asm_ret = pkl_asm_from_string (PKL_GEN_ASM,
                                 PKL_AST_IDENTIFIER_POINTER (PKL_AST_ASM_EXP_TEMPLATE (asm_exp)));
  if (asm_ret != NULL)
    {
      PKL_ERROR (PKL_AST_LOC (PKL_AST_ASM_STMT_TEMPLATE (asm_exp)),
                 "error parsing asm template near %s", asm_ret);
      free (asm_ret);
      PKL_PASS_ERROR;
    }

  /* Check and drop the canary.  */
  {
    pvm_program_label canary_ok_label = pkl_asm_fresh_label (PKL_GEN_ASM);

    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_SWAP);
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_BN, canary_ok_label);
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_PUSH,
                  pvm_make_exception (PVM_E_STACK, PVM_E_STACK_NAME,
                                      PVM_E_STACK_ESTATUS, NULL,
                                      "stack overflow or underflow in asm expression"));
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_RAISE);
    pkl_asm_label (PKL_GEN_ASM, canary_ok_label);
    pkl_asm_insn (PKL_GEN_ASM, PKL_INSN_DROP);
  }

  /* Now the return value is on the stack.  */
  PKL_PASS_BREAK;
}
PKL_PHASE_END_HANDLER

/*
 * Expression handlers.
 *
 * | OPERAND1
 * | [OPERAND2]
 * EXP
 */

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_fbin)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node node = PKL_PASS_NODE;
  pkl_ast_node type = PKL_AST_TYPE (node);
  enum pkl_asm_insn insn;

  switch (PKL_AST_EXP_CODE (node))
    {
    case PKL_AST_OP_ADDF: insn = PKL_INSN_ADDF; break;
    case PKL_AST_OP_SUBF: insn = PKL_INSN_SUBF; break;
    case PKL_AST_OP_MULF: insn = PKL_INSN_MULF; break;
    case PKL_AST_OP_DIVF: insn = PKL_INSN_DIVF; break;
    case PKL_AST_OP_MODF: insn = PKL_INSN_MODF; break;
    case PKL_AST_OP_POWF: insn = PKL_INSN_POWF; break;
    case PKL_AST_OP_CEILDIVF: insn = PKL_INSN_CDIVF; break;
    default:
      PK_UNREACHABLE ();
      break;
    }

  pkl_asm_insn (pasm, insn, type);
  pkl_asm_insn (pasm, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

PKL_PHASE_BEGIN_HANDLER (pkl_gen_ps_op_frela)
{
  pkl_asm pasm = PKL_GEN_ASM;
  pkl_ast_node exp = PKL_PASS_NODE;
  int exp_code = PKL_AST_EXP_CODE (exp);
  pkl_ast_node op1 = PKL_AST_EXP_OPERAND (exp, 0);
  pkl_ast_node op1_type = PKL_AST_TYPE (op1);
  enum pkl_asm_insn insn;

  assert (PKL_AST_TYPE_CODE (op1_type) == PKL_TYPE_INTEGRAL);

  switch (exp_code)
    {
    case PKL_AST_OP_EQF: insn = PKL_INSN_EQF; break;
    case PKL_AST_OP_NEF: insn = PKL_INSN_NEF; break;
    case PKL_AST_OP_LTF: insn = PKL_INSN_LTF; break;
    case PKL_AST_OP_GTF: insn = PKL_INSN_GTF; break;
    case PKL_AST_OP_LEF: insn = PKL_INSN_LEF; break;
    case PKL_AST_OP_GEF: insn = PKL_INSN_GEF; break;
    default:
      PK_UNREACHABLE ();
      break;
    }

  pkl_asm_insn (pasm, insn, op1_type);
  pkl_asm_insn (pasm, PKL_INSN_NIP2);
}
PKL_PHASE_END_HANDLER

struct pkl_phase pkl_phase_gen =
  {
    .initialize = pkl_gen_initialize,
    .finalize = pkl_gen_finalize,

    PKL_PHASE_PS_HANDLER (PKL_AST_SRC, pkl_gen_ps_src),
    PKL_PHASE_PR_HANDLER (PKL_AST_DECL, pkl_gen_pr_decl),
    PKL_PHASE_PS_HANDLER (PKL_AST_DECL, pkl_gen_ps_decl),
    PKL_PHASE_PS_HANDLER (PKL_AST_VAR, pkl_gen_ps_var),
    PKL_PHASE_PR_HANDLER (PKL_AST_LAMBDA, pkl_gen_pr_lambda),
    PKL_PHASE_PS_HANDLER (PKL_AST_LAMBDA, pkl_gen_ps_lambda),
    PKL_PHASE_PR_HANDLER (PKL_AST_COND_EXP, pkl_gen_pr_cond_exp),
    PKL_PHASE_PR_HANDLER (PKL_AST_COMP_STMT, pkl_gen_pr_comp_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_COMP_STMT, pkl_gen_ps_comp_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_NULL_STMT, pkl_gen_ps_null_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_ASS_STMT, pkl_gen_pr_ass_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_INCRDECR, pkl_gen_pr_incrdecr),
    PKL_PHASE_PR_HANDLER (PKL_AST_IF_STMT, pkl_gen_pr_if_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_BREAK_CONTINUE_STMT, pkl_gen_ps_break_continue_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_LOOP_STMT, pkl_gen_pr_loop_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_RETURN_STMT, pkl_gen_pr_return_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_RETURN_STMT, pkl_gen_ps_return_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_EXP_STMT, pkl_gen_ps_exp_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_FORMAT, pkl_gen_pr_format),
    PKL_PHASE_PR_HANDLER (PKL_AST_PRINT_STMT, pkl_gen_pr_print_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_RAISE_STMT, pkl_gen_ps_raise_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_TRY_STMT, pkl_gen_pr_try_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_TRY_STMT_BODY, pkl_gen_ps_try_stmt_body),
    PKL_PHASE_PS_HANDLER (PKL_AST_TRY_STMT_HANDLER, pkl_gen_ps_try_stmt_handler),
    PKL_PHASE_PS_HANDLER (PKL_AST_FUNCALL_ARG, pkl_gen_ps_funcall_arg),
    PKL_PHASE_PR_HANDLER (PKL_AST_FUNCALL, pkl_gen_pr_funcall),
    PKL_PHASE_PR_HANDLER (PKL_AST_FUNC, pkl_gen_pr_func),
    PKL_PHASE_PS_HANDLER (PKL_AST_FUNC, pkl_gen_ps_func),
    PKL_PHASE_PR_HANDLER (PKL_AST_FUNC_ARG, pkl_gen_pr_func_arg),
    PKL_PHASE_PR_HANDLER (PKL_AST_FUNC_TYPE_ARG, pkl_gen_pr_func_type_arg),
    PKL_PHASE_PR_HANDLER (PKL_AST_PROGRAM, pkl_gen_pr_program),
    PKL_PHASE_PS_HANDLER (PKL_AST_PROGRAM, pkl_gen_ps_program),
    PKL_PHASE_PS_HANDLER (PKL_AST_INTEGER, pkl_gen_ps_integer),
    PKL_PHASE_PS_HANDLER (PKL_AST_IDENTIFIER, pkl_gen_ps_identifier),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRING, pkl_gen_ps_string),
    PKL_PHASE_PS_HANDLER (PKL_AST_OFFSET, pkl_gen_ps_offset),
    PKL_PHASE_PR_HANDLER (PKL_AST_CAST, pkl_gen_pr_cast),
    PKL_PHASE_PS_HANDLER (PKL_AST_ISA, pkl_gen_ps_isa),
    PKL_PHASE_PR_HANDLER (PKL_AST_MAP, pkl_gen_pr_map),
    PKL_PHASE_PS_HANDLER (PKL_AST_CONS, pkl_gen_ps_cons),
    PKL_PHASE_PR_HANDLER (PKL_AST_ARRAY, pkl_gen_pr_array),
    PKL_PHASE_PS_HANDLER (PKL_AST_ARRAY, pkl_gen_ps_array),
    PKL_PHASE_PR_HANDLER (PKL_AST_TRIMMER, pkl_gen_pr_trimmer),
    PKL_PHASE_PR_HANDLER (PKL_AST_INDEXER, pkl_gen_pr_indexer),
    PKL_PHASE_PR_HANDLER (PKL_AST_ARRAY_INITIALIZER, pkl_gen_pr_array_initializer),
    PKL_PHASE_PS_HANDLER (PKL_AST_ARRAY_INITIALIZER, pkl_gen_ps_array_initializer),
    PKL_PHASE_PR_HANDLER (PKL_AST_STRUCT, pkl_gen_pr_struct),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT, pkl_gen_ps_struct),
    PKL_PHASE_PR_HANDLER (PKL_AST_STRUCT_FIELD, pkl_gen_pr_struct_field),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT_REF, pkl_gen_ps_struct_ref),
    PKL_PHASE_PR_HANDLER (PKL_AST_STRUCT_TYPE_FIELD, pkl_gen_pr_struct_type_field),
    PKL_PHASE_PR_HANDLER (PKL_AST_ASM_STMT, pkl_gen_pr_asm_stmt),
    PKL_PHASE_PR_HANDLER (PKL_AST_ASM_EXP, pkl_gen_pr_asm_exp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ADD, pkl_gen_ps_op_add),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SUB, pkl_gen_ps_op_sub),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_MUL, pkl_gen_ps_op_mul),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_MOD, pkl_gen_ps_op_mod),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_BAND, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_BNOT, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_NEG, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_POS, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_IOR, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_XOR, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SL, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SR, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_DIV, pkl_gen_ps_op_div),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_CEILDIV, pkl_gen_ps_op_div),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_POW, pkl_gen_ps_op_binexp),
    PKL_PHASE_PR_OP_HANDLER (PKL_AST_OP_AND, pkl_gen_pr_op_and),
    PKL_PHASE_PR_OP_HANDLER (PKL_AST_OP_OR, pkl_gen_pr_op_or),
    PKL_PHASE_PR_OP_HANDLER (PKL_AST_OP_IMPL, pkl_gen_pr_op_impl),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_NOT, pkl_gen_ps_op_not),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_EQ, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_NE, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_LT, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_LE, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_GT, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_GE, pkl_gen_ps_op_rela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_NEGF, pkl_gen_ps_op_binexp),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_EQF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_NEF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_LTF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_GTF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_LEF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_GEF, pkl_gen_ps_op_frela),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ADDF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_SUBF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_MULF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_POWF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_DIVF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_CEILDIVF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_MODF, pkl_gen_ps_op_fbin),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_ATTR, pkl_gen_ps_op_attr),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_BCONC, pkl_gen_ps_op_bconc),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_UNMAP, pkl_gen_ps_op_unmap),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_REMAP, pkl_gen_ps_op_remap),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_IN, pkl_gen_ps_op_in),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_APUSH, pkl_gen_ps_op_apush),
    PKL_PHASE_PS_OP_HANDLER (PKL_AST_OP_APOP, pkl_gen_ps_op_apop),
    PKL_PHASE_PR_OP_HANDLER (PKL_AST_OP_EXCOND, pkl_gen_pr_op_excond),
    PKL_PHASE_PR_OP_HANDLER (PKL_AST_OP_TYPEOF, pkl_gen_pr_op_typeof),
    PKL_PHASE_PR_TYPE_HANDLER (PKL_TYPE_ALIAS, pkl_gen_pr_type_alias),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_VOID, pkl_gen_ps_type_void),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_ANY, pkl_gen_ps_type_any),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_INTEGRAL, pkl_gen_ps_type_integral),
    PKL_PHASE_PR_TYPE_HANDLER (PKL_TYPE_OFFSET, pkl_gen_pr_type_offset),
    PKL_PHASE_PR_TYPE_HANDLER (PKL_TYPE_FUNCTION, pkl_gen_pr_type_function),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_FUNCTION, pkl_gen_ps_type_function),
    PKL_PHASE_PR_TYPE_HANDLER (PKL_TYPE_ARRAY, pkl_gen_pr_type_array),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_STRING, pkl_gen_ps_type_string),
    PKL_PHASE_PR_TYPE_HANDLER (PKL_TYPE_STRUCT, pkl_gen_pr_type_struct),
    PKL_PHASE_PS_TYPE_HANDLER (PKL_TYPE_STRUCT, pkl_gen_ps_type_struct),
    PKL_PHASE_ELSE_HANDLER (pkl_gen_noimpl),
  };
