GitLab will be upgraded on June 2nd 2020 at 2.00 pm (AEDT) to 3.00 pm (AEDT) due to Critical Security Patch Availability. During the update, GitLab and Mattermost services will not be available. If you have any concerns with this, please talk to local Gitlab admin team.

Commit e7ec3053 authored by qinsoon's avatar qinsoon

more refactoring

parent 212638e9
......@@ -2,6 +2,8 @@ use ast::ptr::P;
use ast::op::{BinOp, CmpOp, AtomicRMWOp};
use ast::types::*;
use std::fmt;
pub type WPID = usize;
pub type MuID = usize;
pub type MuTag = &'static str;
......@@ -34,63 +36,82 @@ pub struct BlockContent {
pub keepalives: Option<Vec<P<TreeNode>>>
}
#[derive(Clone, Debug)]
#[derive(Clone)]
/// always use with P<TreeNode>
pub struct TreeNode {
pub v: TreeNodeKind,
pub id: MuID,
pub tag: MuTag,
pub v: TreeNode_,
pub children: Vec<P<TreeNode>>,
}
impl TreeNode {
pub fn new_value(v: P<Value>) -> P<TreeNode> {
P(TreeNode{v: TreeNodeKind::Value(v), children: vec![]})
pub fn new_ssa(id: MuID, tag: MuTag, ty: P<MuType>) -> P<TreeNode> {
P(TreeNode{
id: id,
tag: tag,
v: TreeNode_::Value(P(Value{ty: ty, v: Value_::SSAVar})),
children: vec![]})
}
pub fn new_constant(id: MuID, tag: MuTag, ty: P<MuType>, v: Constant) -> P<TreeNode> {
P(TreeNode{
id: id,
tag: tag,
v: TreeNode_::Value(P(Value{ty: ty, v: Value_::Constant(v)})),
children: vec![]
})
}
pub fn new_inst(v: Instruction) -> P<TreeNode> {
P(TreeNode{v: TreeNodeKind::Instruction(v), children: vec![]})
pub fn new_value(id: MuID, tag: MuTag, v: P<Value>) -> P<TreeNode> {
P(TreeNode{
id: id,
tag: tag,
v: TreeNode_::Value(v),
children: vec![]
}
)
}
pub fn new_inst(id: MuID, tag: MuTag, v: Instruction) -> P<TreeNode> {
P(TreeNode{id: id, tag: tag, v: TreeNode_::Instruction(v), children: vec![]})
}
}
impl fmt::Debug for TreeNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}#{:?}: {:?}", self.tag, self.id, self.v).unwrap();
for child in self.children.iter() {
write!(f, " -> {:?}#{:?}\n", child.tag, child.id).unwrap();
}
write!(f, "")
}
}
#[derive(Clone, Debug)]
pub enum TreeNodeKind {
pub enum TreeNode_ {
Value(P<Value>),
Instruction(Instruction),
}
/// always use with P<Value>
#[derive(Clone, Debug)]
pub enum Value {
SSAVar(SSAVar),
Constant(MuConstant)
}
impl Value {
pub fn new_ssa(v: SSAVar) -> P<Value> {
P(Value::SSAVar(v))
}
pub fn new_constnat(v: MuConstant) -> P<Value> {
P(Value::Constant(v))
}
}
#[derive(Clone, Debug)]
pub struct SSAVar {
pub id: MuID,
pub tag: MuTag,
pub ty: P<MuType_>
pub struct Value {
pub ty: P<MuType>,
pub v: Value_
}
#[derive(Clone, Debug)]
pub struct MuConstant{
pub ty: P<MuType_>,
pub val: Constant
pub enum Value_ {
SSAVar,
Constant(Constant)
}
#[derive(Clone, Debug)]
pub enum Constant {
Int(usize, usize),
IRef(P<MuType_>, Address),
IRef(P<MuType>, Address),
FloatV(f32),
DoubleV(f64),
VectorV(Vec<Constant>),
......@@ -210,20 +231,20 @@ pub enum Expression_ {
},
// yields a reference of the type
New(P<MuType_>),
New(P<MuType>),
// yields an iref of the type
AllocA(P<MuType_>),
AllocA(P<MuType>),
// yields ref
NewHybrid{ // hybrid type, var part length
ty: P<MuType_>,
ty: P<MuType>,
var_len: P<TreeNode>
},
// yields iref
AllocAHybrid{
ty: P<MuType_>,
ty: P<MuType>,
var_len: P<TreeNode>
},
......
......@@ -5,8 +5,10 @@ use ast::ir::*;
use std::collections::HashMap;
use std::sync::RwLock;
pub type MuType = MuType_;
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum MuType_ {
enum MuType_ {
/// int <length>
Int (usize),
/// float
......@@ -15,23 +17,23 @@ pub enum MuType_ {
Double,
/// ref<T>
Ref (P<MuType_>), // Box is needed for non-recursive enum
Ref (P<MuType>), // Box is needed for non-recursive enum
/// iref<T>: internal reference
IRef (P<MuType_>),
IRef (P<MuType>),
/// weakref<T>
WeakRef (P<MuType_>),
WeakRef (P<MuType>),
/// uptr<T>: unsafe pointer
UPtr (P<MuType_>),
UPtr (P<MuType>),
/// struct<T1 T2 ...>
Struct (MuTag),
/// array<T length>
Array (P<MuType_>, usize),
Array (P<MuType>, usize),
/// hybrid<F1 F2 ... V>: a hybrid of fixed length parts and a variable length part
Hybrid (Vec<P<MuType_>>, P<MuType_>),
Hybrid (Vec<P<MuType>>, P<MuType>),
/// void
Void,
......@@ -45,7 +47,7 @@ pub enum MuType_ {
Tagref64,
/// vector<T length>
Vector (P<MuType_>, usize),
Vector (P<MuType>, usize),
/// funcref<@sig>
FuncRef (P<MuFuncSig>),
......@@ -65,7 +67,7 @@ pub struct StructType_ {
}
impl StructType_ {
pub fn set_tys(&mut self, mut list: Vec<P<MuType_>>) {
pub fn set_tys(&mut self, mut list: Vec<P<MuType>>) {
self.tys.clear();
self.tys.append(&mut list);
}
......@@ -152,7 +154,7 @@ impl MuType_ {
}
/// is a type floating-point type?
pub fn is_fp(ty: &MuType_) -> bool {
pub fn is_fp(ty: &MuType) -> bool {
match *ty {
MuType_::Float | MuType_::Double => true,
_ => false
......@@ -160,7 +162,7 @@ pub fn is_fp(ty: &MuType_) -> bool {
}
/// is a type raw pointer?
pub fn is_ptr(ty: &MuType_) -> bool {
pub fn is_ptr(ty: &MuType) -> bool {
match *ty {
MuType_::UPtr(_) | MuType_::UFuncPtr(_) => true,
_ => false
......@@ -168,7 +170,7 @@ pub fn is_ptr(ty: &MuType_) -> bool {
}
/// is a type scalar type?
pub fn is_scalar(ty: &MuType_) -> bool {
pub fn is_scalar(ty: &MuType) -> bool {
match *ty {
MuType_::Int(_)
| MuType_::Float
......@@ -188,7 +190,7 @@ pub fn is_scalar(ty: &MuType_) -> bool {
/// is a type traced by the garbage collector?
/// Note: An aggregated type is traced if any of its part is traced.
pub fn is_traced(ty: &MuType_) -> bool {
pub fn is_traced(ty: &MuType) -> bool {
match *ty {
MuType_::Ref(_) => true,
MuType_::IRef(_) => true,
......@@ -217,7 +219,7 @@ pub fn is_traced(ty: &MuType_) -> bool {
/// is a type native safe?
/// Note: An aggregated type is native safe if all of its parts are native safe.
pub fn is_native_safe(ty: &MuType_) -> bool {
pub fn is_native_safe(ty: &MuType) -> bool {
match *ty {
MuType_::Int(_) => true,
MuType_::Float => true,
......@@ -255,6 +257,6 @@ macro_rules! is_type (
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct MuFuncSig {
pub ret_tys : Vec<P<MuType_>>,
pub arg_tys: Vec<P<MuType_>>
pub ret_tys : Vec<P<MuType>>,
pub arg_tys: Vec<P<MuType>>
}
\ No newline at end of file
......@@ -22,8 +22,10 @@ impl CompilerPass for TreeGenerationPass {
debug!(" block: {:?}", label);
for inst in block.content.take().unwrap().body {
debug!(" {:?}", inst);
}
debug!(" ");
}
}
}
\ No newline at end of file
......@@ -8,7 +8,7 @@ use std::cell::RefCell;
pub struct VMContext {
constants: HashMap<MuTag, P<Value>>,
types: HashMap<MuTag, P<MuType_>>,
types: HashMap<MuTag, P<MuType>>,
func_sigs: HashMap<MuTag, P<MuFuncSig>>,
funcs: HashMap<MuTag, RefCell<MuFunction>>
}
......@@ -23,16 +23,16 @@ impl VMContext {
}
}
pub fn declare_const(&mut self, const_name: MuTag, ty: P<MuType_>, val: Constant) -> P<Value> {
pub fn declare_const(&mut self, const_name: MuTag, ty: P<MuType>, val: Constant) -> P<Value> {
debug_assert!(!self.constants.contains_key(const_name));
let ret = P(Value::Constant(MuConstant{ty: ty, val: val}));
let ret = P(Value{ty: ty, v: Value_::Constant(val)});
self.constants.insert(const_name, ret.clone());
ret
}
pub fn declare_type(&mut self, type_name: MuTag, ty: P<MuType_>) -> P<MuType_> {
pub fn declare_type(&mut self, type_name: MuTag, ty: P<MuType>) -> P<MuType> {
debug_assert!(!self.types.contains_key(type_name));
self.types.insert(type_name, ty.clone());
......@@ -40,7 +40,7 @@ impl VMContext {
ty
}
pub fn declare_func_sig(&mut self, sig_name: MuTag, ret_tys: Vec<P<MuType_>>, arg_tys: Vec<P<MuType_>>) -> P<MuFuncSig> {
pub fn declare_func_sig(&mut self, sig_name: MuTag, ret_tys: Vec<P<MuType>>, arg_tys: Vec<P<MuType>>) -> P<MuFuncSig> {
debug_assert!(!self.func_sigs.contains_key(sig_name));
let ret = P(MuFuncSig{ret_tys: ret_tys, arg_tys: arg_tys});
......
......@@ -23,13 +23,13 @@ pub fn factorial() -> VMContext {
// .typedef @void = void
// .typedef @int_8 = int<8>
// .typedef @int_32 = int<32>
let type_def_int64 = vm.declare_type("int_64", P(MuType_::int(64)));
let type_def_int1 = vm.declare_type("int_1", P(MuType_::int(1)));
let type_def_float = vm.declare_type("float", P(MuType_::float()));
let type_def_double = vm.declare_type("double", P(MuType_::double()));
let type_def_void = vm.declare_type("void", P(MuType_::void()));
let type_def_int8 = vm.declare_type("int8", P(MuType_::int(8)));
let type_def_int32 = vm.declare_type("int32", P(MuType_::int(32)));
let type_def_int64 = vm.declare_type("int_64", P(MuType::int(64)));
let type_def_int1 = vm.declare_type("int_1", P(MuType::int(1)));
let type_def_float = vm.declare_type("float", P(MuType::float()));
let type_def_double = vm.declare_type("double", P(MuType::double()));
let type_def_void = vm.declare_type("void", P(MuType::void()));
let type_def_int8 = vm.declare_type("int8", P(MuType::int(8)));
let type_def_int32 = vm.declare_type("int32", P(MuType::int(32)));
// .const @int_64_1 <@int_64> = 1
let const_def_int64_1 = vm.declare_const("int64_1", type_def_int64.clone(), Constant::Int(64, 1));
......@@ -38,24 +38,24 @@ pub fn factorial() -> VMContext {
let fac_sig = vm.declare_func_sig("fac_sig", vec![type_def_int64.clone()], vec![type_def_int64.clone()]);
// .funcdef @fac VERSION @fac_v1 <@fac_sig>
let fac_func_ref = P(MuType_::funcref(fac_sig.clone()));
let fac_func_ref = P(MuType::funcref(fac_sig.clone()));
// %blk_0(<@int_64> %n_3):
let mut blk_0 = Block::new("blk_0");
let blk_0_n_3 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 0, tag: "n_3", ty: type_def_int64.clone()}));
let const_def_int64_1_local = TreeNode::new_value(const_def_int64_1.clone());
let blk_0_n_3 = TreeNode::new_ssa(0, "n_3", type_def_int64.clone());
let const_def_int64_1_local = TreeNode::new_value(1, "int64_1", const_def_int64_1.clone());
// %v48 = EQ <@int_64> %n_3 @int_64_1
let blk_0_v48 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 1, tag: "v48", ty: type_def_int64.clone()}));
let blk_0_v48 = TreeNode::new_ssa(2, "v48", type_def_int64.clone());
let blk_0_v48_expr = Expression_::CmpOp(
CmpOp::EQ,
blk_0_n_3.clone(),
const_def_int64_1_local.clone()
);
let blk_0_inst0 = TreeNode::new_inst(Instruction::NonTerm(NonTermInstruction::Assign{left: vec![blk_0_v48.clone()], right: blk_0_v48_expr}));
let blk_0_inst0 = TreeNode::new_inst(3, "blk_0_inst0", Instruction::NonTerm(NonTermInstruction::Assign{left: vec![blk_0_v48.clone()], right: blk_0_v48_expr}));
// BRANCH2 %v48 %blk_2(@int_64_1) %blk_1(%n_3)
let blk_0_term = TreeNode::new_inst(Instruction::Term(Terminal::Branch2{
let blk_0_term = TreeNode::new_inst(4, "blk_0_term", Instruction::Term(Terminal::Branch2{
cond: blk_0_v48.clone(),
true_dest: Destination {
target: "blk_2",
......@@ -76,10 +76,10 @@ pub fn factorial() -> VMContext {
// %blk_2(<@int_64> %v53):
let mut blk_2 = Block::new("blk_2");
let blk_2_v53 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 2, tag: "v53", ty: type_def_int64.clone()}));
let blk_2_v53 = TreeNode::new_ssa(5, "v53", type_def_int64.clone());
// RET %v53
let blk_2_term = TreeNode::new_inst(Instruction::Term(Terminal::Return(vec![blk_2_v53.clone()])));
let blk_2_term = TreeNode::new_inst(6, "blk_2_term", Instruction::Term(Terminal::Return(vec![blk_2_v53.clone()])));
let blk_2_content = BlockContent {
args: vec![blk_2_v53.clone()],
......@@ -90,24 +90,24 @@ pub fn factorial() -> VMContext {
// %blk_1(<@int_64> %n_3):
let mut blk_1 = Block::new("blk_1");
let blk_1_n_3 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 3, tag: "n_3", ty: type_def_int64.clone()}));
let blk_1_n_3 = TreeNode::new_ssa(7, "n_3", type_def_int64.clone());
// %v50 = SUB <@int_64> %n_3 @int_64_1
let blk_1_v50 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 4, tag: "v50", ty: type_def_int64.clone()}));
let blk_1_v50 = TreeNode::new_ssa(8, "v50", type_def_int64.clone());
let blk_1_v50_expr = Expression_::BinOp(
BinOp::Sub,
blk_1_n_3.clone(),
const_def_int64_1_local.clone()
);
let blk_1_inst0 = TreeNode::new_inst(Instruction::NonTerm(NonTermInstruction::Assign{left: vec![blk_1_v50.clone()], right: blk_1_v50_expr}));
let blk_1_inst0 = TreeNode::new_inst(9, "blk_1_inst0", Instruction::NonTerm(NonTermInstruction::Assign{left: vec![blk_1_v50.clone()], right: blk_1_v50_expr}));
// %v51 = CALL <@fac_sig> @fac (%v50)
let blk_1_v51 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 5, tag: "v51", ty: type_def_int64.clone()}));
let blk_1_inst1 = TreeNode::new_inst(Instruction::NonTerm(NonTermInstruction::Assign{
let blk_1_v51 = TreeNode::new_ssa(10, "v51", type_def_int64.clone());
let blk_1_inst1 = TreeNode::new_inst(11, "blk_1_inst1", Instruction::NonTerm(NonTermInstruction::Assign{
left: vec![blk_1_v51.clone()],
right: Expression_::ExprCall {
data: CallData {
func: TreeNode::new_value(Value::new_ssa(SSAVar{id: 6, tag: "fac", ty: fac_func_ref.clone()})),
func: TreeNode::new_ssa(12, "fac", fac_func_ref.clone()),
args: vec![blk_1_v50.clone()],
convention: CallConvention::Mu
},
......@@ -116,18 +116,18 @@ pub fn factorial() -> VMContext {
}));
// %v52 = MUL <@int_64> %n_3 %v51
let blk_1_v52 = TreeNode::new_value(Value::new_ssa(SSAVar{id: 9, tag: "v52", ty: type_def_int64.clone()}));
let blk_1_v52 = TreeNode::new_ssa(13, "v52", type_def_int64.clone());
let blk_1_v52_expr = Expression_::BinOp(
BinOp::Mul,
blk_1_n_3.clone(),
blk_1_v51.clone()
);
let blk_1_inst2 = TreeNode::new_inst(Instruction::NonTerm(NonTermInstruction::Assign{
let blk_1_inst2 = TreeNode::new_inst(14, "blk_1_inst2", Instruction::NonTerm(NonTermInstruction::Assign{
left: vec![blk_1_v52.clone()],
right: blk_1_v52_expr
}));
let blk_1_term = TreeNode::new_inst(Instruction::Term(Terminal::Branch1 (
let blk_1_term = TreeNode::new_inst(15, "blk_1_term", Instruction::Term(Terminal::Branch1 (
Destination {
target: "blk_2",
args: vec![DestArg::Normal(blk_1_v52.clone())]
......
......@@ -15,61 +15,61 @@ macro_rules! println_type (
)
);
/// create one of each MuType_
fn create_types() -> Vec<P<MuType_>> {
/// create one of each MuType
fn create_types() -> Vec<P<MuType>> {
let mut types = vec![];
let t0 = MuType_::int(8);
let t0 = MuType::int(8);
types.push(P(t0));
let t1 = MuType_::float();
let t1 = MuType::float();
types.push(P(t1));
let t2 = MuType_::double();
let t2 = MuType::double();
types.push(P(t2));
let t3 = MuType_::muref(types[0].clone());
let t3 = MuType::muref(types[0].clone());
types.push(P(t3));
let t4 = MuType_::iref(types[0].clone());
let t4 = MuType::iref(types[0].clone());
types.push(P(t4));
let t5 = MuType_::weakref(types[0].clone());
let t5 = MuType::weakref(types[0].clone());
types.push(P(t5));
let t6 = MuType_::uptr(types[0].clone());
let t6 = MuType::uptr(types[0].clone());
types.push(P(t6));
let t7 = MuType_::mustruct("MyStructTag1", vec![types[0].clone(), types[1].clone()]);
let t7 = MuType::mustruct("MyStructTag1", vec![types[0].clone(), types[1].clone()]);
types.push(P(t7));
let t8 = MuType_::array(types[0].clone(), 5);
let t8 = MuType::array(types[0].clone(), 5);
types.push(P(t8));
let t9 = MuType_::hybrid(vec![types[7].clone(), types[1].clone()], types[0].clone());
let t9 = MuType::hybrid(vec![types[7].clone(), types[1].clone()], types[0].clone());
types.push(P(t9));
let t10 = MuType_::void();
let t10 = MuType::void();
types.push(P(t10));
let t11 = MuType_::threadref();
let t11 = MuType::threadref();
types.push(P(t11));
let t12 = MuType_::stackref();
let t12 = MuType::stackref();
types.push(P(t12));
let t13 = MuType_::tagref64();
let t13 = MuType::tagref64();
types.push(P(t13));
let t14 = MuType_::vector(types[0].clone(), 5);
let t14 = MuType::vector(types[0].clone(), 5);
types.push(P(t14));
let sig = P(MuFuncSig{ret_tys: vec![types[10].clone()], arg_tys: vec![types[0].clone(), types[0].clone()]});
let t15 = MuType_::funcref(sig.clone());
let t15 = MuType::funcref(sig.clone());
types.push(P(t15));
let t16 = MuType_::ufuncptr(sig.clone());
let t16 = MuType::ufuncptr(sig.clone());
types.push(P(t16));
types
......@@ -107,9 +107,9 @@ fn test_type_constructors() {
#[test]
fn test_cyclic_struct() {
// .typedef @cyclic_struct_ty = struct<ref<@cyclic_struct_ty> int<32>>
let ty = P(MuType_::mustruct_empty("MyStructTag2"));
let ref_ty = P(MuType_::muref(ty.clone()));
let i32_ty = P(MuType_::int(32));
let ty = P(MuType::mustruct_empty("MyStructTag2"));
let ref_ty = P(MuType::muref(ty.clone()));
let i32_ty = P(MuType::int(32));
{
STRUCT_TAG_MAP.write().unwrap().
......@@ -133,17 +133,17 @@ fn test_is_traced() {
assert_eq!(is_traced(&types[5]), true);
assert_eq!(is_traced(&types[6]), false);
assert_eq!(is_traced(&types[7]), false);
let struct3 = MuType_::mustruct("MyStructTag3", vec![types[3].clone(), types[0].clone()]);
let struct3 = MuType::mustruct("MyStructTag3", vec![types[3].clone(), types[0].clone()]);
assert_eq!(is_traced(&struct3), true);
let struct4 = MuType_::mustruct("MyStructTag4", vec![types[3].clone(), types[4].clone()]);
let struct4 = MuType::mustruct("MyStructTag4", vec![types[3].clone(), types[4].clone()]);
assert_eq!(is_traced(&struct4), true);
assert_eq!(is_traced(&types[8]), false);
let ref_array = MuType_::array(types[3].clone(), 5);
let ref_array = MuType::array(types[3].clone(), 5);
assert_eq!(is_traced(&ref_array), true);
assert_eq!(is_traced(&types[9]), false);
let fix_ref_hybrid = MuType_::hybrid(vec![types[3].clone(), types[0].clone()], types[0].clone());
let fix_ref_hybrid = MuType::hybrid(vec![types[3].clone(), types[0].clone()], types[0].clone());
assert_eq!(is_traced(&fix_ref_hybrid), true);
let var_ref_hybrid = MuType_::hybrid(vec![types[0].clone(), types[1].clone()], types[3].clone());
let var_ref_hybrid = MuType::hybrid(vec![types[0].clone(), types[1].clone()], types[3].clone());
assert_eq!(is_traced(&var_ref_hybrid), true);
assert_eq!(is_traced(&types[10]), false);
assert_eq!(is_traced(&types[11]), true);
......@@ -166,17 +166,17 @@ fn test_is_native_safe() {
assert_eq!(is_native_safe(&types[5]), false);
assert_eq!(is_native_safe(&types[6]), true);
assert_eq!(is_native_safe(&types[7]), true);
let struct3 = MuType_::mustruct("MyStructTag3", vec![types[3].clone(), types[0].clone()]);
let struct3 = MuType::mustruct("MyStructTag3", vec![types[3].clone(), types[0].clone()]);
assert_eq!(is_native_safe(&struct3), false);
let struct4 = MuType_::mustruct("MyStructTag4", vec![types[3].clone(), types[4].clone()]);
let struct4 = MuType::mustruct("MyStructTag4", vec![types[3].clone(), types[4].clone()]);
assert_eq!(is_native_safe(&struct4), false);
assert_eq!(is_native_safe(&types[8]), true);
let ref_array = MuType_::array(types[3].clone(), 5);
let ref_array = MuType::array(types[3].clone(), 5);
assert_eq!(is_native_safe(&ref_array), false);
assert_eq!(is_native_safe(&types[9]), true);
let fix_ref_hybrid = MuType_::hybrid(vec![types[3].clone(), types[0].clone()], types[0].clone());
let fix_ref_hybrid = MuType::hybrid(vec![types[3].clone(), types[0].clone()], types[0].clone());
assert_eq!(is_native_safe(&fix_ref_hybrid), false);
let var_ref_hybrid = MuType_::hybrid(vec![types[0].clone(), types[1].clone()], types[3].clone());
let var_ref_hybrid = MuType::hybrid(vec![types[0].clone(), types[1].clone()], types[3].clone());
assert_eq!(is_native_safe(&var_ref_hybrid), false);
assert_eq!(is_native_safe(&types[10]), true);
assert_eq!(is_native_safe(&types[11]), false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment