Files
SystemSimulationApp/tests/manual/context_shadow_replay.c
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

579 lines
40 KiB
C

/* Diagnostic only. The semantic interpreter never calls a physics kernel. */
#include "context_access_diag.h"
#include "context_shadow_replay.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <errno.h>
#include <fenv.h>
#if defined(__SSE__)
#include <xmmintrin.h>
#endif
typedef struct {fenv_t standard;int rounding;unsigned sse;} SREnvironment;
static unsigned sse_control(void){
#if defined(__SSE__)
return _mm_getcsr();
#else
return 0;
#endif
}
static void save_environment(SREnvironment *env){
fegetenv(&env->standard);env->rounding=fegetround();env->sse=0;
#if defined(__SSE__)
env->sse=_mm_getcsr();
#endif
}
static void restore_environment(const SREnvironment *env){
fesetenv(&env->standard);fesetround(env->rounding);
#if defined(__SSE__)
/* MinGW's fesetenv does not restore the complete SSE control register. */
_mm_setcsr(env->sse);
#endif
}
enum {OFF,RECORD,REFERENCE,CANDIDATE,TAIL};
enum {READ_STATE=1,WRITE_STATE,WRITE_OR,READ_PIPE,WRITE_PIPE,QUERY,ALLOCATE,VALID,SCALAR};
enum {OK,NO_RECORD,INPUTS,FIRST_MATCH,QUERY_PATH,CONSUMED,VALID_BITS,CAPACITY,OBSERVER,
EXISTING_UPDATE,PIPE_BRANCH,UNKNOWN_EFFECT,NONFINITE,MEMO_BINDING,MEMO_MISS,MEMO_VALUE,OVERFLOW,NREASONS};
static const char *reasons[]={"accepted","no_current_record","inputs","first_match_relation","query_hit_miss_path",
"consumed_field","valid_test","capacity_scratch","observer_nonnull","existing_entry_update","pipe_hit_branch",
"unknown_effect_or_schema","nonfinite_or_uncovered_branch","memo_lifetime_binding","memo_miss","memo_value","metadata_overflow"};
typedef struct {
int type,slot,offset,size,aux;
unsigned mask;
unsigned char data[sizeof(NativePropertyState)];
double result;
} Event;
typedef struct {
Event events[SR_EVENTS];
unsigned long long jac;
uintptr_t memo_binding;
double inputs[4],output;
size_t entry_count;
int position,n,ready,error,query_open,query_index,path;
int required_flags,baseline_errno,rounding;
unsigned required_sse_flags,sse_mode;
} Plan;
typedef struct {
unsigned long long total,accepted,rejected,mismatches,count_different,relocated,relocated_slots,appends;
unsigned long long paths[4],pt_hit,pt_miss,ph_hit,ph_miss,rejects[NREASONS];
unsigned long long live_contamination,rollbacks,tail_equal,live_tail_equal,memo_immutable,negative_passed;
unsigned long long untouched_equal,metadata_immutable;
size_t metadata_min,metadata_max,event_max;
} Statistics;
static Plan plan;
static Statistics stats;
static int phase,position,group,configured,pending_live,pending_status;
static unsigned long long jac;
static double sim_time,op_inputs[4];
static NativePropertyCache *bound;
static NativePipeCache *bound_pipes;
static SRContext entry,reference,candidate,overlay,before_transaction,tail_ref,tail_cand;
static SRFrame frame_ref,frame_cand;
static FILE *trials,*negative;
static unsigned long long forbidden_native_calls;
static void fatal(const char *message){fprintf(stderr,"shadow fatal: %s\n",message);abort();}
static void initialize(void){
if(configured)return;
const char *s=getenv("CONTEXT_SHADOW_POSITION");configured=s?atoi(s):16;
if(configured!=16 && configured!=52)fatal("invalid stage");
trials=fopen("shadow-trials.jsonl","wb");negative=fopen("shadow-negative.jsonl","wb");
if(!trials || !negative)fatal("open logs");
stats.metadata_min=(size_t)-1;
}
void sr_native_enter(const char *name){
if(phase==CANDIDATE){forbidden_native_calls++;fprintf(stderr,"Candidate called %s\n",name);fatal("physical call during replay");}
}
static int locate(const void *ptr,size_t width,int *domain,int *slot,int *offset){
uintptr_t p=(uintptr_t)ptr;
const void *bases[]={bound,bound?bound->states:NULL,bound_pipes};
size_t sizes[]={sizeof(NativePropertyCache),bound?bound->capacity*sizeof(NativePropertyState):0,SR_PIPES*sizeof(NativePipeCache)};
size_t units[]={sizeof(NativePropertyCache),sizeof(NativePropertyState),sizeof(NativePipeCache)};
for(int i=0;i<3;i++)if(bases[i] && p>=(uintptr_t)bases[i] && p-(uintptr_t)bases[i]+width<=sizes[i]){
size_t delta=p-(uintptr_t)bases[i];*domain=i;*slot=(int)(delta/units[i]);*offset=(int)(delta%units[i]);return 1;
}
return 0;
}
static Event *event(int type){
if(plan.n>=SR_EVENTS){plan.error=OVERFLOW;return NULL;}
Event *e=&plan.events[plan.n++];memset(e,0,sizeof(*e));e->type=type;return e;
}
static int state_pointer(int offset){
return offset==(int)offsetof(NativePropertyState,jacobian) || offset==(int)offsetof(NativePropertyState,temperatures);
}
static int floating_field(int domain,int offset,int size){
if(size!=8)return 0;
if(domain==1 && state_pointer(offset))return 0;
return offset>=(int)offsetof(NativeMedium,R) && offset<(domain==1?(int)offsetof(NativePropertyState,valid):(int)offsetof(NativePipeCache,kind));
}
void ax_access(const char *action,const void *ptr,size_t width,const char *field,const char *fn){
(void)field;
if(phase!=RECORD)return;
int domain,slot,offset;
if(!locate(ptr,width,&domain,&slot,&offset)){
/* The only recorded store to a stack local is scalar_get's output. */
if(!strcmp(action,"write") && strcmp(fn,"native_jacobian_scalar_get"))plan.error=UNKNOWN_EFFECT;
return;
}
if(domain==0){
if(!strcmp(action,"write") && offset!=(int)offsetof(NativePropertyCache,count))plan.error=UNKNOWN_EFFECT;
return; /* count/capacity and pointer bindings are semantic guards. */
}
if(plan.query_open && !strcmp(action,"read"))return; /* Re-run query, not baseline's scan slots. */
if(width>sizeof(NativePropertyState)){plan.error=UNKNOWN_EFFECT;return;}
int write=!strcmp(action,"write");
Event *e=event(domain==1?(write?WRITE_STATE:READ_STATE):(write?WRITE_PIPE:READ_PIPE));
if(!e)return;
e->slot=slot;e->offset=offset;e->size=(int)width;memcpy(e->data,ptr,width);
if(floating_field(domain,offset,(int)width)){double x;memcpy(&x,ptr,8);if(!isfinite(x))plan.error=NONFINITE;}
if(domain==1 && write && (size_t)slot<plan.entry_count)plan.error=EXISTING_UPDATE;
if(domain==2 && slot!=(configured==16?0:28))plan.error=UNKNOWN_EFFECT;
}
void sr_or(const void *ptr,unsigned mask,const char *field,const char *fn){
int previous=plan.n;ax_access("write",ptr,sizeof(unsigned),field,fn);
if(phase==RECORD && plan.n>previous){Event *e=&plan.events[plan.n-1];e->type=WRITE_OR;e->mask=mask;}
}
void ax_bind(NativePropertyCache *p,NativePipeCache *pipes){bound=p;bound_pipes=pipes;}
void ax_begin(unsigned long long j,int color,int pos,double t,const double *inputs,int n){
initialize();phase=OFF;jac=j;group=color;position=pos;sim_time=t;
if(pos!=configured)return;
if(n!=4 || !inputs)fatal("operation signature");
memcpy(op_inputs,inputs,sizeof(op_inputs));
if(color<0){
memset(&plan,0,sizeof(plan));plan.jac=j;plan.position=pos;plan.entry_count=bound->count;
plan.memo_binding=(uintptr_t)bound->jacobian;memcpy(plan.inputs,inputs,sizeof(plan.inputs));
plan.rounding=fegetround();
plan.sse_mode=sse_control()&~63u;
if(bound->temperatures)plan.error=OBSERVER;
phase=RECORD;
}
}
void ax_query(const char *kind,const NativeMedium *m,double p,double second){
if(phase!=RECORD)return;
if(plan.query_open){plan.error=UNKNOWN_EFFECT;return;}
Event *e=event(QUERY);if(!e)return;
e->aux=!strcmp(kind,"PH");e->offset=m->real_helium;e->slot=-2;
double key[]={p,second,m->R,m->cp,m->Tref,m->slope,m->mu,m->muT,m->S};
memcpy(e->data,key,sizeof(key));for(int i=0;i<9;i++)if(!isfinite(key[i]))plan.error=NONFINITE;
plan.query_open=1;plan.query_index=plan.n-1;
}
void ax_match(const char *kind,NativePropertyState *s){
if(phase!=RECORD)return;
if(!plan.query_open){plan.error=UNKNOWN_EFFECT;return;}
Event *e=&plan.events[plan.query_index];
if(e->aux!=(!strcmp(kind,"PH")))plan.error=UNKNOWN_EFFECT;
e->slot=s?(int)(s-bound->states):-1;plan.query_open=0;
}
void ax_new(NativePropertyCache *cache,NativePropertyState *s,int valid){
if(phase!=RECORD)return;
int domain,slot,offset;
if(!valid || !cache || !locate(s,sizeof(*s),&domain,&slot,&offset) || domain!=1 || offset || (size_t)slot+1!=cache->count){plan.error=CAPACITY;return;}
Event *e=event(ALLOCATE);if(e)e->slot=slot;
}
unsigned ax_test(NativePropertyState *s,unsigned mask,const char *fn){
(void)fn;unsigned result=s->valid&mask;
if(phase==RECORD && !plan.query_open){
int domain,slot,offset;
if(!locate(s,sizeof(*s),&domain,&slot,&offset) || domain!=1){plan.error=CAPACITY;return result;}
Event *e=event(VALID);if(e){e->slot=slot;e->mask=mask;memcpy(e->data,&result,sizeof(result));}
}
return result;
}
void ax_scalar(const char *action,NativeJacobianScalars *memo,int kind,int medium,const double *keys,size_t n,int hit,const double *value){
if(phase!=RECORD)return;
if(memo!=bound->jacobian || n>NATIVE_JACOBIAN_SCALAR_KEYS){plan.error=UNKNOWN_EFFECT;return;}
if(!strcmp(action,"get")){
Event *e=event(SCALAR);if(!e)return;e->slot=kind;e->offset=medium;e->size=(int)n;memcpy(e->data,keys,n*8);
if(hit && value){e->result=*value;e->aux=1;}
}else if(!strcmp(action,"put_attempt")){
int found=0;
for(int i=plan.n-1;i>=0;i--){Event *e=&plan.events[i];if(e->type==SCALAR && e->slot==kind && e->offset==medium && e->size==(int)n && !memcmp(e->data,keys,n*8)){
if(!value || !isfinite(*value)){plan.error=NONFINITE;break;}e->result=*value;e->aux=1;found=1;break;
}}
if(!found)plan.error=UNKNOWN_EFFECT;
}else plan.error=UNKNOWN_EFFECT;
}
static int path_of_plan(void){
int pattern[4],n=0;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==QUERY){Event *e=&plan.events[i];if(n==4)return -1;pattern[n++]=e->aux*2+(e->slot>=0);}
if(n==0)return 0;
if(n==2 && pattern[0]==0 && pattern[1]==0)return 1;
if(n==4 && pattern[0]==2 && pattern[1]==0 && pattern[2]==1 && pattern[3]==0)return 2;
if(n==3 && pattern[0]==3 && pattern[1]==1 && pattern[2]==0)return 3;
return -1;
}
void ax_end(const double *outputs,int n){
if(phase!=RECORD)return;
if(n!=1 || !isfinite(outputs[0]))plan.error=NONFINITE;
plan.output=outputs[0];plan.path=path_of_plan();
plan.required_flags=fetestexcept(FE_ALL_EXCEPT);plan.baseline_errno=errno;
plan.required_sse_flags=sse_control()&63u;
if(plan.path<0 || plan.query_open)plan.error=NONFINITE;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==SCALAR && !plan.events[i].aux)plan.error=UNKNOWN_EFFECT;
plan.ready=1;phase=OFF;
}
void ax_result(int result){(void)result;}
static void copy_context(SRContext *dst,const SRContext *src){
memcpy(dst,src,sizeof(*dst));dst->context.states=dst->states;dst->memo.entries=dst->entries;
if(src->context.jacobian==&src->memo)dst->context.jacobian=&dst->memo;
if(src->context.temperatures==&src->observer)dst->context.temperatures=&dst->observer;
for(size_t i=0;i<dst->context.count;i++){
if(src->states[i].jacobian==&src->memo)dst->states[i].jacobian=&dst->memo;
if(src->states[i].temperatures==&src->observer)dst->states[i].temperatures=&dst->observer;
}
}
static void from_live(SRContext *dst,NativePropertyCache *src,NativePipeCache *pipes){
if(src->count>SR_STATES || src->capacity>SR_STATES || !src->jacobian || src->jacobian->capacity>MODEL_JACOBIAN_SCALAR_COUNT)fatal("context bounds");
memset(dst,0,sizeof(*dst));memset(dst->states,0xa5,sizeof(dst->states));
dst->context=*src;memcpy(dst->states,src->states,src->count*sizeof(*src->states));memcpy(dst->pipes,pipes,sizeof(dst->pipes));
dst->memo=*src->jacobian;memcpy(dst->entries,src->jacobian->entries,src->jacobian->capacity*sizeof(*dst->entries));
dst->memo.entries=dst->entries;dst->context.states=dst->states;dst->context.jacobian=&dst->memo;
if(src->temperatures){dst->observer=*src->temperatures;dst->context.temperatures=&dst->observer;}
for(size_t i=0;i<src->count;i++){
if(dst->states[i].jacobian==src->jacobian)dst->states[i].jacobian=&dst->memo;
if(src->temperatures && dst->states[i].temperatures==src->temperatures)dst->states[i].temperatures=&dst->observer;
}
}
static int same_medium_key(const NativeMedium *m,int kind,const double *key){
return m->real_helium==kind && m->R==key[2] && m->cp==key[3] && m->Tref==key[4] && m->slope==key[5] && m->mu==key[6] && m->muT==key[7] && m->S==key[8];
}
static int first_match(SRContext *ctx,const Event *e){
double key[9];memcpy(key,e->data,sizeof(key));
for(size_t i=0;i<ctx->context.count;i++){
NativePropertyState *s=&ctx->states[i];unsigned mask=e->aux?NATIVE_PROPERTY_H:NATIVE_PROPERTY_PT;
if((s->valid&mask) && s->p==key[0] && (e->aux?s->h:s->T)==key[1] && same_medium_key(&s->medium,e->offset,key))return (int)i;
}
return -1;
}
static int map_slot(int *mapping,int logical,int actual){
if(logical<0 || logical>=SR_STATES || actual<0 || actual>=SR_STATES)return 0;
if(mapping[logical]>=0)return mapping[logical]==actual;
for(int i=0;i<SR_STATES;i++)if(i!=logical && mapping[i]==actual)return 0;
mapping[logical]=actual;return 1;
}
/* Same exact bit-key and bounded linear-probe semantics as the read-only memo.
* Only counters change. No physical fallback and no memo insertion is allowed. */
static int scalar_lookup(SRContext *ctx,const Event *e){
NativeJacobianScalars *memo=&ctx->memo;size_t capacity=memo->capacity;
if(!capacity || capacity>MODEL_JACOBIAN_SCALAR_COUNT || (capacity&(capacity-1)) || memo->recording)return MEMO_BINDING;
uint64_t hash=UINT64_C(14695981039346656037)^(unsigned)e->slot;
hash=(hash^(unsigned)e->offset)*UINT64_C(1099511628211);
for(int i=0;i<e->size;i++){uint64_t bits;memcpy(&bits,e->data+i*8,8);hash=(hash^bits)*UINT64_C(1099511628211);hash^=hash>>32;}
if(!hash)hash=1;
size_t limit=capacity<32?capacity:32;
for(size_t i=0;i<limit;i++){
NativeJacobianScalarEntry *v=&ctx->entries[(hash+i)&(capacity-1)];
if(!v->hash)return MEMO_MISS;
if(v->hash==hash && v->kind==e->slot && v->medium_kind==e->offset && v->input_count==(size_t)e->size && !memcmp(v->inputs,e->data,e->size*8)){
if(!isfinite(v->value) || memcmp(&v->value,&e->result,8))return MEMO_VALUE;
memo->reuses[e->slot]++;return OK;
}
}
return MEMO_MISS;
}
static int state_read_equal(SRContext *ctx,int slot,const Event *e){
NativePropertyState *s=&ctx->states[slot];
if(e->offset==(int)offsetof(NativePropertyState,jacobian))return s->jacobian==&ctx->memo;
if(e->offset==(int)offsetof(NativePropertyState,temperatures))return s->temperatures==NULL;
return !memcmp((unsigned char*)s+e->offset,e->data,e->size);
}
static int replay_overlay(SRContext *ctx,const double *inputs,int *mapping,int *appends){
if(!plan.ready || plan.jac!=jac || plan.position!=position)return NO_RECORD;
if(plan.error)return plan.error;
if(fegetround()!=plan.rounding || (fetestexcept(FE_ALL_EXCEPT)&plan.required_flags)!=plan.required_flags ||
(plan.baseline_errno && errno!=plan.baseline_errno) || (sse_control()&~63u)!=plan.sse_mode ||
(sse_control()&plan.required_sse_flags)!=plan.required_sse_flags)return UNKNOWN_EFFECT;
if(memcmp(inputs,plan.inputs,sizeof(plan.inputs)))return INPUTS;
for(int i=0;i<4;i++)if(!isfinite(inputs[i]))return NONFINITE;
if(ctx->context.temperatures)return OBSERVER;
if(ctx->context.capacity>SR_STATES || ctx->context.count>ctx->context.capacity)return CAPACITY;
if(ctx->context.jacobian!=&ctx->memo || ctx->memo.entries!=ctx->entries || ctx->memo.recording || (uintptr_t)bound->jacobian!=plan.memo_binding)return MEMO_BINDING;
for(size_t i=0;i<ctx->context.count;i++)if(ctx->states[i].temperatures || ctx->states[i].jacobian!=&ctx->memo)return MEMO_BINDING;
if(ctx->pipes[position==16?0:28].valid)return PIPE_BRANCH;
for(int i=0;i<SR_STATES;i++)mapping[i]=-1;
*appends=0;int pending_miss=0;
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];int slot=-1;
if(e->type<READ_STATE || e->type>SCALAR)return UNKNOWN_EFFECT;
if(e->type==QUERY){
int actual=first_match(ctx,e);
if((e->slot<0)!=(actual<0))return QUERY_PATH;
if(e->slot>=0 && !map_slot(mapping,e->slot,actual))return FIRST_MATCH;
pending_miss=(actual<0 && !e->aux);continue;
}
if(e->type==ALLOCATE){
if(!pending_miss)return UNKNOWN_EFFECT;
if(ctx->context.count>=ctx->context.capacity)return CAPACITY;
if(!map_slot(mapping,e->slot,(int)ctx->context.count))return FIRST_MATCH;
ctx->context.count++;(*appends)++;pending_miss=0;continue;
}
if(e->type==SCALAR){int reason=scalar_lookup(ctx,e);if(reason)return reason;continue;}
if(e->type==READ_PIPE || e->type==WRITE_PIPE){
if(e->slot!=(position==16?0:28) || e->offset<0 || e->offset+e->size>(int)sizeof(NativePipeCache))return UNKNOWN_EFFECT;
unsigned char *ptr=(unsigned char*)&ctx->pipes[e->slot]+e->offset;
if(e->type==READ_PIPE){if(memcmp(ptr,e->data,e->size))return PIPE_BRANCH;}
else memcpy(ptr,e->data,e->size);
continue;
}
if(e->slot<0 || e->slot>=SR_STATES || (slot=mapping[e->slot])<0 || (size_t)slot>=ctx->context.count)return FIRST_MATCH;
NativePropertyState *s=&ctx->states[slot];
if(e->type==VALID){unsigned expected;memcpy(&expected,e->data,sizeof(expected));if((s->valid&e->mask)!=expected)return VALID_BITS;continue;}
if(e->offset<0 || e->size<0 || e->offset+e->size>(int)sizeof(*s))return UNKNOWN_EFFECT;
if(e->type==READ_STATE){if(!state_read_equal(ctx,slot,e))return CONSUMED;continue;}
if(e->type!=WRITE_STATE && e->type!=WRITE_OR)return UNKNOWN_EFFECT;
if((size_t)e->slot<plan.entry_count || (size_t)slot<entry.context.count)return EXISTING_UPDATE;
if(e->type==WRITE_OR){if(e->offset!=(int)offsetof(NativePropertyState,valid))return UNKNOWN_EFFECT;s->valid|=e->mask;}
else if(e->offset==(int)offsetof(NativePropertyState,jacobian))s->jacobian=&ctx->memo;
else if(e->offset==(int)offsetof(NativePropertyState,temperatures))s->temperatures=NULL;
else memcpy((unsigned char*)s+e->offset,e->data,e->size);
}
if(pending_miss || !isfinite(plan.output))return NONFINITE;
return OK;
}
static void commit_patch(SRContext *dst,double *output,const int *mapping){
/* Apply only validated stores, in recorded order. Everything not addressed
* by a store stays in the original Candidate allocation, even at commit. */
for(int i=0;i<plan.n;i++){
const Event *e=&plan.events[i];
if(e->type==ALLOCATE)dst->context.count++;
else if(e->type==SCALAR)dst->memo.reuses[e->slot]++;
else if(e->type==WRITE_PIPE)memcpy((unsigned char*)&dst->pipes[e->slot]+e->offset,e->data,e->size);
else if(e->type==WRITE_STATE || e->type==WRITE_OR){
NativePropertyState *s=&dst->states[mapping[e->slot]];
if(e->type==WRITE_OR)s->valid|=e->mask;
else if(e->offset==(int)offsetof(NativePropertyState,jacobian))s->jacobian=&dst->memo;
else if(e->offset==(int)offsetof(NativePropertyState,temperatures))s->temperatures=NULL;
else memcpy((unsigned char*)s+e->offset,e->data,e->size);
}
}
*output=plan.output;
}
static int transaction(SRContext *dst,const double *inputs,double *output,int *mapping,int *appends){
copy_context(&overlay,dst);phase=CANDIDATE;
int reason=replay_overlay(&overlay,inputs,mapping,appends);
/* No stores to dst or output occur before this commit point. */
if(!reason)commit_patch(dst,output,mapping);
phase=OFF;return reason;
}
typedef struct {const char *name;size_t offset,size;} Field;
#define FIELD(T,n) {#n,offsetof(T,n),sizeof(((T*)0)->n)}
#define MEDIUM(T,n) {"medium." #n,offsetof(T,medium)+offsetof(NativeMedium,n),sizeof(((NativeMedium*)0)->n)}
#define MEDIUM_FIELDS(T) MEDIUM(T,real_helium),MEDIUM(T,R),MEDIUM(T,cp),MEDIUM(T,Tref),MEDIUM(T,slope),MEDIUM(T,mu),MEDIUM(T,muT),MEDIUM(T,S)
static const Field state_fields[]={MEDIUM_FIELDS(NativePropertyState),FIELD(NativePropertyState,p),FIELD(NativePropertyState,T),FIELD(NativePropertyState,h),FIELD(NativePropertyState,rho),FIELD(NativePropertyState,mu),FIELD(NativePropertyState,isentropic_factor),FIELD(NativePropertyState,isentropic_exponent),FIELD(NativePropertyState,valid)};
static const Field pipe_fields[]={MEDIUM_FIELDS(NativePipeCache),FIELD(NativePipeCache,p1),FIELD(NativePipeCache,p2),FIELD(NativePipeCache,T),FIELD(NativePipeCache,diameter),FIELD(NativePipeCache,length),FIELD(NativePipeCache,roughness),FIELD(NativePipeCache,flow),FIELD(NativePipeCache,kind),FIELD(NativePipeCache,valid)};
static char difference[160];
static int differ(const char *domain,int slot,const char *field){snprintf(difference,sizeof(difference),"%s[%d].%s",domain,slot,field);return 1;}
static int compare_frame(const SRFrame *a,const SRFrame *b){
const double *left[]={a->dy,a->w,a->p,a->h,a->q,a->fb,(const double*)a->g};
const double *right[]={b->dy,b->w,b->p,b->h,b->q,b->fb,(const double*)b->g};
const char *names[]={"dy","w","p","h","q","fb","gas_scalar"};
const int counts[]={NSTATES,NOUTPUTS,48,232,232,152,56*5};
for(int array=0;array<7;array++)for(int i=0;i<counts[array];i++)if(memcmp(&left[array][i],&right[array][i],8))return differ(names[array],i,"value");
return 0;
}
static int compare_contexts(const SRContext *a,const SRContext *b){
if(a->context.count!=b->context.count)return differ("property",-1,"count");
if(a->context.capacity!=b->context.capacity)return differ("property",-1,"capacity");
if(a->context.states!=a->states || b->context.states!=b->states)return differ("property",-1,"states_binding");
if(a->context.jacobian!=&a->memo || b->context.jacobian!=&b->memo)return differ("property",-1,"memo_binding");
if(!!a->context.temperatures!=!!b->context.temperatures)return differ("property",-1,"observer_binding");
for(size_t i=0;i<a->context.count;i++){
const NativePropertyState *x=&a->states[i],*y=&b->states[i];
for(size_t f=0;f<sizeof(state_fields)/sizeof(*state_fields);f++)if(memcmp((const char*)x+state_fields[f].offset,(const char*)y+state_fields[f].offset,state_fields[f].size))return differ("states",(int)i,state_fields[f].name);
if(x->jacobian!=&a->memo || y->jacobian!=&b->memo)return differ("states",(int)i,"memo_binding");
if(!!x->temperatures!=!!y->temperatures)return differ("states",(int)i,"observer_binding");
}
for(int i=0;i<SR_PIPES;i++)for(size_t f=0;f<sizeof(pipe_fields)/sizeof(*pipe_fields);f++)if(memcmp((const char*)&a->pipes[i]+pipe_fields[f].offset,(const char*)&b->pipes[i]+pipe_fields[f].offset,pipe_fields[f].size))return differ("pipes",i,pipe_fields[f].name);
if(a->memo.entries!=a->entries || b->memo.entries!=b->entries || a->memo.capacity!=b->memo.capacity || a->memo.recording!=b->memo.recording)return differ("memo",-1,"lifecycle");
if(memcmp(a->memo.evaluations,b->memo.evaluations,sizeof(a->memo.evaluations)))return differ("memo",-1,"evaluations");
if(memcmp(a->memo.reuses,b->memo.reuses,sizeof(a->memo.reuses)))return differ("memo",-1,"reuses");
for(size_t i=0;i<a->memo.capacity;i++)if(memcmp(&a->entries[i],&b->entries[i],sizeof(*a->entries))){
const Field fields[]={FIELD(NativeJacobianScalarEntry,hash),FIELD(NativeJacobianScalarEntry,inputs),FIELD(NativeJacobianScalarEntry,value),FIELD(NativeJacobianScalarEntry,kind),FIELD(NativeJacobianScalarEntry,medium_kind),FIELD(NativeJacobianScalarEntry,input_count)};
for(size_t k=0;k<sizeof(fields)/sizeof(*fields);k++)if(memcmp((const char*)&a->entries[i]+fields[k].offset,(const char*)&b->entries[i]+fields[k].offset,fields[k].size))return differ("memo_entries",(int)i,fields[k].name);
return differ("memo_entries",(int)i,"padding_byte");
}
if(memcmp(&a->observer,&b->observer,sizeof(a->observer)))return differ("warning",-1,"observer");
return 0;
}
static void dump_blob(FILE *f,const char *name,const void *ptr,size_t n){
fprintf(f,"\"%s\":\"",name);const unsigned char *b=ptr;for(size_t i=0;i<n;i++)fprintf(f,"%02x",b[i]);fputc('"',f);
}
static void mismatch(const char *where,double ref_output,double cand_output){
stats.mismatches++;
if(stats.mismatches==1){
FILE *f=fopen("first-mismatch.json","wb");if(!f)fatal("mismatch log");
fprintf(f,"{\"jac\":%llu,\"position\":%d,\"t\":%.17g,\"where\":\"%s\",\"field\":\"%s\",",jac,configured,sim_time,where,difference);
dump_blob(f,"probe_entry",&entry,sizeof(entry));fputc(',',f);dump_blob(f,"reference",&reference,sizeof(reference));fputc(',',f);dump_blob(f,"candidate",&candidate,sizeof(candidate));fputc(',',f);
dump_blob(f,"metadata",&plan,sizeof(plan));fputc(',',f);dump_blob(f,"reference_output",&ref_output,8);fputc(',',f);dump_blob(f,"candidate_output",&cand_output,8);fputc(',',f);
dump_blob(f,"reference_frame",&frame_ref,sizeof(frame_ref));fputc(',',f);dump_blob(f,"candidate_frame",&frame_cand,sizeof(frame_cand));fputc(',',f);
dump_blob(f,"reference_tail_context",&tail_ref,sizeof(tail_ref));fputc(',',f);dump_blob(f,"candidate_tail_context",&tail_cand,sizeof(tail_cand));fputs("}\n",f);fclose(f);
}
}
static void fill_frame(SRFrame *f,const double *p,const double *h,const double *q,const double *fb,const NativeGas *g,const double *w){
memset(f,0,sizeof(*f));memcpy(f->p,p,sizeof(f->p));memcpy(f->h,h,sizeof(f->h));memcpy(f->q,q,sizeof(f->q));memcpy(f->fb,fb,sizeof(f->fb));memcpy(f->g,g,sizeof(f->g));memcpy(f->w,w,sizeof(f->w));
}
static int live_equal(NativePropertyCache *p,NativePipeCache *pipes){
if(p->count!=entry.context.count || p->capacity!=entry.context.capacity || p->temperatures)return 0;
for(size_t i=0;i<p->count;i++){
NativePropertyState s=entry.states[i];s.jacobian=p->jacobian;
if(memcmp(&s,&p->states[i],sizeof(s)))return 0;
}
return !memcmp(pipes,entry.pipes,sizeof(entry.pipes)) && !memcmp(p->jacobian->entries,entry.entries,p->jacobian->capacity*sizeof(*entry.entries)) &&
!memcmp(p->jacobian->evaluations,entry.memo.evaluations,sizeof(entry.memo.evaluations)) && !memcmp(p->jacobian->reuses,entry.memo.reuses,sizeof(entry.memo.reuses));
}
static int untouched_equal(const SRContext *ctx){
for(size_t i=0;i<entry.context.count;i++){
NativePropertyState expected=entry.states[i];expected.jacobian=(NativeJacobianScalars*)&ctx->memo;
if(memcmp(&ctx->states[i],&expected,sizeof(expected)))return 0;
}
for(int i=0;i<SR_PIPES;i++)if(i!=(position==16?0:28) && memcmp(&ctx->pipes[i],&entry.pipes[i],sizeof(NativePipeCache)))return 0;
/* Unallocated tail is a poison-filled diagnostic canary. */
if(memcmp(ctx->states+ctx->context.count,entry.states+ctx->context.count,(SR_STATES-ctx->context.count)*sizeof(NativePropertyState)))return 0;
return 1;
}
static uint64_t metadata_hash(void){
const unsigned char *bytes=(const unsigned char*)&plan;uint64_t h=UINT64_C(14695981039346656037);
for(size_t i=0;i<sizeof(plan);i++)h=(h^bytes[i])*UINT64_C(1099511628211);
return h;
}
static void negative_tests(const double *inputs);
void sr_shadow(int pos,double t,const double *y,NativePropertyCache *properties,NativePipeCache *pipes,
const double *p,const double *h,const double *q,const double *fb,const NativeGas *g,const double *w,SROperation operation,SRTail tail){
initialize();
if(pos!=configured || group!=(configured==16?6:18))return;
uint64_t metadata_before=metadata_hash();
stats.total++;stats.count_different+=(plan.entry_count!=properties->count);
int saved_errno=errno;SREnvironment saved_env;save_environment(&saved_env);int entry_flags=fetestexcept(FE_ALL_EXCEPT);
from_live(&entry,properties,pipes);copy_context(&reference,&entry);copy_context(&candidate,&entry);
if(compare_contexts(&reference,&candidate))fatal("initial clones differ");
if(reference.states==candidate.states || reference.entries==candidate.entries)fatal("aliased clones");
phase=REFERENCE;double ref_output=operation(&reference.context,reference.pipes,op_inputs);phase=OFF;
int ref_errno=errno,ref_flags=fetestexcept(FE_ALL_EXCEPT);unsigned ref_sse=sse_control();
errno=saved_errno;restore_environment(&saved_env);
memcpy(&before_transaction,&candidate,sizeof(candidate));double cand_output=NAN;int mapping[SR_STATES],appends=0;
int reason=transaction(&candidate,op_inputs,&cand_output,mapping,&appends);
int cand_errno=errno,cand_flags=fetestexcept(FE_ALL_EXCEPT);unsigned cand_sse=sse_control();
if(reason){
stats.rejected++;stats.rejects[reason]++;
if(memcmp(&before_transaction,&candidate,sizeof(candidate)) || !isnan(cand_output))fatal("reject modified Candidate");
stats.rollbacks++;
}else{
stats.accepted++;stats.paths[plan.path]++;stats.appends+=appends;
int relocated=0;
for(int i=0;i<SR_STATES;i++)if(mapping[i]>=0 && mapping[i]!=i){relocated++;stats.relocated_slots++;}
stats.relocated+=(relocated>0);
for(int i=0;i<plan.n;i++)if(plan.events[i].type==QUERY){Event *e=&plan.events[i];if(e->aux){if(e->slot>=0)stats.ph_hit++;else stats.ph_miss++;}else{if(e->slot>=0)stats.pt_hit++;else stats.pt_miss++;}}
size_t bytes=sizeof(Plan)-sizeof(plan.events)+(size_t)plan.n*sizeof(Event);
if(bytes<stats.metadata_min)stats.metadata_min=bytes;
if(bytes>stats.metadata_max)stats.metadata_max=bytes;
if((size_t)plan.n>stats.event_max)stats.event_max=(size_t)plan.n;
if(memcmp(&ref_output,&cand_output,8)){differ("operation",pos,"output");mismatch("operation",ref_output,cand_output);}
else if(compare_contexts(&reference,&candidate))mismatch("operation_context",ref_output,cand_output);
if(!untouched_equal(&reference) || !untouched_equal(&candidate)){differ("probe",-1,"unwritten_data");mismatch("untouched",ref_output,cand_output);}else stats.untouched_equal++;
if(ref_errno!=cand_errno || ref_flags!=cand_flags || ref_sse!=cand_sse){differ("warning",-1,"errno_or_fenv_sse");mismatch("side_effect",ref_output,cand_output);}
if(memcmp(entry.entries,reference.entries,entry.memo.capacity*sizeof(*entry.entries)) || memcmp(entry.entries,candidate.entries,entry.memo.capacity*sizeof(*entry.entries))){differ("memo",-1,"readonly_entries");mismatch("memo",ref_output,cand_output);}else stats.memo_immutable++;
/* Both independent continuations begin at these operation exits. */
copy_context(&tail_ref,&reference);copy_context(&tail_cand,&candidate);
fill_frame(&frame_ref,p,h,q,fb,g,w);memcpy(&frame_cand,&frame_ref,sizeof(frame_ref));
int out_index=pos==16?45:152;frame_ref.q[out_index]=ref_output;frame_cand.q[out_index]=cand_output;
phase=TAIL;errno=saved_errno;restore_environment(&saved_env);
int ref_status=tail(t,y,&tail_ref,&frame_ref);
errno=saved_errno;restore_environment(&saved_env);
int cand_status=tail(t,y,&tail_cand,&frame_cand);phase=OFF;
if(ref_status!=cand_status){differ("evaluator",-1,"return");mismatch("continuation",ref_output,cand_output);}
else if(compare_frame(&frame_ref,&frame_cand))mismatch("continuation",ref_output,cand_output);
else if(compare_contexts(&tail_ref,&tail_cand))mismatch("continuation_context",ref_output,cand_output);
else stats.tail_equal++;
pending_live=1;pending_status=ref_status;
}
fprintf(trials,"{\"jac\":%llu,\"position\":%d,\"t\":%.17g,\"reason\":\"%s\",\"baselineCount\":%llu,\"probeCount\":%llu,\"referenceCount\":%llu,\"candidateCount\":%llu,\"appends\":%d,\"path\":%d,\"events\":%d,\"entryFlags\":%d,\"referenceFlags\":%d,\"candidateFlags\":%d,\"mapping\":[",jac,pos,t,reasons[reason],(unsigned long long)plan.entry_count,(unsigned long long)entry.context.count,(unsigned long long)reference.context.count,(unsigned long long)candidate.context.count,appends,plan.path,plan.n,entry_flags,ref_flags,cand_flags);
int comma=0;if(!reason)for(int i=0;i<SR_STATES;i++)if(mapping[i]>=0)fprintf(trials,"%s[%d,%d]",comma++?",":"",i,mapping[i]);
fputs("]}\n",trials);
if(!reason && (jac==200 || (configured==52 && jac==1)))negative_tests(op_inputs);
if(metadata_hash()!=metadata_before)fatal("Reference or Candidate modified metadata");
stats.metadata_immutable++;
if(!live_equal(properties,pipes))stats.live_contamination++;
phase=OFF;restore_environment(&saved_env);errno=saved_errno;
#if defined(__SSE__)
if(_mm_getcsr()!=saved_env.sse)stats.live_contamination++;
#endif
if(fegetround()!=saved_env.rounding)stats.live_contamination++;
}
void sr_eval_result(int result,const double *dy,const double *w){
if(!pending_live){return;}pending_live=0;
int different=result!=pending_status;
if(different)differ("evaluator",-1,"return");
for(int i=0;!different && i<NSTATES;i++)if(memcmp(&dy[i],&frame_ref.dy[i],8))different=differ("live_dy",i,"value");
for(int i=0;!different && i<NOUTPUTS;i++)if(memcmp(&w[i],&frame_ref.w[i],8))different=differ("live_w",i,"value");
if(different)mismatch("live_tail",0,0);
else stats.live_tail_equal++;
}
/* Adversarial rejection tests run on copies. Every failed transaction must
* leave the entire destination and explicit output sentinel byte-identical. */
static void negative_case(const char *name,int expected,const double *inputs){
SRContext before;memcpy(&before,&candidate,sizeof(before));double output=123.25;int mapping[SR_STATES],appends;
int reason=transaction(&candidate,inputs,&output,mapping,&appends);
int unchanged=!memcmp(&before,&candidate,sizeof(before)) && output==123.25;
fprintf(negative,"{\"jac\":%llu,\"test\":\"%s\",\"reason\":\"%s\",\"expected\":\"%s\",\"unchanged\":%s}\n",jac,name,reasons[reason],reasons[expected],unchanged?"true":"false");
if(reason!=expected || !unchanged)fatal("negative transaction test");
stats.negative_passed++;
}
static void negative_tests(const double *inputs){
Plan saved;memcpy(&saved,&plan,sizeof(saved));
copy_context(&candidate,&entry);candidate.context.temperatures=&candidate.observer;negative_case("observer",OBSERVER,inputs);
copy_context(&candidate,&entry);candidate.context.capacity=candidate.context.count;negative_case("capacity",CAPACITY,inputs);
int allocations=0;for(int i=0;i<plan.n;i++)allocations+=plan.events[i].type==ALLOCATE;
if(allocations>1){copy_context(&candidate,&entry);candidate.context.capacity=candidate.context.count+1;negative_case("capacity_after_first_append",CAPACITY,inputs);}
copy_context(&candidate,&entry);candidate.pipes[position==16?0:28].valid=1;negative_case("pipe_hit",PIPE_BRANCH,inputs);
copy_context(&candidate,&entry);candidate.memo.recording=1;negative_case("memo_recording",MEMO_BINDING,inputs);
copy_context(&candidate,&entry);memset(candidate.entries,0,sizeof(candidate.entries));negative_case("memo_miss",MEMO_MISS,inputs);
copy_context(&candidate,&entry);
for(size_t i=0;i<candidate.memo.capacity;i++)if(candidate.entries[i].hash)candidate.entries[i].value=nextafter(candidate.entries[i].value,INFINITY);
negative_case("memo_value",MEMO_VALUE,inputs);
copy_context(&candidate,&entry);double changed[4];memcpy(changed,inputs,sizeof(changed));changed[0]=nextafter(changed[0],INFINITY);negative_case("changed_input",INPUTS,changed);
int qi=-1,ri=-1,vi=-1,wi=-1;
for(int i=0;i<plan.n;i++){
if(qi<0 && plan.events[i].type==QUERY)qi=i;
if(ri<0 && plan.events[i].type==READ_STATE && plan.events[i].offset==(int)offsetof(NativePropertyState,rho))ri=i;
if(vi<0 && plan.events[i].type==VALID)vi=i;
if(wi<0 && plan.events[i].type==WRITE_STATE)wi=i;
}
if(qi>=0){
copy_context(&candidate,&entry);Event *e=&plan.events[qi];e->slot=e->slot<0?0:-1;negative_case("query_path",QUERY_PATH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
if(ri>=0){copy_context(&candidate,&entry);plan.events[ri].data[0]^=1;negative_case("consumed_rho",CONSUMED,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(vi>=0){copy_context(&candidate,&entry);plan.events[vi].data[0]^=(unsigned char)plan.events[vi].mask;negative_case("valid_test",VALID_BITS,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(wi>=0){copy_context(&candidate,&entry);plan.entry_count=SR_STATES;negative_case("existing_entry_store",EXISTING_UPDATE,inputs);memcpy(&plan,&saved,sizeof(plan));}
if(allocations>1){
int logical=-1;
for(int i=0;i<plan.n;i++)if(plan.events[i].type==ALLOCATE){if(logical<0)logical=plan.events[i].slot;else{plan.events[i].slot=logical;break;}}
copy_context(&candidate,&entry);negative_case("conflicting_append_identity",FIRST_MATCH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
if(qi>=0 && plan.events[qi].slot>=0){
int hit=plan.events[qi].slot;
copy_context(&candidate,&entry);candidate.states[hit].rho=nextafter(candidate.states[hit].rho,INFINITY);negative_case("probe_consumed_rho",CONSUMED,inputs);
copy_context(&candidate,&entry);candidate.states[hit].valid^=NATIVE_PROPERTY_MU;negative_case("probe_valid_bit",VALID_BITS,inputs);
for(int i=qi+1;i<plan.n;i++)if(plan.events[i].type==QUERY && plan.events[i].slot==hit){plan.events[i].slot=hit+1;break;}
copy_context(&candidate,&entry);negative_case("incompatible_first_hit_identity",FIRST_MATCH,inputs);memcpy(&plan,&saved,sizeof(plan));
}
copy_context(&candidate,&entry);plan.events[plan.n-1].type=999;negative_case("late_unknown_event",UNKNOWN_EFFECT,inputs);memcpy(&plan,&saved,sizeof(plan));
copy_context(&candidate,&entry);plan.output=NAN;negative_case("late_nonfinite_output",NONFINITE,inputs);memcpy(&plan,&saved,sizeof(plan));
copy_context(&candidate,&entry);plan.jac++;negative_case("expired_record",NO_RECORD,inputs);memcpy(&plan,&saved,sizeof(plan));
SREnvironment env;save_environment(&env);
if(plan.required_flags){copy_context(&candidate,&entry);feclearexcept(FE_ALL_EXCEPT);negative_case("uncovered_fenv",UNKNOWN_EFFECT,inputs);restore_environment(&env);}
copy_context(&candidate,&entry);fesetround(plan.rounding==FE_DOWNWARD?FE_UPWARD:FE_DOWNWARD);negative_case("rounding_mode",UNKNOWN_EFFECT,inputs);restore_environment(&env);
#if defined(__SSE__)
copy_context(&candidate,&entry);_mm_setcsr(env.sse^0x2000u);negative_case("sse_only_rounding_mode",UNKNOWN_EFFECT,inputs);restore_environment(&env);
#endif
}
void ax_finish(void){
initialize();FILE *f=fopen("shadow-summary.json","wb");if(!f)fatal("summary log");
fprintf(f,"{\"position\":%d,\"total\":%llu,\"accepted\":%llu,\"rejected\":%llu,\"mismatches\":%llu,\"countDifferent\":%llu,\"slotRelocationTrials\":%llu,\"relocatedSlots\":%llu,\"appends\":%llu,\"ptHit\":%llu,\"ptMiss\":%llu,\"phHit\":%llu,\"phMiss\":%llu,\"paths\":[%llu,%llu,%llu,%llu],\"liveContamination\":%llu,\"rejectRollbackChecks\":%llu,\"tailEqual\":%llu,\"liveTailEqual\":%llu,\"memoImmutable\":%llu,\"negativePassed\":%llu,\"forbiddenNativeCalls\":%llu,\"eventBytes\":%llu,\"eventMax\":%llu,\"metadataMin\":%llu,\"metadataMax\":%llu,\"metadataReserved\":%llu,\"contextCopyBytes\":%llu,\"frameCopyBytes\":%llu,\"rejectReasons\":{",configured,stats.total,stats.accepted,stats.rejected,stats.mismatches,stats.count_different,stats.relocated,stats.relocated_slots,stats.appends,stats.pt_hit,stats.pt_miss,stats.ph_hit,stats.ph_miss,stats.paths[0],stats.paths[1],stats.paths[2],stats.paths[3],stats.live_contamination,stats.rollbacks,stats.tail_equal,stats.live_tail_equal,stats.memo_immutable,stats.negative_passed,forbidden_native_calls,(unsigned long long)sizeof(Event),(unsigned long long)stats.event_max,(unsigned long long)stats.metadata_min,(unsigned long long)stats.metadata_max,(unsigned long long)sizeof(Plan),(unsigned long long)sizeof(SRContext),(unsigned long long)sizeof(SRFrame));
for(int i=1;i<NREASONS;i++)fprintf(f,"%s\"%s\":%llu",i>1?",":"",reasons[i],stats.rejects[i]);
fprintf(f,"},\"untouchedProbeEqual\":%llu,\"metadataImmutable\":%llu}\n",stats.untouched_equal,stats.metadata_immutable);fclose(f);fclose(trials);fclose(negative);
}