diff --git a/cranelift/codegen/src/context.rs b/cranelift/codegen/src/context.rs index aeec864d79..704d891776 100644 --- a/cranelift/codegen/src/context.rs +++ b/cranelift/codegen/src/context.rs @@ -212,7 +212,7 @@ impl Context { /// Run the locations verifier on the function. pub fn verify_locations(&self, isa: &dyn TargetIsa) -> VerifierResult<()> { let mut errors = VerifierErrors::default(); - let _ = verify_locations(isa, &self.func, None, &mut errors); + let _ = verify_locations(isa, &self.func, &self.cfg, None, &mut errors); if errors.is_empty() { Ok(()) diff --git a/cranelift/codegen/src/regalloc/coloring.rs b/cranelift/codegen/src/regalloc/coloring.rs index 507d5e3ff7..f23c738583 100644 --- a/cranelift/codegen/src/regalloc/coloring.rs +++ b/cranelift/codegen/src/regalloc/coloring.rs @@ -44,18 +44,19 @@ use crate::cursor::{Cursor, EncCursor}; use crate::dominator_tree::DominatorTree; +use crate::flowgraph::ControlFlowGraph; use crate::ir::{ArgumentLoc, InstBuilder, ValueDef}; use crate::ir::{Ebb, Function, Inst, InstructionData, Layout, Opcode, SigRef, Value, ValueLoc}; use crate::isa::{regs_overlap, RegClass, RegInfo, RegUnit}; use crate::isa::{ConstraintKind, EncInfo, OperandConstraint, RecipeConstraints, TargetIsa}; use crate::packed_option::PackedOption; use crate::regalloc::affinity::Affinity; +use crate::regalloc::diversion::RegDiversions; use crate::regalloc::live_value_tracker::{LiveValue, LiveValueTracker}; use crate::regalloc::liveness::Liveness; use crate::regalloc::liverange::{LiveRange, LiveRangeContext}; use crate::regalloc::register_set::RegisterSet; use crate::regalloc::solver::{Solver, SolverError}; -use crate::regalloc::RegDiversions; use crate::timing; use core::mem; use log::debug; @@ -92,6 +93,7 @@ struct Context<'a> { encinfo: EncInfo, // References to contextual data structures we need. + cfg: &'a ControlFlowGraph, domtree: &'a DominatorTree, liveness: &'a mut Liveness, @@ -126,6 +128,7 @@ impl Coloring { &mut self, isa: &dyn TargetIsa, func: &mut Function, + cfg: &ControlFlowGraph, domtree: &DominatorTree, liveness: &mut Liveness, tracker: &mut LiveValueTracker, @@ -137,6 +140,7 @@ impl Coloring { cur: EncCursor::new(func, isa), reginfo: isa.register_info(), encinfo: isa.encoding_info(), + cfg, domtree, liveness, divert: &mut self.divert, @@ -166,13 +170,13 @@ impl<'a> Context<'a> { debug!("Coloring {}:", ebb); let mut regs = self.visit_ebb_header(ebb, tracker); tracker.drop_dead_params(); - self.divert.clear(); // Now go through the instructions in `ebb` and color the values they define. self.cur.goto_top(ebb); while let Some(inst) = self.cur.next_inst() { self.cur.use_srcloc(inst); - if !self.cur.func.dfg[inst].opcode().is_ghost() { + let opcode = self.cur.func.dfg[inst].opcode(); + if !opcode.is_ghost() { // This is an instruction which either has an encoding or carries ABI-related // register allocation constraints. let enc = self.cur.func.encodings[inst]; @@ -189,6 +193,54 @@ impl<'a> Context<'a> { self.process_ghost_kills(kills, &mut regs); } tracker.drop_dead(inst); + + // We are not able to insert any regmove for diversion or un-diversion after the first + // branch. Instead, we record the diversion to be restored at the entry of the next EBB, + // which should have a single predecessor. + if opcode.is_branch() && cfg!(feature = "basic-blocks") { + // The next instruction is necessarily an unconditional branch. + if let Some(branch) = self.cur.next_inst() { + debug!( + "Skip coloring {}\n from {}\n with diversions {}", + self.cur.display_inst(branch), + regs.input.display(&self.reginfo), + self.divert.display(&self.reginfo) + ); + use crate::ir::instructions::BranchInfo::*; + let target = match self.cur.func.dfg.analyze_branch(branch) { + NotABranch | Table(_, _) => panic!( + "unexpected instruction {} after a conditional branch", + self.cur.display_inst(branch) + ), + SingleDest(ebb, _) => ebb, + }; + + // We have a single branch with a single target, and an EBB with a single + // predecessor. Thus we can forward the diversion set to the next EBB. + if self.cfg.pred_iter(target).count() == 1 { + // Transfer the diversion to the next EBB. + self.divert + .save_for_ebb(&mut self.cur.func.entry_diversions, target); + debug!( + "Set entry-diversion for {} to\n {}", + target, + self.divert.display(&self.reginfo) + ); + } else { + debug_assert!( + self.divert.is_empty(), + "Divert set is non-empty after the terminator." + ); + } + assert_eq!( + self.cur.next_inst(), + None, + "Unexpected instruction after a branch group." + ); + } else { + assert!(opcode.is_terminator()); + } + } } } @@ -205,7 +257,15 @@ impl<'a> Context<'a> { self.domtree, ); + // Copy the content of the registered diversions to be reused at the + // entry of this basic block. self.divert.at_ebb(&self.cur.func.entry_diversions, ebb); + debug!( + "Start {} with entry-diversion set to\n {}", + ebb, + self.divert.display(&self.reginfo) + ); + if self.cur.func.layout.entry_block() == Some(ebb) { // Parameters on the entry block have ABI constraints. self.color_entry_params(tracker.live()) @@ -231,17 +291,35 @@ impl<'a> Context<'a> { "Live-in: {}:{} in {}", lv.value, lv.affinity.display(&self.reginfo), - self.cur.func.locations[lv.value].display(&self.reginfo) + self.divert + .get(lv.value, &self.cur.func.locations) + .display(&self.reginfo) ); if let Affinity::Reg(rci) = lv.affinity { let rc = self.reginfo.rc(rci); let loc = self.cur.func.locations[lv.value]; - match loc { - ValueLoc::Reg(reg) => regs.take(rc, reg, lv.is_local), + let reg = match loc { + ValueLoc::Reg(reg) => reg, ValueLoc::Unassigned => panic!("Live-in {} wasn't assigned", lv.value), ValueLoc::Stack(ss) => { panic!("Live-in {} is in {}, should be register", lv.value, ss) } + }; + if lv.is_local { + regs.take(rc, reg, lv.is_local); + } else { + let loc = self.divert.get(lv.value, &self.cur.func.locations); + let reg_divert = match loc { + ValueLoc::Reg(reg) => reg, + ValueLoc::Unassigned => { + panic!("Diversion: Live-in {} wasn't assigned", lv.value) + } + ValueLoc::Stack(ss) => panic!( + "Diversion: Live-in {} is in {}, should be register", + lv.value, ss + ), + }; + regs.take_divert(rc, reg, reg_divert); } } } @@ -1155,4 +1233,10 @@ impl AvailableRegs { self.global.take(rc, reg); } } + + /// Take a diverted register from both sets for a non-local allocation. + pub fn take_divert(&mut self, rc: RegClass, reg: RegUnit, reg_divert: RegUnit) { + self.input.take(rc, reg_divert); + self.global.take(rc, reg); + } } diff --git a/cranelift/codegen/src/regalloc/context.rs b/cranelift/codegen/src/regalloc/context.rs index 84fe139d87..c5f5da4ecb 100644 --- a/cranelift/codegen/src/regalloc/context.rs +++ b/cranelift/codegen/src/regalloc/context.rs @@ -198,8 +198,14 @@ impl Context { } // Pass: Coloring. - self.coloring - .run(isa, func, domtree, &mut self.liveness, &mut self.tracker); + self.coloring.run( + isa, + func, + cfg, + domtree, + &mut self.liveness, + &mut self.tracker, + ); // This function runs after register allocation has taken // place, meaning values have locations assigned already. @@ -218,7 +224,7 @@ impl Context { if isa.flags().enable_verifier() { let ok = verify_context(func, cfg, domtree, isa, &mut errors).is_ok() && verify_liveness(isa, func, cfg, &self.liveness, &mut errors).is_ok() - && verify_locations(isa, func, Some(&self.liveness), &mut errors).is_ok() + && verify_locations(isa, func, cfg, Some(&self.liveness), &mut errors).is_ok() && verify_cssa( func, cfg, diff --git a/cranelift/codegen/src/verifier/locations.rs b/cranelift/codegen/src/verifier/locations.rs index 8c290bb281..bf1a4e1860 100644 --- a/cranelift/codegen/src/verifier/locations.rs +++ b/cranelift/codegen/src/verifier/locations.rs @@ -1,5 +1,6 @@ //! Verify value locations. +use crate::flowgraph::ControlFlowGraph; use crate::ir; use crate::isa; use crate::regalloc::liveness::Liveness; @@ -21,6 +22,7 @@ use crate::verifier::{VerifierErrors, VerifierStepResult}; pub fn verify_locations( isa: &dyn isa::TargetIsa, func: &ir::Function, + cfg: &ControlFlowGraph, liveness: Option<&Liveness>, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { @@ -30,6 +32,7 @@ pub fn verify_locations( func, reginfo: isa.register_info(), encinfo: isa.encoding_info(), + cfg, liveness, }; verifier.check_constraints(errors)?; @@ -41,6 +44,7 @@ struct LocationVerifier<'a> { func: &'a ir::Function, reginfo: isa::RegInfo, encinfo: isa::EncInfo, + cfg: &'a ControlFlowGraph, liveness: Option<&'a Liveness>, } @@ -53,6 +57,7 @@ impl<'a> LocationVerifier<'a> { for ebb in self.func.layout.ebbs() { divert.at_ebb(&self.func.entry_diversions, ebb); + let mut is_after_branch = false; for inst in self.func.layout.ebb_insts(ebb) { let enc = self.func.encodings[inst]; @@ -70,10 +75,11 @@ impl<'a> LocationVerifier<'a> { if opcode.is_return() { self.check_return_abi(inst, &divert, errors)?; } else if opcode.is_branch() && !divert.is_empty() { - self.check_cfg_edges(inst, &divert, errors)?; + self.check_cfg_edges(inst, &mut divert, is_after_branch, errors)?; } self.update_diversions(inst, &mut divert, errors)?; + is_after_branch = opcode.is_branch(); } } @@ -284,8 +290,9 @@ impl<'a> LocationVerifier<'a> { return fatal!( errors, inst, - "inconsistent with global location {}", - self.func.locations[arg].display(&self.reginfo) + "inconsistent with global location {} ({})", + self.func.locations[arg].display(&self.reginfo), + self.func.dfg.display_inst(inst, None) ); } @@ -299,37 +306,52 @@ impl<'a> LocationVerifier<'a> { fn check_cfg_edges( &self, inst: ir::Inst, - divert: &RegDiversions, + divert: &mut RegDiversions, + is_after_branch: bool, errors: &mut VerifierErrors, ) -> VerifierStepResult<()> { use crate::ir::instructions::BranchInfo::*; + let dfg = &self.func.dfg; + let branch_kind = dfg.analyze_branch(inst); // We can only check CFG edges if we have a liveness analysis. let liveness = match self.liveness { Some(l) => l, None => return Ok(()), }; - let dfg = &self.func.dfg; - match dfg.analyze_branch(inst) { + match branch_kind { NotABranch => panic!( "No branch information for {}", dfg.display_inst(inst, self.isa) ), SingleDest(ebb, _) => { + let unique_predecessor = self.cfg.pred_iter(ebb).count() == 1; + let mut val_to_remove = vec![]; for (&value, d) in divert.iter() { let lr = &liveness[value]; - if lr.is_livein(ebb, liveness.context(&self.func.layout)) { + if is_after_branch && unique_predecessor { + // Forward diversions based on the targeted branch. + if !lr.is_livein(ebb, liveness.context(&self.func.layout)) { + val_to_remove.push(value) + } + } else if lr.is_livein(ebb, liveness.context(&self.func.layout)) { return fatal!( errors, inst, - "{} is diverted to {} and live in to {}", + "SingleDest: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), ebb ); } } + if is_after_branch && unique_predecessor { + for val in val_to_remove.into_iter() { + divert.remove(val); + } + debug_assert!(divert.check_ebb_entry(&self.func.entry_diversions, ebb)); + } } Table(jt, ebb) => { for (&value, d) in divert.iter() { @@ -339,7 +361,7 @@ impl<'a> LocationVerifier<'a> { return fatal!( errors, inst, - "{} is diverted to {} and live in to {}", + "Table.default: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), ebb @@ -351,7 +373,7 @@ impl<'a> LocationVerifier<'a> { return fatal!( errors, inst, - "{} is diverted to {} and live in to {}", + "Table.case: {} is diverted to {} and live in to {}", value, d.to.display(&self.reginfo), ebb diff --git a/cranelift/filetests/filetests/isa/x86/legalize-br-table.clif b/cranelift/filetests/filetests/isa/x86/legalize-br-table.clif index 6d49a6e6d0..e970136ac8 100644 --- a/cranelift/filetests/filetests/isa/x86/legalize-br-table.clif +++ b/cranelift/filetests/filetests/isa/x86/legalize-br-table.clif @@ -1,6 +1,7 @@ test compile target x86_64 +feature !"basic-blocks" ; regex: V=v\d+ ; regex: EBB=ebb\d+ diff --git a/cranelift/filetests/filetests/safepoint/basic.clif b/cranelift/filetests/filetests/safepoint/basic.clif index b47a5bf393..820594e85b 100644 --- a/cranelift/filetests/filetests/safepoint/basic.clif +++ b/cranelift/filetests/filetests/safepoint/basic.clif @@ -1,7 +1,7 @@ test safepoint - set enable_safepoints=true target x86_64 +feature !"basic-blocks" function %test(i32, r64, r64) -> r64 { ebb0(v0: i32, v1:r64, v2:r64): diff --git a/cranelift/filetests/filetests/safepoint/call.clif b/cranelift/filetests/filetests/safepoint/call.clif index 489ebd0438..a322445319 100644 --- a/cranelift/filetests/filetests/safepoint/call.clif +++ b/cranelift/filetests/filetests/safepoint/call.clif @@ -1,7 +1,7 @@ test safepoint - set enable_safepoints=true target x86_64 +feature !"basic-blocks" function %direct() -> r64 { fn0 = %none() diff --git a/cranelift/src/bugpoint_test.clif b/cranelift/src/bugpoint_test.clif index 20c34e2351..157411ed75 100644 --- a/cranelift/src/bugpoint_test.clif +++ b/cranelift/src/bugpoint_test.clif @@ -554,6 +554,9 @@ ebb18: v196 = load.i8 v195 v197 = uextend.i32 v196 brz v197, ebb19 + jump ebb164 + +ebb164: v198 = global_value.i64 gv12 trap user0 @@ -572,6 +575,9 @@ ebb19: v209 = load.i8 v208 v210 = uextend.i32 v209 brz v210, ebb20 + jump ebb163 + +ebb163: v211 = global_value.i64 gv13 trap user0 @@ -605,6 +611,9 @@ ebb22: v233 = load.i8 v232 v234 = uextend.i32 v233 brz v234, ebb23 + jump ebb162 + +ebb162: v235 = global_value.i64 gv16 trap user0 @@ -630,6 +639,9 @@ ebb24: v252 = load.i8 v251 v253 = uextend.i32 v252 brz v253, ebb25 + jump ebb161 + +ebb161: v254 = global_value.i64 gv17 trap user0 @@ -666,6 +678,9 @@ ebb27: v278 = load.i8 v277 v279 = uextend.i32 v278 brz v279, ebb28 + jump ebb160 + +ebb160: v280 = global_value.i64 gv18 trap user0 @@ -681,6 +696,9 @@ ebb28: v289 = load.i8 v288 v290 = uextend.i32 v289 brz v290, ebb29 + jump ebb159 + +ebb159: v291 = global_value.i64 gv19 trap user0 @@ -699,6 +717,9 @@ ebb29: v302 = load.i8 v301 v303 = uextend.i32 v302 brz v303, ebb30 + jump ebb158 + +ebb158: v304 = global_value.i64 gv20 trap user0 @@ -714,6 +735,9 @@ ebb30: v313 = load.i8 v312 v314 = uextend.i32 v313 brz v314, ebb31 + jump ebb157 + +ebb157: v315 = global_value.i64 gv21 trap user0 @@ -887,6 +911,9 @@ ebb49(v1006: i16): v411 = load.i8 v410 v412 = uextend.i32 v411 brz v412, ebb50 + jump ebb156 + +ebb156: v413 = global_value.i64 gv28 trap user0 @@ -908,6 +935,9 @@ ebb50: v424 = load.i8 v423 v425 = uextend.i32 v424 brz v425, ebb51 + jump ebb155 + +ebb155: v426 = global_value.i64 gv29 trap user0 @@ -922,6 +952,9 @@ ebb51: v432 = bint.i8 v431 v433 = uextend.i32 v432 brz v433, ebb52 + jump ebb154 + +ebb154: v434 = global_value.i64 gv30 trap user0 @@ -941,6 +974,9 @@ ebb52: v447 = load.i8 v446 v448 = uextend.i32 v447 brz v448, ebb53 + jump ebb153 + +ebb153: v449 = global_value.i64 gv31 trap user0 @@ -960,6 +996,9 @@ ebb53: v462 = load.i8 v461 v463 = uextend.i32 v462 brz v463, ebb54 + jump ebb152 + +ebb152: v464 = global_value.i64 gv32 trap user0 @@ -976,6 +1015,9 @@ ebb54: v474 = load.i8 v473 v475 = uextend.i32 v474 brz v475, ebb55 + jump ebb151 + +ebb151: v476 = global_value.i64 gv33 trap user0 @@ -1002,6 +1044,9 @@ ebb56: v493 = load.i8 v492 v494 = uextend.i32 v493 brz v494, ebb57 + jump ebb150 + +ebb150: v495 = global_value.i64 gv34 trap user0 @@ -1017,6 +1062,9 @@ ebb57: v504 = load.i8 v503 v505 = uextend.i32 v504 brz v505, ebb58 + jump ebb149 + +ebb149: v506 = global_value.i64 gv35 trap user0 @@ -1032,6 +1080,9 @@ ebb58: v517 = load.i8 v516 v518 = uextend.i32 v517 brz v518, ebb59 + jump ebb148 + +ebb148: v519 = global_value.i64 gv36 trap user0 @@ -1049,6 +1100,9 @@ ebb59: v530 = load.i8 v529 v531 = uextend.i32 v530 brz v531, ebb60 + jump ebb147 + +ebb147: v532 = global_value.i64 gv37 trap user0 @@ -1065,6 +1119,9 @@ ebb60: v542 = load.i8 v541 v543 = uextend.i32 v542 brz v543, ebb61 + jump ebb146 + +ebb146: v544 = global_value.i64 gv38 trap user0 @@ -1118,6 +1175,9 @@ ebb62(v552: i32, v1009: i64, v1013: i64, v1016: i64, v1019: i64, v1022: i16, v10 v556 = bint.i8 v555 v557 = uextend.i32 v556 brz v557, ebb63 + jump ebb145 + +ebb145: v558 = global_value.i64 gv39 trap user0 @@ -1131,6 +1191,9 @@ ebb63: v566 = bint.i8 v565 v567 = uextend.i32 v566 brz v567, ebb64 + jump ebb144 + +ebb144: v568 = global_value.i64 gv40 trap user0 @@ -1175,6 +1238,9 @@ ebb68(v584: i32): v593 = load.i8 v592 v594 = uextend.i32 v593 brz v594, ebb69 + jump ebb143 + +ebb143: v595 = global_value.i64 gv43 trap user0 @@ -1185,6 +1251,9 @@ ebb69: v600 = bint.i8 v599 v601 = uextend.i32 v600 brnz v601, ebb70 + jump ebb142 + +ebb142: v602 = global_value.i64 gv44 trap user0 @@ -1205,6 +1274,9 @@ ebb70: v618 = load.i8 v617 v619 = uextend.i32 v618 brz v619, ebb71 + jump ebb141 + +ebb141: v620 = global_value.i64 gv45 trap user0 @@ -1225,6 +1297,9 @@ ebb71: v632 = load.i8 v631 v633 = uextend.i32 v632 brz v633, ebb72 + jump ebb140 + +ebb140: v634 = global_value.i64 gv46 trap user0 @@ -1240,6 +1315,9 @@ ebb72: v644 = load.i8 v643 v645 = uextend.i32 v644 brz v645, ebb73 + jump ebb139 + +ebb139: v646 = global_value.i64 gv47 trap user0 @@ -1266,6 +1344,9 @@ ebb74: v662 = load.i8 v661 v663 = uextend.i32 v662 brz v663, ebb75 + jump ebb138 + +ebb138: v664 = global_value.i64 gv48 trap user0 @@ -1294,6 +1375,9 @@ ebb76: v686 = load.i8 v685 v687 = uextend.i32 v686 brz v687, ebb77 + jump ebb137 + +ebb137: v688 = global_value.i64 gv49 trap user0 @@ -1465,6 +1549,9 @@ ebb96: v790 = load.i8 v789 v791 = uextend.i32 v790 brz v791, ebb97 + jump ebb136 + +ebb136: v792 = global_value.i64 gv58 trap user0 @@ -1476,6 +1563,9 @@ ebb97: v797 = bint.i8 v796 v798 = uextend.i32 v797 brz v798, ebb98 + jump ebb135 + +ebb135: v799 = global_value.i64 gv59 trap user0 @@ -1515,6 +1605,9 @@ ebb99(v804: i64, v1035: i64, v1037: i64, v1039: i64, v1044: i64, v1052: i16, v10 v813 = load.i8 v812 v814 = uextend.i32 v813 brz v814, ebb100 + jump ebb134 + +ebb134: v815 = global_value.i64 gv60 trap user0 @@ -1534,6 +1627,9 @@ ebb100: v826 = load.i8 v825 v827 = uextend.i32 v826 brz v827, ebb101 + jump ebb133 + +ebb133: v828 = global_value.i64 gv61 trap user0 @@ -1555,6 +1651,9 @@ ebb101: v839 = load.i8 v838 v840 = uextend.i32 v839 brz v840, ebb102 + jump ebb132 + +ebb132: v841 = global_value.i64 gv62 trap user0 @@ -1574,6 +1673,9 @@ ebb102: v852 = load.i8 v851 v853 = uextend.i32 v852 brz v853, ebb103 + jump ebb131 + +ebb131: v854 = global_value.i64 gv63 trap user0 @@ -1591,6 +1693,9 @@ ebb103: v866 = load.i8 v865 v867 = uextend.i32 v866 brz v867, ebb104 + jump ebb130 + +ebb130: v868 = global_value.i64 gv64 trap user0 @@ -1607,6 +1712,9 @@ ebb104: v878 = load.i8 v877 v879 = uextend.i32 v878 brz v879, ebb105 + jump ebb129 + +ebb129: v880 = global_value.i64 gv65 trap user0 @@ -1654,6 +1762,9 @@ ebb109(v896: i64): v905 = load.i8 v904 v906 = uextend.i32 v905 brz v906, ebb110 + jump ebb128 + +ebb128: v907 = global_value.i64 gv68 trap user0 @@ -1664,6 +1775,9 @@ ebb110: v912 = bint.i8 v911 v913 = uextend.i32 v912 brnz v913, ebb111 + jump ebb127 + +ebb127: v914 = global_value.i64 gv69 trap user0 @@ -1684,6 +1798,9 @@ ebb111: v930 = load.i8 v929 v931 = uextend.i32 v930 brz v931, ebb112 + jump ebb126 + +ebb126: v932 = global_value.i64 gv70 trap user0 @@ -1709,6 +1826,9 @@ ebb113: v948 = load.i8 v947 v949 = uextend.i32 v948 brz v949, ebb114 + jump ebb125 + +ebb125: v950 = global_value.i64 gv71 trap user0 @@ -1737,6 +1857,9 @@ ebb115: v972 = load.i8 v971 v973 = uextend.i32 v972 brz v973, ebb116 + jump ebb123 + +ebb123: v974 = global_value.i64 gv72 trap user0 @@ -1752,6 +1875,9 @@ ebb116: v984 = load.i8 v983 v985 = uextend.i32 v984 brz v985, ebb117 + jump ebb122 + +ebb122: v986 = global_value.i64 gv73 trap user0 @@ -1775,6 +1901,9 @@ ebb119: v1001 = load.i8 v1000 v1002 = uextend.i32 v1001 brz v1002, ebb120 + jump ebb121 + +ebb121: v1003 = global_value.i64 gv74 trap user0