#![feature(if_let_guard)] #![feature(slice_take)] use { core::panic, cranelift_codegen::{ CodegenError, Final, FinalizedMachReloc, MachBufferFinalized, ir::{InstBuilder, MemFlags, UserExternalName}, isa::{LookupError, TargetIsa}, settings::Configurable, }, cranelift_frontend::FunctionBuilder, cranelift_module::{Module, ModuleError}, hblang::{ lexer::TokenKind, nodes::Kind, utils::{Ent, EntVec}, }, std::{fmt::Display, ops::Range, usize}, }; mod x86_64; pub struct Backend { ctx: cranelift_codegen::Context, dt_ctx: cranelift_module::DataDescription, fb_ctx: cranelift_frontend::FunctionBuilderContext, module: Option<cranelift_object::ObjectModule>, ctrl_plane: cranelift_codegen::control::ControlPlane, funcs: Functions, globals: EntVec<hblang::ty::Global, Global>, asm: Assembler, } impl Backend { pub fn new(triple: target_lexicon::Triple) -> Result<Self, BackendCreationError> { Ok(Self { ctx: cranelift_codegen::Context::new(), dt_ctx: cranelift_module::DataDescription::new(), fb_ctx: cranelift_frontend::FunctionBuilderContext::default(), ctrl_plane: cranelift_codegen::control::ControlPlane::default(), module: cranelift_object::ObjectModule::new(cranelift_object::ObjectBuilder::new( cranelift_codegen::isa::lookup(triple)?.finish( cranelift_codegen::settings::Flags::new({ let mut bl = cranelift_codegen::settings::builder(); bl.set("enable_verifier", "true").unwrap(); bl }), )?, "main", cranelift_module::default_libcall_names(), )?) .into(), funcs: Default::default(), globals: Default::default(), asm: Default::default(), }) } } impl hblang::backend::Backend for Backend { fn assemble_reachable( &mut self, from: hblang::ty::Func, types: &hblang::ty::Types, files: &hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>, to: &mut Vec<u8>, ) -> hblang::backend::AssemblySpec { debug_assert!(self.asm.frontier.is_empty()); debug_assert!(self.asm.funcs.is_empty()); debug_assert!(self.asm.globals.is_empty()); let mut module = self.module.take().expect("backend can assemble only once"); fn clif_name_to_ty(name: UserExternalName) -> hblang::ty::Id { match name.namespace { 0 => hblang::ty::Kind::Func(hblang::ty::Func::new(name.index as _)), 1 => hblang::ty::Kind::Global(hblang::ty::Global::new(name.index as _)), _ => unreachable!(), } .compress() } self.globals.shadow(types.ins.globals.len()); self.asm.frontier.push(from.into()); while let Some(itm) = self.asm.frontier.pop() { match itm.expand() { hblang::ty::Kind::Func(func) => { let fuc = &mut self.funcs.headers[func]; if fuc.module_id.is_some() { continue; } self.asm.funcs.push(func); self.asm.frontier.extend( fuc.external_names.clone().map(|r| { clif_name_to_ty(self.funcs.external_names[r as usize].clone()) }), ); self.asm.name.clear(); if func == from { self.asm.name.push_str("main"); } else { let file = &files[types.ins.funcs[func].file]; self.asm.name.push_str(hblang::strip_cwd(&file.path)); self.asm.name.push('.'); self.asm.name.push_str(file.ident_str(types.ins.funcs[func].name)); } let linkage = if func == from { cranelift_module::Linkage::Export } else { cranelift_module::Linkage::Local }; build_signature( module.isa().default_call_conv(), types.ins.funcs[func].sig, types, &mut self.ctx.func.signature, &mut vec![], ); fuc.module_id = Some( module .declare_function(&self.asm.name, linkage, &self.ctx.func.signature) .unwrap(), ); } hblang::ty::Kind::Global(glob) => { if self.globals[glob].module_id.is_some() { continue; } self.asm.globals.push(glob); self.asm.name.clear(); let file = &files[types.ins.globals[glob].file]; self.asm.name.push_str(hblang::strip_cwd(&file.path)); self.asm.name.push('.'); self.asm.name.push_str(file.ident_str(types.ins.globals[glob].name)); self.globals[glob].module_id = Some( module .declare_data( &self.asm.name, cranelift_module::Linkage::Local, true, false, ) .unwrap(), ); } _ => unreachable!(), } } for &func in &self.asm.funcs { let fuc = &self.funcs.headers[func]; debug_assert!(!fuc.code.is_empty()); let names = &mut self.funcs.external_names [fuc.external_names.start as usize..fuc.external_names.end as usize]; names.iter_mut().for_each(|nm| { nm.index = self.funcs.headers[hblang::ty::Func::new(nm.index as _)] .module_id .unwrap() .as_u32(); self.ctx.func.params.ensure_user_func_name(nm.clone()); }); module .define_function_bytes( fuc.module_id.unwrap(), &self.ctx.func, fuc.alignment as _, &self.funcs.code[fuc.code.start as usize..fuc.code.end as usize], &self.funcs.relocs[fuc.relocs.start as usize..fuc.relocs.end as usize], ) .unwrap(); } for global in self.asm.globals.drain(..) { let glob = &self.globals[global]; self.dt_ctx.clear(); self.dt_ctx.define(types.ins.globals[global].data.clone().into()); module.define_data(glob.module_id.unwrap(), &self.dt_ctx).unwrap(); } module.finish().object.write_stream(to).unwrap(); hblang::backend::AssemblySpec { code_length: 0, data_length: 0, entry: 0 } } fn disasm<'a>( &'a self, _sluce: &[u8], _eca_handler: &mut dyn FnMut(&mut &[u8]), _types: &'a hblang::ty::Types, _files: &'a hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>, _output: &mut String, ) -> Result<(), std::boxed::Box<dyn core::error::Error + Send + Sync + 'a>> { unimplemented!() } fn emit_body( &mut self, id: hblang::ty::Func, nodes: &hblang::nodes::Nodes, tys: &hblang::ty::Types, files: &hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>, ) { self.ctx.clear(); let isa = self.module.as_ref().unwrap().isa(); let mut lens = vec![]; let stack_ret = build_signature( isa.default_call_conv(), tys.ins.funcs[id].sig, tys, &mut self.ctx.func.signature, &mut lens, ); FuncBuilder { bl: FunctionBuilder::new(&mut self.ctx.func, &mut self.fb_ctx), isa, nodes, tys, files, values: &mut vec![None; nodes.len()], } .build(tys.ins.funcs[id].sig, &lens, stack_ret); self.ctx.func.name = cranelift_codegen::ir::UserFuncName::User(cranelift_codegen::ir::UserExternalName { namespace: 0, index: id.index() as _, }); std::eprintln!("{}", self.ctx.func.display()); self.ctx.compile(isa, &mut self.ctrl_plane).unwrap(); let code = self.ctx.compiled_code().unwrap(); self.funcs.push(id, &self.ctx.func, &code.buffer); } } fn build_signature( call_conv: cranelift_codegen::isa::CallConv, sig: hblang::ty::Sig, types: &hblang::ty::Types, signature: &mut cranelift_codegen::ir::Signature, arg_lens: &mut Vec<usize>, ) -> bool { signature.clear(call_conv); match call_conv { cranelift_codegen::isa::CallConv::SystemV => { x86_64::build_systemv_signature(sig, types, signature, arg_lens) } _ => todo!(), } } struct FuncBuilder<'a, 'b> { bl: cranelift_frontend::FunctionBuilder<'b>, isa: &'a dyn TargetIsa, nodes: &'a hblang::nodes::Nodes, tys: &'a hblang::ty::Types, files: &'a hblang::utils::EntSlice<hblang::ty::Module, hblang::parser::Ast>, values: &'b mut [Option<Result<cranelift_codegen::ir::Value, cranelift_codegen::ir::Block>>], } impl FuncBuilder<'_, '_> { pub fn build(mut self, sig: hblang::ty::Sig, arg_lens: &[usize], stack_ret: bool) { let entry = self.bl.create_block(); self.bl.append_block_params_for_function_params(entry); self.bl.switch_to_block(entry); let mut arg_vals = self.bl.block_params(entry); if stack_ret { let ret_ptr = *arg_vals.take_first().unwrap(); self.values[hblang::nodes::MEM as usize] = Some(Ok(ret_ptr)); } let Self { nodes, tys, .. } = self; let mut parama_len = arg_lens.iter(); let mut typs = sig.args.args(); let mut args = nodes[hblang::nodes::VOID].outputs[hblang::nodes::ARG_START..].iter(); while let Some(aty) = typs.next(tys) { let hblang::ty::Arg::Value(ty) = aty else { continue }; let loc = arg_vals.take(..*parama_len.next().unwrap()).unwrap(); let &arg = args.next().unwrap(); if ty.is_aggregate(tys) { todo!() } else { debug_assert_eq!(loc.len(), 1); self.values[arg as usize] = Some(Ok(loc[0])); } } self.values[hblang::nodes::ENTRY as usize] = Some(Err(entry)); self.emit_node(hblang::nodes::VOID, hblang::nodes::VOID); self.bl.finalize(); } fn value_of(&self, nid: hblang::nodes::Nid) -> cranelift_codegen::ir::Value { self.values[nid as usize].unwrap_or_else(|| panic!("{:?}", self.nodes[nid])).unwrap() } fn block_of(&self, nid: hblang::nodes::Nid) -> cranelift_codegen::ir::Block { self.values[nid as usize].unwrap().unwrap_err() } fn close_block(&mut self, nid: hblang::nodes::Nid) { if matches!(self.nodes[nid].kind, Kind::Loop) { return; } self.bl.seal_block(self.block_of(nid)); } fn emit_node(&mut self, nid: hblang::nodes::Nid, block: hblang::nodes::Nid) { use hblang::nodes::*; let mut args = vec![]; if matches!(self.nodes[nid].kind, Kind::Region | Kind::Loop) { let side = 1 + self.values[nid as usize].is_some() as usize; for &o in self.nodes[nid].outputs.iter() { if self.nodes[o].is_data_phi() { args.push(self.value_of(self.nodes[o].inputs[side])); } } match (self.nodes[nid].kind, self.values[nid as usize]) { (Kind::Loop, Some(blck)) => { self.bl.ins().jump(blck.unwrap_err(), &args); self.bl.seal_block(blck.unwrap_err()); self.close_block(block); return; } (Kind::Region, None) => { let next = self.bl.create_block(); for &o in self.nodes[nid].outputs.iter() { if self.nodes[o].is_data_phi() { self.values[o as usize] = Some(Ok(self.bl.append_block_param( next, ty_to_clif_ty(self.nodes[o].ty, self.tys), ))); } } self.bl.ins().jump(next, &args); self.close_block(block); self.values[nid as usize] = Some(Err(next)); return; } _ => {} } } let node = &self.nodes[nid]; self.values[nid as usize] = Some(match node.kind { Kind::Start => { debug_assert_eq!(self.nodes[node.outputs[0]].kind, Kind::Entry); self.emit_node(node.outputs[0], block); return; } Kind::If => { let &[_, cnd] = node.inputs.as_slice() else { unreachable!() }; let &[then, else_] = node.outputs.as_slice() else { unreachable!() }; let then_bl = self.bl.create_block(); let else_bl = self.bl.create_block(); let c = self.value_of(cnd); self.bl.ins().brif(c, then_bl, &[], else_bl, &[]); self.values[then as usize] = Some(Err(then_bl)); self.values[else_ as usize] = Some(Err(else_bl)); self.close_block(block); self.bl.switch_to_block(then_bl); self.emit_node(then, then); self.bl.switch_to_block(else_bl); self.emit_node(else_, else_); Err(self.block_of(block)) } Kind::Loop => { let next = self.bl.create_block(); for &o in self.nodes[nid].outputs.iter() { if self.nodes[o].is_data_phi() { self.values[o as usize] = Some(Ok(self .bl .append_block_param(next, ty_to_clif_ty(self.nodes[o].ty, self.tys)))); } } self.values[nid as usize] = Some(Err(next)); self.bl.ins().jump(self.values[nid as usize].unwrap().unwrap_err(), &args); self.close_block(block); self.bl.switch_to_block(self.values[nid as usize].unwrap().unwrap_err()); for &o in node.outputs.iter().rev() { self.emit_node(o, nid); } Err(self.block_of(block)) } Kind::Region => { self.bl.ins().jump(self.values[nid as usize].unwrap().unwrap_err(), &args); self.close_block(block); self.bl.switch_to_block(self.values[nid as usize].unwrap().unwrap_err()); for &o in node.outputs.iter().rev() { self.emit_node(o, nid); } return; } Kind::Return { .. } | Kind::Die => { let ret = self.value_of(node.inputs[1]); self.bl.ins().return_(&[ret]); self.close_block(block); self.emit_node(node.outputs[0], block); Err(self.block_of(block)) } Kind::Entry => { for &o in node.outputs.iter().rev() { self.emit_node(o, nid); } return; } Kind::Then | Kind::Else => { for &o in node.outputs.iter().rev() { self.emit_node(o, block); } Err(self.block_of(block)) } Kind::Call { func, unreachable, args } => { if unreachable { todo!() } else { let mut arg_lens = vec![]; let mut signature = cranelift_codegen::ir::Signature::new(self.isa.default_call_conv()); let stack_ret = build_signature( self.isa.default_call_conv(), self.tys.ins.funcs[func].sig, self.tys, &mut signature, &mut arg_lens, ); assert!(!stack_ret, "TODO"); let func_ref = 'b: { let user_name_ref = self.bl.func.declare_imported_user_function( cranelift_codegen::ir::UserExternalName { namespace: 0, index: func.index() as _, }, ); if let Some(id) = self.bl.func.dfg.ext_funcs.keys().find(|&k| { self.bl.func.dfg.ext_funcs[k].name == cranelift_codegen::ir::ExternalName::user(user_name_ref) }) { break 'b id; } let signature = self.bl.func.import_signature(signature); self.bl.func.import_function(cranelift_codegen::ir::ExtFuncData { name: cranelift_codegen::ir::ExternalName::user(user_name_ref), signature, colocated: true, }) }; let args = node.inputs[1..][..args.len()] .iter() .map(|&n| self.value_of(n)) .collect::<Vec<_>>(); let inst = self.bl.ins().call(func_ref, &args); match *self.bl.inst_results(inst) { [] => {} [scala] => self.values[nid as usize] = Some(Ok(scala)), _ => todo!(), } for &o in node.outputs.iter().rev() { if self.nodes[o].inputs[0] == nid || (matches!(self.nodes[o].kind, Kind::Loop | Kind::Region) && self.nodes[o].inputs[1] == nid) { self.emit_node(o, block); } } return; } } Kind::CInt { value } if self.nodes[nid].ty.is_integer() => Ok(self.bl.ins().iconst( cranelift_codegen::ir::Type::int(self.tys.size_of(self.nodes[nid].ty) as u16 * 8) .unwrap(), value, )), Kind::CInt { value } => Ok(match self.tys.size_of(self.nodes[nid].ty) { 4 => self.bl.ins().f32const(f64::from_bits(value as _) as f32), 8 => self.bl.ins().f64const(f64::from_bits(value as _)), _ => unimplemented!(), }), Kind::BinOp { op } => { let &[_, lhs, rhs] = node.inputs.as_slice() else { unreachable!() }; let [lhs, rhs] = [self.value_of(lhs), self.value_of(rhs)]; assert!( node.ty.is_integer() || node.ty == hblang::ty::Id::BOOL, "TODO: unsupported binary type {}", hblang::ty::Display::new(self.tys, self.files, node.ty) ); use cranelift_codegen::ir::condcodes::IntCC as ICC; fn icc_of(op: TokenKind, signed: bool) -> ICC { match op { TokenKind::Lt if signed => ICC::SignedLessThan, TokenKind::Gt if signed => ICC::SignedGreaterThan, TokenKind::Le if signed => ICC::SignedLessThanOrEqual, TokenKind::Ge if signed => ICC::SignedGreaterThanOrEqual, TokenKind::Lt => ICC::UnsignedLessThan, TokenKind::Gt => ICC::UnsignedGreaterThan, TokenKind::Le => ICC::UnsignedLessThanOrEqual, TokenKind::Ge => ICC::UnsignedGreaterThanOrEqual, TokenKind::Eq => ICC::Equal, TokenKind::Ne => ICC::NotEqual, _ => unreachable!(), } } use cranelift_codegen::ir::condcodes::FloatCC as FCC; fn fcc_of(op: TokenKind) -> FCC { match op { TokenKind::Lt => FCC::LessThan, TokenKind::Gt => FCC::GreaterThan, TokenKind::Le => FCC::LessThanOrEqual, TokenKind::Ge => FCC::GreaterThanOrEqual, TokenKind::Eq => FCC::Equal, TokenKind::Ne => FCC::NotEqual, _ => unreachable!(), } } Ok(if node.ty.is_integer() { let signed = node.ty.is_signed(); match op { TokenKind::Add => self.bl.ins().iadd(lhs, rhs), TokenKind::Sub => self.bl.ins().isub(lhs, rhs), TokenKind::Mul => self.bl.ins().imul(lhs, rhs), TokenKind::Shl => self.bl.ins().ishl(lhs, rhs), TokenKind::Xor => self.bl.ins().bxor(lhs, rhs), TokenKind::Band => self.bl.ins().band(lhs, rhs), TokenKind::Bor => self.bl.ins().bor(lhs, rhs), TokenKind::Div if signed => self.bl.ins().sdiv(lhs, rhs), TokenKind::Mod if signed => self.bl.ins().srem(lhs, rhs), TokenKind::Shr if signed => self.bl.ins().sshr(lhs, rhs), TokenKind::Div => self.bl.ins().udiv(lhs, rhs), TokenKind::Mod => self.bl.ins().urem(lhs, rhs), TokenKind::Shr => self.bl.ins().ushr(lhs, rhs), TokenKind::Lt | TokenKind::Gt | TokenKind::Le | TokenKind::Ge | TokenKind::Eq | TokenKind::Ne => self.bl.ins().icmp(icc_of(op, signed), lhs, rhs), op => todo!("{op}"), } } else if node.ty.is_float() { match op { TokenKind::Add => self.bl.ins().fadd(lhs, rhs), TokenKind::Sub => self.bl.ins().fsub(lhs, rhs), TokenKind::Mul => self.bl.ins().fmul(lhs, rhs), TokenKind::Div => self.bl.ins().fdiv(lhs, rhs), TokenKind::Lt | TokenKind::Gt | TokenKind::Le | TokenKind::Ge | TokenKind::Eq | TokenKind::Ne => self.bl.ins().fcmp(fcc_of(op), lhs, rhs), op => todo!("{op}"), } } else { todo!() }) } Kind::RetVal => Ok(self.value_of(node.inputs[0])), Kind::UnOp { op } => { let oper = self.value_of(node.inputs[1]); let dst = node.ty; let src = self .tys .inner_of(self.nodes[node.inputs[1]].ty) .unwrap_or(self.nodes[node.inputs[1]].ty); let dty = ty_to_clif_ty(dst, self.tys); Ok(match op { TokenKind::Sub => self.bl.ins().ineg(oper), TokenKind::Not => self.bl.ins().bnot(oper), TokenKind::Float if dst.is_float() && src.is_unsigned() => { self.bl.ins().fcvt_from_uint(dty, oper) } TokenKind::Float if dst.is_float() && src.is_signed() => { self.bl.ins().fcvt_from_sint(dty, oper) } TokenKind::Number if src.is_float() && dst.is_unsigned() => { self.bl.ins().fcvt_to_uint(dty, oper) } TokenKind::Number if src.is_signed() && (dst.is_integer() || dst.is_pointer()) => { self.bl.ins().sextend(dty, oper) } TokenKind::Number if (src.is_unsigned() || src == hblang::ty::Id::BOOL) && (dst.is_integer() || dst.is_pointer()) => { self.bl.ins().uextend(dty, oper) } TokenKind::Float if dst == hblang::ty::Id::F64 && src.is_float() => { self.bl.ins().fpromote(dty, oper) } TokenKind::Float if dst == hblang::ty::Id::F32 && src.is_float() => { self.bl.ins().fdemote(dty, oper) } _ => todo!(), }) } Kind::Stck => { let slot = self.bl.create_sized_stack_slot(cranelift_codegen::ir::StackSlotData { kind: cranelift_codegen::ir::StackSlotKind::ExplicitSlot, size: self.tys.size_of(node.ty), align_shift: self.tys.align_of(node.ty).ilog2() as _, }); Ok(self.bl.ins().stack_addr(cranelift_codegen::ir::types::I64, slot, 0)) } Kind::Global { global } => { let glob_ref = { // already deduplicated by the SoN let colocated = true; let user_name_ref = self.bl.func.declare_imported_user_function( cranelift_codegen::ir::UserExternalName { namespace: 1, index: global.index() as u32, }, ); self.bl.func.create_global_value( cranelift_codegen::ir::GlobalValueData::Symbol { name: cranelift_codegen::ir::ExternalName::user(user_name_ref), offset: cranelift_codegen::ir::immediates::Imm64::new(0), colocated, tls: false, }, ) }; Ok(self.bl.ins().global_value(cranelift_codegen::ir::types::I64, glob_ref)) } Kind::Load if node.ty.is_aggregate(self.tys) => return, Kind::Load => { let ptr = self.value_of(node.inputs[1]); Ok(self.bl.ins().load(ty_to_clif_ty(node.ty, self.tys), MemFlags::new(), ptr, 0)) } Kind::Stre if node.ty.is_aggregate(self.tys) => { let src = self.value_of(self.nodes[node.inputs[1]].inputs[1]); let dest = self.value_of(node.inputs[2]); self.bl.emit_small_memory_copy( self.isa.frontend_config(), dest, src, self.tys.size_of(node.ty) as _, self.tys.align_of(node.ty) as _, self.tys.align_of(node.ty) as _, false, MemFlags::new(), ); return; } Kind::Stre => { let value = self.value_of(node.inputs[1]); let ptr = self.value_of(node.inputs[2]); self.bl.ins().store(MemFlags::new(), value, ptr, 0); return; } Kind::End | Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops | Kind::Join => return, Kind::Assert { .. } => unreachable!(), }); } } fn ty_to_clif_ty(ty: hblang::ty::Id, tys: &hblang::ty::Types) -> cranelift_codegen::ir::Type { if ty.is_integer() { cranelift_codegen::ir::Type::int(tys.size_of(ty) as u16 * 8).unwrap() } else { unimplemented!() } } #[derive(Default)] struct Global { module_id: Option<cranelift_module::DataId>, } #[derive(Default)] struct FuncHeaders { module_id: Option<cranelift_module::FuncId>, alignment: u32, code: Range<u32>, relocs: Range<u32>, external_names: Range<u32>, } #[derive(Default)] struct Functions { headers: EntVec<hblang::ty::Func, FuncHeaders>, code: Vec<u8>, relocs: Vec<FinalizedMachReloc>, external_names: Vec<UserExternalName>, } impl Functions { fn push( &mut self, id: hblang::ty::Func, func: &cranelift_codegen::ir::Function, code: &MachBufferFinalized<Final>, ) { self.headers.shadow(id.index() + 1); self.headers[id] = FuncHeaders { module_id: None, alignment: code.alignment, code: self.code.len() as u32..self.code.len() as u32 + code.data().len() as u32, relocs: self.relocs.len() as u32..self.relocs.len() as u32 + code.relocs().len() as u32, external_names: self.external_names.len() as u32 ..self.external_names.len() as u32 + func.params.user_named_funcs().len() as u32, }; self.code.extend(code.data()); self.relocs.extend(code.relocs().iter().cloned()); self.external_names.extend(func.params.user_named_funcs().values().cloned()); } } #[derive(Default)] struct Assembler { name: String, frontier: Vec<hblang::ty::Id>, globals: Vec<hblang::ty::Global>, funcs: Vec<hblang::ty::Func>, } #[derive(Debug)] pub enum BackendCreationError { UnsupportedTriplet(LookupError), InvalidFlags(CodegenError), UnsupportedModuleConfig(ModuleError), } impl Display for BackendCreationError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { BackendCreationError::UnsupportedTriplet(err) => { write!(f, "Unsupported triplet: {}", err) } BackendCreationError::InvalidFlags(err) => { write!(f, "Invalid flags: {}", err) } BackendCreationError::UnsupportedModuleConfig(err) => { write!(f, "Unsupported module configuration: {}", err) } } } } impl core::error::Error for BackendCreationError {} impl From<LookupError> for BackendCreationError { fn from(value: LookupError) -> Self { Self::UnsupportedTriplet(value) } } impl From<CodegenError> for BackendCreationError { fn from(value: CodegenError) -> Self { Self::InvalidFlags(value) } } impl From<ModuleError> for BackendCreationError { fn from(value: ModuleError) -> Self { Self::UnsupportedModuleConfig(value) } }