A gensym is never the same name twice in one compiler process, however many macro modules it loads
This commit is contained in:
parent
6b9d1fa644
commit
3a3674efb7
18
lib/macro.ml
18
lib/macro.ml
@ -380,12 +380,28 @@ let dir_of (l : loaded) (loc : Loc.t) =
|
|||||||
always has a signature, and the one thing that could put a name in [fns]
|
always has a signature, and the one thing that could put a name in [fns]
|
||||||
without one is the two lists coming apart — in which case expanding
|
without one is the two lists coming apart — in which case expanding
|
||||||
unchecked is the wrong half to lose. *)
|
unchecked is the wrong half to lose. *)
|
||||||
|
(* The gensym counter, process-wide. Every module links its own runtime and
|
||||||
|
so its own [flan_gensym_n], and a build loads several — one per round when
|
||||||
|
a macro calls a macro, then the one the program is expanded with, and a
|
||||||
|
session loads one per expansion. A counter that restarted in each would
|
||||||
|
hand a later module the name an earlier one had already baked into a
|
||||||
|
macro's code. So the count lives here and is written into the module
|
||||||
|
before every call and read back after, whether the call returns or
|
||||||
|
raises. *)
|
||||||
|
let gensym_n = ref 0L
|
||||||
|
|
||||||
|
let with_gensym (l : loaded) f =
|
||||||
|
let cell = Dynload.dl_sym l.handle "flan_gensym_n" in
|
||||||
|
Dynload.poke_i64 cell 0 !gensym_n;
|
||||||
|
Fun.protect ~finally:(fun () -> gensym_n := Dynload.peek_i64 cell 0) f
|
||||||
|
|
||||||
let checked_call (l : loaded) n ~loc (args : Form.t list) : Form.t =
|
let checked_call (l : loaded) n ~loc (args : Form.t list) : Form.t =
|
||||||
(match List.assoc_opt n l.sigs with
|
(match List.assoc_opt n l.sigs with
|
||||||
| Some sg -> Expand.check_call ~name:n ~loc sg args
|
| Some sg -> Expand.check_call ~name:n ~loc sg args
|
||||||
| None -> ());
|
| None -> ());
|
||||||
dir_of l loc;
|
dir_of l loc;
|
||||||
Expand.call ~loc:(Loc.from_macro n loc) (List.assoc n l.fns) args
|
with_gensym l (fun () ->
|
||||||
|
Expand.call ~loc:(Loc.from_macro n loc) (List.assoc n l.fns) args)
|
||||||
|
|
||||||
let fuel = 200
|
let fuel = 200
|
||||||
|
|
||||||
|
|||||||
@ -2137,19 +2137,19 @@ let source = {flan|
|
|||||||
;; explicit gensym is the settled decision (plan.org, open decision 2); this is
|
;; explicit gensym is the settled decision (plan.org, open decision 2); this is
|
||||||
;; the escape hatch that makes it liveable.
|
;; the escape hatch that makes it liveable.
|
||||||
;;
|
;;
|
||||||
;; The counter lives in the loaded module rather than in the compiler, which is
|
;; The counter is C data in the runtime, flan_gensym_n, because a build loads
|
||||||
;; the one place this departs from the sketch. A module is dlopened once
|
;; more than one macro module — one per round when macros call macros, and
|
||||||
;; per compiler process and every macro in a program shares it, so the counter
|
;; another for every expansion in a session — and each links its own copy of
|
||||||
;; is process-wide in practice; a second module would restart it, and the day
|
;; the runtime. lib/macro.ml keeps the count across them: it writes it into
|
||||||
;; there is one, the fix is to seed this from the module's index.
|
;; the module before every macro call and reads it back after, so no two
|
||||||
(defonce gensym-n i64 0)
|
;; modules in one compiler process draw the same name.
|
||||||
|
(declare gensym-next [] i64 "flan_gensym_next")
|
||||||
|
|
||||||
(defn gensym [] Form
|
(defn gensym [] Form
|
||||||
(set gensym-n (+ gensym-n 1))
|
|
||||||
(let [v (vec-new u8)]
|
(let [v (vec-new u8)]
|
||||||
(push v 126) ; ~
|
(push v 126) ; ~
|
||||||
(push v 103) ; g
|
(push v 103) ; g
|
||||||
(let [d (i64->bytes gensym-n)]
|
(let [d (i64->bytes (gensym-next))]
|
||||||
(dotimes [i (length d)]
|
(dotimes [i (length d)]
|
||||||
(push v (at d i))))
|
(push v (at d i))))
|
||||||
(Form.Sym {.s (string (slice v))})))
|
(Form.Sym {.s (string (slice v))})))
|
||||||
|
|||||||
@ -3193,6 +3193,14 @@ const uint8_t *flan_getenv(const uint8_t *name, int64_t n, int64_t *len) {
|
|||||||
char flan_macro_dir[FLAN_PATH_MAX] = { 0 };
|
char flan_macro_dir[FLAN_PATH_MAX] = { 0 };
|
||||||
int64_t flan_macro_dir_n = 0;
|
int64_t flan_macro_dir_n = 0;
|
||||||
|
|
||||||
|
/* The prelude's gensym counter. C data rather than a Flan global for the same
|
||||||
|
* reason as the two above: lib/macro.ml writes it into a module before every
|
||||||
|
* macro call and reads it back after, which is what keeps it counting across
|
||||||
|
* every module a compiler process loads rather than restarting in each. */
|
||||||
|
int64_t flan_gensym_n = 0;
|
||||||
|
|
||||||
|
int64_t flan_gensym_next(void) { return ++flan_gensym_n; }
|
||||||
|
|
||||||
/* The bytes are the caller's to read and nobody's to free: an expansion is
|
/* The bytes are the caller's to read and nobody's to free: an expansion is
|
||||||
* bounded by the size of the program being compiled, which is exactly the
|
* bounded by the size of the program being compiled, which is exactly the
|
||||||
* budget lib/dynload.ml's `owned` note already spends on a macro's own
|
* budget lib/dynload.ml's `owned` note already spends on a macro's own
|
||||||
|
|||||||
24
test/programs/macro-gensym-rounds.flan
Normal file
24
test/programs/macro-gensym-rounds.flan
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
;;;; Two gensyms from two macro modules are never the same name.
|
||||||
|
;;;;
|
||||||
|
;;;; `outer` calls `baked` in its body, so `baked` is compiled in a round of
|
||||||
|
;;;; its own and `outer`'s body is expanded against that module before `outer`
|
||||||
|
;;;; is compiled. The gensym `baked` draws there becomes a literal in
|
||||||
|
;;;; `outer`'s code; the one `outer` draws itself comes from the later module
|
||||||
|
;;;; the program is expanded with. `main` comes first so that its expansion is
|
||||||
|
;;;; that module's first draw. Were the two the same name, the second binding
|
||||||
|
;;;; would shadow the first and this would print 200.
|
||||||
|
|
||||||
|
(defn main [] i32
|
||||||
|
(println (outer 1))
|
||||||
|
0)
|
||||||
|
|
||||||
|
;; Expands to code that builds the symbol this expansion drew.
|
||||||
|
(defmacro baked []
|
||||||
|
(match (gensym)
|
||||||
|
(Form.Sym s) `(Form.Sym {.s ~(Form.Str {.s s})})
|
||||||
|
_ `(form-nil)))
|
||||||
|
|
||||||
|
(defmacro outer [a]
|
||||||
|
(let [g (baked)
|
||||||
|
h (gensym)]
|
||||||
|
`(let [~g ~a ~h 100] (+ ~g ~h))))
|
||||||
@ -3893,6 +3893,11 @@ level "1"
|
|||||||
outputs ~dev:true "a macro's parameter list, dev"
|
outputs ~dev:true "a macro's parameter list, dev"
|
||||||
"programs/macro-params.flan" macro_params_out;
|
"programs/macro-params.flan" macro_params_out;
|
||||||
|
|
||||||
|
(* A gensym drawn in one round's module and one drawn in the module the
|
||||||
|
program is expanded with are different names. 200 is the two colliding. *)
|
||||||
|
outputs "gensym counts across macro modules"
|
||||||
|
"programs/macro-gensym-rounds.flan" "101\n";
|
||||||
|
|
||||||
(* A macro declared in an imported *package*, which is the half the
|
(* A macro declared in an imported *package*, which is the half the
|
||||||
refusal at [a package's macro is not visible unqualified] above leaves
|
refusal at [a package's macro is not visible unqualified] above leaves
|
||||||
out. The program calls six of them qualified and one of its own
|
out. The program calls six of them qualified and one of its own
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user