#include "silver/ast.h"
#include "silver/compile_ctx.h"
#include "silver/engine.h"
#include "silver/compiler.h"
#include "silver/directives.h"

#include "internal.h"
#include "debug.h"
#include "tokens.h"
#include "runtime.h"
#include "ops/coercion.h"

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

enum {
  SV_ITER_HINT_GENERIC = 0,
  SV_ITER_HINT_ARRAY = 1,
  SV_ITER_HINT_STRING = 4,
};

static const char *pin_source_text(const char *source, ant_offset_t source_len) {
  if (!source || source_len <= 0) return source;
  const char *pinned = code_arena_alloc(source, (size_t)source_len);
  return pinned ? pinned : source;
}

static void emit(sv_compiler_t *c, uint8_t byte) {
  if (c->code_len >= c->code_cap) {
    c->code_cap = c->code_cap ? c->code_cap * 2 : 256;
    c->code = realloc(c->code, (size_t)c->code_cap);
  }
  c->code[c->code_len++] = byte;
}

static void emit_u16(sv_compiler_t *c, uint16_t val) {
  emit(c, (uint8_t)(val & 0xFF));
  emit(c, (uint8_t)(val >> 8));
}

static void emit_u32(sv_compiler_t *c, uint32_t val) {
  emit(c, (uint8_t)(val & 0xFF));
  emit(c, (uint8_t)((val >> 8) & 0xFF));
  emit(c, (uint8_t)((val >> 16) & 0xFF));
  emit(c, (uint8_t)((val >> 24) & 0xFF));
}

static void emit_i32(sv_compiler_t *c, int32_t val) {
  uint32_t u;
  memcpy(&u, &val, 4);
  emit_u32(c, u);
}

static void emit_op(sv_compiler_t *c, sv_op_t op) {
  emit(c, (uint8_t)op);
}

static void compile_receiver_property_get(sv_compiler_t *c, sv_ast_t *node);
static void compile_truthy_test_expr(sv_compiler_t *c, sv_ast_t *node);

static void emit_srcpos(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return;
  const char *code = c->source;
  ant_offset_t clen = c->source_len;
  if (!code || clen <= 0) return;

  uint32_t off = node->src_off;
  if (off > clen) off = (uint32_t)clen;
  uint32_t end = node->src_end;
  if (end > clen) end = (uint32_t)clen;
  if (end < off) end = off;
  if (end == off && off < (uint32_t)clen) end = off + 1;
  if (end == off) return;

  if (c->srcpos_count > 0 && c->last_srcpos_off == off && c->last_srcpos_end == end) return;

  uint32_t line, col;
  if (c->line_table) sv_compile_ctx_line_table_lookup(c->line_table, off, &line, &col);
  else if (c->srcpos_count > 0 && off >= c->last_srcpos_off) {
    line = c->srcpos[c->srcpos_count - 1].line;
    col = c->srcpos[c->srcpos_count - 1].col;
    for (uint32_t i = c->last_srcpos_off; i < off; i++) {
      if (code[i] == '\n') { line++; col = 1; }
      else col++;
    }
  } else {
    line = 1; col = 1;
    for (uint32_t i = 0; i < off; i++) {
      if (code[i] == '\n') { line++; col = 1; }
      else col++;
    }
  }

  if (c->srcpos_count >= c->srcpos_cap) {
    c->srcpos_cap = c->srcpos_cap ? c->srcpos_cap * 2 : 32;
    c->srcpos = realloc(c->srcpos, (size_t)c->srcpos_cap * sizeof(sv_srcpos_t));
  }
  c->srcpos[c->srcpos_count++] = (sv_srcpos_t){
    .bc_offset = (uint32_t)c->code_len,
    .line = line,
    .col = col,
    .src_off = off,
    .src_end = end,
  };
  c->last_srcpos_off = off;
  c->last_srcpos_end = end;
}

static void patch_u32(sv_compiler_t *c, int offset, uint32_t val) {
  c->code[offset]     = (uint8_t)(val & 0xFF);
  c->code[offset + 1] = (uint8_t)((val >> 8) & 0xFF);
  c->code[offset + 2] = (uint8_t)((val >> 16) & 0xFF);
  c->code[offset + 3] = (uint8_t)((val >> 24) & 0xFF);
}

static int add_constant(sv_compiler_t *c, ant_value_t val) {
  if (vtype(val) == T_STR) {
    ant_offset_t slen;
    ant_offset_t off = vstr(c->js, val, &slen);
    const char *sptr = (const char *)(uintptr_t)off;

    const_dedup_entry_t *found = NULL;
    HASH_FIND(hh, c->const_dedup, sptr, (size_t)slen, found);
    if (found) return found->index;

    int idx = c->const_count;
    if (c->const_count >= c->const_cap) {
      c->const_cap = c->const_cap ? c->const_cap * 2 : 16;
      c->constants = realloc(c->constants, (size_t)c->const_cap * sizeof(ant_value_t));
    }
    c->constants[c->const_count++] = val;

    const_dedup_entry_t *entry = malloc(sizeof(const_dedup_entry_t));
    if (entry) {
      entry->str = sptr;
      entry->len = (size_t)slen;
      entry->index = idx;
      HASH_ADD_KEYPTR(hh, c->const_dedup, entry->str, entry->len, entry);
    }
    return idx;
  }

  if (c->const_count >= c->const_cap) {
    c->const_cap = c->const_cap ? c->const_cap * 2 : 16;
    c->constants = realloc(c->constants, (size_t)c->const_cap * sizeof(ant_value_t));
  }
  c->constants[c->const_count] = val;
  return c->const_count++;
}

static void build_gc_const_tables(sv_func_t *func) {
  if (!func || func->const_count <= 0 || !func->constants) return;

  int child_count = 0;
  for (int i = 0; i < func->const_count; i++) {
    if (vtype(func->constants[i]) == T_NTARG) child_count++;
  }

  if (child_count > 0) {
    func->child_funcs = code_arena_bump((size_t)child_count * sizeof(sv_func_t *));
    if (func->child_funcs) {
      int out = 0;
      for (int i = 0; i < func->const_count; i++) {
        if (vtype(func->constants[i]) != T_NTARG) continue;
        func->child_funcs[out++] = (sv_func_t *)(uintptr_t)vdata(func->constants[i]);
      } func->child_func_count = child_count;
    }
  }

  uint8_t *marked_slots = calloc((size_t)func->const_count, sizeof(uint8_t));
  if (!marked_slots) return;

  int slot_count = 0;
  for (int pc = 0; pc < func->code_len;) {
    sv_op_t op = (sv_op_t)func->code[pc];
    int size = (op < OP__COUNT) ? sv_op_size[op] : 0;
    if (size <= 0) break;

    if (op == OP_PUT_CONST) {
      uint32_t idx = sv_get_u32(func->code + pc + 1);
      if (idx < (uint32_t)func->const_count && !marked_slots[idx]) {
        marked_slots[idx] = 1; slot_count++;
      }
    }

    pc += size;
  }

  if (slot_count > 0) {
    func->gc_const_slots = code_arena_bump((size_t)slot_count * sizeof(uint32_t));
    if (func->gc_const_slots) {
      int out = 0;
      for (int i = 0; i < func->const_count; i++) {
        if (!marked_slots[i]) continue;
        func->gc_const_slots[out++] = (uint32_t)i;
      }
      func->gc_const_slot_count = slot_count;
    }
  }

  free(marked_slots);
}

static void emit_constant(sv_compiler_t *c, ant_value_t val) {
  int idx = add_constant(c, val);
  if (idx <= 255) {
    emit_op(c, OP_CONST8);
    emit(c, (uint8_t)idx);
  } else {
    emit_op(c, OP_CONST);
    emit_u32(c, (uint32_t)idx);
  }
}

static void emit_number(sv_compiler_t *c, double num) {
  if (num >= -128 && num <= 127 && num == (int)num && num != -0.0) {
    emit_op(c, OP_CONST_I8);
    emit(c, (uint8_t)(int8_t)num);
  } else emit_constant(c, tov(num));
}


static inline bool is_quoted_ident_key(const sv_ast_t *node) {
  if (!node || node->type != N_IDENT || !node->str || node->len < 2) return false;
  char open = node->str[0];
  char close = node->str[node->len - 1];
  return ((open == '\'' && close == '\'') || (open == '"' && close == '"'));
}

static inline bool is_template_segment(const sv_ast_t *node) {
  return node && node->type == N_STRING && (node->flags & FN_TEMPLATE_SEGMENT);
}

static inline bool is_invalid_cooked_string(const sv_ast_t *node) {
  return is_template_segment(node) && (node->flags & FN_INVALID_COOKED);
}

static inline ant_value_t ast_string_const(sv_compiler_t *c, const sv_ast_t *node) {
  if (!node || !node->str) return js_mkstr_permanent(c->js, "", 0);
  return js_mkstr_permanent(c->js, node->str, node->len);
}

static inline void compile_static_property_key(sv_compiler_t *c, sv_ast_t *key) {
  if (!key) {
    emit_op(c, OP_UNDEF);
    return;
  }

  if (key->type == N_STRING) {
    emit_constant(c, ast_string_const(c, key));
    return;
  }

  if (key->type == N_NUMBER) {
    char buf[32];
    int n = snprintf(buf, sizeof(buf), "%g", key->num);
    emit_constant(c, js_mkstr_permanent(c->js, buf, (size_t)n));
    return;
  }

  if (key->type == N_IDENT) {
    if (is_quoted_ident_key(key))
      emit_constant(c, js_mkstr_permanent(c->js, key->str + 1, key->len - 2));
    else
      emit_constant(c, js_mkstr_permanent(c->js, key->str, key->len));
    return;
  }

  compile_expr(c, key);
}

static int add_atom(sv_compiler_t *c, const char *str, uint32_t len) {
  const char *interned = intern_string(str, (size_t)len);
  const char *stored = interned;
  if (!stored) {
    char *copy = code_arena_bump(len);
    memcpy(copy, str, len);
    stored = copy;
  }

  for (int i = 0; i < c->atom_count; i++) {
    if (c->atoms[i].len == len && c->atoms[i].str == stored)
      return i;
  }
  if (c->atom_count >= c->atom_cap) {
    c->atom_cap = c->atom_cap ? c->atom_cap * 2 : 16;
    c->atoms = realloc(c->atoms, (size_t)c->atom_cap * sizeof(sv_atom_t));
  }
  c->atoms[c->atom_count] = (sv_atom_t){ .str = stored, .len = len };
  return c->atom_count++;
}

static inline bool sv_op_has_ic_slot(sv_op_t op) {
  return op == OP_GET_GLOBAL || op == OP_GET_GLOBAL_UNDEF ||
         op == OP_GET_FIELD || op == OP_GET_FIELD2 || op == OP_PUT_FIELD;
}

static uint16_t alloc_ic_idx(sv_compiler_t *c) {
  if (!c || c->ic_count >= (int)UINT16_MAX) return UINT16_MAX;
  return (uint16_t)c->ic_count++;
}

static void sv_func_init_obj_sites(sv_func_t *func) {
  if (!func || !func->code || func->code_len <= 0) return;

  uint32_t count = 0;
  for (int pc = 0; pc < func->code_len; ) {
    sv_op_t op = (sv_op_t)func->code[pc];
    if (op == OP_OBJECT) count++;
    uint8_t size = (op < OP__COUNT) ? sv_op_size[op] : 1;
    pc += (size > 0) ? size : 1;
  }
  if (count == 0) return;

  func->obj_sites = code_arena_bump((size_t)count * sizeof(sv_obj_site_cache_t));
  memset(func->obj_sites, 0, (size_t)count * sizeof(sv_obj_site_cache_t));
  func->obj_site_count = (uint16_t)count;

  uint32_t idx = 0;
  for (int pc = 0; pc < func->code_len && idx < count; ) {
    sv_op_t op = (sv_op_t)func->code[pc];
    if (op == OP_OBJECT) func->obj_sites[idx++].bc_off = (uint32_t)pc;
    uint8_t size = (op < OP__COUNT) ? sv_op_size[op] : 1;
    pc += (size > 0) ? size : 1;
  }
}

static void emit_atom_idx_op(sv_compiler_t *c, sv_op_t op, uint32_t atom_idx) {
  emit_op(c, op);
  emit_u32(c, atom_idx);
  if (sv_op_has_ic_slot(op))
    emit_u16(c, alloc_ic_idx(c));
}

static void emit_atom_op(sv_compiler_t *c, sv_op_t op, const char *str, uint32_t len) {
  int idx = add_atom(c, str, len);
  emit_atom_idx_op(c, op, (uint32_t)idx);
}

static inline void emit_set_function_name(
  sv_compiler_t *c,
  const char *name, uint32_t len
) {
  if (!name) { name = ""; len = 0; }
  emit_atom_op(c, OP_SET_NAME, name, len);
}

static void emit_const_assign_error(sv_compiler_t *c, const char *name, uint32_t len) {
  static const char prefix[] = "Assignment to constant variable '";
  static const char suffix[] = "'";
  
  uint32_t mlen = (uint32_t)(sizeof(prefix) - 1 + len + sizeof(suffix) - 1);
  char *buf = code_arena_bump(mlen);
  
  memcpy(buf, prefix, sizeof(prefix) - 1);
  memcpy(buf + sizeof(prefix) - 1, name, len);
  memcpy(buf + sizeof(prefix) - 1 + len, suffix, sizeof(suffix) - 1);
  
  int atom = add_atom(c, buf, mlen);
  emit_op(c, OP_THROW_ERROR);
  emit_u32(c, (uint32_t)atom);
  emit(c, (uint8_t)JS_ERR_TYPE);
}

static inline bool is_ident_str(
  const char *name, uint32_t len,
  const char *lit, uint32_t lit_len
) {
  return len == lit_len && memcmp(name, lit, lit_len) == 0;
}

static inline bool is_strict_restricted_ident(const char *name, uint32_t len) {
  return 
    is_ident_str(name, len, "eval", 4) ||
    is_ident_str(name, len, "arguments", 9);
}

static inline bool is_repl_top_level(const sv_compiler_t *c) {
  return 
    c->mode == SV_COMPILE_REPL && c->scope_depth == 0 &&
    c->enclosing && !c->enclosing->enclosing &&
    !c->is_strict;
}

static inline bool has_completion_value(const sv_compiler_t *c) {
  return c && (c->mode == SV_COMPILE_EVAL || c->mode == SV_COMPILE_REPL);
}

static inline bool has_module_import_binding(const sv_compiler_t *c) {
  for (const sv_compiler_t *cur = c; cur; cur = cur->enclosing) {
    if (cur->mode == SV_COMPILE_MODULE) return true;
  }
  return false;
}

static inline bool has_implicit_arguments_obj(const sv_compiler_t *c) {
  return c && !c->is_arrow && c->enclosing;
}

static int resolve_local(sv_compiler_t *c, const char *name, uint32_t len) {
  if (c->local_lookup_heads && c->local_lookup_cap > 0) {
    uint32_t hash = sv_compile_ctx_hash_local_name(name, len);
    int bucket = (int)(hash & (uint32_t)(c->local_lookup_cap - 1));
    for (int i = c->local_lookup_heads[bucket]; i != -1; i = c->locals[i].lookup_next) {
      sv_local_t *loc = &c->locals[i];
      if (
        loc->name_hash == hash &&
        loc->name_len == len &&
        memcmp(loc->name, name, len) == 0
      ) return i;
    }
    return -1;
  }

  for (int i = c->local_count - 1; i >= 0; i--) {
    sv_local_t *loc = &c->locals[i];
    if (loc->name_len == len && memcmp(loc->name, name, len) == 0) return i;
  }
  
  return -1;
}

static int add_local(
  sv_compiler_t *c, const char *name, uint32_t len,
  bool is_const, int depth
) {
  sv_compile_ctx_ensure_local_lookup_capacity(c, c->local_count + 1);
  if (c->local_count >= c->local_cap) {
    c->local_cap = c->local_cap ? c->local_cap * 2 : 16;
    c->locals = realloc(c->locals, (size_t)c->local_cap * sizeof(sv_local_t));
  }
  int idx = c->local_count++;
  if (c->local_count > c->max_local_count)
    c->max_local_count = c->local_count;
  c->locals[idx] = (sv_local_t){
    .name = name, .name_len = len,
    .name_hash = sv_compile_ctx_hash_local_name(name, len),
    .lookup_next = -1,
    .depth = depth, .is_const = is_const, .captured = false,
    .inferred_type = SV_TI_UNKNOWN,
  };
  sv_compile_ctx_local_lookup_insert(c, idx);
  return idx;
}

static int reserve_hidden_locals(sv_compiler_t *c, int count) {
  int base = c->local_count;
  for (int i = 0; i < count; i++)
    add_local(c, "", 0, false, c->scope_depth);
  return base;
}

static inline bool sv_type_is_known(uint8_t t) {
  return t != SV_TI_UNKNOWN;
}

static inline bool sv_type_is_num(uint8_t t) {
  return t == SV_TI_NUM;
}

static void ensure_slot_type_cap(sv_compiler_t *c, int slot) {
  if (slot < 0) return;
  if (slot < c->slot_type_cap) return;
  int new_cap = c->slot_type_cap ? c->slot_type_cap : 16;
  while (new_cap <= slot) new_cap *= 2;
  sv_type_info_t *next = realloc(c->slot_types, (size_t)new_cap * sizeof(sv_type_info_t));
  if (!next) return;
  memset(next + c->slot_type_cap, 0, (size_t)(new_cap - c->slot_type_cap) * sizeof(sv_type_info_t));
  c->slot_types = next;
  c->slot_type_cap = new_cap;
}

static void mark_slot_type(sv_compiler_t *c, int slot, uint8_t type) {
  if (slot < 0) return;
  ensure_slot_type_cap(c, slot);
  if (!c->slot_types || slot >= c->slot_type_cap) return;
  uint8_t old = c->slot_types[slot].type;
  if (!sv_type_is_known(type)) {
    c->slot_types[slot].type = SV_TI_UNKNOWN;
    return;
  }
  if (!sv_type_is_known(old))
    c->slot_types[slot].type = type;
  else if (old != type)
    c->slot_types[slot].type = SV_TI_UNKNOWN;
}

static void set_local_inferred_type(sv_compiler_t *c, int local_idx, uint8_t type) {
  if (local_idx < 0 || local_idx >= c->local_count) return;
  c->locals[local_idx].inferred_type = type;
  if (c->locals[local_idx].depth == -1) return;
  int slot = local_idx - c->param_locals;
  mark_slot_type(c, slot, type);
}

static inline uint8_t get_local_inferred_type(sv_compiler_t *c, int local_idx) {
  if (local_idx < 0 || local_idx >= c->local_count) return SV_TI_UNKNOWN;
  if (c->locals[local_idx].depth == -1) return SV_TI_UNKNOWN;
  if (c->locals[local_idx].is_tdz) return SV_TI_UNKNOWN;
  return c->locals[local_idx].inferred_type;
}

static const char *typeof_name_for_type(uint8_t type) {
  switch (type) {
    case SV_TI_NUM:   return "number";
    case SV_TI_STR:   return "string";
    case SV_TI_BOOL:  return "boolean";
    case SV_TI_UNDEF: return "undefined";
    case SV_TI_ARR:
    case SV_TI_OBJ:
    case SV_TI_NULL:  return "object";
    default:          return NULL;
  }
}

static uint8_t iter_hint_for_type(uint8_t type) {
  switch (type) {
    case SV_TI_ARR: return SV_ITER_HINT_ARRAY;
    case SV_TI_STR: return SV_ITER_HINT_STRING;
    default:        return SV_ITER_HINT_GENERIC;
  }
}

static int ensure_local_at_depth(
  sv_compiler_t *c, const char *name, uint32_t len,
  bool is_const, int depth
) {
  int local = resolve_local(c, name, len);
  if (local != -1 && c->locals[local].depth == depth)
    return local;
  return add_local(c, name, len, is_const, depth);
}

static int local_to_frame_slot(sv_compiler_t *c, int local_idx) {
  if (c->locals[local_idx].depth == -1) return local_idx;
  return c->param_count + (local_idx - c->param_locals);
}

static int resolve_local_slot(sv_compiler_t *c, const char *name, uint32_t len) {
  int local = resolve_local(c, name, len);
  if (local == -1 || c->locals[local].depth == -1) return -1;
  int slot = local - c->param_locals;
  return (slot >= 0 && slot <= 255) ? slot : -1;
}

static int add_upvalue(sv_compiler_t *c, uint16_t index, bool is_local, bool is_const) {
  for (int i = 0; i < c->upvalue_count; i++) {
    if (c->upval_descs[i].index == index &&
        c->upval_descs[i].is_local == is_local)
      return i;
  }
  if (c->upvalue_count >= c->upvalue_cap) {
    c->upvalue_cap = c->upvalue_cap ? c->upvalue_cap * 2 : 8;
    c->upval_descs = realloc(
      c->upval_descs,
      (size_t)c->upvalue_cap * sizeof(sv_upval_desc_t));
  }
  int idx = c->upvalue_count++;
  c->upval_descs[idx] = (sv_upval_desc_t){
    .index = index, .is_local = is_local, .is_const = is_const,
  };
  return idx;
}

static int resolve_super_upvalue(sv_compiler_t *c) {
  if (!c->enclosing) return -1;
  sv_compiler_t *enc = c->enclosing;

  if (!enc->is_arrow) {
    if (enc->super_local < 0) return -1;
    enc->locals[enc->super_local].captured = true;
    uint16_t slot = (uint16_t)local_to_frame_slot(enc, enc->super_local);
    return add_upvalue(c, slot, true, false);
  }

  int upvalue = resolve_super_upvalue(enc);
  if (upvalue == -1) return -1;
  return add_upvalue(c, (uint16_t)upvalue, false, false);
}

static int resolve_arguments_upvalue(sv_compiler_t *c) {
  if (!c->enclosing) return -1;
  sv_compiler_t *enc = c->enclosing;

  if (!enc->is_arrow) {
    if (enc->strict_args_local < 0) return -1;
    enc->locals[enc->strict_args_local].captured = true;
    uint16_t slot = (uint16_t)local_to_frame_slot(enc, enc->strict_args_local);
    return add_upvalue(c, slot, true, false);
  }

  int upvalue = resolve_arguments_upvalue(enc);
  if (upvalue == -1) return -1;
  
  return add_upvalue(c, (uint16_t)upvalue, false, false);
}

static int resolve_upvalue(sv_compiler_t *c, const char *name, uint32_t len) {
  if (!c->enclosing) return -1;

  int local = resolve_local(c->enclosing, name, len);
  if (local != -1) {
    c->enclosing->locals[local].captured = true;
    uint16_t slot = (uint16_t)local_to_frame_slot(c->enclosing, local);
    return add_upvalue(c, slot, true, c->enclosing->locals[local].is_const);
  }

  int upvalue = resolve_upvalue(c->enclosing, name, len);
  if (upvalue != -1) {
    bool uv_const = c->enclosing->upval_descs[upvalue].is_const;
    return add_upvalue(c, (uint16_t)upvalue, false, uv_const);
  }

  return -1;
}

static int emit_jump(sv_compiler_t *c, sv_op_t op) {
  emit_op(c, op);
  int offset = c->code_len;
  emit_i32(c, 0);
  return offset;
}

static void patch_jump(sv_compiler_t *c, int offset) {
  int32_t delta = (int32_t)(c->code_len - offset - 4);
  patch_u32(c, offset, (uint32_t)delta);
}

static void emit_loop(sv_compiler_t *c, int loop_start) {
  emit_op(c, OP_JMP);
  int32_t delta = (int32_t)(loop_start - c->code_len - 4);
  emit_i32(c, delta);
}

static void patch_list_add(sv_patch_list_t *pl, int offset) {
  if (pl->count >= pl->cap) {
    pl->cap = pl->cap ? pl->cap * 2 : 4;
    pl->offsets = realloc(pl->offsets, (size_t)pl->cap * sizeof(int));
  }
  pl->offsets[pl->count++] = offset;
}

static void patch_list_resolve(sv_compiler_t *c, sv_patch_list_t *pl) {
  for (int i = 0; i < pl->count; i++)
    patch_jump(c, pl->offsets[i]);
  free(pl->offsets);
  *pl = (sv_patch_list_t){0};
}

static void push_loop(
  sv_compiler_t *c, int loop_start,
  const char *label, uint32_t label_len,
  bool is_switch
) {
  if (c->loop_count >= c->loop_cap) {
    c->loop_cap = c->loop_cap ? c->loop_cap * 2 : 4;
    c->loops = realloc(c->loops, (size_t)c->loop_cap * sizeof(sv_loop_t));
  }
  if (!label && c->pending_label) {
    label = c->pending_label;
    label_len = c->pending_label_len;
    c->pending_label = NULL;
    c->pending_label_len = 0;
  }
  c->loops[c->loop_count++] = (sv_loop_t){
    .loop_start = loop_start,
    .scope_depth = c->scope_depth,
    .label = label, .label_len = label_len,
    .is_switch = is_switch,
  };
}

