/*
** LuaJIT VM builder: library definition compiler.
** Copyright (C) 2005-2023 Mike Pall. See Copyright Notice in luajit.h
*/

#include "buildvm.h"
#include "lj_obj.h"
#include "lj_bc.h"
#include "lj_lib.h"
#include "buildvm_libbc.h"

/* Context for library definitions. */
static uint8_t obuf[8192];
static uint8_t *optr;
static char modname[80];
static size_t modnamelen;
static char funcname[80];
static int modstate, regfunc;
static int ffid, recffid, ffasmfunc;

enum {
  REGFUNC_OK,
  REGFUNC_NOREG,
  REGFUNC_NOREGUV
};

static void libdef_name(const char *p, int kind)
{
  size_t n = strlen(p);
  if (kind != LIBINIT_STRING) {
    if (n > modnamelen && p[modnamelen] == '_' &&
	!strncmp(p, modname, modnamelen)) {
      p += modnamelen+1;
      n -= modnamelen+1;
    }
  }
  if (n > LIBINIT_MAXSTR) {
    fprintf(stderr, "Error: string too long: '%s'\n",  p);
    exit(1);
  }
  if (optr+1+n+2 > obuf+sizeof(obuf)) {  /* +2 for caller. */
    fprintf(stderr, "Error: output buffer overflow\n");
    exit(1);
  }
  *optr++ = (uint8_t)(n | kind);
  memcpy(optr, p, n);
  optr += n;
}

static void libdef_endmodule(BuildCtx *ctx)
{
  if (modstate != 0) {
    char line[80];
    const uint8_t *p;
    int n;
    if (modstate == 1)
      fprintf(ctx->fp, "  (lua_CFunction)0");
    fprintf(ctx->fp, "\n};\n");
    fprintf(ctx->fp, "static const uint8_t %s%s[] = {\n",
	    LABEL_PREFIX_LIBINIT, modname);
    line[0] = '\0';
    for (n = 0, p = obuf; p < optr; p++) {
      n += sprintf(line+n, "%d,", *p);
      if (n >= 75) {
	fprintf(ctx->fp, "%s\n", line);
	n = 0;
	line[0] = '\0';
      }
    }
    fprintf(ctx->fp, "%s%d\n};\n#endif\n\n", line, LIBINIT_END);
  }
}

static void libdef_module(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(arg);
  if (ctx->mode == BUILD_libdef) {
    libdef_endmodule(ctx);
    optr = obuf;
    *optr++ = (uint8_t)ffid;
    *optr++ = (uint8_t)ffasmfunc;
    *optr++ = 0;  /* Hash table size. */
    modstate = 1;
    fprintf(ctx->fp, "#ifdef %sMODULE_%s\n", LIBDEF_PREFIX, p);
    fprintf(ctx->fp, "#undef %sMODULE_%s\n", LIBDEF_PREFIX, p);
    fprintf(ctx->fp, "static const lua_CFunction %s%s[] = {\n",
	    LABEL_PREFIX_LIBCF, p);
  }
  modnamelen = strlen(p);
  if (modnamelen > sizeof(modname)-1) {
    fprintf(stderr, "Error: module name too long: '%s'\n", p);
    exit(1);
  }
  strcpy(modname, p);
}

static int find_ffofs(BuildCtx *ctx, const char *name)
{
  int i;
  for (i = 0; i < ctx->nglob; i++) {
    const char *gl = ctx->globnames[i];
    if (gl[0] == 'f' && gl[1] == 'f' && gl[2] == '_' && !strcmp(gl+3, name)) {
      return (int)((uint8_t *)ctx->glob[i] - ctx->code);
    }
  }
  fprintf(stderr, "Error: undefined fast function %s%s\n",
	  LABEL_PREFIX_FF, name);
  exit(1);
}

