def check_not_null(
    tensor: Tensor,
    tensor_idx: Optional[int] = None,
    skip_if_lower_bound_is_zero: bool = False,
) -> str:
    """
    Generate a nullptr check to be used by pointer initialization code.

    If skip_if_lower_bound_is_zero == True, no code will be generated
    when the Tensor has at least one dynamic dim with a lower bound
    of zero. This is most useful for outputs; we put the nullptr
    checks at the start of the inference, but we won't know output
    shapes until after Run() finishes. We therefore just relax the check
    for these outputs - only allow them to be null if their lower bound
    is zero, otherwise never allow them to be null.
    """
    name = tensor._attrs["name"]
    if tensor_idx is None:
        check = name
    else:
        check = f"params_[{tensor_idx}].ptr"

    shape = ["1"]
    lower_bound_is_zero = False
    for dim in tensor._attrs["shape"]:
        lower_bound_is_zero |= dim.lower_bound() == 0
        if skip_if_lower_bound_is_zero and lower_bound_is_zero:
            return ""
        if isinstance(dim, IntImm):
            shape.append(str(dim._attrs["values"][0]))
        else:
            shape.append(dim._attrs["name"])

    nullptr_check = f"{check} == nullptr"
    condition = (
        nullptr_check
        # If the lower bound of the shape is positive, never allow
        # the tensor to be null.
        if not lower_bound_is_zero
        # Otherwise, allow it to be null only if the (possibly dynamic)
        # size is zero.
        else f"{nullptr_check} && {'*'.join(shape)} != 0"
    )
    return f"""
if ({condition}) {{
    throw std::runtime_error("Constant {name} was not set! Set the value with set_constant.");
}}
    """

    Generate a nullptr check to be used by pointer initialization code.

    If skip_if_lower_bound_is_zero == True, no code will be generated
    when the Tensor has at least one dynamic dim with a lower bound
    of zero. This is most useful for outputs; we put the nullptr
    checks at the start of the inference, but we won't know output
    shapes until after Run() finishes. We therefore just relax the check
    for these outputs - only allow them to be null if their lower bound
    is zero, otherwise never allow them to be null.
    
python/aitemplate/backend/codegen.py