static void pop_loop(sv_compiler_t *c) {
  sv_loop_t *loop = &c->loops[--c->loop_count];
  patch_list_resolve(c, &loop->breaks);
  free(loop->continues.offsets);
}

static void begin_scope(sv_compiler_t *c) {
  c->scope_depth++;
}

static void end_scope(sv_compiler_t *c) {
  while (
    c->local_count > 0 &&
    c->locals[c->local_count - 1].depth >= c->scope_depth
  ) {
    sv_local_t *loc = &c->locals[c->local_count - 1];
    if (loc->captured) {
      int frame_slot = local_to_frame_slot(c, c->local_count - 1);
      emit_op(c, OP_CLOSE_UPVAL);
      emit_u16(c, (uint16_t)frame_slot);
    }
    sv_compile_ctx_local_lookup_remove(c, c->local_count - 1);
    c->local_count--;
  }
  c->scope_depth--;
}

static void emit_close_upvals(sv_compiler_t *c) {
  for (int i = 0; i < c->local_count; i++) {
  if (c->locals[i].captured) {
    int frame_slot = local_to_frame_slot(c, i);
    emit_op(c, OP_CLOSE_UPVAL);
    emit_u16(c, (uint16_t)frame_slot);
    return;
  }}
}

static void emit_with_get(
  sv_compiler_t *c, const char *name, uint32_t len,
  uint8_t fb_kind, uint16_t fb_idx
) {
  int atom = add_atom(c, name, len);
  emit_op(c, OP_WITH_GET_VAR);
  emit_u32(c, (uint32_t)atom);
  emit(c, fb_kind);
  emit_u16(c, fb_idx);
}

static void emit_with_put(
  sv_compiler_t *c, const char *name, uint32_t len,
  uint8_t fb_kind, uint16_t fb_idx
) {
  int atom = add_atom(c, name, len);
  emit_op(c, OP_WITH_PUT_VAR);
  emit_u32(c, (uint32_t)atom);
  emit(c, fb_kind);
  emit_u16(c, fb_idx);
}

static void emit_get_local(sv_compiler_t *c, int local_idx);
static void emit_put_local(sv_compiler_t *c, int local_idx);
static void emit_put_local_typed(sv_compiler_t *c, int local_idx, uint8_t type);

static inline void emit_get_module_import_binding(sv_compiler_t *c) {
  emit_op(c, OP_SPECIAL_OBJ);
  emit(c, 3);
}

static void emit_get_var(sv_compiler_t *c, const char *name, uint32_t len) {
  bool is_super = is_ident_str(name, len, "super", 5);

  if (is_super && c->super_local >= 0) {
    emit_get_local(c, c->super_local);
    return;
  }
  
  if (is_super && c->is_arrow) {
  int super_upval = resolve_super_upvalue(c);
  if (super_upval != -1) {
    emit_op(c, OP_GET_UPVAL);
    emit_u16(c, (uint16_t)super_upval);
    return;
  }}

  int local = resolve_local(c, name, len);
  if (local != -1) {
    if (c->with_depth > 0) {
      uint8_t kind = c->locals[local].depth == -1 ? WITH_FB_ARG : WITH_FB_LOCAL;
      uint16_t idx = kind == WITH_FB_ARG 
        ? (uint16_t)local
        : (uint16_t)(local - c->param_locals);
      emit_with_get(c, name, len, kind, idx);
      return;
    }
    if (c->locals[local].depth == -1) {
      emit_op(c, OP_GET_ARG);
      emit_u16(c, (uint16_t)local);
    } else {
      int slot = local - c->param_locals;
      if (c->locals[local].is_tdz) {
        int ai = add_atom(c, name, len);
        emit_op(c, OP_GET_LOCAL_CHK);
        emit_u16(c, (uint16_t)slot);
        emit_u32(c, (uint32_t)ai);
      } else if (slot <= 255) {
        emit_op(c, OP_GET_LOCAL8);
        emit(c, (uint8_t)slot);
      } else {
        emit_op(c, OP_GET_LOCAL);
        emit_u16(c, (uint16_t)slot);
      }
    }
    return;
  }
  
  int upval = resolve_upvalue(c, name, len);
  if (upval != -1) {
    if (c->with_depth > 0) {
      emit_with_get(c, name, len, WITH_FB_UPVAL, (uint16_t)upval);
      return;
    }
    emit_op(c, OP_GET_UPVAL);
    emit_u16(c, (uint16_t)upval);
    return;
  }
  
  if (is_ident_str(name, len, "arguments", 9)) {
    if (has_implicit_arguments_obj(c)) {
      if (c->strict_args_local >= 0) {
        emit_get_local(c, c->strict_args_local);
      } else {
        emit_op(c, OP_SPECIAL_OBJ);
        emit(c, 0);
      }
      return;
    }
    if (c->is_arrow) {
      int args_upval = resolve_arguments_upvalue(c);
      if (args_upval != -1) {
        emit_op(c, OP_GET_UPVAL);
        emit_u16(c, (uint16_t)args_upval);
        return;
      }
    }
  }
  
  if (has_module_import_binding(c) && is_ident_str(name, len, "import", 6)) {
    emit_get_module_import_binding(c);
    return;
  }
  
  if (c->with_depth > 0) emit_with_get(c, name, len, WITH_FB_GLOBAL, 0);
  else emit_atom_op(c, OP_GET_GLOBAL, name, len);
}

static void emit_set_var(sv_compiler_t *c, const char *name, uint32_t len, bool keep) {
  int local = resolve_local(c, name, len);
  if (local != -1) {
    if (c->locals[local].is_const) {
      emit_const_assign_error(c, name, len);
      return;
    }
    set_local_inferred_type(c, local, SV_TI_UNKNOWN);

    if (c->with_depth > 0) {
      uint8_t kind = c->locals[local].depth == -1 ? WITH_FB_ARG : WITH_FB_LOCAL;
      uint16_t idx = kind == WITH_FB_ARG 
        ? (uint16_t)local
        : (uint16_t)(local - c->param_locals);
      if (keep) emit_op(c, OP_DUP);
      emit_with_put(c, name, len, kind, idx);
      return;
    }
    if (c->locals[local].depth == -1) {
      emit_op(c, keep ? OP_SET_ARG : OP_PUT_ARG);
      emit_u16(c, (uint16_t)local);
    } else {
      int slot = local - c->param_locals;
      sv_op_t op = keep 
        ? (slot <= 255 ? OP_SET_LOCAL8 : OP_SET_LOCAL)
        : (slot <= 255 ? OP_PUT_LOCAL8 : OP_PUT_LOCAL);
      emit_op(c, op);
      if (slot <= 255) emit(c, (uint8_t)slot);
      else emit_u16(c, (uint16_t)slot);
    }
    return;
  }
  int upval = resolve_upvalue(c, name, len);
  if (upval != -1) {
    if (c->upval_descs[upval].is_const) {
      emit_const_assign_error(c, name, len);
      return;
    }
    if (c->with_depth > 0) {
      if (keep) emit_op(c, OP_DUP);
      emit_with_put(c, name, len, WITH_FB_UPVAL, (uint16_t)upval);
      return;
    }
    emit_op(c, keep ? OP_SET_UPVAL : OP_PUT_UPVAL);
    emit_u16(c, (uint16_t)upval);
    return;
  }
  if (has_module_import_binding(c) && is_ident_str(name, len, "import", 6)) {
    emit_const_assign_error(c, name, len);
    return;
  }
  if (c->with_depth > 0) {
    if (keep) emit_op(c, OP_DUP);
    emit_with_put(c, name, len, WITH_FB_GLOBAL, 0);
  } else {
    if (keep) {
      emit_op(c, OP_DUP);
      emit_atom_op(c, OP_PUT_GLOBAL, name, len);
    } else emit_atom_op(c, OP_PUT_GLOBAL, name, len);
  }
}

static void emit_put_local_typed(sv_compiler_t *c, int local_idx, uint8_t type) {
  int slot = local_idx - c->param_locals;
  if (slot <= 255) { emit_op(c, OP_PUT_LOCAL8); emit(c, (uint8_t)slot); }
  else { emit_op(c, OP_PUT_LOCAL); emit_u16(c, (uint16_t)slot); }
  set_local_inferred_type(c, local_idx, type);
}

static inline void emit_slot_op(sv_compiler_t *c, sv_op_t op, uint16_t slot) {
  emit_op(c, op);
  emit_u16(c, slot);
}

static void emit_put_local(sv_compiler_t *c, int local_idx) {
  emit_put_local_typed(c, local_idx, SV_TI_UNKNOWN);
}

static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node);

static void emit_get_local(sv_compiler_t *c, int local_idx) {
  int slot = local_idx - c->param_locals;
  if (slot <= 255) { emit_op(c, OP_GET_LOCAL8); emit(c, (uint8_t)slot); }
  else { emit_op(c, OP_GET_LOCAL); emit_u16(c, (uint16_t)slot); }
}

static bool match_self_append_local(
  sv_compiler_t *c, sv_ast_t *node,
  int *out_local_idx, uint16_t *out_slot, sv_ast_t **out_rhs
) {
  if (!c || !node || node->type != N_ASSIGN || !node->left || node->left->type != N_IDENT)
    return false;
  if (c->with_depth > 0) return false;

  int local = resolve_local(c, node->left->str, node->left->len);
  if (local < 0 || c->locals[local].is_const) return false;
  if (c->locals[local].is_tdz) return false;
  if (c->locals[local].depth == -1 && c->strict_args_local >= 0) return false;

  sv_ast_t *rhs = NULL;
  if (node->op == TOK_PLUS_ASSIGN) rhs = node->right;
  else if (
    node->op == TOK_ASSIGN &&
    node->right && node->right->type == N_BINARY && node->right->op == TOK_PLUS &&
    node->right->left && node->right->left->type == N_IDENT &&
    node->right->left->len == node->left->len &&
    memcmp(node->right->left->str, node->left->str, node->left->len) == 0
  ) rhs = node->right->right;

  if (!rhs) return false;
  uint8_t local_type = get_local_inferred_type(c, local);
  uint8_t rhs_type = infer_expr_type(c, rhs);
  if (local_type != SV_TI_STR && rhs_type != SV_TI_STR)
    return false;
    
  if (out_local_idx) *out_local_idx = local;
  if (out_slot) *out_slot = (uint16_t)local_to_frame_slot(c, local);
  if (out_rhs) *out_rhs = rhs;
  
  return true;
}

static bool is_self_append_inplace_safe_ident(sv_compiler_t *c, sv_ast_t *node) {
  if (!c || !node || node->type != N_IDENT) return false;

  if (resolve_local(c, node->str, node->len) != -1) return true;
  if (resolve_upvalue(c, node->str, node->len) != -1) return true;

  if (is_ident_str(node->str, node->len, "arguments", 9)) {
    if (has_implicit_arguments_obj(c)) return true;
    if (c->is_arrow && resolve_arguments_upvalue(c) != -1) return true;
  }

  if (c->is_arrow && is_ident_str(node->str, node->len, "super", 5))
    return resolve_super_upvalue(c) != -1;

  if (has_module_import_binding(c) && is_ident_str(node->str, node->len, "import", 6))
    return true;

  return false;
}

static bool is_self_append_inplace_safe_expr(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return false;

  switch (node->type) {
    case N_NUMBER:
    case N_STRING:
    case N_BIGINT:
    case N_BOOL:
    case N_NULL:
    case N_UNDEF:
      return true;

    case N_IDENT:
      return is_self_append_inplace_safe_ident(c, node);

    case N_BINARY: return 
      is_self_append_inplace_safe_expr(c, node->left) &&
      is_self_append_inplace_safe_expr(c, node->right);
      
    case N_UNARY:
    case N_TYPEOF:
    case N_VOID:
      return is_self_append_inplace_safe_expr(c, node->left);
      
    default:
      return false;
  }
}

static bool compile_self_append_stmt(sv_compiler_t *c, sv_ast_t *node) {
  int local = -1;
  uint16_t slot = 0;
  sv_ast_t *rhs = NULL;
  if (!match_self_append_local(c, node, &local, &slot, &rhs)) return false;
  if (is_self_append_inplace_safe_expr(c, rhs)) {
    compile_expr(c, rhs);
    emit_slot_op(c, OP_STR_APPEND_LOCAL, slot);
  } else {
    emit_slot_op(c, OP_GET_SLOT_RAW, slot);
    compile_expr(c, rhs);
    emit_slot_op(c, OP_STR_ALC_SNAPSHOT, slot);
  }
  set_local_inferred_type(c, local, SV_TI_UNKNOWN);
  return true;
}


static inline bool is_ident_name(sv_ast_t *node, const char *name) {
  size_t n = strlen(name);
  return node 
    && node->type == N_IDENT && node->len == (uint32_t)n 
    && memcmp(node->str, name, n) == 0;
}

static void hoist_var_pattern(sv_compiler_t *c, sv_ast_t *pat) {
  if (!pat) return;
  switch (pat->type) {
    case N_IDENT:
      if (resolve_local(c, pat->str, pat->len) == -1)
        add_local(c, pat->str, pat->len, false, 0);
      break;
    case N_ARRAY: case N_ARRAY_PAT:
      for (int i = 0; i < pat->args.count; i++)
        hoist_var_pattern(c, pat->args.items[i]);
      break;
    case N_OBJECT: case N_OBJECT_PAT:
      for (int i = 0; i < pat->args.count; i++) {
        sv_ast_t *prop = pat->args.items[i];
        if (!prop) continue;
        if (prop->type == N_PROPERTY)
          hoist_var_pattern(c, prop->right);
        else if (prop->type == N_REST || prop->type == N_SPREAD)
          hoist_var_pattern(c, prop->right);
      }
      break;
    case N_ASSIGN_PAT:
      hoist_var_pattern(c, pat->left);
      break;
    case N_ASSIGN:
      hoist_var_pattern(c, pat->left);
      break;
    case N_REST: case N_SPREAD:
      hoist_var_pattern(c, pat->right);
      break;
    default:
      break;
  }
}

static void hoist_var_decls(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return;
  switch (node->type) {
    case N_VAR:
      if (node->var_kind == SV_VAR_VAR) {
        for (int i = 0; i < node->args.count; i++) {
          sv_ast_t *decl = node->args.items[i];
          if (decl->type == N_VARDECL && decl->left)
            hoist_var_pattern(c, decl->left);
        }
      }
      break;
    case N_BLOCK:
      for (int i = 0; i < node->args.count; i++)
        hoist_var_decls(c, node->args.items[i]);
      break;
    case N_IF:
      hoist_var_decls(c, node->left);
      hoist_var_decls(c, node->right);
      break;
    case N_WHILE: case N_DO_WHILE: case N_LABEL:
      hoist_var_decls(c, node->body);
      break;
    case N_FOR:
      hoist_var_decls(c, node->init);
      hoist_var_decls(c, node->body);
      break;
    case N_FOR_IN: case N_FOR_OF: case N_FOR_AWAIT_OF:
      hoist_var_decls(c, node->left);
      hoist_var_decls(c, node->body);
      break;
    case N_SWITCH:
      for (int i = 0; i < node->args.count; i++) {
        sv_ast_t *cas = node->args.items[i];
        for (int j = 0; j < cas->args.count; j++)
          hoist_var_decls(c, cas->args.items[j]);
      }
      break;
    case N_TRY:
      hoist_var_decls(c, node->body);
      hoist_var_decls(c, node->catch_body);
      hoist_var_decls(c, node->finally_body);
      break;
    case N_EXPORT:
      hoist_var_decls(c, node->left);
      break;
    default: break;
  }
}

static void hoist_lexical_pattern(sv_compiler_t *c, sv_ast_t *pat,
                                  bool is_const) {
  if (!pat) return;

  switch (pat->type) {
    case N_IDENT:
      ensure_local_at_depth(c, pat->str, pat->len, is_const, c->scope_depth);
      break;
    case N_ASSIGN_PAT:
    case N_ASSIGN:
      hoist_lexical_pattern(c, pat->left, is_const);
      break;
    case N_REST:
    case N_SPREAD:
      hoist_lexical_pattern(c, pat->right, is_const);
      break;
    case N_ARRAY:
    case N_ARRAY_PAT:
      for (int i = 0; i < pat->args.count; i++)
        hoist_lexical_pattern(c, pat->args.items[i], is_const);
      break;
    case N_OBJECT:
    case N_OBJECT_PAT:
      for (int i = 0; i < pat->args.count; i++) {
        sv_ast_t *prop = pat->args.items[i];
        if (!prop) continue;
        if (prop->type == N_PROPERTY)
          hoist_lexical_pattern(c, prop->right, is_const);
        else if (prop->type == N_REST || prop->type == N_SPREAD)
          hoist_lexical_pattern(c, prop->right, is_const);
      }
      break;
    default:
      break;
  }
}

static void annex_b_collect_funcs(sv_ast_t *node, sv_ast_list_t *out) {
  if (!node) return;
  if (node->type == N_FUNC && node->str && !(node->flags & FN_ARROW)) {
    sv_ast_list_push(out, node);
    return;
  }
  if (node->type == N_IF) {
    annex_b_collect_funcs(node->left, out);
    annex_b_collect_funcs(node->right, out);
  } else if (node->type == N_LABEL) annex_b_collect_funcs(node->body, out);
}

static void hoist_lexical_decls(sv_compiler_t *c, sv_ast_list_t *stmts) {
  for (int i = 0; i < stmts->count; i++) {
    sv_ast_t *node = stmts->items[i];
    if (!node) continue;
    sv_ast_t *decl_node = (node->type == N_EXPORT) ? node->left : node;
    if (!decl_node) continue;

    if (decl_node->type == N_VAR && decl_node->var_kind != SV_VAR_VAR) {
      bool is_const = 
        (decl_node->var_kind == SV_VAR_CONST ||
        decl_node->var_kind == SV_VAR_USING ||
        decl_node->var_kind == SV_VAR_AWAIT_USING);
      int lb = c->local_count;
      for (int j = 0; j < decl_node->args.count; j++) {
        sv_ast_t *decl = decl_node->args.items[j];
        if (!decl || decl->type != N_VARDECL || !decl->left) continue;
        hoist_lexical_pattern(c, decl->left, is_const);
      }
      for (int j = lb; j < c->local_count; j++) {
        c->locals[j].is_tdz = true;
        set_local_inferred_type(c, j, SV_TI_UNKNOWN);
        int slot = j - c->param_locals;
        emit_op(c, OP_SET_LOCAL_UNDEF);
        emit_u16(c, (uint16_t)slot);
      }
    } else if (decl_node->type == N_IMPORT_DECL) {
      for (int j = 0; j < decl_node->args.count; j++) {
        sv_ast_t *spec = decl_node->args.items[j];
        if (!spec || spec->type != N_IMPORT_SPEC ||
            !spec->right || spec->right->type != N_IDENT)
          continue;
        ensure_local_at_depth(c, spec->right->str, spec->right->len, true, c->scope_depth);
      }
    } else if (decl_node->type == N_CLASS && decl_node->str) {
      int lb = c->local_count;
      ensure_local_at_depth(c, decl_node->str, decl_node->len, false, c->scope_depth);
      if (c->local_count > lb) {
        c->locals[c->local_count - 1].is_tdz = true;
        set_local_inferred_type(c, c->local_count - 1, SV_TI_UNKNOWN);
        int slot = (c->local_count - 1) - c->param_locals;
        emit_op(c, OP_SET_LOCAL_UNDEF);
        emit_u16(c, (uint16_t)slot);
      }
    } else if (decl_node->type == N_FUNC && decl_node->str && !(decl_node->flags & FN_ARROW)) {
      ensure_local_at_depth(c, decl_node->str, decl_node->len, false, c->scope_depth);
    }
    if (!c->is_strict && (decl_node->type == N_IF || decl_node->type == N_LABEL)) {
      sv_ast_list_t funcs = {0};
      annex_b_collect_funcs(decl_node, &funcs);
      for (int j = 0; j < funcs.count; j++) {
        sv_ast_t *fn = funcs.items[j];
        if (resolve_local(c, fn->str, fn->len) == -1)
          add_local(c, fn->str, fn->len, false, c->scope_depth);
      }
    }
  }
}

static void hoist_one_func(sv_compiler_t *c, sv_ast_t *node) {
  sv_func_t *fn = compile_function_body(c, node, c->mode);
  if (!fn) return;
  int idx = add_constant(c, mkval(T_NTARG, (uintptr_t)fn));
  emit_op(c, OP_CLOSURE);
  emit_u32(c, (uint32_t)idx);
  emit_set_function_name(c, node->str, node->len);
  if (is_repl_top_level(c)) {
    emit_atom_op(c, OP_PUT_GLOBAL, node->str, node->len);
  } else {
    int local = resolve_local(c, node->str, node->len);
    emit_put_local(c, local);
  }
}

static void hoist_func_decls(sv_compiler_t *c, sv_ast_list_t *stmts) {
  for (int i = 0; i < stmts->count; i++) {
    sv_ast_t *node = stmts->items[i];
    if (node && node->type == N_EXPORT && node->left)
      node = node->left;
    if (!node) continue;
    if (node->type == N_FUNC && node->str && !(node->flags & FN_ARROW)) {
      hoist_one_func(c, node);
    }
    if (!c->is_strict && (node->type == N_IF || node->type == N_LABEL)) {
      sv_ast_list_t funcs = {0};
      annex_b_collect_funcs(node, &funcs);
      for (int j = 0; j < funcs.count; j++)
        hoist_one_func(c, funcs.items[j]);
    }
  }
}

static uint8_t infer_expr_type(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return SV_TI_UNDEF;

  switch (node->type) {
    case N_NUMBER:   return SV_TI_NUM;
    case N_STRING:   return SV_TI_STR;
    case N_BOOL:     return SV_TI_BOOL;
    case N_NULL:     return SV_TI_NULL;
    case N_UNDEF:    return SV_TI_UNDEF;
    case N_ARRAY:    return SV_TI_ARR;
    case N_OBJECT:   return SV_TI_OBJ;
    case N_TEMPLATE: return SV_TI_STR;
    case N_TYPEOF:   return SV_TI_STR;
    case N_VOID:     return SV_TI_UNDEF;
    case N_NEW:      return SV_TI_OBJ;

    case N_IDENT: {
      int local = resolve_local(c, node->str, node->len);
      if (local >= 0) return get_local_inferred_type(c, local);
      return SV_TI_UNKNOWN;
    }

    case N_SEQUENCE:
      return infer_expr_type(c, node->right);

    case N_TERNARY: {
      uint8_t lt = infer_expr_type(c, node->left);
      uint8_t rhs_type = infer_expr_type(c, node->right);
      if (lt == rhs_type && sv_type_is_known(lt)) return lt;
      return SV_TI_UNKNOWN;
    }

    case N_UNARY: {
      uint8_t rhs_type = infer_expr_type(c, node->right);
      switch (node->op) {
        case TOK_UPLUS:
        case TOK_UMINUS:
          return sv_type_is_num(rhs_type) ? SV_TI_NUM : SV_TI_UNKNOWN;
        case TOK_NOT:
          return SV_TI_BOOL;
        default:
          return SV_TI_UNKNOWN;
      }
    }

    case N_BINARY: {
      uint8_t lt = infer_expr_type(c, node->left);
      uint8_t rhs_type = infer_expr_type(c, node->right);
      switch (node->op) {
        case TOK_PLUS:
          if (lt == SV_TI_NUM && rhs_type == SV_TI_NUM) return SV_TI_NUM;
          if (lt == SV_TI_STR && rhs_type == SV_TI_STR) return SV_TI_STR;
          return SV_TI_UNKNOWN;
        case TOK_MINUS:
        case TOK_MUL:
        case TOK_DIV:
          return (lt == SV_TI_NUM && rhs_type == SV_TI_NUM) ? SV_TI_NUM : SV_TI_UNKNOWN;
        case TOK_LT:
        case TOK_LE:
        case TOK_GT:
        case TOK_GE:
        case TOK_EQ:
        case TOK_NE:
        case TOK_SEQ:
        case TOK_SNE:
        case TOK_INSTANCEOF:
        case TOK_IN:
          return SV_TI_BOOL;
        case TOK_LAND:
        case TOK_LOR:
        case TOK_NULLISH:
          if (lt == rhs_type && sv_type_is_known(lt)) return lt;
          return SV_TI_UNKNOWN;
        default:
          return SV_TI_UNKNOWN;
      }
    }

    default:
      return SV_TI_UNKNOWN;
  }
}