static void libdef_func(BuildCtx *ctx, char *p, int arg)
{
  if (arg != LIBINIT_CF)
    ffasmfunc++;
  if (ctx->mode == BUILD_libdef) {
    if (modstate == 0) {
      fprintf(stderr, "Error: no module for function definition %s\n", p);
      exit(1);
    }
    if (regfunc == REGFUNC_NOREG) {
      if (optr+1 > obuf+sizeof(obuf)) {
	fprintf(stderr, "Error: output buffer overflow\n");
	exit(1);
      }
      *optr++ = LIBINIT_FFID;
    } else {
      if (arg != LIBINIT_ASM_) {
	if (modstate != 1) fprintf(ctx->fp, ",\n");
	modstate = 2;
	fprintf(ctx->fp, "  %s%s", arg ? LABEL_PREFIX_FFH : LABEL_PREFIX_CF, p);
      }
      if (regfunc != REGFUNC_NOREGUV) obuf[2]++;  /* Bump hash table size. */
      libdef_name(regfunc == REGFUNC_NOREGUV ? "" : p, arg);
    }
  } else if (ctx->mode == BUILD_ffdef) {
    fprintf(ctx->fp, "FFDEF(%s)\n", p);
  } else if (ctx->mode == BUILD_recdef) {
    if (strlen(p) > sizeof(funcname)-1) {
      fprintf(stderr, "Error: function name too long: '%s'\n", p);
      exit(1);
    }
    strcpy(funcname, p);
  } else if (ctx->mode == BUILD_vmdef) {
    int i;
    for (i = 1; p[i] && modname[i-1]; i++)
      if (p[i] == '_') p[i] = '.';
    fprintf(ctx->fp, "\"%s\",\n", p);
  } else if (ctx->mode == BUILD_bcdef) {
    if (arg != LIBINIT_CF)
      fprintf(ctx->fp, ",\n%d", find_ffofs(ctx, p));
  }
  ffid++;
  regfunc = REGFUNC_OK;
}

static uint8_t *libdef_uleb128(uint8_t *p, uint32_t *vv)
{
  uint32_t v = *p++;
  if (v >= 0x80) {
    int sh = 0; v &= 0x7f;
    do { v |= ((*p & 0x7f) << (sh += 7)); } while (*p++ >= 0x80);
  }
  *vv = v;
  return p;
}

static void libdef_fixupbc(uint8_t *p)
{
  uint32_t i, sizebc;
  p += 4;
  p = libdef_uleb128(p, &sizebc);
  p = libdef_uleb128(p, &sizebc);
  p = libdef_uleb128(p, &sizebc);
  for (i = 0; i < sizebc; i++, p += 4) {
    uint8_t op = p[libbc_endian ? 3 : 0];
    uint8_t ra = p[libbc_endian ? 2 : 1];
    uint8_t rc = p[libbc_endian ? 1 : 2];
    uint8_t rb = p[libbc_endian ? 0 : 3];
    if (!LJ_DUALNUM && op == BC_ISTYPE && rc == ~LJ_TNUMX+1) {
      op = BC_ISNUM; rc++;
    }
    p[LJ_ENDIAN_SELECT(0, 3)] = op;
    p[LJ_ENDIAN_SELECT(1, 2)] = ra;
    p[LJ_ENDIAN_SELECT(2, 1)] = rc;
    p[LJ_ENDIAN_SELECT(3, 0)] = rb;
  }
}

static void libdef_lua(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(arg);
  if (ctx->mode == BUILD_libdef) {
    int i;
    for (i = 0; libbc_map[i].name != NULL; i++) {
      if (!strcmp(libbc_map[i].name, p)) {
	int ofs = libbc_map[i].ofs;
	int len = libbc_map[i+1].ofs - ofs;
	obuf[2]++;  /* Bump hash table size. */
	*optr++ = LIBINIT_LUA;
	libdef_name(p, 0);
	memcpy(optr, libbc_code + ofs, len);
	libdef_fixupbc(optr);
	optr += len;
	return;
      }
    }
    fprintf(stderr, "Error: missing libbc definition for %s\n", p);
    exit(1);
  }
}

static uint32_t find_rec(char *name)
{
  char *p = (char *)obuf;
  uint32_t n;
  for (n = 2; *p; n++) {
    if (strcmp(p, name) == 0)
      return n;
    p += strlen(p)+1;
  }
  if (p+strlen(name)+1 >= (char *)obuf+sizeof(obuf)) {
    fprintf(stderr, "Error: output buffer overflow\n");
    exit(1);
  }
  strcpy(p, name);
  return n;
}

static void libdef_rec(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(arg);
  if (ctx->mode == BUILD_recdef) {
    char *q;
    uint32_t n;
    for (; recffid+1 < ffid; recffid++)
      fprintf(ctx->fp, ",\n0");
    recffid = ffid;
    if (*p == '.') p = funcname;
    q = strchr(p, ' ');
    if (q) *q++ = '\0';
    n = find_rec(p);
    if (q)
      fprintf(ctx->fp, ",\n0x%02x00+(%s)", n, q);
    else
      fprintf(ctx->fp, ",\n0x%02x00", n);
  }
}

