Adding simple bytecode versioning. (#9133)

This allows for minor version bumps for backward-compatible changes
(allowing an older vmfb to be loaded on a newer runtime) and major
version bumps (disallowing older vmfbs from being loaded on a newer
runtime).

This first version 0.0 allows all existing modules to be loaded.
Future bumps will result in errors like:
```
E D:\Dev\iree\runtime\src\iree\vm\bytecode_module.c:209: INVALID_ARGUMENT;
bytecode version mismatch; runtime supports 0.0, module has 0.1
```

Note that this only versions the bytecode - the module itself is already
versioned with flatbuffers and doesn't need any version numbers. This
will also allow us to embed bytecode in other containers (ELF/etc) and
reuse the same versioning information.
diff --git a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeEncoder.h b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeEncoder.h
index 94d49a2..0b2e4c2 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeEncoder.h
+++ b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeEncoder.h
@@ -31,6 +31,10 @@
 // Abstract encoder used for function bytecode encoding.
 class BytecodeEncoder : public VMFuncEncoder {
  public:
+  static constexpr uint32_t kVersionMajor = 0;
+  static constexpr uint32_t kVersionMinor = 0;
+  static constexpr uint32_t kVersion = (kVersionMajor << 16) | kVersionMinor;
+
   // Encodes a vm.func to bytecode and returns the result.
   // Returns None on failure.
   static Optional<EncodedBytecodeFunction> encodeFunction(
diff --git a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
index 20c05fe..17c9d28 100644
--- a/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
+++ b/compiler/src/iree/compiler/Dialect/VM/Target/Bytecode/BytecodeModuleTarget.cpp
@@ -768,6 +768,8 @@
   iree_vm_BytecodeModuleDef_rwdata_segments_add(fbb, rwdataSegmentsRef);
   iree_vm_BytecodeModuleDef_function_descriptors_add(fbb,
                                                      functionDescriptorsRef);
+  iree_vm_BytecodeModuleDef_bytecode_version_add(fbb,
+                                                 BytecodeEncoder::kVersion);
   iree_vm_BytecodeModuleDef_bytecode_data_add(fbb, bytecodeDataRef);
   iree_vm_BytecodeModuleDef_debug_database_add(fbb, debugDatabaseRef);
   iree_vm_BytecodeModuleDef_end_as_root(fbb);
diff --git a/runtime/src/iree/schemas/bytecode_module_def.fbs b/runtime/src/iree/schemas/bytecode_module_def.fbs
index df15e9e..21d5ca7 100644
--- a/runtime/src/iree/schemas/bytecode_module_def.fbs
+++ b/runtime/src/iree/schemas/bytecode_module_def.fbs
@@ -236,6 +236,11 @@
   // bytecode_data.
   function_descriptors:[FunctionDescriptor];
 
+  // Bytecode version required by the embedded bytecode data.
+  // Two 16-bit ints representing major and minor version, with minor versions
+  // being backwards compatible.
+  bytecode_version:uint32;
+
   // Bytecode contents. One large buffer containing all of the function op data.
   bytecode_data:[uint8];
 
diff --git a/runtime/src/iree/vm/bytecode_module.c b/runtime/src/iree/vm/bytecode_module.c
index 192464d..338043c 100644
--- a/runtime/src/iree/vm/bytecode_module.c
+++ b/runtime/src/iree/vm/bytecode_module.c
@@ -196,6 +196,22 @@
     }
   }
 
+  // Verify that we can properly handle the bytecode embedded in the module.
+  // We require that major versions match and allow loading of older minor
+  // versions (we keep changes backwards-compatible).
+  const uint32_t bytecode_version =
+      iree_vm_BytecodeModuleDef_bytecode_version(module_def);
+  const uint32_t bytecode_version_major = bytecode_version >> 16;
+  const uint32_t bytecode_version_minor = bytecode_version & 0xFFFF;
+  if ((bytecode_version_major != IREE_VM_BYTECODE_VERSION_MAJOR) ||
+      (bytecode_version_minor > IREE_VM_BYTECODE_VERSION_MINOR)) {
+    return iree_make_status(
+        IREE_STATUS_INVALID_ARGUMENT,
+        "bytecode version mismatch; runtime supports %d.%d, module has %d.%d",
+        IREE_VM_BYTECODE_VERSION_MAJOR, IREE_VM_BYTECODE_VERSION_MINOR,
+        bytecode_version_major, bytecode_version_minor);
+  }
+
   flatbuffers_uint8_vec_t bytecode_data =
       iree_vm_BytecodeModuleDef_bytecode_data(module_def);
   for (size_t i = 0;
diff --git a/runtime/src/iree/vm/bytecode_module_impl.h b/runtime/src/iree/vm/bytecode_module_impl.h
index 01031b1..75fadb5 100644
--- a/runtime/src/iree/vm/bytecode_module_impl.h
+++ b/runtime/src/iree/vm/bytecode_module_impl.h
@@ -30,6 +30,15 @@
 #define VMMAX(a, b) (((a) > (b)) ? (a) : (b))
 #define VMMIN(a, b) (((a) < (b)) ? (a) : (b))
 
+// Major bytecode version; mismatches on this will fail in either direction.
+// This allows coarse versioning of completely incompatible versions.
+#define IREE_VM_BYTECODE_VERSION_MAJOR 0
+// Minor bytecode version; lower versions are allowed to enable newer runtimes
+// to load older serialized files when there are backwards-compatible changes.
+// Higher versions are disallowed as they occur when new ops are added that
+// otherwise cannot be executed by older runtimes.
+#define IREE_VM_BYTECODE_VERSION_MINOR 0
+
 // Maximum register count per bank.
 // This determines the bits required to reference registers in the VM bytecode.
 #define IREE_I32_REGISTER_COUNT 0x7FFF