From 9c04bea9ddc0a053588f1ed5d7b9f7c796335ef2 Mon Sep 17 00:00:00 2001 From: Josh Berdine Date: Wed, 8 May 2019 12:12:21 -0700 Subject: [PATCH] [sledge] Update build, setup, todo Reviewed By: mbouaziz Differential Revision: D15259933 fbshipit-source-id: 603fb5a02 --- sledge/ROADMAP.org | 115 +++++++++++++++++++++++++++++++++++++++++ sledge/SETUP.org | 51 ------------------- sledge/TODO.org | 116 ------------------------------------------ sledge/dune-workspace | 2 +- sledge/sledge.opam | 19 ++++--- sledge/test/Makefile | 2 +- 6 files changed, 129 insertions(+), 176 deletions(-) create mode 100644 sledge/ROADMAP.org delete mode 100644 sledge/SETUP.org diff --git a/sledge/ROADMAP.org b/sledge/ROADMAP.org new file mode 100644 index 000000000..6e1f4d987 --- /dev/null +++ b/sledge/ROADMAP.org @@ -0,0 +1,115 @@ +* lazy tracing +- define a [Trace.t], move global [fs] into it, and thread through code +- add a parent-pointing tree/dag of printing thunks to [Trace.t] +- use "event" and "history" terminology +- change from immediately printing to creating a closure that prints when called, and add it to the dag +- add [fork] and [join] operations on [Trace.t] +- use [Trace.fork] in [Control.exec_term], and [Trace.join] in sync with [Domain.join] (in [Control.Work.run] or wherever) +- add a form of "terminal" trace events, which prints all the ancestor events +- change [Report] (and elsewhere?) to use Trace.terminal +- support ex postfacto trace exploration + + add a global list of terminals + + add to terminals list instead of eagerly printing ancestors of terminals + + dump/Marshal trace state at exit + + add subcommand for querying dumped traces + - list terminals + - print ancestors of given terminal + + support changing enabled status ex postfacto + - record module and function names with printing thunks + - when printing, recheck [enabled] +- support incrementally writing trace data to file +- support incrementally printing history as requested, in reverse +- ? support more advanced queries +* parallelize frontend +- make a scan_types pass over all types to populate anon_struct_name, and change struct_name to only find, not add + see http://llvm.org/doxygen/ValueEnumerator_8cpp_source.html#l00321 +- [Trace.fork] a trace for each function +- replace calls to fold_left_globals and fold_left_functions with calls to parmap +- memo_type and memo_value could be put in shared memory instead + + better sharing (as much as with sequential translation) + + all their contents will live forever anyway + + would need to handle concurrent accesses + + maybe better to put entire Llair.t into shared memory + + ? shared memory = reancient + locks +* parallelize backend +- change exec_* functions to instead of transforming the worklist, to return the new jobs (each job is an edge, depth(s?), and state) + + also, change tracing so that they return new events rather than transform the whole event dag +- adapt infer's ProcessPool + + When a worker finishes its task, it writes to the "up" pipe, a message indicating that it is done, which includes the worker's id and a list of discovered jobs. Then it reads another task from its "down" pipe, which might block. Maybe it should do a slice of gc before reading. + + The orc sits in a select waiting for the "up" pipe to be non-empty. Once it receives a message that a worker has finished, it reads responses from the "up" pipe, adding the jobs sent by the workers to the queue and add the now-idle workers to the back of the queue. When the "up" pipe is empty, it iterates through the idle workers, popping the next task from the queue and writing it to the worker's "down" pipe. Then the orc loops back to waiting on the "up" pipe. If the queue empties while there are still idle workers, keep the queue and add to it on the next finish message. Maybe the orc should check the "up" pipe between writes to worker "down" pipes. + + Actually, repeatedly pop all the jobs for the same block from the queue, and send the list of states to the worker to join and execute from. + + Currently in infer the operation of selecting the task to send to the child is trivial, but IIUC it does not have to be, and the list of tasks does not need to be computed beforehand. So, leaving the basic communication structure the same, it does not seem like a big change to extend the messages from worker to orc to also include a list of tasks to add to the queue, and to have the orc receive them, add them to a priority queue, pop the highest priority task from the queue and send it to the worker. Plus some check to see if there was an idle worker that could be given one of the tasks just returned to the orc. +- initial inefficient version + + communicate blocks + - by forking workers after frontend finishes, thereby giving each worker a copy of the program + - then passing block parent name/index and block index + + but could instead, with some manual serialization code, pass blocks to/from workers over pipes + - receiver must perform a lookup to find their local copy + + communicate states using Marshal + - likely to be slow + - will proactively lose sharing of the representation + + communicate trace events by forcing printing thunks to strings +- optimize by storing program in shared memory (reancient?) + + don't need to finish translation before starting analysis + + pass block address in reancient heap instead of indices + + receiver no longer needs to perform a lookup + + saves memory, and time to copy it, and time to futilely GC it in all workers +- optimize by communicating states without Marshal + + could store them in a reancient heap and then communicate their index + - probably fast, but leaky + + could use a reancient heap for each worker, where it would store its jobs, until there is not enough space, at which point it would delete the heap and allocate a new one, passing the heap to the orc over the pipe + - this would need make a deep copy of every entry, or else deleting the heap is unsafe since there could be sharing between entries + + could perhaps have immortal heap of states appearing in function specs, try to keep sharing between communicated states and immortal ones, and take advantage of how Marshal won't follow pointers out of the GC heap to make communicated states small + + really ought to have a global hash-cons structure which workers add states to in order to communicate them + + check what flow/hack/zonc do + see fbcode/hphp/hack/src/heap/hh_shared.c + + store trace events in shared memory + - to avoid forcing them eagerly + - need a way to Marshal them from shared memory to write to file + + perhaps serially at exit: copy to GC heap and Marshal as normal + + perhaps incrementally copy oldest events from shared memory and Marshal to file +* relax global topological ordering +:PROPERTIES: +:ID: 6D6A0AF5-F68F-4726-95E5-178145A4CB9B +:END: +- needed for lazy translation and bottom-up analysis +- compute call graph (perhaps from ThinLTO info) +- topsort call graph (callee smaller number than caller) + + possible alternative might be to translate functions leaving their sort_index unset + + then set it when first encountered during analysis + + this relies on the assumption that the analysis will perform an appropriately ordered search + + this assumption needs to be checked + + this is probably only applicable for top-down analysis +- add sort_index field to func like block +- change to topsort blocks intraprocedurally +- change priority queue to use lexicographically sorted pair of func and block indices, that is, (block.parent.sort_index, block.sort_index) +- if intraprocedural top orders are insufficient + + change use of block sort_index for priority in queue + + instead of choosing a total order (represented by ints), represent the partial order itself + + build a graph with blocks as vertices and edges for non-retreating jumps + + then a < b iff there is a path from a to b + + perhaps keep the graph transitively-closed, and then a < b iff b is a successor of a + + extending such a graph can only add new ordering relationships, never change existing ones, the partial order is stable under extension, so translating code while analyzing will not break the queue + + is Fheap compatible with a partial order, rather than a total order? + + when adding just-translated code, need to add edges for all existing (non-retreating?) Call sites of added functions: will need to index them +* lazy translation +- need to [[id:6D6A0AF5-F68F-4726-95E5-178145A4CB9B][generalize to partial weak topological order]] to enable adding code during analysis without breaking the priority queue +- translate function when analyzing a Call to a declared but untranslated function +- if in ThinLTO mode, will need to worry about finding/loading bitcode: will need an index from function names to bitcode modules where they are defined (ThinLTO should have this info) +* summarization +- ? standard over-approximation, or something more in tune with refutation +- ? procedures +- ? code segments between function entry and call sites +- common points: + + summary includes + - precondition + - postcondition + - depth for which summary is "sound" assuming every worklist item has higher depth + + a summary for a given pre and depth may be incomplete (if there is an item in the worklist) + + a summary for a pre and depth may be extended with another for the same pre and depth, by disjoining the posts +* differential analysis +* start-anywhere/bottom-up analysis +* non-dnf solver +* arithmetic constraints +* overflow analysis +- Currently expressions are shared between the IR for code and the analyzer's formulas. In order to strengthen the analyzer to detect overflow, it will likely be necessary to distinguish these, letting the analyzer normalize with polynomials, while keeping the same order of operations as the source for the IR. Plus another level of transformers for the IR expressions where their abstract values could be checked for being in range, etc. diff --git a/sledge/SETUP.org b/sledge/SETUP.org deleted file mode 100644 index 40b66dc4c..000000000 --- a/sledge/SETUP.org +++ /dev/null @@ -1,51 +0,0 @@ -1. export SLEDGE=$HOME/sat/sledge -2. clone llvm, clang, and libcxxabi - - cd $SLEDGE - - git clone https://github.com/jberdine/llvm.git --branch ocaml_api - - git clone https://github.com/llvm-mirror/clang.git llvm/tools/clang - - git -C llvm/tools/clang checkout 32f603c58965543e256fdf8c3cb6eaceec2974da - - git clone https://github.com/llvm-mirror/libcxx.git llvm/projects/libcxx - - git clone https://github.com/llvm-mirror/libcxxabi.git llvm/projects/libcxxabi -3. export OPAMJOBS=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 1) -4. create opam switches - - for variant in '' '+flambda'; do opam switch --yes create sledge$variant 4.07.1+rc1$variant; done -5. install llvm deps - - install deps - + sudo yum install cmake ninja - + brew install cmake ninja - - for switch in sledge sledge+flambda; do opam install --yes --switch=$switch ctypes; done -6. build llvm & clang - - cd $SLEDGE/llvm - - mkdir _build - - cd _build - - for switch in sledge sledge+flambda; do cmake -G Ninja -DCMAKE_INSTALL_PREFIX=../_install/$switch -DLLVM_OCAML_INSTALL_PATH=../_install/$switch -DLLVM_TARGETS_TO_BUILD="X86" .. && ninja && ninja install; done -7. install deps - - cd $SLEDGE/llvm - - for switch in sledge sledge+flambda; do opam pin --switch=$switch add -n -k git llvm .; done - - cd $SLEDGE - - for switch in sledge sledge+flambda; do PATH=$SLEDGE/llvm/_install/$switch/bin:$PATH opam install --yes --switch=$switch ./sledge.opam --deps-only; done -8. hush `ld: warning: directory not found for option '-L/opt/local/lib'` - - the zarith package adds a spurious linker option unless you have both brew and macports, so if you see this linker warning when compiling, execute - + sudo mkdir -p /opt/local/lib -9. install dev tools - - opam pin --yes --switch=sledge add tools/opam -10. if needed: point new clang to xcode c++ lib - - cd $SLEDGE/$(opam switch show)/include - - ln -s /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++ . -11. llvm dev workflow - - modify llvm sources - - cd $SLEDGE/llvm/_build; ninja - - iterate - - (optional?) git add -u; git commit -m ... - - ninja && ninja ocaml_doc && ninja install && opam upgrade llvm - - cd $SLEDGE; make - - it is not uncommon to get "inconsistent assumptions" errors: clean and re-make -12. llvm emacs mode - - (add-to-list 'load-path (expand-file-name "$SLEDGE/llvm/utils/emacs")) - - (require 'llvm-mode) - - (require 'autodisass-llvm-bitcode) -13. install llair for llvm_sil - - cd $SLEDGE - - opam pin add -n -k git llair . - + when prompted, ok to create new package - - opam install llair diff --git a/sledge/TODO.org b/sledge/TODO.org index 2a07a3306..f5ba62332 100644 --- a/sledge/TODO.org +++ b/sledge/TODO.org @@ -322,119 +322,3 @@ It seems possible that two edges will be distinct only due to differences betwee ** adapt infer's dead code detection * optimization ** Control uses Var.Set for locals, but could benefit from a set with constant-time union -* roadmap -** lazy tracing -- define a [Trace.t], move global [fs] into it, and thread through code -- add a parent-pointing tree/dag of printing thunks to [Trace.t] -- use "event" and "history" terminology -- change from immediately printing to creating a closure that prints when called, and add it to the dag -- add [fork] and [join] operations on [Trace.t] -- use [Trace.fork] in [Control.exec_term], and [Trace.join] in sync with [Domain.join] (in [Control.Work.run] or wherever) -- add a form of "terminal" trace events, which prints all the ancestor events -- change [Report] (and elsewhere?) to use Trace.terminal -- support ex postfacto trace exploration - + add a global list of terminals - + add to terminals list instead of eagerly printing ancestors of terminals - + dump/Marshal trace state at exit - + add subcommand for querying dumped traces - - list terminals - - print ancestors of given terminal - + support changing enabled status ex postfacto - - record module and function names with printing thunks - - when printing, recheck [enabled] -- support incrementally writing trace data to file -- support incrementally printing history as requested, in reverse -- ? support more advanced queries -** parallelize frontend -- make a scan_types pass over all types to populate anon_struct_name, and change struct_name to only find, not add - see http://llvm.org/doxygen/ValueEnumerator_8cpp_source.html#l00321 -- [Trace.fork] a trace for each function -- replace calls to fold_left_globals and fold_left_functions with calls to parmap -- memo_type and memo_value could be put in shared memory instead - + better sharing (as much as with sequential translation) - + all their contents will live forever anyway - + would need to handle concurrent accesses - + maybe better to put entire Llair.t into shared memory - + ? shared memory = reancient + locks -** parallelize backend -- change exec_* functions to instead of transforming the worklist, to return the new jobs (each job is an edge, depth(s?), and state) - + also, change tracing so that they return new events rather than transform the whole event dag -- adapt infer's ProcessPool - + When a worker finishes its task, it writes to the "up" pipe, a message indicating that it is done, which includes the worker's id and a list of discovered jobs. Then it reads another task from its "down" pipe, which might block. Maybe it should do a slice of gc before reading. - + The orc sits in a select waiting for the "up" pipe to be non-empty. Once it receives a message that a worker has finished, it reads responses from the "up" pipe, adding the jobs sent by the workers to the queue and add the now-idle workers to the back of the queue. When the "up" pipe is empty, it iterates through the idle workers, popping the next task from the queue and writing it to the worker's "down" pipe. Then the orc loops back to waiting on the "up" pipe. If the queue empties while there are still idle workers, keep the queue and add to it on the next finish message. Maybe the orc should check the "up" pipe between writes to worker "down" pipes. - + Actually, repeatedly pop all the jobs for the same block from the queue, and send the list of states to the worker to join and execute from. - + Currently in infer the operation of selecting the task to send to the child is trivial, but IIUC it does not have to be, and the list of tasks does not need to be computed beforehand. So, leaving the basic communication structure the same, it does not seem like a big change to extend the messages from worker to orc to also include a list of tasks to add to the queue, and to have the orc receive them, add them to a priority queue, pop the highest priority task from the queue and send it to the worker. Plus some check to see if there was an idle worker that could be given one of the tasks just returned to the orc. -- initial inefficient version - + communicate blocks - - by forking workers after frontend finishes, thereby giving each worker a copy of the program - - then passing block parent name/index and block index - + but could instead, with some manual serialization code, pass blocks to/from workers over pipes - - receiver must perform a lookup to find their local copy - + communicate states using Marshal - - likely to be slow - - will proactively lose sharing of the representation - + communicate trace events by forcing printing thunks to strings -- optimize by storing program in shared memory (reancient?) - + don't need to finish translation before starting analysis - + pass block address in reancient heap instead of indices - + receiver no longer needs to perform a lookup - + saves memory, and time to copy it, and time to futilely GC it in all workers -- optimize by communicating states without Marshal - + could store them in a reancient heap and then communicate their index - - probably fast, but leaky - + could use a reancient heap for each worker, where it would store its jobs, until there is not enough space, at which point it would delete the heap and allocate a new one, passing the heap to the orc over the pipe - - this would need make a deep copy of every entry, or else deleting the heap is unsafe since there could be sharing between entries - + could perhaps have immortal heap of states appearing in function specs, try to keep sharing between communicated states and immortal ones, and take advantage of how Marshal won't follow pointers out of the GC heap to make communicated states small - + really ought to have a global hash-cons structure which workers add states to in order to communicate them - + check what flow/hack/zonc do - see fbcode/hphp/hack/src/heap/hh_shared.c - + store trace events in shared memory - - to avoid forcing them eagerly - - need a way to Marshal them from shared memory to write to file - + perhaps serially at exit: copy to GC heap and Marshal as normal - + perhaps incrementally copy oldest events from shared memory and Marshal to file -** relax global topological ordering -:PROPERTIES: -:ID: 6D6A0AF5-F68F-4726-95E5-178145A4CB9B -:END: -- needed for lazy translation and bottom-up analysis -- compute call graph (perhaps from ThinLTO info) -- topsort call graph (callee smaller number than caller) - + possible alternative might be to translate functions leaving their sort_index unset - + then set it when first encountered during analysis - + this relies on the assumption that the analysis will perform an appropriately ordered search - + this assumption needs to be checked - + this is probably only applicable for top-down analysis -- add sort_index field to func like block -- change to topsort blocks intraprocedurally -- change priority queue to use lexicographically sorted pair of func and block indices, that is, (block.parent.sort_index, block.sort_index) -- if intraprocedural top orders are insufficient - + change use of block sort_index for priority in queue - + instead of choosing a total order (represented by ints), represent the partial order itself - + build a graph with blocks as vertices and edges for non-retreating jumps - + then a < b iff there is a path from a to b - + perhaps keep the graph transitively-closed, and then a < b iff b is a successor of a - + extending such a graph can only add new ordering relationships, never change existing ones, the partial order is stable under extension, so translating code while analyzing will not break the queue - + is Fheap compatible with a partial order, rather than a total order? - + when adding just-translated code, need to add edges for all existing (non-retreating?) Call sites of added functions: will need to index them -** lazy translation -- need to [[id:6D6A0AF5-F68F-4726-95E5-178145A4CB9B][generalize to partial weak topological order]] to enable adding code during analysis without breaking the priority queue -- translate function when analyzing a Call to a declared but untranslated function -- if in ThinLTO mode, will need to worry about finding/loading bitcode: will need an index from function names to bitcode modules where they are defined (ThinLTO should have this info) -** summarization -- ? standard over-approximation, or something more in tune with refutation -- ? procedures -- ? code segments between function entry and call sites -- common points: - + summary includes - - precondition - - postcondition - - depth for which summary is "sound" assuming every worklist item has higher depth - + a summary for a given pre and depth may be incomplete (if there is an item in the worklist) - + a summary for a pre and depth may be extended with another for the same pre and depth, by disjoining the posts -** differential analysis -** start-anywhere/bottom-up analysis -** non-dnf solver -** arithmetic constraints -** overflow analysis -- Currently expressions are shared between the IR for code and the analyzer's formulas. In order to strengthen the analyzer to detect overflow, it will likely be necessary to distinguish these, letting the analyzer normalize with polynomials, while keeping the same order of operations as the source for the IR. Plus another level of transformers for the IR expressions where their abstract values could be checked for being in range, etc. diff --git a/sledge/dune-workspace b/sledge/dune-workspace index 4dc9cec0a..abe850e5d 100644 --- a/sledge/dune-workspace +++ b/sledge/dune-workspace @@ -2,4 +2,4 @@ (context (opam (switch sledge) (name dev) (merlin))) (context (opam (switch sledge) (name coverage))) -(context (opam (switch sledge+flambda) (name release))) +(context (opam (switch sledge) (name release))) diff --git a/sledge/sledge.opam b/sledge/sledge.opam index 3c89e7949..f440512f4 100644 --- a/sledge/sledge.opam +++ b/sledge/sledge.opam @@ -1,22 +1,27 @@ -opam-version: "1.2" +opam-version: "2.0" maintainer: "Josh Berdine " authors: "Josh Berdine " homepage: "https://github.com/facebook/infer/tree/master/sledge" -bug-reports: "https://github.com/facebook/infer/issues" +bug-reports: "https://github.com/facebook/infer/issues/new?template=sledge_issue_template.md" +dev-repo: "git://github.com/facebook/infer.git" +license: "MIT" build: [ [make "setup"] ["dune" "build" "-p" name "-j" jobs] ] depends: [ + "ocaml" "base" {>= "v0.12.0"} "cmdliner" - "core_kernel" {>= "v0.11.0"} + "core_kernel" "ctypes" "ctypes-foreign" - "dune" {build > "1.4.0"} - "llvm" {build & = "7.0.0"} - "ppx_compare" {>= "v0.11.0"} + "dune" {build} + "llvm" {= "8.0.0"} + "ppx_compare" "ppx_deriving_cmdliner" {>= "0.4.2"} - "ppx_hash" {>= "v0.11.0"} + "ppx_hash" "zarith" ] +synopsis: "SLEdge analyzer" +description: "SLEdge analyzer" diff --git a/sledge/test/Makefile b/sledge/test/Makefile index c445a4012..ec9f69d42 100644 --- a/sledge/test/Makefile +++ b/sledge/test/Makefile @@ -70,7 +70,7 @@ define analyze )$$?; \ case $$status in \ ( 0 | 2 ) ;; \ - ( 152 ) echo -e "RESULT: TIMEOUT" >> $(notdir $(1).$(2)).out ;; \ + ( 137 | 152 ) echo -e "RESULT: TIMEOUT" >> $(notdir $(1).$(2)).out ;; \ ( * ) echo -e "\nRESULT: Internal error: "$$status >> $(notdir $(1).$(2)).out ;; \ esac ; \ exit $$status ; \