Files
MicroST-Compiler/STCompiler.Simulator/Program.cs

284 lines
15 KiB
C#

using System;
using System.IO;
using STCompiler.Common;
using System.Collections.Generic;
class Program {
static int Main(string[] args) {
if (args.Length < 1) {
Console.WriteLine("Usage: StSim <file.stbc>");
return 1;
}
var path = args[0];
if (!File.Exists(path)) { Console.WriteLine("File not found: " + path); return 2; }
var data = File.ReadAllBytes(path);
try { Simulate(data); } catch(Exception ex) { Console.WriteLine("Error: " + ex.Message); return 3; }
return 0;
}
static void Simulate(byte[] data) {
using var ms = new MemoryStream(data);
using var r = new BinaryReader(ms);
string magic = System.Text.Encoding.ASCII.GetString(r.ReadBytes(4));
if (magic != Bytecode.Magic) throw new Exception("Invalid magic");
ushort ver = r.ReadUInt16();
Console.WriteLine($"Version: {ver}");
bool oldFormat = ver < Bytecode.Version;
ushort nConsts = r.ReadUInt16();
Console.WriteLine($"Consts: {nConsts}");
var consts = new List<object>();
if (oldFormat) {
// old format: constants stored as 4-byte ints
for (int i = 0; i < nConsts; i++) { int v = r.ReadInt32(); consts.Add(v); Console.WriteLine($" [{i}] = {v}"); }
} else {
for (int i = 0; i < nConsts; i++) {
byte t = r.ReadByte();
switch(t) {
case 1: { long v = r.ReadInt64(); consts.Add(v); Console.WriteLine($" [{i}] (long) = {v}"); break; }
case 2: { double v = r.ReadDouble(); consts.Add(v); Console.WriteLine($" [{i}] (double) = {v}"); break; }
case 3: { float v = r.ReadSingle(); consts.Add(v); Console.WriteLine($" [{i}] (float) = {v}"); break; }
case 4: { int v = r.ReadInt32(); consts.Add(v); Console.WriteLine($" [{i}] (int) = {v}"); break; }
default: { Console.WriteLine($" [{i}] Unknown const type {t}"); break; }
}
}
}
ushort nVars = r.ReadUInt16();
Console.WriteLine($"Vars: {nVars}");
var varTypes = new byte[nVars];
for (int i = 0; i < nVars; i++) { varTypes[i] = r.ReadByte(); Console.WriteLine($" Var[{i}] type = {varTypes[i]}"); }
ushort codeLen = r.ReadUInt16();
Console.WriteLine($"CodeLen: {codeLen} bytes");
var code = r.ReadBytes(codeLen);
// Initialize arrays for element type tracking
var arrayRanges = new Dictionary<int, (int start, int length, VarType elementType)>();
Console.WriteLine("\nAnalyzing variable types:");
for (int i = 0; i < nVars; i++) {
Console.WriteLine($" [{i}] = {(VarType)varTypes[i]}");
}
Console.WriteLine("\nDetecting arrays:");
for (int i = 0; i < nVars; i++) {
if (varTypes[i] == (byte)VarType.ARRAY && i + 2 <= nVars) {
// An array consists of:
// - ARRAY marker at position i
// - ElementType at position i+1
// - Array elements starting at position i+2
int arrayStart = i;
VarType elementType = (VarType)varTypes[i + 1];
int startOfElements = i + 2;
// Count elements until we hit either end of vars or a different type
int length = 0;
for (int j = startOfElements; j < nVars; j++) {
if (varTypes[j] != (byte)elementType) break;
length++;
}
arrayRanges[arrayStart] = (startOfElements, length, elementType);
Console.WriteLine($"Found array at index {arrayStart}:");
Console.WriteLine($" - Element type: {elementType}");
Console.WriteLine($" - Elements start at: {startOfElements}");
Console.WriteLine($" - Length: {length}");
// Skip past the array definition (ARRAY marker + element type)
i = startOfElements - 1;
}
}
var stack = new Stack<object>();
var vars = new object[nVars];
int ip = 0;
Console.WriteLine("\n--- Disassembly / Simulation ---");
while (ip < code.Length) {
int addr = ip;
byte op = code[ip++];
Console.Write($"{addr:0000}: 0x{op:X2} ");
switch (op) {
case Bytecode.OpCodes.NOP: Console.WriteLine("NOP"); break;
case Bytecode.OpCodes.PUSH_CONST: {
ushort ci = (ushort)(code[ip++] | (code[ip++] << 8));
if (oldFormat) { byte typeMarker = code[ip++]; /* skip legacy type byte */ }
Console.WriteLine($"PUSH_CONST {ci} ({consts[ci]})");
stack.Push(consts[ci]);
break; }
case Bytecode.OpCodes.PUSH_REAL_CONST: {
ushort ci = (ushort)(code[ip++] | (code[ip++] << 8));
if (oldFormat) { byte typeMarker = code[ip++]; /* skip legacy type byte */ }
Console.WriteLine($"PUSH_REAL_CONST {ci} ({consts[ci]})");
stack.Push(consts[ci]);
break; }
case Bytecode.OpCodes.LOAD_VAR: {
ushort vi = (ushort)(code[ip++] | (code[ip++] << 8));
Console.WriteLine($"LOAD_VAR {vi}");
// Check if the variable index is within bounds
if (vi >= vars.Length) {
throw new Exception($"Variable index {vi} is out of bounds (max: {vars.Length - 1})");
}
if (arrayRanges.ContainsKey(vi)) {
var (start, arrayLength, arrayElementType) = arrayRanges[vi];
var arrayData = new object[arrayLength];
Array.Copy(vars, start, arrayData, 0, arrayLength);
stack.Push(arrayData);
} else {
stack.Push(vars[vi]);
}
break;
}
case Bytecode.OpCodes.STORE_VAR: {
ushort vi = (ushort)(code[ip++] | (code[ip++] << 8));
Console.WriteLine($"STORE_VAR {vi}");
// Check if the variable index is within bounds
if (vi >= vars.Length) {
throw new Exception($"Variable index {vi} is out of bounds (max: {vars.Length - 1})");
}
var value = stack.Pop();
if (arrayRanges.ContainsKey(vi)) {
var (start, arrayLength, arrayElementType) = arrayRanges[vi];
if (value is object[] arr) {
Array.Copy(arr, 0, vars, start, Math.Min(arr.Length, arrayLength));
} else {
Console.WriteLine($"Warning: Attempting to store non-array value in array variable at index {vi}");
vars[vi] = value;
}
} else {
vars[vi] = value;
}
break;
}
case Bytecode.OpCodes.JZ: { ushort target = (ushort)(code[ip++] | (code[ip++] << 8)); Console.WriteLine($"JZ addr={target:0000}"); var cond = stack.Pop(); bool isFalse = cond is int ci ? ci == 0 : cond is long cl ? cl == 0L : cond is double cd ? cd == 0.0 : cond == null; if (isFalse) ip = target; break; }
case Bytecode.OpCodes.JMP: {
ushort target = (ushort)(code[ip++] | (code[ip++] << 8));
Console.WriteLine($"JMP addr={target:0000}");
ip = target;
break;
}
case Bytecode.OpCodes.HALT:
Console.WriteLine("HALT");
ip = code.Length;
break;
case Bytecode.OpCodes.ARRAY_BOUNDS_CHECK:
// Extract the array index from the instruction
byte arrayIndex = 0;
if (ip < code.Length) {
arrayIndex = code[ip];
ip++;
}
// Check if this variable is actually an array
if (!arrayRanges.ContainsKey(arrayIndex)) {
Console.WriteLine($"Warning: ARRAY_BOUNDS_CHECK instruction for non-array variable {arrayIndex}");
continue; // Skip this instruction
}
Console.WriteLine($"ARRAY_BOUNDS_CHECK for array at index {arrayIndex}");
if (stack.Count < 1) {
throw new Exception("Stack underflow during array bounds check");
}
// Convert index value safely
var indexObj = stack.Pop();
int index = Convert.ToInt32(indexObj);
// The arrayStart from the instruction should be the array marker directly
if (!arrayRanges.TryGetValue(arrayIndex, out var arrayInfo)) {
// This should not happen since we checked above
throw new Exception($"Array metadata missing for index {arrayIndex}");
}
var (elementsStart, length, elementType) = arrayInfo;
// Adjust for IEC array indexing (1-based)
int adjustedIndex = index - 1;
if (adjustedIndex < 0 || adjustedIndex >= length) {
throw new Exception($"Array index {index} out of bounds [1..{length}]");
}
// Calculate the actual element address
stack.Push(elementsStart + adjustedIndex);
Console.WriteLine($"Accessing element {index} of array, mapped to variable index {elementsStart + adjustedIndex}");
break;
default:
;
// fallback handlers
if (Bytecode.OpName(op).StartsWith("ADD_") || Bytecode.OpName(op).StartsWith("SUB_") || Bytecode.OpName(op).StartsWith("MUL_") || Bytecode.OpName(op).StartsWith("DIV_")) {
dynamic b = stack.Pop(); dynamic a = stack.Pop(); if (Bytecode.OpName(op).StartsWith("ADD_")) stack.Push(a + b); else if (Bytecode.OpName(op).StartsWith("SUB_")) stack.Push(a - b); else if (Bytecode.OpName(op).StartsWith("MUL_")) stack.Push(a * b); else stack.Push(a / b);
break;
}
if (Bytecode.OpName(op).StartsWith("LT_") || Bytecode.OpName(op).StartsWith("GT_") || Bytecode.OpName(op).StartsWith("LE_") || Bytecode.OpName(op).StartsWith("GE_") || Bytecode.OpName(op).StartsWith("EQ_") || Bytecode.OpName(op).StartsWith("NEQ_")) {
dynamic rVal = stack.Pop(); dynamic lVal = stack.Pop();
bool res = Bytecode.OpName(op).StartsWith("LT_") ? (lVal < rVal) :
Bytecode.OpName(op).StartsWith("GT_") ? (lVal > rVal) :
Bytecode.OpName(op).StartsWith("LE_") ? (lVal <= rVal) :
Bytecode.OpName(op).StartsWith("GE_") ? (lVal >= rVal) :
Bytecode.OpName(op).StartsWith("EQ_") ? (lVal == rVal) :
(lVal != rVal);
stack.Push(res ? 1 : 0);
break;
}
// Array operations
if (op == Bytecode.OpCodes.ARRAY_BOUNDS_CHECK) {
// Extract the array index from the instruction
byte arrayIndexInner = 0;
if (ip < code.Length) {
arrayIndexInner = code[ip];
ip++;
}
// Check if this variable is actually an array
if (!arrayRanges.ContainsKey(arrayIndexInner)) {
Console.WriteLine($"Warning: ARRAY_BOUNDS_CHECK instruction for non-array variable {arrayIndexInner}");
continue; // Skip this instruction
}
Console.WriteLine($"ARRAY_BOUNDS_CHECK for array at index {arrayIndexInner}");
if (stack.Count < 1) {
throw new Exception("Stack underflow during array bounds check");
}
// Convert index value safely
var indexObjInner = stack.Pop();
int indexInner = Convert.ToInt32(indexObjInner);
// The arrayStart from the instruction should be the array marker directly
if (!arrayRanges.TryGetValue(arrayIndexInner, out var arrayInfoInner)) {
// This should not happen since we checked above
throw new Exception($"Array metadata missing for index {arrayIndexInner}");
}
var (elementsStartInner, lengthInner, elementTypeInner) = arrayInfoInner;
if (indexInner < 0 || indexInner >= lengthInner) {
throw new Exception($"Array index {indexInner} out of bounds [0..{lengthInner-1}]");
}
// Calculate the actual element address
stack.Push(elementsStartInner + indexInner);
Console.WriteLine($"Accessing element {indexInner} of array, mapped to variable index {elementsStartInner + indexInner}");
break;
}
throw new Exception($"Unknown opcode 0x{op:X2}");
}
}
Console.WriteLine("Execution finished\n");
// Detailed variable summary
Console.WriteLine("=== Variable summary ===");
Console.WriteLine("Index\tType\t\tValue");
for (int i = 0; i < vars.Length; i++) {
string typeName = (i < varTypes.Length) ? ((VarType)varTypes[i]).ToString() : "Unknown";
var value = vars[i] ?? "null";
Console.WriteLine($"{i}\t{typeName.PadRight(8)}\t{value}");
}
}
}