Replace module hashes for external modules

NOTE: This is a temporary solution

A future approach could be to have Catala generate a module loader (with the
proper hash), relieving the user implementation from having to do the
registration.
This commit is contained in:
Louis Gesbert 2024-05-27 11:45:45 +02:00
parent f04e889173
commit db87409125
6 changed files with 41 additions and 14 deletions

View File

@ -106,3 +106,5 @@ let of_string s =
(fun catala_version flags_hash contents -> (fun catala_version flags_hash contents ->
{ catala_version; flags_hash = Flags.of_t flags_hash; contents }) { catala_version; flags_hash = Flags.of_t flags_hash; contents })
with Scanf.Scan_failure _ -> failwith "Hash.of_string" with Scanf.Scan_failure _ -> failwith "Hash.of_string"
let external_placeholder = "*external*"

View File

@ -71,3 +71,11 @@ val format : Format.formatter -> full -> unit
val of_string : string -> full val of_string : string -> full
(** @raise Failure *) (** @raise Failure *)
val external_placeholder : string
(** It's inconvenient to need hash updates on external modules. This string is
uses as a hash instead for those cases.
NOTE: This is a temporary solution A future approach could be to have Catala
generate a module loader (with the proper hash), relieving the user
implementation from having to do the registration. *)

View File

@ -725,8 +725,12 @@ let check_and_reexport_used_modules fmt ~hashf modules =
| Ok () -> ()@,\ | Ok () -> ()@,\
@[<hv 2>| Error h -> failwith \"Hash mismatch for module %a, it may \ @[<hv 2>| Error h -> failwith \"Hash mismatch for module %a, it may \
need recompiling\"@]@]@," need recompiling\"@]@]@,"
(ModuleName.to_string m) Hash.format (hashf intf_id.hash) (ModuleName.to_string m)
ModuleName.format m; (fun ppf h ->
if intf_id.is_external then
Format.pp_print_string ppf Hash.external_placeholder
else Hash.format ppf h)
(hashf intf_id.hash) ModuleName.format m;
Format.fprintf fmt "@[<hv 2>module %a@ = %a@]@," ModuleName.format m Format.fprintf fmt "@[<hv 2>module %a@ = %a@]@," ModuleName.format m
ModuleName.format m) ModuleName.format m)
modules modules
@ -735,7 +739,8 @@ let format_module_registration
fmt fmt
(bnd : ('m Ast.expr Var.t * _) String.Map.t) (bnd : ('m Ast.expr Var.t * _) String.Map.t)
modname modname
hash = hash
is_external =
Format.pp_open_vbox fmt 2; Format.pp_open_vbox fmt 2;
Format.pp_print_string fmt "let () ="; Format.pp_print_string fmt "let () =";
Format.pp_print_space fmt (); Format.pp_print_space fmt ();
@ -758,7 +763,11 @@ let format_module_registration
Format.pp_print_char fmt ' '; Format.pp_print_char fmt ' ';
Format.pp_print_string fmt "]"; Format.pp_print_string fmt "]";
Format.pp_print_space fmt (); Format.pp_print_space fmt ();
Format.fprintf fmt "\"%a\"" Hash.format hash; Format.fprintf fmt "\"%a\""
(fun ppf h ->
if is_external then Format.pp_print_string ppf Hash.external_placeholder
else Hash.format ppf h)
hash;
Format.pp_close_box fmt (); Format.pp_close_box fmt ();
Format.pp_close_box fmt (); Format.pp_close_box fmt ();
Format.pp_print_newline fmt () Format.pp_print_newline fmt ()
@ -791,6 +800,7 @@ let format_program
match p.module_name, exec_scope with match p.module_name, exec_scope with
| Some (modname, intf_id), None -> | Some (modname, intf_id), None ->
format_module_registration fmt bnd modname (hashf intf_id.hash) format_module_registration fmt bnd modname (hashf intf_id.hash)
intf_id.is_external
| None, Some scope_name -> | None, Some scope_name ->
let scope_body = Program.get_scope_body p scope_name in let scope_body = Program.get_scope_body p scope_name in
format_scope_exec p.decl_ctx fmt bnd scope_name scope_body format_scope_exec p.decl_ctx fmt bnd scope_name scope_body

