From e73b939de0f04f85b0a7a98bef30e11637cd9807 Mon Sep 17 00:00:00 2001 From: sofia Date: Tue, 22 Jul 2025 14:05:50 +0300 Subject: [PATCH] Add all cast instructions to lib --- reid-llvm-lib/src/builder.rs | 138 ++++++++++++++++++++- reid-llvm-lib/src/compile.rs | 72 +++++++++++ reid-llvm-lib/src/fmt.rs | 36 ++++++ reid-llvm-lib/src/lib.rs | 224 ++++++++++++++++++----------------- 4 files changed, 362 insertions(+), 108 deletions(-) diff --git a/reid-llvm-lib/src/builder.rs b/reid-llvm-lib/src/builder.rs index 645a568..47c3c3b 100644 --- a/reid-llvm-lib/src/builder.rs +++ b/reid-llvm-lib/src/builder.rs @@ -4,8 +4,8 @@ use std::{cell::RefCell, rc::Rc}; use crate::{ - BlockData, CustomTypeKind, FunctionData, Instr, InstructionData, ModuleData, NamedStruct, - TerminatorKind, Type, TypeCategory, TypeData, + Block, BlockData, CustomTypeKind, FunctionData, Instr, InstructionData, ModuleData, + NamedStruct, TerminatorKind, Type, TypeCategory, TypeData, debug_information::{ DebugInformation, DebugLocationValue, DebugMetadataValue, DebugProgramValue, InstructionDebugRecordData, @@ -467,6 +467,18 @@ impl Builder { _ => Err(()), } } + Instr::Trunc(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::ZExt(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::SExt(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::FPTrunc(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::FPExt(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::FPToUI(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::FPToSI(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::UIToFP(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::SIToFP(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::PtrToInt(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::IntToPtr(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), + Instr::BitCast(instr, ty) => instr.cast_to(self, &ty).map(|_| ()), } } } @@ -505,3 +517,125 @@ impl Builder { } } } + +impl InstructionValue { + pub fn with_location(self, block: &Block, location: DebugLocationValue) -> InstructionValue { + unsafe { + block.builder.add_instruction_location(&self, location); + } + self + } + + pub fn maybe_location( + self, + block: &mut Block, + location: Option, + ) -> InstructionValue { + unsafe { + if let Some(location) = location { + block.builder.add_instruction_location(&self, location); + } + } + self + } + + pub fn add_record(&self, block: &mut Block, record: InstructionDebugRecordData) { + unsafe { + block.builder.add_instruction_record(self, record); + } + } + + pub(crate) fn get_type(&self, builder: &Builder) -> Result { + use Instr::*; + unsafe { + match &builder.instr_data(self).kind { + Param(nth) => builder + .function_data(&self.0.0) + .params + .get(*nth) + .cloned() + .ok_or(()), + Constant(c) => Ok(c.get_type()), + Add(lhs, rhs) => match_types(lhs, rhs, &builder), + FAdd(lhs, rhs) => match_types(lhs, rhs, &builder), + Sub(lhs, rhs) => match_types(lhs, rhs, &builder), + FSub(lhs, rhs) => match_types(lhs, rhs, &builder), + Mul(lhs, rhs) => match_types(lhs, rhs, &builder), + FMul(lhs, rhs) => match_types(lhs, rhs, &builder), + UDiv(lhs, rhs) => match_types(lhs, rhs, &builder), + SDiv(lhs, rhs) => match_types(lhs, rhs, &builder), + FDiv(lhs, rhs) => match_types(lhs, rhs, &builder), + URem(lhs, rhs) => match_types(lhs, rhs, &builder), + SRem(lhs, rhs) => match_types(lhs, rhs, &builder), + FRem(lhs, rhs) => match_types(lhs, rhs, &builder), + And(lhs, rhs) => match_types(lhs, rhs, &builder), + ICmp(_, _, _) => Ok(Type::Bool), + FCmp(_, _, _) => Ok(Type::Bool), + FunctionCall(function_value, _) => Ok(builder.function_data(function_value).ret), + Phi(values) => values.first().ok_or(()).and_then(|v| v.get_type(&builder)), + Alloca(ty) => Ok(Type::Ptr(Box::new(ty.clone()))), + Load(_, ty) => Ok(ty.clone()), + Store(_, value) => value.get_type(builder), + ArrayAlloca(ty, _) => Ok(Type::Ptr(Box::new(ty.clone()))), + GetElemPtr(instr, _) => { + let instr_ty = instr.get_type(builder)?; + let Type::Ptr(inner_ty) = &instr_ty else { + panic!("GetStructElemPtr on non-pointer! ({:?})", &instr_ty) + }; + match *inner_ty.clone() { + Type::Array(elem_ty, _) => Ok(Type::Ptr(Box::new(*elem_ty.clone()))), + _ => Ok(instr_ty), + } + } + GetStructElemPtr(instr, idx) => { + let instr_ty = instr.get_type(builder)?; + let Type::Ptr(inner_ty) = instr_ty else { + panic!("GetStructElemPtr on non-pointer! ({:?})", &instr_ty) + }; + let Type::CustomType(ty_value) = *inner_ty else { + panic!("GetStructElemPtr on non-struct! ({:?})", &inner_ty) + }; + let field_ty = match builder.type_data(&ty_value).kind { + CustomTypeKind::NamedStruct(NamedStruct(_, fields)) => { + fields.get_unchecked(*idx as usize).clone() + } + }; + Ok(Type::Ptr(Box::new(field_ty))) + } + ExtractValue(instr, idx) => { + let instr_ty = instr.get_type(builder)?; + Ok(match instr_ty { + Type::CustomType(struct_ty) => { + let data = builder.type_data(&struct_ty); + match data.kind { + CustomTypeKind::NamedStruct(named_struct) => { + named_struct.1.get(*idx as usize).unwrap().clone() + } + } + } + Type::Array(elem_ty, _) => *elem_ty.clone(), + _ => return Err(()), + }) + } + Trunc(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + ZExt(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + SExt(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + FPTrunc(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + FPExt(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + FPToUI(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + FPToSI(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + UIToFP(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + SIToFP(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + PtrToInt(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + IntToPtr(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + BitCast(instr, ty) => instr.cast_to(builder, ty).map(|_| ty.clone()), + } + } + } + + fn cast_to(&self, builder: &Builder, ty: &Type) -> Result { + self.get_type(builder)? + .cast_instruction(*self, &ty) + .ok_or(()) + } +} diff --git a/reid-llvm-lib/src/compile.rs b/reid-llvm-lib/src/compile.rs index bfb8d12..6063cc5 100644 --- a/reid-llvm-lib/src/compile.rs +++ b/reid-llvm-lib/src/compile.rs @@ -943,6 +943,78 @@ impl InstructionHolder { *idx, name.as_ptr(), ), + Trunc(instr_val, ty) => LLVMBuildTrunc( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + ZExt(instr_val, ty) => LLVMBuildZExt( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + SExt(instr_val, ty) => LLVMBuildSExt( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + FPTrunc(instr_val, ty) => LLVMBuildTrunc( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + FPExt(instr_val, ty) => LLVMBuildFPExt( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + FPToUI(instr_val, ty) => LLVMBuildFPToUI( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + FPToSI(instr_val, ty) => LLVMBuildFPToSI( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + UIToFP(instr_val, ty) => LLVMBuildUIToFP( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + SIToFP(instr_val, ty) => LLVMBuildSIToFP( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + PtrToInt(instr_val, ty) => LLVMBuildPtrToInt( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + IntToPtr(instr_val, ty) => LLVMBuildIntToPtr( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), + BitCast(instr_val, ty) => LLVMBuildBitCast( + module.builder_ref, + module.values.get(instr_val).unwrap().value_ref, + ty.as_llvm(module.context_ref, &module.types), + name.as_ptr(), + ), } }; if let Some(record) = &self.record { diff --git a/reid-llvm-lib/src/fmt.rs b/reid-llvm-lib/src/fmt.rs index 1ebfb05..d3ec0b9 100644 --- a/reid-llvm-lib/src/fmt.rs +++ b/reid-llvm-lib/src/fmt.rs @@ -342,6 +342,42 @@ impl Debug for Instr { Instr::ExtractValue(instruction_value, index) => { fmt_index(f, instruction_value, &index.to_string()) } + Instr::Trunc(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::ZExt(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::SExt(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::FPTrunc(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::FPExt(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::FPToUI(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::FPToSI(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::UIToFP(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::SIToFP(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::PtrToInt(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::IntToPtr(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } + Instr::BitCast(instr_val, ty) => { + write!(f, "{:?} to {:?} ({})", instr_val, ty, self.default_name()) + } } } } diff --git a/reid-llvm-lib/src/lib.rs b/reid-llvm-lib/src/lib.rs index 232d415..bd5a419 100644 --- a/reid-llvm-lib/src/lib.rs +++ b/reid-llvm-lib/src/lib.rs @@ -233,6 +233,18 @@ impl Instr { Instr::ICmp(..) => "icmp", Instr::FunctionCall(..) => "call", Instr::FCmp(_, _, _) => "fcmp", + Instr::Trunc(_, _) => "trunc", + Instr::ZExt(_, _) => "zext", + Instr::SExt(_, _) => "sext", + Instr::FPTrunc(_, _) => "fptrunc", + Instr::FPExt(_, _) => "pfext", + Instr::FPToUI(_, _) => "fptoui", + Instr::FPToSI(_, _) => "fptosi", + Instr::UIToFP(_, _) => "uitofp", + Instr::SIToFP(_, _) => "sitofp", + Instr::PtrToInt(_, _) => "ptrtoint", + Instr::IntToPtr(_, _) => "inttoptr", + Instr::BitCast(_, _) => "bitcast", } } } @@ -310,34 +322,6 @@ impl<'builder> Block<'builder> { } } -impl InstructionValue { - pub fn with_location(self, block: &Block, location: DebugLocationValue) -> InstructionValue { - unsafe { - block.builder.add_instruction_location(&self, location); - } - self - } - - pub fn maybe_location( - self, - block: &mut Block, - location: Option, - ) -> InstructionValue { - unsafe { - if let Some(location) = location { - block.builder.add_instruction_location(&self, location); - } - } - self - } - - pub fn add_record(&self, block: &mut Block, record: InstructionDebugRecordData) { - unsafe { - block.builder.add_instruction_record(self, record); - } - } -} - #[derive(Clone)] pub struct InstructionData { kind: Instr, @@ -355,6 +339,7 @@ pub enum CmpPredicate { NE, } +/// https://llvm.org/docs/LangRef.html#instruction-reference #[derive(Clone)] pub enum Instr { Param(usize), @@ -397,8 +382,56 @@ pub enum Instr { /// Integer Comparison ICmp(CmpPredicate, InstructionValue, InstructionValue), + /// FLoat Comparison FCmp(CmpPredicate, InstructionValue, InstructionValue), + /// The `trunc` instruction truncates the high order bits in value and + /// converts the remaining bits to ty2. Since the source size must be larger + /// than the destination size, `trunc` cannot be a no-op cast. It will + /// always truncate bits. + Trunc(InstructionValue, Type), + /// The `zext` fills the high order bits of the value with zero bits until + /// it reaches the size of the destination type, ty2. + ZExt(InstructionValue, Type), + /// The `sext` instruction performs a sign extension by copying the sign bit + /// (highest order bit) of the value until it reaches the bit size of the + /// type ty2. + SExt(InstructionValue, Type), + /// The `fptrunc` instruction casts a value from a larger floating-point + /// type to a smaller floating-point type. + FPTrunc(InstructionValue, Type), + /// The `fpext` instruction extends the value from a smaller floating-point + /// type to a larger floating-point type. + FPExt(InstructionValue, Type), + /// The `fptoui` instruction takes a value to cast, which must be a scalar + /// or vector floating-point value, and a type to cast it to ty2, which must + /// be an integer type. + FPToUI(InstructionValue, Type), + /// The `fptosi` instruction takes a value to cast, which must be a scalar + /// or vector floating-point value, and a type to cast it to ty2, which must + /// be an integer type. + FPToSI(InstructionValue, Type), + /// The `uitofp` instruction takes a value to cast, which must be a scalar + /// or vector integer value, and a type to cast it to ty2, which must be an + /// floating-point type. + UIToFP(InstructionValue, Type), + /// The `sitofp` instruction takes a value to cast, which must be a scalar + /// or vector integer value, and a type to cast it to ty2, which must be an + /// floating-point type + SIToFP(InstructionValue, Type), + /// The `ptrtoint` instruction converts value to integer type ty2 by + /// interpreting the all pointer representation bits as an integer + /// (equivalent to a bitcast) and either truncating or zero extending that + /// value to the size of the integer type. + PtrToInt(InstructionValue, Type), + /// The `inttoptr` instruction converts value to type ty2 by applying either + /// a zero extension or a truncation depending on the size of the integer + /// value. + IntToPtr(InstructionValue, Type), + /// The `bitcast` instruction converts value to type ty2. It is always a + /// no-op cast because no bits change with this conversion. + BitCast(InstructionValue, Type), + FunctionCall(FunctionValue, Vec), } @@ -473,84 +506,6 @@ pub enum CustomTypeKind { #[derive(Debug, PartialEq, Eq, Clone, Hash)] pub struct NamedStruct(pub String, pub Vec); -impl InstructionValue { - pub(crate) fn get_type(&self, builder: &Builder) -> Result { - use Instr::*; - unsafe { - match &builder.instr_data(self).kind { - Param(nth) => builder - .function_data(&self.0.0) - .params - .get(*nth) - .cloned() - .ok_or(()), - Constant(c) => Ok(c.get_type()), - Add(lhs, rhs) => match_types(lhs, rhs, &builder), - FAdd(lhs, rhs) => match_types(lhs, rhs, &builder), - Sub(lhs, rhs) => match_types(lhs, rhs, &builder), - FSub(lhs, rhs) => match_types(lhs, rhs, &builder), - Mul(lhs, rhs) => match_types(lhs, rhs, &builder), - FMul(lhs, rhs) => match_types(lhs, rhs, &builder), - UDiv(lhs, rhs) => match_types(lhs, rhs, &builder), - SDiv(lhs, rhs) => match_types(lhs, rhs, &builder), - FDiv(lhs, rhs) => match_types(lhs, rhs, &builder), - URem(lhs, rhs) => match_types(lhs, rhs, &builder), - SRem(lhs, rhs) => match_types(lhs, rhs, &builder), - FRem(lhs, rhs) => match_types(lhs, rhs, &builder), - And(lhs, rhs) => match_types(lhs, rhs, &builder), - ICmp(_, _, _) => Ok(Type::Bool), - FCmp(_, _, _) => Ok(Type::Bool), - FunctionCall(function_value, _) => Ok(builder.function_data(function_value).ret), - Phi(values) => values.first().ok_or(()).and_then(|v| v.get_type(&builder)), - Alloca(ty) => Ok(Type::Ptr(Box::new(ty.clone()))), - Load(_, ty) => Ok(ty.clone()), - Store(_, value) => value.get_type(builder), - ArrayAlloca(ty, _) => Ok(Type::Ptr(Box::new(ty.clone()))), - GetElemPtr(instr, _) => { - let instr_ty = instr.get_type(builder)?; - let Type::Ptr(inner_ty) = &instr_ty else { - panic!("GetStructElemPtr on non-pointer! ({:?})", &instr_ty) - }; - match *inner_ty.clone() { - Type::Array(elem_ty, _) => Ok(Type::Ptr(Box::new(*elem_ty.clone()))), - _ => Ok(instr_ty), - } - } - GetStructElemPtr(instr, idx) => { - let instr_ty = instr.get_type(builder)?; - let Type::Ptr(inner_ty) = instr_ty else { - panic!("GetStructElemPtr on non-pointer! ({:?})", &instr_ty) - }; - let Type::CustomType(ty_value) = *inner_ty else { - panic!("GetStructElemPtr on non-struct! ({:?})", &inner_ty) - }; - let field_ty = match builder.type_data(&ty_value).kind { - CustomTypeKind::NamedStruct(NamedStruct(_, fields)) => { - fields.get_unchecked(*idx as usize).clone() - } - }; - Ok(Type::Ptr(Box::new(field_ty))) - } - ExtractValue(instr, idx) => { - let instr_ty = instr.get_type(builder)?; - Ok(match instr_ty { - Type::CustomType(struct_ty) => { - let data = builder.type_data(&struct_ty); - match data.kind { - CustomTypeKind::NamedStruct(named_struct) => { - named_struct.1.get(*idx as usize).unwrap().clone() - } - } - } - Type::Array(elem_ty, _) => *elem_ty.clone(), - _ => return Err(()), - }) - } - } - } - } -} - impl ConstValue { pub fn get_type(&self) -> Type { use Type::*; @@ -657,6 +612,63 @@ impl Type { } } } + + pub fn cast_instruction(&self, value: InstructionValue, other: &Type) -> Option { + use Type::*; + match (self, other) { + (I8, I16 | I32 | I64 | I128) => Some(Instr::SExt(value, other.clone())), + (I16, I32 | I64 | I128) => Some(Instr::SExt(value, other.clone())), + (I32, I64 | I128) => Some(Instr::SExt(value, other.clone())), + (I64, I128) => Some(Instr::SExt(value, other.clone())), + (I128 | U128, I64 | U64 | I32 | U32 | I16 | U16 | I8 | U8) => { + Some(Instr::Trunc(value, other.clone())) + } + (I64 | U64, I32 | U32 | I16 | U16 | I8 | U8) => { + Some(Instr::Trunc(value, other.clone())) + } + (I32 | U32, I16 | U16 | I8 | U8) => Some(Instr::Trunc(value, other.clone())), + (I16 | U16, I8 | U8) => Some(Instr::Trunc(value, other.clone())), + (U8 | I8, U16 | I16 | U32 | I32 | U64 | I64 | U128 | I128) => { + Some(Instr::ZExt(value, other.clone())) + } + (U16 | I16, U32 | I32 | U64 | I64 | U128 | I128) => { + Some(Instr::ZExt(value, other.clone())) + } + (U32 | I32, U64 | I64 | U128 | I128) => Some(Instr::ZExt(value, other.clone())), + (U64 | I64, U128 | I128) => Some(Instr::ZExt(value, other.clone())), + (U8 | U16 | U32 | U64 | U128, F16 | F32 | F32B | F64 | F80 | F128 | F128PPC) => { + Some(Instr::UIToFP(value, other.clone())) + } + (I8 | I16 | I32 | I64 | I128, F16 | F32 | F32B | F64 | F80 | F128 | F128PPC) => { + Some(Instr::SIToFP(value, other.clone())) + } + (F16 | F32 | F32B | F64 | F80 | F128 | F128PPC, U8 | U16 | U32 | U64 | U128) => { + Some(Instr::FPToUI(value, other.clone())) + } + (F16 | F32 | F32B | F64 | F80 | F128 | F128PPC, I8 | I16 | I32 | I64 | I128) => { + Some(Instr::FPToSI(value, other.clone())) + } + (I128 | U128 | I64 | U64 | I32 | U32 | I16 | U16 | I8 | U8, Ptr(_)) => { + Some(Instr::IntToPtr(value, other.clone())) + } + (Ptr(_), I128 | U128 | I64 | U64 | I32 | U32 | I16 | U16 | I8 | U8) => { + Some(Instr::PtrToInt(value, other.clone())) + } + (F16, F32 | F32B | F64 | F80 | F128 | F128PPC) => { + Some(Instr::FPExt(value, other.clone())) + } + (F32 | F32B, F64 | F80 | F128 | F128PPC) => Some(Instr::FPExt(value, other.clone())), + (F64, F80 | F128 | F128PPC) => Some(Instr::FPExt(value, other.clone())), + (F80, F128 | F128PPC) => Some(Instr::FPExt(value, other.clone())), + (F128PPC | F128, F80 | F64 | F32B | F32 | F16) => { + Some(Instr::FPTrunc(value, other.clone())) + } + (F80, F64 | F32B | F32 | F16) => Some(Instr::FPTrunc(value, other.clone())), + (F64, F32B | F32 | F16) => Some(Instr::FPTrunc(value, other.clone())), + (F32B | F32, F16) => Some(Instr::FPTrunc(value, other.clone())), + _ => None, + } + } } #[derive(PartialEq, Eq, PartialOrd, Ord)]