DVX_GUI/src/apps/kpunch/dvxbasic/compiler/codegen.c
2026-10-05 20:01:04 -05:00

395 lines
12 KiB
C

// 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
// 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;
}