A bool returned from C reads as its low bit on --x86 as on LLVM, and the where-and fix and the x-1 hint follow the code's own syntax rather than its file name

This commit is contained in:
Joseph Ferano 2026-09-25 21:50:56 +07:00
parent c3a05d7447
commit e81191d770
8 changed files with 76 additions and 33 deletions

View File

@ -8868,7 +8868,7 @@ and unknown_name : 'a. ?setting:bool -> ctx -> Loc.t -> string -> 'a =
and [x/2] are one name each. When the parts either side of an operator and [x/2] are one name each. When the parts either side of an operator
character are a value in scope and a number or another value, that is character are a value in scope and a number or another value, that is
almost certainly the arithmetic, and the sentence says how to spell it. *) almost certainly the arithmetic, and the sentence says how to spell it. *)
(if Filename.check_suffix loc.Loc.file ".fln" then begin (if Source.indented_at loc then begin
let known s = let known s =
s <> "" s <> ""
&& (String.for_all (fun c -> (c >= '0' && c <= '9') || c = '.') s && (String.for_all (fun c -> (c >= '0' && c <= '9') || c = '.') s

View File

@ -1209,6 +1209,22 @@ and header (s : st) w : Form.t =
let wt = advance p in let wt = advance p in
let rec preds acc = let rec preds acc =
let e, _ = expr p in let e, _ = expr p in
(* [and] is how a condition joins tests, so it is what gets written
for several predicates; the clause separates them with commas. *)
(match e.Form.v with
| Form.List ({ Form.v = Form.Sym "and"; _ } :: (_ :: _ as ps)) ->
let spell (q : Form.t) =
match q.Form.v with
| Form.List [ { Form.v = Form.Sym n; _ };
{ Form.v = Form.Sym v; _ } ] ->
Printf.sprintf "%s(%s)" n v
| _ -> Form.to_string q
in
failk "where-and" e.Form.loc
"a where clause separates its predicates with commas, not and \
— write where %s"
(String.concat ", " (List.map spell ps))
| _ -> ());
match (peek p).tok with match (peek p).tok with
| COMMA -> ignore (advance p); preds (e :: acc) | COMMA -> ignore (advance p); preds (e :: acc)
| _ -> List.rev (e :: acc) | _ -> List.rev (e :: acc)

View File

@ -374,26 +374,13 @@ let constraints (body : Form.t list) : Ast.pred list * Form.t list =
{ Ast.pname = name; pvar = String.sub v 1 (String.length v - 1); { Ast.pname = name; pvar = String.sub v 1 (String.length v - 1);
ploc = p.Form.loc } ploc = p.Form.loc }
(* [and] is how a condition joins tests, so it is what gets written (* [and] is how a condition joins tests, so it is what gets written
for several predicates. The fix is spelled in the file's own syntax, for several predicates. The .fln reader refuses its own spelling of
which only the location's file name tells apart here. *) this with its own fix; what reaches here is paren code. *)
| Form.List ({ Form.v = Form.Sym "and"; _ } :: (_ :: _ as ps)) -> | Form.List ({ Form.v = Form.Sym "and"; _ } :: (_ :: _ as ps)) ->
let fln = Filename.check_suffix p.Form.loc.Loc.file ".fln" in Loc.fail p.Form.loc
let fln_pred (q : Form.t) = "a where clause puts several predicates in a vector, not in an and \
match q.Form.v with — write {:where [%s]}"
| Form.List [ { Form.v = Form.Sym n; _ }; { Form.v = Form.Sym v; _ } ] (String.concat " " (List.map Form.to_string ps))
-> Printf.sprintf "%s(%s)" n v
| _ -> Form.to_string q
in
if fln then
Loc.fail p.Form.loc
"a where clause separates its predicates with commas, not and — \
write where %s"
(String.concat ", " (List.map fln_pred ps))
else
Loc.fail p.Form.loc
"a where clause puts several predicates in a vector, not in an \
and — write {:where [%s]}"
(String.concat " " (List.map Form.to_string ps))
| _ -> | _ ->
Loc.fail p.Form.loc Loc.fail p.Form.loc
"a where predicate is (name? $t), one predicate about one type \ "a where predicate is (name? $t), one predicate about one type \

View File

@ -41,13 +41,24 @@ let syntax_of_field = function
| Some ("indented" | "fln") -> Indented | Some ("indented" | "fln") -> Indented
| _ -> Paren | _ -> Paren
let in_request = ref false
let with_code ?indent ~syntax ~at f = let with_code ?indent ~syntax ~at f =
let s = !code_syntax and a = !code_at and i = !code_indent in let s = !code_syntax and a = !code_at and i = !code_indent
and r = !in_request in
code_syntax := syntax; code_syntax := syntax;
code_at := at; code_at := at;
code_indent := indent; code_indent := indent;
in_request := true;
Fun.protect Fun.protect
~finally:(fun () -> code_syntax := s; code_at := a; code_indent := i) f ~finally:(fun () ->
code_syntax := s; code_at := a; code_indent := i; in_request := r) f
(* Was the code at [loc] written in the indented syntax? Inside an editor
request the request says; outside one a file's name does, as
[read_file] decides. *)
let indented_at (loc : Loc.t) =
if !in_request then !code_syntax = Indented else is_indented loc.Loc.file
(* The paren reader started at a line and column: [Reader.read_all] always (* The paren reader started at a line and column: [Reader.read_all] always
starts at 1:1. *) starts at 1:1. *)

View File

@ -3244,6 +3244,11 @@ and call_native ?at f ~sym ?(chan = false) ~(args : Tast.expr list) ~rty dst =
unsupported unsupported
"%s returns %s by value, which needs SysV return classification this \ "%s returns %s by value, which needs SysV return classification this \
backend does not have" sym (Types.to_string rty); backend does not have" sym (Types.to_string rty);
(* A C bool is 0 or 1 only by the callee's good faith: [(declare f [..]
bool "abs")] hands back whatever byte abs left. LLVM declares the
return [i1] and reads its low bit, so this does too, and every bool
past this point is 0 or 1 — which [=], [not] and [match] assume. *)
if Types.equal rty Types.Bool then and_imm f.b ~dst:rax 1;
store_loc f ~reg:(if is_float rty then xmm0 else rax) dst rty store_loc f ~reg:(if is_float rty then xmm0 else rax) dst rty
end end

View File

@ -0,0 +1,24 @@
;;;; A bool from C that is not 0 or 1 reads as its low bit on both backends:
;;;; abs(3) is 3, which is true, and equal to abs(1).
(declare two [n i32] bool "abs")
(defstruct Flag [on bool])
(defn main [] i32
(let [a (two 3)
b (two 1)
c (two 0)
f (Flag {.on a})
v [a b]]
(print (= a b)) (print " ")
(print (!= a b)) (print " ")
(print (= a true)) (print " ")
(print (= (.on f) b)) (print " ")
(print (= (at v 0) (at v 1))) (print " ")
(print (match a true "T" false "F")) (print " ")
(print (if a "t" "f")) (print " ")
(print (not a)) (print " ")
(print (= c false))
(println "")
0))

View File

@ -459,6 +459,12 @@ let () =
"programs/match-bool.flan" match_bool_out; "programs/match-bool.flan" match_bool_out;
outputs ~dev:true "match over a bool and keywords, dev" outputs ~dev:true "match over a bool and keywords, dev"
"programs/match-bool.flan" match_bool_out; "programs/match-bool.flan" match_bool_out;
let ffi_bool_out = "true false true true true T t false true\n" in
outputs "a bool from C" "programs/ffi-bool.flan" ffi_bool_out;
outputs ~opt:"-O0" "a bool from C, -O0" "programs/ffi-bool.flan"
ffi_bool_out;
outputs ~x86:true "a bool from C, --x86" "programs/ffi-bool.flan"
ffi_bool_out;
(* update, ++ and -- evaluate their place's subexpressions once: the (* update, ++ and -- evaluate their place's subexpressions once: the
counts are the number of calls an index or a key function got. *) counts are the number of calls an index or a key function got. *)
let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in let update_out = "3\n11 20 90\n1 1 3\n16\n2\n7 1\n32\n2 50\n" in

View File

@ -287,6 +287,11 @@ let () =
"(defn g [h (Fn [i32 i32] bool)] () (h 1 2))"; "(defn g [h (Fn [i32 i32] bool)] () (h 1 2))";
reads "untyped parameter is dyn" "fn id(x) -> dyn = x" "(defn id [x dyn] dyn x)"; reads "untyped parameter is dyn" "fn id(x) -> dyn = x" "(defn id [x dyn] dyn x)";
refuses "no return type" "fn f(x)\n x" "indent/return-type" "-> i32"; refuses "no return type" "fn f(x)\n x" "indent/return-type" "-> i32";
(* The fix is .fln's commas whatever the file is called: [read] names it
<syntax>. *)
refuses "where predicates joined with and"
"fn f(x: $t, y: $u) -> i32 where ordered?($t) and equal?($u)\n 0"
"indent/where-and" "commas, not and — write where ordered?($t), equal?($u)";
(* Characters, lexed before brackets and separators. *) (* Characters, lexed before brackets and separators. *)
reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])"; reads "character literals" "x = [\\( \\, \\space \\)]" "(set x [\\( \\, \\space \\)])";
reads "character arguments" "f(\\,, \\))" "(f \\, \\))"; reads "character arguments" "f(\\,, \\))" "(f \\, \\))";
@ -534,17 +539,6 @@ let () =
if not (Test_support.contains d.Loc.dmsg "Did you mean x - 1?") then if not (Test_support.contains d.Loc.dmsg "Did you mean x - 1?") then
fail "x-1: %s" d.Loc.dmsg fail "x-1: %s" d.Loc.dmsg
| exception e -> fail "x-1: %s" (Printexc.to_string e)); | exception e -> fail "x-1: %s" (Printexc.to_string e));
(* Several where predicates joined with and: the fix is .fln's commas. *)
let f = Filename.concat scratch "syntax-where-and.fln" in
write f "fn f(x: $t, y: $u) -> i32 where ordered?($t) and equal?($u)\n 0\n";
(match Front.checked f with
| _ -> fail "where joined with and checked"
| exception Loc.Error d ->
if not (Test_support.contains d.Loc.dmsg
"separates its predicates with commas, not and — write where \
ordered?($t), equal?($u)") then
fail "where joined with and: %s" d.Loc.dmsg
| exception e -> fail "where joined with and: %s" (Printexc.to_string e));
(* One package, one file in two syntaxes: refused naming both. *) (* One package, one file in two syntaxes: refused naming both. *)
let dir = Filename.concat scratch "syntax-twin" in let dir = Filename.concat scratch "syntax-twin" in
let pkg = Filename.concat dir "geo" in let pkg = Filename.concat dir "geo" in