// 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 #include #include // ============================================================ // 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->debugVars); if (mod->debugUdtDefs) { for (int32_t i = 0; i < mod->debugUdtDefCount; i++) { free(mod->debugUdtDefs[i].fields); } free(mod->debugUdtDefs); } if (mod->formVarInfo) { free(mod->formVarInfo); } 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); } } }