Summary: Change command line interface to include buck and llvm integration as separate subcommands. Reviewed By: kren1 Differential Revision: D15614567 fbshipit-source-id: b7618571bmaster
parent
bb9f44dee2
commit
6a2da2acc4
@ -0,0 +1,212 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
(** sledge-buck integration *)
|
||||
|
||||
module Process = Shexp_process
|
||||
open Process.Infix
|
||||
|
||||
(* directory relative to buck root containing llvm binaries *)
|
||||
let llvm_bin = lazy (Option.value (Config.find "llvm-bin-dir") ~default:"")
|
||||
|
||||
(* buck build mode to specify an LTO build *)
|
||||
let mode = lazy (Config.find_exn "buck-build-mode")
|
||||
|
||||
(* resolve relative path wrt root *)
|
||||
let make_absolute root path =
|
||||
if Filename.is_relative path then Filename.concat root path else path
|
||||
|
||||
(* initial working directory *)
|
||||
let cwd = Unix.getcwd ()
|
||||
|
||||
(* query buck for root *)
|
||||
let buck_root =
|
||||
let open Process in
|
||||
lazy (String.strip (eval (run "buck" ["root"] |- read_all)))
|
||||
|
||||
(* use buck root for working directory *)
|
||||
let context () =
|
||||
let open Process in
|
||||
Context.create ~cwd:(Path (Lazy.force buck_root)) ()
|
||||
|
||||
(* invoke the LTO build for the target *)
|
||||
let buck_build ~context target =
|
||||
let open Process in
|
||||
eval ~context (run "buck" ["build"; "@mode/" ^ Lazy.force mode; target])
|
||||
|
||||
(* split a fully-qualified buck target into file and rule *)
|
||||
let parse_target target =
|
||||
try Scanf.sscanf target "//%s@:%s" (fun file rule -> (file, rule))
|
||||
with Scanf.Scan_failure _ | End_of_file ->
|
||||
fail "could not parse target: %s" target ()
|
||||
|
||||
(* compute the filename of the linker.argsfile for the target *)
|
||||
let argsfile ~context target =
|
||||
let file, rule = parse_target target in
|
||||
Printf.sprintf "%s/buck-out/%s/bin/%s/%s#binary/linker.argsfile"
|
||||
Process.(eval ~context cwd_logical)
|
||||
(Lazy.force mode) file rule
|
||||
|
||||
(* add a file to the list of modules, if it exists *)
|
||||
let add_module ?archive_name ~context file rev_modules =
|
||||
let open Process in
|
||||
if eval ~context (file_exists file) then file :: rev_modules
|
||||
else (
|
||||
( match archive_name with
|
||||
| None -> warn "%s doesn't exist" file ()
|
||||
| Some archive_name ->
|
||||
warn "%s doesn't exist in archive: %s" file
|
||||
(Filename.basename archive_name)
|
||||
() ) ;
|
||||
rev_modules )
|
||||
|
||||
(* use llvm-ar to get toc of an archive *)
|
||||
let expand_thin_archive ~context archive_name rev_modules =
|
||||
let open Process in
|
||||
eval ~context
|
||||
( run (Lazy.force llvm_bin ^ "llvm-ar") ["t"; archive_name]
|
||||
|- fold_lines ~init:rev_modules ~f:(fun rev_modules line ->
|
||||
return (add_module ~archive_name ~context line rev_modules) ) )
|
||||
|
||||
(* Use llvm-ar to check if the archive contains any bitcode files; if it
|
||||
does, fail for now as it doesn't seem to happen. *)
|
||||
let expand_arch_archive ~context archive_name =
|
||||
let number_of_bitcode_files =
|
||||
let open Process in
|
||||
eval ~context
|
||||
( run (Lazy.force llvm_bin ^ "llvm-ar") ["t"; archive_name]
|
||||
|- fold_lines ~init:0 ~f:(fun acc name ->
|
||||
return
|
||||
(let _, is_bc =
|
||||
eval ~context
|
||||
(* Need to run_exit_status, because otherwise llvm-ar
|
||||
gets -8 signal; which is strange. Repeating the
|
||||
command in bash doesn't signal and this works. *)
|
||||
( run_exit_status
|
||||
(Lazy.force llvm_bin ^ "llvm-ar")
|
||||
["p"; archive_name; name]
|
||||
|+ run "head" ["-c2"]
|
||||
|+ read_all )
|
||||
in
|
||||
if String.equal is_bc "BC" then (
|
||||
warn "found bc file %s in %s" name archive_name () ;
|
||||
acc + 1 )
|
||||
else acc) ) )
|
||||
in
|
||||
number_of_bitcode_files = 0
|
||||
|| fail "found %d bitcode files in archive %s" number_of_bitcode_files
|
||||
archive_name ()
|
||||
|
||||
(* find bitcode module(s) in a linker arg *)
|
||||
let parse_linker_arg ~context rev_modules arg =
|
||||
if String.is_suffix arg ~suffix:".o" then
|
||||
add_module ~context arg rev_modules
|
||||
else if String.is_suffix arg ~suffix:".a" then
|
||||
let thin_archive =
|
||||
String.strip Process.(eval (run "head" ["-1"; arg] |- read_all))
|
||||
in
|
||||
if String.equal thin_archive "!<thin>" then
|
||||
expand_thin_archive ~context arg rev_modules
|
||||
else if String.equal thin_archive "!<arch>" then (
|
||||
assert (expand_arch_archive ~context arg) ;
|
||||
rev_modules )
|
||||
else fail "unknown type of archive file %s" thin_archive ()
|
||||
else rev_modules
|
||||
|
||||
(* build target and find constituent bitcode modules *)
|
||||
let bitcode_files_of ~target =
|
||||
let context = context () in
|
||||
buck_build ~context target ;
|
||||
let modules =
|
||||
In_channel.with_file
|
||||
(argsfile ~context target)
|
||||
~f:(In_channel.fold_lines ~init:[] ~f:(parse_linker_arg ~context))
|
||||
|> List.rev
|
||||
in
|
||||
List.map ~f:(make_absolute (Lazy.force buck_root)) modules
|
||||
|
||||
(* link and optimize the modules *)
|
||||
let llvm_link_opt ~output modules =
|
||||
let context = context () in
|
||||
let open Process in
|
||||
eval ~context
|
||||
( run
|
||||
(Lazy.force llvm_bin ^ "llvm-link")
|
||||
( "-internalize" :: "-internalize-public-api-list=main" :: "-o=-"
|
||||
:: modules )
|
||||
|- run (Lazy.force llvm_bin ^ "opt") ["-o=" ^ output; "-globaldce"] )
|
||||
|
||||
(** command line interface *)
|
||||
|
||||
open Command.Let_syntax
|
||||
|
||||
let ( |*> ) x f = x |> Command.Param.apply f
|
||||
|
||||
let abs_path_arg =
|
||||
Command.Param.(Arg_type.map string ~f:(make_absolute cwd))
|
||||
|
||||
let main ~(command : unit Command.basic_command) ~analyze =
|
||||
let target_flag = Command.Param.(anon ("<target>" %: string)) in
|
||||
let bitcode_inputs =
|
||||
let%map_open target = target_flag
|
||||
and output =
|
||||
flag "output-modules" (optional string)
|
||||
~doc:"<file> write list of bitcode files to <file>"
|
||||
in
|
||||
let bitcode_files = bitcode_files_of ~target in
|
||||
( match output with
|
||||
| Some file -> Out_channel.write_lines file bitcode_files
|
||||
| None ->
|
||||
Format.printf "%a"
|
||||
(List.pp " " Format.pp_print_string)
|
||||
bitcode_files ) ;
|
||||
bitcode_files
|
||||
in
|
||||
let bitcode_cmd =
|
||||
let summary = "report bitcode files in buck target" in
|
||||
let readme () =
|
||||
"Build a buck target and report the included bitcode files."
|
||||
in
|
||||
let param = bitcode_inputs >>| fun _ () -> () in
|
||||
command ~summary ~readme param
|
||||
in
|
||||
let analyze_cmd =
|
||||
let summary = "analyze buck target" in
|
||||
let readme () =
|
||||
"Analyze code in a buck target. This is a convenience wrapper for \
|
||||
the sequence `sledge buck bitcode`; `sledge llvm translate`; \
|
||||
`sledge analyze`."
|
||||
in
|
||||
let param = bitcode_inputs |*> analyze in
|
||||
command ~summary ~readme param
|
||||
in
|
||||
let link_cmd =
|
||||
let summary = "link buck target to LLVM bitcode" in
|
||||
let readme () =
|
||||
"Link code in a buck target to a single LLVM bitcode module. This \
|
||||
also internalizes all symbols except `main` and removes dead code."
|
||||
in
|
||||
let param =
|
||||
let%map_open target = target_flag
|
||||
and output =
|
||||
flag "output" (required abs_path_arg)
|
||||
~doc:"<file> write linked output to <file>"
|
||||
in
|
||||
fun () -> llvm_link_opt ~output (bitcode_files_of ~target)
|
||||
in
|
||||
command ~summary ~readme param
|
||||
in
|
||||
let summary = "integration with Buck" in
|
||||
let readme () =
|
||||
"Code can be provided by a buck build target, such as \
|
||||
//fully/qualified/build:target. The mechanism used to integrate with \
|
||||
buck uses the arguments passed to the linker, so the target must \
|
||||
specify a binary that will be linked, not for instance a library \
|
||||
archive."
|
||||
in
|
||||
Command.group ~summary ~readme ~preserve_subcommand_order:()
|
||||
[("analyze", analyze_cmd); ("bitcode", bitcode_cmd); ("link", link_cmd)]
|
@ -0,0 +1,11 @@
|
||||
(*
|
||||
* Copyright (c) 2019-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*)
|
||||
|
||||
val main :
|
||||
command:unit Command.basic_command
|
||||
-> analyze:(string list -> unit -> unit) Command.Param.t
|
||||
-> Command.t
|
Loading…
Reference in new issue