DVX_GUI/apps/dvxbasic/runtime/serialize.c

521 lines
12 KiB
C

// serialize.c -- DVX BASIC module serialization
//
// Binary format (all little-endian, no alignment padding):
//
// Header:
// magic 4 bytes "DBXM"
// version uint16 1
// entryPoint int32
// globalCount int32
// codeLen int32
// constCount int32
// dataCount int32
// procCount int32
//
// code[] codeLen bytes of raw bytecode
//
// constants[] constCount entries, each:
// len uint16 + len bytes of string data
//
// dataPool[] dataCount entries, each:
// type uint8 + value (type-dependent)
//
// procs[] procCount entries, each:
// codeAddr int32
// paramCount int32
// localCount int32
// returnType uint8
// isFunction uint8
// nameLen uint16 + nameLen bytes
#include "serialize.h"
#include "../compiler/opcodes.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Write helpers
// ============================================================
typedef struct {
uint8_t *buf;
int32_t len;
int32_t cap;
} BufT;
static void bufInit(BufT *b) {
b->cap = 4096;
b->buf = (uint8_t *)malloc(b->cap);
b->len = 0;
}
static void bufGrow(BufT *b, int32_t need) {
while (b->len + need > b->cap) {
b->cap *= 2;
}
b->buf = (uint8_t *)realloc(b->buf, b->cap);
}
static void bufWrite(BufT *b, const void *data, int32_t len) {
bufGrow(b, len);
memcpy(b->buf + b->len, data, len);
b->len += len;
}
static void bufWriteU8(BufT *b, uint8_t v) {
bufWrite(b, &v, 1);
}
static void bufWriteU16(BufT *b, uint16_t v) {
bufWrite(b, &v, 2);
}
static void bufWriteI32(BufT *b, int32_t v) {
bufWrite(b, &v, 4);
}
static void bufWriteStr(BufT *b, const char *s) {
uint16_t len = s ? (uint16_t)strlen(s) : 0;
bufWriteU16(b, len);
if (len > 0) {
bufWrite(b, s, len);
}
}
// ============================================================
// Read helpers
// ============================================================
typedef struct {
const uint8_t *data;
int32_t len;
int32_t pos;
} ReaderT;
static bool rOk(const ReaderT *r, int32_t need) {
return r->pos + need <= r->len;
}
static uint8_t rU8(ReaderT *r) {
if (!rOk(r, 1)) {
return 0;
}
return r->data[r->pos++];
}
static uint16_t rU16(ReaderT *r) {
if (!rOk(r, 2)) {
return 0;
}
uint16_t v;
memcpy(&v, r->data + r->pos, 2);
r->pos += 2;
return v;
}
static int32_t rI32(ReaderT *r) {
if (!rOk(r, 4)) {
return 0;
}
int32_t v;
memcpy(&v, r->data + r->pos, 4);
r->pos += 4;
return v;
}
static double rF64(ReaderT *r) {
if (!rOk(r, 8)) {
return 0.0;
}
double v;
memcpy(&v, r->data + r->pos, 8);
r->pos += 8;
return v;
}
// Read a length-prefixed string into a malloc'd buffer.
static char *rStr(ReaderT *r) {
uint16_t len = rU16(r);
if (len == 0 || !rOk(r, len)) {
char *s = (char *)malloc(1);
s[0] = '\0';
return s;
}
char *s = (char *)malloc(len + 1);
memcpy(s, r->data + r->pos, len);
s[len] = '\0';
r->pos += len;
return s;
}
// ============================================================
// basModuleSerialize
// ============================================================
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
if (!mod || !outLen) {
return NULL;
}
BufT b;
bufInit(&b);
// Header
bufWrite(&b, "DBXM", 4);
bufWriteU16(&b, 1); // version
bufWriteI32(&b, mod->entryPoint);
bufWriteI32(&b, mod->globalCount);
bufWriteI32(&b, mod->codeLen);
bufWriteI32(&b, mod->constCount);
bufWriteI32(&b, mod->dataCount);
bufWriteI32(&b, mod->procCount);
// Code
bufWrite(&b, mod->code, mod->codeLen);
// String constant pool
for (int32_t i = 0; i < mod->constCount; i++) {
const char *s = mod->constants[i] ? mod->constants[i]->data : "";
bufWriteStr(&b, s);
}
// Data pool
for (int32_t i = 0; i < mod->dataCount; i++) {
const BasValueT *v = &mod->dataPool[i];
bufWriteU8(&b, v->type);
switch (v->type) {
case BAS_TYPE_INTEGER:
bufWriteI32(&b, v->intVal);
break;
case BAS_TYPE_LONG:
bufWriteI32(&b, v->longVal);
break;
case BAS_TYPE_SINGLE: {
float f = (float)v->dblVal;
bufWrite(&b, &f, 4);
break;
}
case BAS_TYPE_DOUBLE:
bufWrite(&b, &v->dblVal, 8);
break;
case BAS_TYPE_STRING:
bufWriteStr(&b, v->strVal ? v->strVal->data : "");
break;
case BAS_TYPE_BOOLEAN:
bufWriteU8(&b, v->intVal ? 1 : 0);
break;
default:
bufWriteI32(&b, 0);
break;
}
}
// Procedure table
for (int32_t i = 0; i < mod->procCount; i++) {
const BasProcEntryT *p = &mod->procs[i];
bufWriteI32(&b, p->codeAddr);
bufWriteI32(&b, p->paramCount);
bufWriteI32(&b, p->localCount);
bufWriteU8(&b, p->returnType);
bufWriteU8(&b, p->isFunction ? 1 : 0);
bufWriteStr(&b, p->name);
}
// Form variable info (runtime-essential for per-form variable allocation)
bufWriteI32(&b, mod->formVarInfoCount);
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
const BasFormVarInfoT *fv = &mod->formVarInfo[i];
bufWriteStr(&b, fv->formName);
bufWriteI32(&b, fv->varCount);
bufWriteI32(&b, fv->initCodeAddr);
bufWriteI32(&b, fv->initCodeLen);
}
*outLen = b.len;
return b.buf;
}
// ============================================================
// basModuleDeserialize
// ============================================================
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
if (!data || dataLen < 30) {
return NULL;
}
ReaderT r = { data, dataLen, 0 };
// Check magic
if (data[0] != 'D' || data[1] != 'B' || data[2] != 'X' || data[3] != 'M') {
return NULL;
}
r.pos = 4;
uint16_t version = rU16(&r);
if (version != 1) {
return NULL;
}
BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));
mod->entryPoint = rI32(&r);
mod->globalCount = rI32(&r);
mod->codeLen = rI32(&r);
mod->constCount = rI32(&r);
mod->dataCount = rI32(&r);
mod->procCount = rI32(&r);
// Code
if (!rOk(&r, mod->codeLen)) {
free(mod);
return NULL;
}
mod->code = (uint8_t *)malloc(mod->codeLen);
memcpy(mod->code, r.data + r.pos, mod->codeLen);
r.pos += mod->codeLen;
// String constant pool
mod->constants = (BasStringT **)calloc(mod->constCount, sizeof(BasStringT *));
for (int32_t i = 0; i < mod->constCount; i++) {
char *s = rStr(&r);
mod->constants[i] = basStringNew(s, (int32_t)strlen(s));
free(s);
}
// Data pool
if (mod->dataCount > 0) {
mod->dataPool = (BasValueT *)calloc(mod->dataCount, sizeof(BasValueT));
for (int32_t i = 0; i < mod->dataCount; i++) {
uint8_t type = rU8(&r);
mod->dataPool[i].type = type;
switch (type) {
case BAS_TYPE_INTEGER:
mod->dataPool[i].intVal = (int16_t)rI32(&r);
break;
case BAS_TYPE_LONG:
mod->dataPool[i].longVal = rI32(&r);
break;
case BAS_TYPE_SINGLE: {
float f;
if (rOk(&r, 4)) {
memcpy(&f, r.data + r.pos, 4);
r.pos += 4;
} else {
f = 0.0f;
}
mod->dataPool[i].dblVal = (double)f;
break;
}
case BAS_TYPE_DOUBLE:
mod->dataPool[i].dblVal = rF64(&r);
break;
case BAS_TYPE_STRING: {
char *s = rStr(&r);
mod->dataPool[i].strVal = basStringNew(s, (int32_t)strlen(s));
free(s);
break;
}
case BAS_TYPE_BOOLEAN:
mod->dataPool[i].intVal = rU8(&r) ? -1 : 0;
break;
default:
rI32(&r);
break;
}
}
}
// Procedure table
if (mod->procCount > 0) {
mod->procs = (BasProcEntryT *)calloc(mod->procCount, sizeof(BasProcEntryT));
for (int32_t i = 0; i < mod->procCount; i++) {
BasProcEntryT *p = &mod->procs[i];
p->codeAddr = rI32(&r);
p->paramCount = rI32(&r);
p->localCount = rI32(&r);
p->returnType = rU8(&r);
p->isFunction = rU8(&r) != 0;
char *name = rStr(&r);
snprintf(p->name, BAS_MAX_PROC_NAME, "%s", name);
free(name);
}
}
// Form variable info
mod->formVarInfoCount = rI32(&r);
if (mod->formVarInfoCount > 0) {
mod->formVarInfo = (BasFormVarInfoT *)calloc(mod->formVarInfoCount, sizeof(BasFormVarInfoT));
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
BasFormVarInfoT *fv = &mod->formVarInfo[i];
char *fvName = rStr(&r);
snprintf(fv->formName, BAS_MAX_PROC_NAME, "%s", fvName);
free(fvName);
fv->varCount = rI32(&r);
fv->initCodeAddr = rI32(&r);
fv->initCodeLen = rI32(&r);
}
}
return mod;
}
// ============================================================
// basModuleFree
// ============================================================
void basModuleFree(BasModuleT *mod) {
if (!mod) {
return;
}
free(mod->code);
if (mod->constants) {
for (int32_t i = 0; i < mod->constCount; i++) {
basStringUnref(mod->constants[i]);
}
free(mod->constants);
}
if (mod->dataPool) {
for (int32_t i = 0; i < mod->dataCount; i++) {
basValRelease(&mod->dataPool[i]);
}
free(mod->dataPool);
}
free(mod->procs);
free(mod->formVarInfo);
free(mod->debugVars);
if (mod->debugUdtDefs) {
for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
free(mod->debugUdtDefs[i].fields);
}
free(mod->debugUdtDefs);
}
free(mod);
}
// ============================================================
// basDebugSerialize
// ============================================================
uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
if (!mod || !outLen) {
return NULL;
}
BufT b;
bufInit(&b);
// Debug variables
bufWriteI32(&b, mod->debugVarCount);
for (int32_t i = 0; i < mod->debugVarCount; i++) {
const BasDebugVarT *v = &mod->debugVars[i];
bufWriteStr(&b, v->name);
bufWriteStr(&b, v->formName);
bufWriteU8(&b, v->scope);
bufWriteU8(&b, v->dataType);
bufWriteI32(&b, v->index);
bufWriteI32(&b, v->procIndex);
}
*outLen = b.len;
return b.buf;
}
// ============================================================
// basDebugDeserialize
// ============================================================
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen) {
if (!mod || !data || dataLen < 4) {
return;
}
ReaderT r = { data, dataLen, 0 };
// Debug variables
mod->debugVarCount = rI32(&r);
if (mod->debugVarCount > 0) {
mod->debugVars = (BasDebugVarT *)calloc(mod->debugVarCount, sizeof(BasDebugVarT));
for (int32_t i = 0; i < mod->debugVarCount; i++) {
BasDebugVarT *v = &mod->debugVars[i];
char *name = rStr(&r);
char *formName = rStr(&r);
snprintf(v->name, BAS_MAX_PROC_NAME, "%s", name);
snprintf(v->formName, BAS_MAX_PROC_NAME, "%s", formName);
free(name);
free(formName);
v->scope = rU8(&r);
v->dataType = rU8(&r);
v->index = rI32(&r);
v->procIndex = rI32(&r);
}
}
}