static void compile_yield_star_expr(sv_compiler_t *c, sv_ast_t *node) {
  if (node->right) compile_expr(c, node->right);
  else emit_op(c, OP_UNDEF);

  int base_local = reserve_hidden_locals(c, 4);
  uint16_t base_slot = (uint16_t)(base_local - c->param_locals);

  emit_op(c, OP_YIELD_STAR_INIT);
  emit_u16(c, base_slot);

  emit_op(c, OP_UNDEF);
  emit_op(c, OP_YIELD_STAR_NEXT);
  emit_u16(c, base_slot);
}

void compile_expr(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) { emit_op(c, OP_UNDEF); return; }
  emit_srcpos(c, node);

  switch (node->type) {
    case N_NUMBER:
      emit_number(c, node->num);
      break;

    case N_STRING: {
      emit_constant(c, ast_string_const(c, node));
      break;
    }

    case N_BIGINT: {
      bool neg = false;
      const char *digits = node->str;
      uint32_t dlen = node->len;
      if (dlen > 0 && digits[0] == '-') {
        neg = true; digits++; dlen--;
      }
      if (dlen > 0 && digits[dlen - 1] == 'n') dlen--;
      ant_value_t bi = js_mkbigint(c->js, digits, dlen, neg);
      emit_constant(c, bi);
      break;
    }

    case N_BOOL:
      emit_op(c, node->num != 0.0 ? OP_TRUE : OP_FALSE);
      break;

    case N_NULL:
      emit_op(c, OP_NULL);
      break;

    case N_UNDEF:
      emit_op(c, OP_UNDEF);
      break;

    case N_THIS:
      emit_op(c, OP_THIS);
      break;

    case N_GLOBAL_THIS:
      emit_op(c, OP_GLOBAL);
      break;

    case N_NEW_TARGET: {
      static const char nt_name[] = "\x01new.target";
      int local = resolve_local(c, nt_name, sizeof(nt_name) - 1);
      if (local >= 0) {
        emit_get_local(c, local);
      } else {
        int upval = resolve_upvalue(c, nt_name, sizeof(nt_name) - 1);
        if (upval >= 0) {
          emit_op(c, OP_GET_UPVAL);
          emit_u16(c, (uint16_t)upval);
        } else {
          emit_op(c, OP_UNDEF);
        }
      }
      break;
    }

    case N_IDENT:
      emit_get_var(c, node->str, node->len);
      break;

    case N_BINARY:
      compile_binary(c, node);
      break;

    case N_UNARY:
      compile_unary(c, node);
      break;

    case N_UPDATE:
      compile_update(c, node);
      break;

    case N_ASSIGN:
      compile_assign(c, node);
      break;

    case N_TERNARY:
      compile_ternary(c, node);
      break;

    case N_CALL:
      compile_call(c, node);
      break;

    case N_NEW:
      compile_new(c, node);
      break;

    case N_MEMBER:
      compile_member(c, node);
      break;

    case N_OPTIONAL:
      compile_optional(c, node);
      break;

    case N_ARRAY:
      compile_array(c, node);
      break;

    case N_OBJECT:
      compile_object(c, node);
      break;

    case N_FUNC:
      compile_func_expr(c, node);
      break;

    case N_CLASS:
      compile_class(c, node);
      break;

    case N_SEQUENCE:
      compile_expr(c, node->left);
      emit_op(c, OP_POP);
      compile_expr(c, node->right);
      break;

    case N_TYPEOF:
      compile_typeof(c, node);
      break;

    case N_VOID:
      compile_expr(c, node->right);
      emit_op(c, OP_VOID);
      break;

    case N_DELETE:
      compile_delete(c, node);
      break;

    case N_SPREAD:
      compile_expr(c, node->right);
      break;

    case N_TEMPLATE:
      compile_template(c, node);
      break;

    case N_AWAIT:
      compile_expr(c, node->right);
      emit_op(c, OP_AWAIT);
      if (c->enclosing && !c->enclosing->enclosing)
        c->is_tla = true;
      break;

    case N_YIELD:
      if (node->flags) compile_yield_star_expr(c, node);
      else {
        if (node->right) compile_expr(c, node->right);
        else emit_op(c, OP_UNDEF);
        emit_op(c, OP_YIELD);
      }
      break;

    case N_THROW:
      compile_expr(c, node->right);
      emit_op(c, OP_THROW);
      break;

    case N_TAGGED_TEMPLATE: {
      compile_expr(c, node->left);
      sv_ast_t *tpl = node->right;
      int n = tpl->args.count;
      int n_strings = 0, n_exprs = 0;
      for (int i = 0; i < n; i++) {
        if (is_template_segment(tpl->args.items[i])) n_strings++;
        else n_exprs++;
      }
      int cache_idx = add_constant(c, js_mkundef());
      emit_op(c, OP_CONST);
      emit_u32(c, (uint32_t)cache_idx);
      int skip_jump = emit_jump(c, OP_JMP_TRUE_PEEK);
      emit_op(c, OP_POP);
      for (int i = 0; i < n; i++) {
        sv_ast_t *item = tpl->args.items[i];
        if (!is_template_segment(item)) continue;
        if (is_invalid_cooked_string(item))
          emit_op(c, OP_UNDEF);
        else emit_constant(c, ast_string_const(c, item));
      }
      emit_op(c, OP_ARRAY);
      emit_u16(c, (uint16_t)n_strings);
      for (int i = 0; i < n; i++) {
        sv_ast_t *item = tpl->args.items[i];
        if (!is_template_segment(item)) continue;
        const char *raw = item->aux ? item->aux : item->str;
        uint32_t raw_len = item->aux ? item->aux_len : item->len;
        emit_constant(c, js_mkstr_permanent(c->js, raw ? raw : "", raw_len));
      }
      emit_op(c, OP_ARRAY);
      emit_u16(c, (uint16_t)n_strings);
      emit_atom_op(c, OP_GET_GLOBAL, "Object", 6);
      emit_atom_op(c, OP_GET_FIELD2, "freeze", 6);
      emit_op(c, OP_ROT3L);
      emit_op(c, OP_CALL_METHOD);
      emit_u16(c, 1);
      emit_atom_op(c, OP_DEFINE_FIELD, "raw", 3);
      emit_atom_op(c, OP_GET_GLOBAL, "Object", 6);
      emit_atom_op(c, OP_GET_FIELD2, "freeze", 6);
      emit_op(c, OP_ROT3L);
      emit_op(c, OP_CALL_METHOD);
      emit_u16(c, 1);
      emit_op(c, OP_DUP);
      emit_op(c, OP_PUT_CONST);
      emit_u32(c, (uint32_t)cache_idx);
      patch_jump(c, skip_jump);
      for (int i = 0; i < n; i++) {
        sv_ast_t *item = tpl->args.items[i];
        if (is_template_segment(item)) continue;
        compile_expr(c, item);
      }
      emit_op(c, OP_CALL);
      emit_u16(c, (uint16_t)(1 + n_exprs));
      break;
    }

    case N_IMPORT:
      compile_expr(c, node->right);
      if (has_module_import_binding(c)) {
        emit_get_module_import_binding(c);
        emit_op(c, OP_SWAP);
        emit_op(c, OP_CALL);
        emit_u16(c, 1);
      } else emit_op(c, OP_IMPORT);
      break;

    case N_REGEXP:
      emit_constant(c, js_mkstr_permanent(c->js, node->str ? node->str : "", node->len));
      emit_constant(c, js_mkstr_permanent(c->js, node->aux ? node->aux : "", node->aux_len));
      emit_op(c, OP_REGEXP);
      break;

    default:
      emit_op(c, OP_UNDEF);
      break;
  }
}

void compile_binary(sv_compiler_t *c, sv_ast_t *node) {
  uint8_t op = node->op;

  if (op == TOK_LAND) {
    compile_expr(c, node->left);
    int jump = emit_jump(c, OP_JMP_FALSE_PEEK);
    emit_op(c, OP_POP);
    compile_expr(c, node->right);
    patch_jump(c, jump);
    return;
  }
  
  if (op == TOK_LOR) {
    compile_expr(c, node->left);
    int jump = emit_jump(c, OP_JMP_TRUE_PEEK);
    emit_op(c, OP_POP);
    compile_expr(c, node->right);
    patch_jump(c, jump);
    return;
  }
  
  if (op == TOK_NULLISH) {
    compile_expr(c, node->left);
    int jump = emit_jump(c, OP_JMP_NOT_NULLISH);
    emit_op(c, OP_POP);
    compile_expr(c, node->right);
    patch_jump(c, jump);
    return;
  }

  if (op == TOK_IN && node->left->type == N_IDENT &&
      node->left->len > 0 && node->left->str[0] == '#') {
    emit_constant(c, js_mkstr_permanent(c->js, node->left->str, node->left->len));
    compile_expr(c, node->right);
    emit_op(c, OP_IN);
    return;
  }

  uint8_t left_type = SV_TI_UNKNOWN;
  uint8_t right_type = SV_TI_UNKNOWN;
  if (op == TOK_PLUS || op == TOK_MINUS || op == TOK_MUL || op == TOK_DIV) {
    left_type = infer_expr_type(c, node->left);
    right_type = infer_expr_type(c, node->right);
  }

  compile_expr(c, node->left);
  compile_expr(c, node->right);

  switch (op) {
    case TOK_PLUS:
      emit_op(c, (left_type == SV_TI_NUM && right_type == SV_TI_NUM) ? OP_ADD_NUM : OP_ADD);
      break;
    case TOK_MINUS:
      emit_op(c, (left_type == SV_TI_NUM && right_type == SV_TI_NUM) ? OP_SUB_NUM : OP_SUB);
      break;
    case TOK_MUL:
      emit_op(c, (left_type == SV_TI_NUM && right_type == SV_TI_NUM) ? OP_MUL_NUM : OP_MUL);
      break;
    case TOK_DIV:
      emit_op(c, (left_type == SV_TI_NUM && right_type == SV_TI_NUM) ? OP_DIV_NUM : OP_DIV);
      break;
    case TOK_REM:        emit_op(c, OP_MOD); break;
    case TOK_EXP:        emit_op(c, OP_EXP); break;
    case TOK_LT:         emit_op(c, OP_LT);  break;
    case TOK_LE:         emit_op(c, OP_LE);  break;
    case TOK_GT:         emit_op(c, OP_GT);  break;
    case TOK_GE:         emit_op(c, OP_GE);  break;
    case TOK_EQ:         emit_op(c, OP_EQ);  break;
    case TOK_NE:         emit_op(c, OP_NE);  break;
    case TOK_SEQ:        emit_op(c, OP_SEQ); break;
    case TOK_SNE:        emit_op(c, OP_SNE); break;
    case TOK_AND:        emit_op(c, OP_BAND); break;
    case TOK_OR:         emit_op(c, OP_BOR);  break;
    case TOK_XOR:        emit_op(c, OP_BXOR); break;
    case TOK_SHL:        emit_op(c, OP_SHL);  break;
    case TOK_SHR:        emit_op(c, OP_SHR);  break;
    case TOK_ZSHR:       emit_op(c, OP_USHR); break;
    case TOK_INSTANCEOF:
      emit_op(c, OP_INSTANCEOF);
      emit_u16(c, alloc_ic_idx(c));
      break;
    case TOK_IN:         emit_op(c, OP_IN);  break;
    default:             emit_op(c, OP_UNDEF); break;
  }
}

void compile_unary(sv_compiler_t *c, sv_ast_t *node) {
  compile_expr(c, node->right);
  switch (node->op) {
    case TOK_NOT:    emit_op(c, OP_NOT);   break;
    case TOK_TILDA:  emit_op(c, OP_BNOT);  break;
    case TOK_UPLUS:  emit_op(c, OP_UPLUS); break;
    case TOK_UMINUS: emit_op(c, OP_NEG);   break;
    default: break;
  }
}


void compile_update(sv_compiler_t *c, sv_ast_t *node) {
  bool prefix = (node->flags & 1);
  bool is_inc = (node->op == TOK_POSTINC);
  sv_ast_t *target = node->right;

  if (target->type == N_IDENT) {
    if (prefix) {
      emit_get_var(c, target->str, target->len);
      emit_op(c, is_inc ? OP_INC : OP_DEC);
      emit_set_var(c, target->str, target->len, true);
    } else {
      emit_get_var(c, target->str, target->len);
      emit_op(c, is_inc ? OP_POST_INC : OP_POST_DEC);
      emit_set_var(c, target->str, target->len, false);
    }
  } else if (target->type == N_MEMBER && !(target->flags & 1)) {
    compile_expr(c, target->left);
    emit_op(c, OP_DUP);
    int atom = add_atom(c, target->right->str, target->right->len);
    emit_atom_idx_op(c, OP_GET_FIELD, (uint32_t)atom);
    if (prefix) {
      emit_op(c, is_inc ? OP_INC : OP_DEC);
      emit_op(c, OP_INSERT2);
      emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom);
    } else {
      emit_op(c, is_inc ? OP_POST_INC : OP_POST_DEC);
      emit_op(c, OP_SWAP_UNDER);
      emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom);
    }
  } else if (target->type == N_MEMBER && (target->flags & 1)) {
    compile_expr(c, target->left);
    compile_expr(c, target->right);
    emit_op(c, OP_DUP2);
    emit_op(c, OP_GET_ELEM);
    if (prefix) {
      emit_op(c, is_inc ? OP_INC : OP_DEC);
      emit_op(c, OP_INSERT3);
      emit_op(c, OP_PUT_ELEM);
    } else {
      emit_op(c, is_inc ? OP_POST_INC : OP_POST_DEC);
      emit_op(c, OP_ROT4_UNDER);
      emit_op(c, OP_PUT_ELEM);
    }
  } else {
    emit_op(c, OP_UNDEF);
  }
}

void compile_assign(sv_compiler_t *c, sv_ast_t *node) {
  sv_ast_t *target = node->left;
  uint8_t op = node->op;
  int append_local = -1;
  uint16_t append_slot = 0;
  sv_ast_t *append_rhs = NULL;
  
  bool can_append_builder = match_self_append_local(
    c, node, &append_local, 
    &append_slot, &append_rhs
  );

  if (op == TOK_ASSIGN) {
    if (target->type == N_MEMBER && !(target->flags & 1)) {
      int atom = add_atom(c, target->right->str, target->right->len);
      compile_expr(c, target->left);
      compile_expr(c, node->right);
      emit_op(c, OP_INSERT2);
      emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom);
      return;
    }
    
    if (target->type == N_MEMBER && (target->flags & 1)) {
      compile_expr(c, target->left);
      compile_expr(c, target->right);
      compile_expr(c, node->right);
      emit_op(c, OP_INSERT3);
      emit_op(c, OP_PUT_ELEM);
      return;
    }
    
    if (can_append_builder) {
      if (is_self_append_inplace_safe_expr(c, append_rhs)) {
        compile_expr(c, append_rhs);
        emit_slot_op(c, OP_STR_APPEND_LOCAL, append_slot);
      } else {
        emit_slot_op(c, OP_GET_SLOT_RAW, append_slot);
        compile_expr(c, append_rhs);
        emit_slot_op(c, OP_STR_ALC_SNAPSHOT, append_slot);
      }
      emit_get_var(c, target->str, target->len);
      set_local_inferred_type(c, append_local, SV_TI_UNKNOWN);
      return;
    }

    compile_expr(c, node->right);
    compile_lhs_set(c, target, true);
    return;
  }

  if (target->type == N_IDENT) {
    int lhs_local = resolve_local(c, target->str, target->len);
    uint8_t lhs_type = (lhs_local >= 0) ? get_local_inferred_type(c, lhs_local) : SV_TI_UNKNOWN;
    uint8_t rhs_type = infer_expr_type(c, node->right);

    if (can_append_builder) {
      if (is_self_append_inplace_safe_expr(c, append_rhs)) {
        compile_expr(c, append_rhs);
        emit_slot_op(c, OP_STR_APPEND_LOCAL, append_slot);
      } else {
        emit_slot_op(c, OP_GET_SLOT_RAW, append_slot);
        compile_expr(c, append_rhs);
        emit_slot_op(c, OP_STR_ALC_SNAPSHOT, append_slot);
      }
      emit_get_var(c, target->str, target->len);
      set_local_inferred_type(c, append_local, SV_TI_UNKNOWN);
      return;
    }

    if (op == TOK_PLUS_ASSIGN) {
      int slot = resolve_local_slot(c, target->str, target->len);
      if (slot >= 0 && !c->locals[slot + c->param_locals].is_const) {
        sv_ast_t *rhs = node->right;
        bool rhs_pure = rhs && (
          rhs->type == N_NUMBER || rhs->type == N_STRING ||
          rhs->type == N_BOOL || rhs->type == N_NULL ||
          rhs->type == N_UNDEF || rhs->type == N_IDENT
        );
        if (rhs_pure) {
          compile_expr(c, rhs);
          emit_op(c, OP_ADD_LOCAL);
          emit(c, (uint8_t)slot);
          emit_get_local(c, c->param_locals + slot);
          set_local_inferred_type(c, c->param_locals + slot, SV_TI_UNKNOWN);
          return;
        }
      }
    }

    if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN ||
        op == TOK_NULLISH_ASSIGN) {
      emit_get_var(c, target->str, target->len);
      int skip = emit_jump(c,
        op == TOK_LOR_ASSIGN ? OP_JMP_TRUE_PEEK :
        op == TOK_LAND_ASSIGN ? OP_JMP_FALSE_PEEK : OP_JMP_NOT_NULLISH);
      emit_op(c, OP_POP);
      compile_expr(c, node->right);
      emit_set_var(c, target->str, target->len, true);
      patch_jump(c, skip);
      return;
    }

    emit_get_var(c, target->str, target->len);
    compile_expr(c, node->right);
    switch (op) {
      case TOK_PLUS_ASSIGN:
        emit_op(c, (lhs_type == SV_TI_NUM && rhs_type == SV_TI_NUM) ? OP_ADD_NUM : OP_ADD);
        break;
      case TOK_MINUS_ASSIGN:
        emit_op(c, (lhs_type == SV_TI_NUM && rhs_type == SV_TI_NUM) ? OP_SUB_NUM : OP_SUB);
        break;
      case TOK_MUL_ASSIGN:
        emit_op(c, (lhs_type == SV_TI_NUM && rhs_type == SV_TI_NUM) ? OP_MUL_NUM : OP_MUL);
        break;
      case TOK_DIV_ASSIGN:
        emit_op(c, (lhs_type == SV_TI_NUM && rhs_type == SV_TI_NUM) ? OP_DIV_NUM : OP_DIV);
        break;
      case TOK_REM_ASSIGN:     emit_op(c, OP_MOD); break;
      case TOK_SHL_ASSIGN:     emit_op(c, OP_SHL); break;
      case TOK_SHR_ASSIGN:     emit_op(c, OP_SHR); break;
      case TOK_ZSHR_ASSIGN:    emit_op(c, OP_USHR); break;
      case TOK_AND_ASSIGN:     emit_op(c, OP_BAND); break;
      case TOK_XOR_ASSIGN:     emit_op(c, OP_BXOR); break;
      case TOK_OR_ASSIGN:      emit_op(c, OP_BOR); break;
      case TOK_EXP_ASSIGN:    emit_op(c, OP_EXP); break;
      default: break;
    }
    emit_set_var(c, target->str, target->len, true);
  } else if (target->type == N_MEMBER && !(target->flags & 1)) {
    int atom = add_atom(c, target->right->str, target->right->len);

    if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN ||
        op == TOK_NULLISH_ASSIGN) {
      compile_expr(c, target->left);
      emit_op(c, OP_DUP);
      emit_atom_idx_op(c, OP_GET_FIELD, (uint32_t)atom);
      int skip = emit_jump(c,
        op == TOK_LOR_ASSIGN ? OP_JMP_TRUE_PEEK :
        op == TOK_LAND_ASSIGN ? OP_JMP_FALSE_PEEK : OP_JMP_NOT_NULLISH);
      emit_op(c, OP_POP);
      compile_expr(c, node->right);
      emit_op(c, OP_INSERT2);
      emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom);
      int end = emit_jump(c, OP_JMP);
      patch_jump(c, skip);
      emit_op(c, OP_NIP);
      patch_jump(c, end);
      return;
    }

    compile_expr(c, target->left);
    emit_op(c, OP_DUP);
    emit_atom_idx_op(c, OP_GET_FIELD, (uint32_t)atom);
    compile_expr(c, node->right);
    switch (op) {
      case TOK_PLUS_ASSIGN:  emit_op(c, OP_ADD); break;
      case TOK_MINUS_ASSIGN: emit_op(c, OP_SUB); break;
      case TOK_MUL_ASSIGN:   emit_op(c, OP_MUL); break;
      case TOK_DIV_ASSIGN:   emit_op(c, OP_DIV); break;
      case TOK_REM_ASSIGN:   emit_op(c, OP_MOD); break;
      case TOK_SHL_ASSIGN:   emit_op(c, OP_SHL); break;
      case TOK_SHR_ASSIGN:   emit_op(c, OP_SHR); break;
      case TOK_ZSHR_ASSIGN:  emit_op(c, OP_USHR); break;
      case TOK_AND_ASSIGN:   emit_op(c, OP_BAND); break;
      case TOK_XOR_ASSIGN:   emit_op(c, OP_BXOR); break;
      case TOK_OR_ASSIGN:    emit_op(c, OP_BOR); break;
      case TOK_EXP_ASSIGN:  emit_op(c, OP_EXP); break;
      default: break;
    }
    emit_op(c, OP_INSERT2);
    emit_atom_idx_op(c, OP_PUT_FIELD, (uint32_t)atom);
  } else if (target->type == N_MEMBER && (target->flags & 1)) {

    if (op == TOK_LOR_ASSIGN || op == TOK_LAND_ASSIGN ||
        op == TOK_NULLISH_ASSIGN) {
      compile_expr(c, target->left);
      compile_expr(c, target->right);
      emit_op(c, OP_DUP2);
      emit_op(c, OP_GET_ELEM);
      int skip = emit_jump(c,
        op == TOK_LOR_ASSIGN ? OP_JMP_TRUE_PEEK :
        op == TOK_LAND_ASSIGN ? OP_JMP_FALSE_PEEK : OP_JMP_NOT_NULLISH);
      emit_op(c, OP_POP);
      compile_expr(c, node->right);
      emit_op(c, OP_INSERT3);
      emit_op(c, OP_PUT_ELEM);
      int end = emit_jump(c, OP_JMP);
      patch_jump(c, skip);
      emit_op(c, OP_NIP2);
      patch_jump(c, end);
      return;
    }

    compile_expr(c, target->left);
    compile_expr(c, target->right);
    emit_op(c, OP_DUP2);
    emit_op(c, OP_GET_ELEM);
    compile_expr(c, node->right);
    switch (op) {
      case TOK_PLUS_ASSIGN:  emit_op(c, OP_ADD); break;
      case TOK_MINUS_ASSIGN: emit_op(c, OP_SUB); break;
      case TOK_MUL_ASSIGN:   emit_op(c, OP_MUL); break;
      case TOK_DIV_ASSIGN:   emit_op(c, OP_DIV); break;
      case TOK_REM_ASSIGN:   emit_op(c, OP_MOD); break;
      case TOK_SHL_ASSIGN:   emit_op(c, OP_SHL); break;
      case TOK_SHR_ASSIGN:   emit_op(c, OP_SHR); break;
      case TOK_ZSHR_ASSIGN:  emit_op(c, OP_USHR); break;
      case TOK_AND_ASSIGN:   emit_op(c, OP_BAND); break;
      case TOK_XOR_ASSIGN:   emit_op(c, OP_BXOR); break;
      case TOK_OR_ASSIGN:    emit_op(c, OP_BOR); break;
      case TOK_EXP_ASSIGN:  emit_op(c, OP_EXP); break;
      default: break;
    }
    emit_op(c, OP_INSERT3);
    emit_op(c, OP_PUT_ELEM);
  } else {
    compile_expr(c, node->right);
  }
}

void compile_lhs_set(sv_compiler_t *c, sv_ast_t *target, bool keep) {
  if (target->type == N_IDENT) {
    emit_set_var(c, target->str, target->len, keep);
  } else if (target->type == N_MEMBER && !(target->flags & 1)) {
    if (keep) emit_op(c, OP_DUP);
    compile_expr(c, target->left);
    emit_op(c, OP_SWAP);
    emit_atom_op(c, OP_PUT_FIELD, target->right->str, target->right->len);
  } else if (target->type == N_MEMBER && (target->flags & 1)) {
    if (keep) emit_op(c, OP_DUP);
    compile_expr(c, target->left);
    compile_expr(c, target->right);
    emit_op(c, OP_ROT3L);
    emit_op(c, OP_PUT_ELEM);
  } else if (target->type == N_ARRAY_PAT || target->type == N_ARRAY) {
    compile_array_destructure(c, target, keep);
  } else if (target->type == N_OBJECT_PAT || target->type == N_OBJECT) {
    compile_object_destructure(c, target, keep);
  }
}

