Skip to content

Commit d2eba07

Browse files
Fix stack values (#814)
* fixed stack checking * added stack tests
1 parent 782c619 commit d2eba07

4 files changed

Lines changed: 191 additions & 55 deletions

File tree

src/views/component-viewer/scvd-debug-target.ts

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -415,54 +415,62 @@ export class ScvdDebugTarget {
415415
return this.readMemory(address, maxLength * bytesPerChar);
416416
}
417417

418+
private normalizeCalcMemUsedArgs(
419+
stackAddress: number,
420+
stackSize: number,
421+
fillPattern: number,
422+
magicValue: number
423+
): { addr: number; size: number; fill: number; magic: number } {
424+
const toUint32 = (value: number): number => (Number.isFinite(value) ? (value >>> 0) : 0);
425+
return {
426+
addr: toUint32(stackAddress),
427+
size: toUint32(stackSize),
428+
fill: toUint32(fillPattern),
429+
magic: toUint32(magicValue),
430+
};
431+
}
432+
418433
public async calculateMemoryUsage(startAddress: number, size: number, FillPattern: number, MagicValue: number): Promise<number | undefined> {
419-
const memData = await this.readMemory(startAddress, size);
420-
if (memData !== undefined) {
421-
let usedBytes = 0;
422-
const fillPatternBytes = new Uint8Array(4);
423-
const magicValueBytes = new Uint8Array(4);
424-
// Use FillPattern for the fill pattern bytes (little-endian)
425-
fillPatternBytes[0] = (FillPattern & 0xFF);
426-
fillPatternBytes[1] = (FillPattern >> 8) & 0xFF;
427-
fillPatternBytes[2] = (FillPattern >> 16) & 0xFF;
428-
fillPatternBytes[3] = (FillPattern >> 24) & 0xFF;
429-
// Use MagicValue for the magic value bytes (little-endian)
430-
magicValueBytes[0] = (MagicValue & 0xFF);
431-
magicValueBytes[1] = (MagicValue >> 8) & 0xFF;
432-
magicValueBytes[2] = (MagicValue >> 16) & 0xFF;
433-
magicValueBytes[3] = (MagicValue >> 24) & 0xFF;
434-
435-
for (let i = 0; i < memData.length; i += 4) {
436-
const chunk = memData.subarray(i, i + 4);
437-
const matchesFill = chunk.every((byte, idx) => byte === fillPatternBytes.at(idx));
438-
const matchesMagic = matchesFill || chunk.every((byte, idx) => byte === magicValueBytes.at(idx));
439-
if (!matchesMagic) {
440-
usedBytes += chunk.length;
441-
}
442-
}
434+
const normalized = this.normalizeCalcMemUsedArgs(startAddress, size, FillPattern, MagicValue);
435+
if (normalized.addr === 0 || normalized.size === 0) {
436+
return 0;
437+
}
438+
const memData = await this.readMemory(normalized.addr, normalized.size);
439+
if (memData === undefined || memData.length < normalized.size) {
440+
return undefined;
441+
}
442+
443+
const k = Math.floor(normalized.size / 4); // number of u32 words
444+
if (k <= 0) {
445+
return 0;
446+
}
447+
448+
const view = new DataView(memData.buffer, memData.byteOffset, memData.byteLength);
449+
const readWord = (index: number): number => view.getUint32(index * 4, true);
450+
const word0 = readWord(0);
451+
const fill = normalized.fill;
452+
const magic = normalized.magic;
443453

444-
const usedPercent = Math.floor((usedBytes / size) * 100) & 0x1FF;
445-
let result = usedBytes & 0xFFFFF; // bits 0..19
446-
result |= (usedPercent << 20); // bits 20..28
447-
448-
// Check for overflow (MagicValue overwritten)
449-
let overflow = true;
450-
const tailStart = Math.max(0, memData.length - 4);
451-
for (let i = tailStart; i < memData.length; i++) {
452-
const expected = magicValueBytes.at(i - tailStart);
453-
if (memData.at(i) !== expected) {
454-
overflow = false;
454+
let n = 0;
455+
if (word0 === magic) {
456+
n = 1; // aStk[0] := MAGIC pattern for overflow check.
457+
for (; n < k; n += 1) {
458+
if (readWord(n) !== fill) {
455459
break;
456460
}
457461
}
458-
if (overflow) {
459-
result |= (1 << 31); // set overflow bit
460-
}
462+
}
461463

462-
return result;
464+
const usedWords = k - n;
465+
const usedBytes = usedWords * 4;
466+
const usedPercent = Math.trunc((usedWords * 100) / k);
467+
468+
let result = (usedBytes & 0xFFFFF) | ((usedPercent & 0xFF) << 20);
469+
if (fill !== magic && word0 !== magic) {
470+
result |= (1 << 31); // overflow indicator
463471
}
464472

465-
return undefined;
473+
return result >>> 0;
466474
}
467475

468476

src/views/component-viewer/scvd-eval-interface.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,10 @@ export class ScvdEvalInterface implements ModelHost, DataAccessHost, IntrinsicPr
348348
*/
349349
public async __CalcMemUsed(stackAddress: number, stackSize: number, fillPattern: number, magicValue: number): Promise<number | undefined> {
350350
const memUsed = await this.debugTarget.calculateMemoryUsage(
351-
stackAddress >>> 0,
352-
stackSize >>> 0,
353-
fillPattern >>> 0,
354-
magicValue >>> 0
351+
stackAddress,
352+
stackSize,
353+
fillPattern,
354+
magicValue
355355
);
356356
return memUsed;
357357
}

src/views/component-viewer/test/integration/parser-evaluator/evaluator/evaluator-datahost-hooks.test.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { MemoryHost } from '../../../../data-host/memory-host';
2929
import { ScvdFormatSpecifier } from '../../../../model/scvd-format-specifier';
3030
import { RegisterHost } from '../../../../data-host/register-host';
3131
import { ScvdDebugTarget } from '../../../../scvd-debug-target';
32+
import { TargetReadCache } from '../../../../target-read-cache';
3233

3334
class BasicRef extends ScvdNode {
3435
constructor(parent?: ScvdNode) {
@@ -356,6 +357,104 @@ describe('evaluator data host hooks', () => {
356357
expect(out).toBe('mac=1E-30-6C-A2-45-5F');
357358
});
358359

360+
it('evaluates __CalcMemUsed with varied stack usage and uses the readMemory cache', async () => {
361+
const packWords = (...words: number[]) => {
362+
const bytes = new Uint8Array(words.length * 4);
363+
const view = new DataView(bytes.buffer);
364+
words.forEach((word, index) => view.setUint32(index * 4, word >>> 0, true));
365+
return bytes;
366+
};
367+
368+
const makeFreeBytesData = (freeBytes: number, corruptMagicBytes = 0): Uint8Array => {
369+
const bytes = new Uint8Array(12);
370+
bytes.fill(0xcc);
371+
const view = new DataView(bytes.buffer);
372+
view.setUint32(0, magic >>> 0, true);
373+
if (corruptMagicBytes > 0) {
374+
const magicCorruptLen = Math.min(magicBytes, corruptMagicBytes);
375+
const start = magicBytes - magicCorruptLen;
376+
bytes.set(corruptBytes.subarray(0, magicCorruptLen), start);
377+
}
378+
if (freeBytes < 8) {
379+
bytes.set(corruptBytes.subarray(0, 8 - freeBytes), 4 + freeBytes);
380+
}
381+
return bytes;
382+
};
383+
384+
const fill = 0xCCCCCCCC;
385+
const magic = 0xE25A2EA5;
386+
const magicBytes = 4;
387+
const corruptBytes = new Uint8Array([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]);
388+
const cases = [
389+
{ name: 'free 0 bytes', size: 12, data: makeFreeBytesData(0), usedBytes: 8, overflow: 0 },
390+
{ name: 'free 1 bytes', size: 12, data: makeFreeBytesData(1), usedBytes: 8, overflow: 0 },
391+
{ name: 'free 2 bytes', size: 12, data: makeFreeBytesData(2), usedBytes: 8, overflow: 0 },
392+
{ name: 'free 3 bytes', size: 12, data: makeFreeBytesData(3), usedBytes: 8, overflow: 0 },
393+
{ name: 'free 4 bytes', size: 12, data: makeFreeBytesData(4), usedBytes: 4, overflow: 0 },
394+
{ name: 'free 5 bytes', size: 12, data: makeFreeBytesData(5), usedBytes: 4, overflow: 0 },
395+
{ name: 'magic partially corrupted', size: 12, data: makeFreeBytesData(4, 1), usedBytes: 12, overflow: 1 },
396+
{ name: 'all free', size: 8, data: packWords(magic, fill), usedBytes: 0, overflow: 0 },
397+
{ name: 'all used', size: 8, data: packWords(magic, 0x11111111), usedBytes: 4, overflow: 0 },
398+
{ name: 'overflow', size: 8, data: packWords(0xDEADBEEF, fill), usedBytes: 8, overflow: 1 },
399+
{ name: 'magic corrupted with usage', size: 8, data: packWords(0xDEADBEEF, 0x11111111), usedBytes: 8, overflow: 1 },
400+
{ name: 'fill equals magic', size: 8, data: packWords(0x11111111, magic), usedBytes: 8, overflow: 0, fillOverride: magic },
401+
{ name: 'non-multiple size', size: 10, data: packWords(magic, fill, fill), usedBytes: 0, overflow: 0 },
402+
];
403+
404+
for (const testCase of cases) {
405+
const readMemoryFromTarget = jest.fn().mockResolvedValue(testCase.data);
406+
const debugTarget = new ScvdDebugTarget();
407+
(debugTarget as unknown as { targetReadCache: TargetReadCache | undefined }).targetReadCache = new TargetReadCache();
408+
(debugTarget as unknown as { readMemoryFromTarget: (addr: number | bigint, size: number) => Promise<Uint8Array | undefined> })
409+
.readMemoryFromTarget = readMemoryFromTarget;
410+
411+
const evalIf = new ScvdEvalInterface(
412+
new MemoryHost(),
413+
{} as RegisterHost,
414+
debugTarget,
415+
new ScvdFormatSpecifier()
416+
);
417+
const ctx = new EvalContext({ data: evalIf, container: new BasicRef() });
418+
const pr = parseExpression(
419+
`__CalcMemUsed(0x1000, ${testCase.size}, 0x${(testCase.fillOverride ?? fill).toString(16).toUpperCase()}, 0x${magic.toString(16).toUpperCase()})`,
420+
false
421+
);
422+
423+
const out1 = await evaluator.evaluateParseResult(pr, ctx);
424+
const out2 = await evaluator.evaluateParseResult(pr, ctx);
425+
426+
const usedPercent = Math.trunc((testCase.usedBytes * 100) / testCase.size);
427+
const expected = ((testCase.usedBytes & 0xfffff) | ((usedPercent & 0xff) << 20) | (testCase.overflow ? 1 << 31 : 0)) >>> 0;
428+
expect(out1).toBe(expected);
429+
expect(out2).toBe(expected);
430+
expect(readMemoryFromTarget).toHaveBeenCalledTimes(1);
431+
}
432+
433+
const readMemoryFromTarget = jest.fn();
434+
const debugTarget = new ScvdDebugTarget();
435+
(debugTarget as unknown as { targetReadCache: TargetReadCache | undefined }).targetReadCache = new TargetReadCache();
436+
(debugTarget as unknown as { readMemoryFromTarget: (addr: number | bigint, size: number) => Promise<Uint8Array | undefined> })
437+
.readMemoryFromTarget = readMemoryFromTarget;
438+
const evalIf = new ScvdEvalInterface(
439+
new MemoryHost(),
440+
{} as RegisterHost,
441+
debugTarget,
442+
new ScvdFormatSpecifier()
443+
);
444+
const ctx = new EvalContext({ data: evalIf, container: new BasicRef() });
445+
const addrZero = parseExpression(
446+
`__CalcMemUsed(0, 8, 0x${fill.toString(16).toUpperCase()}, 0x${magic.toString(16).toUpperCase()})`,
447+
false
448+
);
449+
await expect(evaluator.evaluateParseResult(addrZero, ctx)).resolves.toBe(0);
450+
const sizeZero = parseExpression(
451+
`__CalcMemUsed(0x1000, 0, 0x${fill.toString(16).toUpperCase()}, 0x${magic.toString(16).toUpperCase()})`,
452+
false
453+
);
454+
await expect(evaluator.evaluateParseResult(sizeZero, ctx)).resolves.toBe(0);
455+
expect(readMemoryFromTarget).not.toHaveBeenCalled();
456+
});
457+
359458
it('computes nested array offsets for member and var arrays', async () => {
360459
const host = new NestedArrayHost();
361460
const ctx = new EvalContext({ data: host, container: host.root });

src/views/component-viewer/test/integration/scvd-debug-target.test.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,27 +220,56 @@ describe('scvd-debug-target', () => {
220220
});
221221

222222
it('calculates memory usage and overflow bit', async () => {
223+
const packWords = (...words: number[]) => {
224+
const bytes = new Uint8Array(words.length * 4);
225+
const view = new DataView(bytes.buffer);
226+
words.forEach((word, index) => view.setUint32(index * 4, word >>> 0, true));
227+
return bytes;
228+
};
229+
223230
class MemTarget extends ScvdDebugTarget {
224231
constructor(private readonly data: Uint8Array) { super(); }
225232
async readMemory(): Promise<Uint8Array | undefined> {
226233
return this.data;
227234
}
228235
}
229-
// Two chunks: first fill pattern, second magic value triggers overflow bit
230-
const data = new Uint8Array([0, 0, 0, 0, 0x44, 0x33, 0x22, 0x11]);
231-
const target = new MemTarget(data);
232-
const result = await target.calculateMemoryUsage(0, 8, 0, 0x11223344);
233-
expect(result).toBeDefined();
234-
expect((result as number) >>> 0).toBe(0x80000000);
236+
237+
const fill = 0xCCCCCCCC;
238+
const magic = 0xE25A2EA5;
239+
240+
// Magic at start, remaining fill => 0 used, 0%, no overflow
241+
const clean = new MemTarget(packWords(magic, fill));
242+
const cleanResult = await clean.calculateMemoryUsage(0x1000, 8, fill, magic);
243+
expect(cleanResult).toBe(0);
244+
245+
// Magic at start, one word used
246+
const used = new MemTarget(packWords(magic, 0x11111111));
247+
const usedResult = await used.calculateMemoryUsage(0x1000, 8, fill, magic);
248+
expect(usedResult).toBeDefined();
249+
expect((usedResult as number) & 0xFFFFF).toBe(4);
250+
expect(((usedResult as number) >> 20) & 0xFF).toBe(50);
251+
expect(((usedResult as number) >>> 31) & 1).toBe(0);
252+
253+
// Magic overwritten at start => overflow and 100% used
254+
const overflow = new MemTarget(packWords(0xDEADBEEF, fill));
255+
const overflowResult = await overflow.calculateMemoryUsage(0x1000, 8, fill, magic);
256+
expect(overflowResult).toBeDefined();
257+
expect((overflowResult as number) & 0xFFFFF).toBe(8);
258+
expect(((overflowResult as number) >> 20) & 0xFF).toBe(100);
259+
expect(((overflowResult as number) >>> 31) & 1).toBe(1);
235260

236261
// No data path
237262
const noData = new MemTarget(undefined as unknown as Uint8Array);
238-
await expect(noData.calculateMemoryUsage(0, 4, 0, 0)).resolves.toBeUndefined();
263+
await expect(noData.calculateMemoryUsage(0x1000, 4, fill, magic)).resolves.toBeUndefined();
239264

240-
const used = new MemTarget(new Uint8Array([1, 0, 0, 0, 1, 0, 0, 0]));
241-
const usedResult = await used.calculateMemoryUsage(0, 8, 0, 0);
242-
expect((usedResult as number) >>> 0).toBeGreaterThan(0);
243-
expect(((usedResult as number) >>> 31) & 1).toBe(0);
265+
// Fill equals magic: overflow bit must stay clear even if first word mismatches
266+
const fillIsMagic = new MemTarget(packWords(0x12345678, fill));
267+
const fillIsMagicResult = await fillIsMagic.calculateMemoryUsage(0x1000, 8, fill, fill);
268+
expect(((fillIsMagicResult as number) >>> 31) & 1).toBe(0);
269+
270+
// Address/size guard returns 0
271+
await expect(clean.calculateMemoryUsage(0, 8, fill, magic)).resolves.toBe(0);
272+
await expect(clean.calculateMemoryUsage(0x1000, 0, fill, magic)).resolves.toBe(0);
244273
});
245274

246275
it('reads string from pointer and registers', async () => {

0 commit comments

Comments
 (0)