Ctrl K
ComunidadGuías…

packages/codemode/script/disasm.ts

Rama dev · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bun

// The bytecode disassembler: pcs, ops, constants, handler and finally tables, local names, source lines.
import { sourceLocation } from "../src/vm/runtime/model.js"
import type { Code } from "../src/vm/code.js"
import { compileSource } from "../src/vm/compiler/compile.js"
import {
  BinOpSymbols,
  CheckBaseKind,
  CodeFlags,
  CondKind,
  Op,
  OpNames,
  PutKind,
  UnaryOpSymbols,
} from "../src/vm/opcodes.js"
import { parseProgram } from "../src/vm/runtime/execute.js"

const formatFlags = (flags: number): string => {
  const names: Array<string> = []
  if (flags & CodeFlags.PROGRAM) names.push("PROGRAM")
  if (flags & CodeFlags.ARROW) names.push("ARROW")
  if (flags & CodeFlags.ASYNC) names.push("ASYNC")
  if (flags & CodeFlags.GENERATOR) names.push("GENERATOR")
  if (flags & CodeFlags.SIMPLE_PARAMS) names.push("SIMPLE_PARAMS")
  if (flags & CodeFlags.USES_THIS) names.push("USES_THIS")
  if (flags & CodeFlags.USES_ARGUMENTS) names.push("USES_ARGUMENTS")
  if (flags & CodeFlags.INTERNAL) names.push("INTERNAL")
  if (flags & CodeFlags.NEEDS_ARGS) names.push("NEEDS_ARGS")
  return names.join("|") || "NONE"
}

export const disassembleCode = (c: Code): string => {
  const lines: Array<string> = []
  const header = `=== fn#${c.index} ${c.name || "<anonymous>"} (params=${c.params}, locals=${c.locals}, maxStack=${c.maxStack}, flags=${formatFlags(c.flags)}) ===`
  lines.push(header)
  if (c.localNames.length > 0) {
    lines.push(`locals: ${c.localNames.map((n, i) => `${i}:${n}`).join(", ")}`)
  }
  if (c.captures.length > 0) {
    const caps: Array<string> = []
    for (let i = 0, j = 0; i < c.captures.length; i += 2, j++) {
      const from = c.captures[i] === 0 ? "local" : "upval"
      const idx = c.captures[i + 1]!
      caps.push(`${j}:${c.upvalNames[j] ?? "?"}(${from}[${idx}])`)
    }
    lines.push(`upvals: ${caps.join(", ")}`)
  }

  const words = c.code
  for (let pc = 0; pc < words.length; pc++) {
    const w = words[pc]!
    const op = w & 0xff
    let a = (w >>> 8) & 0xff
    let bx = w >>> 16
    if (op !== Op.EXTRA && pc > 0 && (words[pc - 1]! & 0xff) === Op.EXTRA) {
      const w1 = words[pc - 1]!
      const w0 = pc > 1 ? words[pc - 2]! : 0
      a = (w0 & 0xff) === Op.EXTRA ? ((w0 >>> 16) << 16) | (w1 & 0xff00) | a : (w1 & 0xff00) | a
      bx = ((w1 >>> 16) << 16) | bx
    }
    const sbx = w >> 16
    const opName = OpNames[op] ?? `OP_${op}`
    const operand = formatOperand(c, op, a, bx, sbx)
    const siteNode = c.siteAt(pc)
    const loc = siteNode?.loc ? sourceLocation(siteNode) : undefined
    const locText = loc ? ` ; L${loc.line}:${loc.column}` : ""
    lines.push(`${String(pc).padStart(4)}  ${opName.padEnd(18)} ${operand.padEnd(28)}${locText}`.trimEnd())
  }

  if (c.handlers.length > 0) {
    const hList: Array<string> = []
    for (let i = 0; i < c.handlers.length; i += 4) {
      hList.push(`[${c.handlers[i]}, ${c.handlers[i + 1]}) -> ${c.handlers[i + 2]} (depth ${c.handlers[i + 3]})`)
    }
    lines.push(`handlers: ${hList.join(", ")}`)
  }

  if (c.finallies.length > 0) {
    const fList: Array<string> = []
    for (let level = 0; level < c.finallyOffsets.length; level++) {
      const off = c.finallyOffsets[level]!
      const tag = c.finallies[off]!
      const val = c.finallies[off + 1]!
      const depth = c.finallies[off + 2]!
      const n = c.finallies[off + 3]!
      const targets: Array<number> = []
      for (let k = 0; k < n; k++) targets.push(c.finallies[off + 4 + k]!)
      fList.push(`#${level}{tag=${tag}, val=${val}, depth=${depth}, targets=[${targets.join(", ")}]}`)
    }
    lines.push(`finallies: ${fList.join(", ")}`)
  }

  return lines.join("\n")
}

