From 5b4f3811e6d1683c30ce293b4019eb2ecb50cb09 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Mon, 29 Aug 2022 21:57:28 +0300 Subject: [PATCH] feat(tvix/eval): insert strictness points for unary/binary operators The arguments of all unary/binary operators that are built in to Nix are forced when encountered. This emits the necessary OpForce operations. Change-Id: I691fcdbebfe7586cfe217c68d44b10b1192f82d1 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6357 Tested-by: BuildkiteCI Reviewed-by: sterni --- tvix/eval/src/compiler/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tvix/eval/src/compiler/mod.rs b/tvix/eval/src/compiler/mod.rs index d34564a75..b3315ee91 100644 --- a/tvix/eval/src/compiler/mod.rs +++ b/tvix/eval/src/compiler/mod.rs @@ -217,6 +217,7 @@ impl Compiler { fn compile_unary_op(&mut self, slot: Option, op: ast::UnaryOp) { self.compile(slot, op.expr().unwrap()); + self.emit_force(); let opcode = match op.operator().unwrap() { ast::UnaryOpKind::Invert => OpCode::OpInvert, @@ -245,7 +246,10 @@ impl Compiler { // the stack in the correct order before pushing the // instruction for the operation itself. self.compile(slot, op.lhs().unwrap()); + self.emit_force(); + self.compile(slot, op.rhs().unwrap()); + self.emit_force(); match op.operator().unwrap() { BinOpKind::Add => self.chunk().push_op(OpCode::OpAdd), @@ -281,6 +285,7 @@ impl Compiler { // Leave left-hand side value on the stack. self.compile(slot, node.lhs().unwrap()); + self.emit_force(); // If this value is false, jump over the right-hand side - the // whole expression is false. @@ -291,6 +296,7 @@ impl Compiler { // of the whole expression. self.chunk().push_op(OpCode::OpPop); self.compile(slot, node.rhs().unwrap()); + self.emit_force(); self.patch_jump(end_idx); self.chunk().push_op(OpCode::OpAssertBool); @@ -305,12 +311,15 @@ impl Compiler { // Leave left-hand side value on the stack self.compile(slot, node.lhs().unwrap()); + self.emit_force(); // Opposite of above: If this value is **true**, we can // short-circuit the right-hand side. let end_idx = self.chunk().push_op(OpCode::OpJumpIfTrue(JumpOffset(0))); self.chunk().push_op(OpCode::OpPop); self.compile(slot, node.rhs().unwrap()); + self.emit_force(); + self.patch_jump(end_idx); self.chunk().push_op(OpCode::OpAssertBool); } @@ -324,12 +333,15 @@ impl Compiler { // Leave left-hand side value on the stack and invert it. self.compile(slot, node.lhs().unwrap()); + self.emit_force(); self.chunk().push_op(OpCode::OpInvert); // Exactly as `||` (because `a -> b` = `!a || b`). let end_idx = self.chunk().push_op(OpCode::OpJumpIfTrue(JumpOffset(0))); self.chunk().push_op(OpCode::OpPop); self.compile(slot, node.rhs().unwrap()); + self.emit_force(); + self.patch_jump(end_idx); self.chunk().push_op(OpCode::OpAssertBool); } @@ -1144,6 +1156,10 @@ impl Compiler { idx } + fn emit_force(&mut self) { + self.chunk().push_op(OpCode::OpForce); + } + fn emit_warning(&mut self, node: rnix::SyntaxNode, kind: WarningKind) { self.warnings.push(EvalWarning { node, kind }) } @@ -1270,7 +1286,7 @@ pub fn compile( // `OpForce`. A thunk should not be returned to the user in an // unevaluated state (though in practice, a value *containing* a // thunk might be returned). - c.chunk().push_op(OpCode::OpForce); + c.emit_force(); Ok(CompilationOutput { lambda: c.contexts.pop().unwrap().lambda,