1 // Copyright 2012-2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 * rusti - A REPL using the JIT backend
14 * Rusti works by serializing state between lines of input. This means that each
15 * line can be run in a separate task, and the only limiting factor is that all
16 * local bound variables are encodable.
18 * This is accomplished by feeding in generated input to rustc for execution in
19 * the JIT compiler. Currently input actually gets fed in three times to get
20 * information about the program.
23 * In this pass, the input is simply thrown at the parser and the input comes
24 * back. This validates the structure of the program, and at this stage the
25 * global items (fns, structs, impls, traits, etc.) are filtered from the
26 * input into the "global namespace". These declarations shadow all previous
27 * declarations of an item by the same name.
30 * After items have been stripped, the remaining input is passed to rustc
31 * along with all local variables declared (initialized to nothing). This pass
32 * runs up to typechecking. From this, we can learn about the types of each
33 * bound variable, what variables are bound, and also ensure that all the
34 * types are encodable (the input can actually be run).
37 * Finally, a program is generated to deserialize the local variable state,
38 * run the code input, and then reserialize all bindings back into a local
39 * hash map. This code is then run in the JIT engine provided by the rust
43 * Once this code runs, the input has fully been run and the hash map of local
44 * variables from TLS is read back into the local store of variables. This is
45 * then used later to pass back along to the parent rusti task and then begin
46 * waiting for input again.
49 * When running rusti code, it's important to consume ownership of the LLVM
50 * jit contextual information to prevent code from being deallocated too soon
51 * (before drop glue runs, see #7732). For this reason, the jit context is
52 * consumed and also passed along to the parent task. The parent task then
53 * keeps around all contexts while rusti is running. This must be done because
54 * tasks could in theory be spawned off and running in the background (still
57 * Encoding/decoding is done with EBML, and there is simply a map of ~str ->
58 * ~[u8] maintaining the values of each local binding (by name).
61 #[link(name = "rusti",
63 uuid = "7fb5bf52-7d45-4fee-8325-5ad3311149fc",
64 url = "https://github.com/mozilla/rust/tree/master/src/rusti")];
66 #[license = "MIT/ASL2"];
67 #[crate_type = "lib"];
73 use std::{libc, io, os, task};
77 use rustc::driver::{driver, session};
78 use rustc::back::link::jit;
79 use syntax::{ast, diagnostic};
80 use syntax::ast_util::*;
81 use syntax::parse::token;
82 use syntax::print::pprust;
91 * A structure shared across REPL instances for storing history
92 * such as statements and view items. I wish the AST was sendable.
98 lib_search_paths: ~[~str],
99 engines: ~[~jit::Engine],
104 // Action to do after reading a :command
107 action_run_line(~str),
110 /// Run an input string in a Repl, returning the new Repl.
111 fn run(mut program: ~Program, binary: ~str, lib_search_paths: ~[~str],
112 input: ~str) -> (~Program, Option<~jit::Engine>)
114 // Build some necessary rustc boilerplate for compiling things
115 let binary = binary.to_managed();
116 let options = @session::options {
117 crate_type: session::unknown_crate,
119 addl_lib_search_paths: @mut lib_search_paths.map(|p| Path(*p)),
121 .. (*session::basic_options()).clone()
123 // Because we assume that everything is encodable (and assert so), add some
124 // extra helpful information if the error crops up. Otherwise people are
125 // bound to be very confused when they find out code is running that they
127 let sess = driver::build_session(options, |cm, msg, lvl| {
128 diagnostic::emit(cm, msg, lvl);
129 if msg.contains("failed to find an implementation of trait") &&
130 msg.contains("extra::serialize::Encodable") {
132 "Currrently rusti serializes bound locals between \
133 different lines of input. This means that all \
134 values of local variables need to be encodable, \
135 and this type isn't encodable",
139 let intr = token::get_ident_interner();
142 // Stage 1: parse the input and filter it into the program (as necessary)
144 debug!("parsing: %s", input);
145 let crate = parse_input(sess, input);
146 let mut to_run = ~[]; // statements to run (emitted back into code)
147 let new_locals = @mut ~[]; // new locals being defined
148 let mut result = None; // resultant expression (to print via pp)
149 do find_main(crate, sess) |blk| {
150 // Fish out all the view items, be sure to record 'extern mod' items
151 // differently beause they must appear before all 'use' statements
152 for vi in blk.view_items.iter() {
153 let s = do with_pp(intr) |pp, _| {
154 pprust::print_view_item(pp, vi);
157 ast::view_item_extern_mod(*) => {
158 program.record_extern(s);
160 ast::view_item_use(*) => { program.record_view_item(s); }
164 // Iterate through all of the block's statements, inserting them into
165 // the correct portions of the program
166 for stmt in blk.stmts.iter() {
167 let s = do with_pp(intr) |pp, _| { pprust::print_stmt(pp, *stmt); };
169 ast::StmtDecl(d, _) => {
171 ast::DeclItem(it) => {
172 let name = sess.str_of(it.ident);
174 // Structs are treated specially because to make
175 // them at all usable they need to be decorated
176 // with #[deriving(Encoable, Decodable)]
177 ast::item_struct(*) => {
178 program.record_struct(name, s);
180 // Item declarations are hoisted out of main()
181 _ => { program.record_item(name, s); }
185 // Local declarations must be specially dealt with,
186 // record all local declarations for use later on
187 ast::DeclLocal(l) => {
188 let mutbl = l.is_mutbl;
189 do each_binding(l) |path, _| {
190 let s = do with_pp(intr) |pp, _| {
191 pprust::print_path(pp, path, false);
193 new_locals.push((s, mutbl));
200 // run statements with expressions (they have effects)
201 ast::StmtMac(*) | ast::StmtSemi(*) | ast::StmtExpr(*) => {
206 result = do blk.expr.map_move |e| {
207 do with_pp(intr) |pp, _| { pprust::print_expr(pp, e); }
210 // return fast for empty inputs
211 if to_run.len() == 0 && result.is_none() {
212 return (program, None);
216 // Stage 2: run everything up to typeck to learn the types of the new
217 // variables introduced into the program
219 info!("Learning about the new types in the program");
220 program.set_cache(); // before register_new_vars (which changes them)
221 let input = to_run.connect("\n");
222 let test = program.test_code(input, &result, *new_locals);
223 debug!("testing with ^^^^^^ %?", (||{ println(test) })());
224 let dinput = driver::str_input(test.to_managed());
225 let cfg = driver::build_configuration(sess);
227 let crate = driver::phase_1_parse_input(sess, cfg.clone(), &dinput);
228 let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
229 let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
231 // Once we're typechecked, record the types of all local variables defined
233 do find_main(crate, sess) |blk| {
234 program.register_new_vars(blk, analysis.ty_cx);
238 // Stage 3: Actually run the code in the JIT
240 info!("actually running code");
241 let code = program.code(input, &result);
242 debug!("actually running ^^^^^^ %?", (||{ println(code) })());
243 let input = driver::str_input(code.to_managed());
244 let cfg = driver::build_configuration(sess);
245 let outputs = driver::build_output_filenames(&input, &None, &None, [], sess);
246 let sess = driver::build_session(options, diagnostic::emit);
248 let crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
249 let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
250 let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
251 let trans = driver::phase_4_translate_to_llvm(sess, expanded_crate, &analysis, outputs);
252 driver::phase_5_run_llvm_passes(sess, &trans, outputs);
255 // Stage 4: Inform the program that computation is done so it can update all
256 // local variable bindings.
258 info!("cleaning up after code");
259 program.consume_cache();
262 // Stage 5: Extract the LLVM execution engine to take ownership of the
263 // generated JIT code. This means that rusti can spawn parallel
264 // tasks and we won't deallocate the code emitted until rusti
265 // itself is destroyed.
267 return (program, jit::consume_engine());
269 fn parse_input(sess: session::Session, input: &str) -> @ast::Crate {
270 let code = fmt!("fn main() {\n %s \n}", input);
271 let input = driver::str_input(code.to_managed());
272 let cfg = driver::build_configuration(sess);
273 driver::phase_1_parse_input(sess, cfg.clone(), &input)
276 fn find_main(crate: @ast::Crate, sess: session::Session,
277 f: &fn(&ast::Block)) {
278 for item in crate.module.items.iter() {
280 ast::item_fn(_, _, _, _, ref blk) => {
281 if item.ident == sess.ident_of("main") {
288 fail!("main function was expected somewhere...");
292 // Compiles a crate given by the filename as a library if the compiled
293 // version doesn't exist or is older than the source file. Binary is
294 // the name of the compiling executable. Returns Some(true) if it
295 // successfully compiled, Some(false) if the crate wasn't compiled
296 // because it already exists and is newer than the source file, or
297 // None if there were compile errors.
298 fn compile_crate(src_filename: ~str, binary: ~str) -> Option<bool> {
300 let src_path = Path(src_filename);
301 let binary = binary.to_managed();
302 let options = @session::options {
304 addl_lib_search_paths: @mut ~[os::getcwd()],
305 .. (*session::basic_options()).clone()
307 let input = driver::file_input(src_path.clone());
308 let sess = driver::build_session(options, diagnostic::emit);
309 *sess.building_library = true;
310 let cfg = driver::build_configuration(sess);
311 let outputs = driver::build_output_filenames(
312 &input, &None, &None, [], sess);
313 // If the library already exists and is newer than the source
314 // file, skip compilation and return None.
315 let mut should_compile = true;
316 let dir = os::list_dir_path(&Path(outputs.out_filename.dirname()));
317 let maybe_lib_path = do dir.iter().find |file| {
318 // The actual file's name has a hash value and version
319 // number in it which is unknown at this time, so looking
320 // for a file that matches out_filename won't work,
321 // instead we guess which file is the library by matching
322 // the prefix and suffix of out_filename to files in the
324 let file_str = file.filename().unwrap();
325 file_str.starts_with(outputs.out_filename.filestem().unwrap())
326 && file_str.ends_with(outputs.out_filename.filetype().unwrap())
328 match maybe_lib_path {
330 let (src_mtime, _) = src_path.get_mtime().unwrap();
331 let (lib_mtime, _) = lib_path.get_mtime().unwrap();
332 if lib_mtime >= src_mtime {
333 should_compile = false;
338 if (should_compile) {
339 println(fmt!("compiling %s...", src_filename));
340 let crate = driver::phase_1_parse_input(sess, cfg.clone(), &input);
341 let expanded_crate = driver::phase_2_configure_and_expand(sess, cfg, crate);
342 let analysis = driver::phase_3_run_analysis_passes(sess, expanded_crate);
343 let trans = driver::phase_4_translate_to_llvm(sess, expanded_crate, &analysis, outputs);
344 driver::phase_5_run_llvm_passes(sess, &trans, outputs);
348 Ok(true) => Some(true),
349 Ok(false) => Some(false),
354 /// Tries to get a line from rl after outputting a prompt. Returns
355 /// None if no input was read (e.g. EOF was reached).
356 fn get_line(use_rl: bool, prompt: &str) -> Option<~str> {
358 let result = rl::read(prompt);
363 rl::add_history(line);
368 if io::stdin().eof() {
371 Some(io::stdin().read_line())
376 /// Run a command, e.g. :clear, :exit, etc.
377 fn run_cmd(repl: &mut Repl, _in: @io::Reader, _out: @io::Writer,
378 cmd: ~str, args: ~[~str], use_rl: bool) -> CmdAction {
379 let mut action = action_none;
381 ~"exit" => repl.running = false,
383 repl.program.clear();
385 // XXX: Win32 version of linenoise can't do this
390 ":{\\n ..lines.. \\n:}\\n - execute multiline command\n\
391 :load <crate> ... - loads given crates as dynamic libraries\n\
392 :clear - clear the bindings\n\
393 :exit - exit from the repl\n\
394 :help - show this message");
397 let mut loaded_crates: ~[~str] = ~[];
398 for arg in args.iter() {
399 let (crate, filename) =
400 if arg.ends_with(".rs") || arg.ends_with(".rc") {
401 (arg.slice_to(arg.len() - 3).to_owned(), (*arg).clone())
403 ((*arg).clone(), *arg + ".rs")
405 match compile_crate(filename, repl.binary.clone()) {
406 Some(_) => loaded_crates.push(crate),
410 for crate in loaded_crates.iter() {
411 let crate_path = Path(*crate);
412 let crate_dir = crate_path.dirname();
413 repl.program.record_extern(fmt!("extern mod %s;", *crate));
414 if !repl.lib_search_paths.iter().any(|x| x == &crate_dir) {
415 repl.lib_search_paths.push(crate_dir);
418 if loaded_crates.is_empty() {
419 println("no crates loaded");
421 printfln!("crates loaded: %s", loaded_crates.connect(", "));
425 let mut multiline_cmd = ~"";
426 let mut end_multiline = false;
427 while (!end_multiline) {
428 match get_line(use_rl, "rusti| ") {
429 None => fail!("unterminated multiline command :{ .. :}"),
431 if line.trim() == ":}" {
432 end_multiline = true;
434 multiline_cmd.push_str(line);
435 multiline_cmd.push_char('\n');
440 action = action_run_line(multiline_cmd);
442 _ => println(~"unknown cmd: " + cmd)
447 /// Executes a line of input, which may either be rust code or a
448 /// :command. Returns a new Repl if it has changed.
449 pub fn run_line(repl: &mut Repl, input: @io::Reader, out: @io::Writer, line: ~str,
450 use_rl: bool) -> bool
452 if line.starts_with(":") {
453 // drop the : and the \n (one byte each)
454 let full = line.slice(1, line.len());
455 let split: ~[~str] = full.word_iter().map(|s| s.to_owned()).collect();
456 let len = split.len();
459 let cmd = split[0].clone();
462 let args = if len > 1 {
463 split.slice(1, len).to_owned()
466 match run_cmd(repl, input, out, cmd, args, use_rl) {
468 action_run_line(multiline_cmd) => {
469 if !multiline_cmd.is_empty() {
470 return run_line(repl, input, out, multiline_cmd, use_rl);
479 let line = Cell::new(line);
480 let program = Cell::new(repl.program.clone());
481 let lib_search_paths = Cell::new(repl.lib_search_paths.clone());
482 let binary = Cell::new(repl.binary.clone());
483 let result = do task::try {
484 run(program.take(), binary.take(), lib_search_paths.take(), line.take())
488 Ok((program, engine)) => {
489 repl.program = program;
491 Some(e) => { repl.engines.push(e); }
496 Err(*) => { return false; }
501 let args = os::args();
505 pub fn main_args(args: &[~str]) {
506 #[fixed_stack_segment]; #[inline(never)];
508 let input = io::stdin();
509 let out = io::stdout();
510 let mut repl = Repl {
512 binary: args[0].clone(),
514 lib_search_paths: ~[],
517 program: ~Program::new(),
520 let istty = unsafe { libc::isatty(libc::STDIN_FILENO as i32) } != 0;
522 // only print this stuff if the user is actually typing into rusti
524 println("WARNING: The Rust REPL is experimental and may be");
525 println("unstable. If you encounter problems, please use the");
526 println("compiler instead. Type :help for help.");
528 do rl::complete |line, suggest| {
529 if line.starts_with(":") {
539 match get_line(istty, repl.prompt) {
548 run_line(&mut repl, input, out, line, istty);
557 use program::Program;
565 lib_search_paths: ~[],
567 program: ~Program::new(),
571 // FIXME: #7220 rusti on 32bit mac doesn't work.
572 // FIXME: #7641 rusti on 32bit linux cross compile doesn't work
573 // FIXME: #7115 re-enable once LLVM has been upgraded
574 #[cfg(thiswillneverbeacfgflag)]
575 fn run_program(prog: &str) {
577 for cmd in prog.split_iter('\n') {
578 assert!(run_line(&mut r, io::stdin(), io::stdout(),
579 cmd.to_owned(), false),
580 "the command '%s' failed", cmd);
583 fn run_program(_: &str) {}
593 fn regression_5937() {
594 run_program("use std::hashmap;");
599 fn regression_5784() {
600 run_program("let a = 3;");
605 // XXX: can't spawn new tasks because the JIT code is cleaned up
606 // after the main function is done.
608 spawn( || println(\"Please don't segfault\") );
609 do spawn { println(\"Please?\"); }
615 fn inferred_integers_usable() {
616 run_program("let a = 2;\n()\n");
620 assert!((a as uint) + b == 7)
626 fn local_variables_allow_shadowing() {
642 assert!(d.len() == 0);
648 fn vectors_usable() {
654 assert!(d.len() == 9);
661 fn structs_usable() {
671 fn mutable_variables_work() {
675 let mut b = std::hashmap::HashSet::new::<int>();
677 assert!(b.contains(&5))
678 assert!(b.len() == 1)
684 fn functions_saved() {
686 fn fib(x: int) -> int { if x < 2 {x} else { fib(x - 1) + fib(x - 2) } }
697 mod b { pub fn foo() -> uint { 3 } }
698 assert!(b::foo() == 3)
704 fn multiple_functions() {
714 fn multiple_items_same_name() {
727 fn simultaneous_definition_and_expression() {
738 let result = run_line(&mut r, io::stdin(), io::stdout(),