Files
SystemSimulationApp/tests/manual/specialized_replay_experiment.py
ljz 7611f13208 修复循环信号与事件采样并接入 LSTP 接触定位,补充八路验证及复用实验
相较上一版 Jacobian 确定性复用更新,本次补齐事件边界一致性、结果两侧采样及接触事件定位;保留已有物性复用和组件力学公式。

- 统一 UD00 信号求值与下一事件查询的绝对时间边界,修复循环边界浮点舍入导致的阶段错位、重复或漏报,并覆盖零时长、多阶段及长周期场景。
- 引入原生输出语义 v2:保留规则网格真实时间,补充内部时间事件和状态事件的左邻及事件后采样,按保存时间、状态和离散模式重放结果。
- 两条代码生成路径均发出 LSTP 接触描述,默认定位间隙过零及非负力模式的力截断;仅在接受事件时更新防重复记录,增加 contactEvents 诊断计数。
- 补充 MASS/LSTP 独立事件实验、八路全曲线与驱动阶段配对评估,以及 Amesim 不连续点输出对照和力差定位报告;MASS 新增释放机制仍保留为独立实验。
- 保存局部 probe、context 访问与回退、shadow replay、R288 real skip/typed replay 及阀门数值尾部诊断工具和报告;未证明净收益的实验不启用为生产默认优化。
- 更新原生运行说明和元件建模规范,补充信号边界、输出语义、接触事件和实验依赖回归测试。

验证:五组专项回归共 34 项全部通过;37 个待提交 Python 文件语法检查通过;git diff --cached --check 通过。
2026-09-17 23:50:13 +08:00

261 lines
15 KiB
Python

