Quote from: José Roca on Yesterday at 10:59:50 AMTo be honest, the few PowerBASIC users who remain are perfectly happy with the old 32‑bit compiler, and they will continue using it indefinitely.
There is no real demand for a new compiler, because the existing one already does everything they need — and most of them are not interested in switching tools or workflows at this point.
That's also why community feedback is so limited: the user base is small, aging, and largely static. They are not looking for innovation; they are looking for stability.
/*
* StrArr.h - Dynamic 8-bit string array
*
* This header provides a complete implementation of a dynamic array
* that stores pointers to null-terminated strings.
*
* Design:
* - Uses long long for indices and counts
* - Stores allocated strings in data array
* - Empty strings ("") are stored as NULL pointer (0)
* - All functions begin with 'StrArr' prefix to avoid conflicts
*/
#ifndef STRARR_H
#define STRARR_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/* Default compare callback - ASCII case sensitive */
int StrArrDefaultCompare(const char* a, const char* b);
/* Case insensitive compare callback */
int StrArrCaseInsensitiveCompare(const char* a, const char* b);
typedef struct {
char** data; /* Array of string pointers */
long long count; /* Number of strings currently stored */
long long capacity; /* Allocated capacity of data array */
} StrArrT;
/* Helper function declarations */
static int StrArrCompareWrapper(const void* a, const void* b);
/* Compare callback type */
typedef int (*StrArrCompareFunc)(const char*, const char*);
/* Global callback for qsort */
static StrArrCompareFunc g_compare_callback = NULL;
/* Wrapper for qsort */
static int StrArrCompareWrapper(const void* a, const void* b) {
const char* str_a = *(const char**)a;
const char* str_b = *(const char**)b;
/* Handle NULL pointers (empty strings) */
if (str_a == NULL) str_a = "";
if (str_b == NULL) str_b = "";
if (g_compare_callback) {
return g_compare_callback(str_a, str_b);
}
return StrArrDefaultCompare(str_a, str_b);
}
/* Initialize a string array structure */
void StrArrInit(StrArrT* arr) {
if (arr == NULL) return;
arr->data = NULL;
arr->count = 0;
arr->capacity = 0;
}
/* Free all memory used by the array and reset structure */
void StrArrFinal(StrArrT* arr) {
long long i;
if (arr == NULL) return;
/* Free each allocated string */
if (arr->data != NULL) {
for (i = 0; i < arr->count; i++) {
if (arr->data[i] != NULL) {
free(arr->data[i]);
}
}
free(arr->data);
}
arr->data = NULL;
arr->count = 0;
arr->capacity = 0;
}
/* Cleanup callback for when StrArrT is stored as handle in other structures */
void StrArrFinalCB(void* arr_ptr) {
StrArrT* arr = (StrArrT*)arr_ptr;
if (arr == NULL) return;
/* Free internal data but not the structure itself */
if (arr->data != NULL) {
long long i;
for (i = 0; i < arr->count; i++) {
if (arr->data[i] != NULL) {
free(arr->data[i]);
}
}
free(arr->data);
}
arr->data = NULL;
arr->count = 0;
arr->capacity = 0;
}
/* Clear all strings from array (same as StrArrFinal) */
void StrArrClear(StrArrT* arr) {
StrArrFinal(arr);
}
/* Ensure enough capacity for additional items */
int StrArrGrow(StrArrT* arr, long long items) {
long long new_capacity;
char** new_data;
if (arr == NULL) return 0;
if (arr->capacity >= arr->count + items) {
return 1; /* Already enough room */
}
/* Calculate new capacity: double the required size or at least 2*(count+items) */
new_capacity = 2 * (arr->count + items);
if (new_capacity < 4) new_capacity = 4;
new_data = (char**)realloc(arr->data, new_capacity * sizeof(char*));
if (new_data == NULL) return 0;
arr->data = new_data;
arr->capacity = new_capacity;
return 1;
}
/* Shrink array to exactly fit current count */
void StrArrShrink(StrArrT* arr) {
char** new_data;
if (arr == NULL || arr->data == NULL) return;
if (arr->capacity <= arr->count) return;
if (arr->count == 0) {
free(arr->data);
arr->data = NULL;
arr->capacity = 0;
return;
}
new_data = (char**)realloc(arr->data, arr->count * sizeof(char*));
if (new_data != NULL) {
arr->data = new_data;
arr->capacity = arr->count;
}
}
/* Return current number of strings */
long long StrArrGetCount(StrArrT* arr) {
if (arr == NULL) return 0;
return arr->count;
}
/* Set array size, growing or shrinking as needed */
int StrArrSetCount(StrArrT* arr, long long items) {
long long i;
if (arr == NULL) return 0;
if (items < 0) return 0;
if (items > arr->capacity) {
if (!StrArrGrow(arr, items - arr->count)) {
return 0;
}
}
/* If shrinking, free removed strings */
if (items < arr->count) {
for (i = items; i < arr->count; i++) {
if (arr->data[i] != NULL) {
free(arr->data[i]);
}
}
}
/* Initialize new elements to NULL */
for (i = arr->count; i < items; i++) {
arr->data[i] = NULL;
}
arr->count = items;
return 1;
}
/* Get string at index - always returns a valid string (never NULL) */
const char* StrArrGet(StrArrT* arr, long long index) {
if (arr == NULL) return "";
if (index < 0 || index >= arr->count) return "";
if (arr->data[index] == NULL) return "";
return arr->data[index];
}
/* Set string at index, freeing old string */
int StrArrSet(StrArrT* arr, long long index, const char* value) {
char* new_str;
if (arr == NULL) return 0;
if (index < 0 || index >= arr->count) return 0;
/* Free existing string if any */
if (arr->data[index] != NULL) {
free(arr->data[index]);
}
/* Store empty string as NULL */
if (value == NULL || value[0] == '\0') {
arr->data[index] = NULL;
return 1;
}
/* Allocate and copy the new string */
new_str = (char*)malloc((strlen(value) + 1) * sizeof(char));
if (new_str == NULL) return 0;
strcpy(new_str, value);
arr->data[index] = new_str;
return 1;
}
/* Add string to the end of the array */
int StrArrAdd(StrArrT* arr, const char* value) {
if (arr == NULL) return 0;
if (!StrArrGrow(arr, 1)) return 0;
arr->data[arr->count] = NULL;
arr->count++;
return StrArrSet(arr, arr->count - 1, value);
}
/* Insert string at specified index, shifting elements up */
int StrArrInsert(StrArrT* arr, long long index, const char* value) {
long long i;
if (arr == NULL) return 0;
if (index < 0 || index > arr->count) return 0;
if (!StrArrGrow(arr, 1)) return 0;
/* Shift elements up */
for (i = arr->count; i > index; i--) {
arr->data[i] = arr->data[i - 1];
}
arr->data[index] = NULL;
arr->count++;
return StrArrSet(arr, index, value);
}
/* Delete string at index, shifting elements down */
int StrArrDelete(StrArrT* arr, long long index) {
long long i;
if (arr == NULL) return 0;
if (index < 0 || index >= arr->count) return 0;
/* Free the string at index */
if (arr->data[index] != NULL) {
free(arr->data[index]);
}
/* Shift elements down */
for (i = index; i < arr->count - 1; i++) {
arr->data[i] = arr->data[i + 1];
}
arr->count--;
/* Shrink if capacity is more than double the count */
if (arr->capacity > 2 * arr->count) {
StrArrShrink(arr);
}
return 1;
}
/* Fast delete: swap with last element before deleting */
int StrArrFastDelete(StrArrT* arr, long long index) {
if (arr == NULL) return 0;
if (index < 0 || index >= arr->count) return 0;
/* Free the string at index */
if (arr->data[index] != NULL) {
free(arr->data[index]);
}
/* If not deleting the last element, copy last element to this position */
if (index != arr->count - 1) {
arr->data[index] = arr->data[arr->count - 1];
}
arr->count--;
/* Shrink if capacity is more than double the count */
if (arr->capacity > 2 * arr->count) {
StrArrShrink(arr);
}
return 1;
}
/* Push string onto stack (same as Add) */
int StrArrPush(StrArrT* arr, const char* value) {
return StrArrAdd(arr, value);
}
/* Peek at top string without removing */
const char* StrArrPeek(StrArrT* arr) {
if (arr == NULL) return "";
if (arr->count == 0) return "";
return StrArrGet(arr, arr->count - 1);
}
/* Pop string from stack (remove and return) */
/* IMPORTANT: Returns allocated memory that caller must free */
/* Returns NULL if error or empty */
char* StrArrPop(StrArrT* arr) {
char* result;
const char* str;
if (arr == NULL) return NULL;
if (arr->count == 0) return NULL;
str = StrArrGet(arr, arr->count - 1);
/* Make a copy of the result */
result = (char*)malloc((strlen(str) + 1) * sizeof(char));
if (result == NULL) return NULL;
strcpy(result, str);
/* Delete the last element */
if (arr->data[arr->count - 1] != NULL) {
free(arr->data[arr->count - 1]);
}
arr->count--;
/* Shrink if needed */
if (arr->capacity > 2 * arr->count) {
StrArrShrink(arr);
}
return result;
}
/* Swap two elements in the array */
int StrArrSwap(StrArrT* arr, long long a, long long b) {
char* temp;
if (arr == NULL) return 0;
if (a < 0 || a >= arr->count) return 0;
if (b < 0 || b >= arr->count) return 0;
if (a == b) return 1;
temp = arr->data[a];
arr->data[a] = arr->data[b];
arr->data[b] = temp;
return 1;
}
/* Sort the array using quicksort with callback */
int StrArrSort(StrArrT* arr, StrArrCompareFunc callback) {
if (arr == NULL) return 0;
if (arr->count <= 1) return 1;
g_compare_callback = callback;
qsort(arr->data, arr->count, sizeof(char*), StrArrCompareWrapper);
return 1;
}
/* Random sort - shuffles the array */
int StrArrRandomSort(StrArrT* arr) {
long long i, j;
if (arr == NULL) return 0;
if (arr->count <= 1) return 1;
srand((unsigned int)time(NULL));
/* Fisher-Yates shuffle */
for (i = arr->count - 1; i > 0; i--) {
j = rand() % (i + 1);
StrArrSwap(arr, i, j);
}
return 1;
}
/* Binary search for value in sorted array */
long long StrArrBinarySearch(StrArrT* arr, const char* value, StrArrCompareFunc callback) {
long long left = 0, right, mid;
int cmp;
if (arr == NULL) return -1;
if (arr->count == 0) return -1;
if (value == NULL) value = "";
right = arr->count - 1;
while (left <= right) {
mid = left + (right - left) / 2;
if (callback) {
cmp = callback(StrArrGet(arr, mid), value);
} else {
cmp = StrArrDefaultCompare(StrArrGet(arr, mid), value);
}
if (cmp < 0) {
left = mid + 1;
} else if (cmp > 0) {
right = mid - 1;
} else {
return mid; /* Found */
}
}
return -1; /* Not found */
}
/* Insert value at sorted position */
int StrArrBinaryInsert(StrArrT* arr, const char* value, StrArrCompareFunc callback) {
long long left = 0, right, mid;
int cmp;
long long insert_pos = 0;
if (arr == NULL) return 0;
if (value == NULL) value = "";
/* If empty, just add */
if (arr->count == 0) {
return StrArrAdd(arr, value);
}
/* Binary search for insertion position */
right = arr->count - 1;
while (left <= right) {
mid = left + (right - left) / 2;
if (callback) {
cmp = callback(StrArrGet(arr, mid), value);
} else {
cmp = StrArrDefaultCompare(StrArrGet(arr, mid), value);
}
if (cmp < 0) {
left = mid + 1;
insert_pos = left;
} else if (cmp > 0) {
right = mid - 1;
insert_pos = mid;
} else {
/* Equal values - insert after existing one */
insert_pos = mid + 1;
break;
}
}
return StrArrInsert(arr, insert_pos, value);
}
/* Binary search and delete value */
int StrArrBinaryDelete(StrArrT* arr, const char* value, StrArrCompareFunc callback) {
long long index;
if (arr == NULL) return 0;
if (value == NULL) value = "";
index = StrArrBinarySearch(arr, value, callback);
if (index == -1) return 1; /* Not found - not an error */
return StrArrDelete(arr, index);
}
/* Remove duplicates (array remains sorted) */
int StrArrUnique(StrArrT* arr, StrArrCompareFunc callback) {
long long i;
if (arr == NULL) return 0;
if (arr->count <= 1) return 1;
/* Sort first */
if (!StrArrSort(arr, callback)) return 0;
/* Remove duplicates from back to front */
for (i = arr->count - 1; i > 0; i--) {
int cmp;
if (callback) {
cmp = callback(StrArrGet(arr, i), StrArrGet(arr, i - 1));
} else {
cmp = StrArrDefaultCompare(StrArrGet(arr, i), StrArrGet(arr, i - 1));
}
if (cmp == 0) {
if (!StrArrDelete(arr, i)) return 0;
}
}
return 1;
}
/* Fast remove duplicates (array may not be sorted after) */
int StrArrFastUnique(StrArrT* arr, StrArrCompareFunc callback) {
long long i;
if (arr == NULL) return 0;
if (arr->count <= 1) return 1;
/* Sort first */
if (!StrArrSort(arr, callback)) return 0;
/* Remove duplicates from back to front using fast delete */
for (i = arr->count - 1; i > 0; i--) {
int cmp;
if (callback) {
cmp = callback(StrArrGet(arr, i), StrArrGet(arr, i - 1));
} else {
cmp = StrArrDefaultCompare(StrArrGet(arr, i), StrArrGet(arr, i - 1));
}
if (cmp == 0) {
if (!StrArrFastDelete(arr, i)) return 0;
}
}
return 1;
}
/* Reverse the array */
int StrArrReverse(StrArrT* arr) {
long long i, j;
if (arr == NULL) return 0;
for (i = 0, j = arr->count - 1; i < j; i++, j--) {
StrArrSwap(arr, i, j);
}
return 1;
}
/* Store array to binary file */
int StrArrFileStore(StrArrT* arr, const char* filename) {
FILE* file;
long long i;
size_t len;
if (arr == NULL) return 0;
if (filename == NULL) return 0;
file = fopen(filename, "wb");
if (file == NULL) return 0;
/* Write count */
if (fwrite(&arr->count, sizeof(long long), 1, file) != 1) {
fclose(file);
return 0;
}
/* Write each string */
for (i = 0; i < arr->count; i++) {
const char* str = StrArrGet(arr, i);
len = strlen(str) + 1; /* Include null terminator */
if (fwrite(&len, sizeof(size_t), 1, file) != 1) {
fclose(file);
return 0;
}
if (fwrite(str, sizeof(char), len, file) != len) {
fclose(file);
return 0;
}
}
fclose(file);
return 1;
}
/* Restore array from binary file */
int StrArrFileRestore(StrArrT* arr, const char* filename) {
FILE* file;
long long i, count;
size_t len;
char* buffer;
if (arr == NULL) return 0;
if (filename == NULL) return 0;
/* Clear existing data */
StrArrFinal(arr);
file = fopen(filename, "rb");
if (file == NULL) return 0;
/* Read count */
if (fread(&count, sizeof(long long), 1, file) != 1) {
fclose(file);
return 0;
}
/* Set count (allocate space) */
if (!StrArrSetCount(arr, count)) {
fclose(file);
return 0;
}
/* Read each string */
for (i = 0; i < count; i++) {
if (fread(&len, sizeof(size_t), 1, file) != 1) {
StrArrFinal(arr);
fclose(file);
return 0;
}
buffer = (char*)malloc(len * sizeof(char));
if (buffer == NULL) {
StrArrFinal(arr);
fclose(file);
return 0;
}
if (fread(buffer, sizeof(char), len, file) != len) {
free(buffer);
StrArrFinal(arr);
fclose(file);
return 0;
}
/* Store string */
if (len == 1 && buffer[0] == '\0') {
free(buffer);
arr->data[i] = NULL;
} else {
arr->data[i] = buffer;
}
}
fclose(file);
return 1;
}
/* Default compare function - ASCII case sensitive */
int StrArrDefaultCompare(const char* a, const char* b) {
if (a == NULL) a = "";
if (b == NULL) b = "";
return strcmp(a, b);
}
/* Case insensitive compare function */
int StrArrCaseInsensitiveCompare(const char* a, const char* b) {
if (a == NULL) a = "";
if (b == NULL) b = "";
return _stricmp(a, b); /* MSVC specific, use strcasecmp on other platforms */
}
#endif /* STRARR_H */
## Swarm State — Live Update
| Worker | Status | Result |
|--------|--------|--------|
| **W1 — Benchmark Analyzer** | ✅ **COMPLETE** | DEFECT_REPORT.md produced (8KB) |
| **W2 — Array Codegen** | ⏳ Running | No commits yet |
| **W3 — Loop Optimizer** | ⏳ Running | No commits yet |
| **W4 — Opt Pass Enabler** | ⏳ Running | No commits yet |
| **W5 — Intrinsic Inliner** | ⏳ Running | No commits yet |
---
## W1 Report Summary (Ready Now)
The analyzer disassembled the 10 worst tests and identified **4 root cause clusters**:
### 1. Stack-Only Locals — The Dominant Defect
**Every local variable (including loop counters) lives on the stack.** No scalar register allocation.
Test 021 (`DO_WHILE_loop`) — **6.43× slower** than PBWin:
```asm
lea eax, [ebp-8] ; &i
mov eax, [eax] ; load i from stack
mov [ebp-16], eax ; spill to another stack slot
mov edx, [ebp-16] ; reload
cmp edx, 100000
```
PBWin keeps `i` in `ECX` or `EDX`. PBXA32 does a 5-instruction stack dance per increment.
**Fix target:** `src/codegen/codegen_ra_linear_scan.inc` — promote high-use `alloca` values (especially loop induction variables) to physical registers.
### 2. Missing `SDIV` Strength Reduction
The optimizer rewrites `MUL` → `SHL` and `UDIV` → `LSHR`, but **has no rewrite for signed division**. Test 072 (`shift_right__divide`) still emits full `CDQ`+`IDIV` for `i \ 2`.
**Fix target:** `src/optimizer/pipeline_strength.inc`
### 3. Array Access Through Runtime Helpers
Even fixed-size local arrays call `__pbxa32_lnarr_get` / `__pbxa32_lnarr_set` instead of inline `base + (index * size)` addressing.
**Fix target:** `src/ir/lowering_expr_main.inc`, `src/ir/lowering_stmt_assign.inc`
### 4. String/Math Builtins as Runtime Calls
`LEFT$`, `CHR$`, `ASC`, `ATN`, `CHOOSE` — all call into `pbxa32rt.o` every iteration. `CHOOSE` is lowered to nested `IIF` runtime calls instead of a jump table.
**Fix target:** `src/codegen/codegen_intrinsics_main.inc`, `src/ir/lowering_builtins_string_core.inc`
---
## Ranked Impact Table
| Rank | Test | Ratio | Defect | Est. Speedup |
|------|------|-------|--------|-------------|
| 1 | 021 DO_WHILE_loop | **6.43×** | Stack-only locals | ~5.5× |
| 2 | 031 LEFT$ | **5.33×** | String runtime calls | ~4.5× |
| 3 | 038 CHR$/ASC | **4.10×** | Per-iteration runtime calls | ~3.5× |
| 4 | 015 FOR STEP -1 | **2.73×** | Stack-only locals | ~2.2× |
| 5 | 072 shift_right | **2.74×** | Missing SDIV→SAR | ~2.0× |
| 6 | 066 ATN | **2.83×** | Math runtime call | ~2.2× |
| 7 | 063 MIN/MAX | **2.66×** | CMOV result spilled | ~2.0× |
| 8 | 074 CHOOSE | **2.62×** | Nested IIF calls | ~2.2× |
| 9 | 044 1D array | **2.27×** | Array runtime helpers | ~1.8× |
| 10 | 048 2D array | **2.16×** | Array helpers + IDIV | ~1.7× |
---
## What This Means for W2–W5
The analyzer's findings **validate the worker assignments** and add precision:
- **W3 (Loop Optimizer)** — The #1 and #4 defects confirm that register-promoting loop counters is the highest-impact fix. The ASM shows exactly where to intervene.
- **W2 (Array Codegen)** — Confirmed: arrays go through `__pbxa32_lnarr_get/set`. The fix is to bypass these for fixed local arrays.
- **W5 (Intrinsic Inliner)** — Confirmed: `CHR$`, `ASC`, `LEFT$`, `ATN`, `CHOOSE` are all runtime calls. The inline paths are well-defined.
- **W4 (Opt Pass Enabler)** — The missing `SDIV`→`SAR` strength reduction is a perfect target for the optimizer pipeline. Also, enabling `sparse-dce` would remove the redundant stack spills shown in the ASM.
**W2–W5 are still running.** I'll report again as each completes. The first to finish will likely be W5 (intrinsics — self-contained changes) or W2 (arrays — focused scope).
## Swarm State — Active Progress Update
All **4 coder workers (W2–W5)** are actively editing source files. Here's the live diff from their worktrees:
| Worker | Files Modified | Key Changes |
|--------|---------------|-------------|
| **W2 — Arrays** | 5 files | `lowering_builtins_array.inc` +129B, `codegen.c` +522B, `codegen_emit_ir_ops.inc` −1,019B |
| **W3 — Loops** | 4 files | `codegen_ra_linear_scan.inc` −2,397B (**major RA refactor**), `codegen.c` +522B, `codegen_emit_ir_ops.inc` −1,019B |
| **W4 — Opt Pass** | 6 files | `analysis.c` −2,482B (**alias analysis added**), `pipeline.c` +725B, `pipeline_opt_passes.inc` −599B |
| **W5 — Intrinsics** | 3 files | `codegen_intrinsics_helpers.inc` −1,789B (**inlined helpers**), `codegen_intrinsics_main.inc` −161B |
### Notable Observations
1. **W3 cut `codegen_ra_linear_scan.inc` by 2,397 bytes** — This is a significant reduction, likely removing redundant spill code or consolidating the register allocator. This directly addresses the #1 defect from W1's report (stack-only locals).
2. **W4 cut `analysis.c` by 2,482 bytes** — Likely replaced placeholder/stub code with actual alias analysis. This enables `sparse-dce`.
3. **W2, W3, and W4 all touched the same 3 files** (`lowering_control_loops.inc`, `codegen.c`, `codegen_emit_ir_ops.inc`) with **identical byte deltas**. This suggests they may have made the same or compatible changes — good for clean merging.
4. **W5 built `PBXA32.exe`** and generated test BAS files (`_test__061.bas` through `_test__079.bas`) — actively validating the inline intrinsic changes.
### W1 Status
✅ **COMPLETE** — DEFECT_REPORT.md delivered. The 10-test analysis identified 4 root cause clusters with specific file targets. All 4 coder workers are now addressing these exact defects.
### Next Milestones
- W5 appears closest to completion (built + testing)
- W3's RA refactor is the highest-impact change
- W4's alias analysis unlocks the disabled optimizer passes
- W2's array bypass is the most complex (touches semantic + lowering + codegen)
**All workers are still running.** I'll merge and verify as each commits.# PBXA32 Project Analysis — Where We Are
## Executive Summary
**PBXA32** is a full-stack compiler for **PowerBASIC syntax** written in **ISO C11**, targeting **32-bit x86 PE-COFF executables** on Windows. It is a from-scratch implementation of a commercial-grade BASIC compiler with its own lexer, parser, semantic analyzer, SSA-like IR, optimizer pipeline, x86 native code generator, COFF assembler, and PE32 linker.
**Current version**: 0.2.0 (A15 integration)
**Codebase**: ~100+ C source files + `.inc` fragments, well over 100,000 lines of C
**Build host**: MinGW-w64 GCC 15.2+ on Windows
---
## Compiler Architecture (Front-to-Back)
| Stage | Status | Key Files |
|-------|--------|-----------|
| **Lexer** | ✅ Complete | `src/lexer/lexer.c`, `token.c` — case-insensitive keywords, typed identifiers, literals, compound assignments |
| **Preprocessor** | ✅ Complete | `src/preprocessor/preprocessor.c` — multi-pass `#INCLUDE`, directives, macros |
| **Parser** | ✅ Complete | `src/parser/parser.c` + 20+ `.inc` fragments — recursive-descent AST, namespaces, classes, interfaces, full statement set |
| **Semantic Analyzer** | ✅ Complete | `src/semantic/analyzer.c`, `typesys.c`, `symbols.c` — type system, symbol tables, builtin registration |
| **IR (Lowering)** | ✅ Active | `src/ir/lowering.c` + 30+ `.inc` fragments — AST → SSA-like IR with value IDs, basic blocks, intrinsics |
| **Optimizer** | ⚠️ Shallow | `src/optimizer/pipeline.c`, `analysis.c` — mem2reg, copyprop/CSE, instcombine, strength reduction; **sparse-dce disabled at O2**, **licm-lite disabled at O3** |
| **x86 Codegen** | ✅ Complete | `src/codegen/codegen.c` + 15+ `.inc` fragments — EBP-framed locals, stack allocation, vreg allocator, inline asm support |
| **Assembler** | ✅ Complete | `src/assembler/object_writer.c` — COFF object writer with `.text/.rdata/.data/.bss`, MODRM/SIB encoding, debug info |
| **Linker** | ✅ Complete | `src/linker/linker.c` — PE32 linker with import thunks, exports, startup thunk, BSS handling |
| **Runtime** | ✅ Complete | `src/runtime/pbxa32rt.c` + containers + math — freestanding 32-bit runtime, no CRT dependency |
---
## Current Verification State
### Benchmarks (100-test suite vs PBCC / PBWin / SB)
- **Compile-time**: PBXA32 wins on **all 93** compiled tests — ~2.4× faster than PBCC, ~2.4× faster than PBWin
- **Runtime**: PBXA32 wins only **4/83** vs PBWin — PBWin is ~1.2× faster overall, SB ~0.6×
- **Correctness**: **93/100** tests compile, run, and produce matching exit codes
- **7 tests** fail to compile (mostly directives: `ENUM`, `TYPE`, `MACRO`, `JOIN`, `SPLIT` in some configs, `ON_ERROR_GOTO`, `HEX/OCT/BIN`)
### Stage Tests (Internal)
- **Smoke tests**: 15/15 PASS
- **E2E tests**: 47/47 PASS
- **Stage tests**: 79/92 PASS (13 pre-existing failures, not regressions)
### Known Pre-Existing Failures
1. **`#DIM ALL` + `FOR EACH`** — parser/analyzer gap with implicit declarations
2. **`ON ERROR GOTO`** — codegen pending
3. **`CCODE` / `CVARGET` / `DECLARE`** — C interop edge cases
4. **File I/O stages** (`stage_ap_fileio2`, `stage_ay2_xprint`) — pre-existing runtime issues
5. **Optimizer golden IR mismatch** — expected, pipeline is evolving
6. **`TIME$` / `DATE$` `LEN()`** — string pointer tracking bug
7. **`MACRO` runtime crash** — preprocessor expansion edge case
---
## Recent Work (Last Session — A15 Integration)
The most recent commit (`a124dee`) integrated A15 with:
- 100% assembler coverage + 8-bit full pipeline
- Fixed `BITSET`/`BITRESET` lowering via new intrinsic path
- Fixed `INCR`/`DECR` with 2-argument step values
- Fixed `SPLIT` array statement (parser + lowering + analyzer)
- Fixed `REPEAT$` crash (0xC0000005) — parameter order corrected in runtime
- Fixed `ENUM` member linker error (symbol table lookup)
- Fixed `#DIM ALL` rejecting new builtins
---
## Where the Leverage Is (Open Work)
### 1. **Runtime Performance** (Biggest Gap)
PBXA32 compiles faster but runs slower than PBWin. The README explicitly states:
> *"Array codegen and loop-heavy control flow are still the largest runtime hot spots."*
This is where an x86/optimization expert would focus:
- Array access lowering generates suboptimal addressing modes
- Loop control flow (FOR/WHILE/DO) has overhead from the EBP-frame style
- The optimizer pipeline is "shallow" — mem2reg and instcombine run, but LICM and aggressive DCE are disabled because the codegen can't yet handle the IR shapes they produce
### 2. **Optimizer Pipeline Depth**
Two passes are explicitly disabled:
- `sparse-dce` at O2 — *"memory deps not traced"*
- `licm-lite` at O3 — *"codegen forward-reference issue"*
Unlocking these would likely close the runtime gap with PBWin.
### 3. **Long-Tail Language Features**
- Deep COM support
- `ON ERROR GOTO` (control-flow exception model)
- `MACRO` preprocessor expansion stability
- `JOIN` / `SPLIT` full array runtime
- `HEX$` / `OCT$` / `BIN$` string formatting
---
## Build & Development Workflow
```powershell
# Full compiler build
.\build_compiler.ps1
# Run full test lane
powershell -File test_lane\run_all.ps1
# 100-test benchmark vs PBCC/PBWin
powershell -File test_lane\run_bench_100.ps1
```
The compiler is built as a **64-bit host executable** (`PBXA32.exe`) that cross-compiles to **32-bit x86 PE** targets. The target runtime is compiled separately with `-m32 -ffreestanding -nostdlib` and linked into user programs.
---
## Bottom Line
**You have a functionally complete, correctness-verified PowerBASIC compiler that successfully compiles and runs ~93% of a 100-test benchmark suite against the commercial reference compilers (PBCC, PBWin).**
The compiler is **solid on correctness** and **wins on compile-time speed**. The remaining work is **runtime performance optimization** (array codegen, loop lowering, enabling the disabled optimizer passes) and **long-tail feature completion** (COM, error handling, a few string builtins).
This is no longer a "can we build a compiler?" project. It is a **"can we make it faster than the incumbent?"** project. The architecture is sound; the IR and optimizer are the next leverage points.Provider | Einstieg | Erneuerung | Uptime | Datacenter | Money-Back
-----------------|----------|------------|-----------|------------|------------
IONOS | $1/mo | $14-18/mo | 99.9% | 2 | 30 Tage
Hostinger | $2.69/mo | $7.99/mo | 99.9% | 8 | 30 Tage
SiteGround | $2.99/mo | ~$15/mo | 99.9% | 6+ | 30 Tage
Bluehost | $1.99/mo | $9.99/mo | 99.9% | 1+CDN | 30 Tage
A2 Hosting | $2.99/mo | $10.99/mo | 99.9% | 3 | Anytime
HostArmada | $1.99/mo | $9.95/mo | 99.9% | 9 | 45 Tage
DreamHost | $2.89/mo | $10.99/mo | 100% SLA | 2+CDN | 97 Tage
Chemicloud | $2.49/mo | $11.95/mo | 99.99% | 7 | 45 Tage
Page created in 0.105 seconds with 18 queries.