# 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.

import numpy as np
import pytest

import tvm
import tvm.testing
from tvm.script import ir as I
from tvm.script import relax as R
from tvm.script import tirx as T


def test_infer_shape_of_1d_static_view():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor) -> R.Tensor([4096]):
        B: R.Tensor([4096]) = R.memory.view(A, R.shape([4096]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor):
        B = R.memory.view(A, R.shape([4096]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_shape_of_2d_static_view():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor) -> R.Tensor([64, 64]):
        B: R.Tensor([64, 64]) = R.memory.view(A, R.shape([64, 64]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor):
        B = R.memory.view(A, R.shape([64, 64]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_shape_argument_is_not_shape():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor([16])):
            B = R.memory.view(A, R.prim_value(42))
            return B


def test_infer_shape_of_1d_static_view_smaller_than_1d_source():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor([4096])) -> R.Tensor([16]):
        B: R.Tensor([16]) = R.memory.view(A, R.shape([16]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([4096])):
        B = R.memory.view(A, R.shape([16]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_shape_of_2d_static_view_smaller_than_1d_source():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor([4096])) -> R.Tensor([4, 4]):
        B: R.Tensor([4, 4]) = R.memory.view(A, R.shape([4, 4]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([4096])):
        B = R.memory.view(A, R.shape([4, 4]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_shape_of_2d_static_view_same_size_as_2d_source():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor([64, 64])) -> R.Tensor([16, 256]):
        B: R.Tensor([16, 256]) = R.memory.view(A, R.shape([16, 256]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([64, 64])):
        B = R.memory.view(A, R.shape([16, 256]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_1d_static_view_larger_than_1d_source():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor([16])):
            B = R.memory.view(A, R.shape([17]))
            return B


def test_error_if_static_2d_view_larger_than_source():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor([16])):
            B = R.memory.view(A, R.shape([4, 5]))
            return B


def test_infer_shape_of_1d_dynamic_view():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor(["N"])) -> R.Tensor(["N // 2"]):
        N = T.int64()
        B: R.Tensor([N // 2]) = R.memory.view(A, R.shape([N // 2]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor(["N"])):
        N = T.int64()
        B = R.memory.view(A, R.shape([N // 2]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_shape_of_2d_dynamic_view_of_1d_source():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor(["N"])) -> R.Tensor(["N // 8", 8]):
        N = T.int64()
        B: R.Tensor([N // 8, 8]) = R.memory.view(A, R.shape([N // 8, 8]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor(["N"])):
        N = T.int64()
        B = R.memory.view(A, R.shape([N // 8, 8]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_shape_of_2d_dynamic_view():
    @R.function(private=True)
    def explicit_ty(A: R.Tensor(["N"])) -> R.Tensor(["N // 2"]):
        N = T.int64()
        B: R.Tensor([N // 2]) = R.memory.view(A, R.shape([N // 2]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor(["N"])):
        N = T.int64()
        B = R.memory.view(A, R.shape([N // 2]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_1d_dynamic_view_larger_than_1d_source():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor(["N"])):
            N = T.int64()
            B = R.memory.view(A, R.shape([N + 1]))
            return B


@pytest.mark.xfail(reason="See https://github.com/apache/tvm/pull/16877")
def test_error_if_1d_dynamic_view_provably_larger_than_1d_source():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor(["N"])):
            N = T.int64()
            B = R.memory.view(A, R.shape([N + T.if_then_else(N < 0, -1, 1)]))
            return B


def test_error_if_2d_dynamic_view_provably_larger_than_1d_source():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor(["N"])):
            N = T.int64()
            B = R.memory.view(A, R.shape([N // 4 + 1, 4]))
            return B


def test_validity_of_dynamic_view_may_depend_on_runtime_value():
    """Validity checks may be delayed until runtime

    The runtime implementation of `R.memory.view` checks the validity of any
    dynamic shape.  A compile-time error should only be issued the
    runtime check would fail for *all* dynamic shapes.

    In this example, the output of `R.memory.view` contains `N` elements when
    `N` is evenly divisible by 4, and `N+4` elements otherwise.  The
    runtime check would pass whenever the argument's size is divisible
    by 4.  Even though the runtime check would fail when `N` isn't
    divisible by 4, no compile-time error should be emitted.

    """

    @R.function
    def func(A: R.Tensor(["N"])):
        N = T.int64()
        B = R.memory.view(A, R.shape([(N + 3) // 4, 4]))
        return B


def test_infer_dtype_of_float32_view():
    """R.memory.view can reinterpret the contents as another type

    For example, if the same backing allocation is used for multiple
    arrays with distinct datatypes.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor) -> R.Tensor("float32"):
        B: R.Tensor("float32") = R.memory.view(A, dtype=R.dtype("float32"))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor):
        B = R.memory.view(A, dtype=R.dtype("float32"))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_view_dtype_is_void():
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor("float32")):
            B = R.memory.view(A, dtype=R.dtype("void"))
            return B


def test_view_without_explicit_dtype_keeps_input_dtype():
    """If R.memory.view only specifies the shape, the dtype is unchanged"""

    @R.function(private=True)
    def explicit_ty(A: R.Tensor([16], "float32")) -> R.Tensor([4, 4], "float32"):
        B: R.Tensor([4, 4], "float32") = R.memory.view(A, R.shape([4, 4]))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([16], "float32")):
        B = R.memory.view(A, R.shape([4, 4]))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_dtype_of_float32_view_from_relax_var():
    """R.memory.view can reinterpret the contents as another type

    Any relax object can be stored in a relax variable.  Even if the
    `R.dtype` argument is stored in a variable, struct inference may
    be applied.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor) -> R.Tensor("float32"):
        dtype = R.dtype("float32")
        B: R.Tensor("float32") = R.memory.view(A, dtype=dtype)
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor):
        dtype = R.dtype("float32")
        B = R.memory.view(A, dtype=dtype)
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_infer_dtype_of_view_with_unknown_dtype():
    """DType may be provided as argument

    Because we do not know the value provided in `dtype`, the element
    type of the array is unknown.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor("float32"), dtype: R.Any) -> R.Tensor:
        B: R.Tensor = R.memory.view(A, dtype=dtype)
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor("float32"), dtype: R.Any):
        B = R.memory.view(A, dtype=dtype)
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_view_dtype_may_be_smaller_than_input_dtype():
    """Viewing with a smaller dtype does not exceed original bounds

    This is not typically desired behavior, as the view would span
    fewer bytes than the original array.  However, this is legal, and
    may occur as the result of optimization passes.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor("uint32")) -> R.Tensor("float8"):
        B: R.Tensor("float8") = R.memory.view(A, dtype=R.dtype("float8"))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor("uint32")):
        B = R.memory.view(A, dtype=R.dtype("float8"))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_view_dtype_is_larger_than_input_dtype():
    """A view may not exceed the bounds of the viewed array"""
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor([16], "uint8")):
            B = R.memory.view(A, dtype=R.dtype("float16"))
            return B


def test_increase_dtype_size_while_decreasing_number_of_elements():
    """R.memory.view may update both dtype and shape simultaneously

    Like `test_error_if_dtype_results_in_larger_view`, but the view
    contains fewer elements than the backing array.  This results in a
    view that is the same size as the backing array, and would not
    exceed the bounds of the original array.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor([16], "uint8")) -> R.Tensor([8], "float16"):
        B: R.Tensor([8], "float16") = R.memory.view(A, shape=R.shape([8]), dtype=R.dtype("float16"))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([16], "uint8")):
        B = R.memory.view(A, shape=R.shape([8]), dtype=R.dtype("float16"))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_decrease_dtype_size_while_increasing_number_of_elements():
    """R.memory.view may update both dtype and shape simultaneously"""

    @R.function(private=True)
    def explicit_ty(A: R.Tensor([8], "float16")) -> R.Tensor([16], "uint8"):
        B: R.Tensor([16], "uint8") = R.memory.view(A, shape=R.shape([16]), dtype=R.dtype("uint8"))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor([8], "float16")):
        B = R.memory.view(A, shape=R.shape([16]), dtype=R.dtype("uint8"))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_error_if_number_of_bytes_of_view_is_larger_than_original():
    """R.memory.view may update both dtype and shape simultaneously

    In this test case, the source array is 16 bytes (8 elements * 2
    bytes/element), but the view is 32 bytes (32 elements * 1
    byte/element).

    """
    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor([8], "float16")):
            B = R.memory.view(A, shape=R.shape([32]), dtype=R.dtype("uint8"))
            return B


def test_error_for_non_zero_relative_byte_offset():
    """R.memory.view must not exceed bounds of the original array

    Providing a non-zero `relative_byte_offset`, without updating
    either the dtype or the shape of the array, would allow the view
    to overrun the end of the original array.

    """

    with pytest.raises(tvm.error.DiagnosticError):

        @R.function
        def func(A: R.Tensor):
            B = R.memory.view(A, relative_byte_offset=16)
            return B


def test_applying_relative_byte_offset_of_zero_is_legal():
    """Using relative_byte_offset=0 is no-op

    Providing a `relative_byte_offset` of zero, without updating
    either the dtype or the shape of the array, is legal, though it is
    a no-op.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor) -> R.Tensor:
        B: R.Tensor = R.memory.view(A, relative_byte_offset=R.prim_value(0))
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor):
        B = R.memory.view(A, relative_byte_offset=R.prim_value(0))
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_applying_unknown_relative_byte_offset_is_legal():
    """Using an unknown relative_byte_offset is legal

    Since providing a `relative_byte_offset` of zero, without updating
    either the dtype or the shape of the array, is legal, we may not
    emit a compile-time error for an unknown `relative_byte_offset` in
    this case.

    """

    @R.function(private=True)
    def explicit_ty(A: R.Tensor, relative_byte_offset: R.Prim("int64")) -> R.Tensor:
        B: R.Tensor = R.memory.view(A, relative_byte_offset=relative_byte_offset)
        return B

    @R.function(private=True)
    def inferred_ty(A: R.Tensor, relative_byte_offset: R.Prim("int64")):
        B = R.memory.view(A, relative_byte_offset=relative_byte_offset)
        return B

    tvm.ir.assert_structural_equal(explicit_ty, inferred_ty)


def test_legalize_is_no_op():
    """R.memory.view is not legalized until LowerRuntimeBuiltin"""

    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A)
            return B

    Expected = Before

    After = tvm.relax.transform.LegalizeOps()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


def test_lower_runtime_builtin_shape_change():
    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A, shape=R.shape([64, 64]))
            return B

    @I.ir_module
    class Expected:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            _ = R.null_value()
            B = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([64, 64]),
                R.dtype("float32"),
                R.prim_value(0),
            )
            return B

    After = tvm.relax.transform.LowerRuntimeBuiltin()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


def test_lower_runtime_builtin_view_shape_from_unknown():
    """R.memory.view does not require the input tensor to have a known shape"""

    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor(dtype="float32")):
            B = R.memory.view(A, shape=R.shape([64, 64]))
            return B

    @I.ir_module
    class Expected:
        @R.function
        def main(A: R.Tensor(dtype="float32")):
            _ = R.null_value()
            B = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([64, 64]),
                R.dtype("float32"),
                R.prim_value(0),
            )
            return B

    After = tvm.relax.transform.LowerRuntimeBuiltin()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


def test_lower_runtime_builtin_dtype_change():
    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A, dtype=R.dtype("int32"))
            return B

    @I.ir_module
    class Expected:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([4096]),
                R.dtype("int32"),
                R.prim_value(0),
            )
            return B

    After = tvm.relax.transform.LowerRuntimeBuiltin()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


def test_lower_runtime_builtin_byte_offset():
    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A, relative_byte_offset=R.prim_value(0))
            return B

    @I.ir_module
    class Expected:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            _ = R.null_value()
            B = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([4096]),
                R.dtype("float32"),
                R.prim_value(0),
            )
            return B

    After = tvm.relax.transform.LowerRuntimeBuiltin()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


def test_lower_runtime_builtin_view_with_multiple_updated_fields():
    """R.memory.view may update more than one field in the view

    In this test case, a 4-kilobyte buffer is provided.  The first
    2-kilobytes of the buffer are used as a 1-d array of 512 int32.
    The last 2-kilobytes of the buffer are used as a 2-d array of
    [16,64] float16 values.

    """

    @I.ir_module
    class Before:
        @R.function
        def main(A: R.Tensor([4096], "uint8")):
            B = R.memory.view(
                A,
                shape=R.shape([512]),
                dtype=R.dtype("int32"),
            )
            C = R.memory.view(
                A,
                shape=R.shape([16, 64]),
                dtype=R.dtype("float16"),
                relative_byte_offset=R.prim_value(2048),
            )
            return (B, C)

    @I.ir_module
    class Expected:
        @R.function
        def main(A: R.Tensor([4096], "uint8")):
            B = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([512]),
                R.dtype("int32"),
                R.prim_value(0),
            )
            C = R.ExternFunc(
                "runtime.TVMTensorCreateView",
                R.Callable(
                    derive_func="tvm.relax.type.infer_view_ty",
                    purity=True,
                ),
            )(
                A,
                R.shape([16, 64]),
                R.dtype("float16"),
                R.prim_value(2048),
            )
            return (B, C)

    After = tvm.relax.transform.LowerRuntimeBuiltin()(Before)
    tvm.ir.assert_structural_equal(Expected, After)


@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_execute_no_op_view(target):
    if not tvm.testing.device_enabled(target):
        pytest.skip(f"{target} not enabled")

    @I.ir_module
    class Module:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A)
            return B

    built = tvm.compile(Module, target=target)
    np_input = np.random.random([4096]).astype("float32")
    np_expected = np_input

    def run_and_check():
        dev = tvm.device(target)
        vm = tvm.relax.VirtualMachine(built, device=dev)
        tvm_input = tvm.runtime.tensor(np_input, dev)
        tvm_output = vm["main"](tvm_input)
        tvm.testing.assert_allclose(tvm_output.numpy(), np_expected)

    if target == "llvm":
        run_and_check()
    else:
        tvm.testing.run_with_gpu_lock(run_and_check)


@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_execute_view_with_new_shape(target):
    if not tvm.testing.device_enabled(target):
        pytest.skip(f"{target} not enabled")

    @I.ir_module
    class Module:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A, shape=R.shape([64, 64]))
            return B

    built = tvm.compile(Module, target=target)
    np_input = np.random.random([4096]).astype("float32")
    np_expected = np_input.reshape(64, 64)

    def run_and_check():
        dev = tvm.device(target)
        vm = tvm.relax.VirtualMachine(built, device=dev)
        tvm_input = tvm.runtime.tensor(np_input, dev)
        tvm_output = vm["main"](tvm_input)
        tvm.testing.assert_allclose(tvm_output.numpy(), np_expected)

    if target == "llvm":
        run_and_check()
    else:
        tvm.testing.run_with_gpu_lock(run_and_check)


@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_execute_view_with_new_byte_offset(target):
    if not tvm.testing.device_enabled(target):
        pytest.skip(f"{target} not enabled")

    @I.ir_module
    class Module:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(
                A,
                shape=R.shape([16, 64]),
                relative_byte_offset=32 * 64 * 4,
            )
            return B

    built = tvm.compile(Module, target=target)
    np_input = np.random.random([4096]).astype("float32")
    np_expected = np_input.reshape(64, 64)[32:48, :]

    def run_and_check():
        dev = tvm.device(target)
        vm = tvm.relax.VirtualMachine(built, device=dev)
        tvm_input = tvm.runtime.tensor(np_input, dev)
        tvm_output = vm["main"](tvm_input)
        tvm.testing.assert_allclose(tvm_output.numpy(), np_expected)

    if target == "llvm":
        run_and_check()
    else:
        tvm.testing.run_with_gpu_lock(run_and_check)


@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_execute_view_with_new_dtype(target):
    if not tvm.testing.device_enabled(target):
        pytest.skip(f"{target} not enabled")

    @I.ir_module
    class Module:
        @R.function
        def main(A: R.Tensor([4096], "float32")):
            B = R.memory.view(A, dtype="uint32")
            return B

    built = tvm.compile(Module, target=target)
    np_input = np.random.random([4096]).astype("float32")
    np_expected = np_input.view("uint32")

    def run_and_check():
        dev = tvm.device(target)
        vm = tvm.relax.VirtualMachine(built, device=dev)
        tvm_input = tvm.runtime.tensor(np_input, dev)
        tvm_output = vm["main"](tvm_input)
        tvm.testing.assert_allclose(tvm_output.numpy(), np_expected)

    if target == "llvm":
        run_and_check()
    else:
        tvm.testing.run_with_gpu_lock(run_and_check)


@pytest.mark.parametrize("target", ["llvm", pytest.param("cuda", marks=pytest.mark.gpu)])
def test_execute_view_with_multiple_updated_fields(target):
    if not tvm.testing.device_enabled(target):
        pytest.skip(f"{target} not enabled")

    @I.ir_module
    class Module:
        @R.function
        def main(A: R.Tensor([4096], "uint8")):
            B = R.memory.view(
                A,
                shape=R.shape([512]),
                dtype=R.dtype("int32"),
            )
            C = R.memory.view(
                A,
                shape=R.shape([16, 64]),
                dtype=R.dtype("float16"),
                relative_byte_offset=R.prim_value(2048),
            )
            return (B, C)

    built = tvm.compile(Module, target=target)
    np_input = np.random.randint(0, 255, size=[4096]).astype("uint8")
    np_expected = [
        np_input[:2048].view("int32"),
        np_input[2048:].view("float16").reshape(16, 64),
    ]

    def run_and_check():
        dev = tvm.device(target)
        vm = tvm.relax.VirtualMachine(built, device=dev)
        tvm_input = tvm.runtime.tensor(np_input, dev)
        tvm_output = vm["main"](tvm_input)
        tvm.testing.assert_allclose(tvm_output[0].numpy(), np_expected[0])
        tvm.testing.assert_allclose(tvm_output[1].numpy(), np_expected[1])

    if target == "llvm":
        run_and_check()
    else:
        tvm.testing.run_with_gpu_lock(run_and_check)


if __name__ == "__main__":
    tvm.testing.main()
