/* flan_dyn — the dynamic-value ABI. * * Milestone 1 of dynamic-by-default. A [flan_dyn] is one machine word, and * every operation the compiler cannot type statically becomes a call to one of * the functions below. The compiler emits these declarations from Emit; this * header is the same contract written for C, and the two are diffed rather * than trusted to agree. * * The word is opaque. Nothing outside the runtime may read a tag out of it, * because which bits carry the tag is the runtime's business and milestone 2 * moves them: the compiler only ever passes words it was given back to the * functions here. That is what lets boxing change representation without a * recompile of the emitter. * * Every function takes and returns scalars, for the reason flan_rt.c gives: * nothing returns a struct by value, so the emitted .ll never has to agree * with a platform's struct-return ABI. */ #ifndef FLAN_DYN_H #define FLAN_DYN_H #include typedef uint64_t flan_dyn; /* ── Construction ────────────────────────────────────────────────────── * * The typed-to-dyn direction. Integer literals in dyn context box as i64: * there is one integer width behind a dyn value, so the defaulting question * that a wider set of boxes would raise does not arise. * * [flan_dyn_nil] is the absent value, and is what an [if] with no else branch * answers in dyn context. It is not Unit — Unit does not box, because a value * of the zero-sized type carries nothing a dyn word could hold. */ flan_dyn flan_dyn_nil(void); flan_dyn flan_dyn_from_i64(int64_t v); flan_dyn flan_dyn_from_f64(double v); flan_dyn flan_dyn_from_bool(int32_t v); /* A string, as ptr+len — the shape [T] and string already have in Emit.ll. * The runtime copies: the bytes behind a Flan string may be a literal in * rodata or a slice of a buffer the program goes on to write. */ flan_dyn flan_dyn_from_bytes(const uint8_t *ptr, int64_t len); /* The heterogeneous vector. In milestone 1 this is the runtime's own object * rather than a Flan (Vec T) that happens to hold dyn words, which is why * push/at/len on it go through the dyn ops below: the compiler knows only * that it holds a dyn. */ flan_dyn flan_dyn_vec_new(void); /* ── Operations ──────────────────────────────────────────────────────── * * Arithmetic dispatches on what the two words actually hold and traps through * flan_trap_hook when they do not agree. The message is the runtime's: it is * the side that knows which pair of types arrived, and a message assembled by * the compiler could only name the static types, which are dyn and dyn. */ flan_dyn flan_dyn_add(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_sub(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_mul(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_div(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_rem(flan_dyn a, flan_dyn b); /* The orderings answer a dyn holding a bool, not a C int: the result of an * operation on dyn operands is a dyn, so that a comparison can be pushed into * a heterogeneous vector like anything else. Where the compiler needs an i1 to * branch on it follows with flan_dyn_need_bool. */ flan_dyn flan_dyn_lt(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_le(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_gt(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_ge(flan_dyn a, flan_dyn b); /* Structural, and never traps. Two values of unrelated types are not an error * to compare — they are unequal. This is the one op where a type mismatch has * an answer instead of a trap, and = is the operator most likely to meet a * heterogeneous container. */ flan_dyn flan_dyn_eq(flan_dyn a, flan_dyn b); flan_dyn flan_dyn_len(flan_dyn v); flan_dyn flan_dyn_at(flan_dyn v, flan_dyn i); void flan_dyn_set_at(flan_dyn v, flan_dyn i, flan_dyn x); void flan_dyn_push(flan_dyn v, flan_dyn x); void flan_dyn_print(flan_dyn v); /* ── Extraction ──────────────────────────────────────────────────────── * * The dyn-to-typed direction, and the only one: a dyn reaches a typed slot * through an annotation the programmer wrote — a typed parameter, a typed * binding — and never by inference. A mismatch traps; the runtime owns the * message for the reason given above. */ int64_t flan_dyn_need_i64(flan_dyn v); double flan_dyn_need_f64(flan_dyn v); int32_t flan_dyn_need_bool(flan_dyn v); /* ── Roots ───────────────────────────────────────────────────────────── * * The collector is precise, so it has to be told where the live dyn words on * the machine stack are. A dyn local or temporary that lives across a call or * an allocation is pushed as a root at its binding and popped at scope exit, * one [flan_dyn_root_pop] per scope carrying the count the scope pushed. * * The address is registered, not the word: the slot is written again while it * is rooted, and a collection in between has to see the current value. */ void flan_dyn_root_push(flan_dyn *slot); void flan_dyn_root_pop(int64_t n); /* Called once from main before any other function here. */ void flan_gc_init(void); #endif /* FLAN_DYN_H */