@@ -29,6 +29,7 @@ import { MemoryHost } from '../../../../data-host/memory-host';
2929import { ScvdFormatSpecifier } from '../../../../model/scvd-format-specifier' ;
3030import { RegisterHost } from '../../../../data-host/register-host' ;
3131import { ScvdDebugTarget } from '../../../../scvd-debug-target' ;
32+ import { TargetReadCache } from '../../../../target-read-cache' ;
3233
3334class 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 } ) ;
0 commit comments