static void memcpy_endian(void *dst, void *src, size_t n)
{
  union { uint8_t b; uint32_t u; } host_endian;
  host_endian.u = 1;
  if (host_endian.b == LJ_ENDIAN_SELECT(1, 0)) {
    memcpy(dst, src, n);
  } else {
    size_t i;
    for (i = 0; i < n; i++)
      ((uint8_t *)dst)[i] = ((uint8_t *)src)[n-i-1];
  }
}

static void libdef_push(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(arg);
  if (ctx->mode == BUILD_libdef) {
    int len = (int)strlen(p);
    if (*p == '"') {
      if (len > 1 && p[len-1] == '"') {
	p[len-1] = '\0';
	libdef_name(p+1, LIBINIT_STRING);
	return;
      }
    } else if (*p >= '0' && *p <= '9') {
      char *ep;
      double d = strtod(p, &ep);
      if (*ep == '\0') {
	if (optr+1+sizeof(double) > obuf+sizeof(obuf)) {
	  fprintf(stderr, "Error: output buffer overflow\n");
	  exit(1);
	}
	*optr++ = LIBINIT_NUMBER;
	memcpy_endian(optr, &d, sizeof(double));
	optr += sizeof(double);
	return;
      }
    } else if (!strcmp(p, "lastcl")) {
      if (optr+1 > obuf+sizeof(obuf)) {
	fprintf(stderr, "Error: output buffer overflow\n");
	exit(1);
      }
      *optr++ = LIBINIT_LASTCL;
      return;
    } else if (len > 4 && !strncmp(p, "top-", 4)) {
      if (optr+2 > obuf+sizeof(obuf)) {
	fprintf(stderr, "Error: output buffer overflow\n");
	exit(1);
      }
      *optr++ = LIBINIT_COPY;
      *optr++ = (uint8_t)atoi(p+4);
      return;
    }
    fprintf(stderr, "Error: bad value for %sPUSH(%s)\n", LIBDEF_PREFIX, p);
    exit(1);
  }
}

static void libdef_set(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(arg);
  if (ctx->mode == BUILD_libdef) {
    if (p[0] == '!' && p[1] == '\0') p[0] = '\0';  /* Set env. */
    libdef_name(p, LIBINIT_STRING);
    *optr++ = LIBINIT_SET;
    obuf[2]++;  /* Bump hash table size. */
  }
}

static void libdef_regfunc(BuildCtx *ctx, char *p, int arg)
{
  UNUSED(ctx); UNUSED(p);
  regfunc = arg;
}

typedef void (*LibDefFunc)(BuildCtx *ctx, char *p, int arg);

typedef struct LibDefHandler {
  const char *suffix;
  const char *stop;
  const LibDefFunc func;
  const int arg;
} LibDefHandler;

static const LibDefHandler libdef_handlers[] = {
  { "MODULE_",	" \t\r\n",	libdef_module,		0 },
  { "CF(",	")",		libdef_func,		LIBINIT_CF },
  { "ASM(",	")",		libdef_func,		LIBINIT_ASM },
  { "ASM_(",	")",		libdef_func,		LIBINIT_ASM_ },
  { "LUA(",	")",		libdef_lua,		0 },
  { "REC(",	")",		libdef_rec,		0 },
  { "PUSH(",	")",		libdef_push,		0 },
  { "SET(",	")",		libdef_set,		0 },
  { "NOREGUV",	NULL,		libdef_regfunc,		REGFUNC_NOREGUV },
  { "NOREG",	NULL,		libdef_regfunc,		REGFUNC_NOREG },
  { NULL,	NULL,		(LibDefFunc)0,		0 }
};