"""R288-only typed replay cost-floor experiment; no production source changes."""
from pathlib import Path
import argparse,hashlib,json,os,re,shutil,subprocess,statistics,struct
import real_skip_experiment as base
ROOT=base.ROOT;HERE=Path(__file__).parent;OUT=ROOT/'test/r288-typed-replay-20260917'
OLD=base.OUT
original_core=base.core
def replace(s,a,b):return base.replace(s,a,b)
def body(s,name,change):return base.shadow.access.change_function(s,name,change)
def function_replace(s,name,new):
a,b,e=base.ex.function_span(s,name);return s[:a]+new+s[e:]
def profile_core():
s=original_core(False)
pro='''
static uint64_t prof_ticks[24],prof_calls[24];
typedef struct {uint64_t start;int category;} Prof;
static void prof_end(Prof *s){prof_ticks[s->category]+=lp_tick()-s->start;prof_calls[s->category]++;}
#define PROF(c) Prof ps __attribute__((cleanup(prof_end)))={lp_tick(),c}
'''
s=s.replace('static Plan plan;',pro+'\nstatic Plan plan;')
for name,index in [('first_match',10),('map_slot',11),('scalar_lookup',12),('state_read_equal',13),('from_live',14)]:s=body(s,name,lambda b,i=index:'PROF('+str(i)+');'+b)
s=s.replace('const Event *e=&plan.events[i];int slot=-1;','const Event *e=&plan.events[i];PROF(e->type);int slot=-1;')
s=body(s,'replay_overlay',lambda b:replace('uint64_t header_start=lp_tick();'+b,'for(int i=0;i<SR_STATES;i++)mapping[i]=-1;','prof_ticks[17]+=lp_tick()-header_start;prof_calls[17]++;\nfor(int i=0;i<SR_STATES;i++)mapping[i]=-1;'))
def split_copy(b):
portions=[
(18,'memset(dst,0,sizeof(*dst));memset(dst->states,0xa5,sizeof(dst->states));'),
(19,'dst->context=*src;memcpy(dst->states,src->states,src->count*sizeof(*src->states));'),
(20,'memcpy(dst->pipes,pipes,sizeof(dst->pipes));'),
(21,'dst->memo=*src->jacobian;memcpy(dst->entries,src->jacobian->entries,src->jacobian->capacity*sizeof(*dst->entries));')]
for tag,text in portions:b=replace(b,text,'{PROF('+str(tag)+');'+text+'}')
start=b.index('dst->memo.entries=');b=b[:start]+'{PROF(22);'+b[start:]+'}'
return b
s=body(s,'from_live',split_copy)
return s
def profile_runtime():
s=(HERE/'real_skip_runtime.c').read_text()
s=s.replace('static int force_every;','static int force_every;\nstatic FILE *plans_file;\nstatic uint64_t timer_ticks,dispatch_ticks;static volatile unsigned dispatch_sink;')
s=body(s,'rr_start',lambda b:b+'\nplans_file=fopen("plans.bin","wb");if(!plans_file)fatal("plans");\nfor(int i=0;i<100000;i++){uint64_t t=lp_tick();timer_ticks+=lp_tick()-t;}\n')
s=body(s,'rr_record_end',lambda b:b+'''
int saved=errno;SREnvironment env;save_environment(&env);
fwrite(&plan,1,sizeof(plan),plans_file);
uint64_t dispatch_start=lp_tick();unsigned checksum=0;
for(int repeat=0;repeat<100;repeat++)for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];switch(e->type){
case QUERY:checksum+=(unsigned)e->slot;break;case SCALAR:checksum+=(unsigned)e->size;break;
case ALLOCATE:checksum++;break;case WRITE_STATE:case WRITE_PIPE:checksum+=(unsigned)e->offset;break;
case READ_STATE:case READ_PIPE:checksum+=(unsigned)e->size;break;case WRITE_OR:case VALID:checksum+=e->mask;break;default:checksum++;
}}
dispatch_ticks+=lp_tick()-dispatch_start;dispatch_sink=checksum;
restore_environment(&env);errno=saved;
''')
s=body(s,'build_patch',lambda b:'PROF(15);'+b)
s=body(s,'commit',lambda b:'PROF(16);'+b)
s=body(s,'rr_finish',lambda b:b+'''
fclose(plans_file);FILE *pf=fopen("attribution.json","wb");
fprintf(pf,"{\\"frequency\\":%llu,\\"emptyTimerTicks\\":%llu,\\"emptyTimerCalls\\":100000,\\"dispatchTicks\\":%llu,\\"dispatchRepeats\\":100,\\"categories\\":[",(unsigned long long)frequency,(unsigned long long)timer_ticks,(unsigned long long)dispatch_ticks);
for(int i=0;i<24;i++)fprintf(pf,"%s{\\"id\\":%d,\\"ticks\\":%llu,\\"calls\\":%llu}",i?",":"",i,(unsigned long long)prof_ticks[i],(unsigned long long)prof_calls[i]);
fputs("]}",pf);fclose(pf);
''')
return s
def minimal_sources(audit):
# The audit build adds the minimal capture hooks to the certified access
# source, allowing one physical baseline execution to feed BOTH schemas.
directory=base.ACCESS if audit else base.BASE
codes={n:(directory/n).read_text(encoding='utf-8') for n in ['properties.c','pipe.c']}
s=codes['properties.c']
if audit:
# Native access hooks feed the compact recorder through ax_* wrappers.
return codes
s=body(s,'property_pt',lambda b:replace(replace(b,'return s;','{fp_query(cache,m,p,T,s);return s;}'),'return property_new(cache,m,p,T,scratch);','fp_query(cache,m,p,T,NULL);return property_new(cache,m,p,T,scratch);'))
s=body(s,'property_new',lambda b:replace(b,'return s;','fp_allocate(cache,s,valid);return s;'))
s=body(s,'native_temperature_ph_context',lambda b:'fp_unknown();'+b)
s=body(s,'native_jacobian_scalar_get',lambda b:replace('fp_scalar_key(kind,medium_kind,inputs,count);'+b,'return 1;','fp_scalar_value(kind,medium_kind,inputs,count,*value);return 1;'))
s=body(s,'native_jacobian_scalar_put',lambda b:'fp_scalar_value(kind,medium_kind,inputs,count,value);'+b)
codes['properties.c']=s
return codes
def fast_core(audit):
if audit:
s=original_core(True)
s=s.replace('static SRContext overlay;','')
s=body(s,'ax_query',lambda b:b+'\nif(phase==RECORD && strcmp(kind,"PT"))fp_unknown();')
s=body(s,'ax_match',lambda b:b+'''
if(phase==RECORD && !strcmp(kind,"PT")){
Event *q=&plan.events[plan.query_index];double key[9];memcpy(key,q->data,sizeof(key));
NativeMedium m={q->offset,key[2],key[3],key[4],key[5],key[6],key[7],key[8]};fp_query(bound,&m,key[0],key[1],s);
}
''')
s=body(s,'ax_new',lambda b:'if(phase==RECORD)fp_allocate(cache,s,valid);'+b)
s=body(s,'ax_scalar',lambda b:b+'''
if(phase==RECORD){
if(!strcmp(action,"get")){fp_scalar_key(kind,medium,keys,n);if(hit && value)fp_scalar_value(kind,medium,keys,n,*value);}
else if(!strcmp(action,"put_attempt") && value)fp_scalar_value(kind,medium,keys,n,*value);
}
''')
return s
s=(HERE/'context_shadow_replay.c').read_text()
s=s[:s.index('typedef struct {\n int type,slot')]
s=s.replace('static void save_environment','static __attribute__((unused)) void save_environment').replace('static void restore_environment','static __attribute__((unused)) void restore_environment')
s+='''
static unsigned long long jac;
static NativePropertyCache *bound;
static NativePipeCache *bound_pipes;
static void fatal(const char *s){fprintf(stderr,"typed replay fatal: %s\\n",s);abort();}
'''
return s
def fast_runtime(audit,direct=False):
s=(HERE/'real_skip_runtime.c').read_text()
s=s.replace('static int force_every;','static __attribute__((unused)) int force_every;\n#include "fast_replay.inc"')
start=s.index('/* The translated write set');end=s.index('double rr_execute(',start)
s=s[:start]+s[end:]
execute=(HERE/'typed_replay_runtime.inc').read_text()
s=function_replace(s,'rr_execute',execute)
if audit:s=s.replace('void rr_start(void){','#include "typed_contract.inc"\n#include "typed_oracle.inc"\nvoid rr_start(void){')
s=function_replace(s,'rr_jacobian','void rr_jacobian(void){jac++;fast.ready=0;\n#if RR_AUDIT\nplan.ready=0;\n#endif\n}')
s=function_replace(s,'rr_record_begin','''void rr_record_begin(NativePropertyCache *p,NativePipeCache *pipes,const double *x){
baseline_start=lp_tick();bound=p;bound_pipes=pipes;fp_begin(p,pipes,x);
#if RR_AUDIT
ax_bind(p,pipes);ax_begin(jac,-1,16,0,x,4);
#endif
}''')
s=function_replace(s,'rr_record_end','''void rr_record_end(double output){
fp_end(output);
#if RR_AUDIT
ax_end(&output,1);fp_contract_check();
#endif
baseline_ticks+=lp_tick()-baseline_start;
}''')
if direct:
s=s.replace('static __attribute__((unused)) int force_every;','static FILE *archived_plans;\nstatic __attribute__((unused)) int force_every;')
s=body(s,'rr_start',lambda b:b+'\narchived_plans=fopen(getenv("RR_PLAN_ORACLE"),"rb");if(!archived_plans)fatal("archived plan oracle");\n')
s=s.replace('ax_end(&output,1);fp_contract_check();','''
int saved_errno=errno;SREnvironment saved_env;save_environment(&saved_env);
if(fread(&plan,1,sizeof(plan),archived_plans)!=sizeof(plan))fatal("archived plan EOF");
uintptr_t previous_owner=plan.memo_binding;
for(int i=0;i<plan.n;i++){
Event *e=&plan.events[i];
if((e->type==READ_STATE || e->type==WRITE_STATE) && e->offset==(int)offsetof(NativePropertyState,jacobian)){
uintptr_t pointer;memcpy(&pointer,e->data,sizeof(pointer));if(pointer!=previous_owner)fatal("archived memo binding");memcpy(e->data,&fast.owner,sizeof(fast.owner));
}}
plan.memo_binding=(uintptr_t)fast.owner;phase=OFF;
restore_environment(&saved_env);errno=saved_errno;fp_contract_check();
''')
s=body(s,'rr_finish',lambda b:b+'\nif(fgetc(archived_plans)!=EOF){fatal("remaining archived plans");}\nfclose(archived_plans);\n')
s=s.replace('(unsigned long long)sizeof(plan),(unsigned long long)sizeof(overlay),(unsigned long long)sizeof(patches)', '(unsigned long long)sizeof(fast),(unsigned long long)sizeof(Pending),(unsigned long long)sizeof(Pending)')
s=body(s,'rr_finish',lambda b:b+'''
FILE *fextra=fopen("typed-summary.json","wb");
fprintf(fextra,"{\\"metadataBytes\\":%llu,\\"captureScratchBytes\\":%llu,\\"pendingBytes\\":%llu,\\"contractChecks\\":%llu,\\"oracleChecks\\":%llu,\\"negativeChecks\\":%llu}",(unsigned long long)sizeof(fast),(unsigned long long)sizeof(capture),(unsigned long long)sizeof(Pending),
#if RR_AUDIT
contract_checks,oracle_checks,negative_checks
#else
0ULL,0ULL,0ULL
#endif
);fclose(fextra);
''')
return s
def contract_source():
data=(OUT/'P/perf-skip-run/plans.bin').read_bytes();schemas={}
for off in range(0,len(data),90224):
count=struct.unpack_from('<Q',data,off+90112+56)[0];n=struct.unpack_from('<i',data,off+90112+68)[0];path=struct.unpack_from('<i',data,off+90112+88)[0]
entries=[]
for i in range(n):
t,s,o,z,a,m=struct.unpack_from('<iiiiiI',data,off+i*176)
if t in (1,2,3,7,8):s-=count
entries.append((t,s,o,z,a,m))
if path in schemas:assert schemas[path]==entries
else:schemas[path]=entries
assert sorted(schemas)==[0,1] and len(schemas[0])==12 and len(schemas[1])==132
prefix='typedef struct {int type,slot,offset,size,aux;unsigned mask;} Contract;\n'
for path,events in schemas.items():prefix+='static const Contract schema'+str(path)+'[]={'+','.join('{'+','.join(map(str,e))+'}' for e in events)+'};\n'
return prefix+(HERE/'typed_contract_check.inc').read_text()
def prepare(mode,audit=False):
certified=json.loads((OLD/'perf-skip/build.json').read_text())['originalHashes']
for name,expected in certified.items():assert hashlib.sha256((base.BASE/name).read_text(encoding='utf-8').encode()).hexdigest()==expected,name
root=OUT/mode;root.mkdir(parents=True,exist_ok=True)
staging=root/'source';staging.mkdir(exist_ok=True)
for name in ['context_shadow_replay.c','context_shadow_replay.h','real_skip.h']:shutil.copyfile(HERE/name,staging/name)
runtime=fast_runtime(audit,mode=='D') if mode in 'CD' else profile_runtime() if mode=='P' else (HERE/'real_skip_runtime.c').read_text()
(staging/'real_skip_runtime.c').write_text(runtime,encoding='utf-8')
if mode in 'CD':
header=(staging/'real_skip.h').read_text();header+='\n'+(HERE/'typed_replay_hooks.h').read_text();(staging/'real_skip.h').write_text(header)
work=root/('audit-skip' if audit else 'perf-skip');work.mkdir(exist_ok=True)
shutil.copyfile(HERE/'typed_replay_core.inc',work/'fast_replay.inc')
if audit:
(work/'typed_contract.inc').write_text(contract_source(),encoding='utf-8')
shutil.copyfile(HERE/'typed_oracle.inc',work/'typed_oracle.inc')
access=staging/'capture';access.mkdir(exist_ok=True)
for n in ['context_access_diag.h','context_fallback_diag.h']:shutil.copyfile(base.ACCESS/n,access/n)
for n,code in minimal_sources(audit and mode!='D').items():(access/n).write_text(code,encoding='utf-8')
base.OUT=root;base.HERE=staging
if mode in 'CD':base.ACCESS=access
base.core=lambda enabled:fast_core(enabled) if mode in 'CD' else profile_core() if mode=='P' else original_core(enabled)
if not audit:
gate=root/'audit-skip-run';gate.mkdir(exist_ok=True)
if mode!='C':shutil.copyfile(OLD/'audit-skip-run/validation.json',gate/'validation.json')
else:assert json.loads((gate/'validation.json').read_text())['exact']
# original_core reads base.HERE; the source copies are byte-identical.
base.prepare(audit,mode!='A')
if mode in 'CD':
manifest=root/('audit-skip' if audit else 'perf-skip')/'build.json';info=json.loads(manifest.read_text())
info['guardMatchesShadow']=False;info['wholeContextGuardUnchanged']=True
info['typedGuardValidation']='See C/D full-trajectory contracts, generic oracle and forced-reject audits.'
base.write(manifest,info)
if mode=='C' and not audit:
cc,_,_=base.ex.builder.toolchain();nm=Path(cc).with_name('nm.exe' if os.name=='nt' else 'nm')
symbols=subprocess.check_output([str(nm),'--defined-only',str(root/'perf-skip/model.exe')],text=True)
assert not re.search(r'(?m)\b(?:plan|overlay|before_reject|after_reject|oracle_generic|oracle_typed|replay_overlay|ax_access)$',symbols)
base.write(root/'perf-skip/stripped-audit-proof.json',dict(noGenericInterpreter=True,noFullOverlay=True,noAuditOracle=True))
def run(mode,audit=False,label=None,force=0):
base.OUT=OUT/mode
if mode=='D':os.environ['RR_PLAN_ORACLE']=str(OUT/'P/perf-skip-run/plans.bin')
if audit:
expected=base.OUT/'audit-control-run';expected.mkdir(exist_ok=True)
target=expected/'audit.bin'
if not target.exists():os.link(OLD/'audit-control-run/audit.bin',target)
result=base.run(audit,mode!='A',label,force)
result['mode']=mode
if mode in 'CD':
extra=json.loads((base.OUT/result['label']/'typed-summary.json').read_text())
assert extra['metadataBytes']==240 and extra['captureScratchBytes']==96 and extra['pendingBytes']==456
assert extra['contractChecks']==extra['oracleChecks']==(896 if audit else 0)
assert extra['negativeChecks']==(20 if audit and not force else 0)
result['typed']=extra
base.write(base.OUT/result['label']/'validation.json',result)
return result
def benchmark(rounds):
for mode in 'CD':
for label in ['audit-skip-run','forced-reject']:assert json.loads((OUT/mode/label/'validation.json').read_text())['exact']
rows=[]
for mode in 'ABC':run(mode,label='warm')
for i in range(rounds):
order='ABC'[i%3:]+'ABC'[:i%3]
for mode in order:rows.append(run(mode,label=f'round-{i+1}'))
base.write(OUT/'performance.json',rows)
if __name__=='__main__':
p=argparse.ArgumentParser();p.add_argument('action',choices=['prepare','run','benchmark']);p.add_argument('--mode',choices=list('ABCPD'),default='C');p.add_argument('--audit',action='store_true');p.add_argument('--label');p.add_argument('--force',type=int,default=0);p.add_argument('--rounds',type=int,default=9);a=p.parse_args()
if a.action=='prepare':prepare(a.mode,a.audit)
elif a.action=='run':run(a.mode,a.audit,a.label,a.force)
else:benchmark(a.rounds)