void compile_ternary(sv_compiler_t *c, sv_ast_t *node) {
  compile_truthy_test_expr(c, node->cond);
  int else_jump = emit_jump(c, OP_JMP_FALSE);
  compile_expr(c, node->left);
  int end_jump = emit_jump(c, OP_JMP);
  patch_jump(c, else_jump);
  compile_expr(c, node->right);
  patch_jump(c, end_jump);
}

void compile_typeof(sv_compiler_t *c, sv_ast_t *node) {
  sv_ast_t *arg = node->right;
  if (arg->type == N_IDENT) {
    int local = resolve_local(c, arg->str, arg->len);
    if (local != -1) {
      uint8_t inferred = get_local_inferred_type(c, local);
      const char *known = typeof_name_for_type(inferred);
      if (known) {
        emit_constant(c, js_mkstr_permanent(c->js, known, strlen(known)));
        return;
      }
      emit_get_var(c, arg->str, arg->len);
    } else {
      int upval = resolve_upvalue(c, arg->str, arg->len);
      if (upval != -1) {
        emit_op(c, OP_GET_UPVAL);
        emit_u16(c, (uint16_t)upval);
      } else if (
          has_implicit_arguments_obj(c) &&
          is_ident_str(arg->str, arg->len, "arguments", 9)
        ) {
        if (c->strict_args_local >= 0) {
          emit_get_local(c, c->strict_args_local);
        } else {
          emit_op(c, OP_SPECIAL_OBJ);
          emit(c, 0);
        }
      } else emit_atom_op(c, OP_GET_GLOBAL_UNDEF, arg->str, arg->len);
    }
  } else compile_expr(c, arg);
  emit_op(c, OP_TYPEOF);
}

static bool sv_node_has_optional_base(sv_ast_t *n) {
  while (n) {
    if (n->type == N_OPTIONAL) return true;
    if (n->type == N_MEMBER || n->type == N_CALL) n = n->left;
    else break;
  }
  return false;
}

static void compile_delete_optional(sv_compiler_t *c, sv_ast_t *arg) {
  compile_expr(c, arg->left);
  int ok_jump = emit_jump(c, OP_JMP_NOT_NULLISH);
  emit_op(c, OP_POP);
  emit_op(c, OP_TRUE);
  int end_jump = emit_jump(c, OP_JMP);
  patch_jump(c, ok_jump);
  if (arg->flags & 1) {
    compile_expr(c, arg->right);
  } else {
    ant_value_t key = js_mkstr_permanent(c->js, arg->right->str, arg->right->len);
    emit_constant(c, key);
  }
  emit_op(c, OP_DELETE);
  patch_jump(c, end_jump);
}

void compile_delete(sv_compiler_t *c, sv_ast_t *node) {
  sv_ast_t *arg = node->right;
  if (arg->type == N_OPTIONAL) {
    compile_delete_optional(c, arg);
  } else if (arg->type == N_MEMBER && sv_node_has_optional_base(arg->left)) {
    compile_delete_optional(c, arg);
  } else if (arg->type == N_MEMBER && !(arg->flags & 1)) {
    compile_expr(c, arg->left);
    ant_value_t key = js_mkstr_permanent(c->js, arg->right->str, arg->right->len);
    emit_constant(c, key);
    emit_op(c, OP_DELETE);
  } else if (arg->type == N_MEMBER && (arg->flags & 1)) {
    compile_expr(c, arg->left);
    compile_expr(c, arg->right);
    emit_op(c, OP_DELETE);
  } else if (arg->type == N_IDENT) {
    emit_atom_op(c, OP_DELETE_VAR, arg->str, arg->len);
  } else {
    compile_expr(c, arg);
    emit_op(c, OP_POP);
    emit_op(c, OP_TRUE);
  }
}

void compile_template(sv_compiler_t *c, sv_ast_t *node) {
  int n = node->args.count;
  if (n == 0) {
    emit_constant(c, js_mkstr_permanent(c->js, "", 0));
    return;
  }
  for (int i = 0; i < n; i++) {
    sv_ast_t *item = node->args.items[i];
    if (is_invalid_cooked_string(item)) {
      static const char msg[] = "Invalid or unexpected token";
      int atom = add_atom(c, msg, sizeof(msg) - 1);
      emit_op(c, OP_THROW_ERROR);
      emit_u32(c, (uint32_t)atom);
      emit(c, (uint8_t)JS_ERR_SYNTAX);
      return;
    }
  }
  compile_expr(c, node->args.items[0]);
  if (!is_template_segment(node->args.items[0]))
    emit_op(c, OP_TO_PROPKEY);
  for (int i = 1; i < n; i++) {
    compile_expr(c, node->args.items[i]);
    if (!is_template_segment(node->args.items[i]))
      emit_op(c, OP_TO_PROPKEY);
    emit_op(c, OP_ADD);
  }
}

static bool call_has_spread_arg(const sv_ast_t *node) {
  if (!node) return false;
  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *arg = node->args.items[i];
    if (arg && arg->type == N_SPREAD) return true;
  }
  return false;
}

static void compile_push_arg_to_array(sv_compiler_t *c, sv_ast_t *arg) {
  emit_op(c, OP_DUP);
  emit_op(c, OP_DUP);
  emit_atom_op(c, OP_GET_FIELD, "push", 4);
  compile_expr(c, arg);
  emit_op(c, OP_CALL_METHOD);
  emit_u16(c, 1);
  emit_op(c, OP_POP);
}

static void compile_concat_spread_to_array(sv_compiler_t *c, sv_ast_t *spread_arg) {
  compile_expr(c, spread_arg->right);
  emit_op(c, OP_SPREAD);
}

static void compile_call_args_array(sv_compiler_t *c, sv_ast_t *call_node) {
  emit_op(c, OP_ARRAY);
  emit_u16(c, 0);
  for (int i = 0; i < call_node->args.count; i++) {
    sv_ast_t *arg = call_node->args.items[i];
    if (arg && arg->type == N_SPREAD) compile_concat_spread_to_array(c, arg);
    else compile_push_arg_to_array(c, arg);
  }
}

typedef enum {
  SV_CALL_DIRECT = 0,
  SV_CALL_METHOD = 1,
  SV_CALL_SUPER = 2,
} sv_call_kind_t;

static inline bool sv_call_kind_has_receiver(sv_call_kind_t kind) {
  return kind == SV_CALL_METHOD || kind == SV_CALL_SUPER;
}

static void compile_receiver_property_get(sv_compiler_t *c, sv_ast_t *node) {
  emit_op(c, OP_DUP);
  if (node->flags & 1) {
    compile_expr(c, node->right);
    emit_op(c, OP_GET_ELEM);
  } else {
    emit_srcpos(c, node->right);
    emit_atom_op(c, OP_GET_FIELD, node->right->str, node->right->len);
  }
}

static void compile_call_emit_invoke(
  sv_compiler_t *c, sv_ast_t *node,
  sv_call_kind_t kind, bool has_spread
) {
  int argc = node->args.count;
  if (has_spread) {
    if (sv_call_kind_has_receiver(kind)) emit_op(c, OP_SWAP);
    else emit_op(c, OP_GLOBAL);
    compile_call_args_array(c, node);
    emit_op(c, kind == SV_CALL_SUPER ? OP_SUPER_APPLY : OP_APPLY);
    emit_u16(c, 1);
    return;
  }

  for (int i = 0; i < argc; i++)
    compile_expr(c, node->args.items[i]);
  emit_op(c, sv_call_kind_has_receiver(kind) ? OP_CALL_METHOD : OP_CALL);
  emit_u16(c, (uint16_t)argc);
}

static sv_call_kind_t compile_call_setup_non_optional(sv_compiler_t *c, sv_ast_t *callee) {
  if (is_ident_name(callee, "super")) {
    emit_op(c, OP_THIS);
    emit_get_var(c, "super", 5);
    return SV_CALL_SUPER;
  }

  if (callee->type == N_MEMBER && is_ident_name(callee->left, "super")) {
    emit_op(c, OP_THIS);
    emit_op(c, OP_THIS);
    emit_get_var(c, "super", 5);
    if (callee->flags & 1)
      compile_expr(c, callee->right);
    else
      emit_constant(c, js_mkstr_permanent(c->js, callee->right->str, callee->right->len));
    emit_op(c, OP_GET_SUPER_VAL);
    return SV_CALL_METHOD;
  }

  if (callee->type == N_MEMBER) {
    compile_expr(c, callee->left);
    compile_receiver_property_get(c, callee);
    return SV_CALL_METHOD;
  }

  compile_expr(c, callee);
  return SV_CALL_DIRECT;
}

static bool compile_call_is_proto_intrinsic(
  sv_compiler_t *c, sv_ast_t *node, bool has_spread
) {
  if (!node || has_spread || node->args.count != 1) return false;
  sv_ast_t *callee = node->left;
  if (!callee || callee->type != N_MEMBER) return false;
  if ((callee->flags & 1) || !callee->right || !callee->right->str) return false;
  if (is_ident_name(callee->left, "super")) return false;
  if (!is_ident_str(callee->right->str, callee->right->len, "isPrototypeOf", 13))
    return false;

  compile_expr(c, callee->left);
  compile_receiver_property_get(c, callee);
  compile_expr(c, node->args.items[0]);
  emit_op(c, OP_CALL_IS_PROTO);
  emit_u16(c, alloc_ic_idx(c));
  return true;
}

static bool compile_call_array_includes_intrinsic(
  sv_compiler_t *c, sv_ast_t *node, bool has_spread
) {
  if (!node || has_spread || node->args.count > UINT16_MAX) return false;
  sv_ast_t *callee = node->left;
  
  if (!callee || callee->type != N_MEMBER) return false;
  if ((callee->flags & 1) || !callee->right || !callee->right->str) return false;
  if (is_ident_name(callee->left, "super")) return false;
  if (!is_ident_str(callee->right->str, callee->right->len, "includes", 8))
    return false;

  compile_expr(c, callee->left);
  compile_receiver_property_get(c, callee);
  for (int i = 0; i < node->args.count; i++)
    compile_expr(c, node->args.items[i]);
  emit_op(c, OP_CALL_ARRAY_INCLUDES);
  emit_u16(c, (uint16_t)node->args.count);
  
  return true;
}

static bool compile_regexp_exec_truthy_intrinsic(
  sv_compiler_t *c, sv_ast_t *node
) {
  if (!node || node->type != N_CALL || call_has_spread_arg(node) || node->args.count != 1)
    return false;
    
  sv_ast_t *callee = node->left;
  if (!callee || callee->type != N_MEMBER) return false;
  if ((callee->flags & 1) || !callee->right || !callee->right->str) return false;
  if (is_ident_name(callee->left, "super")) return false;
  if (!is_ident_str(callee->right->str, callee->right->len, "exec", 4))
    return false;

  compile_expr(c, callee->left);
  compile_receiver_property_get(c, callee);
  compile_expr(c, node->args.items[0]);
  emit_op(c, OP_RE_EXEC_TRUTHY);
  
  return true;
}

static void compile_truthy_test_expr(sv_compiler_t *c, sv_ast_t *node) {
  if (compile_regexp_exec_truthy_intrinsic(c, node)) return;
  compile_expr(c, node);
}

static void compile_optional_call_after_setup(
  sv_compiler_t *c, sv_ast_t *call_node,
  sv_call_kind_t kind, bool has_spread
) {
  emit_op(c, OP_DUP);
  emit_op(c, OP_IS_UNDEF_OR_NULL);
  int j_do_call = emit_jump(c, OP_JMP_FALSE);
  emit_op(c, OP_POP);
  if (sv_call_kind_has_receiver(kind))
    emit_op(c, OP_POP);
  emit_op(c, OP_UNDEF);
  int j_end = emit_jump(c, OP_JMP);
  patch_jump(c, j_do_call);
  compile_call_emit_invoke(c, call_node, kind, has_spread);
  patch_jump(c, j_end);
}

static void compile_call_optional(
  sv_compiler_t *c, sv_ast_t *node,
  sv_ast_t *opt_callee, bool has_spread
) {
  if (opt_callee->right) {
    compile_expr(c, opt_callee->left);
    emit_op(c, OP_DUP);
    emit_op(c, OP_IS_UNDEF_OR_NULL);
    int j_have_obj = emit_jump(c, OP_JMP_FALSE);
    emit_op(c, OP_POP);
    emit_op(c, OP_UNDEF);
    int j_end = emit_jump(c, OP_JMP);
    patch_jump(c, j_have_obj);

    compile_receiver_property_get(c, opt_callee);
    compile_call_emit_invoke(c, node, SV_CALL_METHOD, has_spread);
    patch_jump(c, j_end);
    
    return;
  }

  sv_ast_t *target = opt_callee->left;
  if (target && target->type == N_OPTIONAL && target->right) {
    compile_expr(c, target->left);
    emit_op(c, OP_DUP);
    emit_op(c, OP_IS_UNDEF_OR_NULL);
    int j_have_obj = emit_jump(c, OP_JMP_FALSE);
    emit_op(c, OP_POP);
    emit_op(c, OP_UNDEF);
    int j_end = emit_jump(c, OP_JMP);
    patch_jump(c, j_have_obj);

    compile_receiver_property_get(c, target);
    compile_optional_call_after_setup(c, node, SV_CALL_METHOD, has_spread);
    patch_jump(c, j_end);
    return;
  }

  sv_call_kind_t kind = compile_call_setup_non_optional(c, target);
  compile_optional_call_after_setup(c, node, kind, has_spread);
}

void compile_call(sv_compiler_t *c, sv_ast_t *node) {
  sv_ast_t *callee = node->left;
  bool has_spread = call_has_spread_arg(node);

  if (callee->type == N_OPTIONAL) {
    compile_call_optional(c, node, callee, has_spread);
    return;
  }

  if (compile_call_is_proto_intrinsic(c, node, has_spread))
    return;

  if (compile_call_array_includes_intrinsic(c, node, has_spread))
    return;

  if (
    !has_spread && node->args.count >= 2 &&
    callee->type == N_MEMBER &&
    is_ident_name(callee->left, "Ant") &&
    resolve_local(c, "Ant", 3) == -1 &&
    callee->right && callee->right->type == N_IDENT &&
    callee->right->len == 5 && memcmp(callee->right->str, "match", 5) == 0 &&
    node->args.items[1]->type == N_OBJECT
  ) {
    sv_ast_t *obj = node->args.items[1];
    sv_ast_t *param = sv_ast_new(N_IDENT);
    
    param->str = "$"; param->len = 1;
    sv_ast_t *arrow = sv_ast_new(N_FUNC);
    
    arrow->flags = FN_ARROW;
    arrow->body = obj;
    arrow->line = obj->line; arrow->col = obj->col;
    arrow->src_off = obj->src_off; arrow->src_end = obj->src_end;
    
    sv_ast_list_push(&arrow->args, param);
    node->args.items[1] = arrow;
  }

  if (!has_spread && is_ident_name(callee, "eval")) {
    if (node->args.count > 0)
      compile_expr(c, node->args.items[0]);
    else
      emit_op(c, OP_UNDEF);
    for (int i = 1; i < node->args.count; i++) {
      compile_expr(c, node->args.items[i]);
      emit_op(c, OP_POP);
    }
    
    emit_op(c, OP_EVAL);
    emit_u32(c, 0);
    
    return;
  }

  if (callee->type == N_MEMBER && sv_node_has_optional_base(callee->left)) {
    compile_expr(c, callee->left);
    int ok_jump  = emit_jump(c, OP_JMP_NOT_NULLISH);
    emit_op(c, OP_POP);
    emit_op(c, OP_UNDEF);
    int end_jump = emit_jump(c, OP_JMP);
    patch_jump(c, ok_jump);
    compile_receiver_property_get(c, callee);
    compile_call_emit_invoke(c, node, SV_CALL_METHOD, has_spread);
    patch_jump(c, end_jump);
    return;
  }

  sv_call_kind_t kind = compile_call_setup_non_optional(c, callee);
  compile_call_emit_invoke(c, node, kind, has_spread);
}

void compile_new(sv_compiler_t *c, sv_ast_t *node) {
  compile_expr(c, node->left);
  emit_op(c, OP_DUP);
  if (call_has_spread_arg(node)) {
    compile_call_args_array(c, node);
    emit_op(c, OP_NEW_APPLY);
    emit_u16(c, 1);
  } else {
    int argc = node->args.count;
    for (int i = 0; i < argc; i++)
      compile_expr(c, node->args.items[i]);
    emit_op(c, OP_NEW);
    emit_u16(c, (uint16_t)argc);
  }
}

void compile_member(sv_compiler_t *c, sv_ast_t *node) {
  if (is_ident_name(node->left, "super")) {
    emit_op(c, OP_THIS);
    emit_get_var(c, "super", 5);
    if (node->flags & 1)
      compile_expr(c, node->right);
    else
      emit_constant(c, js_mkstr_permanent(c->js, node->right->str, node->right->len));
    emit_op(c, OP_GET_SUPER_VAL);
    return;
  }

  compile_expr(c, node->left);

  int ok_jump = -1, end_jump = -1;
  if (sv_node_has_optional_base(node->left)) {
    ok_jump  = emit_jump(c, OP_JMP_NOT_NULLISH);
    end_jump = emit_jump(c, OP_JMP);
    patch_jump(c, ok_jump);
  }

  if (node->flags & 1) {
    compile_expr(c, node->right);
    emit_op(c, OP_GET_ELEM);
  } else {
    if (node->right->len == 6 && memcmp(node->right->str, "length", 6) == 0)
      emit_op(c, OP_GET_LENGTH);
    else {
      emit_srcpos(c, node->right);
      emit_atom_op(c, OP_GET_FIELD, node->right->str, node->right->len);
    }
  }

  if (end_jump >= 0) patch_jump(c, end_jump);
}

void compile_optional_get(sv_compiler_t *c, sv_ast_t *node) {
  if (node->flags & 1) {
    compile_expr(c, node->right);
    emit_op(c, OP_GET_ELEM_OPT);
  } else {
    emit_srcpos(c, node->right);
    emit_atom_op(c, OP_GET_FIELD_OPT, node->right->str, node->right->len);
  }
}

void compile_optional(sv_compiler_t *c, sv_ast_t *node) {
  compile_expr(c, node->left);
  compile_optional_get(c, node);
}

void compile_array(sv_compiler_t *c, sv_ast_t *node) {
  int count = node->args.count;
  bool has_spread = false;
  for (int i = 0; i < count; i++) {
    sv_ast_t *elem = node->args.items[i];
    if (elem && elem->type == N_SPREAD) {
      has_spread = true;
      break;
    }
  }

  if (!has_spread) {
    for (int i = 0; i < count; i++) {
      sv_ast_t *elem = node->args.items[i];
      if (elem && elem->type == N_EMPTY)
        emit_op(c, OP_EMPTY);
      else
        compile_expr(c, elem);
    }
    emit_op(c, OP_ARRAY);
    emit_u16(c, (uint16_t)count);
    return;
  }

  emit_op(c, OP_ARRAY);
  emit_u16(c, 0);
  for (int i = 0; i < count; i++) {
    sv_ast_t *elem = node->args.items[i];
    if (elem && elem->type == N_SPREAD) compile_concat_spread_to_array(c, elem);
    else compile_push_arg_to_array(c, elem);
  }
}

void compile_object(sv_compiler_t *c, sv_ast_t *node) {
  emit_op(c, OP_OBJECT);
  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *prop = node->args.items[i];
    if (prop->type == N_SPREAD) {
      compile_expr(c, prop->right);
      emit_op(c, OP_COPY_DATA_PROPS);
      emit(c, 0);
      emit_op(c, OP_POP);
      continue;
    }
    
    if (prop->type != N_PROPERTY) continue;
    if (prop->flags & FN_GETTER || prop->flags & FN_SETTER) {
      compile_expr(c, prop->right);
      uint8_t flags = 0;
      if (prop->flags & FN_GETTER) flags |= 1;
      if (prop->flags & FN_SETTER) flags |= 2;
      if (prop->flags & FN_COMPUTED) compile_expr(c, prop->left);
      else compile_static_property_key(c, prop->left);
      emit_op(c, OP_SWAP);
      emit_op(c, OP_DEFINE_METHOD_COMP);
      emit(c, flags);
    } else if (prop->flags & FN_COMPUTED) {
      compile_expr(c, prop->left);
      compile_expr(c, prop->right);
      emit_op(c, OP_DEFINE_METHOD_COMP);
      emit(c, 0);
    } else {
      if (prop->right && (prop->right->type == N_FUNC || prop->right->type == N_CLASS) &&
          (!prop->right->str || prop->right->len == 0) &&
          prop->left && prop->left->type == N_IDENT && !is_quoted_ident_key(prop->left)) {
        c->inferred_name = prop->left->str;
        c->inferred_name_len = prop->left->len;
      }
      compile_expr(c, prop->right);
      if ((prop->flags & FN_COLON) &&
          prop->left->type == N_IDENT && !is_quoted_ident_key(prop->left) &&
          is_ident_str(prop->left->str, prop->left->len, "__proto__", 9)) {
        emit_op(c, OP_SET_PROTO);
        continue;
      }
      if (prop->left->type == N_IDENT && !is_quoted_ident_key(prop->left)) {
        emit_atom_op(c, OP_DEFINE_FIELD, prop->left->str, prop->left->len);
      } else if (is_quoted_ident_key(prop->left)) {
        emit_atom_op(c, OP_DEFINE_FIELD, prop->left->str + 1, prop->left->len - 2);
      } else if (prop->left->type == N_STRING) {
        emit_atom_op(c, OP_DEFINE_FIELD, prop->left->str ? prop->left->str : "", prop->left->len);
      } else if (prop->left->type == N_NUMBER) {
        char buf[32];
        int n = snprintf(buf, sizeof(buf), "%g", prop->left->num);
        emit_atom_op(c, OP_DEFINE_FIELD, buf, (uint32_t)n);
      } else emit_atom_op(c, OP_DEFINE_FIELD, prop->left->str, prop->left->len);
    }
  }
}

void compile_func_expr(sv_compiler_t *c, sv_ast_t *node) {
  bool has_name = node->str && node->len > 0 && !(node->flags & FN_ARROW);
  int name_local = -1;

  if (has_name) {
    begin_scope(c);
    name_local = add_local(c, node->str, node->len, true, c->scope_depth);
    emit_op(c, OP_UNDEF);
    emit_put_local(c, name_local);
  }

  sv_func_t *fn = compile_function_body(c, node, SV_COMPILE_SCRIPT);
  if (!fn) {
    emit_op(c, OP_UNDEF);
    if (has_name) end_scope(c);
    return;
  }
  
  int idx = add_constant(c, mkval(T_NTARG, (uintptr_t)fn));
  emit_op(c, OP_CLOSURE);
  emit_u32(c, (uint32_t)idx);

  if (node->str && node->len > 0) {
    emit_set_function_name(c, node->str, node->len);
  } else if (c->inferred_name) {
    emit_set_function_name(c, c->inferred_name, c->inferred_name_len);
    c->inferred_name = NULL;
    c->inferred_name_len = 0;
  } else {
    emit_set_function_name(c, NULL, 0);
  }

  if (has_name) {
    emit_op(c, OP_DUP);
    emit_put_local(c, name_local);
    end_scope(c);
  }
}

typedef enum {
  DESTRUCTURE_ASSIGN = 0,
  DESTRUCTURE_BIND   = 1,
} sv_destructure_mode_t;

static void emit_build_object_rest(sv_compiler_t *c) {
  emit_op(c, OP_DUP);
  emit_op(c, OP_OBJECT);
  emit_op(c, OP_SWAP);
  emit_op(c, OP_COPY_DATA_PROPS);
  emit(c, 0);
  emit_op(c, OP_POP);
}

static void emit_delete_rest_key(sv_compiler_t *c, sv_ast_t *key) {
  emit_op(c, OP_DUP);
  if (key->type == N_IDENT) {
    emit_constant(c, js_mkstr_permanent(c->js, key->str, key->len));
  } else if (key->type == N_STRING) {
    emit_constant(c, ast_string_const(c, key));
  } else if (key->type == N_NUMBER) {
    emit_number(c, key->num);
  } else {
    compile_expr(c, key);
  }
  emit_op(c, OP_DELETE);
  emit_op(c, OP_POP);
}

static bool is_destructure_pattern_node(sv_ast_t *node) {
  return node && (node->type == N_ARRAY_PAT || node->type == N_ARRAY ||
                  node->type == N_OBJECT_PAT || node->type == N_OBJECT);
}

static void compile_destructure_pattern(sv_compiler_t *c, sv_ast_t *pat,
                                        bool keep, bool consume_source,
                                        sv_destructure_mode_t mode,
                                        sv_var_kind_t kind);

