// The MIT License (MIT) // // Copyright (C) 2026 Scott Duensing // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // codegen.c -- DVX BASIC p-code emitter implementation #include "codegen.h" #include "opcodes.h" #include "thirdparty/stb_ds_wrap.h" #include #include #include // Function prototypes (alphabetical) uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len); void basAddData(BasCodeGenT *cg, BasValueT val); void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName, bool shared); void basCodeGenAddFormVarInfo(BasCodeGenT *cg, const BasFormVarInfoT *info); void basCodeGenAddUdtDef(BasCodeGenT *cg, const BasDebugUdtDefT *def); BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg); void basCodeGenFree(BasCodeGenT *cg); void basCodeGenInit(BasCodeGenT *cg); void basCodeGenTruncate(BasCodeGenT *cg, int32_t codeLen, int32_t constCount); int32_t basCodePos(const BasCodeGenT *cg); void basEmit16(BasCodeGenT *cg, int16_t v); void basEmit32(BasCodeGenT *cg, int32_t v); void basEmit8(BasCodeGenT *cg, uint8_t b); static void basEmitBytes(BasCodeGenT *cg, const void *data, int32_t len); void basEmitDouble(BasCodeGenT *cg, double v); void basEmitI64(BasCodeGenT *cg, int64_t v); void basEmitU16(BasCodeGenT *cg, uint16_t v); void basPatch32(BasCodeGenT *cg, int32_t pos, int32_t val); static uint32_t constantHash(const char *text, int32_t len); static bool copyDebugInfo(BasModuleT *mod, const BasCodeGenT *cg); uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) { // Intern: walk only the hash bucket, comparing the precomputed hash // and length before touching the string bytes. uint32_t h = constantHash(text, len); int32_t *bucket = &cg->constBuckets[h & (BAS_CONST_BUCKETS - 1)]; for (int32_t link = *bucket; link != 0; link = cg->constNext[link - 1]) { int32_t i = link - 1; if (cg->constHashes[i] == h && cg->constants[i]->len == len && memcmp(cg->constants[i]->data, text, len) == 0) { return (uint16_t)i; } } if (cg->constCount >= BAS_MAX_CONSTANTS) { cg->overflow = true; return 0; } uint16_t idx = (uint16_t)cg->constCount; BasStringT *s = basStringNew(text, len); arrput(cg->constants, s); arrput(cg->constHashes, h); arrput(cg->constNext, *bucket); *bucket = idx + 1; cg->constCount = (int32_t)arrlen(cg->constants); return idx; } void basAddData(BasCodeGenT *cg, BasValueT val) { BasValueT copy = basValCopy(val); arrput(cg->dataPool, copy); cg->dataCount = (int32_t)arrlen(cg->dataPool); } void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName, bool shared) { BasDebugVarT dv; memset(&dv, 0, sizeof(dv)); snprintf(dv.name, BAS_MAX_IDENT, "%s", name); dv.scope = scope; dv.dataType = dataType; dv.shared = shared; dv.index = index; dv.procIndex = procIndex; if (formName && formName[0]) { snprintf(dv.formName, BAS_MAX_IDENT, "%s", formName); } arrput(cg->debugVars, dv); cg->debugVarCount = (int32_t)arrlen(cg->debugVars); } void basCodeGenAddFormVarInfo(BasCodeGenT *cg, const BasFormVarInfoT *info) { arrput(cg->formVarInfo, *info); cg->formVarInfoCount = (int32_t)arrlen(cg->formVarInfo); } void basCodeGenAddUdtDef(BasCodeGenT *cg, const BasDebugUdtDefT *def) { arrput(cg->debugUdtDefs, *def); cg->debugUdtDefCount = (int32_t)arrlen(cg->debugUdtDefs); } BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) { // basVmLoadModule refuses a module with more globals than it has // slots; never build one (the compiler reports the error with a // message, this is the last line of defence for other front ends). if (cg->globalCount > BAS_VM_MAX_GLOBALS) { return NULL; } BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT)); if (!mod) { return NULL; } mod->globalCount = cg->globalCount; mod->entryPoint = 0; // Every table is copied in full or the build fails: a module missing // its constants, DATA, form variables or debug tables would load and // then misbehave. Each count is set only once its array exists, so // basModuleFree releases exactly what was copied. mod->code = (uint8_t *)malloc(cg->codeLen); if (!mod->code) { basModuleFree(mod); return NULL; } memcpy(mod->code, cg->code, cg->codeLen); mod->codeLen = cg->codeLen; // Copy constant pool (share string refs) if (cg->constCount > 0) { mod->constants = (BasStringT **)malloc(cg->constCount * sizeof(BasStringT *)); if (!mod->constants) { basModuleFree(mod); return NULL; } for (int32_t i = 0; i < cg->constCount; i++) { mod->constants[i] = basStringRef(cg->constants[i]); } mod->constCount = cg->constCount; } // Copy data pool if (cg->dataCount > 0) { mod->dataPool = (BasValueT *)malloc(cg->dataCount * sizeof(BasValueT)); if (!mod->dataPool) { basModuleFree(mod); return NULL; } for (int32_t i = 0; i < cg->dataCount; i++) { mod->dataPool[i] = basValCopy(cg->dataPool[i]); } mod->dataCount = cg->dataCount; } // The line numbers ERL reports (they survive stripping) int32_t lineNumbers = (int32_t)arrlen(cg->lineNumbers); if (lineNumbers > 0) { mod->lineNumbers = (BasLineNumberT *)malloc((size_t)lineNumbers * sizeof(BasLineNumberT)); if (!mod->lineNumbers) { basModuleFree(mod); return NULL; } memcpy(mod->lineNumbers, cg->lineNumbers, (size_t)lineNumbers * sizeof(BasLineNumberT)); mod->lineNumberCount = lineNumbers; } if (!copyDebugInfo(mod, cg)) { basModuleFree(mod); return NULL; } return mod; } void basCodeGenFree(BasCodeGenT *cg) { for (int32_t i = 0; i < cg->constCount; i++) { basStringUnref(cg->constants[i]); } for (int32_t i = 0; i < cg->dataCount; i++) { basValRelease(&cg->dataPool[i]); } arrfree(cg->code); arrfree(cg->constants); arrfree(cg->constHashes); arrfree(cg->constNext); arrfree(cg->dataPool); arrfree(cg->lineNumbers); arrfree(cg->formVarInfo); arrfree(cg->debugVars); for (int32_t i = 0; i < cg->debugUdtDefCount; i++) { free(cg->debugUdtDefs[i].fields); } arrfree(cg->debugUdtDefs); // Leave the generator in the same state as basCodeGenInit so a reuse // after Free starts clean (no stale counts or overflow flag). basCodeGenInit(cg); } void basCodeGenInit(BasCodeGenT *cg) { memset(cg, 0, sizeof(*cg)); } void basCodeGenTruncate(BasCodeGenT *cg, int32_t codeLen, int32_t constCount) { arrsetlen(cg->code, codeLen); cg->codeLen = codeLen; // Newest first: an entry being dropped is always the head of its // bucket once every newer entry is gone. for (int32_t i = cg->constCount - 1; i >= constCount; i--) { cg->constBuckets[cg->constHashes[i] & (BAS_CONST_BUCKETS - 1)] = cg->constNext[i]; basStringUnref(cg->constants[i]); } arrsetlen(cg->constants, constCount); arrsetlen(cg->constHashes, constCount); arrsetlen(cg->constNext, constCount); cg->constCount = constCount; } int32_t basCodePos(const BasCodeGenT *cg) { return cg->codeLen; } void basEmit16(BasCodeGenT *cg, int16_t v) { uint8_t buf[sizeof(v)]; basWriteI16LE(buf, v); basEmitBytes(cg, buf, (int32_t)sizeof(buf)); } void basEmit32(BasCodeGenT *cg, int32_t v) { uint8_t buf[sizeof(v)]; basWriteI32LE(buf, v); basEmitBytes(cg, buf, (int32_t)sizeof(buf)); } void basEmit8(BasCodeGenT *cg, uint8_t b) { arrput(cg->code, b); cg->codeLen = (int32_t)arrlen(cg->code); } static void basEmitBytes(BasCodeGenT *cg, const void *data, int32_t len) { memcpy(arraddnptr(cg->code, len), data, len); cg->codeLen = (int32_t)arrlen(cg->code); } void basEmitDouble(BasCodeGenT *cg, double v) { basEmitBytes(cg, &v, (int32_t)sizeof(v)); } void basEmitI64(BasCodeGenT *cg, int64_t v) { basEmitBytes(cg, &v, (int32_t)sizeof(v)); } void basEmitU16(BasCodeGenT *cg, uint16_t v) { uint8_t buf[sizeof(v)]; basWriteU16LE(buf, v); basEmitBytes(cg, buf, (int32_t)sizeof(buf)); } void basPatch32(BasCodeGenT *cg, int32_t pos, int32_t val) { if (pos >= 0 && pos + (int32_t)sizeof(val) <= cg->codeLen) { basWriteI32LE(&cg->code[pos], val); } } // FNV-1a over the constant's bytes (case-sensitive: constants are exact). static uint32_t constantHash(const char *text, int32_t len) { uint32_t h = BAS_FNV1A_OFFSET; for (int32_t i = 0; i < len; i++) { h ^= (uint32_t)(uint8_t)text[i]; h *= BAS_FNV1A_PRIME; } return h; } // Copy the form-variable and debugger tables into mod. Returns false on // an allocation failure; each count is set only once its array exists, so // the caller's basModuleFree releases exactly what was copied. static bool copyDebugInfo(BasModuleT *mod, const BasCodeGenT *cg) { if (cg->formVarInfoCount > 0) { mod->formVarInfo = (BasFormVarInfoT *)malloc(cg->formVarInfoCount * sizeof(BasFormVarInfoT)); if (!mod->formVarInfo) { return false; } memcpy(mod->formVarInfo, cg->formVarInfo, cg->formVarInfoCount * sizeof(BasFormVarInfoT)); mod->formVarInfoCount = cg->formVarInfoCount; } if (cg->debugVarCount > 0) { mod->debugVars = (BasDebugVarT *)malloc(cg->debugVarCount * sizeof(BasDebugVarT)); if (!mod->debugVars) { return false; } memcpy(mod->debugVars, cg->debugVars, cg->debugVarCount * sizeof(BasDebugVarT)); mod->debugVarCount = cg->debugVarCount; } if (cg->debugUdtDefCount > 0) { mod->debugUdtDefs = (BasDebugUdtDefT *)calloc(cg->debugUdtDefCount, sizeof(BasDebugUdtDefT)); if (!mod->debugUdtDefs) { return false; } // The calloc left every fields pointer NULL, so a failure part way // through leaves nothing basModuleFree cannot release. mod->debugUdtDefCount = cg->debugUdtDefCount; for (int32_t i = 0; i < cg->debugUdtDefCount; i++) { const BasDebugUdtDefT *src = &cg->debugUdtDefs[i]; mod->debugUdtDefs[i] = *src; mod->debugUdtDefs[i].fields = NULL; if (src->fieldCount > 0) { mod->debugUdtDefs[i].fields = (BasDebugFieldT *)malloc(src->fieldCount * sizeof(BasDebugFieldT)); if (!mod->debugUdtDefs[i].fields) { mod->debugUdtDefs[i].fieldCount = 0; return false; } memcpy(mod->debugUdtDefs[i].fields, src->fields, src->fieldCount * sizeof(BasDebugFieldT)); } } } return true; }