blob: d1bf0a92b2871dce3875eeed2e9bb021ec18bfdd [file]
/*
* 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.
*/
#ifndef TVM_FFI_TESTING_OBJECT_H_
#define TVM_FFI_TESTING_OBJECT_H_
#include <tvm/ffi/any.h>
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/memory.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
namespace tvm {
namespace ffi {
namespace testing {
// We deliberately pad extra
// in the header to test cases
// where the object subclass address
// do not align with the base object address
// not handling properly will cause buffer overflow
class BasePad {
public:
int64_t extra[4];
};
class TNumberObj : public BasePad, public Object {
public:
// declare as one slot, with float as overflow
static constexpr uint32_t _type_child_slots = 1;
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO("test.Number", TNumberObj, Object);
};
class TNumber : public ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TNumber, ObjectRef, TNumberObj);
};
class TIntObj : public TNumberObj {
public:
int64_t value;
TIntObj(int64_t value) : value(value) {}
explicit TIntObj(UnsafeInit) {}
int64_t GetValue() const { return value; }
inline static void RegisterReflection();
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.Int", TIntObj, TNumberObj);
};
class TInt : public TNumber {
public:
explicit TInt(int64_t value) { data_ = make_object<TIntObj>(value); }
static TInt StaticAdd(TInt lhs, TInt rhs) { return TInt(lhs->value + rhs->value); }
static int64_t CustomAnyHash(const Any& src) {
return static_cast<int64_t>(src.cast<TInt>()->value + 1024);
}
static bool CustomAnyEqual(const Any& lhs, const Any& rhs) {
return lhs.cast<TInt>()->value == rhs.cast<TInt>()->value;
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TInt, TNumber, TIntObj);
};
inline void TIntObj::RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TIntObj>()
.def_ro("value", &TIntObj::value)
.def_static("static_add", &TInt::StaticAdd, "static add method");
// define extra type attributes
refl::TypeAttrDef<TIntObj>()
.def("test.GetValue", &TIntObj::GetValue)
.attr("test.size", sizeof(TIntObj))
.attr(refl::type_attr::kAnyHash, reinterpret_cast<void*>(&TInt::CustomAnyHash))
.attr(refl::type_attr::kAnyEqual, reinterpret_cast<void*>(&TInt::CustomAnyEqual));
// custom json serialization
refl::TypeAttrDef<TIntObj>()
.def(refl::type_attr::kDataToJson,
[](const TIntObj* self) -> Map<String, Any> {
return Map<String, Any>{{"value", self->value}};
})
.def(refl::type_attr::kDataFromJson, [](Map<String, Any> json_obj) -> TInt {
return TInt(json_obj["value"].cast<int64_t>());
});
}
class TFloatObj : public TNumberObj {
public:
double value;
TFloatObj(double value) : value(value) {}
double Add(double other) const { return value + other; }
static void RegisterReflection();
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.Float", TFloatObj, TNumberObj);
};
class TFloat : public TNumber {
public:
explicit TFloat(double value) { data_ = make_object<TFloatObj>(value); }
static uint64_t CustomAnyHash(const Any& src) {
double value = src.cast<TFloat>()->value;
return static_cast<int64_t>(value * 10 + 2048);
}
static bool CustomAnyEqual(const Any& lhs, const Any& rhs) {
return lhs.cast<TFloat>()->value == rhs.cast<TFloat>()->value;
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TFloat, TNumber, TFloatObj);
};
inline void TFloatObj::RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TFloatObj>()
.def_ro("value", &TFloatObj::value, "float value field", refl::default_value(10.0))
.def("sub", [](const TFloatObj* self, double other) -> double { return self->value - other; })
.def("add", &TFloatObj::Add, "add method");
refl::TypeAttrDef<TFloatObj>()
.def(refl::type_attr::kAnyHash, &TFloat::CustomAnyHash)
.def(refl::type_attr::kAnyEqual, &TFloat::CustomAnyEqual);
}
class TPrimExprObj : public Object {
public:
std::string dtype;
double value;
TPrimExprObj(std::string dtype, double value) : dtype(dtype), value(value) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TPrimExprObj>()
.def_rw("dtype", &TPrimExprObj::dtype, "dtype field", refl::default_value("float"))
.def_ro("value", &TPrimExprObj::value, "value field", refl::default_value(0))
.def("sub", [](TPrimExprObj* self, double other) -> double {
// this is ok because TPrimExprObj is declared asmutable
return self->value - other;
});
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.PrimExpr", TPrimExprObj, Object);
};
class TPrimExpr : public ObjectRef {
public:
explicit TPrimExpr(std::string dtype, double value) {
data_ = make_object<TPrimExprObj>(dtype, value);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TPrimExpr, ObjectRef, TPrimExprObj);
};
class TVarObj : public Object {
public:
std::string name;
TVarObj(std::string name) : name(name) {}
// need unsafe init constructor for json serialization
explicit TVarObj(UnsafeInit) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TVarObj>().def_ro("name", &TVarObj::name,
refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.Var", TVarObj, Object);
};
class TVar : public ObjectRef {
public:
explicit TVar(std::string name) { data_ = make_object<TVarObj>(name); }
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TVar, ObjectRef, TVarObj);
};
// FreeVar test object that has a sub-field referencing another FreeVar.
// This models the "var with nested vars" case (analogous to a relax::Var
// whose struct_info contains tir shape vars). It is used to exercise the
// difference between SEqHashDefRecursive and SEqHashDefNonRecursive at the
// FFI layer: under recursive semantics the nested ``dep`` var rebinds
// transitively; under non-recursive semantics it is treated as a use of an
// outer-scope binding and equality fails when no such outer binding exists.
class TVarWithDepObj : public Object {
public:
std::string name;
// Optional dependency var; when null, this object behaves like a plain
// FreeVar with no nested free vars.
Optional<ObjectRef> dep;
TVarWithDepObj(std::string name, Optional<ObjectRef> dep)
: name(std::move(name)), dep(std::move(dep)) {}
explicit TVarWithDepObj(UnsafeInit) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TVarWithDepObj>()
.def_ro("name", &TVarWithDepObj::name, refl::AttachFieldFlag::SEqHashIgnore())
// ``dep`` participates in structural equality without any def flag,
// so it is a USE position. Whether the FreeVar in ``dep`` may rebind
// is decided by the def flag on whichever outer field reaches this
// object.
.def_ro("dep", &TVarWithDepObj::dep);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.VarWithDep", TVarWithDepObj, Object);
};
class TVarWithDep : public ObjectRef {
public:
explicit TVarWithDep(std::string name, Optional<ObjectRef> dep = std::nullopt) {
data_ = make_object<TVarWithDepObj>(std::move(name), std::move(dep));
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TVarWithDep, ObjectRef, TVarWithDepObj);
};
// Holder with one recursive-def field and one non-recursive-def field.
// Used by StructuralEqualHash.NonRecursiveDef tests below.
class TDefHolderObj : public Object {
public:
TVarWithDep def_recursive;
TVarWithDep def_non_recursive;
TDefHolderObj(TVarWithDep def_recursive, TVarWithDep def_non_recursive)
: def_recursive(std::move(def_recursive)), def_non_recursive(std::move(def_non_recursive)) {}
explicit TDefHolderObj(UnsafeInit) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TDefHolderObj>()
.def_ro("def_recursive", &TDefHolderObj::def_recursive,
refl::AttachFieldFlag::SEqHashDefRecursive())
.def_ro("def_non_recursive", &TDefHolderObj::def_non_recursive,
refl::AttachFieldFlag::SEqHashDefNonRecursive());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.DefHolder", TDefHolderObj, Object);
};
class TDefHolder : public ObjectRef {
public:
explicit TDefHolder(TVarWithDep def_recursive, TVarWithDep def_non_recursive) {
data_ = make_object<TDefHolderObj>(std::move(def_recursive), std::move(def_non_recursive));
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TDefHolder, ObjectRef, TDefHolderObj);
};
class TFuncObj : public Object {
public:
Array<TVar> params;
Array<ObjectRef> body;
Optional<String> comment;
// need unsafe init constructor or default constructor for json serialization
explicit TFuncObj(UnsafeInit) {}
TFuncObj(Array<TVar> params, Array<ObjectRef> body, Optional<String> comment)
: params(params), body(body), comment(comment) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TFuncObj>()
.def_ro("params", &TFuncObj::params, refl::AttachFieldFlag::SEqHashDefRecursive())
.def_ro("body", &TFuncObj::body)
.def_ro("comment", &TFuncObj::comment, refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.Func", TFuncObj, Object);
};
class TFunc : public ObjectRef {
public:
explicit TFunc(Array<TVar> params, Array<ObjectRef> body, Optional<String> comment) {
data_ = make_object<TFuncObj>(params, body, comment);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TFunc, ObjectRef, TFuncObj);
};
class TCustomFuncObj : public Object {
public:
Array<TVar> params;
Array<ObjectRef> body;
String comment;
TCustomFuncObj(Array<TVar> params, Array<ObjectRef> body, String comment)
: params(params), body(body), comment(comment) {}
bool SEqual(const TCustomFuncObj* other,
ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> cmp) const {
if (!cmp(params, other->params, true, "params")) {
return false;
}
if (!cmp(body, other->body, false, "body")) {
return false;
}
return true;
}
int64_t SHash(int64_t init_hash, ffi::TypedFunction<int64_t(AnyView, int64_t, bool)> hash) const {
int64_t hash_value = init_hash;
hash_value = hash(params, hash_value, true);
hash_value = hash(body, hash_value, false);
return hash_value;
}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TCustomFuncObj>()
.def_ro("params", &TCustomFuncObj::params)
.def_ro("body", &TCustomFuncObj::body)
.def_ro("comment", &TCustomFuncObj::comment);
refl::TypeAttrDef<TCustomFuncObj>()
.def(refl::type_attr::kSEqual, &TCustomFuncObj::SEqual)
.def(refl::type_attr::kSHash, &TCustomFuncObj::SHash);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.CustomFunc", TCustomFuncObj, Object);
};
class TCustomFunc : public ObjectRef {
public:
explicit TCustomFunc(Array<TVar> params, Array<ObjectRef> body, String comment) {
data_ = make_object<TCustomFuncObj>(params, body, comment);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TCustomFunc, ObjectRef, TCustomFuncObj);
};
// Test object with all POD field types to exercise serialization of every field kind.
class TAllFieldsObj : public Object {
public:
bool v_bool;
int64_t v_int;
double v_float;
DLDataType v_dtype;
DLDevice v_device;
String v_str;
Optional<String> v_opt_str;
Array<Any> v_array;
Map<String, Any> v_map;
TAllFieldsObj(bool v_bool, int64_t v_int, double v_float, DLDataType v_dtype, DLDevice v_device,
String v_str, Optional<String> v_opt_str, Array<Any> v_array,
Map<String, Any> v_map)
: v_bool(v_bool),
v_int(v_int),
v_float(v_float),
v_dtype(v_dtype),
v_device(v_device),
v_str(v_str),
v_opt_str(v_opt_str),
v_array(v_array),
v_map(v_map) {}
explicit TAllFieldsObj(UnsafeInit) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TAllFieldsObj>()
.def_ro("v_bool", &TAllFieldsObj::v_bool)
.def_ro("v_int", &TAllFieldsObj::v_int)
.def_ro("v_float", &TAllFieldsObj::v_float)
.def_ro("v_dtype", &TAllFieldsObj::v_dtype)
.def_ro("v_device", &TAllFieldsObj::v_device)
.def_ro("v_str", &TAllFieldsObj::v_str)
.def_ro("v_opt_str", &TAllFieldsObj::v_opt_str)
.def_ro("v_array", &TAllFieldsObj::v_array)
.def_ro("v_map", &TAllFieldsObj::v_map);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.AllFields", TAllFieldsObj, Object);
};
class TAllFields : public ObjectRef {
public:
explicit TAllFields(bool v_bool, int64_t v_int, double v_float, DLDataType v_dtype,
DLDevice v_device, String v_str, Optional<String> v_opt_str,
Array<Any> v_array, Map<String, Any> v_map) {
data_ = make_object<TAllFieldsObj>(v_bool, v_int, v_float, v_dtype, v_device, v_str, v_opt_str,
v_array, v_map);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TAllFields, ObjectRef, TAllFieldsObj);
};
// Test object with fields that have default values to test deserialization with missing fields
class TWithDefaultsObj : public Object {
public:
int64_t required_val;
int64_t default_int;
String default_str;
bool default_bool;
TWithDefaultsObj(int64_t required_val, int64_t default_int, String default_str, bool default_bool)
: required_val(required_val),
default_int(default_int),
default_str(default_str),
default_bool(default_bool) {}
explicit TWithDefaultsObj(UnsafeInit) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TWithDefaultsObj>()
.def_ro("required_val", &TWithDefaultsObj::required_val)
.def_ro("default_int", &TWithDefaultsObj::default_int, refl::default_value(42))
.def_ro("default_str", &TWithDefaultsObj::default_str, refl::default_value("default"))
.def_ro("default_bool", &TWithDefaultsObj::default_bool, refl::default_value(true));
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("test.WithDefaults", TWithDefaultsObj, Object);
};
class TWithDefaults : public ObjectRef {
public:
explicit TWithDefaults(int64_t required_val, int64_t default_int = 42,
String default_str = "default", bool default_bool = true) {
data_ = make_object<TWithDefaultsObj>(required_val, default_int, default_str, default_bool);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TWithDefaults, ObjectRef, TWithDefaultsObj);
};
} // namespace testing
template <>
inline constexpr bool use_default_type_traits_v<testing::TPrimExpr> = true;
template <>
struct TypeTraits<testing::TPrimExpr>
: public ObjectRefWithFallbackTraitsBase<testing::TPrimExpr, StrictBool, int64_t, double,
String> {
TVM_FFI_INLINE static testing::TPrimExpr ConvertFallbackValue(StrictBool value) {
return testing::TPrimExpr("bool", static_cast<double>(value));
}
TVM_FFI_INLINE static testing::TPrimExpr ConvertFallbackValue(int64_t value) {
return testing::TPrimExpr("int64", static_cast<double>(value));
}
TVM_FFI_INLINE static testing::TPrimExpr ConvertFallbackValue(double value) {
return testing::TPrimExpr("float32", static_cast<double>(value));
}
// hack into the dtype to store string
TVM_FFI_INLINE static testing::TPrimExpr ConvertFallbackValue(String value) {
return testing::TPrimExpr(value, 0);
}
};
} // namespace ffi
} // namespace tvm
#endif // TVM_FFI_TESTING_OBJECT_H_