src/README.md, and cleaning up things while at it

This commit is contained in:
dzaima 2021-08-14 21:10:37 +03:00
parent 86922ce67b
commit de86ce2aaa
11 changed files with 270 additions and 39 deletions

220
src/README.md Normal file
View File

@ -0,0 +1,220 @@
# Overview of the CBQN source
## Conventions
Functions starting with `m_` allocate a new object.
Functions starting with `q_` are queries/predicates, and return a boolean.
Functions ending with `R` are either supposed to be called rarely, or the caller expects that a part of it happens rarely.
Functions ending with `U` return (or take) a non-owned object (`U` = "unincremented").
Variables starting with `bi_` are builtins (primitives or special values).
Which arguments are consumed usually is described in a comment after the function or its prototype. Otherwise, check the source.
```
src/
builtins/
sfns.c structural functions
fns.c other functions
arithd.c dyadic arithmetic functions
arithm.c monadic arithmetic functions (incl •math stuff)
cmp.c dyadic comparison functions
sort.c sort/grade/bins
md1.c 1-modifiers
md2.c 2-modifiers
sysfn.c •-definitions
internal.c •internal
opt/ files which aren't needed for every build configuration
gen/ generated files
core/ things included everywhere
utils/ utilities included as needed
```
## Base
`B` represents any BQN object. Some are heap-allocated, some are not.
```C
Type checks (all are safe to execute on any B instance):
test tag instance description
isVal(x) [many] any heap-allocated object
isFun(x) FUN_TAG a function (guaranteed heap-allocated)
isMd1(x) MD1_TAG a 1-modifier (guaranteed heap-allocated)
isMd2(x) MD2_TAG a 2-modifier (guaranteed heap-allocated)
isMd (x) [many] any modifier (guaranteed heap-allocated)
isCallable(x) [many] isFun|isMd (guaranteed heap-allocated)
isArr(x) ARR_TAG an array type (guaranteed heap-allocated)
isAtm(x) [many] !isArr(x) can be either heap-allocated or tagged
isChr(x) C32_TAG a character (never heap-allocated)
isF64(x) F64_TAG a number (never heap-allocated)
isNsp(x) NSP_TAG a namespace (guaranteed heap-allocated)
isObj(x) OBJ_TAG internal (guaranteed heap allocated)
and then there are some extra types for variables for the VM & whatever; see h.h *_TAG definitions
Extra functions for converting types:
m_f64(x) // f64 → B
m_c32(x) // codepoint → B
m_i32(x) // i32 → B
m_usz(x) // usz → B
o2i(x) // B → i32, throw if impossible
o2i64(x) // B → i64, throw if impossible
o2u64(x) // B → u64, throw if impossible
o2s(x) // B → usz, throw if impossible
o2c(x) // B → c32, throw if impossible
o2f(x) // B → f64, throw if impossible
o2iu(x) // B → i32, assumes is valid (u ≡ unchecked)
o2i64u(x) // B → i64, assumes is valid
o2su(x) // B → usz, assumes is valid
o2cu(x) // B → c32, assumes is valid
o2fu(x) // B → f64, assumes is valid
o2b(x) // B → bool, throw if impossible
q_i32(x) // query if x is convertible to i32
q_i64(x) // query if x is convertible to i64
q_f64(x) // query if x is convertible to f64 (equivalent to isF64)
q_N(x) // query if x is · (≡ bi_N)
noFill(x) // if x represents undefined fill (returned by getFill*; ≡ bi_noFill)
tag(x,*_TAG) // pointer → B
See src/h.h for more basic functions
```
An object can be allocated with `mm_alloc(sizeInBytes, t_something)`. The returned object is a `Value*`, but should be used as some subtype. `mm_free` can be used to force-free an object regardless of its reference count.
A heap-allocated object can be cast to a `Value*` with `v(x)`, to an `Arr*` with `a(x)`, or to any pointer type with `c(Type,x)`. `v(x)->type` stores the type of an object (see `enum Type` in `src/h.h`), which is used by runtime functions to decide how to interpret an object.
Reference count of any `B` object can be incremented/decremented with `inc(x)`/`dec(x)` (`inc(x)` also returns `x`, so you can use it inline in the consumer). A pointer type has `ptr_inc(x)`/`ptr_dec(x)`. `dec`/`ptr_dec` will return the object to the memory manager if the refcount goes to zero.
Since reference counting is hard, there's `make heapverify` that verifies that code executed does it right (and screams unreadable messages when it doesn't). After any changes, I'd suggest running:
```bash
#!/usr/bin/env bash
make rtverify && echo 'rtverify:' && ./BQN -M 1000 path/to/mlochbaum/BQN/test/this.bqn
make heapverify && echo 'heapverify:' && ./BQN -M 1000 path/to/mlochbaum/BQN/test/this.bqn -noerr bytecode header identity literal namespace prim simple syntax token under undo
```
Temporary allocations can be made with `utils/talloc.h`:
```C
#include "utils/talloc.h"
TALLOC(char, buf, 123); // allocate char* buf with 123 elements
// buf is now a regular char* and can be stored/passed around as needed
TREALLOC(buf, 456); // extend buf
TFREE(buf); // free buf
// if the size is guaranteed to be small enough, using VLAs is fine i guess
TSALLOC(i32, stack, 10); // allocate an i32 stack with initially reserved 10 items (initial reserve must be positive!)
TSADD(stack, 15); // add a single item
TSADD(stack, (i32*){1,2,3}, 3); // add many items
usz sz = TSSIZE(stack); // get the current height of the stack
i32 item = stack[1]; // get the 2nd item
TSFREE(stack); // free the stack
// note that TSALLOC creates multiple local variables, and as such cannot be passed around to other functions
```
## Virtual functions
All virtual method accesses require that the argument is heap-allocated.
You can get a virtual function of a `B` instance with `TI(x, something)`. There's also `TIv(x, something)` for a pointer `x` instead. See `#define FOR_TI` in `src/h.h` for available functions.
Call a BQN function with `c1(f, x)` or `c2(f, w, x)`. A specific builtin can be called by looking up the appropriate name in `src/utils/builtins.h` (and adding the `bi_` prefix).
Calling a modifier involves deriving it with `m1_d`/`m2_d`, using a regular `c1`/`c2`, and managing the refcounts of everything while at that.
## Arrays
If you know that `x` is an array (e.g. by testing `isArr(x)` beforehand), `a(x)->ia` will give you the product of the shape, `rnk(x)` will give you the rank, and `a(x)->sh` will give you a `usz*` to the full shape.
Allocating an array:
```C
i32* rp; B r = m_i32arrv(&rp, 123); // allocate a 123-element i32 vector
i32* rp; B r = m_i32arrc(&rp, x); // allocate an array with the same shape as x (x must be an array; x isn't consumed)
i32* rp; Arr* r = m_i32arrp(&rp, 123); // allocate a 123-element i32-array without allocating shape
// then do one of these:
arr_shVec(r); // set shape of r to a vector
usz* sh = arr_shAlloc(r, 4); // allocate a rank 4 shape; write to sh the individual items; sh will be NULL for ranks 0 and 1
arr_shCopy(r, x); // copy the shape object of x (doesn't consume x)
B result = taga(r);
// see stuff.h for m_shArr/arr_shSetI/arr_shSetU for ways to batch-assign a single shape object to multiple objects
u32* rp; B r = m_c32arrv(%rp, 10); // 10-char string
// etc for m_(c32|f64|i32)arr[vcp]
usz ri=0; HArr_p r = m_harrs(123, &ri); // allocate 123-item arbitrary type array
// write items with r.a[ri++] or equivalent. Whenever GC could happen, ri must point to after the end of the currently set items
// then do one of these to finish the array:
B result = harr_fv(r); // sets shape to a vector
B result = harr_fc(r, x); // copies the shape of x, doesn't consume x
B result = harr_fcd(r, x); // copies the shape of x and consumes it
usz* sh = harr_fa(r, 4); // allocate shape for a rank 4 array. To get the result `B` object, just do r.b
// If at any point you want to free the object before finishing it, use harr_abandon(r)
// If you're sure GC cannot happen before all items in the array are set, you can use: (U means "unsafe")
HArr_p r = m_harrUv(10); // 10-item vector
HArr_p r = m_harrUc(10, x); // 10-item array with the same shape as x
HArr_p r = m_harrUp(10); // 10-item array without any set shape. Use the arr_shWhatever(r.c, …)
// you can use withFill to add a fill element to a created array (or manually create a fillarr, see src/core/fillarr.h)
B r = m_str32(U"⟨1⋄2⋄3⟩"); // a constant string with unicode chars
B r = m_str8(5, "hello"); // an ASCII string with specified length
B r = m_str8l("hello"); // an ASCII string, read until null byte
#include "utils/utf.h"
B r = fromUTF8l("⟨1⋄2⋄3⟩") // parse UTF-8 from a char*
B r = fromUTF8("⟨1⋄2⋄3⟩", 17) // ↑ but with a specified length
u64 sz = utf8lenB(x); TALLOC(char, buf, sz+1); toUTF8(x, buf); buf[sz]=0; /*use buf as a C-string*/ TFREE(buf);
// src/utils/mut.h provides a way to build an array by copying parts of other arrays
// some functions for specific cases:
B r = m_unit(x); // equivalent to <𝕩
B r = m_hunit(x); // like the above, except no fill is set
B r = m_atomUnit(x); // if x is likely to be an atom, this is a better alternative to m_unit
B r = m_v1(a); // ⟨a⟩
B r = m_v2(a,b); // ⟨a,b⟩
B r = m_v3(a,b,c); // ⟨a,b,c⟩
B r = m_v4(a,b,c,d); // ⟨a,b,c,d⟩
B r = emptyHVec(); // an empty vector with no fill
B r = emptyIVec(); // an empty integer vector
B r = emptyCVec(); // an empty character vector
B r = emptySVec(); // an empty string vector
// rank can be manually set for an exsiting object with sprnk or srnk, but be careful to keep the shape object in sync!
```
Retrieving data from arrays:
```C
// generic methods:
BS2B xget = TI(x,get); // and then (presumably in a loop) call xget(x,i) to get the i-th element of the ravel. This gives owned objects
BS2B xgetU = TI(x,getU); // returns non-owned objects instead, useful if you're only gonna retrieve atoms or won't keep them around (e.g. only use in `equals` or `hash` or sth)
// for specific array types:
if (TI(x,elType)==el_i32) i32* xp = i32any_ptr(x); // for either t_i32arr or t_i32slice; for t_i32arr only, there's i32arr_ptr(x)
if (TI(x,elType)==el_c32) u32* xp = c32any_ptr(x); // ↑
if (TI(x,elType)==el_f64) f64* xp = f64any_ptr(x); // ↑
if (v(x)->type==t_harr) B* xp = harr_ptr(x);
if (v(x)->type==t_harr || v(x)->type==t_hslice) B* xp = hany_ptr(x); // note that elType==el_B doesn't imply hany_ptr is safe!
if (v(x)->type==t_fillarr) B* xp = fillarr_ptr(x);
```
## Errors
Throw an error with `thrM("some message")` or `thr(some B instance)` or `thrOOM()`. What to do with active references at the time of the error is TBD when GC is actually completed, but you can just not worry about that for now.
A fancier message can be created with `thrF(message, …)` with printf-like (but different!!) varargs (source in `do_fmt`):
```
%i decimal i32
%l decimal i64
%ui decimal u32
%ul decimal u64
%xi hex u32
%xl hex u64
%s decimal usz
%f f64
%p pointer
%c char
%S char* C-string consisting of ASCII
%U char* of UTF-8 data
%R a B instance of a string or number
%H the shape of a B instance
%% "%"
```
See `#define CATCH` in `src/h.h` for how to catch errors.
Use `assert(predicate)` for checks (for optimized builds they're replaced with `if (!predicate) invoke_undefined_behavior();` so it's still invoked!!).
There's also `err("message")` that (at least currently) is kept in optimized builds as-is, and always kills the process on being called.

