From 36d8147e8e39566f5c1f242a6b5f85e4413e4267 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 11 Aug 2019 20:20:15 +0200 Subject: [PATCH] Improve the situation regarding boolean operations. --- bytecode/src/bytecode.rs | 18 +- compiler/src/compile.rs | 185 ++++++++++++-------- compiler/src/symboltable.rs | 5 +- parser/src/ast.rs | 3 +- parser/src/python.lalrpop | 30 +++- tests/snippets/short_circuit_evaluations.py | 15 ++ vm/src/frame.rs | 24 ++- vm/src/stdlib/ast.rs | 7 +- 8 files changed, 191 insertions(+), 96 deletions(-) create mode 100644 tests/snippets/short_circuit_evaluations.py diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs index 0d5008b8393..2c3d4316364 100644 --- a/bytecode/src/bytecode.rs +++ b/bytecode/src/bytecode.rs @@ -138,12 +138,24 @@ pub enum Instruction { Jump { target: Label, }, - JumpIf { + /// Pop the top of the stack, and jump if this value is true. + JumpIfTrue { target: Label, }, + /// Pop the top of the stack, and jump if this value is false. JumpIfFalse { target: Label, }, + /// Peek at the top of the stack, and jump if this value is true. + /// Otherwise, pop top of stack. + JumpIfTrueOrPop { + target: Label, + }, + /// Peek at the top of the stack, and jump if this value is false. + /// Otherwise, pop top of stack. + JumpIfFalseOrPop { + target: Label, + }, MakeFunction { flags: FunctionOpArg, }, @@ -411,8 +423,10 @@ impl Instruction { Continue => w!(Continue), Break => w!(Break), Jump { target } => w!(Jump, label_map[target]), - JumpIf { target } => w!(JumpIf, label_map[target]), + JumpIfTrue { target } => w!(JumpIfTrue, label_map[target]), JumpIfFalse { target } => w!(JumpIfFalse, label_map[target]), + JumpIfTrueOrPop { target } => w!(JumpIfTrueOrPop, label_map[target]), + JumpIfFalseOrPop { target } => w!(JumpIfFalseOrPop, label_map[target]), MakeFunction { flags } => w!(MakeFunction, format!("{:?}", flags)), CallFunction { typ } => w!(CallFunction, format!("{:?}", typ)), ForIter { target } => w!(ForIter, label_map[target]), diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index a13d3617e83..a0f20c1e08e 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -15,6 +15,7 @@ use rustpython_parser::{ast, parser}; type BasicOutputStream = PeepholeOptimizer; +/// Main structure holding the state of compilation. struct Compiler { output_stack: Vec, scope_stack: Vec, @@ -107,12 +108,6 @@ pub enum Mode { Single, } -#[derive(Clone, Copy)] -enum EvalContext { - Statement, - Expression, -} - pub(crate) type Label = usize; impl Default for Compiler @@ -350,14 +345,14 @@ impl Compiler { match orelse { None => { // Only if: - self.compile_test(test, None, Some(end_label), EvalContext::Statement)?; + self.compile_jump_if(test, false, end_label)?; self.compile_statements(body)?; self.set_label(end_label); } Some(statements) => { // if - else: let else_label = self.new_label(); - self.compile_test(test, None, Some(else_label), EvalContext::Statement)?; + self.compile_jump_if(test, false, else_label)?; self.compile_statements(body)?; self.emit(Instruction::Jump { target: end_label }); @@ -459,7 +454,7 @@ impl Compiler { // if some flag, ignore all assert statements! if self.optimize == 0 { let end_label = self.new_label(); - self.compile_test(test, Some(end_label), None, EvalContext::Statement)?; + self.compile_jump_if(test, true, end_label)?; self.emit(Instruction::LoadName { name: String::from("AssertionError"), scope: bytecode::NameScope::Local, @@ -1006,7 +1001,7 @@ impl Compiler { self.set_label(start_label); - self.compile_test(test, None, Some(else_label), EvalContext::Statement)?; + self.compile_jump_if(test, false, else_label)?; let was_in_loop = self.in_loop; self.in_loop = true; @@ -1118,12 +1113,9 @@ impl Compiler { }); // if comparison result is false, we break with this value; if true, try the next one. - // (CPython compresses these three opcodes into JUMP_IF_FALSE_OR_POP) - self.emit(Instruction::Duplicate); - self.emit(Instruction::JumpIfFalse { + self.emit(Instruction::JumpIfFalseOrPop { target: break_label, }); - self.emit(Instruction::Pop); } // handle the last comparison @@ -1256,66 +1248,120 @@ impl Compiler { self.emit(Instruction::BinaryOperation { op: i, inplace }); } - fn compile_test( + /// Implement boolean short circuit evaluation logic. + /// https://en.wikipedia.org/wiki/Short-circuit_evaluation + /// + /// This means, in a boolean statement 'x and y' the variable y will + /// not be evaluated when x is false. + /// + /// The idea is to jump to a label if the expression is either true or false + /// (indicated by the condition parameter). + fn compile_jump_if( &mut self, expression: &ast::Expression, - true_label: Option