static void compile_destructure_store(sv_compiler_t *c, sv_ast_t *target,
                                      sv_destructure_mode_t mode,
                                      sv_var_kind_t kind) {
  if (!target) return;

  if (mode == DESTRUCTURE_ASSIGN) {
    compile_lhs_set(c, target, false);
    return;
  }

  if (target->type == N_IDENT) {
    bool is_const = (kind == SV_VAR_CONST);
    int idx = ensure_local_at_depth(c, target->str, target->len,
                                    is_const, c->scope_depth);
    emit_put_local(c, idx);
    return;
  }

  if (is_destructure_pattern_node(target)) {
    compile_destructure_pattern(c, target, false, true, mode, kind);
    return;
  }

  compile_lhs_set(c, target, false);
}

static void compile_destructure_pattern(
  sv_compiler_t *c, sv_ast_t *pat,
  bool keep, bool consume_source,
  sv_destructure_mode_t mode,
  sv_var_kind_t kind
) {
  if (!pat) return;
  if (keep) emit_op(c, OP_DUP);

  if (pat->type == N_ARRAY_PAT || pat->type == N_ARRAY) {
    if (!consume_source && !keep) emit_op(c, OP_DUP);
    emit_op(c, OP_DESTRUCTURE_INIT);
    
    int err_local = add_local(c, "", 0, false, c->scope_depth);
    int try_jump = emit_jump(c, OP_TRY_PUSH);
    c->try_depth++;
    
    for (int i = 0; i < pat->args.count; i++) {
      sv_ast_t *elem = pat->args.items[i];
      if (!elem || elem->type == N_EMPTY) {
        emit_op(c, OP_DESTRUCTURE_NEXT);
        emit_op(c, OP_POP);
        continue;
      }
      
      if (elem->type == N_REST || elem->type == N_SPREAD) {
        emit_op(c, OP_DESTRUCTURE_REST);
        compile_destructure_store(c, elem->right, mode, kind);
        continue;
      }
      
      sv_ast_t *target = elem;
      sv_ast_t *default_val = NULL;
      if (elem->type == N_ASSIGN_PAT ||
          (elem->type == N_ASSIGN && elem->op == TOK_ASSIGN)) {
        target = elem->left;
        default_val = elem->right;
      }
      
      emit_op(c, OP_DESTRUCTURE_NEXT);
      
      if (default_val) {
        emit_op(c, OP_DUP);
        emit_op(c, OP_IS_UNDEF);
        int skip = emit_jump(c, OP_JMP_FALSE);
        emit_op(c, OP_POP);
        compile_expr(c, default_val);
        patch_jump(c, skip);
      }
      
      compile_destructure_store(c, target, mode, kind);
    }

    emit_op(c, OP_TRY_POP);
    emit_op(c, OP_DESTRUCTURE_CLOSE);
    int end_jump = emit_jump(c, OP_JMP);

    patch_jump(c, try_jump);
    int catch_tag = emit_jump(c, OP_CATCH);
    
    emit_put_local(c, err_local);
    emit_op(c, OP_DESTRUCTURE_CLOSE);
    emit_get_local(c, err_local);
    emit_op(c, OP_THROW);
    patch_jump(c, catch_tag);
    patch_jump(c, end_jump);
    c->try_depth--;
    
    return;
  } else if (pat->type == N_OBJECT_PAT || pat->type == N_OBJECT) {
    for (int i = 0; i < pat->args.count; i++) {
      sv_ast_t *prop = pat->args.items[i];
      if (!prop) continue;

      if (prop->type == N_REST || prop->type == N_SPREAD) {
        emit_build_object_rest(c);
        for (int j = 0; j < i; j++) {
          sv_ast_t *prev = pat->args.items[j];
          if (!prev || prev->type != N_PROPERTY || !prev->left) continue;
          emit_delete_rest_key(c, prev->left);
        }
        compile_destructure_store(c, prop->right, mode, kind);
        continue;
      }
      if (prop->type != N_PROPERTY) continue;

      sv_ast_t *key = prop->left;
      sv_ast_t *value = prop->right;
      sv_ast_t *default_val = NULL;
      if (value && (value->type == N_ASSIGN_PAT ||
                    (value->type == N_ASSIGN && value->op == TOK_ASSIGN))) {
        default_val = value->right;
        value = value->left;
      }

      emit_op(c, OP_DUP);
      if ((prop->flags & FN_COMPUTED)) {
        compile_expr(c, key);
        emit_op(c, OP_GET_ELEM);
      } else if (key->type == N_IDENT) {
        emit_atom_op(c, OP_GET_FIELD, key->str, key->len);
      } else {
        compile_expr(c, key);
        emit_op(c, OP_GET_ELEM);
      }

      if (default_val) {
        emit_op(c, OP_DUP);
        emit_op(c, OP_IS_UNDEF);
        int skip = emit_jump(c, OP_JMP_FALSE);
        emit_op(c, OP_POP);
        compile_expr(c, default_val);
        patch_jump(c, skip);
      }

      compile_destructure_store(c, value, mode, kind);
    }
  }

  if (consume_source) emit_op(c, OP_POP);
}

void compile_array_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) {
  compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET);
}

void compile_object_destructure(sv_compiler_t *c, sv_ast_t *pat, bool keep) {
  compile_destructure_pattern(c, pat, keep, true, DESTRUCTURE_ASSIGN, SV_VAR_LET);
}

static bool is_tail_callable(sv_compiler_t *c, sv_ast_t *node) {
  if (c->try_depth > 0) return false;
  if (c->using_cleanup_count > 0) return false;
  if (node->type != N_CALL) return false;
  if (call_has_spread_arg(node)) return false;
  
  sv_ast_t *callee = node->left;
  if (
    callee->type == N_IDENT && 
    callee->len == 5 && memcmp(callee->str, "super", 5) == 0
  ) return false;
  
  return true;
}

static void emit_using_dispose_call(
  sv_compiler_t *c,
  int stack_local,
  int completion_local,
  bool is_async,
  bool suppressed_completion
) {
  emit_get_local(c, stack_local);
  if (suppressed_completion) {
    emit_get_local(c, completion_local);
  }
  if (is_async && suppressed_completion) emit_op(c, OP_USING_DISPOSE_ASYNC_SUPPRESSED);
  else if (is_async) emit_op(c, OP_USING_DISPOSE_ASYNC);
  else if (suppressed_completion) emit_op(c, OP_USING_DISPOSE_SUPPRESSED);
  else emit_op(c, OP_USING_DISPOSE);
  if (is_async) emit_op(c, OP_AWAIT);
}

static void emit_using_cleanups_to_depth(sv_compiler_t *c, int target_depth) {
for (int i = c->using_cleanup_count - 1; i >= 0; i--) {
  sv_using_cleanup_t *cleanup = &c->using_cleanups[i];
  if (cleanup->scope_depth <= target_depth) break;
  emit_using_dispose_call(c, cleanup->stack_local, -1, cleanup->is_async, false);
  emit_op(c, OP_POP);
}}

static void emit_return_from_stack(sv_compiler_t *c) {
  emit_using_cleanups_to_depth(c, -1);
  emit_close_upvals(c);
  emit_op(c, OP_RETURN);
}

static void compile_tail_call(sv_compiler_t *c, sv_ast_t *node) {
  sv_ast_t *callee = node->left;

  if (callee->type == N_OPTIONAL) {
    compile_call(c, node);
    emit_return_from_stack(c);
    return;
  }

  sv_call_kind_t kind = compile_call_setup_non_optional(c, callee);
  int argc = node->args.count;
  for (int i = 0; i < argc; i++)
    compile_expr(c, node->args.items[i]);

  emit_close_upvals(c);
  emit_op(c, kind == SV_CALL_METHOD ? OP_TAIL_CALL_METHOD : OP_TAIL_CALL);
  emit_u16(c, (uint16_t)argc);
}

void compile_tail_return_expr(sv_compiler_t *c, sv_ast_t *expr) {
  if (expr->type == N_TERNARY) {
    compile_truthy_test_expr(c, expr->cond);
    int else_jump = emit_jump(c, OP_JMP_FALSE);
    compile_tail_return_expr(c, expr->left);
    patch_jump(c, else_jump);
    compile_tail_return_expr(c, expr->right);
    return;
  }

  if (expr->type == N_AWAIT && c->is_async && c->try_depth == 0 && !c->is_tla &&
      c->using_cleanup_count == 0) {
    compile_expr(c, expr->right);
    emit_return_from_stack(c);
    return;
  }

  if (is_tail_callable(c, expr)) {
    compile_tail_call(c, expr);
    return;
  }

  compile_expr(c, expr);
  emit_return_from_stack(c);
}

void compile_stmts(sv_compiler_t *c, sv_ast_list_t *list) {
  for (int i = 0; i < list->count; i++) compile_stmt(c, list->items[i]);
}

static bool stmt_list_has_using_decl(sv_ast_list_t *list, bool *has_await_using) {
  bool found = false;
  for (int i = 0; i < list->count; i++) {
    sv_ast_t *node = list->items[i];
    if (!node) continue;
    
    sv_ast_t *decl = (node->type == N_EXPORT) ? node->left : node;
    if (!decl || decl->type != N_VAR) continue;
    
    if (decl->var_kind == SV_VAR_USING || decl->var_kind == SV_VAR_AWAIT_USING) {
      if (decl->var_kind == SV_VAR_AWAIT_USING && has_await_using) *has_await_using = true;
      found = true;
    }
  }
  
  return found;
}

static void emit_empty_disposal_stack(sv_compiler_t *c) {
  emit_op(c, OP_ARRAY);
  emit_u16(c, 0);
}

static void emit_using_push(sv_compiler_t *c, bool is_async) {
  emit_op(c, is_async ? OP_USING_PUSH_ASYNC : OP_USING_PUSH);
}

static void pop_using_cleanup(sv_compiler_t *c) {
  if (c->using_cleanup_count > 0) c->using_cleanup_count--;
}

static void emit_dispose_resource(sv_compiler_t *c, bool is_async) {
  emit_op(c, is_async ? OP_DISPOSE_RESOURCE_ASYNC : OP_DISPOSE_RESOURCE);
  if (is_async) emit_op(c, OP_AWAIT);
}

static void push_using_cleanup(
  sv_compiler_t *c,
  int stack_local,
  int scope_depth,
  bool is_async
) {
  if (c->using_cleanup_count >= c->using_cleanup_cap) {
    int cap = c->using_cleanup_cap ? c->using_cleanup_cap * 2 : 4;
    c->using_cleanups = realloc(c->using_cleanups, (size_t)cap * sizeof(sv_using_cleanup_t));
    c->using_cleanup_cap = cap;
  }

  c->using_cleanups[c->using_cleanup_count++] = (sv_using_cleanup_t){
    .stack_local = stack_local,
    .scope_depth = scope_depth,
    .is_async = is_async,
  };
}

static void compile_block_with_using(sv_compiler_t *c, sv_ast_t *node) {
  bool has_await_using = false;
  bool has_using = stmt_list_has_using_decl(&node->args, &has_await_using);

  begin_scope(c);
  hoist_lexical_decls(c, &node->args);
  hoist_func_decls(c, &node->args);

  if (!has_using) {
    compile_stmts(c, &node->args);
    end_scope(c);
    return;
  }

  emit_empty_disposal_stack(c);
  int stack_local = add_local(c, "", 0, false, c->scope_depth);
  
  emit_put_local(c, stack_local);
  int err_local = add_local(c, "", 0, false, c->scope_depth);

  int old_using_stack = c->using_stack_local;
  bool old_using_async = c->using_stack_async;
  
  c->using_stack_local = stack_local;
  c->using_stack_async = has_await_using;
  push_using_cleanup(c, stack_local, c->scope_depth, has_await_using);

  c->try_depth++;
  int try_jump = emit_jump(c, OP_TRY_PUSH);
  compile_stmts(c, &node->args);
  emit_op(c, OP_TRY_POP);
  c->try_depth--;

  emit_using_dispose_call(c, stack_local, -1, has_await_using, false);
  emit_op(c, OP_POP);
  int end_jump = emit_jump(c, OP_JMP);

  patch_jump(c, try_jump);
  int catch_tag = emit_jump(c, OP_CATCH);
  
  emit_put_local(c, err_local);
  emit_using_dispose_call(c, stack_local, err_local, has_await_using, true);
  
  if (!has_await_using) emit_op(c, OP_THROW);
  patch_jump(c, catch_tag);
  patch_jump(c, end_jump);

  c->using_stack_local = old_using_stack;
  c->using_stack_async = old_using_async;
  
  pop_using_cleanup(c);
  end_scope(c);
}

void compile_stmt(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return;
  emit_srcpos(c, node);

  switch (node->type) {
    case N_EMPTY:
    case N_DEBUGGER:
      break;

    case N_BLOCK:
      compile_block_with_using(c, node);
      break;

    case N_VAR:
      compile_var_decl(c, node);
      break;

    case N_IMPORT_DECL:
      compile_import_decl(c, node);
      break;

    case N_EXPORT:
      compile_export_decl(c, node);
      break;

    case N_IF:
      compile_if(c, node);
      break;

    case N_WHILE:
      compile_while(c, node);
      break;

    case N_DO_WHILE:
      compile_do_while(c, node);
      break;

    case N_FOR:
      compile_for(c, node);
      break;

    case N_FOR_IN:
      compile_for_in(c, node);
      break;

    case N_FOR_OF:
      compile_for_of(c, node);
      break;

    case N_FOR_AWAIT_OF:
      if (c->enclosing && !c->enclosing->enclosing) c->is_tla = true;
      compile_for_of(c, node);
      break;

    case N_RETURN:
      if (node->right) {
        compile_tail_return_expr(c, node->right);
      } else {
        emit_op(c, OP_UNDEF);
        emit_return_from_stack(c);
      }
      break;

    case N_THROW:
      compile_expr(c, node->right);
      emit_op(c, OP_THROW);
      break;

    case N_BREAK:
      compile_break(c, node);
      break;

    case N_CONTINUE:
      compile_continue(c, node);
      break;

    case N_TRY:
      compile_try(c, node);
      break;

    case N_SWITCH:
      compile_switch(c, node);
      break;

    case N_LABEL:
      compile_label(c, node);
      break;

    case N_ASSIGN:
      if (compile_self_append_stmt(c, node)) break;
      compile_expr(c, node);
      emit_op(c, OP_POP);
      break;

    case N_FUNC:
      if (node->str && !(node->flags & FN_ARROW)) break;
      compile_expr(c, node);
      emit_op(c, OP_POP);
      break;

    case N_CLASS:
      compile_class(c, node);
      emit_op(c, OP_POP);
      break;

    case N_WITH:
      compile_expr(c, node->left);
      emit_op(c, OP_ENTER_WITH);
      c->with_depth++;
      compile_stmt(c, node->body);
      c->with_depth--;
      emit_op(c, OP_EXIT_WITH);
      break;

    default:
      compile_expr(c, node);
      emit_op(c, OP_POP);
      break;
  }
}

enum {
  IMPORT_BIND_DEFAULT   = 1 << 0,
  IMPORT_BIND_NAMESPACE = 1 << 1,
};

void compile_import_decl(sv_compiler_t *c, sv_ast_t *node) {
  if (!node->right) return;
  bool repl_top = is_repl_top_level(c);

  compile_expr(c, node->right);
  emit_op(c, OP_IMPORT_SYNC);

  if (node->args.count == 0) {
    emit_op(c, OP_POP);
    return;
  }

  int ns_local = add_local(c, "", 0, false, c->scope_depth);
  emit_put_local(c, ns_local);

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *spec = node->args.items[i];
    if (!spec || spec->type != N_IMPORT_SPEC ||
        !spec->right || spec->right->type != N_IDENT)
      continue;

    emit_get_local(c, ns_local);

    if (!(spec->flags & IMPORT_BIND_NAMESPACE)) {
      if ((spec->flags & IMPORT_BIND_DEFAULT) ||
          !spec->left || spec->left->type != N_IDENT) {
        emit_op(c, OP_IMPORT_DEFAULT);
      } else {
        emit_atom_op(c, OP_IMPORT_NAMED, spec->left->str, spec->left->len);
      }
    }

    if (repl_top) {
      emit_atom_op(c, OP_PUT_GLOBAL, spec->right->str, spec->right->len);
    } else {
      int idx = ensure_local_at_depth(c, spec->right->str, spec->right->len,
                                      true, c->scope_depth);
      emit_put_local(c, idx);
    }
  }
}

static void compile_export_emit(sv_compiler_t *c, const char *name, uint32_t len) {
  emit_get_var(c, name, len);
  emit_atom_op(c, OP_EXPORT, name, len);
}

static void defer_export(sv_compiler_t *c, const char *name, uint32_t len) {
  if (c->deferred_export_count >= c->deferred_export_cap) {
    int cap = c->deferred_export_cap ? c->deferred_export_cap * 2 : 8;
    c->deferred_exports = realloc(c->deferred_exports, cap * sizeof(sv_deferred_export_t));
    c->deferred_export_cap = cap;
  }
  c->deferred_exports[c->deferred_export_count++] =
    (sv_deferred_export_t){ .name = name, .len = len };
}

static void defer_export_pattern(sv_compiler_t *c, sv_ast_t *pat) {
  if (!pat) return;
  switch (pat->type) {
    case N_IDENT:
      defer_export(c, pat->str, pat->len);
      break;
    case N_ASSIGN_PAT:
      defer_export_pattern(c, pat->left);
      break;
    case N_REST:
    case N_SPREAD:
      defer_export_pattern(c, pat->right);
      break;
    case N_ARRAY:
    case N_ARRAY_PAT:
      for (int i = 0; i < pat->args.count; i++)
        defer_export_pattern(c, pat->args.items[i]);
      break;
    case N_OBJECT:
    case N_OBJECT_PAT:
      for (int i = 0; i < pat->args.count; i++) {
        sv_ast_t *prop = pat->args.items[i];
        if (!prop) continue;
        if (prop->type == N_PROPERTY) defer_export_pattern(c, prop->right);
        else defer_export_pattern(c, prop);
      }
      break;
    default:
      break;
  }
}

static void compile_export_pattern(sv_compiler_t *c, sv_ast_t *pat) {
  if (!pat) return;
  switch (pat->type) {
    case N_IDENT:
      compile_export_emit(c, pat->str, pat->len);
      break;
    case N_ASSIGN_PAT:
      compile_export_pattern(c, pat->left);
      break;
    case N_REST:
    case N_SPREAD:
      compile_export_pattern(c, pat->right);
      break;
    case N_ARRAY:
    case N_ARRAY_PAT:
      for (int i = 0; i < pat->args.count; i++)
        compile_export_pattern(c, pat->args.items[i]);
      break;
    case N_OBJECT:
    case N_OBJECT_PAT:
      for (int i = 0; i < pat->args.count; i++) {
        sv_ast_t *prop = pat->args.items[i];
        if (!prop) continue;
        if (prop->type == N_PROPERTY) compile_export_pattern(c, prop->right);
        else compile_export_pattern(c, prop);
      }
      break;
    default:
      break;
  }
}

void compile_export_decl(sv_compiler_t *c, sv_ast_t *node) {
  if (!node) return;

  if (node->flags & EX_DEFAULT) {
    if (node->left) compile_expr(c, node->left);
    else emit_op(c, OP_UNDEF);
    emit_atom_op(c, OP_EXPORT, "default", 7);
    return;
  }

  if ((node->flags & EX_DECL) && node->left) {
    sv_ast_t *decl = node->left;
    if (decl->type == N_VAR) {
      compile_var_decl(c, decl);
      bool is_var = decl->var_kind == SV_VAR_VAR;
      for (int i = 0; i < decl->args.count; i++) {
        sv_ast_t *var = decl->args.items[i];
        if (!var || var->type != N_VARDECL) continue;
        if (is_var && c->mode == SV_COMPILE_MODULE)
          defer_export_pattern(c, var->left);
        else
          compile_export_pattern(c, var->left);
      }
      return;
    }

    compile_stmt(c, decl);
    if ((decl->type == N_FUNC || decl->type == N_CLASS) &&
        decl->str && decl->len > 0) {
      compile_export_emit(c, decl->str, decl->len);
    }
    return;
  }

  if ((node->flags & EX_STAR) && (node->flags & EX_FROM) && node->right) {
    compile_expr(c, node->right);
    emit_op(c, OP_IMPORT_SYNC);

    if (node->flags & EX_NAMESPACE) {
      if (node->args.count > 0) {
        sv_ast_t *spec = node->args.items[0];
        if (spec && spec->type == N_IMPORT_SPEC &&
            spec->right && spec->right->type == N_IDENT) {
          emit_atom_op(c, OP_EXPORT, spec->right->str, spec->right->len);
          return;
        }
      }
      emit_op(c, OP_POP);
      return;
    }

    emit_op(c, OP_EXPORT_ALL);
    return;
  }

  if (!(node->flags & EX_NAMED) || node->args.count == 0) return;

  int ns_local = -1;
  if (node->flags & EX_FROM) {
    if (!node->right) return;
    compile_expr(c, node->right);
    emit_op(c, OP_IMPORT_SYNC);
    ns_local = add_local(c, "", 0, false, c->scope_depth);
    emit_put_local(c, ns_local);
  }

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *spec = node->args.items[i];
    if (!spec || spec->type != N_IMPORT_SPEC || !spec->left || !spec->right ||
        spec->left->type != N_IDENT || spec->right->type != N_IDENT)
      continue;

    if (node->flags & EX_FROM) {
      emit_get_local(c, ns_local);
      if (spec->left->len == 7 && memcmp(spec->left->str, "default", 7) == 0)
        emit_op(c, OP_IMPORT_DEFAULT);
      else
        emit_atom_op(c, OP_IMPORT_NAMED, spec->left->str, spec->left->len);
      emit_atom_op(c, OP_EXPORT, spec->right->str, spec->right->len);
    } else {
      emit_get_var(c, spec->left->str, spec->left->len);
      emit_atom_op(c, OP_EXPORT, spec->right->str, spec->right->len);
    }
  }
}

void compile_var_decl(sv_compiler_t *c, sv_ast_t *node) {
  sv_var_kind_t kind = node->var_kind;
  bool is_using = (kind == SV_VAR_USING || kind == SV_VAR_AWAIT_USING);
  bool is_await_using = (kind == SV_VAR_AWAIT_USING);
  bool is_const = (kind == SV_VAR_CONST || is_using);
  bool repl_top = is_repl_top_level(c);

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *decl = node->args.items[i];
    if (decl->type != N_VARDECL) continue;
    sv_ast_t *target = decl->left;

    if (repl_top) {
      if (!decl->right && kind == SV_VAR_VAR) continue;
      if (decl->right) {
        compile_expr(c, decl->right);
      } else {
        emit_op(c, OP_UNDEF);
      }
      if (target->type == N_IDENT) {
        emit_atom_op(c, OP_PUT_GLOBAL, target->str, target->len);
      } else {
        compile_destructure_pattern(c, target, false, true,
                                    DESTRUCTURE_ASSIGN, kind);
      }
    } else if (kind == SV_VAR_VAR) {
      if (decl->right) {
        uint8_t init_type = infer_expr_type(c, decl->right);
        compile_expr(c, decl->right);
        if (target->type == N_IDENT) {
          int idx = resolve_local(c, target->str, target->len);
          if (idx >= 0 && c->locals[idx].depth != -1)
            emit_put_local_typed(c, idx, init_type);
          else
            compile_lhs_set(c, target, false);
        } else {
          compile_lhs_set(c, target, false);
        }
      }
    } else {
      if (target->type == N_IDENT) {
        int idx = ensure_local_at_depth(c, target->str, target->len,
                                        is_const, c->scope_depth);
        uint8_t init_type = SV_TI_UNKNOWN;
        if (decl->right) {
          init_type = infer_expr_type(c, decl->right);
          compile_expr(c, decl->right);
        } else if (!is_const) {
          emit_op(c, OP_UNDEF);
          init_type = SV_TI_UNDEF;
        }
        if (decl->right || !is_const) {
          emit_put_local_typed(c, idx, init_type);
          c->locals[idx].is_tdz = false;
          if (is_using) {
            if (c->using_stack_local >= 0) {
              emit_get_local(c, c->using_stack_local);
              emit_get_local(c, idx);
              emit_using_push(c, is_await_using);
            } else {
              emit_get_local(c, idx);
              emit_dispose_resource(c, is_await_using);
            }
            emit_op(c, OP_POP);
          }
        }
      } else {
        if (decl->right) {
          compile_expr(c, decl->right);
          compile_destructure_binding(c, target, kind);
          emit_op(c, OP_POP);
        }
      }
    }
  }
}

void compile_destructure_binding(sv_compiler_t *c, sv_ast_t *pat, sv_var_kind_t kind) {
  compile_destructure_pattern(c, pat, false, false, DESTRUCTURE_BIND, kind);
}

