/* Diagnostic only. The semantic interpreter never calls a physics kernel. */ #include "context_access_diag.h" #include "context_shadow_replay.h" #include #include #include #include #include #include #if defined(__SSE__) #include #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)slotprevious){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;iaux*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;icontext.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;icontext.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;icount;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;icontext.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;imemo;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;isize;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;ientries[(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;icontext.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;itypetype>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->slottype==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;itype==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;icontext.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;icontext.count;i++){ const NativePropertyState *x=&a->states[i],*y=&b->states[i]; for(size_t f=0;fjacobian!=&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;ipipes[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;imemo.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;kentries[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;ip,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;icount;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;imemo; if(memcmp(&ctx->states[i],&expected,sizeof(expected)))return 0; } for(int i=0;ipipes[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;icount); 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=0 && mapping[i]!=i){relocated++;stats.relocated_slots++;} stats.relocated+=(relocated>0); for(int i=0;iaux){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(bytesstats.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=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 && i1){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=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=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;i1?",":"",reasons[i],stats.rejects[i]); fprintf(f,"},\"untouchedProbeEqual\":%llu,\"metadataImmutable\":%llu}\n",stats.untouched_equal,stats.metadata_immutable);fclose(f);fclose(trials);fclose(negative); }