You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by GitBox <gi...@apache.org> on 2020/07/01 21:08:12 UTC

[GitHub] [incubator-tvm] weberlo commented on a change in pull request #5921: µTVM CRT modifications for on-device RPC server

weberlo commented on a change in pull request #5921:
URL: https://github.com/apache/incubator-tvm/pull/5921#discussion_r448588346



##########
File path: include/tvm/runtime/crt/func_registry.h
##########
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * \file include/tvm/runtime/crt/func_registry.h
+ * \brief Defines generic string-based function lookup structs
+ */
+#ifndef TVM_RUNTIME_CRT_FUNC_REGISTRY_H_
+#define TVM_RUNTIME_CRT_FUNC_REGISTRY_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <tvm/runtime/c_backend_api.h>
+#include <tvm/runtime/crt/error_codes.h>
+
+typedef uint16_t tvm_function_index_t;
+
+typedef uint16_t tvm_module_index_t;
+
+/*!
+ * \brief A data structure that facilitates function lookup by C-string name.
+ */
+typedef struct TVMFuncRegistry {
+  /*! \brief Names of registered functions, concatenated together and separated by \0.
+   * An additional \0 is present at the end of the concatenated blob to mark the end.
+   *
+   * Byte 0 is the number of functions in `funcs`.
+   */
+  const char* names;
+
+  /*! \brief Function pointers, in the same order as their names in `names`. */
+  const TVMBackendPackedCFunc* funcs;
+} TVMFuncRegistry;
+
+/*!
+ * \brief Get packed function from registry by name.
+ *
+ * \param reg TVMFunctionRegistry instance that contains the function.
+, * \param name The function name
+ * \param function_index Pointer to receive the 0-based index of the function in the registry, if it
+ *     was found. Unmodified otherwise.
+ * \return kTvmErrorNoError when successful. kTvmErrorFunctionNameNotFound when no function matched
+`name`.
+ */
+tvm_crt_error_t TVMFuncRegistry_Lookup(const TVMFuncRegistry* reg, const char* name,
+                                       tvm_function_index_t* function_index);
+
+/*!
+ * \brief Fetch TVMBackendPackedCFunc given a function index
+ *
+ * \param reg TVMFunctionRegistry instance that contains the function.
+ * \param index Index of the function.
+ * \param out_func Pointer which receives the function pointer at `index`, if a valid
+ *      index was given. Unmodified otherwise.
+ * \return kTvmErrorNoError when successful. kTvmErrorFunctionIndexInvalid when index was out of
+ * range.
+ */
+tvm_crt_error_t TVMFuncRegistry_GetByIndex(const TVMFuncRegistry* reg, tvm_function_index_t index,
+                                           TVMBackendPackedCFunc* out_func);
+
+/*!
+ * \brief A TVMFuncRegistry that supports adding and changing the functions.
+ */
+typedef struct TVMMutableFuncRegistry {
+  TVMFuncRegistry registry;
+
+  /*! \brief maximum number of functions in this registry. */
+  size_t max_functions;
+} TVMMutableFuncRegistry;
+
+/*!
+ * \brief Size of an average function name in a TVMMutableFuncRegistry, in bytes.
+ *
+ * This is just an assumption made by the runtime for ease of use.
+ */
+static const size_t kTvmAverageFunctionNameSizeBytes = 10;
+
+/*!
+ * \brief Size of an average entry in a TVMMutableFuncRegistry, in bytes.
+ *
+ * Assumes a constant average function name length.
+ */
+static const size_t kTvmAverageFuncEntrySizeBytes =
+    kTvmAverageFunctionNameSizeBytes + 1 + sizeof(void*);

Review comment:
       why the `+ 1` here?  if it's for the null terminator, should that be in the const above?

