diff --git a/compiler/bin-wasm_of_ocaml/compile.ml b/compiler/bin-wasm_of_ocaml/compile.ml index 7611d011a0..cd967dcaea 100644 --- a/compiler/bin-wasm_of_ocaml/compile.ml +++ b/compiler/bin-wasm_of_ocaml/compile.ml @@ -522,9 +522,12 @@ let run tmp_wasm_file in let wasm_name = - Printf.sprintf - "code-%s" - (String.sub (Digest.to_hex (Digest.file tmp_wasm_file)) ~pos:0 ~len:20) + if Config.Flag.wasi () + then "code" + else + Printf.sprintf + "code-%s" + (String.sub (Digest.to_hex (Digest.file tmp_wasm_file)) ~pos:0 ~len:20) in let tmp_wasm_file' = Filename.concat tmp_dir (wasm_name ^ ".wasm") in Sys.rename tmp_wasm_file tmp_wasm_file'; diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 308f8d5602..cb1eefcfea 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -1078,6 +1078,35 @@ module Generate (Target : Target_sig.S) = struct :: context.other_fields; name + let add_missing_primitives ~context l = + let failwith_desc = W.Fun { params = [ Value.value ]; result = [] } in + List.iter l ~f:(fun (exported_name, arity) -> + let name = Code.Var.fresh_n exported_name in + let locals, body = + function_body + ~context + ~param_names:[] + ~body: + (let* failwith = + register_import ~import_module:"env" ~name:"caml_failwith" failwith_desc + in + let* msg = + Constant.translate (String (exported_name ^ " not implemented")) + in + let* () = instr (CallInstr (failwith, [ msg ])) in + push Value.unit) + in + context.other_fields <- + W.Function + { name + ; exported_name = Some exported_name + ; typ = func_type arity + ; param_names = [] + ; locals + ; body + } + :: context.other_fields) + let entry_point context toplevel_fun entry_name = let typ, param_names, body = entry_point ~toplevel_fun in let locals, body = function_body ~context ~param_names ~body in @@ -1238,6 +1267,10 @@ let add_init_function = let module G = Generate (Gc_target) in G.add_init_function +let add_missing_primitives = + let module G = Generate (Gc_target) in + G.add_missing_primitives + let output ch ~context = let module G = Generate (Gc_target) in let fields = G.output ~context in diff --git a/compiler/lib-wasm/generate.mli b/compiler/lib-wasm/generate.mli index 773917310b..d183c865c0 100644 --- a/compiler/lib-wasm/generate.mli +++ b/compiler/lib-wasm/generate.mli @@ -34,6 +34,9 @@ val add_start_function : context:Code_generation.context -> Wasm_ast.var -> unit val add_init_function : context:Code_generation.context -> to_link:string list -> unit +val add_missing_primitives : + context:Code_generation.context -> (string * int) list -> unit + val output : out_channel -> context:Code_generation.context -> unit val wasm_output : out_channel -> context:Code_generation.context -> unit diff --git a/compiler/lib-wasm/link.ml b/compiler/lib-wasm/link.ml index 4178ce0b3a..037d90a8de 100644 --- a/compiler/lib-wasm/link.ml +++ b/compiler/lib-wasm/link.ml @@ -181,12 +181,13 @@ module Wasm_binary = struct let reftype ch = reftype' (input_byte ch) ch - let valtype ch = - let i = read_uint ch in + let valtype' i ch = match i with - | 0x7b | 0x7c | 0x7d | 0x7e | 0x7f -> () + | 0x7B | 0x7C | 0x7D | 0x7E | 0x7F -> () | _ -> reftype' i ch + let valtype ch = valtype' (read_uint ch) ch + let limits ch = match input_byte ch with | 0 -> ignore (read_uint ch) @@ -201,32 +202,95 @@ module Wasm_binary = struct reftype ch; limits ch + type comptype = + | Func of { arity : int } + | Struct + | Array + + let supertype ch = + match input_byte ch with + | 0 -> () + | 1 -> ignore (read_uint ch) + | _ -> assert false + + let storagetype ch = + let i = read_uint ch in + match i with + | 0x78 | 0x77 -> () + | _ -> valtype' i ch + + let fieldtype ch = + storagetype ch; + ignore (input_byte ch) + + let comptype i ch = + match i with + | 0x5E -> + fieldtype ch; + Array + | 0x5F -> + ignore (vec fieldtype ch); + Struct + | 0x60 -> + let params = vec valtype ch in + let _ = vec valtype ch in + Func { arity = List.length params } + | c -> failwith (Printf.sprintf "Unknown comptype %d" c) + + let subtype i ch = + match i with + | 0x50 -> + supertype ch; + comptype (input_byte ch) ch + | 0x4F -> + supertype ch; + comptype (input_byte ch) ch + | _ -> comptype i ch + + let rectype ch = + match input_byte ch with + | 0x4E -> vec (fun ch -> subtype (input_byte ch) ch) ch + | i -> [ subtype i ch ] + + type importdesc = + | Func of int + | Table + | Mem + | Global + | Tag + type import = { module_ : string ; name : string + ; desc : importdesc } let import ch = let module_ = name ch in let name = name ch in let d = read_uint ch in - let _ = + let desc = match d with - | 0 -> ignore (read_uint ch) - | 1 -> tabletype ch - | 2 -> memtype ch + | 0 -> Func (read_uint ch) + | 1 -> + tabletype ch; + Table + | 2 -> + memtype ch; + Mem | 3 -> let _typ = valtype ch in let _mut = input_byte ch in - () + Global | 4 -> assert (read_uint ch = 0); - ignore (read_uint ch) + ignore (read_uint ch); + Tag | _ -> Format.eprintf "Unknown import %x@." d; assert false in - { module_; name } + { module_; name; desc } let export ch = let name = name ch in @@ -256,6 +320,7 @@ module Wasm_binary = struct type interface = { imports : import list ; exports : string list + ; types : comptype array } let read_interface ch = @@ -263,7 +328,11 @@ module Wasm_binary = struct match next_section ch with | None -> i | Some s -> - if s.id = 2 + if s.id = 1 + then + find_sections + { i with types = Array.of_list (List.flatten (vec rectype ch.ch)) } + else if s.id = 2 then find_sections { i with imports = vec import ch.ch } else if s.id = 7 then { i with exports = vec export ch.ch } @@ -271,7 +340,7 @@ module Wasm_binary = struct skip_section ch s; find_sections i) in - find_sections { imports = []; exports = [] } + find_sections { imports = []; exports = []; types = [||] } let append_source_map_section ~file ~url = let ch = open_out_gen [ Open_wronly; Open_append; Open_binary ] 0o666 file in @@ -405,6 +474,13 @@ let generate_start_function ~to_link ~out_file = Generate.wasm_output ch ~context; if times () then Format.eprintf " generate start: %a@." Timer.print t1 +let generate_missing_primitives ~missing_primitives ~out_file = + Filename.gen_file out_file + @@ fun ch -> + let context = Generate.start () in + Generate.add_missing_primitives ~context missing_primitives; + Generate.wasm_output ch ~context + let output_js js = Code.Var.reset (); let b = Buffer.create 1024 in @@ -665,17 +741,20 @@ let compute_dependencies ~files_to_link ~files = let compute_missing_primitives (runtime_intf, intfs) = let provided_primitives = StringSet.of_list runtime_intf.Wasm_binary.exports in - StringSet.elements + StringMap.bindings @@ List.fold_left - ~f:(fun s { Wasm_binary.imports; _ } -> + ~f:(fun s { Wasm_binary.imports; types; _ } -> List.fold_left - ~f:(fun s { Wasm_binary.module_; name; _ } -> - if String.equal module_ "env" && not (StringSet.mem name provided_primitives) - then StringSet.add name s - else s) + ~f:(fun s { Wasm_binary.module_; name; desc } -> + match module_, desc with + | "env", Func idx when not (StringSet.mem name provided_primitives) -> ( + match types.(idx) with + | Func { arity } -> StringMap.add name arity s + | _ -> s) + | _ -> s) ~init:s imports) - ~init:StringSet.empty + ~init:StringMap.empty intfs let load_information files = @@ -711,6 +790,69 @@ let gen_dir dir f = remove_directory d_tmp; raise exc +let link_to_module ~to_link ~files_to_link ~files ~enable_source_maps:_ ~dir = + let process_file ~name ~module_name file = + Zip.with_open_in file + @@ fun z -> + let intf = + let ch, pos, len, _ = Zip.get_entry z ~name in + Wasm_binary.read_interface (Wasm_binary.from_channel ~name ch pos len) + in + ( { Wasm_link.module_name + ; file + ; code = Some (Zip.read_entry z ~name) + ; opt_source_map = None + } + , intf ) + in + let runtime_file = fst (List.hd files) in + let z = Zip.open_in runtime_file in + let runtime, runtime_intf = + process_file ~name:"runtime.wasm" ~module_name:"env" runtime_file + in + let prelude = + { Wasm_link.module_name = "OCaml" + ; file = runtime_file + ; code = Some (Zip.read_entry z ~name:"prelude.wasm") + ; opt_source_map = None + } + in + Zip.close_in z; + let lst = + List.tl files + |> List.filter_map ~f:(fun (file, _) -> + if StringSet.mem file files_to_link + then Some (process_file ~name:"code.wasm" ~module_name:"OCaml" file) + else None) + in + let missing_primitives = + if Config.Flag.genprim () + then compute_missing_primitives (runtime_intf, List.map ~f:snd lst) + else [] + in + let start_module = Filename.concat dir "start.wasm" in + generate_start_function ~to_link ~out_file:start_module; + let start = + { Wasm_link.module_name = "OCaml" + ; file = start_module + ; code = None + ; opt_source_map = None + } + in + generate_missing_primitives ~missing_primitives ~out_file:"stubs.wasm"; + let missing_primitives = + { Wasm_link.module_name = "env" + ; file = "stubs.wasm" + ; code = None + ; opt_source_map = None + } + in + ignore + (Wasm_link.f + (runtime :: prelude :: missing_primitives :: start :: List.map ~f:fst lst) + ~filter_export:(fun nm -> String.equal nm "_start" || String.equal nm "memory") + ~output_file:(Filename.concat dir "code.wasm")) + let link ~output_file ~linkall ~enable_source_maps ~files = if times () then Format.eprintf "linking@."; let t = Timer.make () in @@ -801,30 +943,35 @@ let link ~output_file ~linkall ~enable_source_maps ~files = if times () then Format.eprintf " finding what to link: %a@." Timer.print t1; if times () then Format.eprintf " scan: %a@." Timer.print t; let t = Timer.make () in - let interfaces, wasm_dir, link_spec = + let missing_primitives, wasm_dir, link_spec = let dir = Filename.chop_extension output_file ^ ".assets" in gen_dir dir @@ fun tmp_dir -> Sys.mkdir tmp_dir 0o777; - let start_module = - "start-" - ^ String.sub - (Digest.to_hex (Digest.string (String.concat ~sep:"/" to_link))) - ~pos:0 - ~len:8 - in - generate_start_function - ~to_link - ~out_file:(Filename.concat tmp_dir (start_module ^ ".wasm")); - let module_names, interfaces = - link_to_directory ~files_to_link ~files ~enable_source_maps ~dir:tmp_dir - in - ( interfaces - , dir - , let to_link = compute_dependencies ~files_to_link ~files in - List.combine module_names (None :: None :: to_link) @ [ start_module, None ] ) + if not (Config.Flag.wasi ()) + then ( + let start_module = + "start-" + ^ String.sub + (Digest.to_hex (Digest.string (String.concat ~sep:"/" to_link))) + ~pos:0 + ~len:8 + in + let module_names, interfaces = + link_to_directory ~files_to_link ~files ~enable_source_maps ~dir:tmp_dir + in + let missing_primitives = compute_missing_primitives interfaces in + generate_start_function + ~to_link + ~out_file:(Filename.concat tmp_dir (start_module ^ ".wasm")); + ( List.map ~f:fst missing_primitives + , dir + , let to_link = compute_dependencies ~files_to_link ~files in + List.combine module_names (None :: None :: to_link) @ [ start_module, None ] )) + else ( + link_to_module ~to_link ~files_to_link ~files ~enable_source_maps ~dir:tmp_dir; + [], dir, [ "code", None ]) in - let missing_primitives = compute_missing_primitives interfaces in if times () then Format.eprintf " copy wasm files: %a@." Timer.print t; let t1 = Timer.make () in let js_runtime = diff --git a/compiler/lib-wasm/link.mli b/compiler/lib-wasm/link.mli index 0c788e7d47..55424663f1 100644 --- a/compiler/lib-wasm/link.mli +++ b/compiler/lib-wasm/link.mli @@ -19,9 +19,17 @@ open Stdlib module Wasm_binary : sig + type importdesc = + | Func of int + | Table + | Mem + | Global + | Tag + type import = { module_ : string ; name : string + ; desc : importdesc } val check : contents:string -> bool diff --git a/compiler/lib-wasm/wasm_link.ml b/compiler/lib-wasm/wasm_link.ml index a53fc34a4d..b3a1939f82 100644 --- a/compiler/lib-wasm/wasm_link.ml +++ b/compiler/lib-wasm/wasm_link.ml @@ -1878,7 +1878,7 @@ type input = ; opt_source_map : Source_map.Standard.t option } -let f files ~output_file = +let f ?(filter_export = fun _ -> true) files ~output_file = let files = Array.map ~f:(fun { module_name; file; code; opt_source_map } -> @@ -2132,20 +2132,28 @@ let f files ~output_file = Array.iter ~f:Scan.clear_position_data positions; (* 7: export *) + let exports = + Array.map + ~f:(fun intf -> + map_exportable_info + (fun _ exports -> List.filter ~f:(fun (nm, _) -> filter_export nm) exports) + intf.Read.exports) + intfs + in let export_count = Array.fold_left - ~f:(fun count intf -> + ~f:(fun count exports -> fold_exportable_info (fun _ exports count -> List.length exports + count) count - intf.Read.exports) + exports) ~init:0 - intfs + exports in Write.uint buf export_count; - let exports = Hashtbl.create 128 in + let export_tbl = Hashtbl.create 128 in Array.iteri - ~f:(fun i intf -> + ~f:(fun i exports -> iter_exportable_info (fun kind lst -> let map = @@ -2158,7 +2166,7 @@ let f files ~output_file = in List.iter ~f:(fun (name, idx) -> - match Hashtbl.find exports name with + match Hashtbl.find export_tbl name with | i' -> failwith (Printf.sprintf @@ -2167,11 +2175,11 @@ let f files ~output_file = files.(i').file files.(i).file) | exception Not_found -> - Hashtbl.add exports name i; + Hashtbl.add export_tbl name i; Write.export buf kind name map.(idx)) lst) - intf.Read.exports) - intfs; + exports) + exports; add_section out_ch ~id:7 buf; (* 8: start *) diff --git a/compiler/lib-wasm/wasm_link.mli b/compiler/lib-wasm/wasm_link.mli index 0c0ed0a582..4cbd769668 100644 --- a/compiler/lib-wasm/wasm_link.mli +++ b/compiler/lib-wasm/wasm_link.mli @@ -23,4 +23,5 @@ type input = ; opt_source_map : Source_map.Standard.t option } -val f : input list -> output_file:string -> Source_map.t +val f : + ?filter_export:(string -> bool) -> input list -> output_file:string -> Source_map.t diff --git a/compiler/lib/build_info.ml b/compiler/lib/build_info.ml index 4de9956edf..b5da01bbb4 100644 --- a/compiler/lib/build_info.ml +++ b/compiler/lib/build_info.ml @@ -68,6 +68,7 @@ let create kind = | v -> Printf.sprintf "%s+%s" Compiler_version.s v in [ "use-js-string", string_of_bool (Config.Flag.use_js_string ()) + ; "wasi", string_of_bool (Config.Flag.wasi ()) ; "effects", string_of_effects_backend (Config.effects ()) ; "version", version ; "kind", string_of_kind kind @@ -139,9 +140,9 @@ let merge fname1 info1 fname2 info2 = match k, v1, v2 with | "kind", v1, v2 -> if Option.equal String.equal v1 v2 then v1 else Some (string_of_kind `Unknown) - | ("effects" | "use-js-string" | "version"), Some v1, Some v2 + | ("effects" | "use-js-string" | "wasi" | "version"), Some v1, Some v2 when String.equal v1 v2 -> Some v1 - | (("effects" | "use-js-string" | "version") as key), v1, v2 -> + | (("effects" | "use-js-string" | "wasi" | "version") as key), v1, v2 -> raise (Incompatible_build_info { key; first = fname1, v1; second = fname2, v2 }) | _, Some v1, Some v2 when String.equal v1 v2 -> Some v1 @@ -156,7 +157,7 @@ let configure t = StringMap.iter (fun k v -> match k with - | "use-js-string" -> Config.Flag.set k (bool_of_string v) + | "use-js-string" | "wasi" -> Config.Flag.set k (bool_of_string v) | "effects" -> Config.set_effects_backend (effects_backend_of_string v) | _ -> ()) t diff --git a/dune b/dune index e8b701e0d7..21183b243c 100644 --- a/dune +++ b/dune @@ -32,13 +32,18 @@ (tools/node_wrapper.exe as node.exe))) (wasi (wasm_of_ocaml + (build_runtime_flags + (:standard + --enable + wasi + (:include wasi_extra_flags))) (flags (:standard --pretty --enable wasi (:include wasi_extra_flags))) - (compilation_mode whole_program)) + (compilation_mode separate)) (binaries (tools/node_wrapper.exe as node) (tools/node_wrapper.exe as node.exe)))