From 1f40f3ce157f518ef03474d1408b08cd7c50e994 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Sat, 20 Mar 2021 00:41:52 +0000 Subject: [PATCH] compiler: Map host numpy.bool_ values to TBool Since we don't implement any integer-like operations for TBool (addition, bitwise not, etc.), TBool is currently neither strictly equivalent to builtin bool nor numpy.bool_, but through very obvious compiler errors (operation not supported) rather than silently different runtime behaviour. Just mapping both to TBool thus is a huge improvement over the current behaviour (where numpy.False_ is a true-like object). In the future, we could still implement more operations for TBool, presumably following numpy.bool_ rather than the builtin type, just like builtin integers get translated to the numpy-like TInt{32,64}. GitHub: Fixes #1275. --- artiq/compiler/embedding.py | 7 ++--- .../compiler/transforms/llvm_ir_generator.py | 3 ++- artiq/test/coredevice/test_embedding.py | 26 +++++++++++++++++++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index c39c7a086..b3f9c6d98 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -162,10 +162,11 @@ class ASTSynthesizer: typ = builtins.TNone() return asttyped.NameConstantT(value=value, type=typ, loc=self._add(repr(value))) - elif value is True or value is False: + elif isinstance(value, (bool, numpy.bool_)): typ = builtins.TBool() - return asttyped.NameConstantT(value=value, type=typ, - loc=self._add(repr(value))) + coerced = bool(value) + return asttyped.NameConstantT(value=coerced, type=typ, + loc=self._add(repr(coerced))) elif value is numpy.float: typ = builtins.fn_float() return asttyped.NameConstantT(value=None, type=typ, diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 7ba51cb08..4dfb37cc7 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -1558,7 +1558,8 @@ class LLVMIRGenerator: return ll.Constant.literal_struct([]) elif builtins.is_bool(typ): assert value in (True, False), fail_msg - return ll.Constant(llty, value) + # Explicitly cast to bool to handle numpy.bool_. + return ll.Constant(llty, bool(value)) elif builtins.is_int(typ): assert isinstance(value, (int, numpy.int32, numpy.int64)), fail_msg return ll.Constant(llty, int(value)) diff --git a/artiq/test/coredevice/test_embedding.py b/artiq/test/coredevice/test_embedding.py index 275328d02..dbe07b85f 100644 --- a/artiq/test/coredevice/test_embedding.py +++ b/artiq/test/coredevice/test_embedding.py @@ -36,6 +36,12 @@ class RoundtripTest(ExperimentCase): self.assertRoundtrip(True) self.assertRoundtrip(False) + def test_numpy_bool(self): + # These won't return as numpy.bool_, but the bare-Python results should still + # compare equal. + self.assertRoundtrip(numpy.True_) + self.assertRoundtrip(numpy.False_) + def test_int(self): self.assertRoundtrip(numpy.int32(42)) self.assertRoundtrip(numpy.int64(42)) @@ -492,3 +498,23 @@ class AssertTest(ExperimentCase): check_fail(lambda: exp.check(False), "AssertionError") exp.check_msg(True) check_fail(lambda: exp.check_msg(False), "foo") + + +class _NumpyBool(EnvExperiment): + def build(self): + self.setattr_device("core") + self.np_true = numpy.True_ + self.np_false = numpy.False_ + + @kernel + def run(self): + assert self.np_true + assert self.np_true == True + assert not self.np_false + assert self.np_false == False + + +class NumpyBoolTest(ExperimentCase): + def test_numpy_bool(self): + """Test NumPy bools decay to ARTIQ compiler builtin bools as expected""" + self.create(_NumpyBool).run()