##########
File path: src/runtime/crt/common/crt_runtime_api.c
##########
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// LINT_C_FILE
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/crt/func_registry.h>
+#include <tvm/runtime/crt/internal/common/ndarray.h>
+#include <tvm/runtime/crt/internal/common/packed_func.h>
+#include <tvm/runtime/crt/internal/graph_runtime/graph_runtime.h>
+#include <tvm/runtime/crt/memory.h>
+#include <tvm/runtime/crt/platform.h>
+
+// Handle internal errors
+
+static char g_last_error[1024];
+
+void TVMAPISetLastError(const char* msg) { strncpy(g_last_error, msg, sizeof(g_last_error)); }
+
+__attribute__((format(printf, 1, 2))) int TVMAPIErrorf(const char* msg, ...) {
+  va_list args;
+  int to_return;
+
+  va_start(args, msg);
+  to_return = vsnprintf(g_last_error, sizeof(g_last_error), msg, args);
+  va_end(args);
+
+  return to_return;
+}
+
+const char* TVMGetLastError(void) { return g_last_error; }
+
+// Manipulate NDArray on target device
+
+int TVMArrayAlloc(const tvm_index_t* shape, int ndim, int dtype_code, int dtype_bits,
+                  int dtype_lanes, int device_type, int device_id, TVMArrayHandle* out) {
+  DLDataType dtype;
+  dtype.code = dtype_code;
+  dtype.bits = dtype_bits;
+  dtype.lanes = dtype_lanes;
+  DLContext ctx;
+  ctx.device_type = (DLDeviceType)device_type;
+  ctx.device_id = device_id;
+  TVMNDArray arr = TVMNDArray_Empty(ndim, shape, dtype, ctx);
+  **out = arr.dl_tensor;
+  return 0;
+}
+
+int TVMArrayFree(TVMArrayHandle handle) {
+  TVMNDArray arr;
+  arr.dl_tensor = *handle;
+  return TVMNDArray_Release(&arr);
+}
+
+int TVMDeviceAllocDataSpace(DLContext ctx, size_t nbytes, size_t alignment, DLDataType type_hint,
+                            void** out_data) {
+  if (alignment != 1) {
+    nbytes = (nbytes + alignment - 1) / alignment * alignment;
+  }
+
+  *out_data = vmalloc(nbytes);
+  return 0;
+}
+
+int TVMDeviceFreeDataSpace(TVMContext ctx, void* ptr) {
+  vfree(ptr);
+  return 0;
+}
+
+int TVMDeviceCopyDataFromTo(const void* from, size_t from_offset, void* to, size_t to_offset,
+                            size_t num_bytes, TVMContext ctx_from, TVMContext ctx_to,
+                            DLDataType type_hint, TVMStreamHandle stream) {
+  memcpy(((uint8_t*)to) + to_offset, ((uint8_t*)from) + from_offset, num_bytes);
+  return 0;
+}
+
+int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream) { return 0; }
+
+static TVMMutableFuncRegistry global_func_registry;
+
+int TVMFuncRegisterGlobal(const char* name, TVMFunctionHandle f, int override) {
+  return TVMMutableFuncRegistry_Set(&global_func_registry, name, f, override != 0);
+}
+
+static const TVMModule* registered_modules[TVM_CRT_MAX_REGISTERED_MODULES];
+
+/*! \brief Passed as `module_index` to EncodeFunctionHandle. */
+static const tvm_module_index_t kGlobalFuncModuleIndex = TVM_CRT_MAX_REGISTERED_MODULES;
+
+static int DecodeModuleHandle(TVMModuleHandle handle, tvm_module_index_t* out_module_index) {
+  tvm_module_index_t module_index;
+
+  module_index = ((tvm_module_index_t)((uintptr_t)handle)) & ~0x8000;
+  if (module_index > TVM_CRT_MAX_REGISTERED_MODULES || registered_modules[module_index] == NULL) {
+    TVMAPIErrorf("invalid module handle: %08x", module_index);
+    return -1;
+  }
+
+  *out_module_index = module_index;
+  return 0;
+}
+
+static TVMModuleHandle EncodeModuleHandle(tvm_module_index_t module_index) {
+  return (TVMModuleHandle)((uintptr_t)(module_index | 0x8000));
+}
+
+static int _TVMModCreateFromCModule(const TVMModule* mod, TVMModuleHandle* out_handle) {
+  tvm_module_index_t idx;
+
+  for (idx = 0; idx < TVM_CRT_MAX_REGISTERED_MODULES; idx++) {
+    if (registered_modules[idx] == NULL) {
+      registered_modules[idx] = mod;
+      *out_handle = EncodeModuleHandle(idx);
+      return 0;
+    }
+  }
+
+  return -1;
+}
+
+int TVMModFree(TVMModuleHandle mod) {
+  tvm_module_index_t module_index;
+  if (DecodeModuleHandle(mod, &module_index) != 0) {
+    return -1;
+  }
+
+  registered_modules[module_index] = NULL;
+  return 0;
+}
+
+static const TVMModuleHandle kTVMModuleHandleUninitialized = (TVMModuleHandle)(~0UL);
+
+static TVMModuleHandle system_lib_handle;
+
+int SystemLibraryCreate(TVMValue* args, int* type_codes, int num_args, TVMValue* ret_val,
+                        int* ret_type_codes) {
+  const TVMModule* system_lib;
+
+  if (system_lib_handle == kTVMModuleHandleUninitialized) {
+    system_lib = TVMSystemLibEntryPoint();
+    if (_TVMModCreateFromCModule(system_lib, &system_lib_handle) != 0) {
+      TVMAPIErrorf("error registering system lib");
+      return -1;
+    }
+  }
+
+  ret_val[0].v_handle = system_lib_handle;
+  ret_type_codes[0] = kTVMModuleHandle;
+  return 0;
+}
+
+static TVMFunctionHandle EncodeFunctionHandle(tvm_module_index_t module_index,
+                                              tvm_function_index_t function_index) {
+  return (TVMFunctionHandle)((uintptr_t)(
+      ((module_index | 0x8000) << (sizeof(tvm_function_index_t) * 8)) | (function_index | 0x8000)));

Review comment:
       add a define (maybe `INT16_MSB_MASK`) and use for all occurrences of 0x8000 

##########
File path: src/runtime/crt/common/crt_runtime_api.c
##########
@@ -0,0 +1,335 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// LINT_C_FILE
+
+#include <assert.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tvm/runtime/c_runtime_api.h>
+#include <tvm/runtime/crt/func_registry.h>
+#include <tvm/runtime/crt/internal/common/ndarray.h>
+#include <tvm/runtime/crt/internal/common/packed_func.h>
+#include <tvm/runtime/crt/internal/graph_runtime/graph_runtime.h>
+#include <tvm/runtime/crt/memory.h>
+#include <tvm/runtime/crt/platform.h>
+
+// Handle internal errors
+
+static char g_last_error[1024];
+
+void TVMAPISetLastError(const char* msg) { strncpy(g_last_error, msg, sizeof(g_last_error)); }
+
+__attribute__((format(printf, 1, 2))) int TVMAPIErrorf(const char* msg, ...) {
+  va_list args;
+  int to_return;
+
+  va_start(args, msg);
+  to_return = vsnprintf(g_last_error, sizeof(g_last_error), msg, args);
+  va_end(args);
+
+  return to_return;
+}
+
+const char* TVMGetLastError(void) { return g_last_error; }
+
+// Manipulate NDArray on target device
+
+int TVMArrayAlloc(const tvm_index_t* shape, int ndim, int dtype_code, int dtype_bits,
+                  int dtype_lanes, int device_type, int device_id, TVMArrayHandle* out) {
+  DLDataType dtype;
+  dtype.code = dtype_code;
+  dtype.bits = dtype_bits;
+  dtype.lanes = dtype_lanes;
+  DLContext ctx;
+  ctx.device_type = (DLDeviceType)device_type;
+  ctx.device_id = device_id;
+  TVMNDArray arr = TVMNDArray_Empty(ndim, shape, dtype, ctx);
+  **out = arr.dl_tensor;
+  return 0;
+}
+
+int TVMArrayFree(TVMArrayHandle handle) {
+  TVMNDArray arr;
+  arr.dl_tensor = *handle;
+  return TVMNDArray_Release(&arr);
+}
+
+int TVMDeviceAllocDataSpace(DLContext ctx, size_t nbytes, size_t alignment, DLDataType type_hint,
+                            void** out_data) {
+  if (alignment != 1) {
+    nbytes = (nbytes + alignment - 1) / alignment * alignment;
+  }
+
+  *out_data = vmalloc(nbytes);
+  return 0;
+}
+
+int TVMDeviceFreeDataSpace(TVMContext ctx, void* ptr) {
+  vfree(ptr);
+  return 0;
+}
+
+int TVMDeviceCopyDataFromTo(const void* from, size_t from_offset, void* to, size_t to_offset,
+                            size_t num_bytes, TVMContext ctx_from, TVMContext ctx_to,
+                            DLDataType type_hint, TVMStreamHandle stream) {
+  memcpy(((uint8_t*)to) + to_offset, ((uint8_t*)from) + from_offset, num_bytes);
+  return 0;
+}
+
+int TVMSynchronize(int device_type, int device_id, TVMStreamHandle stream) { return 0; }
+
+static TVMMutableFuncRegistry global_func_registry;
+
+int TVMFuncRegisterGlobal(const char* name, TVMFunctionHandle f, int override) {
+  return TVMMutableFuncRegistry_Set(&global_func_registry, name, f, override != 0);
+}
+
+static const TVMModule* registered_modules[TVM_CRT_MAX_REGISTERED_MODULES];
+
+/*! \brief Passed as `module_index` to EncodeFunctionHandle. */
+static const tvm_module_index_t kGlobalFuncModuleIndex = TVM_CRT_MAX_REGISTERED_MODULES;
+
+static int DecodeModuleHandle(TVMModuleHandle handle, tvm_module_index_t* out_module_index) {
+  tvm_module_index_t module_index;
+
+  module_index = ((tvm_module_index_t)((uintptr_t)handle)) & ~0x8000;
+  if (module_index > TVM_CRT_MAX_REGISTERED_MODULES || registered_modules[module_index] == NULL) {
+    TVMAPIErrorf("invalid module handle: %08x", module_index);
+    return -1;
+  }
+
+  *out_module_index = module_index;
+  return 0;
+}
+
+static TVMModuleHandle EncodeModuleHandle(tvm_module_index_t module_index) {
+  return (TVMModuleHandle)((uintptr_t)(module_index | 0x8000));
+}
+
+static int _TVMModCreateFromCModule(const TVMModule* mod, TVMModuleHandle* out_handle) {
+  tvm_module_index_t idx;
+
+  for (idx = 0; idx < TVM_CRT_MAX_REGISTERED_MODULES; idx++) {
+    if (registered_modules[idx] == NULL) {
+      registered_modules[idx] = mod;
+      *out_handle = EncodeModuleHandle(idx);
+      return 0;
+    }
+  }
+
+  return -1;
+}
+
+int TVMModFree(TVMModuleHandle mod) {
+  tvm_module_index_t module_index;
+  if (DecodeModuleHandle(mod, &module_index) != 0) {
+    return -1;
+  }
+
+  registered_modules[module_index] = NULL;
+  return 0;
+}
+
+static const TVMModuleHandle kTVMModuleHandleUninitialized = (TVMModuleHandle)(~0UL);
+
+static TVMModuleHandle system_lib_handle;
+
+int SystemLibraryCreate(TVMValue* args, int* type_codes, int num_args, TVMValue* ret_val,
+                        int* ret_type_codes) {
+  const TVMModule* system_lib;
+
+  if (system_lib_handle == kTVMModuleHandleUninitialized) {
+    system_lib = TVMSystemLibEntryPoint();
+    if (_TVMModCreateFromCModule(system_lib, &system_lib_handle) != 0) {
+      TVMAPIErrorf("error registering system lib");
+      return -1;
+    }
+  }
+
+  ret_val[0].v_handle = system_lib_handle;
+  ret_type_codes[0] = kTVMModuleHandle;
+  return 0;
+}
+
+static TVMFunctionHandle EncodeFunctionHandle(tvm_module_index_t module_index,
+                                              tvm_function_index_t function_index) {
+  return (TVMFunctionHandle)((uintptr_t)(
+      ((module_index | 0x8000) << (sizeof(tvm_function_index_t) * 8)) | (function_index | 0x8000)));

Review comment:
       also, why is the _function_ index ORing a valid bit as well (as opposed to the RFC, where it was just the module index)?

##########
File path: src/runtime/crt/include/tvm/runtime/crt/internal/graph_runtime/graph_runtime.h
##########
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * \file src/runtime/crt/include/tvm/runtime/crt/internal/graph_runtime/graph_runtime.h
+ * \brief Tiny graph runtime that can run graph containing only tvm PackedFunc.
+ */
+#ifndef TVM_RUNTIME_CRT_INCLUDE_TVM_RUNTIME_CRT_INTERNAL_GRAPH_RUNTIME_GRAPH_RUNTIME_H_
+#define TVM_RUNTIME_CRT_INCLUDE_TVM_RUNTIME_CRT_INTERNAL_GRAPH_RUNTIME_GRAPH_RUNTIME_H_
+
+#include <tvm/runtime/crt/graph_runtime.h>
+#include <tvm/runtime/crt/internal/common/module.h>
+#include <tvm/runtime/crt/internal/common/ndarray.h>
+#include <tvm/runtime/crt/internal/graph_runtime/load_json.h>
+
+// Memory pool entry.
+typedef struct TVMGraphRuntimePoolEntry {
+  size_t size;
+  int device_type;
+} TVMGraphRuntimePoolEntry;
+
+// Node entry
+typedef struct TVMGraphRuntimeNodeEntry {
+  uint32_t node_id;
+  uint32_t index;
+  uint32_t version;
+  // JSON Loader
+  void (*Load)(JSONReader* reader);
+} TVMGraphRuntimeNodeEntry;
+
+// Node
+typedef struct TVMGraphRuntimeNode {
+  // operator type in string
+  char op_type[16];
+  // name of the op
+  char name[120];

Review comment:
       define consts in `crt_config.h`?

##########
File path: src/runtime/crt/include/tvm/runtime/crt/internal/common/packed_func.h
##########
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * \file tvm/runtime/crt/include/tvm/runtime/crt/internal/common/packed_func.h
+ * \brief Type-erased function used across TVM API.
+ */
+#ifndef TVM_RUNTIME_CRT_INCLUDE_TVM_RUNTIME_CRT_INTERNAL_COMMON_PACKED_FUNC_H_
+#define TVM_RUNTIME_CRT_INCLUDE_TVM_RUNTIME_CRT_INTERNAL_COMMON_PACKED_FUNC_H_
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <tvm/runtime/c_runtime_api.h>
+
+#include "crt_config.h"
+#include "module.h"
+
+DLDataType String2DLDataType(const char* s);
+
+typedef struct TVMArgs {
+  TVMValue values[TVM_CRT_MAX_ARGS];
+  int tcodes[TVM_CRT_MAX_ARGS]; /* Data type should be identical to type_codes in TVMPackedCFunc */
+  uint32_t values_count;
+} TVMArgs;
+
+TVMArgs TVMArgs_Create(TVMValue* values, uint32_t* tcodes, uint32_t values_count);
+
+typedef struct TVMPackedFunc {
+  char name[200];

Review comment:
       maybe make this smaller and defined in `crt_config.h`?

##########
File path: tests/crt/func_registry_test.cc
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "../../src/runtime/crt/func_registry.c"
+
+#include <dmlc/logging.h>
+#include <gtest/gtest.h>
+#include <tvm/runtime/crt/func_registry.h>
+
+typedef struct {
+  const char* a;
+  const char* b;
+  int ret_val;
+} strcmp_test_t;
+
+strcmp_test_t strcmp_tests[] = {
+    {"Foo", "Foo", 0},        {"Foo", "Bar", 'F' - 'B'},    {"Foo", "", 'F'},
+    {"Fabulous", "Fab", 'u'}, {"Fab", "Fabulous", 0 - 'u'},
+};
+
+std::ostream& operator<<(std::ostream& os, const strcmp_test_t& test) {
+  os << "strcmp_cursor(\"" << test.a << "\", \"" << test.b << "\") -> " << test.ret_val;
+  return os;
+}
+
+class StrCmpTestFixture : public ::testing::TestWithParam<strcmp_test_t> {};
+
+TEST_P(StrCmpTestFixture, Match) {
+  strcmp_test_t param = GetParam();
+  const char* cursor = param.a;
+  EXPECT_EQ(param.ret_val, strcmp_cursor(&cursor, param.b));
+
+  EXPECT_EQ('\0', *cursor);
+
+  size_t a_length = strlen(param.a);
+  EXPECT_EQ(param.a + a_length, cursor);
+}
+
+INSTANTIATE_TEST_SUITE_P(StrCmpTests, StrCmpTestFixture, ::testing::ValuesIn(strcmp_tests));
+
+TEST(StrCmpScan, Test) {
+  const char* a = "Foo\0Bar\0Whoops\0";
+  const char* cursor = a;
+
+  EXPECT_EQ('o', strcmp_cursor(&cursor, "Fo"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 3);
+  cursor++;
+
+  EXPECT_EQ(0 - 'r', strcmp_cursor(&cursor, "Barr"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 7);
+  cursor++;
+
+  EXPECT_EQ('h' - 'B', strcmp_cursor(&cursor, "WB"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 14);
+  cursor++;
+
+  EXPECT_EQ(0, *cursor);
+  const char* before_cursor = cursor;
+  EXPECT_EQ(0, strcmp_cursor(&cursor, ""));
+  EXPECT_EQ(before_cursor, cursor);
+}
+
+TEST(FuncRegistry, Empty) {
+  TVMFuncRegistry registry{"\000", NULL};
+
+  EXPECT_EQ(kTvmErrorFunctionNameNotFound, TVMFuncRegistry_Lookup(&registry, "foo", NULL));
+  EXPECT_EQ(kTvmErrorFunctionIndexInvalid,
+            TVMFuncRegistry_GetByIndex(&registry, (tvm_function_index_t)0, NULL));
+}
+
+extern "C" {
+static int Foo(TVMValue* args, int* type_codes, int num_args, TVMValue* out_ret_value,
+               int* out_ret_tcode) {
+  return 0;
+}
+static int Bar(TVMValue* args, int* type_codes, int num_args, TVMValue* out_ret_value,
+               int* out_ret_tcode) {
+  return 0;
+}
+}
+
+// Matches the style of registry defined in generated C modules.
+const char* kBasicFuncNames = "\002Foo\0Bar\0";  // NOTE: final \0
+const TVMBackendPackedCFunc funcs[2] = {&Foo, &Bar};
+const TVMFuncRegistry kConstRegistry = {kBasicFuncNames, (const TVMBackendPackedCFunc*)funcs};
+
+TEST(FuncRegistry, ConstGlobalRegistry) {
+  tvm_function_index_t func_index = -1;
+  TVMBackendPackedCFunc func = nullptr;
+
+  // Foo
+  EXPECT_EQ(kBasicFuncNames[0], 2);
+  EXPECT_EQ(kBasicFuncNames[1], 'F');
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_Lookup(&kConstRegistry, "Foo", &func_index));
+  EXPECT_EQ(0, func_index);
+
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_GetByIndex(&kConstRegistry, func_index, &func));
+  EXPECT_EQ(func, &Foo);
+
+  // Bar
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_Lookup(&kConstRegistry, "Bar", &func_index));
+  EXPECT_EQ(1, func_index);
+
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_GetByIndex(&kConstRegistry, func_index, &func));
+  EXPECT_EQ(func, &Bar);
+
+  // Expected not found.
+  tvm_function_index_t prev_func_index = func_index;
+  EXPECT_EQ(kTvmErrorFunctionNameNotFound,
+            TVMFuncRegistry_Lookup(&kConstRegistry, "Baz", &func_index));
+  EXPECT_EQ(prev_func_index, func_index);
+
+  // Expected index out of range.
+  func = nullptr;
+  EXPECT_EQ(kTvmErrorFunctionIndexInvalid, TVMFuncRegistry_GetByIndex(&kConstRegistry, 2, &func));
+  EXPECT_EQ(func, nullptr);
+}
+
+/*! \brief Return a test function handle, with number repeating for all bytes in a void*. */
+static TVMBackendPackedCFunc TestFunctionHandle(uint8_t number) {
+  uintptr_t handle = 0;
+  for (size_t i = 0; i < sizeof(TVMBackendPackedCFunc); i++) {
+    handle |= ((uintptr_t)handle) << (8 * i);
+  }
+
+  return (TVMBackendPackedCFunc)handle;
+}
+
+TEST(MutableFuncRegistry, Create) {
+  char mem_buffer[kTvmAverageFuncEntrySizeBytes * 3];
+  // A substring used to create function names for testing.
+  const char* function_name_chars = "abcdefghijklmnopqrstuvwxyz";
+
+  // function_name_chars is used to produce 2 function names. The second one is expected to
+  // overfill `names`.
+  EXPECT_LT(kTvmAverageFuncEntrySizeBytes, strlen(function_name_chars) + 1);
+
+  for (unsigned int buf_size = 0; buf_size < 10 + 1 + sizeof(void*); buf_size++) {

Review comment:
       should `10 + 1 + sizeof(void*)` be replaced with `kTvmAverageFuncEntrySizeBytes`?

##########
File path: tests/crt/func_registry_test.cc
##########
@@ -0,0 +1,223 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "../../src/runtime/crt/func_registry.c"
+
+#include <dmlc/logging.h>
+#include <gtest/gtest.h>
+#include <tvm/runtime/crt/func_registry.h>
+
+typedef struct {
+  const char* a;
+  const char* b;
+  int ret_val;
+} strcmp_test_t;
+
+strcmp_test_t strcmp_tests[] = {
+    {"Foo", "Foo", 0},        {"Foo", "Bar", 'F' - 'B'},    {"Foo", "", 'F'},
+    {"Fabulous", "Fab", 'u'}, {"Fab", "Fabulous", 0 - 'u'},
+};
+
+std::ostream& operator<<(std::ostream& os, const strcmp_test_t& test) {
+  os << "strcmp_cursor(\"" << test.a << "\", \"" << test.b << "\") -> " << test.ret_val;
+  return os;
+}
+
+class StrCmpTestFixture : public ::testing::TestWithParam<strcmp_test_t> {};
+
+TEST_P(StrCmpTestFixture, Match) {
+  strcmp_test_t param = GetParam();
+  const char* cursor = param.a;
+  EXPECT_EQ(param.ret_val, strcmp_cursor(&cursor, param.b));
+
+  EXPECT_EQ('\0', *cursor);
+
+  size_t a_length = strlen(param.a);
+  EXPECT_EQ(param.a + a_length, cursor);
+}
+
+INSTANTIATE_TEST_SUITE_P(StrCmpTests, StrCmpTestFixture, ::testing::ValuesIn(strcmp_tests));
+
+TEST(StrCmpScan, Test) {
+  const char* a = "Foo\0Bar\0Whoops\0";
+  const char* cursor = a;
+
+  EXPECT_EQ('o', strcmp_cursor(&cursor, "Fo"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 3);
+  cursor++;
+
+  EXPECT_EQ(0 - 'r', strcmp_cursor(&cursor, "Barr"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 7);
+  cursor++;
+
+  EXPECT_EQ('h' - 'B', strcmp_cursor(&cursor, "WB"));
+  EXPECT_EQ(0, *cursor);
+  EXPECT_EQ(cursor, a + 14);
+  cursor++;
+
+  EXPECT_EQ(0, *cursor);
+  const char* before_cursor = cursor;
+  EXPECT_EQ(0, strcmp_cursor(&cursor, ""));
+  EXPECT_EQ(before_cursor, cursor);
+}
+
+TEST(FuncRegistry, Empty) {
+  TVMFuncRegistry registry{"\000", NULL};
+
+  EXPECT_EQ(kTvmErrorFunctionNameNotFound, TVMFuncRegistry_Lookup(&registry, "foo", NULL));
+  EXPECT_EQ(kTvmErrorFunctionIndexInvalid,
+            TVMFuncRegistry_GetByIndex(&registry, (tvm_function_index_t)0, NULL));
+}
+
+extern "C" {
+static int Foo(TVMValue* args, int* type_codes, int num_args, TVMValue* out_ret_value,
+               int* out_ret_tcode) {
+  return 0;
+}
+static int Bar(TVMValue* args, int* type_codes, int num_args, TVMValue* out_ret_value,
+               int* out_ret_tcode) {
+  return 0;
+}
+}
+
+// Matches the style of registry defined in generated C modules.
+const char* kBasicFuncNames = "\002Foo\0Bar\0";  // NOTE: final \0
+const TVMBackendPackedCFunc funcs[2] = {&Foo, &Bar};
+const TVMFuncRegistry kConstRegistry = {kBasicFuncNames, (const TVMBackendPackedCFunc*)funcs};
+
+TEST(FuncRegistry, ConstGlobalRegistry) {
+  tvm_function_index_t func_index = -1;
+  TVMBackendPackedCFunc func = nullptr;
+
+  // Foo
+  EXPECT_EQ(kBasicFuncNames[0], 2);
+  EXPECT_EQ(kBasicFuncNames[1], 'F');
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_Lookup(&kConstRegistry, "Foo", &func_index));
+  EXPECT_EQ(0, func_index);
+
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_GetByIndex(&kConstRegistry, func_index, &func));
+  EXPECT_EQ(func, &Foo);
+
+  // Bar
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_Lookup(&kConstRegistry, "Bar", &func_index));
+  EXPECT_EQ(1, func_index);
+
+  EXPECT_EQ(kTvmErrorNoError, TVMFuncRegistry_GetByIndex(&kConstRegistry, func_index, &func));
+  EXPECT_EQ(func, &Bar);
+
+  // Expected not found.
+  tvm_function_index_t prev_func_index = func_index;
+  EXPECT_EQ(kTvmErrorFunctionNameNotFound,
+            TVMFuncRegistry_Lookup(&kConstRegistry, "Baz", &func_index));
+  EXPECT_EQ(prev_func_index, func_index);
+
+  // Expected index out of range.
+  func = nullptr;
+  EXPECT_EQ(kTvmErrorFunctionIndexInvalid, TVMFuncRegistry_GetByIndex(&kConstRegistry, 2, &func));
+  EXPECT_EQ(func, nullptr);
+}
+
+/*! \brief Return a test function handle, with number repeating for all bytes in a void*. */
+static TVMBackendPackedCFunc TestFunctionHandle(uint8_t number) {
+  uintptr_t handle = 0;
+  for (size_t i = 0; i < sizeof(TVMBackendPackedCFunc); i++) {
+    handle |= ((uintptr_t)handle) << (8 * i);
+  }
+
+  return (TVMBackendPackedCFunc)handle;
+}
+
+TEST(MutableFuncRegistry, Create) {
+  char mem_buffer[kTvmAverageFuncEntrySizeBytes * 3];
+  // A substring used to create function names for testing.
+  const char* function_name_chars = "abcdefghijklmnopqrstuvwxyz";
+
+  // function_name_chars is used to produce 2 function names. The second one is expected to
+  // overfill `names`.
+  EXPECT_LT(kTvmAverageFuncEntrySizeBytes, strlen(function_name_chars) + 1);
+
+  for (unsigned int buf_size = 0; buf_size < 10 + 1 + sizeof(void*); buf_size++) {
+    EXPECT_EQ(kTvmErrorBufferTooSmall, TVMMutableFuncRegistry_Create(NULL, mem_buffer, buf_size));
+  }
+
+  for (unsigned int rem = 0; rem < kTvmAverageFuncEntrySizeBytes; rem++) {
+    // test_function name will be used to test overfilling.
+    char test_function_name[kTvmAverageFuncNameSizeBytes + 2 + rem];

Review comment:
       why `+ 2` here?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org