Where a program starts, how a big array clears, and which key folds
flan-dev's start command proposed the last program it had started, so invoking it from a fresh project's buffer offered the previous project's file. It now proposes the buffer it was called from; restarting the previous program is what flan-dev-restart-program is for. Zeroing a fixed array wrote one typed store per element. Above 64 bytes that becomes a memset, which LLVM can lower as a bulk clear; below it the inline stores are still cheaper than a call. Outline's minor-mode map owned TAB in the lowering buffer, so the folding keys that buffer defines never ran. A buffer-local overriding map gives them back without touching Outline anywhere else. FIX.org collects the rough edges found while using the dev loop.
This commit is contained in:
parent
65d14f42f0
commit
798c852934
26
FIX.org
Normal file
26
FIX.org
Normal file
@ -0,0 +1,26 @@
|
||||
* Stuff I've found
|
||||
** Why do I need to call flan-dev to open another window?
|
||||
let: flan: the program exited; restart flan dev
|
||||
** I can't eval a toplevel defvar, need to eval-defun (C-c C-c)
|
||||
For Flan’s intended live-program workflow, C-x C-e on any complete top-level form should do the natural thing:
|
||||
|
||||
- expression → compile/run temporary thunk; print its value
|
||||
- defn, defvar, defmacro, etc. → compile/install it; report what changed
|
||||
|
||||
The compiler already has both paths. The current split is an editor/UI artifact: C-x C-e is wired directly to eval-expr, while C-c C-c is wired to declaration reload. It is not a fundamental limitation.
|
||||
|
||||
A good fix would make C-x C-e context-aware: if the enclosing form is top-level, send it through the declaration evaluator; otherwise use expression evaluation. Then C-c C-c can remain a convenient explicit “reload this definition” alias, but not the only way defvar works.
|
||||
** I can't eval a top level Vec
|
||||
slurp returns (Vec u8), an owning, move-only buffer. Flan currently forbids every move-only global because it has no global ownership/lifetime model: any function could read and free it, while ownership tracking only exists within one function.
|
||||
|
||||
For data that is fixed at build time, use an embedded immutable array instead:
|
||||
|
||||
(defconst the-data (embed "game-data.edn"))
|
||||
|
||||
That produces a fixed [u8], not a heap-owning Vec, so it can live globally. It also resolves relative to sand.flan.
|
||||
|
||||
If game-data.edn genuinely must be loaded at runtime, then today it has to be owned by a local—typically load it in main and pass it through the functions that need it. For a game-wide runtime-owned data asset, that is a missing language/runtime feature, not a bad use case on your part.
|
||||
** The edn module seems to need a struct declaration, it should do both; go into a struct but also return a Map with Vecs and Sets when we don't provide a type
|
||||
** defenum needs optional autoincrementing discriminants
|
||||
** We need a javascript backend so we can reach the world
|
||||
** We need to have C-style unions, maybe those are called defunion, and then sum types are defdata or deftype
|
||||
@ -1,4 +1 @@
|
||||
* Additional things
|
||||
** Why do we have prints dedicted per type? That's terrible, what do we need to have proper (println) work for everything?
|
||||
** The current std is suffering from the same problem, functions should be generic? Why is it like this?
|
||||
** Why do we have =at= and =nth= if they're the same? Pick one, maybe =at=. For all STD functions make sure we don't have redundancy, even if it matches clojure or lisp
|
||||
|
||||
@ -570,13 +570,19 @@ When called interactively while a daemon this Emacs started is alive, asks
|
||||
before stopping it and switching programs. A noninteractive call still
|
||||
refuses: callers cannot silently discard a running program's state."
|
||||
(interactive
|
||||
;; The program last started, where there was one: a restart after a quit is
|
||||
;; the common case, and it is rarely the buffer point happens to be in —
|
||||
;; you quit from wherever you were reading when you decided to.
|
||||
(list (read-file-name "flan dev: " nil flan-dev--file t
|
||||
(and buffer-file-name
|
||||
(string-suffix-p ".flan" buffer-file-name)
|
||||
(file-name-nondirectory buffer-file-name)))))
|
||||
;; Starting a program is about the buffer the command was called from.
|
||||
;; Keeping [flan-dev--file] as DEFAULT here made a previous project win
|
||||
;; over the current buffer: after working on sand.flan, invoking this from
|
||||
;; ~/Development/gameboy/gameboy.flan still proposed (and could start)
|
||||
;; sand.flan. [flan-dev-restart-program] is the deliberate way to restart
|
||||
;; the previous program; an ordinary M-x command must not do that.
|
||||
(let ((file (and buffer-file-name
|
||||
(string-suffix-p ".flan" buffer-file-name)
|
||||
(expand-file-name buffer-file-name))))
|
||||
(list (read-file-name "flan dev: "
|
||||
(and file (file-name-directory file))
|
||||
file t
|
||||
(and file (file-name-nondirectory file))))))
|
||||
(when (process-live-p flan-dev--daemon)
|
||||
;; `interactive' has already read FILE. Refusing only here used to make
|
||||
;; that selection look as though it had been ignored: the old daemon kept
|
||||
|
||||
@ -562,6 +562,16 @@ that is felt."
|
||||
(setq-local outline-regexp "[▸▾] ")
|
||||
(setq-local outline-level (lambda () 1))
|
||||
(outline-minor-mode 1)
|
||||
;; Minor-mode bindings take precedence over this mode's map. Outline owns
|
||||
;; TAB by default, so give this buffer its intended folding keys back
|
||||
;; without changing Outline's bindings anywhere else.
|
||||
(let ((map (copy-keymap outline-minor-mode-map)))
|
||||
(define-key map (kbd "TAB") #'flan-lower-toggle)
|
||||
(define-key map [backtab] #'flan-lower-cycle-all)
|
||||
(setq-local minor-mode-overriding-map-alist
|
||||
(cons (cons 'outline-minor-mode map)
|
||||
(assq-delete-all 'outline-minor-mode
|
||||
minor-mode-overriding-map-alist))))
|
||||
(add-hook 'kill-buffer-hook #'flan-lower--clean nil t))
|
||||
|
||||
;;;###autoload
|
||||
|
||||
@ -853,6 +853,8 @@ is written instead — the real `message' call the real command makes."
|
||||
(with-current-buffer flan-lower-buffer
|
||||
(test-flan--check "C-c C-l opens a lowering buffer in its own mode"
|
||||
(derived-mode-p 'flan-lower-mode))
|
||||
(test-flan--check "TAB is the lowering buffer's toggle, not Outline's"
|
||||
(eq (key-binding (kbd "TAB")) #'flan-lower-toggle))
|
||||
;; The claim the header makes, which is the opposite of the one
|
||||
;; `flan-disassemble' makes, and the reason both commands exist.
|
||||
(test-flan--check "whose header says it is the file and not the program"
|
||||
|
||||
27
lib/emit.ml
27
lib/emit.ml
@ -577,6 +577,25 @@ let ins f fmt =
|
||||
Printf.ksprintf
|
||||
(fun s -> if f.live then Buffer.add_string f.b (" " ^ s ^ f.dloc ^ "\n")) fmt
|
||||
|
||||
(* A fixed array has no padding between elements, so its zero value is exactly
|
||||
a run of zero bytes. Naming that operation lets LLVM choose its bulk-clear
|
||||
implementation instead of expanding a large aggregate store into one store
|
||||
per element. Keep small arrays as typed stores: their inline code is
|
||||
cheaper than a call on targets which do not inline the intrinsic. *)
|
||||
let bulk_zero_min_bytes = 64
|
||||
|
||||
let emit_bulk_zero f ptr ty =
|
||||
match ty with
|
||||
| Types.Array _ ->
|
||||
let size, align = lay f.md ty in
|
||||
if size >= bulk_zero_min_bytes then begin
|
||||
ins f
|
||||
"call void @llvm.memset.p0.i64(ptr align %d %s, i8 0, i64 %d, i1 false)"
|
||||
align ptr size;
|
||||
true
|
||||
end else false
|
||||
| _ -> false
|
||||
|
||||
let term f fmt =
|
||||
Printf.ksprintf
|
||||
(fun s ->
|
||||
@ -1084,8 +1103,11 @@ and value_at f (e : Tast.expr) : string =
|
||||
"zeroinitializer"
|
||||
| Tast.Set (p, v) ->
|
||||
let ptr, ty = place f p in
|
||||
let v' = value f v in
|
||||
ins f "store %s %s, ptr %s" (ll ty) v' ptr;
|
||||
(match v.Tast.e with
|
||||
| Tast.Zero _ when emit_bulk_zero f ptr ty -> ()
|
||||
| _ ->
|
||||
let v' = value f v in
|
||||
ins f "store %s %s, ptr %s" (ll ty) v' ptr);
|
||||
"zeroinitializer"
|
||||
| Tast.Make (_, fields) -> aggregate f e.Tast.ty fields
|
||||
| Tast.MakeCase (uname, case, fields) ->
|
||||
@ -2602,6 +2624,7 @@ let header = {|; Generated by flan. The layout is C's: no object headers anywher
|
||||
%flanframe = type { ptr, ptr, ptr }
|
||||
@flan_frame_head = external global ptr
|
||||
|
||||
declare void @llvm.memset.p0.i64(ptr nocapture writeonly, i8, i64, i1 immarg)
|
||||
declare void @flan_rt_init(i32, ptr)
|
||||
declare void @flan_argv(ptr)
|
||||
declare void @flan_write_stdout(ptr, i64)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user