View File

@ -1158,6 +1158,10 @@ let evaluate_expr ctx lang e = evaluate_expr ctx lang (addcustom e)
let load_runtime_modules ~hashf prg = let load_runtime_modules ~hashf prg =
let load (mname, intf_id) = let load (mname, intf_id) =
let hash = hashf intf_id.hash in let hash = hashf intf_id.hash in
let expect_hash =
if intf_id.is_external then Hash.external_placeholder
else Hash.to_string hash
in
let obj_file = let obj_file =
Dynlink.adapt_filename Dynlink.adapt_filename
File.(Pos.get_file (Mark.get (ModuleName.get_info mname)) -.- "cmo") File.(Pos.get_file (Mark.get (ModuleName.get_info mname)) -.- "cmo")
@ -1176,9 +1180,7 @@ let load_runtime_modules ~hashf prg =
"While loading compiled module from %a:@;<1 2>@[<hov>%a@]" "While loading compiled module from %a:@;<1 2>@[<hov>%a@]"
File.format obj_file Format.pp_print_text File.format obj_file Format.pp_print_text
(Dynlink.error_message dl_err)); (Dynlink.error_message dl_err));
match match Runtime.check_module (ModuleName.to_string mname) expect_hash with
Runtime.check_module (ModuleName.to_string mname) (Hash.to_string hash)
with
| Ok () -> () | Ok () -> ()
| Error bad_hash -> | Error bad_hash ->
Message.debug Message.debug
@ -1186,7 +1188,10 @@ let load_runtime_modules ~hashf prg =
ModuleName.format mname Hash.format hash ModuleName.format mname Hash.format hash
(fun ppf h -> (fun ppf h ->
try Hash.format ppf (Hash.of_string h) try Hash.format ppf (Hash.of_string h)
with Failure _ -> Format.pp_print_string ppf "<invalid>") with Failure _ ->
if h = Hash.external_placeholder then
Format.fprintf ppf "@{<cyan>%s@}" Hash.external_placeholder
else Format.fprintf ppf "@{<red><invalid>@}")
bad_hash; bad_hash;
Message.error Message.error
"Module %a@ needs@ recompiling:@ %a@ was@ likely@ compiled@ from@ an@ \ "Module %a@ needs@ recompiling:@ %a@ was@ likely@ compiled@ from@ an@ \

View File

@ -31,10 +31,10 @@ catala implementation and compile to OCaml (removing the `external` directive):
``` ```
```shell-session ```shell-session
$ clerk build _build/.../Prorata_external.ml $ clerk build _build/.../prorata_external.ml
``` ```
(beware the `_build/`, and the capitalisation of the module name) (beware the `_build/`, it is required here)
## Write the OCaml implementation ## Write the OCaml implementation
@ -44,9 +44,11 @@ capitalisation to match). Edit to replace the dummy implementation by your code.
Refer to `runtimes/ocaml/runtime.mli` for what is available (especially the Refer to `runtimes/ocaml/runtime.mli` for what is available (especially the
`Oper` module to manipulate the types). `Oper` module to manipulate the types).
Keep the `register_module` at the end as is, it's needed for the toplevel to use Keep the `register_module` at the end, but replace the hash (which should be of
the value (you would get `Failure("Could not resolve reference to Xxx")` during the form `"CM0|XXXXXXXX|XXXXXXXX|XXXXXXXX"`) by the string `"*external*"`. This
evaluation). section is needed for the Catala interpreter to find the declared values --- the
error `Failure("Could not resolve reference to Xxx")` during evaluation is a
symptom that it is missing.
## Compile and test ## Compile and test

View File

@ -37,4 +37,4 @@ let () =
Runtime_ocaml.Runtime.register_module "Prorata_external" Runtime_ocaml.Runtime.register_module "Prorata_external"
[ "prorata", Obj.repr prorata_; [ "prorata", Obj.repr prorata_;
"prorata2", Obj.repr prorata2_ ] "prorata2", Obj.repr prorata2_ ]
"todo-module-hash" "*external*"