const formatOperand = (c: Code, op: number, a: number, bx: number, sbx: number): string => {
  switch (op) {
    case Op.PUSH_UNDEF:
    case Op.PUSH_NULL:
    case Op.POP:
    case Op.DUP:
    case Op.NEW_OBJECT:
    case Op.COPY_DATA_PROPS:
    case Op.PUT_ELEM:
    case Op.DELETE_ELEM:
    case Op.TO_KEY:
    case Op.IN:
    case Op.INSTANCEOF:
    case Op.ADD:
    case Op.SUB:
    case Op.MUL:
    case Op.LT:
    case Op.LE:
    case Op.GT:
    case Op.GE:
    case Op.SEQ:
    case Op.SNE:
    case Op.NOT:
    case Op.THROW:
    case Op.RETURN:
    case Op.END_PROGRAM:
    case Op.NOP:
    case Op.CALL_SPREAD:
    case Op.NEW_SPREAD:
    case Op.FOR_IN:
    case Op.GENERATOR_START:
      return ""
    case Op.PUSH_BOOL:
      return a === 1 ? "true" : "false"
    case Op.PUSH_INT:
      return String(sbx)
    case Op.PUSH_CONST:
      return `${bx} (${JSON.stringify(c.consts[bx])})`
    case Op.POPN:
    case Op.NEW_ARRAY:
    case Op.CONCAT:
    case Op.COPY_REST:
    case Op.END_FINALLY:
    case Op.EXTRA:
      return a === 0 ? String(bx) : `a=${a} bx=${bx}`
    case Op.PICK:
    case Op.ROT:
    case Op.CALL:
      return String(a)
    case Op.NEW:
      return a === 255 ? "check" : String(a)
    case Op.GET_LOCAL:
    case Op.HOLE_LOCAL:
    case Op.COPY_CELL:
    case Op.GET_CELL:
      return `${bx} (${c.localNames[bx] ?? "?"})`
    case Op.NEW_CELL:
      return `${a === 1 ? "wrap " : ""}${bx} (${c.localNames[bx] ?? "?"})`
    case Op.PUT_LOCAL:
    case Op.PUT_CELL: {
      const prefix = a === PutKind.INIT ? ".init " : a === PutKind.CONST ? ".const " : ""
      return `${prefix}${bx} (${c.localNames[bx] ?? "?"})`
    }
    case Op.GET_UPVAL:
      return `${bx} (${c.upvalNames[bx] ?? "?"})`
    case Op.PUT_UPVAL: {
      const prefix = a === PutKind.INIT ? ".init " : a === PutKind.CONST ? ".const " : ""
      return `${prefix}${bx} (${c.upvalNames[bx] ?? "?"})`
    }
    case Op.GET_GLOBAL: {
      const prefix = a === 1 ? ".typeof " : a === 2 ? ".put " : ""
      return `${prefix}${JSON.stringify(c.consts[bx])}`
    }
    case Op.GET_INTRINSIC:
      return `${JSON.stringify(c.consts[bx])}`
    case Op.ARGS:
      return a === 0 ? "arguments" : a === 1 ? `rest(${bx})` : a === 2 ? "self" : a === 3 ? "this" : `arg[${bx}]`
    case Op.CLOSURE:
      return `fn#${bx}`
    case Op.SET_NAME:
      return a === 0 ? JSON.stringify(c.consts[bx]) : "(computed)"
    case Op.GET_PROP: {
      const prefix = a === 1 ? ".keep " : a === 2 ? ".destr " : ""
      return `${prefix}${JSON.stringify(c.consts[bx])}`
    }
    case Op.GET_ELEM:
      return a === 1 ? ".keep" : a === 2 ? ".destr" : ""
    case Op.PUT_PROP:
    case Op.DELETE_PROP:
      return JSON.stringify(c.consts[bx])
    case Op.DEFINE_FIELD:
      return a === 0 ? JSON.stringify(c.consts[bx]) : "(computed)"
    case Op.CHECK_BASE:
      return a === CheckBaseKind.READ
        ? ".read"
        : a === CheckBaseKind.WRITE_PROP
          ? `.writeProp ${JSON.stringify(c.consts[bx])}`
          : a === CheckBaseKind.WRITE_ELEM
            ? ".writeElem"
            : ".destructure"
    case Op.BINOP:
      return BinOpSymbols[a] ?? String(a)
    case Op.UNARY:
      return UnaryOpSymbols[a] ?? String(a)
    case Op.INC:
      return a === 0 ? "+1" : "-1"
    case Op.JUMP:
    case Op.LOOP:
    case Op.JUMP_IF_FALSE:
    case Op.JUMP_IF_TRUE:
      return `-> ${bx}`
    case Op.JUMP_COND: {
      const kind =
        a === CondKind.TRUTHY_KEEP
          ? ".truthyKeep"
          : a === CondKind.FALSY_KEEP
            ? ".falsyKeep"
            : a === CondKind.NOT_NULLISH_KEEP
              ? ".notNullishKeep"
              : a === CondKind.DEFINED
                ? ".defined"
                : a === CondKind.NULLISH
                  ? ".nullish"
                  : ".nullishMethod"
      return `${kind} -> ${bx}`
    }
    case Op.ITER_STEP:
    case Op.ITER_RESULT:
    case Op.ITER_CLOSE:
    case Op.ITER_APPEND_ALL:
    case Op.YSTAR:
    case Op.YSTAR_RESULT:
      return `slot=${a} -> ${bx}`
    case Op.AWAIT:
      return a === 1 ? ".unlocated" : ""
    case Op.YIELD:
      return ""
    case Op.RESUME:
      return `-> ${bx}`
    default:
      return `a=${a} bx=${bx}`
  }
}

export const disassemble = (codes: ReadonlyArray<Code> | Code): string => {
  const list = Array.isArray(codes) ? codes : [codes]
  return list.map(disassembleCode).join("\n\n")
}

export const disassembleSource = (sourceText: string): string => {
  const parsed = parseProgram(sourceText)
  if (!parsed.ok) throw new Error(parsed.error.message)
  return disassemble(compileSource(parsed.source))
}

if (import.meta.main) {
  const arg = process.argv[2]
  if (!arg) {
    console.error("Usage: bun run script/disasm.ts <code-or-file>")
    process.exit(1)
  }
  const file = Bun.file(arg)
  const source = (await file.exists()) ? await file.text() : arg
  console.log(disassembleSource(source))
}