static bool fold_static_typeof_compare(
  sv_compiler_t *c, sv_ast_t *cond, bool *out_truth
) {
  if (!cond || cond->type != N_BINARY) return false;
  if (!(cond->op == TOK_SEQ || cond->op == TOK_SNE ||
        cond->op == TOK_EQ  || cond->op == TOK_NE))
    return false;

  sv_ast_t *typeof_node = NULL;
  sv_ast_t *str_node = NULL;
  if (cond->left && cond->left->type == N_TYPEOF &&
      cond->right && cond->right->type == N_STRING) {
    typeof_node = cond->left;
    str_node = cond->right;
  } else if (cond->right && cond->right->type == N_TYPEOF &&
             cond->left && cond->left->type == N_STRING) {
    typeof_node = cond->right;
    str_node = cond->left;
  } else return false;

  if (!typeof_node->right || typeof_node->right->type != N_IDENT) return false;
  sv_ast_t *ident = typeof_node->right;
  int local = resolve_local(c, ident->str, ident->len);
  if (local < 0) return false;
  const char *known = typeof_name_for_type(get_local_inferred_type(c, local));
  if (!known) return false;

  bool is_equal = (strlen(known) == str_node->len &&
                   memcmp(known, str_node->str, str_node->len) == 0);
  bool truth = (cond->op == TOK_SEQ || cond->op == TOK_EQ) ? is_equal : !is_equal;
  *out_truth = truth;
  return true;
}

void compile_if(sv_compiler_t *c, sv_ast_t *node) {
  bool folded_truth = false;
  if (fold_static_typeof_compare(c, node->cond, &folded_truth)) {
    if (folded_truth) compile_stmt(c, node->left);
    else if (node->right) compile_stmt(c, node->right);
    return;
  }

  compile_truthy_test_expr(c, node->cond);
  int else_jump = emit_jump(c, OP_JMP_FALSE);
  compile_stmt(c, node->left);
  if (node->right) {
    int end_jump = emit_jump(c, OP_JMP);
    patch_jump(c, else_jump);
    compile_stmt(c, node->right);
    patch_jump(c, end_jump);
  } else {
    patch_jump(c, else_jump);
  }
}

void compile_while(sv_compiler_t *c, sv_ast_t *node) {
  int loop_start = c->code_len;
  push_loop(c, loop_start, NULL, 0, false);

  compile_truthy_test_expr(c, node->cond);
  int exit_jump = emit_jump(c, OP_JMP_FALSE);
  compile_stmt(c, node->body);

  sv_loop_t *loop = &c->loops[c->loop_count - 1];
  for (int i = 0; i < loop->continues.count; i++)
    patch_jump(c, loop->continues.offsets[i]);

  emit_loop(c, loop_start);
  patch_jump(c, exit_jump);
  pop_loop(c);
}

void compile_do_while(sv_compiler_t *c, sv_ast_t *node) {
  int loop_start = c->code_len;
  push_loop(c, loop_start, NULL, 0, false);
  compile_stmt(c, node->body);

  sv_loop_t *loop = &c->loops[c->loop_count - 1];
  for (int i = 0; i < loop->continues.count; i++)
    patch_jump(c, loop->continues.offsets[i]);

  compile_truthy_test_expr(c, node->cond);
  int exit_jump = emit_jump(c, OP_JMP_FALSE);
  emit_loop(c, loop_start);
  patch_jump(c, exit_jump);
  pop_loop(c);
}

static void for_add_slot_unique(int **slots, int *count, int *cap, int slot) {
  if (slot < 0) return;
  for (int i = 0; i < *count; i++) {
    if ((*slots)[i] == slot) return;
  }
  if (*count >= *cap) {
    int new_cap = (*cap > 0) ? (*cap * 2) : 8;
    int *new_slots = realloc(*slots, (size_t)new_cap * sizeof(int));
    if (!new_slots) return;
    *slots = new_slots;
    *cap = new_cap;
  }
  (*slots)[(*count)++] = slot;
}

static void for_collect_pattern_slots(sv_compiler_t *c, sv_ast_t *pat, int **slots, int *count, int *cap) {
  if (!pat) return;
  switch (pat->type) {
    case N_IDENT: {
      int slot = resolve_local(c, pat->str, pat->len);
      for_add_slot_unique(slots, count, cap, slot);
      break;
    }
    case N_ASSIGN_PAT:
    case N_ASSIGN:
      for_collect_pattern_slots(c, pat->left, slots, count, cap);
      break;
    case N_REST:
    case N_SPREAD:
      for_collect_pattern_slots(c, pat->right, slots, count, cap);
      break;
    case N_ARRAY:
    case N_ARRAY_PAT:
      for (int i = 0; i < pat->args.count; i++)
        for_collect_pattern_slots(c, pat->args.items[i], slots, count, cap);
      break;
    case N_OBJECT:
    case N_OBJECT_PAT:
      for (int i = 0; i < pat->args.count; i++) {
        sv_ast_t *prop = pat->args.items[i];
        if (!prop) continue;
        if (prop->type == N_PROPERTY)
          for_collect_pattern_slots(c, prop->right, slots, count, cap);
        else
          for_collect_pattern_slots(c, prop, slots, count, cap);
      }
      break;
    default:
      break;
  }
}

static void for_collect_var_decl_slots(sv_compiler_t *c, sv_ast_t *init_var, int **slots, int *count, int *cap) {
  if (!init_var || init_var->type != N_VAR) return;
  for (int i = 0; i < init_var->args.count; i++) {
    sv_ast_t *decl = init_var->args.items[i];
    if (!decl || decl->type != N_VARDECL || !decl->left) continue;
    for_collect_pattern_slots(c, decl->left, slots, count, cap);
  }
}

void compile_for(sv_compiler_t *c, sv_ast_t *node) {
  begin_scope(c);

  int *iter_slots = NULL;
  int iter_count = 0;
  int iter_cap = 0;

  if (node->init) {
    if (node->init->type == N_VAR) {
      sv_var_kind_t kind = node->init->var_kind;
      compile_var_decl(c, node->init);
      if (kind == SV_VAR_LET || kind == SV_VAR_CONST)
        for_collect_var_decl_slots(c, node->init, &iter_slots, &iter_count, &iter_cap);
    } else {
      compile_expr(c, node->init);
      emit_op(c, OP_POP);
    }
  }

  int loop_start = c->code_len;
  push_loop(c, loop_start, NULL, 0, false);

  int exit_jump = -1;
  if (node->cond) {
    compile_truthy_test_expr(c, node->cond);
    exit_jump = emit_jump(c, OP_JMP_FALSE);
  }

  int iter_inner_start = -1;
  if (iter_count > 0) {
  begin_scope(c);
  iter_inner_start = c->local_count;
  for (int i = 0; i < iter_count; i++) {
    int outer_idx = iter_slots[i];
    sv_local_t outer = c->locals[outer_idx];
    emit_get_local(c, outer_idx);
    int inner_idx = add_local(c, outer.name, outer.name_len, outer.is_const, c->scope_depth);
    emit_put_local(c, inner_idx);
  }}

  compile_stmt(c, node->body);
  sv_loop_t *loop = &c->loops[c->loop_count - 1];
  for (int i = 0; i < loop->continues.count; i++)
    patch_jump(c, loop->continues.offsets[i]);

  int break_close_slot = -1;
  if (iter_count > 0) {
    for (int i = 0; i < iter_count; i++) {
      int inner_idx = iter_inner_start + i;
      if (!c->locals[inner_idx].captured) continue;
      int slot = local_to_frame_slot(c, inner_idx);
      if (break_close_slot < 0 || slot < break_close_slot)
        break_close_slot = slot;
    }

    for (int i = 0; i < iter_count; i++) {
      emit_get_local(c, iter_inner_start + i);
      emit_put_local(c, iter_slots[i]);
    }
    end_scope(c);
  }

  if (node->update) {
    sv_ast_t *upd = node->update;
    int slot;
    if (upd->type == N_UPDATE && upd->right && upd->right->type == N_IDENT &&
        (slot = resolve_local_slot(c, upd->right->str, upd->right->len)) >= 0) {
      emit_op(c, upd->op == TOK_POSTINC ? OP_INC_LOCAL : OP_DEC_LOCAL);
      emit(c, (uint8_t)slot);
      set_local_inferred_type(c, c->param_locals + slot, SV_TI_UNKNOWN);
    } else {
      compile_expr(c, upd);
      emit_op(c, OP_POP);
    }
  }

  emit_loop(c, loop_start);
  if (exit_jump >= 0) patch_jump(c, exit_jump);

  int skip_break_cleanup = -1;
  if (break_close_slot >= 0)
    skip_break_cleanup = emit_jump(c, OP_JMP);

  pop_loop(c);

  if (break_close_slot >= 0) {
    emit_op(c, OP_CLOSE_UPVAL);
    emit_u16(c, (uint16_t)break_close_slot);
    patch_jump(c, skip_break_cleanup);
  }

  free(iter_slots);
  end_scope(c);
}


static void compile_for_each(sv_compiler_t *c, sv_ast_t *node, bool is_for_of);

void compile_for_in(sv_compiler_t *c, sv_ast_t *node) {
  compile_for_each(c, node, false);
}


void compile_for_of(sv_compiler_t *c, sv_ast_t *node) {
  compile_for_each(c, node, true);
}

static void compile_for_each_assign_target(sv_compiler_t *c, sv_ast_t *lhs) {
  if (!lhs) return;
  if (lhs->type == N_VAR && lhs->args.count > 0) {
    sv_ast_t *decl = lhs->args.items[0];
    sv_ast_t *target = decl->left ? decl->left : decl;
    if (target->type == N_IDENT) {
      int loc = resolve_local(c, target->str, target->len);
      if (loc == -1) {
        bool is_const = (lhs->var_kind == SV_VAR_CONST ||
                         lhs->var_kind == SV_VAR_USING ||
                         lhs->var_kind == SV_VAR_AWAIT_USING);
        loc = add_local(c, target->str, target->len, is_const, c->scope_depth);
      }
      emit_put_local(c, loc);
    } else {
      if (lhs->var_kind == SV_VAR_VAR) {
        compile_lhs_set(c, target, false);
      } else {
        compile_destructure_binding(c, target, lhs->var_kind);
        emit_op(c, OP_POP);
      }
    }
    return;
  }
  if (lhs->type == N_IDENT) {
    emit_set_var(c, lhs->str, lhs->len, false);
    return;
  }
  compile_lhs_set(c, lhs, false);
}

static void compile_using_dispose_target(sv_compiler_t *c, sv_ast_t *lhs) {
  if (!lhs || lhs->type != N_VAR) return;
  bool is_await_using = lhs->var_kind == SV_VAR_AWAIT_USING;
  if (lhs->var_kind != SV_VAR_USING && !is_await_using) return;
  if (lhs->args.count == 0) return;

  sv_ast_t *decl = lhs->args.items[0];
  if (!decl || decl->type != N_VARDECL || !decl->left || decl->left->type != N_IDENT) return;

  emit_get_var(c, decl->left->str, decl->left->len);
  emit_dispose_resource(c, is_await_using);
  emit_op(c, OP_POP);
}

static bool compile_using_push_target(sv_compiler_t *c, sv_ast_t *lhs, int stack_local) {
  if (!lhs || lhs->type != N_VAR) return false;
  bool is_await_using = lhs->var_kind == SV_VAR_AWAIT_USING;
  if (lhs->var_kind != SV_VAR_USING && !is_await_using) return false;
  if (lhs->args.count == 0) return false;

  sv_ast_t *decl = lhs->args.items[0];
  if (!decl || decl->type != N_VARDECL || !decl->left || decl->left->type != N_IDENT) return false;

  emit_get_local(c, stack_local);
  emit_get_var(c, decl->left->str, decl->left->len);
  emit_using_push(c, is_await_using);
  emit_op(c, OP_POP);
  
  return true;
}

static void compile_for_each(sv_compiler_t *c, sv_ast_t *node, bool is_for_of) {
  begin_scope(c);

  int *iter_slots = NULL;
  int iter_count = 0;
  int iter_cap = 0;

  if (node->left && node->left->type == N_VAR &&
      node->left->var_kind != SV_VAR_VAR) {
    bool is_const = 
      (node->left->var_kind == SV_VAR_CONST ||
       node->left->var_kind == SV_VAR_USING ||
       node->left->var_kind == SV_VAR_AWAIT_USING);
    int lb = c->local_count;
    for (int i = 0; i < node->left->args.count; i++) {
      sv_ast_t *decl = node->left->args.items[i];
      if (!decl || decl->type != N_VARDECL || !decl->left) continue;
      hoist_lexical_pattern(c, decl->left, is_const);
    }
    for (int i = lb; i < c->local_count; i++) {
      c->locals[i].is_tdz = true;
      set_local_inferred_type(c, i, SV_TI_UNKNOWN);
      int slot = i - c->param_locals;
      emit_op(c, OP_SET_LOCAL_UNDEF);
      emit_u16(c, (uint16_t)slot);
    }
    for_collect_var_decl_slots(c, node->left, &iter_slots, &iter_count, &iter_cap);
  }

  if (!is_for_of && node->left && node->left->type == N_VAR &&
      node->left->var_kind == SV_VAR_VAR && node->left->args.count > 0) {
    sv_ast_t *decl = node->left->args.items[0];
    if (decl && decl->right) {
      compile_expr(c, decl->right);
      sv_ast_t *target = decl->left ? decl->left : decl;
      if (target->type == N_IDENT) {
        int loc = resolve_local(c, target->str, target->len);
        if (loc == -1)
          loc = add_local(c, target->str, target->len, false, c->scope_depth);
        emit_put_local(c, loc);
      } else {
        compile_lhs_set(c, target, false);
      }
    }
  }

  int iter_local = -1;
  int idx_local = -1;
  int exit_jump = -1;
  int try_jump_for_of = -1;
  int iter_err_local = -1;
  int break_close_slot = -1;
  int iter_inner_start = -1;
  int using_stack_local = -1;

  bool is_for_await = (node->type == N_FOR_AWAIT_OF);
  bool is_using_loop = node->left && node->left->type == N_VAR &&
    (node->left->var_kind == SV_VAR_USING || node->left->var_kind == SV_VAR_AWAIT_USING);
  bool is_await_using_loop = is_using_loop && node->left->var_kind == SV_VAR_AWAIT_USING;
  int old_using_stack = c->using_stack_local;
  bool old_using_async = c->using_stack_async;
  uint8_t iter_hint = 0;
  if (is_for_of && !is_for_await)
    iter_hint = iter_hint_for_type(infer_expr_type(c, node->right));

  if (is_using_loop) {
    emit_empty_disposal_stack(c);
    using_stack_local = add_local(c, "", 0, false, c->scope_depth);
    emit_put_local(c, using_stack_local);
  }

  compile_expr(c, node->right);
  if (is_for_of) {
    emit_op(c, is_for_await ? OP_FOR_AWAIT_OF : OP_FOR_OF);
    iter_err_local = add_local(c, "", 0, false, c->scope_depth);
    try_jump_for_of = emit_jump(c, OP_TRY_PUSH);
    c->try_depth++;
  } else {
    emit_op(c, OP_FOR_IN);  
    iter_local = add_local(c, "", 0, false, c->scope_depth);
    emit_put_local(c, iter_local);

    emit_number(c, 0);
    idx_local = add_local(c, "", 0, false, c->scope_depth);
    emit_put_local(c, idx_local);
  }

  int loop_start = c->code_len;
  push_loop(c, loop_start, NULL, 0, false);

  if (is_using_loop) {
    emit_empty_disposal_stack(c);
    emit_put_local(c, using_stack_local);
  }

  if (is_for_of) {
    if (is_for_await) {
      emit_op(c, OP_AWAIT_ITER_NEXT);
    } else {
      emit_op(c, OP_ITER_NEXT);
      emit(c, iter_hint);
    }
    exit_jump = emit_jump(c, OP_JMP_TRUE);  
  } else {
    emit_get_local(c, idx_local);
    emit_get_local(c, iter_local);
    emit_op(c, OP_GET_LENGTH);
    emit_op(c, OP_LT);
    exit_jump = emit_jump(c, OP_JMP_FALSE);

    emit_get_local(c, iter_local);
    emit_get_local(c, idx_local);
    emit_op(c, OP_GET_ELEM);
  }

  compile_for_each_assign_target(c, node->left);

  if (iter_count > 0) {
    begin_scope(c);
    iter_inner_start = c->local_count;
    for (int i = 0; i < iter_count; i++) {
      int outer_idx = iter_slots[i];
      sv_local_t outer = c->locals[outer_idx];
      emit_get_local(c, outer_idx);
      int inner_idx = add_local(c, outer.name, outer.name_len,
                                outer.is_const, c->scope_depth);
      emit_put_local(c, inner_idx);
    }
  }

  if (is_using_loop) {
    c->using_stack_local = using_stack_local;
    c->using_stack_async = is_await_using_loop;
    push_using_cleanup(c, using_stack_local, c->scope_depth, is_await_using_loop);
    compile_using_push_target(c, node->left, using_stack_local);
  }

  compile_stmt(c, node->body);
  sv_loop_t *loop = &c->loops[c->loop_count - 1];
  for (int i = 0; i < loop->continues.count; i++)
    patch_jump(c, loop->continues.offsets[i]);

  if (is_using_loop) {
    emit_using_dispose_call(c, using_stack_local, -1, is_await_using_loop, false);
    emit_op(c, OP_POP);

    c->using_stack_local = old_using_stack;
    c->using_stack_async = old_using_async;
    pop_using_cleanup(c);
  } else compile_using_dispose_target(c, node->left);

  if (iter_count > 0) {
    for (int i = 0; i < iter_count; i++) {
      int inner_idx = iter_inner_start + i;
      if (!c->locals[inner_idx].captured) continue;
      int slot = local_to_frame_slot(c, inner_idx);
      if (break_close_slot < 0 || slot < break_close_slot)
        break_close_slot = slot;
    }
    end_scope(c);
  }

  if (!is_for_of) {
    emit_get_local(c, idx_local);
    emit_op(c, OP_INC);
    emit_put_local(c, idx_local);
  }

  emit_loop(c, loop_start);
  patch_jump(c, exit_jump);

  if (is_for_of) {
    emit_op(c, OP_POP);
    emit_op(c, OP_TRY_POP);

    int skip_break_cleanup = -1;
    if (break_close_slot >= 0)
      skip_break_cleanup = emit_jump(c, OP_JMP);

    pop_loop(c);
    if (break_close_slot >= 0) {
      emit_op(c, OP_CLOSE_UPVAL);
      emit_u16(c, (uint16_t)break_close_slot);
      patch_jump(c, skip_break_cleanup);
    }
    emit_op(c, OP_TRY_POP);   
    emit_op(c, OP_ITER_CLOSE);
    int end_jump = emit_jump(c, OP_JMP);

    patch_jump(c, try_jump_for_of);
    int catch_tag = emit_jump(c, OP_CATCH);
    emit_put_local(c, iter_err_local);
    
    if (is_using_loop) {
      emit_using_dispose_call(c, using_stack_local, iter_err_local, is_await_using_loop, true);
      emit_put_local(c, iter_err_local);
    }
    
    emit_op(c, OP_ITER_CLOSE);          
    emit_get_local(c, iter_err_local);  
    emit_op(c, OP_THROW);              
    patch_jump(c, catch_tag);

    patch_jump(c, end_jump);
    c->try_depth--;
  } else {
    int skip_break_cleanup = -1;
    if (break_close_slot >= 0)
      skip_break_cleanup = emit_jump(c, OP_JMP);

    pop_loop(c);
    if (break_close_slot >= 0) {
      emit_op(c, OP_CLOSE_UPVAL);
      emit_u16(c, (uint16_t)break_close_slot);
      patch_jump(c, skip_break_cleanup);
    }
  }

  free(iter_slots);
  end_scope(c);
}


static void emit_close_upvals_to_depth(sv_compiler_t *c, int target_depth) {
for (int i = c->local_count - 1; i >= 0; i--) {
  if (c->locals[i].depth <= target_depth) break;
  if (c->locals[i].captured) {
    int frame_slot = local_to_frame_slot(c, i);
    emit_op(c, OP_CLOSE_UPVAL);
    emit_u16(c, (uint16_t)frame_slot);
  }
}}

void compile_break(sv_compiler_t *c, sv_ast_t *node) {
  if (c->loop_count == 0) return;

  int target = c->loop_count - 1;
  if (node->str) for (int i = c->loop_count - 1; i >= 0; i--) if (
    c->loops[i].label &&
    c->loops[i].label_len == node->len &&
    memcmp(c->loops[i].label, node->str, node->len) == 0
  ) { target = i; break;  }

  emit_close_upvals_to_depth(c, c->loops[target].scope_depth);
  emit_using_cleanups_to_depth(c, c->loops[target].scope_depth);
  
  int offset = emit_jump(c, OP_JMP);
  patch_list_add(&c->loops[target].breaks, offset);
}


void compile_continue(sv_compiler_t *c, sv_ast_t *node) {
  for (int i = c->loop_count - 1; i >= 0; i--) if (node->str) {
    if (
      c->loops[i].label &&
      c->loops[i].label_len == node->len &&
      memcmp(c->loops[i].label, node->str, node->len) == 0
    ) {
      emit_close_upvals_to_depth(c, c->loops[i].scope_depth);
      emit_using_cleanups_to_depth(c, c->loops[i].scope_depth);
      patch_list_add(&c->loops[i].continues, emit_jump(c, OP_JMP));
      return;
    }
  } else if (!c->loops[i].is_switch) {
    emit_close_upvals_to_depth(c, c->loops[i].scope_depth);
    emit_using_cleanups_to_depth(c, c->loops[i].scope_depth);
    patch_list_add(&c->loops[i].continues, emit_jump(c, OP_JMP));
    return;
  }
}

static void compile_finally_block(sv_compiler_t *c, sv_ast_t *finally_body) {
  int finally_jump = emit_jump(c, OP_FINALLY);
  compile_stmt(c, finally_body);
  emit_op(c, OP_FINALLY_RET);
  patch_jump(c, finally_jump);
}

static void compile_catch_body(sv_compiler_t *c, sv_ast_t *node) {
  begin_scope(c);
  if (node->catch_param && node->catch_param->type == N_IDENT) {
    int loc = add_local(c, node->catch_param->str, node->catch_param->len, false, c->scope_depth);
    emit_put_local(c, loc);
  } else if (node->catch_param && is_destructure_pattern_node(node->catch_param)) {
    compile_destructure_binding(c, node->catch_param, SV_VAR_LET);
    emit_op(c, OP_POP);
  } else emit_op(c, OP_POP);  
  
  compile_stmt(c, node->catch_body);
  end_scope(c);
}

void compile_try(sv_compiler_t *c, sv_ast_t *node) {
  c->try_depth++;
  int try_jump = emit_jump(c, OP_TRY_PUSH);

  compile_stmt(c, node->body);
  emit_op(c, OP_TRY_POP);

  bool has_catch = (node->catch_body != NULL);
  bool has_finally = (node->finally_body != NULL);

  if (!has_finally) {
    int end_jump = emit_jump(c, OP_JMP);
    patch_jump(c, try_jump);
    int catch_tag = emit_jump(c, OP_CATCH);
    if (has_catch)
      compile_catch_body(c, node);
    else
      emit_op(c, OP_POP);
    patch_jump(c, catch_tag);
    patch_jump(c, end_jump);
    c->try_depth--;
    return;
  }

  int to_finally_from_try = emit_jump(c, OP_JMP);
  int to_finally_from_catch = -1;
  int to_finally_from_throw = -1;

  patch_jump(c, try_jump);

  if (has_catch) {
    int catch_tag = emit_jump(c, OP_CATCH);
    int catch_throw_jump = emit_jump(c, OP_TRY_PUSH);

    compile_catch_body(c, node);
    emit_op(c, OP_TRY_POP);
    to_finally_from_catch = emit_jump(c, OP_JMP);

    patch_jump(c, catch_throw_jump);
    emit_op(c, OP_POP);  
    to_finally_from_throw = emit_jump(c, OP_JMP);

    patch_jump(c, catch_tag);
  } else {
    emit_op(c, OP_POP);  
    to_finally_from_throw = emit_jump(c, OP_JMP);
  }

  patch_jump(c, to_finally_from_try);
  if (to_finally_from_catch >= 0) patch_jump(c, to_finally_from_catch);
  if (to_finally_from_throw >= 0) patch_jump(c, to_finally_from_throw);

  compile_finally_block(c, node->finally_body);

  c->try_depth--;
}