View File

@ -248,7 +248,7 @@ B fold_c1(B d, B x) { B f = c(Md1D,d)->f;
dec(x);
if (isFun(f)) {
B r = TI(f,identity)(f);
if (!isNothing(r)) return inc(r);
if (!q_N(r)) return inc(r);
}
thrM("´: No identity found");
}

View File

@ -596,7 +596,7 @@ B sys_c1(B t, B x) {
else if (eqStr(c, U"flines")) r.a[i] = m_nfn(fLinesDesc, path_dir(inc(comp_currPath)));
else if (eqStr(c, U"import")) r.a[i] = m_nfn(importDesc, path_dir(inc(comp_currPath)));
else if (eqStr(c, U"args")) {
if(isNothing(comp_currArgs)) thrM("No arguments present for •args");
if(q_N(comp_currArgs)) thrM("No arguments present for •args");
r.a[i] = inc(comp_currArgs);
} else { dec(x); thrF("Unknown system function •%R", c); }
}

View File

@ -1,10 +1,5 @@
// memory defs
static void* mm_alloc(usz sz, u8 type);
static void mm_free(Value* x);
static u64 mm_size(Value* x);
static void mm_visit(B x);
static void mm_visitP(void* x);
NORETURN void bqn_exit(i32 code);
u64 mm_heapUsed(void);
void printAllocStats(void);

