raze/source/common/scripting/core/vmdisasm.cpp
2023-11-07 18:35:11 +01:00

691 lines
19 KiB
C++

/*
** vmdisasm.cpp
**
**---------------------------------------------------------------------------
** Copyright -2016 Randy Heit
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include "dobject.h"
#include "c_console.h"
#include "vmintern.h"
#include "printf.h"
#define NOP MODE_AUNUSED | MODE_BUNUSED | MODE_CUNUSED
#define LI MODE_AI | MODE_BCJOINT | MODE_BCIMMS
#define LKI MODE_AI | MODE_BCJOINT | MODE_BCKI
#define LKF MODE_AF | MODE_BCJOINT | MODE_BCKF
#define LKS MODE_AS | MODE_BCJOINT | MODE_BCKS
#define LKP MODE_AP | MODE_BCJOINT | MODE_BCKP
#define LFP MODE_AP | MODE_BUNUSED | MODE_CUNUSED
#define RP MODE_AP | MODE_BUNUSED | MODE_CUNUSED
#define RIRPKI MODE_AI | MODE_BP | MODE_CKI
#define RIRPRI MODE_AI | MODE_BP | MODE_CI
#define RFRPKI MODE_AF | MODE_BP | MODE_CKI
#define RFRPRI MODE_AF | MODE_BP | MODE_CI
#define RSRPKI MODE_AS | MODE_BP | MODE_CKI
#define RSRPRI MODE_AS | MODE_BP | MODE_CI
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
#define RVRPKI MODE_AV | MODE_BP | MODE_CKI
#define RVRPRI MODE_AV | MODE_BP | MODE_CI
#define RIRPI8 MODE_AI | MODE_BP | MODE_CIMMZ
#define RPRIKI MODE_AP | MODE_BI | MODE_CKI
#define RPRIRI MODE_AP | MODE_BI | MODE_CI
#define RPRFKI MODE_AP | MODE_BF | MODE_CKI
#define RPRFRI MODE_AP | MODE_BF | MODE_CI
#define RPRSKI MODE_AP | MODE_BS | MODE_CKI
#define RPRSRI MODE_AP | MODE_BS | MODE_CI
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
#define RPRVKI MODE_AP | MODE_BV | MODE_CKI
#define RPRVRI MODE_AP | MODE_BV | MODE_CI
#define RPRII8 MODE_AP | MODE_BI | MODE_CIMMZ
#define RIRI MODE_AI | MODE_BI | MODE_CUNUSED
#define RFRF MODE_AF | MODE_BF | MODE_CUNUSED
#define RSRS MODE_AS | MODE_BS | MODE_CUNUSED
#define RPRP MODE_AP | MODE_BP | MODE_CUNUSED
#define RPKP MODE_AP | MODE_BKP | MODE_CUNUSED
#define RXRXI8 MODE_AX | MODE_BX | MODE_CIMMZ
#define RPRPRP MODE_AP | MODE_BP | MODE_CP
#define RPRPKP MODE_AP | MODE_BP | MODE_CKP
#define RII16 MODE_AI | MODE_BCJOINT | MODE_BCIMMS
#define I24 MODE_ABCJOINT
#define I8 MODE_AIMMZ | MODE_BUNUSED | MODE_CUNUSED
#define I8I16 MODE_AIMMZ | MODE_BCIMMZ
#define __BCP MODE_PARAM24
#define RPI8 MODE_AP | MODE_BIMMZ | MODE_CUNUSED
#define KPI8 MODE_AKP | MODE_BIMMZ | MODE_CUNUSED
#define RPI8I8 MODE_AP | MODE_BIMMZ | MODE_CIMMZ
#define RPRPI8 MODE_AP | MODE_BP | MODE_CIMMZ
#define KPI8I8 MODE_AKP | MODE_BIMMZ | MODE_CIMMZ
#define I8BCP MODE_AIMMZ | MODE_BCJOINT | MODE_BCPARAM
#define THROW MODE_AIMMZ | MODE_BCTHROW
#define CATCH MODE_AIMMZ | MODE_BCCATCH
#define CAST MODE_AX | MODE_BX | MODE_CIMMZ | MODE_BCCAST
#define CASTB MODE_AI | MODE_BX | MODE_CIMMZ | MODE_BCCAST
#define RSRSRS MODE_AS | MODE_BS | MODE_CS
#define RIRS MODE_AI | MODE_BS | MODE_CUNUSED
#define I8RXRX MODE_AIMMZ | MODE_BX | MODE_CX
#define RIRIRI MODE_AI | MODE_BI | MODE_CI
#define RIRII8 MODE_AI | MODE_BI | MODE_CIMMZ
#define RFRII8 MODE_AF | MODE_BI | MODE_CIMMZ
#define RPRII8 MODE_AP | MODE_BI | MODE_CIMMZ
#define RSRII8 MODE_AS | MODE_BI | MODE_CIMMZ
#define RIRIKI MODE_AI | MODE_BI | MODE_CKI
#define RIKIRI MODE_AI | MODE_BKI | MODE_CI
#define RIKII8 MODE_AI | MODE_BKI | MODE_CIMMZ
#define RIRIIs MODE_AI | MODE_BI | MODE_CIMMS
#define I8RIRI MODE_AIMMZ | MODE_BI | MODE_CI
#define I8RIKI MODE_AIMMZ | MODE_BI | MODE_CKI
#define I8KIRI MODE_AIMMZ | MODE_BKI | MODE_CI
#define RFRFRF MODE_AF | MODE_BF | MODE_CF
#define RFRFKF MODE_AF | MODE_BF | MODE_CKF
#define RFKFRF MODE_AF | MODE_BKF | MODE_CF
#define I8RFRF MODE_AIMMZ | MODE_BF | MODE_CF
#define I8RFKF MODE_AIMMZ | MODE_BF | MODE_CKF
#define I8KFRF MODE_AIMMZ | MODE_BKF | MODE_CF
#define RFRFI8 MODE_AF | MODE_BF | MODE_CIMMZ
#define RVRV MODE_AV | MODE_BV | MODE_CUNUSED
#define RVRVRV MODE_AV | MODE_BV | MODE_CV
#define RVRVKV MODE_AV | MODE_BV | MODE_CKV
#define RVKVRV MODE_AV | MODE_BKV | MODE_CV
#define RVRVRF MODE_AV | MODE_BV | MODE_CF
#define RVRVKF MODE_AV | MODE_BV | MODE_CKF
#define RVKVRF MODE_AV | MODE_BKV | MODE_CF
#define RFRV MODE_AF | MODE_BV | MODE_CUNUSED
#define I8RVRV MODE_AIMMZ | MODE_BV | MODE_CV
#define I8RVKV MODE_AIMMZ | MODE_BV | MODE_CKV
#define RPRPRI MODE_AP | MODE_BP | MODE_CI
#define RPRPKI MODE_AP | MODE_BP | MODE_CKI
#define RIRPRP MODE_AI | MODE_BP | MODE_CP
#define I8RPRP MODE_AIMMZ | MODE_BP | MODE_CP
#define I8RPKP MODE_AIMMZ | MODE_BP | MODE_CKP
#define CIRR MODE_ACMP | MODE_BI | MODE_CI
#define CIRK MODE_ACMP | MODE_BI | MODE_CKI
#define CIKR MODE_ACMP | MODE_BKI | MODE_CI
#define CFRR MODE_ACMP | MODE_BF | MODE_CF
#define CFRK MODE_ACMP | MODE_BF | MODE_CKF
#define CFKR MODE_ACMP | MODE_BKF | MODE_CF
#define CVRR MODE_ACMP | MODE_BV | MODE_CV
#define CVRK MODE_ACMP | MODE_BV | MODE_CKV
#define CPRR MODE_ACMP | MODE_BP | MODE_CP
#define CPRK MODE_ACMP | MODE_BP | MODE_CKP
const VMOpInfo OpInfo[NUM_OPS] =
{
#define xx(op, name, mode, alt, kreg, ktype) { #name, mode },
#include "vmops.h"
};
static const char *const FlopNames[] =
{
"abs",
"neg",
"exp",
"log",
"log10",
"sqrt",
"ceil",
"floor",
"acos rad",
"asin rad",
"atan rad",
"cos rad",
"sin rad",
"tan rad",
"acos deg",
"asin deg",
"atan deg",
"cos deg",
"sin deg",
"tan deg",
"cosh",
"sinh",
"tanh",
"round",
};
static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func);
static int printf_wrapper(FILE *f, const char *fmt, ...)
{
va_list argptr;
int count;
va_start(argptr, fmt);
if (f == NULL)
{
count = VPrintf(PRINT_HIGH, fmt, argptr);
}
else
{
count = vfprintf(f, fmt, argptr);
}
va_end(argptr);
return count;
}
void VMDumpConstants(FILE *out, const VMScriptFunction *func)
{
char tmp[30];
int i, j, k, kk;
if (func->KonstD != NULL && func->NumKonstD != 0)
{
printf_wrapper(out, "\nConstant integers:\n");
kk = (func->NumKonstD + 3) / 4;
for (i = 0; i < kk; ++i)
{
for (j = 0, k = i; j < 4 && k < func->NumKonstD; j++, k += kk)
{
mysnprintf(tmp, countof(tmp), "%3d. %d", k, func->KonstD[k]);
printf_wrapper(out, "%-20s", tmp);
}
printf_wrapper(out, "\n");
}
}
if (func->KonstF != NULL && func->NumKonstF != 0)
{
printf_wrapper(out, "\nConstant floats:\n");
kk = (func->NumKonstF + 3) / 4;
for (i = 0; i < kk; ++i)
{
for (j = 0, k = i; j < 4 && k < func->NumKonstF; j++, k += kk)
{
mysnprintf(tmp, countof(tmp), "%3d. %.16f", k, func->KonstF[k]);
printf_wrapper(out, "%-20s", tmp);
}
printf_wrapper(out, "\n");
}
}
if (func->KonstA != NULL && func->NumKonstA != 0)
{
printf_wrapper(out, "\nConstant addresses:\n");
kk = (func->NumKonstA + 3) / 4;
for (i = 0; i < kk; ++i)
{
for (j = 0, k = i; j < 4 && k < func->NumKonstA; j++, k += kk)
{
mysnprintf(tmp, countof(tmp), "%3d. %p", k, func->KonstA[k].v);
printf_wrapper(out, "%-22s", tmp);
}
printf_wrapper(out, "\n");
}
}
if (func->KonstS != NULL && func->NumKonstS != 0)
{
printf_wrapper(out, "\nConstant strings:\n");
for (i = 0; i < func->NumKonstS; ++i)
{
printf_wrapper(out, "%3d. %s\n", i, func->KonstS[i].GetChars());
}
}
}
void VMDisasm(FILE *out, const VMOP *code, int codesize, const VMScriptFunction *func)
{
VMFunction *callfunc = nullptr;
const char *name;
int col;
int mode;
int a;
bool cmp;
char cmpname[8];
for (int i = 0; i < codesize; ++i)
{
name = OpInfo[code[i].op].Name;
mode = OpInfo[code[i].op].Mode;
a = code[i].a;
cmp = (mode & MODE_ATYPE) == MODE_ACMP;
// String comparison encodes everything in a single instruction.
if (code[i].op == OP_CMPS)
{
switch (a & CMP_METHOD_MASK)
{
case CMP_EQ: name = "beq"; break;
case CMP_LT: name = "blt"; break;
case CMP_LE: name = "ble"; break;
}
mode = MODE_AIMMZ;
mode |= (a & CMP_BK) ? MODE_BKS : MODE_BS;
mode |= (a & CMP_CK) ? MODE_CKS : MODE_CS;
a &= CMP_CHECK | CMP_APPROX;
cmp = true;
}
if (code[i].op == OP_PARAM && code[i].a & REGT_ADDROF)
{
name = "parama";
}
if (cmp)
{ // Comparison instruction. Modify name for inverted test.
if (!(a & CMP_CHECK))
{
strcpy(cmpname, name);
if (name[1] == 'e')
{ // eq -> ne
cmpname[1] = 'n', cmpname[2] = 'e';
}
else if (name[2] == 't')
{ // lt -> ge
cmpname[1] = 'g', cmpname[2] = 'e';
}
else
{ // le -> gt
cmpname[1] = 'g', cmpname[2] = 't';
}
name = cmpname;
}
}
printf_wrapper(out, "%08x: %02x%02x%02x%02x %-8s", i << 2, code[i].op, code[i].a, code[i].b, code[i].c, name);
col = 0;
switch (code[i].op)
{
case OP_JMP:
//case OP_TRY:
col = printf_wrapper(out, "%08x", (i + 1 + code[i].i24) << 2);
break;
case OP_PARAMI:
col = printf_wrapper(out, "%d", code[i].i24);
break;
case OP_CALL_K:
{
callfunc = (VMFunction *)func->KonstA[code[i].a].o;
col = printf_wrapper(out, "[%p],%d", callfunc, code[i].b);
if (code[i].op == OP_CALL_K)
{
col += printf_wrapper(out, ",%d", code[i].c);
}
break;
}
case OP_PARAM:
{
col = print_reg(out, col, code[i].i24 & 0xffffff, MODE_PARAM24, 16, func);
break;
}
case OP_RESULT:
{
// Default handling for this broke after changing OP_PARAM...
col = print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func);
break;
}
case OP_RET:
if (code[i].b != REGT_NIL)
{
if (a == RET_FINAL)
{
col = print_reg(out, 0, code[i].i16u, MODE_PARAM, 16, func);
}
else
{
col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
col += print_reg(out, col, code[i].i16u, MODE_PARAM, 16, func);
if (a & RET_FINAL)
{
col += printf_wrapper(out, " [final]");
}
}
}
break;
case OP_RETI:
if (a == RET_FINAL)
{
col = printf_wrapper(out, "%d", code[i].i16);
}
else
{
col = print_reg(out, 0, a & ~RET_FINAL, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
col += print_reg(out, col, code[i].i16, MODE_IMMS, 16, func);
if (a & RET_FINAL)
{
col += printf_wrapper(out, " [final]");
}
}
break;
case OP_FLOP:
col = printf_wrapper(out, "f%d,f%d,%d", code[i].a, code[i].b, code[i].c);
if (code[i].c < countof(FlopNames))
{
col += printf_wrapper(out, " [%s]", FlopNames[code[i].c]);
}
break;
default:
if ((mode & MODE_BCTYPE) == MODE_BCCAST)
{
switch (code[i].c)
{
case CASTB_I:
mode = MODE_AI | MODE_BI | MODE_CUNUSED;
break;
case CASTB_A:
mode = MODE_AI | MODE_BP | MODE_CUNUSED;
break;
case CAST_I2F:
case CAST_U2F:
mode = MODE_AF | MODE_BI | MODE_CUNUSED;
break;
case CAST_Co2S:
case CAST_So2S:
case CAST_N2S:
case CAST_I2S:
case CAST_U2S:
mode = MODE_AS | MODE_BI | MODE_CUNUSED;
break;
case CAST_F2I:
case CAST_F2U:
case CASTB_F:
mode = MODE_AI | MODE_BF | MODE_CUNUSED;
break;
case CAST_F2S:
case CAST_V22S:
case CAST_V32S:
mode = MODE_AS | MODE_BF | MODE_CUNUSED;
break;
case CAST_P2S:
mode = MODE_AS | MODE_BP | MODE_CUNUSED;
break;
case CAST_S2Co:
case CAST_S2So:
case CAST_S2N:
case CAST_S2I:
case CASTB_S:
mode = MODE_AI | MODE_BS | MODE_CUNUSED;
break;
case CAST_S2F:
mode = MODE_AF | MODE_BS | MODE_CUNUSED;
break;
default:
mode = MODE_AX | MODE_BX | MODE_CIMMZ;
break;
}
}
col = print_reg(out, 0, a, (mode & MODE_ATYPE) >> MODE_ASHIFT, 24, func);
if ((mode & MODE_BCTYPE) == MODE_BCTHROW)
{
if (code[i].a == 0)
{
mode = (MODE_BP | MODE_CUNUSED);
}
else if (code[i].a == 1)
{
mode = (MODE_BKP | MODE_CUNUSED);
}
else
{
mode = (MODE_BCJOINT | MODE_BCIMMS);
}
}
else if ((mode & MODE_BCTYPE) == MODE_BCCATCH)
{
switch (code[i].a)
{
case 0:
mode = MODE_BUNUSED | MODE_CUNUSED;
break;
case 1:
mode = MODE_BUNUSED | MODE_CP;
break;
case 2:
mode = MODE_BP | MODE_CP;
break;
case 3:
mode = MODE_BKP | MODE_CP;
break;
default:
mode = MODE_BIMMZ | MODE_CIMMZ;
break;
}
}
if ((mode & (MODE_BTYPE | MODE_CTYPE)) == MODE_BCJOINT)
{
col += print_reg(out, col, code[i].i16u, (mode & MODE_BCTYPE) >> MODE_BCSHIFT, 16, func);
}
else
{
col += print_reg(out, col, code[i].b, (mode & MODE_BTYPE) >> MODE_BSHIFT, 24, func);
col += print_reg(out, col, code[i].c, (mode & MODE_CTYPE) >> MODE_CSHIFT, 24, func);
}
break;
}
if (cmp && i + 1 < codesize)
{
if (code[i+1].op != OP_JMP)
{ // comparison instructions must be followed by jump
col += printf_wrapper(out, " => *!*!*!*\n");
}
else
{
col += printf_wrapper(out, " => %08x", (i + 2 + code[i+1].i24) << 2);
}
}
if (col > 30)
{
col = 30;
}
printf_wrapper(out, "%*c", 30 - col, ';');
if (!cmp && (code[i].op == OP_JMP || /*code[i].op == OP_TRY ||*/ code[i].op == OP_PARAMI))
{
printf_wrapper(out, "%d\n", code[i].i24);
}
else
{
printf_wrapper(out, "%d,%d,%d", code[i].a, code[i].b, code[i].c);
if (cmp && i + 1 < codesize && code[i+1].op == OP_JMP)
{
printf_wrapper(out, ",%d\n", code[++i].i24);
}
else if (code[i].op == OP_CALL_K && callfunc)
{
printf_wrapper(out, " [%s]\n", callfunc->PrintableName);
}
else
{
printf_wrapper(out, "\n");
}
}
}
}
static int print_reg(FILE *out, int col, int arg, int mode, int immshift, const VMScriptFunction *func)
{
if (mode == MODE_UNUSED || mode == MODE_CMP)
{
return 0;
}
if (col > 0)
{
col = printf_wrapper(out, ",");
}
switch(mode)
{
case MODE_I:
return col+printf_wrapper(out, "d%d", arg);
case MODE_F:
return col+printf_wrapper(out, "f%d", arg);
case MODE_S:
return col+printf_wrapper(out, "s%d", arg);
case MODE_P:
return col+printf_wrapper(out, "a%d", arg);
case MODE_V:
return col+printf_wrapper(out, "v%d", arg);
case MODE_KI:
if (func != NULL)
{
return col+printf_wrapper(out, "%d", func->KonstD[arg]);
}
return printf_wrapper(out, "kd%d", arg);
case MODE_KF:
if (func != NULL)
{
return col+printf_wrapper(out, "%#g", func->KonstF[arg]);
}
return col+printf_wrapper(out, "kf%d", arg);
case MODE_KS:
if (func != NULL)
{
return col+printf_wrapper(out, "\"%.27s\"", func->KonstS[arg].GetChars());
}
return col+printf_wrapper(out, "ks%d", arg);
case MODE_KP:
if (func != NULL)
{
return col+printf_wrapper(out, "%p", func->KonstA[arg]);
}
return col+printf_wrapper(out, "ka%d", arg);
case MODE_KV:
if (func != NULL)
{
return col+printf_wrapper(out, "(%f,%f,%f)", func->KonstF[arg], func->KonstF[arg+1], func->KonstF[arg+2]);
}
return col+printf_wrapper(out, "kv%d", arg);
case MODE_IMMS:
return col+printf_wrapper(out, "%d", (arg << immshift) >> immshift);
case MODE_IMMZ:
return col+printf_wrapper(out, "%d", arg);
case MODE_PARAM:
case MODE_PARAM24:
{
int regtype, regnum;
#ifdef __BIG_ENDIAN__
if (mode == MODE_PARAM)
{
regtype = (arg >> 8) & 255;
regnum = arg & 255;
}
else
{
regtype = (arg >> 16) & 255;
regnum = arg & 65535;
}
#else
if (mode == MODE_PARAM)
{
regtype = arg & 255;
regnum = (arg >> 8) & 255;
}
else
{
regtype = arg & 255;
regnum = (arg >> 8) & 65535;
}
#endif
switch (regtype & (REGT_TYPE | REGT_KONST | REGT_MULTIREG))
{
case REGT_INT:
return col+printf_wrapper(out, "d%d", regnum);
case REGT_FLOAT:
return col+printf_wrapper(out, "f%d", regnum);
case REGT_STRING:
return col+printf_wrapper(out, "s%d", regnum);
case REGT_POINTER:
return col+printf_wrapper(out, "a%d", regnum);
case REGT_FLOAT | REGT_MULTIREG2:
return col+printf_wrapper(out, "v%d.2", regnum);
case REGT_FLOAT | REGT_MULTIREG3:
return col+printf_wrapper(out, "v%d.3", regnum);
case REGT_FLOAT | REGT_MULTIREG4:
return col+printf_wrapper(out, "v%d.4", regnum);
case REGT_INT | REGT_KONST:
return col+print_reg(out, 0, regnum, MODE_KI, 0, func);
case REGT_FLOAT | REGT_KONST:
return col+print_reg(out, 0, regnum, MODE_KF, 0, func);
case REGT_STRING | REGT_KONST:
return col+print_reg(out, 0, regnum, MODE_KS, 0, func);
case REGT_POINTER | REGT_KONST:
return col+print_reg(out, 0, regnum, MODE_KP, 0, func);
case REGT_FLOAT | REGT_MULTIREG | REGT_KONST:
return col+print_reg(out, 0, regnum, MODE_KV, 0, func);
default:
if (regtype == REGT_NIL)
{
return col+printf_wrapper(out, "nil");
}
return col+printf_wrapper(out, "param[t=%d,%c,%c,n=%d]",
regtype & REGT_TYPE,
regtype & REGT_KONST ? 'k' : 'r',
regtype & REGT_MULTIREG ? 'm' : 's',
regnum);
}
}
default:
return col+printf_wrapper(out, "$%d", arg);
}
}
//==========================================================================
//
// Do some postprocessing after everything has been defined
//
//==========================================================================
void DumpFunction(FILE *dump, VMScriptFunction *sfunc, const char *label, int labellen)
{
const char *marks = "=======================================================";
fprintf(dump, "\n%.*s %s %.*s", max(3, 38 - labellen / 2), marks, label, max(3, 38 - labellen / 2), marks);
fprintf(dump, "\nInteger regs: %-3d Float regs: %-3d Address regs: %-3d String regs: %-3d\nStack size: %d\n",
sfunc->NumRegD, sfunc->NumRegF, sfunc->NumRegA, sfunc->NumRegS, sfunc->MaxParam);
VMDumpConstants(dump, sfunc);
fprintf(dump, "\nDisassembly @ %p:\n", sfunc->Code);
VMDisasm(dump, sfunc->Code, sfunc->CodeSize, sfunc);
}