code: def register_op(
      lower_func,
      op_name,
      target,
      src_type_name,
      dest_type_name=None,
      intrinsic_name=None,
  ):
      """Register a lowering function for a specific operator of a custom datatype
  
      At build time, Relay must lower operators over custom datatypes into
      operators it understands how to compile. For each custom datatype operator
      which Relay finds while lowering custom datatypes, Relay expects to find a
      user-defined lowering function. Users register their user-defined lowering
      functions using this function.
  
      Users should use create_lower_func to create their lowering function. It
      should serve most use-cases.
  
      Currently, this will work with Casts, intrinsics (e.g. sqrt, sigmoid), and
      binary expressions (e.g. Add, Sub, Mul, Div).
  
      See the LowerCustomDatatypes pass to see how registered functions are used.
  
      Lowering Functions
      ------------------
      TODO(@gussmith23) Get the terminology right here.
      Lowering functions take in a Relay node, and should return a semantically
      equivalent Relay node which Relay can build. This means that the returned
      node should not contain any custom datatypes. Users should likely not need
      to define lowering functions by hand -- see the helper function
      create_lower_func.
  
      Parameters
      ----------
      lower_func : function
          The lowering function to call. See create_lower_func.
  
      op_name : str
          The name of the operation which the function computes, given by its
          class name (e.g. Add, LE, Cast, Call).
  
      target : str
          The name of codegen target.
  
      src_type_name : str
          The name of the custom datatype, e.g. posites2 (but not custom[posites2]32).
          If op_name is not "Cast", then target type is guaranteed to be the same as src_type_name.
  
      dest_type_name : str
          If op_name is "Cast", then this is required and should be set to the dest datatype of
          the argument to the Cast. If op_name is not "Cast", this is unused.
  
      intrinsic_name : str
          If op_name is "Call" and intrinsic_name is not None, then we assume the
          op is a Call to an Intrinsic, and intrinsic_name is the intrinsic's
          name.
      """
  
      if op_name == "Cast":
          assert dest_type_name is not None
          lower_func_name = (
              "tvm.datatype.lower."
              + target
              + "."
              + op_name
              + "."
              + dest_type_name
              + "."
              + src_type_name
          )
      elif op_name == "Call" and intrinsic_name is not None:
          lower_func_name = (
              "tvm.datatype.lower."
              + target
              + "."
              + op_name
              + ".intrin."
              + intrinsic_name
              + "."
              + src_type_name
          )
      else:
          lower_func_name = (
              "tvm.datatype.lower."
              + target
              + "."
              + op_name
              + "."
              + src_type_name
          )
      tvm._ffi.register_func(lower_func_name, lower_func)
  
file_path: tvm/python/tvm/target/datatype.py
imports: import tvm
  from tvm.runtime import convert, DataType
  from tvm.tir.expr import Call, Cast, FloatImm, BinaryOpExpr
  from tvm.tir.op import call_pure_extern
  from tvm._ffi import register_func
  from tvm.tir import call_intrin
label_desc: Use image node_id 5 for calling a global function with example usage: register_op(lower_func, op_name, target, src_type_name, dest_type_name, intrinsic_name) without return types