19
src/h.h
View File

@ -1,6 +1,5 @@
#pragma once
// #define ATOM_I32
#ifdef DEBUG
// #define DEBUG_VM
#endif
@ -53,6 +52,7 @@
// #define RT_VERIFY // compare native and runtime versions of primitives
// #define NO_RT // whether to completely disable self-hosted runtime loading
// #define PRECOMP // execute just precompiled code at src/gen/interp
// #define ATOM_I32 // not a thing; ignore pls
#ifdef __OpenBSD__
@ -107,6 +107,7 @@
#define AUTO __auto_type
#define LIKELY(X) __builtin_expect(X,1)
#define RARE(X) __builtin_expect(X,0)
#define fsizeof(T,F,E,N) (offsetof(T, F) + sizeof(E)*(N)) // type, flexible array member name, flexible array member type, item amount
#define RFLD(X,T,F) ((T*)((char*)(X) - offsetof(T,F))) // value, result type, field name; reverse-read field: `T* x = …; E v = x->f; x == RFLD(v, T, f)`
#define N64x "%"SCNx64
#define N64d "%"SCNd64
@ -123,7 +124,6 @@ typedef u8 ur;
CTR_FOR(F)
#undef F
#define fsizeof(T,F,E,N) (offsetof(T, F) + sizeof(E)*(N)) // type, flexible array member name, flexible array member type, item amount
// .FF0 .111111111110000000000000000000000000000000000000000000000000000 infinity
// .FF8 .111111111111000000000000000000000000000000000000000000000000000 qNaN
// .FF. .111111111110nnn................................................ sNaN aka tagged aka not f64, if nnn≠0
@ -148,7 +148,6 @@ void cbqn_init(void);
typedef union B {
u64 u;
i64 s;
f64 f;
} B;
#define b(x) ((B)(x))
@ -234,6 +233,11 @@ extern B bi_emptyHVec, bi_emptyIVec, bi_emptyCVec, bi_emptySVec;
#define emptyIVec() ({ B t = bi_emptyIVec; ptr_inc(v(t)); t; })
#define emptyCVec() ({ B t = bi_emptyCVec; ptr_inc(v(t)); t; })
#define emptySVec() ({ B t = bi_emptySVec; ptr_inc(v(t)); t; })
static void* mm_alloc(usz sz, u8 type);
static void mm_free(Value* x);
static u64 mm_size(Value* x);
static void mm_visit(B x);
static void mm_visitP(void* x);
static void dec(B x);
static B inc(B x);
static void ptr_dec(void* x);
@ -263,7 +267,7 @@ NOINLINE NORETURN void thrF(char* s, ...);
NOINLINE NORETURN void thrOOM(void);
jmp_buf* prepareCatch(void);
#if CATCH_ERRORS
#define CATCH setjmp(*prepareCatch()) // use as `if (CATCH) { /*handle error*/ dec(catchMessage); } /*potentially erroring thing*/ popCatch();`
#define CATCH setjmp(*prepareCatch()) // use as `if (CATCH) { /*handle error*/ dec(catchMessage); return; } /*potentially erroring thing*/ popCatch(); /*no errors yay*/`
#else // note: popCatch() must always be called if no error was caught, so no returns before it!
#define CATCH false
#endif
@ -370,7 +374,7 @@ typedef B (*BBBBBB2B)(B, B, B, B, B, B);
F(BS2B, getU) /* like get, but doesn't increment result (mostly equivalent to `B t=get(…); dec(t); t`) */ \
F(BB2B, m1_d) /* consume all args; (m, f) */ \
F(BBB2B, m2_d) /* consume all args; (m, f, g) */ \
F(BSS2A, slice) /* consumes; create slice from given starting position and length; add shape & rank yourself; may not actually be a Slice object; preserves fill */ \
F(BSS2A, slice) /* consumes; create slice from a starting position and length; add shape & rank yourself; may not actually be a Slice object; preserves fill */ \
F(B2B, identity) /* return identity element of this function; doesn't consume */ \
\
F( BBB2B, fn_uc1) /* t,o, x→r; r≡O⌾( T ) x; consumes x */ \
@ -398,8 +402,7 @@ typedef B (*BBBBBB2B)(B, B, B, B, B, B);
#define TI(X,V) (ti_##V[v(X)->type])
static bool isNothing(B b) { return b.u==bi_N.u; }
static void mm_free(Value* x);
static bool q_N(B b) { return b.u==bi_N.u; }
// refcount
static bool reusable(B x) { return v(x)->refc==1; }
@ -427,7 +430,7 @@ static B inc(B x) {
if (isVal(VALIDATE(x))) v(x)->refc++;
return x;
}
static B incBy(B x, i64 am) {
static B incBy(B x, i64 am) { // am mustn't be negative!
if (isVal(VALIDATE(x))) v(x)->refc+= am;
return x;
}

View File

@ -53,7 +53,7 @@ INS B i_FN1C(B f, B x, u32* bc) { POS_UPD; // TODO figure out a way to instead p
dec(f); return r;
}
INS B i_FN1O(B f, B x, u32* bc) { POS_UPD;
B r = isNothing(x)? x : c1(f, x);
B r = q_N(x)? x : c1(f, x);
dec(f); return r;
}
INS B i_FN2C(B w, B f, B x, u32* bc) { POS_UPD;
@ -62,18 +62,18 @@ INS B i_FN2C(B w, B f, B x, u32* bc) { POS_UPD;
}
INS B i_FN2O(B w, B f, B x, u32* bc) { POS_UPD;
B r;
if (isNothing(x)) { dec(w); r = x; }
else r = isNothing(w)? c1(f, x) : c2(f, w, x);
if (q_N(x)) { dec(w); r = x; }
else r = q_N(w)? c1(f, x) : c2(f, w, x);
dec(f);
return r;
}
INS B i_FN1Oi(B x, BB2B fm, u32* bc) { POS_UPD;
B r = isNothing(x)? x : fm(b((u64)0), x);
B r = q_N(x)? x : fm(b((u64)0), x);
return r;
}
INS B i_FN2Oi(B w, B x, BB2B fm, BBB2B fd, u32* bc) { POS_UPD;
if (isNothing(x)) { dec(w); return x; }
else return isNothing(w)? fm(b((u64)0), x) : fd(b((u64)0), w, x);
if (q_N(x)) { dec(w); return x; }
else return q_N(w)? fm(b((u64)0), x) : fd(b((u64)0), w, x);
}
INS B i_ARR_0() { // TODO combine with ADDI
return emptyHVec();
@ -95,7 +95,7 @@ INS B i_OP2D(B f,B m, B g, u32* bc) { POS_UPD; return m2_d (m,f,g); }
INS B i_OP2H(B m, B g ) { return m2_h (m, 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 isNothing(f)? m_atop(g,h) : 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");
}
@ -143,7 +143,7 @@ INS B i_NSPM(B o, u32 l) {
return tag(a,OBJ_TAG);
}
INS B i_CHKV(B x, u32* bc, B* cStack) {
if(isNothing(x)) { POS_UPD; GS_UPD; thrM("Unexpected Nothing (·)"); }
if(q_N(x)) { POS_UPD; GS_UPD; thrM("Unexpected Nothing (·)"); }
return x;
}
INS B i_FAIL(u32* bc, B* cStack) {
@ -268,7 +268,7 @@ static OptRes opt(u32* bc0) {
}
case TR3D: case TR3O: { S(f,0) S(g,1) S(h,2)
if (f.p==-1 | g.p==-1 | h.p==-1) goto defIns;
if (isNothing(f.v)) thrM("JIT optimization: didn't expect constant ·");
if (q_N(f.v)) thrM("JIT optimization: didn't expect constant ·");
B d = m_fork(inc(f.v), inc(g.v), inc(h.v));
cact = 5; RM(f.p); RM(g.p); RM(h.p);
TSADD(data, d.u);
@ -563,7 +563,7 @@ Nvm_res m_nvm(Body* body) {
} break;
case LOCU: TOPs; { u64 d=*bc++; u64 p=*bc++;
LSC(R_A1,d); MOV8rmo(R_RES,R_A1,p*8+offsetof(Scope,vars)); // read variable
IMM(R_A2, bi_optOut.u); MOV8mro(R_A1, R_A2,p*8+offsetof(Scope,vars)); // set to
IMM(R_A2, bi_optOut.u); MOV8mro(R_A1, R_A2,p*8+offsetof(Scope,vars)); // set to bi_optOut
} break;
case EXTO: TOPs; { u64 d=*bc++; IMM(R_A0,*bc++); LSC(R_A1,d); IMM(R_A2,off); INV(3,1,i_EXTO); } break; // (u32 p, Scope* sc, u32* bc, S)
// case LOCU: TOPs; { u64 d=*bc++; IMM(R_A0,*bc++); LSC(R_A1,d); CCALL(i_LOCU); } break; // (u32 p, Scope* sc)

View File

@ -23,7 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// 0 rax V result
// 1 rcx V arg 3
// 2 rdx V arg 2
// 3 rbx
// 3 rbx
// 4 rsp stack
// 5 rbp base
// 6 rsi V arg 1

View File

@ -281,7 +281,7 @@ void load_init() { // very last init function
#else
B r = rtComplete[i]? inc(fruntime[i]) : rtObjGet(rtObjRaw, i);
#endif
if (isNothing(r)) { printf("· in runtime!\n"); exit(1); }
if (q_N(r)) { printf("· in runtime!\n"); exit(1); }
if (isVal(r)) v(r)->flags|= i+1;
#ifdef RT_WRAP
r = rtWrap_wrap(r);

View File

@ -71,14 +71,14 @@ B file_lines(B path) { // consumes
B path_resolve(B base, B rel) { // consumes rel; assumes base is a char vector or bi_N
assert((isArr(base) || isNothing(base)) && isArr(rel));
assert((isArr(base) || q_N(base)) && isArr(rel));
BS2B rgetU = TI(rel,getU);
usz ria = a(rel)->ia;
if (rnk(rel)!=1) thrM("Paths must be character vectors");
for (usz i = 0; i < ria; i++) if (!isC32(rgetU(rel, i))) thrM("Paths must be character vectors");
if (ria==0) { dec(rel); return inc(base); }
if (o2cu(rgetU(rel, 0))=='/') return rel;
if (isNothing(base)) thrM("Using relative path with no absolute base path known");
if (q_N(base)) thrM("Using relative path with no absolute base path known");
BS2B bgetU = TI(base,getU);
usz bia = a(base)->ia;
bool has = bia && o2cu(bgetU(base, bia-1))=='/';
@ -92,8 +92,8 @@ B path_resolve(B base, B rel) { // consumes rel; assumes base is a char vector o
}
B path_dir(B path) { // consumes; returns directory part of file path with trailing slash, or ·
assert(isArr(path) || isNothing(path));
if (isNothing(path)) return path;
assert(isArr(path) || q_N(path));
if (q_N(path)) return path;
BS2B pgetU = TI(path,getU);
usz pia = a(path)->ia;
if (pia==0) thrM("Empty file path");

View File

@ -1,5 +1,18 @@
#pragma once
/* Usage:
Start with MAKE_MUT(name, itemAmount);
MAKE_MUT allocates the object on the stack, so everything must happen within the scope of it.
Optionally, call mut_init(name, el_something) with an appropriate ElType
End with mut_f(v|c|cd|p);
There must be no allocations while a mut object is being built so GC doesn't do bad things.
mut_pfree must be used to free a partially finished `mut` instance safely (e.g. before throwing an error)
methods ending with G expect that mut_init has been called with a type that can fit the elements that it'll set
*/
typedef struct Mut {
u8 type;
usz ia;

View File

@ -380,13 +380,13 @@ NOINLINE Block* compile(B bcq, B objs, B allBlocks, B allBodies, B indices, B to
comp->objs = toHArr(objs);
comp->blockAm = 0;
B nameList;
if (isNothing(tokenInfo)) {
if (q_N(tokenInfo)) {
nameList = bi_emptyHVec;
} else {
B t = TI(tokenInfo,getU)(tokenInfo,2);
nameList = TI(t,getU)(t,0);
}
if (!isNothing(src) && !isNothing(indices)) {
if (!q_N(src) && !q_N(indices)) {
if (isAtm(indices) || rnk(indices)!=1 || a(indices)->ia!=2) thrM("VM compiler: Bad indices");
for (i32 i = 0; i < 2; i++) {
B ind = TI(indices,getU)(indices,i);
@ -546,7 +546,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume
}
case FN1O: { P(f)P(x)
GS_UPD;POS_UPD;
ADD(isNothing(x)? x : c1(f, x)); dec(f);
ADD(q_N(x)? x : c1(f, x)); dec(f);
break;
}
case FN2C: { P(w)P(f)P(x)
@ -556,8 +556,8 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume
}
case FN2O: { P(w)P(f)P(x)
GS_UPD;POS_UPD;
if (isNothing(x)) { dec(w); ADD(x); }
else ADD(isNothing(w)? c1(f, x) : c2(f, w, x));
if (q_N(x)) { dec(w); ADD(x); }
else ADD(q_N(w)? c1(f, x) : c2(f, w, x));
dec(f);
break;
}
@ -592,7 +592,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume
case TR2D: { P(g)P(h) ADD(m_atop( g,h)); break; }
case TR3D: { P(f)P(g)P(h) ADD(m_fork(f,g,h)); break; }
case TR3O: { P(f)P(g)P(h)
if (isNothing(f)) { ADD(m_atop(g,h)); dec(f); }
if (q_N(f)) { ADD(m_atop(g,h)); dec(f); }
else ADD(m_fork(f,g,h));
break;
}
@ -658,7 +658,7 @@ B evalBC(Block* bl, Body* b, Scope* sc) { // doesn't consume
}
case RETN: goto end;
case CHKV: {
if (isNothing(PEEK(1))) { GS_UPD; POS_UPD; thrM("Unexpected Nothing (·)"); }
if (q_N(PEEK(1))) { GS_UPD; POS_UPD; thrM("Unexpected Nothing (·)"); }
break;
}
case FAIL: thrM("No body matched");
@ -974,7 +974,7 @@ NOINLINE B vm_fmtPoint(B src, B prepend, B path, usz cs, usz ce) { // consumes p
NOINLINE void vm_printPos(Comp* comp, i32 bcPos, i64 pos) {
B src = comp->src;
if (!isNothing(src) && !isNothing(comp->indices)) {
if (!q_N(src) && !q_N(comp->indices)) {
B inds = TI(comp->indices,getU)(comp->indices, 0); usz cs = o2s(TI(inds,getU)(inds,bcPos));
B inde = TI(comp->indices,getU)(comp->indices, 1); usz ce = o2s(TI(inde,getU)(inde,bcPos))+1;
// printf(" bcPos=%d\n", bcPos); // in case the pretty error generator is broken