395 lines
12 KiB
C
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;
|
|
}
|
|
|
|
|