void compile_switch(sv_compiler_t *c, sv_ast_t *node) {
  int case_count = node->args.count;
  int default_case = -1;
  int *match_to_stub = NULL;
  int *stub_to_body = NULL;

  if (case_count > 0) {
    match_to_stub = calloc((size_t)case_count, sizeof(int));
    stub_to_body = calloc((size_t)case_count, sizeof(int));
    for (int i = 0; i < case_count; i++) {
      match_to_stub[i] = -1;
      stub_to_body[i] = -1;
    }
  }

  compile_expr(c, node->cond);  
  begin_scope(c);
  push_loop(c, c->code_len, NULL, 0, true);

  for (int i = 0; i < case_count; i++) {
    sv_ast_t *cas = node->args.items[i];
    if (!cas->left) {
      default_case = i;
      continue;
    }
    emit_op(c, OP_DUP);
    compile_expr(c, cas->left);
    emit_op(c, OP_SEQ);
    match_to_stub[i] = emit_jump(c, OP_JMP_TRUE);
  }

  int miss_jump = emit_jump(c, OP_JMP);
  int default_to_body = -1;
  int miss_to_end = -1;

  for (int i = 0; i < case_count; i++) {
    if (match_to_stub[i] < 0) continue;
    patch_jump(c, match_to_stub[i]);
    emit_op(c, OP_POP);
    stub_to_body[i] = emit_jump(c, OP_JMP);
  }

  patch_jump(c, miss_jump);
  emit_op(c, OP_POP);
  if (default_case >= 0)
    default_to_body = emit_jump(c, OP_JMP);
  else
    miss_to_end = emit_jump(c, OP_JMP);

  for (int i = 0; i < case_count; i++) {
    if (i == default_case && default_to_body >= 0)
      patch_jump(c, default_to_body);
    if (stub_to_body[i] >= 0)
      patch_jump(c, stub_to_body[i]);

    sv_ast_t *cas = node->args.items[i];
    compile_stmts(c, &cas->args);
  }

  if (miss_to_end >= 0)
    patch_jump(c, miss_to_end);

  free(match_to_stub);
  free(stub_to_body);
  pop_loop(c);
  end_scope(c);
}


static inline bool is_loop_node(sv_ast_t *n) {
  return n && 
    (n->type == N_WHILE || n->type == N_DO_WHILE ||
    n->type == N_FOR    || n->type == N_FOR_IN   ||
    n->type == N_FOR_OF || n->type == N_FOR_AWAIT_OF);
}

void compile_label(sv_compiler_t *c, sv_ast_t *node) {
  if (is_loop_node(node->body)) {
    c->pending_label = node->str;
    c->pending_label_len = node->len;
    compile_stmt(c, node->body);
    c->pending_label = NULL;
    c->pending_label_len = 0;
  } else {
    push_loop(c, c->code_len, node->str, node->len, false);
    compile_stmt(c, node->body);
    pop_loop(c);
  }
}


static void emit_field_inits(sv_compiler_t *c, sv_ast_t **fields, int count) {
  sv_compiler_t *enc = c->enclosing;
  for (int i = 0; i < count; i++) {
    sv_ast_t *m = fields[i];
    emit_op(c, OP_THIS);
    if (m->right) compile_expr(c, m->right);
    else emit_op(c, OP_UNDEF);
    if (m->flags & FN_COMPUTED) {
      int key_local = enc->computed_key_locals[i];
      enc->locals[key_local].captured = true;
      uint16_t slot = (uint16_t)local_to_frame_slot(enc, key_local);
      int uv = add_upvalue(c, slot, true, false);
      emit_op(c, OP_GET_UPVAL);
      emit_u16(c, (uint16_t)uv);
    } else {
      compile_static_property_key(c, m->left);
    }
    emit_op(c, OP_SWAP);
    emit_op(c, OP_DEFINE_METHOD_COMP);
    emit(c, 0);
    emit_op(c, OP_POP);
  }
}


static sv_ast_t *find_class_constructor(sv_ast_t *node) {
  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *m = node->args.items[i];
    if (m->type == N_METHOD && m->left && m->left->type == N_IDENT &&
        m->left->len == 11 &&
        memcmp(m->left->str, "constructor", 11) == 0) {
      return m;
    }
  }
  return NULL;
}

static inline bool is_class_method_def(const sv_ast_t *m) {
  return 
    m && m->right && m->right->type == N_FUNC &&
    (m->right->flags & FN_METHOD);
}

static void compile_class_method(
  sv_compiler_t *c, sv_ast_t *m,
  int ctor_local, int proto_local,
  int preeval_key
) {
  bool is_static = !!(m->flags & FN_STATIC);
  int home_local = is_static ? ctor_local : proto_local;
  bool is_fn = is_class_method_def(m);

  if (is_fn) {
    if (is_static) m->right->flags |= FN_STATIC;
    compile_func_expr(c, m->right);   
    emit_get_local(c, home_local);    
    emit_op(c, OP_SET_HOME_OBJ);      
    emit_op(c, OP_SWAP);              
  } else {
    emit_get_local(c, home_local);    
    if (m->right) compile_expr(c, m->right);
    else emit_op(c, OP_UNDEF);        
  }

  uint8_t method_flags = 0;
  if (m->flags & FN_GETTER) method_flags |= 1;
  if (m->flags & FN_SETTER) method_flags |= 2;

  if (m->flags & FN_COMPUTED) {
    if (preeval_key >= 0) emit_get_local(c, preeval_key);
    else compile_expr(c, m->left);
  } else compile_static_property_key(c, m->left); 
  emit_op(c, OP_SWAP);                          
  emit_op(c, OP_DEFINE_METHOD_COMP);
  emit(c, method_flags);                        
  emit_op(c, OP_POP);
}

static inline int compile_class_precompute_key(sv_compiler_t *c, sv_ast_t *key_expr) {
  compile_expr(c, key_expr);
  int loc = add_local(c, "", 0, false, c->scope_depth);
  emit_put_local(c, loc);
  return loc;
}

void compile_class(sv_compiler_t *c, sv_ast_t *node) {
  int outer_name_local = -1;
  bool class_repl_top = is_repl_top_level(c);

  sv_ast_t *ctor_method = NULL;
  bool has_static_name = false;

  int field_count = 0;
  int computed_method_count = 0;

  if (node->str) outer_name_local = resolve_local(c, node->str, node->len);
  if (node->left) compile_expr(c, node->left);
  else emit_op(c, OP_UNDEF);

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *m = node->args.items[i];
    if (m->type != N_METHOD) continue;
    
    if (
      !(m->flags & FN_STATIC) &&
      !(m->flags & FN_COMPUTED) &&
      m->left && m->left->type == N_IDENT &&
      m->left->len == 11 &&
      memcmp(m->left->str, "constructor", 11) == 0
    ) { ctor_method = m; continue; }
    
    if (
      (m->flags & FN_STATIC) &&
      !(m->flags & FN_COMPUTED) &&
      m->left && m->left->str &&
      m->left->len == 4 &&
      memcmp(m->left->str, "name", 4) == 0
    ) has_static_name = true;
    
    bool is_fn = is_class_method_def(m);
    if (!(m->flags & FN_STATIC) && !is_fn) field_count++;
    if (node->str && (m->flags & FN_COMPUTED) && (is_fn || (m->flags & FN_STATIC))) computed_method_count++;
  }

  sv_ast_t **field_inits = NULL;
  int *computed_key_locals = NULL;
  int *method_comp_keys = NULL;
  if (field_count > 0) {
    field_inits = malloc(sizeof(sv_ast_t *) * field_count);
    computed_key_locals = malloc(sizeof(int) * field_count);
  }
  if (computed_method_count > 0) {
    method_comp_keys = malloc(sizeof(int) * node->args.count);
    for (int i = 0; i < node->args.count; i++) method_comp_keys[i] = -1;
  }

  if (field_count > 0 || method_comp_keys) {
  int fi = 0;
  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *m = node->args.items[i];
    if (m->type != N_METHOD || m == ctor_method) continue;
    
    bool is_fn = is_class_method_def(m);
    bool is_instance_field = !(m->flags & FN_STATIC) && !is_fn;
    
    if (is_instance_field) {
      if (field_inits) field_inits[fi] = m;
      if (computed_key_locals) computed_key_locals[fi] = (m->flags & FN_COMPUTED)
        ? compile_class_precompute_key(c, m->left) : -1;
      fi++;
      continue;
    }
    
    if (!method_comp_keys || !(m->flags & FN_COMPUTED)) continue;
    method_comp_keys[i] = compile_class_precompute_key(c, m->left);
  }}

  int inner_name_local = -1;
  if (node->str) {
    begin_scope(c);
    inner_name_local = add_local(c, node->str, node->len, true, c->scope_depth);
  }

  if (ctor_method && ctor_method->right) {
    c->field_inits = field_inits;
    c->field_init_count = field_count;
    c->computed_key_locals = computed_key_locals;
    compile_func_expr(c, ctor_method->right);
    c->field_inits = NULL;
    c->field_init_count = 0;
    c->computed_key_locals = NULL;
  } else if (field_count > 0) {
    c->computed_key_locals = computed_key_locals;
    sv_compiler_t comp;
    sv_compile_ctx_init_child(&comp, c, NULL, c->mode);

    if (node->left) {
      emit_op(&comp, OP_THIS);
      emit_op(&comp, OP_SPECIAL_OBJ);
      emit(&comp, 2);
      emit_op(&comp, OP_SWAP);
      emit_op(&comp, OP_SPECIAL_OBJ);
      emit(&comp, 0);
      emit_op(&comp, OP_SUPER_APPLY);
      emit_u16(&comp, 1);
      emit_op(&comp, OP_POP);
    }

    emit_field_inits(&comp, field_inits, field_count);
    emit_op(&comp, OP_RETURN_UNDEF);

    sv_func_t *fn = code_arena_bump(sizeof(sv_func_t));
    memset(fn, 0, sizeof(sv_func_t));
    fn->code = code_arena_bump((size_t)comp.code_len);
    memcpy(fn->code, comp.code, (size_t)comp.code_len);
    fn->code_len = comp.code_len;
    sv_func_init_obj_sites(fn);
    if (comp.const_count > 0) {
      fn->constants = code_arena_bump((size_t)comp.const_count * sizeof(ant_value_t));
      memcpy(fn->constants, comp.constants, (size_t)comp.const_count * sizeof(ant_value_t));
      fn->const_count = comp.const_count;
      build_gc_const_tables(fn);
    }
    if (comp.atom_count > 0) {
      fn->atoms = code_arena_bump((size_t)comp.atom_count * sizeof(sv_atom_t));
      memcpy(fn->atoms, comp.atoms, (size_t)comp.atom_count * sizeof(sv_atom_t));
      fn->atom_count = comp.atom_count;
    }
    fn->ic_count = (uint16_t)comp.ic_count;
    if (fn->ic_count > 0) {
      fn->ic_slots = code_arena_bump((size_t)fn->ic_count * sizeof(sv_ic_entry_t));
      memset(fn->ic_slots, 0, (size_t)fn->ic_count * sizeof(sv_ic_entry_t));
    }
    if (comp.upvalue_count > 0) {
      fn->upval_descs = code_arena_bump(
        (size_t)comp.upvalue_count * sizeof(sv_upval_desc_t));
      memcpy(fn->upval_descs, comp.upval_descs,
             (size_t)comp.upvalue_count * sizeof(sv_upval_desc_t));
      fn->upvalue_count = comp.upvalue_count;
    }
    fn->max_locals = comp.max_local_count;
    fn->max_stack = fn->max_locals + 64;
    fn->local_type_count = fn->max_locals;
    if (fn->max_locals > 0) {
      fn->local_types = code_arena_bump((size_t)fn->max_locals * sizeof(sv_type_info_t));
      memset(fn->local_types, 0, (size_t)fn->max_locals * sizeof(sv_type_info_t));
      if (comp.slot_types) {
        int ncopy = fn->max_locals < comp.slot_type_cap ? fn->max_locals : comp.slot_type_cap;
        memcpy(fn->local_types, comp.slot_types, (size_t)ncopy * sizeof(sv_type_info_t));
      }
    }
    fn->param_count = comp.param_count;
    fn->is_strict = comp.is_strict;
    fn->filename = c->js->filename;
    fn->source_line = (int)node->line;
    if (node->str && node->len > 0) {
      char *name = code_arena_bump(node->len + 1);
      memcpy(name, node->str, node->len);
      name[node->len] = '\0';
      fn->name = name;
    }
    sv_compile_ctx_cleanup(&comp);
    int idx = add_constant(c, mkval(T_NTARG, (uintptr_t)fn));
    emit_op(c, OP_CLOSURE);
    emit_u32(c, (uint32_t)idx);
  } else emit_op(c, OP_UNDEF);
  
  free(field_inits);
  free(computed_key_locals);
  c->computed_key_locals = NULL;

  if (node->str && !has_static_name) {
    int atom = add_atom(c, node->str, node->len);
    emit_op(c, OP_DEFINE_CLASS);
    emit_u32(c, (uint32_t)atom);
    emit(c, 1);
  } else {
    emit_op(c, OP_DEFINE_CLASS);
    emit_u32(c, 0);
    emit(c, 0);
  }
  
  emit_u32(c, node->src_off);
  emit_u32(c, node->src_end);

  int proto_local = add_local(c, "", 0, false, c->scope_depth);
  int ctor_local = add_local(c, "", 0, false, c->scope_depth);
  emit_put_local(c, proto_local);
  emit_put_local(c, ctor_local);

  if (inner_name_local >= 0) {
    emit_get_local(c, ctor_local);
    emit_put_local(c, inner_name_local);
  }

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *m = node->args.items[i];
    if (m->type == N_STATIC_BLOCK) {
      begin_scope(c);
      compile_stmts(c, &m->args);
      end_scope(c);
      continue;
    }
    
    if (m->type != N_METHOD) continue;
    if (m == ctor_method) continue;
    
    bool is_fn = is_class_method_def(m);
    if (!is_fn && !(m->flags & FN_STATIC)) continue;
    
    compile_class_method(
      c, m, ctor_local, proto_local, 
      method_comp_keys ? method_comp_keys[i] : -1
    );
  }

  free(method_comp_keys);
  emit_get_local(c, ctor_local);

  if (class_repl_top && node->str) {
    emit_op(c, OP_DUP);
    emit_atom_op(c, OP_PUT_GLOBAL, node->str, node->len);
  } else if (outer_name_local >= 0) {
    emit_op(c, OP_DUP);
    emit_put_local(c, outer_name_local);
    c->locals[outer_name_local].is_tdz = false;
  }

  if (node->str) end_scope(c);
}

static bool ast_contains_await_expr(const sv_ast_t *node) {
  if (!node) return false;
  
  if (node->type == N_AWAIT) return true;
  if (node->type == N_FUNC) return false;

  if (ast_contains_await_expr(node->left))         return true;
  if (ast_contains_await_expr(node->right))        return true;
  if (ast_contains_await_expr(node->cond))         return true;
  if (ast_contains_await_expr(node->body))         return true;
  if (ast_contains_await_expr(node->catch_body))   return true;
  if (ast_contains_await_expr(node->finally_body)) return true;
  if (ast_contains_await_expr(node->catch_param))  return true;
  if (ast_contains_await_expr(node->init))         return true;
  if (ast_contains_await_expr(node->update))       return true;

  for (int i = 0; i < node->args.count; i++) {
    if (ast_contains_await_expr(node->args.items[i])) return true;
  }

  return false;
}

static bool func_params_contain_await(const sv_ast_t *node) {
  if (!node) return false;
  for (int i = 0; i < node->args.count; i++) {
    if (ast_contains_await_expr(node->args.items[i])) return true;
  }
  return false;
}

