Repository: facebookresearch/CompilerGym

Issue:
Observation 'Bitcode' without going through a temporary file.
## 🚀 Feature

Have an observation which just returns the bytes of the bitcode, rather than the file. Going through a temporary file is a (tiny) bit annoying.

```
env.observation["Bitcode"] -> bytes
env.bitcode == env.observation["Bitcode"]
```
## Motivation

I'm passing bitcode to LLVM tools. They will accept bitcode on stdin and emit it to stdout. No need for a temporary file.

## Pitch

Don't use a file when a `bytes` will do? 

## Alternatives

Keep using a file.



Discussion:
Yep, this is a good idea. Happy to take this if you want, will only take a few min.

Cheers,
Chris

Resolving patch:
diff --git a/compiler_gym/spaces/sequence.py b/compiler_gym/spaces/sequence.py
--- a/compiler_gym/spaces/sequence.py
+++ b/compiler_gym/spaces/sequence.py
@@ -79,9 +79,18 @@ def contains(self, x):
         upper_bound = float("inf") if self.size_range[1] is None else self.size_range[1]
         if not (lower_bound <= len(x) <= upper_bound):
             return False
-        for element in x:
-            if not isinstance(element, self.dtype):
+
+        # TODO(cummins): The dtype API is inconsistent. When dtype=str or
+        # dtype=bytes, we expect this to be the type of the entire sequence. But
+        # for dtype=int, we expect this to be the type of each element. We
+        # should distinguish these differences better.
+        if self.dtype in {str, bytes}:
+            if not isinstance(x, self.dtype):
                 return False
+        else:
+            for element in x:
+                if not isinstance(element, self.dtype):
+                    return False
 
         # Run the bounds check on every scalar element, if there is a scalar
         # range specified.


Test patch:
diff --git a/tests/llvm/observation_spaces_test.py b/tests/llvm/observation_spaces_test.py
--- a/tests/llvm/observation_spaces_test.py
+++ b/tests/llvm/observation_spaces_test.py
@@ -41,6 +41,7 @@ def test_observation_spaces(env: LlvmEnv):
     assert set(env.observation.spaces.keys()) == {
         "Ir",
         "IrSha1",
+        "Bitcode",
         "BitcodeFile",
         "InstCount",
         "InstCountDict",
@@ -105,12 +106,31 @@ def test_ir_sha1_observation_space(env: LlvmEnv):
 
 
 def test_bitcode_observation_space(env: LlvmEnv):
+    env.reset("cbench-v1/crc32")
+    key = "Bitcode"
+    space = env.observation.spaces[key]
+    assert isinstance(space.space, Sequence)
+    assert space.space.dtype == bytes
+    assert space.space.size_range == (0, None)
+
+    assert space.deterministic
+    assert not space.platform_dependent
+
+    value: str = env.observation[key]
+    print(value)  # For debugging in case of error.
+    assert isinstance(value, bytes)
+    assert space.space.contains(value)
+
+
+def test_bitcode_file_observation_space(env: LlvmEnv):
     env.reset("cbench-v1/crc32")
     key = "BitcodeFile"
     space = env.observation.spaces[key]
     assert isinstance(space.space, Sequence)
     assert space.space.dtype == str
     assert space.space.size_range == (0, 4096)
+    assert not space.deterministic
+    assert not space.platform_dependent
 
     value: str = env.observation[key]
     print(value)  # For debugging in case of error.
@@ -121,8 +141,24 @@ def test_bitcode_observation_space(env: LlvmEnv):
     finally:
         os.unlink(value)
 
-    assert not space.deterministic
-    assert not space.platform_dependent
+
+@pytest.mark.parametrize(
+    "benchmark_uri", ["cbench-v1/crc32", "cbench-v1/qsort", "cbench-v1/gsm"]
+)
+def test_bitcode_file_equivalence(env: LlvmEnv, benchmark_uri: str):
+    """Test that LLVM produces the same bitcode as a file and as a byte array."""
+    env.reset(benchmark=benchmark_uri)
+
+    bitcode = env.observation.Bitcode()
+    bitcode_file = env.observation.BitcodeFile()
+
+    try:
+        with open(bitcode_file, "rb") as f:
+            bitcode_from_file = f.read()
+
+        assert bitcode == bitcode_from_file
+    finally:
+        os.unlink(bitcode_file)
 
 
 # The Autophase feature vector for benchmark://cbench-v1/crc32 in its initial
diff --git a/tests/spaces/sequence_test.py b/tests/spaces/sequence_test.py
--- a/tests/spaces/sequence_test.py
+++ b/tests/spaces/sequence_test.py
@@ -54,5 +54,12 @@ def test_contains_with_float_scalar_range():
     assert not space.contains([0.0, 0])  # wrong shape
 
 
+def test_bytes_contains():
+    space = Sequence(size_range=(0, None), dtype=bytes)
+    assert space.contains(b"Hello, world!")
+    assert space.contains(b"")
+    assert not space.contains("Hello, world!")
+
+
 if __name__ == "__main__":
     main()