From 7b8171ef2d03e4a2235b4bf0af255dab1b53bc25 Mon Sep 17 00:00:00 2001 From: Eric Holk Date: Tue, 17 Jul 2012 17:03:27 -0700 Subject: [PATCH] Added liveness analysis for protocols, and removed warnings about empty states. --- src/libcore/future.rs | 6 +- src/libstd/bitv.rs | 8 +++ src/libsyntax/ext/pipes.rs | 3 + src/libsyntax/ext/pipes/liveness.rs | 91 +++++++++++++++++++++++++++ src/libsyntax/ext/pipes/proto.rs | 20 ++++++ src/libsyntax/syntax.rc | 1 + src/test/run-pass/pipe-detect-term.rs | 8 +-- src/test/run-pass/pipe-peek.rs | 4 +- src/test/run-pass/pipe-select.rs | 2 - src/test/run-pass/pipe-sleep.rs | 6 +- 10 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 src/libsyntax/ext/pipes/liveness.rs diff --git a/src/libcore/future.rs b/src/libcore/future.rs index f26643a6cab4..f7efe70b5dcf 100644 --- a/src/libcore/future.rs +++ b/src/libcore/future.rs @@ -81,7 +81,7 @@ fn from_port(-port: future_pipe::client::waiting) -> future { port_ <-> *port; let port = option::unwrap(port_); alt recv(port) { - future_pipe::completed(data, _next) { #move(data) } + future_pipe::completed(data) { #move(data) } } } } @@ -135,10 +135,8 @@ fn with(future: future, blk: fn(A) -> B) -> B { proto! future_pipe { waiting:recv { - completed(T) -> terminated + completed(T) -> ! } - - terminated:send { } } #[test] diff --git a/src/libstd/bitv.rs b/src/libstd/bitv.rs index 74dadc556ebc..1a3d5e081139 100644 --- a/src/libstd/bitv.rs +++ b/src/libstd/bitv.rs @@ -253,6 +253,14 @@ impl methods for bitv { fn each(f: fn(bool) -> bool) { each(self, f) } fn each_storage(f: fn(&uint) -> bool) { each_storage(self, f) } fn eq_vec(v: ~[uint]) -> bool { eq_vec(self, v) } + + fn ones(f: fn(uint) -> bool) { + for uint::range(0, self.nbits) |i| { + if self.get(i) { + if !f(i) { break } + } + } + } } impl of to_str::to_str for bitv { diff --git a/src/libsyntax/ext/pipes.rs b/src/libsyntax/ext/pipes.rs index bea1c8cae0cb..5a1462cea309 100644 --- a/src/libsyntax/ext/pipes.rs +++ b/src/libsyntax/ext/pipes.rs @@ -27,6 +27,9 @@ fn expand_proto(cx: ext_ctxt, _sp: span, id: ast::ident, // check for errors visit(proto, cx); + // do analysis + liveness::analyze(proto, cx); + // compile base::mr_item(proto.compile(cx)) } diff --git a/src/libsyntax/ext/pipes/liveness.rs b/src/libsyntax/ext/pipes/liveness.rs new file mode 100644 index 000000000000..4f8039925202 --- /dev/null +++ b/src/libsyntax/ext/pipes/liveness.rs @@ -0,0 +1,91 @@ +/* + +Liveness analysis for protocols. This is useful for a lot of possible +optimizations. + +This analysis computes the "co-live" relationship between +states. Co-live is defined inductively as follows. + +1. u is co-live with v if u can transition to v in one message. + +2. u is co-live with v if there exists a w such that u and w are +co-live, w and v are co-live, and u and w have the same direction. + +This relationship approximates when it is safe to store two states in +the same memory location. If there is no u such u is co-live with +itself, then the protocol is bounded. + +(These assertions could use proofs) + +In addition, this analysis does reachability, to warn when we have +useless states. + +The algorithm is a fixpoint computation. For each state, we initialize +a bitvector containing whether it is co-live with each other state. At +first we use rule (1) above to set each vector. Then we iterate +updating the states using rule (2) until there are no changes. + +*/ + +import dvec::extensions; + +import std::bitv::{bitv, methods}; + +import proto::methods; +import ast_builder::empty_span; + +fn analyze(proto: protocol, _cx: ext_ctxt) { + #debug("initializing colive analysis"); + let num_states = proto.num_states(); + let colive = do (copy proto.states).map_to_vec |state| { + let bv = bitv(num_states, false); + for state.reachable |s| { + bv.set(s.id, true); + } + bv + }; + + let mut i = 0; + let mut changed = true; + while changed { + changed = false; + #debug("colive iteration %?", i); + for colive.eachi |i, this_colive| { + let this = proto.get_state_by_id(i); + for this_colive.ones |j| { + let next = proto.get_state_by_id(j); + if this.dir == next.dir { + changed = changed || this_colive.union(colive[j]); + } + } + } + i += 1; + } + + #debug("colive analysis complete"); + + // Determine if we're bounded + let mut self_live = ~[]; + for colive.eachi |i, bv| { + if bv.get(i) { + vec::push(self_live, proto.get_state_by_id(i)) + } + } + + if self_live.len() > 0 { + let states = str::connect(self_live.map(|s| *s.name), ~" "); + + #debug("protocol %s is unbounded due to loops involving: %s", + *proto.name, states); + + // Someday this will be configurable with a warning + //cx.span_warn(empty_span(), + // #fmt("protocol %s is unbounded due to loops \ + // involving these states: %s", + // *proto.name, + // states)); + } + else { + #debug("protocol %s is bounded. yay!", *proto.name); + } +} \ No newline at end of file diff --git a/src/libsyntax/ext/pipes/proto.rs b/src/libsyntax/ext/pipes/proto.rs index 1c88732adb09..e9070c79b558 100644 --- a/src/libsyntax/ext/pipes/proto.rs +++ b/src/libsyntax/ext/pipes/proto.rs @@ -55,6 +55,7 @@ impl methods for message { enum state { state_(@{ + id: uint, name: ident, dir: direction, ty_params: ~[ast::ty_param], @@ -81,6 +82,20 @@ impl methods for state { cx.ty_path_ast_builder (path(self.name).add_tys(cx.ty_vars(self.ty_params))) } + + /// Iterate over the states that can be reached in one message + /// from this state. + fn reachable(f: fn(state) -> bool) { + for self.messages.each |m| { + alt m { + message(_, _, _, some({state: id, _})) { + let state = self.proto.get_state(id); + if !f(state) { break } + } + _ { } + } + } + } } enum protocol { @@ -104,6 +119,8 @@ impl methods for protocol { self.states.find(|i| i.name == name).get() } + fn get_state_by_id(id: uint) -> state { self.states[id] } + fn has_state(name: ident) -> bool { self.states.find(|i| i.name == name) != none } @@ -113,6 +130,7 @@ impl methods for protocol { let messages = dvec(); let state = state_(@{ + id: self.states.len(), name: name, dir: dir, ty_params: ty_params, @@ -127,6 +145,8 @@ impl methods for protocol { fn filename() -> ~str { ~"proto://" + *self.name } + + fn num_states() -> uint { self.states.len() } } trait visitor { diff --git a/src/libsyntax/syntax.rc b/src/libsyntax/syntax.rc index 9750f2d5bd12..10ff0d336c2d 100644 --- a/src/libsyntax/syntax.rc +++ b/src/libsyntax/syntax.rc @@ -87,5 +87,6 @@ mod ext { mod pipec; mod proto; mod check; + mod liveness; } } diff --git a/src/test/run-pass/pipe-detect-term.rs b/src/test/run-pass/pipe-detect-term.rs index 633be6972d09..db8cde820ac4 100644 --- a/src/test/run-pass/pipe-detect-term.rs +++ b/src/test/run-pass/pipe-detect-term.rs @@ -11,16 +11,14 @@ import pipes::{try_recv, recv}; proto! oneshot { waiting:send { - signal -> signaled + signal -> ! } - - signaled:send { } } fn main() { let iotask = uv::global_loop::get(); - let c = pipes::spawn_service(oneshot::init, |p| { + pipes::spawn_service(oneshot::init, |p| { alt try_recv(p) { some(*) { fail } none { } @@ -36,8 +34,6 @@ fn main() { // Make sure the right thing happens during failure. fn failtest() { - let iotask = uv::global_loop::get(); - let (c, p) = oneshot::init(); do task::spawn_with(c) |_c| { diff --git a/src/test/run-pass/pipe-peek.rs b/src/test/run-pass/pipe-peek.rs index 9a2451cd47a0..451526274341 100644 --- a/src/test/run-pass/pipe-peek.rs +++ b/src/test/run-pass/pipe-peek.rs @@ -6,10 +6,8 @@ import std::uv; proto! oneshot { waiting:send { - signal -> signaled + signal -> ! } - - signaled:send { } } fn main() { diff --git a/src/test/run-pass/pipe-select.rs b/src/test/run-pass/pipe-select.rs index 5aab6b634b81..056b0cb477a6 100644 --- a/src/test/run-pass/pipe-select.rs +++ b/src/test/run-pass/pipe-select.rs @@ -11,8 +11,6 @@ proto! oneshot { waiting:send { signal -> ! } - - signaled:send { } } proto! stream { diff --git a/src/test/run-pass/pipe-sleep.rs b/src/test/run-pass/pipe-sleep.rs index c4b44ff0be72..3ef0f4a4a641 100644 --- a/src/test/run-pass/pipe-sleep.rs +++ b/src/test/run-pass/pipe-sleep.rs @@ -7,10 +7,8 @@ import pipes::recv; proto! oneshot { waiting:send { - signal -> signaled + signal -> ! } - - signaled:send { } } fn main() { @@ -19,7 +17,7 @@ fn main() { let c = pipes::spawn_service(oneshot::init, |p| { recv(p); }); let iotask = uv::global_loop::get(); - sleep(iotask, 5000); + sleep(iotask, 500); signal(c); } \ No newline at end of file