diff --git a/NEXT.md b/NEXT.md index fafe3e1..c6c7453 100644 --- a/NEXT.md +++ b/NEXT.md @@ -271,7 +271,11 @@ type-checks the wrapper against the generated prototype. Trusted: that the signature is the function's real signature — no header is read, deliberately, so nothing can check either. A `_Static_assert` on `sizeof`/`offsetof` was considered and rejected as circular: both sides would come from the same field -list. +list. Padding is not a separate hazard: for every field type the generator +admits — the machine integers, the two floats, `bool`, a pointer and a nested +struct — LLVM's layout is C's, and `emit.ml` writes no datalayout, so clang +applies the target's rules to both halves. Everything where they could diverge +is refused at the field. **One thing got sharper and should be said plainly:** the prototype is now generated *from the declaration*, so a scalar's width carries ABI weight it did diff --git a/lib/shim.ml b/lib/shim.ml index dfe6c99..4c8b8a0 100644 --- a/lib/shim.ml +++ b/lib/shim.ml @@ -40,6 +40,14 @@ typedef permutes with it. And clang type-checks the wrapper against the [extern] prototype, so the flattening cannot disagree with the prototype. + Padding is not a separate hazard, which is worth saying because it reads + like one. For every field type this generator admits — the machine + integers, the two floats, [bool], a pointer and a nested struct — LLVM's + struct layout is C's, and [emit.ml] writes no datalayout, so clang applies + the target's own rules to both halves and they land in the same place. + Everything where the two could diverge — a fixed array, a slice, an + [Option], a map, a union — is refused at the field, by name. + Trusted: that the [defstruct] describes the library's real struct, and that the [declare-c] signature is the function's real signature. No library header is read — deliberately, so a build needs the shared library and not @@ -520,7 +528,7 @@ let flan_wrapper (fn : Ast.fn) (s : shim) raw : Ast.decl_kind = (* ── Expansion ──────────────────────────────────────────────────────── *) -let one env (fn : Ast.fn) csym loc = +let one env ~taken (fn : Ast.fn) csym loc = let needed = ref [] in let sargs = List.map @@ -559,7 +567,15 @@ let one env (fn : Ast.fn) csym loc = let decls = if needs_flan then let raw = raw_name fn.Ast.name in - [ Ast.Declare (flattened fn s raw, s.swrap); flan_wrapper fn s raw ] + (* The flattened declaration's name is made up, so it can collide with + one somebody wrote. Refused here, naming both, rather than arriving as + the checker's "declared twice" about a name not in the file. *) + if Hashtbl.mem taken raw then + fail loc + "the declare-c of %s needs the name %s for the declaration it \ + generates, and %s is declared already — rename one of them" + fn.Ast.name raw raw + else [ Ast.Declare (flattened fn s raw, s.swrap); flan_wrapper fn s raw ] else [ Ast.Declare (flattened fn s fn.Ast.name, s.swrap) ] in (decls, s, !needed) @@ -569,6 +585,16 @@ let one env (fn : Ast.fn) csym loc = no C compile. *) let expand (decls : Ast.decl list) : Ast.decl list * string option = let env = scan decls in + (* The flattened declaration's name is made up, so it can collide with one + somebody wrote. Refused here, naming both, rather than surfacing as the + checker's "declared twice" about a name that is not in the file. *) + let taken = Hashtbl.create 64 in + List.iter + (fun (d : Ast.decl) -> + match Ast.declared_name d with + | Some n -> Hashtbl.replace taken n d.Ast.dloc + | None -> ()) + decls; let shims = ref [] in let needed = ref [] in let out = @@ -576,7 +602,7 @@ let expand (decls : Ast.decl list) : Ast.decl list * string option = (fun (d : Ast.decl) -> match d.Ast.d with | Ast.DeclareC (fn, csym) -> - let ds, s, n = one env fn csym d.Ast.dloc in + let ds, s, n = one env ~taken fn csym d.Ast.dloc in shims := !shims @ [ s ]; List.iter (fun x -> if not (List.mem x !needed) then needed := !needed @ [ x ]) diff --git a/test/test_acceptance.ml b/test/test_acceptance.ml index cb3f018..04b5ed8 100644 --- a/test/test_acceptance.ml +++ b/test/test_acceptance.ml @@ -680,6 +680,11 @@ let () = shim_refuses "declare-c: a field C cannot hold" "(defstruct S [xs [i32]])\n(declare-c f [s S] \"F\")" "field xs of S is a slice"; + shim_refuses "declare-c: the generated name is already taken" + (v2 + ^ "(defn mid-c [a (Ptr Vector2) out (Ptr Vector2)])\n\ + (declare-c mid [a Vector2] Vector2 \"Mid\")") + "needs the name mid-c for the declaration it generates"; shim_refuses "declare-c: two Flan names for one C function" "(declare-c a [] \"Same\")\n(declare-c b [] \"Same\")" "one declare-c per C function"; diff --git a/vendor/raylib/raylib.flan b/vendor/raylib/raylib.flan index a84dc2b..a2d4d9a 100644 --- a/vendor/raylib/raylib.flan +++ b/vendor/raylib/raylib.flan @@ -391,7 +391,7 @@ [center Vector2 radius f32 color Color] "DrawCircleLinesV") -;; Two radii, horizontal then vertical. Equal radii is a circle, so a wrapper +;; Two radii, horizontal then vertical. Equal radii is a circle, so a binding ;; that exchanged them would be invisible unless they differ — which is why ;; sand.flan's ellipse is deliberately wider than it is tall. (declare-c draw-ellipse