From 88f8daedbf8c137077790f69135398cb60edf196 Mon Sep 17 00:00:00 2001 From: dzaima Date: Thu, 1 Feb 2024 21:12:47 +0200 Subject: [PATCH] proper bi_optOut handling --- src/h.h | 12 ++++++------ src/jit/nvm_x86_64.c | 14 +++++++------- src/jit/x86_64.h | 5 ++++- src/load.c | 11 ++++++++++- src/vm.c | 14 ++++++++++---- src/vm.h | 10 ++++++++-- test/cases/syntax.bqn | 30 ++++++++++++++++++++---------- 7 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/h.h b/src/h.h index 51b3a902..2158d346 100644 --- a/src/h.h +++ b/src/h.h @@ -176,7 +176,7 @@ typedef u8 ur; // .FF. .111111111110nnn................................................ sNaN aka tagged aka not f64, if nnn≠0 // 7FF. 0111111111110................................................... direct value with no need of refcounting static const u16 C32_TAG = 0b0111111111110001; // 7FF1 0111111111110001................00000000000ccccccccccccccccccccc char -static const u16 TAG_TAG = 0b0111111111110010; // 7FF2 0111111111110010................nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn special value (0=nothing, 1=undefined var, 2=bad header; 3=optimized out; 4=error?; 5=no fill) +static const u16 TAG_TAG = 0b0111111111110010; // 7FF2 0111111111110010nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn special value (see bi_N ect definitions further below) static const u16 VAR_TAG = 0b0111111111110011; // 7FF3 0111111111110011ddddddddddddddddnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn variable reference static const u16 EXT_TAG = 0b0111111111110100; // 7FF4 0111111111110100ddddddddddddddddnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn extended variable reference static const u16 RAW_TAG = 0b0111111111110101; // 7FF5 0111111111110101nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn raw 48 bits of data @@ -346,11 +346,11 @@ u64 mm_heapUsed(void); #endif // some primitive actions -static const B bi_N = b((u64)0x7FF2000000000000ull); // tag(0,TAG_TAG); // make gcc happy -static const B bi_noVar = b((u64)0x7FF2000000000001ull); // tag(1,TAG_TAG); -static const B bi_okHdr = b((u64)0x7FF2000000000002ull); // tag(2,TAG_TAG); -static const B bi_optOut = b((u64)0x7FF2000000000003ull); // tag(3,TAG_TAG); -static const B bi_noFill = b((u64)0x7FF2000000000005ull); // tag(5,TAG_TAG); +static const B bi_N = b((u64)0x7FF2000000000000ull); +static const B bi_noVar = b((u64)0x7FF2C00000000001ull); +static const B bi_okHdr = b((u64)0x7FF2000000000002ull); +static const B bi_optOut = b((u64)0x7FF2800000000003ull); +static const B bi_noFill = b((u64)0x7FF2000000000005ull); extern GLOBAL B bi_emptyHVec, bi_emptyIVec, bi_emptyCVec, bi_emptySVec; #define emptyHVec() incG(bi_emptyHVec) #define emptyIVec() incG(bi_emptyIVec) diff --git a/src/jit/nvm_x86_64.c b/src/jit/nvm_x86_64.c index 372c07f9..09a476b5 100644 --- a/src/jit/nvm_x86_64.c +++ b/src/jit/nvm_x86_64.c @@ -164,12 +164,12 @@ INS B i_MD2C(B f,B m, B g, u32* bc) { POS_UPD; return m2_d (m,f,g); } INS B i_TR2D(B g, B h ) { return m_atop( g,h); } INS B i_TR3D(B f,B g, B h ) { return m_fork(f,g,h); } INS B i_TR3O(B f,B g, B h ) { return q_N(f)? m_atop(g,h) : m_fork(f,g,h); } -INS B i_NOVAR(u32* bc, B* cStack) { - POS_UPD; GS_UPD; thrM("Reading variable before its defined"); +INS B i_BADREAD(u32* bc, B* cStack, B var) { + POS_UPD; GS_UPD; v_tagError(var, 0); } INS B i_EXTO(u32 p, Scope* sc, u32* bc, B* cStack) { B l = sc->ext->vars[p]; - if(l.u==bi_noVar.u) { POS_UPD; GS_UPD; thrM("Reading variable before its defined"); } + if(isTag(l)) { POS_UPD; GS_UPD; v_tagError(l, false); } return inc(l); } INS B i_EXTU(u32 p, Scope* sc) { @@ -225,8 +225,8 @@ INS B i_SETMi(B f, B x, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c2(f,v_getI( INS B i_SETCi(B f, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c1(f,v_getI(sc, p, true) ); dec(f); v_setI(sc, p, inc(r), true, false); return r; } INS void i_SETNv(B x, Scope* sc, u32 p ) { v_setI(sc, p, x, false, false); } INS void i_SETUv(B x, Scope* sc, u32 p, u32* bc) { POS_UPD; v_setI(sc, p, x, true, false); } -INS void i_SETMv(B f, B x, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c2(f,v_getI(sc, p, false),x); dec(f); v_setI(sc, p, r, true, false); } -INS void i_SETCv(B f, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c1(f,v_getI(sc, p, false) ); dec(f); v_setI(sc, p, r, true, false); } +INS void i_SETMv(B f, B x, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c2(f,v_getI(sc, p, true),x); dec(f); v_setI(sc, p, r, true, false); } +INS void i_SETCv(B f, Scope* sc, u32 p, u32* bc) { POS_UPD; B r = c1(f,v_getI(sc, p, true) ); dec(f); v_setI(sc, p, r, true, false); } INS B i_FLDG(B ns, u32 p, Scope* sc) { if (!isNsp(ns)) thrM("Trying to read a field from non-namespace"); B r = inc(ns_getU(ns, p)); @@ -647,7 +647,7 @@ Nvm_res m_nvm(Body* body) { #define LSC(R,D) { if(D) MOV8rmo(R,R_SP,VAR8(pscs,D)); else MOV(R,r_SC); } // TODO return r_SC directly without a pointless mov #define INCV(R) INC4mo(R, offsetof(Value,refc)); // ADD4mi(R_A3, 1); CCALL(i_INC); #ifdef __BMI2__ // TODO move to runtime detection maybe - #define INCB(R,T,U) IMM(T,0xfffffffffffffull);ADD(T,R);IMM(U,0x7fffffffffffeull);CMP(T,U);{J1(cA,lI);MOVi1l(U,0x30);BZHI(U,R,U);INCV(U);LBL1(lI);} + #define INCB(R,T,U) IMM(T,0xfffffffffffffull);ADD(T,R);IMM(U,0x7fffffffffffeull);CMP(T,U);{J1(cA,lI);MOVi1l(U,48);BZHI(U,R,U);INCV(U);LBL1(lI);} #else #define INCB(R,T,U) IMM(T,0xfffffffffffffull);ADD(T,R);IMM(U,0x7fffffffffffeull);CMP(T,U);{J1(cA,lI);IMM(U,0xffffffffffffull);AND(U,R);INCV(U);LBL1(lI);} #endif @@ -698,8 +698,8 @@ Nvm_res m_nvm(Body* body) { case EXTM: TOPs; { u64 d=*bc++; u64 p=*bc++; IMM(R_RES, tagu64((u64)d<<32 | (u32)p, EXT_TAG).u); } break; case VARO: TOPs; { u64 d=*bc++; u64 p=*bc++; LSC(R_A1,d); MOV8rmo(R_RES,R_A1,p*8+offsetof(Scope,vars)); // read variable + MOV(R_A2,R_RES); SHR8i(R_A2, 47); CMP4i(R_A2, v_bad17_read); { J1(cNE,lN); IMM(R_A0,off); MOV(R_A2,R_RES); INV(1,1,i_BADREAD); LBL1(lN); } // check for error INCB(R_RES,R_A2,R_A3); // increment refcount if one's needed - if (d) { IMM(R_A2, bi_noVar.u); CMP(R_A2,R_RES); J1(cNE,lN); IMM(R_A0,off); INV(1,1,i_NOVAR); LBL1(lN); } // check for error } break; case VARU: TOPs; { u64 d=*bc++; u64 p=*bc++; LSC(R_A1,d); MOV8rmo(R_RES,R_A1,p*8+offsetof(Scope,vars)); // read variable diff --git a/src/jit/x86_64.h b/src/jit/x86_64.h index 22d299aa..37273298 100644 --- a/src/jit/x86_64.h +++ b/src/jit/x86_64.h @@ -197,7 +197,10 @@ ASMI(XOR, Reg o, Reg i) { ASMS; REX8(o,i); ASM1(0x31); MRMr(o,i); ASME; } ASMI(AND, Reg o, Reg i) { ASMS; REX8(o,i); ASM1(0x21); MRMr(o,i); ASME; } ASMI(XOR4, Reg o, Reg i) { ASMS; REX4(o,i); ASM1(0x31); MRMr(o,i); ASME; } -ASMI(CMP, Reg o, Reg i) { ASMS; REX8(o,i); ASM1(0x39); MRMr(o,i); ASME; } + +ASMI(CMP, Reg o, Reg i) { ASMS; REX8(o,i); ASM1(0x39); MRMr(o,i); ASME; } +ASMI(CMP2i,Reg o, i16 imm) { ASMS; REX2(o,0); ASM1(0x81); MRM1(o,0xF8); ASM2(imm); ASME; } +ASMI(CMP4i,Reg o, i32 imm) { ASMS; REX4(o,0); ASM1(0x81); MRM1(o,0xF8); ASM4(imm); ASME; } ASMI(ADDi, Reg o, i32 imm) { if(!imm) return; ASMS; REX8(o,0); if(imm==(i8)imm) { ASM1(0x83); MRM1(o,0xc0); ASM1(imm); } else { ASM1(0x81); MRM1(o,0xc0); ASM4(imm); } ASME; } ASMI(SUBi, Reg o, i32 imm) { if(!imm) return; ASMS; REX8(o,0); if(imm==(i8)imm) { ASM1(0x83); MRM1(o,0xe8); ASM1(imm); } else { ASM1(0x81); MRM1(o,0xe8); ASM4(imm); } ASME; } diff --git a/src/load.c b/src/load.c index 7e522cf2..e0006370 100644 --- a/src/load.c +++ b/src/load.c @@ -824,7 +824,16 @@ void base_init() { // very first init function TIi(t_customObj,visit) = customObj_visit; TIi(t_arbObj,visit) = noop_visit; - assert((MD1_TAG>>1) == (MD2_TAG>>1)); // just to be sure it isn't changed incorrectly, `isMd` depends on this + // making sure that expected properties of constants hold + assert((MD1_TAG>>1) == (MD2_TAG>>1)); // dependencies: isMd + + // dependencies: v_tagCheck & the JIT equivalent + assert(v_bad17_read == (bi_noVar.u >>47)); + assert(v_bad17_read == (bi_optOut.u>>47)); + assert(v_bad17_read != (bi_N.u >>47)); + assert(v_bad18_write == (bi_noVar.u >>46)); + assert(v_bad18_write != (bi_optOut.u>>46)); + assert(v_bad18_write != (bi_N.u >>46)); #define FA(N,X) bi_##N = m_bfn(N##_c1, N##_c2, pf_##N); #define FM(N,X) bi_##N = m_bfn(N##_c1, c2_bad, pf_##N); diff --git a/src/vm.c b/src/vm.c index 4415fd07..ef16f264 100644 --- a/src/vm.c +++ b/src/vm.c @@ -537,6 +537,12 @@ FORCE_INLINE bool v_merge(Scope* pscs[], B s, B x, bool upd, bool hdr) { return true; } +NOINLINE NORETURN void v_tagError(B x, bool write) { + char* act = write? "Assignment: Attempting to modify" : "Attempting to read"; + if (x.u == bi_noVar.u) thrF("%S variable which is not yet defined", act); + if (x.u == bi_optOut.u) thrF("%S variable which isn't available due to incomplete or aborted F↩", act); + fatal("Unexpected v_tagError argument"); +} NOINLINE void v_setF(Scope* pscs[], B s, B x, bool upd) { if (isArr(s)) { VTY(s, t_harr); B* sp = harr_ptr(s); @@ -571,7 +577,7 @@ NOINLINE void v_setF(Scope* pscs[], B s, B x, bool upd) { Scope* sc = pscs[V_DEPTH(s)]; B prev = sc->ext->vars[V_POS(s)]; if (upd) { - if (prev.u==bi_noVar.u) thrM("↩: Updating undefined variable"); + if (v_checkBadWrite(prev)) v_tagError(prev, true); dec(prev); } else dec(prev); sc->ext->vars[V_POS(s)] = inc(x); @@ -621,7 +627,7 @@ NOINLINE B v_getF(Scope* pscs[], B s) { } else if (isExt(s)) { Scope* sc = pscs[V_DEPTH(s)]; B r = sc->ext->vars[V_POS(s)]; - if (r.u==bi_noVar.u) thrM("↩: Reading variable that hasn't been set"); + if (v_checkBadRead(r)) v_tagError(r, false); sc->ext->vars[V_POS(s)] = bi_optOut; return r; } else { @@ -823,7 +829,7 @@ B evalBC(Body* b, Scope* sc, Block* bl) { // doesn't consume } case VARO: { u32 d = *bc++; u32 p = *bc++; B l = pscs[d]->vars[p]; - if(l.u==bi_noVar.u) { POS_UPD; thrM("Reading variable before its defined"); } + if(v_checkBadRead(l)) { POS_UPD; v_tagError(l, false); } ADD(inc(l)); break; } @@ -840,7 +846,7 @@ B evalBC(Body* b, Scope* sc, Block* bl) { // doesn't consume } case EXTO: { u32 d = *bc++; u32 p = *bc++; B l = pscs[d]->ext->vars[p]; - if(l.u==bi_noVar.u) { POS_UPD; thrM("Reading variable before its defined"); } + if(v_checkBadRead(l)) { POS_UPD; v_tagError(l, false); } ADD(inc(l)); break; } diff --git a/src/vm.h b/src/vm.h index faf92229..989e4c4d 100644 --- a/src/vm.h +++ b/src/vm.h @@ -279,10 +279,16 @@ typedef struct WrappedObj { #define V_POS(X) ((u32)((X).u)) #define V_DEPTH(X) ((u16)((X).u>>32)) +static u32 v_bad17_read = (TAG_TAG<<1) | 1; +static u32 v_bad18_write = (TAG_TAG<<2) | 3; +SHOULD_INLINE bool v_checkBadRead(B x) { return (x.u >> 47) == v_bad17_read; } +SHOULD_INLINE bool v_checkBadWrite(B x) { return (x.u >> 46) == v_bad18_write; } +NOINLINE NORETURN void v_tagError(B x, bool write); + NOINLINE B v_getF(Scope* pscs[], B s); // doesn't consume FORCE_INLINE B v_getI(Scope* sc, u32 p, bool chk) { B r = sc->vars[p]; - if (chk && r.u==bi_noVar.u) thrM("↩: Reading variable that hasn't been set"); + if (chk && v_checkBadRead(r)) v_tagError(r, false); sc->vars[p] = bi_optOut; return r; } @@ -296,7 +302,7 @@ NOINLINE bool v_sethF(Scope* pscs[], B s, B x); // doesn't consume FORCE_INLINE void v_setI(Scope* sc, u32 p, B x, bool upd, bool chk) { // consumes x if (upd) { B prev = sc->vars[p]; - if (chk && prev.u==bi_noVar.u) thrM("↩: Updating variable that hasn't been set"); + if (chk && v_checkBadWrite(prev)) v_tagError(prev, true); sc->vars[p] = x; dec(prev); } else { diff --git a/test/cases/syntax.bqn b/test/cases/syntax.bqn index bb2431bf..6136bf5b 100644 --- a/test/cases/syntax.bqn +++ b/test/cases/syntax.bqn @@ -1,3 +1,5 @@ +%DEF jiteq _jiteq ← {r ← ⍷ (1⋈𝔽)⎊{𝕊: 0⋈•CurrentError@}¨ 4⥊<𝕩 ⋄ "TEST FAIL: all results weren't equal" ! 1=≠r ⋄ {0‿e: !e; 1‿e: e}⊑r} + # body usage a←"zero"‿"one" ⋄ !a≡a⊸{𝕩?1⊑𝕨;0⊑𝕨}¨0‿1 !"No header matched argument" % { 𝕊a‿b: a+b ; d𝕊a‿b‿c: a+b+c} ↕⥊3 @@ -29,13 +31,13 @@ a←"zero"‿"one" ⋄ !a≡a⊸{𝕩?1⊑𝕨;0⊑𝕨}¨0‿1 !"No dyadic F˜⁼ undo header found for this 2-modifier block" % 1 +{𝔽𝔾𝕩}+˜⁼ 1 # assignment -!"Reading variable before its defined" % {a} ⋄ a←1 -!"↩: Reading variable that hasn't been set" % {a⊢↩↕10} ⋄ a←2 -!"↩: Updating variable that hasn't been set" % {a↩↕10} ⋄ a←2 -!"↩: Reading variable that hasn't been set" % {a (↕10)˙↩} ⋄ a←2 -!"↩: Reading variable that hasn't been set" % a←1 ⋄ {a‿b⊢↩↕2} ⋄ b←2 -!"↩: Updating variable that hasn't been set" % a←1 ⋄ {a‿b↩↕2} ⋄ b←2 -!"↩: Reading variable that hasn't been set" % a←1 ⋄ {a‿b (↕10)˙↩} ⋄ b←2 +!"Attempting to read variable which is not yet defined" % {a} ⋄ a←1 +!"Attempting to read variable which is not yet defined" % {a⊢↩↕10} ⋄ a←2 +!"Assignment: Attempting to modify variable which is not yet defined" % {a↩↕10} ⋄ a←2 +!"Attempting to read variable which is not yet defined" % {a (↕10)˙↩} ⋄ a←2 +!"Attempting to read variable which is not yet defined" % a←1 ⋄ {a‿b⊢↩↕2} ⋄ b←2 +!"Assignment: Attempting to modify variable which is not yet defined" % a←1 ⋄ {a‿b↩↕2} ⋄ b←2 +!"Attempting to read variable which is not yet defined" % a←1 ⋄ {a‿b (↕10)˙↩} ⋄ b←2 !"Assignment: Mismatched shape for spread assignment" % a‿b←1 !"Assignment: Mismatched shape for spread assignment" % a‿b←1‿2‿3 !"Assignment: Mismatched shape for spread assignment" % ⟨a⟩←<1 @@ -43,9 +45,17 @@ a←"zero"‿"one" ⋄ !a≡a⊸{𝕩?1⊑𝕨;0⊑𝕨}¨0‿1 !"Assignment: Mismatched shape for spread assignment" % ⟨⟩←⟨1⟩ ⟨⟩←⟨⟩ %% ⟨⟩ -# mid-update assignment; precise behavior here isn't particularly important, but it should at least be consistent -a←↕2 ⋄ a {𝕊: a←"foo" ⋄ {𝕊:a!↩0}⎊•CurrentError @}↩ ⋄ %% "foo" -!"Reading variable that was optimized out by F↩ after error" % a←"foo" ⋄ ! "foo"≡{𝕊:a!↩@}⎊•CurrentError @ ⋄ •Type a +# mid-update variable behavior +%USE jiteq ⋄ {𝕊: a←"foo" ⋄ {𝕊:a!↩@}⎊•CurrentError @ ⋄ 1} _jiteq @ %% 1 +!"Attempting to read variable which isn't available due to incomplete or aborted F↩" % %USE jiteq ⋄ {𝕊: a←"foo" ⋄ ! "foo" ≡ {𝕊:a!↩@}⎊•CurrentError @ ⋄ a ⋄ 1} _jiteq @ +!"Attempting to read variable which isn't available due to incomplete or aborted F↩" % %USE jiteq ⋄ {𝕊: a←"foo" ⋄ a {𝕊: ≠a}↩ ⋄ 1} _jiteq @ +!"Attempting to read variable which isn't available due to incomplete or aborted F↩" % %USE jiteq ⋄ {𝕊: a←1 ⋄ a {𝕊: a ⊢↩}↩ ⋄ 1} _jiteq @ + +# not-yet-defined variable usage +!"Attempting to read variable which is not yet defined" % %USE jiteq ⋄ {𝕊: {a ⊢↩ } ⋄ a←1} _jiteq @ +!"Attempting to read variable which is not yet defined" % %USE jiteq ⋄ {𝕊: {a ⊢↩ ⋄ 1} ⋄ a←1} _jiteq @ +!"Attempting to read variable which is not yet defined" % %USE jiteq ⋄ {𝕊: {a ⋄ 1} ⋄ a←1} _jiteq @ +!"Assignment: Attempting to modify variable which is not yet defined" % %USE jiteq ⋄ {𝕊: {a‿a↩1‿2 ⋄ 1} ⋄ a←1} _jiteq @ # namespaces ⟨a,b⟩←•BQN"{x⇐1‿2⋄a⇐3‿4⋄y←5‿6⋄b⇐7‿8}" ⋄ a‿b %% ⟨3‿4 ⋄ 7‿8⟩