sv_func_t *compile_function_body(
  sv_compiler_t *enclosing,
  sv_ast_t *node,
  sv_compile_mode_t mode
) {
  if ((node->flags & FN_ASYNC) && func_params_contain_await(node)) {
    js_mkerr_typed(
      enclosing->js, JS_ERR_SYNTAX,
      "await is not allowed in async function parameters");
    return NULL;
  }

  sv_compiler_t comp;
  sv_compile_ctx_init_child(&comp, enclosing, node, mode);

  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *p = node->args.items[i];
    if (p->type == N_IDENT) {
      add_local(&comp, p->str, p->len, false, -1);  
    } else if (p->type == N_REST && p->right && p->right->type == N_IDENT) {
      add_local(&comp, p->right->str, p->right->len, false, -1);
    } else if (p->type == N_ASSIGN_PAT && p->left && p->left->type == N_IDENT) {
      add_local(&comp, p->left->str, p->left->len, false, -1);
    } else add_local(&comp, "", 0, false, -1);
  }

  comp.param_locals = comp.local_count;

  bool has_own_use_strict = false;
  if (node->body && node->body->type == N_BLOCK) for (int i = 0; i < node->body->args.count; i++) {
    sv_ast_t *stmt = node->body->args.items[i];
    if (!stmt || stmt->type == N_EMPTY) continue;
    if (stmt->type != N_STRING) break;
    if (sv_ast_is_use_strict(comp.js, stmt)) {
      has_own_use_strict = true;
      comp.is_strict = true;
    }
  }

  if (comp.is_strict) {
    const char *param_names[256];
    uint32_t param_lens[256];
    int param_name_count = 0;

    for (int i = 0; i < node->args.count; i++) {
      sv_ast_t *p = node->args.items[i];
      const char *pname = NULL;
      uint32_t plen = 0;

      if (p && p->type == N_IDENT) {
        pname = p->str; plen = p->len;
      } else if (p && p->type == N_REST && p->right && p->right->type == N_IDENT) {
        pname = p->right->str; plen = p->right->len;
      } else if (p && p->type == N_ASSIGN_PAT && p->left && p->left->type == N_IDENT) {
        pname = p->left->str; plen = p->left->len;
      }

      if (!pname || plen == 0) continue;

      if (is_strict_restricted_ident(pname, plen)) {
        js_mkerr_typed(
          comp.js, JS_ERR_SYNTAX,
          "strict mode forbids '%.*s' as a parameter name",
          (int)plen, pname);
        return NULL;
      }

      for (int j = 0; j < param_name_count; j++) {
        if (param_lens[j] == plen &&
            memcmp(param_names[j], pname, plen) == 0) {
          js_mkerr_typed(
            comp.js, JS_ERR_SYNTAX,
            "duplicate parameter name '%.*s' in strict mode",
            (int)plen, pname);
          return NULL;
        }
      }

      if (param_name_count < (int)(sizeof(param_names) / sizeof(param_names[0]))) {
        param_names[param_name_count] = pname;
        param_lens[param_name_count] = plen;
        param_name_count++;
      }
    }
  }

  bool has_non_simple_params = false;
  for (int i = 0; i < node->args.count; i++) {
    sv_ast_t *p = node->args.items[i];
    if (p->type != N_IDENT) { has_non_simple_params = true; break; }
  }

  if (has_own_use_strict && has_non_simple_params) {
    js_mkerr_typed(
      comp.js, JS_ERR_SYNTAX,
      "Illegal 'use strict' directive in function with non-simple parameter list");
    return NULL;
  }
  
  bool repl_top = is_repl_top_level(&comp);
  if (!has_non_simple_params && node->body) {
    if (node->body->type == N_BLOCK) {
      if (!repl_top) {
        for (int i = 0; i < node->body->args.count; i++)
          hoist_var_decls(&comp, node->body->args.items[i]);
        hoist_lexical_decls(&comp, &node->body->args);
      }
      hoist_func_decls(&comp, &node->body->args);
    } else if (!repl_top) hoist_var_decls(&comp, node->body);
  }

  if (!has_non_simple_params) {
    for (int i = 0; i < node->args.count; i++) {
      sv_ast_t *p = node->args.items[i];
      if (p->type == N_ASSIGN_PAT) {
        emit_op(&comp, OP_GET_ARG);
        emit_u16(&comp, (uint16_t)i);
        emit_op(&comp, OP_DUP);
        emit_op(&comp, OP_IS_UNDEF);
        int skip = emit_jump(&comp, OP_JMP_FALSE);
        emit_op(&comp, OP_POP);
        compile_expr(&comp, p->right);
        patch_jump(&comp, skip);
        if (p->left && p->left->type == N_IDENT) {
          emit_op(&comp, OP_PUT_ARG);
          emit_u16(&comp, (uint16_t)i);
        } else if (p->left) {
          compile_destructure_binding(&comp, p->left, SV_VAR_LET);
          emit_op(&comp, OP_POP);
        } else emit_op(&comp, OP_POP);
      } else if (
        p->type == N_ARRAY_PAT || p->type == N_ARRAY ||
        p->type == N_OBJECT_PAT || p->type == N_OBJECT) {
        emit_op(&comp, OP_GET_ARG);
        emit_u16(&comp, (uint16_t)i);
        compile_destructure_binding(&comp, p, SV_VAR_LET);
        emit_op(&comp, OP_POP);
      } else if (p->type == N_REST && p->right && p->right->type == N_IDENT) {
        emit_op(&comp, OP_REST);
        emit_u16(&comp, (uint16_t)i);
        int loc = resolve_local(&comp, p->right->str, p->right->len);
        if (loc >= 0) {
          emit_op(&comp, OP_PUT_ARG);
          emit_u16(&comp, (uint16_t)loc);
        }
      } else if (p->type == N_REST && p->right) {
        emit_op(&comp, OP_REST);
        emit_u16(&comp, (uint16_t)i);
        compile_destructure_binding(&comp, p->right, SV_VAR_LET);
        emit_op(&comp, OP_POP);
      }
    }
  } else {
    int *param_bind_locals = NULL;
    int  param_bind_count  = 0;
    int  param_bind_cap    = 0;

    for (int i = 0; i < node->args.count; i++) {
      sv_ast_t *p = node->args.items[i];
      const char *pname = NULL;
      uint32_t    plen  = 0;
      if (p->type == N_IDENT) {
        pname = p->str; plen = p->len;
      } else if ((
        p->type == N_ASSIGN_PAT) && p->left &&
        p->left->type == N_IDENT) {
        pname = p->left->str; plen = p->left->len;
      } else if (
        p->type == N_REST && p->right &&
        p->right->type == N_IDENT) {
        pname = p->right->str; plen = p->right->len;
      }

      int lb = comp.local_count;
      if (pname && plen) {
        int loc = add_local(&comp, pname, plen, false, 0);
        comp.locals[loc].is_tdz = true;
        set_local_inferred_type(&comp, loc, SV_TI_UNKNOWN);
        int slot = loc - comp.param_locals;
        emit_op(&comp, OP_SET_LOCAL_UNDEF);
        emit_u16(&comp, (uint16_t)slot);
      }

      if (param_bind_count >= param_bind_cap) {
        param_bind_cap = param_bind_cap ? param_bind_cap * 2 : 8;
        param_bind_locals = realloc(
          param_bind_locals,
          (size_t)param_bind_cap * sizeof(int));
      }
      param_bind_locals[param_bind_count++] = lb;
    }

    for (int i = 0; i < node->args.count; i++) {
      sv_ast_t *p = node->args.items[i];
      int bind_lb = param_bind_locals[i];

      if (p->type == N_IDENT) {
        if (bind_lb < comp.local_count) {
          emit_op(&comp, OP_GET_ARG);
          emit_u16(&comp, (uint16_t)i);
          int slot = bind_lb - comp.param_locals;
          comp.locals[bind_lb].is_tdz = false;
          if (slot <= 255) { emit_op(&comp, OP_PUT_LOCAL8); emit(&comp, (uint8_t)slot); }
          else { emit_op(&comp, OP_PUT_LOCAL); emit_u16(&comp, (uint16_t)slot); }
          set_local_inferred_type(&comp, bind_lb, SV_TI_UNKNOWN);
        }
      } else if (p->type == N_ASSIGN_PAT) {
        emit_op(&comp, OP_GET_ARG);
        emit_u16(&comp, (uint16_t)i);
        emit_op(&comp, OP_DUP);
        emit_op(&comp, OP_IS_UNDEF);
        int skip = emit_jump(&comp, OP_JMP_FALSE);
        emit_op(&comp, OP_POP);
        compile_expr(&comp, p->right);
        patch_jump(&comp, skip);
        if (p->left && p->left->type == N_IDENT && bind_lb < comp.local_count) {
          int slot = bind_lb - comp.param_locals;
          comp.locals[bind_lb].is_tdz = false;
          if (slot <= 255) { emit_op(&comp, OP_PUT_LOCAL8); emit(&comp, (uint8_t)slot); }
          else { emit_op(&comp, OP_PUT_LOCAL); emit_u16(&comp, (uint16_t)slot); }
          set_local_inferred_type(&comp, bind_lb, SV_TI_UNKNOWN);
        } else if (p->left) {
          compile_destructure_binding(&comp, p->left, SV_VAR_LET);
          emit_op(&comp, OP_POP);
        } else {
          emit_op(&comp, OP_POP);
        }
      } else if (
        p->type == N_ARRAY_PAT || p->type == N_ARRAY ||
        p->type == N_OBJECT_PAT || p->type == N_OBJECT) {
        emit_op(&comp, OP_GET_ARG);
        emit_u16(&comp, (uint16_t)i);
        compile_destructure_binding(&comp, p, SV_VAR_LET);
        emit_op(&comp, OP_POP);
      } else if (
        p->type == N_REST && p->right && p->right->type == N_IDENT &&
        bind_lb < comp.local_count) {
        emit_op(&comp, OP_REST);
        emit_u16(&comp, (uint16_t)i);
        int slot = bind_lb - comp.param_locals;
        comp.locals[bind_lb].is_tdz = false;
        if (slot <= 255) { emit_op(&comp, OP_PUT_LOCAL8); emit(&comp, (uint8_t)slot); }
        else { emit_op(&comp, OP_PUT_LOCAL); emit_u16(&comp, (uint16_t)slot); }
        set_local_inferred_type(&comp, bind_lb, SV_TI_UNKNOWN);
      } else if (p->type == N_REST && p->right) {
        emit_op(&comp, OP_REST);
        emit_u16(&comp, (uint16_t)i);
        compile_destructure_binding(&comp, p->right, SV_VAR_LET);
        emit_op(&comp, OP_POP);
      }
    }

    free(param_bind_locals);
    sv_ast_t *body = node->body;

    if (body && body->type != N_BLOCK) {
      if (!repl_top) hoist_var_decls(&comp, body);
    } else if (body) {
      if (!repl_top) {
        for (int i = 0; i < body->args.count; i++)
          hoist_var_decls(&comp, body->args.items[i]);
        hoist_lexical_decls(&comp, &body->args);
      }
      hoist_func_decls(&comp, &body->args);
    }
  }

  if (!comp.is_arrow && has_implicit_arguments_obj(&comp) && (node->flags & FN_USES_ARGS)) {
    static const char args_name[] = "\x01arguments";
    comp.strict_args_local = add_local(&comp, args_name, sizeof(args_name) - 1, false, comp.scope_depth);
    emit_op(&comp, OP_SPECIAL_OBJ);
    emit(&comp, 0);
    emit_put_local(&comp, comp.strict_args_local);
  }

  if (!comp.is_arrow && comp.enclosing && (node->flags & FN_USES_NEW_TARGET)) {
    static const char nt_name[] = "\x01new.target";
    comp.new_target_local = add_local(&comp, nt_name, sizeof(nt_name) - 1, false, comp.scope_depth);
    emit_op(&comp, OP_SPECIAL_OBJ);
    emit(&comp, 1);
    emit_put_local(&comp, comp.new_target_local);
  }

  if (!comp.is_arrow && comp.enclosing && (node->flags & (FN_METHOD | FN_GETTER | FN_SETTER | FN_STATIC))) {
    static const char sv_name[] = "\x01super";
    comp.super_local = add_local(&comp, sv_name, sizeof(sv_name) - 1, false, comp.scope_depth);
    emit_op(&comp, OP_SPECIAL_OBJ);
    emit(&comp, 2);
    emit_put_local(&comp, comp.super_local);
  }

  if (enclosing->field_init_count > 0) {
    emit_field_inits(&comp, enclosing->field_inits, enclosing->field_init_count);
  }

  bool body_has_await_using = false;
  bool body_has_using = node->body && node->body->type == N_BLOCK &&
    stmt_list_has_using_decl(&node->body->args, &body_has_await_using);
  int body_using_try_jump = -1;
  int body_using_err_local = -1;
  int old_using_stack = comp.using_stack_local;
  bool old_using_async = comp.using_stack_async;

  if (body_has_using) {
    emit_empty_disposal_stack(&comp);
    int body_using_stack = add_local(&comp, "", 0, false, comp.scope_depth);
    emit_put_local(&comp, body_using_stack);
    body_using_err_local = add_local(&comp, "", 0, false, comp.scope_depth);

    comp.using_stack_local = body_using_stack;
    comp.using_stack_async = body_has_await_using;
    push_using_cleanup(&comp, body_using_stack, comp.scope_depth, body_has_await_using);

    comp.try_depth++;
    body_using_try_jump = emit_jump(&comp, OP_TRY_PUSH);
  }

  if (node->body) {
    if (node->body->type == N_BLOCK) {
      int last_expr_idx = -1;
      if (has_completion_value(&comp) && node->body->args.count > 0) {
        sv_ast_t *last = node->body->args.items[node->body->args.count - 1];
        if (sv_ast_can_be_expression_statement(last))
          last_expr_idx = node->body->args.count - 1;
      }
      for (int i = 0; i < node->body->args.count; i++) {
        sv_ast_t *stmt = node->body->args.items[i];
        if (i == last_expr_idx) {
          compile_expr(&comp, stmt);
          emit_return_from_stack(&comp);
        } else compile_stmt(&comp, stmt);
      }
    } else compile_tail_return_expr(&comp, node->body);
  }

  for (int i = 0; i < comp.deferred_export_count; i++) {
    sv_deferred_export_t *e = &comp.deferred_exports[i];
    compile_export_emit(&comp, e->name, e->len);
  }

  if (body_has_using) {
    emit_op(&comp, OP_TRY_POP);
    comp.try_depth--;

    emit_using_dispose_call(&comp, comp.using_stack_local, -1, body_has_await_using, false);
    emit_op(&comp, OP_POP);
    int end_jump = emit_jump(&comp, OP_JMP);

    patch_jump(&comp, body_using_try_jump);
    int catch_tag = emit_jump(&comp, OP_CATCH);
    emit_put_local(&comp, body_using_err_local);
    emit_using_dispose_call(&comp, comp.using_stack_local, body_using_err_local, body_has_await_using, true);
    if (!body_has_await_using) emit_op(&comp, OP_THROW);
    patch_jump(&comp, catch_tag);
    patch_jump(&comp, end_jump);

    pop_using_cleanup(&comp);
    comp.using_stack_local = old_using_stack;
    comp.using_stack_async = old_using_async;
  }
  
  emit_using_cleanups_to_depth(&comp, -1);
  emit_close_upvals(&comp);
  emit_op(&comp, OP_RETURN_UNDEF);

  int max_locals = comp.max_local_count - comp.param_locals;
  sv_func_t *func = code_arena_bump(sizeof(sv_func_t));
  memset(func, 0, sizeof(sv_func_t));

  func->code = code_arena_bump((size_t)comp.code_len);
  memcpy(func->code, comp.code, (size_t)comp.code_len);
  func->code_len = comp.code_len;
  sv_func_init_obj_sites(func);

  if (comp.const_count > 0) {
    func->constants = code_arena_bump((size_t)comp.const_count * sizeof(ant_value_t));
    memcpy(func->constants, comp.constants, (size_t)comp.const_count * sizeof(ant_value_t));
    func->const_count = comp.const_count;
    build_gc_const_tables(func);
  }

  if (comp.atom_count > 0) {
    func->atoms = code_arena_bump((size_t)comp.atom_count * sizeof(sv_atom_t));
    memcpy(func->atoms, comp.atoms, (size_t)comp.atom_count * sizeof(sv_atom_t));
    func->atom_count = comp.atom_count;
  }

  func->ic_count = (uint16_t)comp.ic_count;
  if (func->ic_count > 0) {
    func->ic_slots = code_arena_bump((size_t)func->ic_count * sizeof(sv_ic_entry_t));
    memset(func->ic_slots, 0, (size_t)func->ic_count * sizeof(sv_ic_entry_t));
  }

  if (comp.upvalue_count > 0) {
    func->upval_descs = code_arena_bump(
      (size_t)comp.upvalue_count * sizeof(sv_upval_desc_t));
    memcpy(func->upval_descs, comp.upval_descs,
      (size_t)comp.upvalue_count * sizeof(sv_upval_desc_t));
    func->upvalue_count = comp.upvalue_count;
  }

  if (comp.srcpos_count > 0) {
    func->srcpos = code_arena_bump((size_t)comp.srcpos_count * sizeof(sv_srcpos_t));
    memcpy(func->srcpos, comp.srcpos, (size_t)comp.srcpos_count * sizeof(sv_srcpos_t));
    func->srcpos_count = comp.srcpos_count;
  }

  if (enclosing->source && enclosing->source_len > 0) {
    func->source = enclosing->source;
    func->source_len = (int)enclosing->source_len;
    func->source_start = (int)node->src_off;
    func->source_end   = (node->src_end > node->src_off)
      ? (int)node->src_end : func->source_len;
  }

  func->max_locals = max_locals;
  func->max_stack = max_locals + 64;
  func->local_type_count = max_locals;
  if (max_locals > 0) {
    func->local_types = code_arena_bump((size_t)max_locals * sizeof(sv_type_info_t));
    memset(func->local_types, 0, (size_t)max_locals * sizeof(sv_type_info_t));
    if (comp.slot_types) {
      int ncopy = max_locals < comp.slot_type_cap ? max_locals : comp.slot_type_cap;
      memcpy(func->local_types, comp.slot_types, (size_t)ncopy * sizeof(sv_type_info_t));
    }
  }
  func->param_count = comp.param_count;
  func->is_strict = comp.is_strict;
  func->is_arrow = comp.is_arrow;
  
  func->is_async = !!(node->flags & FN_ASYNC);
  func->has_await = false;
  func->is_generator = !!(node->flags & FN_GENERATOR);
  func->is_method = !!(node->flags & FN_METHOD);
  func->is_static = !!(node->flags & FN_STATIC);
  func->is_tla = comp.is_tla;
  func->filename = enclosing->filename ? enclosing->filename : enclosing->js->filename;
  func->source_line = (int)node->line;
  
  if (node->str && node->len > 0) {
    char *name = code_arena_bump(node->len + 1);
    memcpy(name, node->str, node->len);
    name[node->len] = '\0';
    func->name = name;
  } else if (enclosing->inferred_name && enclosing->inferred_name_len > 0) {
    char *name = code_arena_bump(enclosing->inferred_name_len + 1);
    memcpy(name, enclosing->inferred_name, enclosing->inferred_name_len);
    name[enclosing->inferred_name_len] = '\0';
    func->name = name;
  }

  if (func->is_async || func->is_tla) {
  const uint8_t *ip = func->code;
  const uint8_t *end = func->code + func->code_len;
  while (ip < end) {
    sv_op_t op = (sv_op_t)*ip;
    if (op == OP_AWAIT || op == OP_FOR_AWAIT_OF || op == OP_AWAIT_ITER_NEXT) {
      func->has_await = true;
      break;
    }
    int sz = sv_op_size[op];
    if (sz <= 0) break;
    ip += sz;
  }}

  sv_compile_ctx_cleanup(&comp);
  return func;
}

static const char *sv_op_names[OP__COUNT] = {
#define OP_DEF(name, size, n_pop, n_push, f) [OP_##name] = #name,
#include "silver/opcode.h"
};

enum {
  SVF_none, SVF_u8, SVF_i8, SVF_u16, SVF_i16, SVF_u32, SVF_i32,
  SVF_atom, SVF_atom_u8, SVF_label, SVF_label8, SVF_loc, SVF_loc8,
  SVF_loc_atom, SVF_arg, SVF_const, SVF_const8, SVF_npop, SVF_var_ref,
};

static const uint8_t sv_op_fmts[OP__COUNT] = {
#define OP_DEF(name, size, n_pop, n_push, f) [OP_##name] = SVF_##f,
#include "silver/opcode.h"
};

void sv_disasm(ant_t *js, sv_func_t *func, const char *label) {
  const char *fname = func->name ? func->name : "";

  fprintf(stderr, "[generated bytecode for function: %s (%p <SharedFunctionInfo %s>)]\n",
    fname, (void *)func, fname);
  fprintf(stderr, "Bytecode length: %d\n", func->code_len);
  fprintf(stderr, "Parameter count %d\n", func->param_count);
  fprintf(stderr, "Register count %d\n", func->max_locals);
  fprintf(stderr, "Frame size %d\n", func->max_locals * (int)sizeof(ant_value_t));

  int pc = 0;
  while (pc < func->code_len) {
    uint8_t op = func->code[pc];
    const char *name = (op < OP__COUNT) ? sv_op_names[op] : "???";
    uint8_t size = (op < OP__COUNT) ? sv_op_size[op] : 1;
    uint8_t fmt = (op < OP__COUNT) ? sv_op_fmts[op] : SVF_none;

    uint32_t line, col;
    if (sv_lookup_srcpos(func, pc, &line, &col))
      fprintf(stderr, "%5u S> ", line);
    else
      fprintf(stderr, "         ");

    fprintf(stderr, "%p @ %4d : ", (void *)(func->code + pc), pc);

    char hex[32];
    int hlen = 0;
    for (int i = 0; i < size && i < 8; i++)
      hlen += snprintf(hex + hlen, sizeof(hex) - hlen, "%02x ", func->code[pc + i]);
      
    fprintf(stderr, "%-18s", hex);
    fprintf(stderr, "%s", name ? name : "???");

    switch (fmt) {
    case SVF_u8:
      fprintf(stderr, " [%d]", func->code[pc + 1]);
      break;
    case SVF_i8:
      fprintf(stderr, " [%d]", (int8_t)func->code[pc + 1]);
      break;
    case SVF_u16:
      fprintf(stderr, " [%d]", sv_get_u16(func->code + pc + 1));
      break;
    case SVF_i16:
      fprintf(stderr, " [%d]", (int16_t)sv_get_u16(func->code + pc + 1));
      break;
    case SVF_u32:
      fprintf(stderr, " [%d]", (int)sv_get_u32(func->code + pc + 1));
      break;
    case SVF_i32:
      fprintf(stderr, " [%d]", (int32_t)sv_get_u32(func->code + pc + 1));
      break;
    case SVF_atom: {
      uint32_t idx = sv_get_u32(func->code + pc + 1);
      if (idx < (uint32_t)func->atom_count)
        fprintf(stderr, " [%.*s]", (int)func->atoms[idx].len, func->atoms[idx].str);
      else
        fprintf(stderr, " a%u", idx);
      if ((op == OP_GET_GLOBAL || op == OP_GET_GLOBAL_UNDEF ||
           op == OP_GET_FIELD || op == OP_GET_FIELD2 || op == OP_PUT_FIELD) && size >= 7)
        fprintf(stderr, " ic[%u]", sv_get_u16(func->code + pc + 5));
      break;
    }
    case SVF_atom_u8: {
      uint32_t idx = sv_get_u32(func->code + pc + 1);
      uint8_t extra = func->code[pc + 5];
      if (idx < (uint32_t)func->atom_count)
        fprintf(stderr, " [%.*s], [%d]", (int)func->atoms[idx].len, func->atoms[idx].str, extra);
      else
        fprintf(stderr, " a%u, [%d]", idx, extra);
      break;
    }
    case SVF_label: {
      int32_t off = (int32_t)sv_get_u32(func->code + pc + 1);
      fprintf(stderr, " [%d] (%d)", off, pc + size + off);
      break;
    }
    case SVF_label8: {
      int8_t off = (int8_t)func->code[pc + 1];
      fprintf(stderr, " [%d] (%d)", off, pc + size + off);
      break;
    }
    case SVF_loc:
      fprintf(stderr, " r%d", sv_get_u16(func->code + pc + 1));
      break;
    case SVF_loc8:
      fprintf(stderr, " r%d", func->code[pc + 1]);
      break;
    case SVF_loc_atom: {
      uint16_t slot = sv_get_u16(func->code + pc + 1);
      uint32_t aidx = sv_get_u32(func->code + pc + 3);
      fprintf(stderr, " r%d", slot);
      if (aidx < (uint32_t)func->atom_count)
        fprintf(stderr, ", [%.*s]", (int)func->atoms[aidx].len, func->atoms[aidx].str);
      else
        fprintf(stderr, ", a%u", aidx);
      break;
    }
    case SVF_arg:
      fprintf(stderr, " a%d", sv_get_u16(func->code + pc + 1));
      break;
    case SVF_const: {
      uint32_t idx = sv_get_u32(func->code + pc + 1);
      fprintf(stderr, " [%u]", idx);
      break;
    }
    case SVF_const8:
      fprintf(stderr, " [%d]", func->code[pc + 1]);
      break;
    case SVF_npop:
      fprintf(stderr, " %d", sv_get_u16(func->code + pc + 1));
      break;
    case SVF_var_ref:
      fprintf(stderr, " [%d]", sv_get_u16(func->code + pc + 1));
      break;
    default:
      break;
    }

    fprintf(stderr, "\n");
    pc += size;
  }

  fprintf(stderr, "Constant pool (size = %d)\n", func->const_count);
  for (int i = 0; i < func->const_count; i++) {
    ant_value_t v = func->constants[i];
    uint8_t t = vtype(v);
    if (t == T_STR) {
      ant_offset_t slen;
      ant_offset_t soff = vstr(js, v, &slen);
      fprintf(stderr, "           %d: <String[%d]: #%.*s>\n", i, (int)slen, (int)slen, (const char *)(uintptr_t)soff);
    } else if (t == T_NUM) {
      fprintf(stderr, "           %d: <Number [%g]>\n", i, tod(v));
    } else if (t == T_CFUNC) {
      sv_func_t *child = (sv_func_t *)(uintptr_t)vdata(v);
      const char *cname = child->name ? child->name : "";
      fprintf(stderr, "           %d: <SharedFunctionInfo %s>\n", i, cname);
    } else fprintf(stderr, "           %d: <Unknown type=%d>\n", i, t);
  }

  if (func->atom_count > 0) {
    fprintf(stderr, "Atom table (size = %d)\n", func->atom_count);
    for (int i = 0; i < func->atom_count; i++)
      fprintf(stderr, "           %d: <String[%d]: #%.*s>\n",
        i, (int)func->atoms[i].len, (int)func->atoms[i].len, func->atoms[i].str);
  }

  fprintf(stderr, "Handler Table (size = 0)\n");
  fprintf(stderr, "Source Position Table (size = %d)\n", func->srcpos_count);
  fprintf(stderr, "\n");

  for (int i = 0; i < func->const_count; i++) {
  if (vtype(func->constants[i]) == T_NTARG) {
    sv_func_t *child = (sv_func_t *)(uintptr_t)vdata(func->constants[i]);
    char child_label[256];
    snprintf(child_label, sizeof(child_label), "%s/closure[%d]", label, i);
    sv_disasm(js, child, child_label);
  }}
}

sv_func_t *sv_compile(ant_t *js, sv_ast_t *program, sv_compile_mode_t mode, const char *source, ant_offset_t source_len) {
  if (!program || program->type != N_PROGRAM) return NULL;
  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] start kind=program mode=%d len=%u body=%d strict=%d\n",
    (int)mode, (unsigned)source_len,
    program->args.count, (program->flags & FN_PARSE_STRICT) != 0 ? 1 : 0
  );
  
  static const char *k_top_name_script = "<script>";
  static const char *k_top_name_module = "<module>";
  static const char *k_top_name_eval   = "<eval>";
  static const char *k_top_name_repl   = "<repl>";
  const        char *top_name          = k_top_name_script;
  
  switch (mode) {
    case SV_COMPILE_MODULE: top_name = k_top_name_module; break;
    case SV_COMPILE_EVAL:   top_name = k_top_name_eval; break;
    case SV_COMPILE_REPL:   top_name = k_top_name_repl; break;
    case SV_COMPILE_SCRIPT:
    default:                top_name = k_top_name_script; break;
  }

  sv_ast_t top_fn;
  memset(&top_fn, 0, sizeof(top_fn));
  
  top_fn.type = N_FUNC;
  top_fn.line = 1;
  top_fn.str = top_name;
  top_fn.len = (uint32_t)strlen(top_name);
  top_fn.src_off = 0;
  top_fn.src_end = (source_len > 0) ? (uint32_t)source_len : 0;
  top_fn.body = sv_ast_new(N_BLOCK);
  top_fn.body->args = program->args;

  sv_compiler_t root;
  sv_compile_ctx_init_root(
    &root, js, js->filename,
    pin_source_text(source, source_len),
    source_len, mode,
    (program->flags & FN_PARSE_STRICT) != 0,  NULL
  );
  
  root.line_table = sv_compile_ctx_build_line_table(root.source, source_len);
  sv_func_t *func = compile_function_body(&root, &top_fn, mode);
  sv_compile_ctx_free_line_table(root.line_table);
  
  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] end kind=program mode=%d thrown=%d func=%p\n",
    (int)mode, js->thrown_exists ? 1 : 0, (void *)func
  );
  
  if (js->thrown_exists || !func) return NULL;
  return func;
}

sv_func_t *sv_compile_function(ant_t *js, const char *source, size_t len, bool is_async, bool is_generator) {
  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] start kind=function len=%u async=%d generator=%d\n",
    (unsigned)len, is_async ? 1 : 0, is_generator ? 1 : 0
  );
  
  const char *prefix = is_async
    ? (is_generator ? "(async function*" : "(async function")
    : (is_generator ? "(function*" : "(function");
    
  size_t prefix_len = strlen(prefix);
  size_t wrapped_len = prefix_len + len + 1;

  char *wrapped = malloc(wrapped_len + 1);
  if (!wrapped) return NULL;

  memcpy(wrapped, prefix, prefix_len);
  memcpy(wrapped + prefix_len, source, len);
  wrapped[prefix_len + len] = ')';
  wrapped[wrapped_len] = '\0';
  
  bool parse_strict = sv_vm_is_strict(js->vm);
  sv_ast_t *program = sv_parse(js, wrapped, (ant_offset_t)wrapped_len, parse_strict);
  if (!program) { free(wrapped); return NULL; }

  sv_ast_t *func_node = NULL;
  if (program->args.count > 0) {
    sv_ast_t *stmt = program->args.items[0];
    if (stmt && stmt->type == N_FUNC)  func_node = stmt;
    else if (stmt && stmt->left && stmt->left->type == N_FUNC) func_node = stmt->left;
  }

  if (!func_node) { free(wrapped); return NULL; }

  sv_compiler_t root;
  sv_compile_ctx_init_root(
    &root, js, js->filename,
    pin_source_text(wrapped, (ant_offset_t)wrapped_len),
    (ant_offset_t)wrapped_len, SV_COMPILE_SCRIPT,
    (program->flags & FN_PARSE_STRICT) != 0, NULL
  );
  
  root.line_table = sv_compile_ctx_build_line_table(root.source, (ant_offset_t)wrapped_len);
  sv_func_t *func = compile_function_body(&root, func_node, SV_COMPILE_SCRIPT);
  sv_compile_ctx_free_line_table(root.line_table);
  free(wrapped);

  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] end kind=function thrown=%d func=%p\n",
    js->thrown_exists ? 1 : 0, (void *)func
  );
    
  if (js->thrown_exists || !func) return NULL;
  return func;
}

sv_func_t *sv_compile_function_with_params(
  ant_t *js,
  const sv_param_t *params,
  int param_count,
  const char *body,
  size_t body_len,
  bool is_async
) {
  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] start kind=function-with-params len=%u params=%d async=%d\n",
    (unsigned)body_len, param_count, is_async ? 1 : 0
  );

  if (!body) {
    body = "";
    body_len = 0;
  }

  bool parse_strict = sv_vm_is_strict(js->vm);
  sv_ast_t *program = sv_parse(js, body, (ant_offset_t)body_len, parse_strict);
  if (!program) return NULL;

  static const char *k_top_name_function = "<function>";
  static const char *k_top_name_async_function = "<async function>";
  const char *top_name = is_async ? k_top_name_async_function : k_top_name_function;

  sv_ast_t top_fn;
  memset(&top_fn, 0, sizeof(top_fn));

  top_fn.type = N_FUNC;
  top_fn.line = 1;
  top_fn.str = top_name;
  top_fn.len = (uint32_t)strlen(top_name);
  top_fn.src_off = 0;
  top_fn.src_end = (body_len > 0) ? (uint32_t)body_len : 0;
  if (is_async) top_fn.flags |= FN_ASYNC;

  for (int i = 0; i < param_count; i++) {
    const char *name = (params && params[i].name) ? params[i].name : "";
    size_t name_len = 0;
    if (params && params[i].name) {
      name_len = params[i].len ? params[i].len : strlen(name);
    }

    sv_ast_t *ident = sv_ast_new(N_IDENT);
    if (!ident) return NULL;

    ident->str = name;
    ident->len = (uint32_t)name_len;
    ident->line = 1;
    ident->col = 1;
    sv_ast_list_push(&top_fn.args, ident);
  }

  top_fn.body = sv_ast_new(N_BLOCK);
  if (!top_fn.body) return NULL;
  top_fn.body->args = program->args;

  sv_compiler_t root;
  sv_compile_ctx_init_root(
    &root, js, js->filename,
    pin_source_text(body, (ant_offset_t)body_len),
    (ant_offset_t)body_len, SV_COMPILE_SCRIPT,
    (program->flags & FN_PARSE_STRICT) != 0, NULL
  );
  
  root.line_table = sv_compile_ctx_build_line_table(root.source, (ant_offset_t)body_len);
  sv_func_t *func = compile_function_body(&root, &top_fn, SV_COMPILE_SCRIPT);
  sv_compile_ctx_free_line_table(root.line_table);
  
  if (sv_compile_trace_unlikely) fprintf(
    stderr, "[compile] end kind=function-with-params thrown=%d func=%p\n",
    js->thrown_exists ? 1 : 0, (void *)func
  );
  
  if (js->thrown_exists || !func) return NULL;
  return func;
}