/* Emit C source code for library function definitions. */
void emit_lib(BuildCtx *ctx)
{
  const char *fname;

  if (ctx->mode == BUILD_ffdef || ctx->mode == BUILD_libdef ||
      ctx->mode == BUILD_recdef)
    fprintf(ctx->fp, "/* This is a generated file. DO NOT EDIT! */\n\n");
  else if (ctx->mode == BUILD_vmdef)
    fprintf(ctx->fp, "ffnames = {\n[0]=\"Lua\",\n\"C\",\n");
  if (ctx->mode == BUILD_recdef)
    fprintf(ctx->fp, "static const uint16_t recff_idmap[] = {\n0,\n0x0100");
  recffid = ffid = FF_C+1;
  ffasmfunc = 0;

  while ((fname = *ctx->args++)) {
    char buf[256];  /* We don't care about analyzing lines longer than that. */
    FILE *fp;
    if (fname[0] == '-' && fname[1] == '\0') {
      fp = stdin;
    } else {
      fp = fopen(fname, "r");
      if (!fp) {
	fprintf(stderr, "Error: cannot open input file '%s': %s\n",
		fname, strerror(errno));
	exit(1);
      }
    }
    modstate = 0;
    regfunc = REGFUNC_OK;
    while (fgets(buf, sizeof(buf), fp) != NULL) {
      char *p;
      /* Simplistic pre-processor. Only handles top-level #if/#endif. */
      if (buf[0] == '#' && buf[1] == 'i' && buf[2] == 'f') {
	int ok = 1;
	size_t len = strlen(buf);
	if (buf[len-1] == '\n') {
	  buf[len-1] = 0;
	  if (buf[len-2] == '\r') {
	    buf[len-2] = 0;
	  }
	}
	if (!strcmp(buf, "#if LJ_52"))
	  ok = LJ_52;
	else if (!strcmp(buf, "#if LJ_HASJIT"))
	  ok = LJ_HASJIT;
	else if (!strcmp(buf, "#if LJ_HASFFI"))
	  ok = LJ_HASFFI;
	else if (!strcmp(buf, "#if LJ_HASBUFFER"))
	  ok = LJ_HASBUFFER;
	if (!ok) {
	  int lvl = 1;
	  while (fgets(buf, sizeof(buf), fp) != NULL) {
	    if (buf[0] == '#' && buf[1] == 'e' && buf[2] == 'n') {
	      if (--lvl == 0) break;
	    } else if (buf[0] == '#' && buf[1] == 'i' && buf[2] == 'f') {
	      lvl++;
	    }
	  }
	  continue;
	}
      }
      for (p = buf; (p = strstr(p, LIBDEF_PREFIX)) != NULL; ) {
	const LibDefHandler *ldh;
	p += sizeof(LIBDEF_PREFIX)-1;
	for (ldh = libdef_handlers; ldh->suffix != NULL; ldh++) {
	  size_t n, len = strlen(ldh->suffix);
	  if (!strncmp(p, ldh->suffix, len)) {
	    p += len;
	    n = ldh->stop ? strcspn(p, ldh->stop) : 0;
	    if (!p[n]) break;
	    p[n] = '\0';
	    ldh->func(ctx, p, ldh->arg);
	    p += n+1;
	    break;
	  }
	}
	if (ldh->suffix == NULL) {
	  buf[strlen(buf)-1] = '\0';
	  fprintf(stderr, "Error: unknown library definition tag %s%s\n",
		  LIBDEF_PREFIX, p);
	  exit(1);
	}
      }
    }
    fclose(fp);
    if (ctx->mode == BUILD_libdef) {
      libdef_endmodule(ctx);
    }
  }

  if (ctx->mode == BUILD_ffdef) {
    fprintf(ctx->fp, "\n#undef FFDEF\n\n");
    fprintf(ctx->fp,
      "#ifndef FF_NUM_ASMFUNC\n#define FF_NUM_ASMFUNC %d\n#endif\n\n",
      ffasmfunc);
  } else if (ctx->mode == BUILD_vmdef) {
    fprintf(ctx->fp, "},\n\n");
  } else if (ctx->mode == BUILD_bcdef) {
    int i;
    fprintf(ctx->fp, "\n};\n\n");
    fprintf(ctx->fp, "LJ_DATADEF const uint16_t lj_bc_mode[] = {\n");
    fprintf(ctx->fp, "BCDEF(BCMODE)\n");
    for (i = ffasmfunc-1; i > 0; i--)
      fprintf(ctx->fp, "BCMODE_FF,\n");
    fprintf(ctx->fp, "BCMODE_FF\n};\n\n");
  } else if (ctx->mode == BUILD_recdef) {
    char *p = (char *)obuf;
    fprintf(ctx->fp, "\n};\n\n");
    fprintf(ctx->fp, "static const RecordFunc recff_func[] = {\n"
	    "recff_nyi,\n"
	    "recff_c");
    while (*p) {
      fprintf(ctx->fp, ",\nrecff_%s", p);
      p += strlen(p)+1;
    }
    fprintf(ctx->fp, "\n};\n\n");
  }
}

