1use std::collections::{HashMap, HashSet};
115
116use rucc_base::{Interner, Symbol};
117use rucc_ir::{
118 AbiList, AttrSet, Block, CallInfo, Datum, Def, Extra, Float, Func, FuncId, Global, Imm, Inst,
119 InstData, IntPred, Linkage, MemInfo, MemOrder, Module, Opcode, Pic, Restrict, Signature,
120 SymbolRef, Type, Value,
121};
122
123use crate::extents::vouched;
124use crate::{Cfg, Fuel, Stats, uses};
125
126pub const NAME: &str = "libcall";
128
129const DEPTH: u32 = 4;
136
137const CHAIN: u32 = 12;
144
145const ROUNDS: u32 = 8;
150
151const REPLACEMENTS: [&str; 16] = [
153 "__memcpy_chk",
154 "ceilf",
155 "floorf",
156 "fputc",
157 "fputs",
158 "fwrite",
159 "memcpy",
160 "nearbyintf",
161 "putchar",
162 "puts",
163 "rintf",
164 "roundf",
165 "strchr",
166 "strcpy",
167 "strlen",
168 "truncf",
169];
170
171const UNCHECKED: [&str; 18] = [
178 "__memcpy_chk",
179 "__strcat_chk",
180 "__strcpy_chk",
181 "__strncpy_chk",
182 "memcpy",
183 "memmove",
184 "mempcpy",
185 "memset",
186 "snprintf",
187 "sprintf",
188 "stpcpy",
189 "stpncpy",
190 "strcat",
191 "strcpy",
192 "strncat",
193 "strncpy",
194 "vsnprintf",
195 "vsprintf",
196];
197
198const SOURCES: [&str; 55] = [
200 "__fprintf_chk",
201 "__memcpy_chk",
202 "__memmove_chk",
203 "__mempcpy_chk",
204 "__memset_chk",
205 "__printf_chk",
206 "__snprintf_chk",
207 "__sprintf_chk",
208 "__stpcpy_chk",
209 "__stpncpy_chk",
210 "__strcat_chk",
211 "__strcpy_chk",
212 "__strncat_chk",
213 "__strncpy_chk",
214 "__vfprintf_chk",
215 "__vprintf_chk",
216 "__vsnprintf_chk",
217 "__vsprintf_chk",
218 "bcopy",
219 "ceil",
220 "floor",
221 "fprintf",
222 "fprintf_unlocked",
223 "fputs",
224 "fputs_unlocked",
225 "index",
226 "memchr",
227 "memcmp",
228 "memmove",
229 "mempcpy",
230 "nearbyint",
231 "printf",
232 "printf_unlocked",
233 "rindex",
234 "rint",
235 "round",
236 "sprintf",
237 "stpcpy",
238 "strcat",
239 "strchr",
240 "strcmp",
241 "strcpy",
242 "strcspn",
243 "strlen",
244 "strncat",
245 "strncmp",
246 "strncpy",
247 "strnlen",
248 "strpbrk",
249 "strrchr",
250 "strspn",
251 "strstr",
252 "trunc",
253 "vfprintf",
254 "vprintf",
255];
256
257#[derive(Debug, Clone, PartialEq, Eq)]
259enum Plan {
260 Drop,
262 Answer(Answer),
265 Swap {
267 callee: Symbol,
269 signature: Signature,
271 args: Vec<Argument>,
273 answer: Option<Answer>,
277 },
278 Unchecked {
284 callee: Symbol,
286 drop: &'static [usize],
288 },
289 Narrow {
295 callee: Symbol,
297 signature: Signature,
299 arg: Value,
301 },
302}
303
304impl Plan {
305 fn rename(&mut self, renamed: &HashMap<Value, Value>) {
307 if renamed.is_empty() {
308 return;
309 }
310 let answer = match self {
311 Plan::Drop | Plan::Unchecked { .. } => None,
312 Plan::Narrow { arg, .. } => {
313 *arg = renamed.get(arg).copied().unwrap_or(*arg);
314 None
315 }
316 Plan::Answer(answer) => Some(answer),
317 Plan::Swap { args, answer, .. } => {
318 for arg in args {
319 if let Argument::Have(value) | Argument::At(value, _) = arg {
320 *value = renamed.get(value).copied().unwrap_or(*value);
321 }
322 }
323 answer.as_mut()
324 }
325 };
326 let value = match answer {
327 Some(Answer::Along(value, _) | Answer::Least { count: value, .. }) => value,
328 Some(Answer::Byte { of, .. } | Answer::Less { step: of, .. }) => of,
329 Some(Answer::Nowhere | Answer::Number(_)) | None => return,
330 };
331 *value = renamed.get(value).copied().unwrap_or(*value);
332 }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
337enum Answer {
338 Along(Value, u64),
340 Nowhere,
342 Number(i128),
348 Least {
351 count: Value,
353 len: u64,
355 },
356 Less {
360 len: u64,
362 step: Value,
364 },
365 Byte {
372 of: Value,
374 against: u8,
376 leading: bool,
379 },
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
384enum Side {
385 First,
387 Last,
389}
390
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
393enum Set {
394 Inside,
396 Outside,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
402enum Argument {
403 Have(Value),
405 Char(u8),
407 Count(u64),
409 Text(Vec<u8>),
411 At(Value, u64),
413}
414
415struct Shapes {
422 held: HashMap<&'static str, Option<(Symbol, Signature)>>,
425 named: HashMap<&'static str, Option<(Symbol, Option<Signature>)>>,
428}
429
430impl Shapes {
431 fn of(module: &Module, names: &mut Interner) -> Self {
433 let mut held: HashMap<&'static str, Option<(Symbol, Signature)>> = REPLACEMENTS
434 .iter()
435 .map(|&name| (name, Some((names.intern(name), canonical(module, name)))))
436 .collect();
437 let mut named: HashMap<&'static str, Option<(Symbol, Option<Signature>)>> =
438 UNCHECKED.iter().map(|&name| (name, Some((names.intern(name), None)))).collect();
439 for id in module.funcs() {
440 let func = &module[id];
443 let spelled = func.spelled.unwrap_or(func.name);
444 if let Some(slot) = named.get_mut(names.resolve(spelled)) {
445 *slot = Some((func.name, Some(func.signature().clone())));
446 }
447 let Some(slot) = held.get_mut(names.resolve(spelled)) else { continue };
448 let declared = func.signature();
449 let agrees = slot.as_ref().is_some_and(|(_, want)| {
450 !declared.variadic
451 && declared.param_types().eq(want.param_types())
452 && declared.return_types().eq(want.return_types())
453 });
454 *slot = agrees.then(|| (func.name, declared.clone()));
455 }
456 for id in module.globals() {
459 let name = names.resolve(module[id].name);
460 if let Some(slot) = held.get_mut(name) {
461 *slot = None;
462 }
463 if let Some(slot) = named.get_mut(name) {
464 *slot = None;
465 }
466 }
467 for id in module.aliases() {
468 let name = names.resolve(module[id].name);
469 if let Some(slot) = held.get_mut(name) {
470 *slot = None;
471 }
472 if let Some(slot) = named.get_mut(name) {
473 *slot = None;
474 }
475 }
476 Self { held, named }
477 }
478
479 fn get(&self, name: &'static str) -> Option<(Symbol, Signature)> {
481 self.held.get(name)?.clone()
482 }
483
484 fn unchecked(&self, name: &str, want: &Signature) -> Option<Symbol> {
487 let (symbol, declared) = self.named.get(name)?.as_ref()?;
488 let agrees = declared.as_ref().is_none_or(|declared| {
489 declared.variadic == want.variadic
490 && declared.param_types().eq(want.param_types())
491 && declared.return_types().eq(want.return_types())
492 });
493 agrees.then_some(*symbol)
494 }
495}
496
497fn canonical(module: &Module, name: &str) -> Signature {
502 let int = int();
503 let size = size(module);
504 match name {
505 "puts" => Signature::new().with_params(&[Type::PTR]).with_returns(&[int]),
506 "putchar" => Signature::new().with_params(&[int]).with_returns(&[int]),
507 "fputc" => Signature::new().with_params(&[int, Type::PTR]).with_returns(&[int]),
508 "fputs" => Signature::new().with_params(&[Type::PTR, Type::PTR]).with_returns(&[int]),
509 "strchr" => Signature::new().with_params(&[Type::PTR, int]).with_returns(&[Type::PTR]),
510 "strlen" => Signature::new().with_params(&[Type::PTR]).with_returns(&[size]),
511 "strcpy" => {
512 Signature::new().with_params(&[Type::PTR, Type::PTR]).with_returns(&[Type::PTR])
513 }
514 "memcpy" => {
515 Signature::new().with_params(&[Type::PTR, Type::PTR, size]).with_returns(&[Type::PTR])
516 }
517 "ceilf" | "floorf" | "nearbyintf" | "rintf" | "roundf" | "truncf" => {
518 let float = Type::float(Float::F32);
519 Signature::new().with_params(&[float]).with_returns(&[float])
520 }
521 "__memcpy_chk" => Signature::new()
522 .with_params(&[Type::PTR, Type::PTR, size, size])
523 .with_returns(&[Type::PTR]),
524 _ => {
527 Signature::new().with_params(&[Type::PTR, size, size, Type::PTR]).with_returns(&[size])
528 }
529 }
530}
531
532const fn int() -> Type {
538 Type::int(32)
539}
540
541fn size(module: &Module) -> Type {
543 Type::int(module.datalayout.pointer_bits)
544}
545
546pub fn fold(
551 module: &mut Module,
552 names: &mut Interner,
553 no_builtin: &[String],
554 pic: Pic,
555 fuel: &mut Fuel,
556) -> Vec<(FuncId, Stats)> {
557 let shapes = Shapes::of(module, names);
558 let standard: HashMap<Symbol, Symbol> =
562 module.funcs().filter_map(|id| Some((module[id].name, module[id].spelled?))).collect();
563 let library: HashSet<Symbol> = module
568 .funcs()
569 .filter(|&id| !module[id].is_declaration())
570 .map(|id| module[id].name)
571 .filter(|&name| {
572 let name = names.resolve(standard.get(&name).copied().unwrap_or(name));
573 SOURCES.contains(&name) || REPLACEMENTS.contains(&name)
574 })
575 .collect();
576 let mut texts: HashMap<Vec<u8>, Symbol> = HashMap::new();
579 let mut done = Vec::new();
580 for id in module.funcs().collect::<Vec<FuncId>>() {
581 if module[id].is_declaration()
586 || library.contains(&module[id].name)
587 || !mentions(&module[id], names, &standard)
588 {
589 continue;
590 }
591 let mut stats = Stats::new();
592 for _ in 0..ROUNDS {
596 let plans = {
600 let func = &module[id];
601 let site = Site {
602 module,
603 func,
604 cfg: &Cfg::new(func),
605 shapes: &shapes,
606 counts: &uses::count(func),
607 standard: &standard,
608 names,
609 no_builtin,
610 pic,
611 };
612 site.survey(fuel, &mut stats)
613 };
614 if plans.is_empty() {
615 break;
616 }
617 let mut renamed: HashMap<Value, Value> = HashMap::new();
621 for (inst, mut plan) in plans {
622 plan.rename(&renamed);
623 let made = apply(module, id, names, &mut texts, inst, plan);
624 for value in renamed.values_mut() {
625 if let Some(&to) = made.get(value) {
626 *value = to;
627 }
628 }
629 renamed.extend(made);
630 }
631 }
632 if stats.changed() {
633 done.push((id, stats));
634 }
635 }
636 done
637}
638
639fn mentions(func: &Func, names: &Interner, standard: &HashMap<Symbol, Symbol>) -> bool {
641 func.blocks().flat_map(|block| func.insts(block)).any(|inst| {
642 let data = &func[inst];
643 let Extra::Call(at) = data.extra else { return false };
644 data.opcode == Opcode::Call
645 && func[at].callee.is_some_and(|callee| {
646 let spelled = standard.get(&callee).copied().unwrap_or(callee);
647 SOURCES.contains(&names.resolve(spelled))
648 })
649 })
650}
651
652struct Site<'a> {
654 module: &'a Module,
656 func: &'a Func,
658 cfg: &'a Cfg,
660 shapes: &'a Shapes,
662 counts: &'a [u32],
664 standard: &'a HashMap<Symbol, Symbol>,
666 names: &'a Interner,
668 no_builtin: &'a [String],
670 pic: Pic,
672}
673
674impl Site<'_> {
675 fn survey(&self, fuel: &mut Fuel, stats: &mut Stats) -> Vec<(Inst, Plan)> {
677 let mut plans = Vec::new();
678 for block in self.func.blocks().collect::<Vec<_>>() {
679 for inst in self.func.insts(block).collect::<Vec<Inst>>() {
680 let Some(plan) = self.plan(inst) else { continue };
681 if !fuel.take() {
682 stats.missed("call to the library folded");
683 continue;
684 }
685 stats.optimized(match &plan {
686 Plan::Drop => "call to the library that writes nothing removed",
687 Plan::Answer(_) => "call to the library whose answer is known folded",
688 Plan::Swap { .. } => "call to the library folded",
689 Plan::Unchecked { .. } => "checking call whose check cannot fail made plain",
690 Plan::Narrow { .. } => "rounding of a widened float done in float",
691 });
692 plans.push((inst, plan));
693 }
694 }
695 plans
696 }
697
698 fn plan(&self, inst: Inst) -> Option<Plan> {
701 let data = &self.func[inst];
702 if data.opcode != Opcode::Call || self.func.mem_in(inst).is_some() {
703 return None;
704 }
705 let ignored = data.results().all(|result| self.counts[result.index()] == 0);
709 let name = self.called(inst)?;
710 let args: Vec<Value> = self.func[data.args].to_vec();
711 match name {
714 "printf" if ignored => self.printf(&args, false),
715 "printf_unlocked" if ignored => self.printf(&args, true),
716 "fprintf" if ignored => self.fprintf(&args, false),
717 "fprintf_unlocked" if ignored => self.fprintf(&args, true),
718 "fputs" if ignored => self.fputs(&args, false),
719 "fputs_unlocked" if ignored => self.fputs(&args, true),
720 "__printf_chk" if ignored => self.printf(args.get(1..)?, false),
725 "vprintf" | "__vprintf_chk" if ignored => {
726 let format = if name == "vprintf" { 0 } else { 1 };
727 self.printf(&[*args.get(format)?], false)
728 }
729 "__fprintf_chk" if ignored => {
730 let mut rest = vec![*args.first()?];
731 rest.extend_from_slice(args.get(2..)?);
732 self.fprintf(&rest, false)
733 }
734 "vfprintf" | "__vfprintf_chk" if ignored => {
735 let format = if name == "vfprintf" { 1 } else { 2 };
736 self.fprintf(&[*args.first()?, *args.get(format)?], false)
737 }
738 "strstr" => self.strstr(data, &args),
739 "strchr" | "index" => self.strchr(data, &args, Side::First),
742 "strrchr" | "rindex" => self.strchr(data, &args, Side::Last),
743 "memchr" => self.memchr(data, &args),
744 "memcmp" => self.memcmp(inst, data, &args),
745 "strlen" => self.strlen(inst, data, &args),
746 "strnlen" => self.strnlen(data, &args),
747 "strcmp" => self.strcmp(data, &args),
748 "strncmp" => self.strncmp(data, &args),
749 "strcspn" => self.span(data, &args, Set::Outside),
750 "strspn" => self.span(data, &args, Set::Inside),
751 "strpbrk" => self.strpbrk(data, &args),
752 "strcpy" | "stpcpy" => self.strcpy(data, name, &args, ignored),
753 "strcat" => self.strcat(inst, data, &args),
754 "strncat" => self.strncat(data, &args),
755 "mempcpy" => self.mempcpy(data, &args, ignored),
756 "memmove" => self.memmove(data, &args),
757 "strncpy" => self.strncpy(data, &args),
758 "bcopy" => {
760 let [source, dest, count] = *args else { return None };
761 if data.results().next().is_some() {
762 return None;
763 }
764 self.moved(dest, source, count).map(|plan| match plan {
765 Plan::Answer(_) => Plan::Drop,
766 plan => plan,
767 })
768 }
769 "sprintf" => self.sprintf(data, &args, ignored),
770 "ceil" => self.narrow(data, &args, "ceilf"),
771 "floor" => self.narrow(data, &args, "floorf"),
772 "nearbyint" => self.narrow(data, &args, "nearbyintf"),
773 "rint" => self.narrow(data, &args, "rintf"),
774 "round" => self.narrow(data, &args, "roundf"),
775 "trunc" => self.narrow(data, &args, "truncf"),
776 "__memcpy_chk" | "__memmove_chk" | "__mempcpy_chk" | "__memset_chk" => {
777 self.memory_chk(data, name, &args, ignored)
778 }
779 "__strcpy_chk" | "__stpcpy_chk" => self.strcpy_chk(data, name, &args, ignored),
780 "__strncpy_chk" | "__stpncpy_chk" => self.strncpy_chk(data, name, &args, ignored),
781 "__strcat_chk" => self.strcat_chk(data, &args),
782 "__strncat_chk" => self.strncat_chk(data, &args),
783 "__sprintf_chk" | "__vsprintf_chk" => self.sprintf_chk(data, name, &args),
784 "__snprintf_chk" | "__vsnprintf_chk" => self.snprintf_chk(data, name, &args),
785 _ => None,
786 }
787 }
788
789 fn answers(&self, data: &InstData) -> Option<Type> {
794 let mut results = data.results();
795 let ty = self.func[results.next()?].ty;
796 (results.next().is_none() && ty.is_int() && !ty.is_vector()).then_some(ty)
797 }
798
799 fn places(&self, data: &InstData) -> bool {
801 let mut results = data.results();
802 results.next().is_some_and(|result| self.func[result].ty == Type::PTR)
803 && results.next().is_none()
804 }
805
806 fn character(&self, value: Value) -> Option<u8> {
809 let (imm, ty) = crate::fold::evaluated(self.func, value, DEPTH)?;
810 u8::try_from(imm.signed(ty).rem_euclid(256)).ok()
811 }
812
813 fn count(&self, value: Value) -> Option<usize> {
819 let narrow = self.widened(value);
820 let (imm, ty) = crate::fold::evaluated(self.func, narrow, DEPTH)?;
821 (narrow == value || imm.signed(ty) >= 0).then_some(())?;
824 usize::try_from(imm.unsigned()).ok()
825 }
826
827 fn widened(&self, value: Value) -> Value {
832 let Def::Result { inst, .. } = self.func[value].def else { return value };
833 if !matches!(self.func[inst].opcode, Opcode::SExt | Opcode::ZExt) {
834 return value;
835 }
836 self.func[self.func[inst].args].first().copied().unwrap_or(value)
837 }
838
839 fn strchr(&self, data: &InstData, args: &[Value], side: Side) -> Option<Plan> {
845 if args.len() != 2 || self.func[args[0]].ty != Type::PTR || !self.places(data) {
846 return None;
847 }
848 let wanted = self.character(args[1])?;
849 let Some(text) = self.one(args[0]) else {
850 return match (wanted, side) {
854 (0, Side::Last) => {
855 self.call("strchr", vec![Argument::Have(args[0]), Argument::Char(0)])
856 }
857 _ => None,
858 };
859 };
860 let found = match (wanted, side) {
861 (0, _) => Some(text.len()),
862 (_, Side::First) => text.iter().position(|&byte| byte == wanted),
863 (_, Side::Last) => text.iter().rposition(|&byte| byte == wanted),
864 };
865 Some(Plan::Answer(match found {
866 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
867 None => Answer::Nowhere,
868 }))
869 }
870
871 fn memchr(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
878 (args.len() == 3).then_some(())?; if self.func[args[0]].ty != Type::PTR || !self.places(data) {
880 return None;
881 }
882 let wanted = self.character(args[1])?;
883 let count = self.count(args[2])?;
884 let bytes = self.raw(args[0])?;
885 let window = bytes.get(..count)?;
886 Some(Plan::Answer(match window.iter().position(|&byte| byte == wanted) {
887 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
888 None => Answer::Nowhere,
889 }))
890 }
891
892 fn strlen(&self, inst: Inst, data: &InstData, args: &[Value]) -> Option<Plan> {
894 if args.len() != 1 || self.func[args[0]].ty != Type::PTR {
895 return None;
896 }
897 self.answers(data)?;
898 if let Some(len) = self.length(args[0]) {
899 return Some(Plan::Answer(Answer::Number(i128::try_from(len).ok()?)));
900 }
901 if let Some(text) = self.stored(inst, args[0]) {
902 return Some(Plan::Answer(Answer::Number(i128::try_from(text.len()).ok()?)));
903 }
904 let Def::Result { inst, .. } = self.func[args[0]].def else { return None };
908 if self.func[inst].opcode != Opcode::PtrAdd {
909 return None;
910 }
911 let &[base, step] = &self.func[self.func[inst].args] else { return None };
912 let len = u64::try_from(self.literal(base)?.len()).ok()?;
913 if self.largest(step)? > u128::from(len) {
914 return None;
915 }
916 Some(Plan::Answer(Answer::Less { len, step }))
917 }
918
919 fn stored(&self, call: Inst, value: Value) -> Option<Vec<u8>> {
930 let (base, offset) = self.address(value)?;
931 let bytes = self.before(call, base)?;
932 let mut text = Vec::new();
933 for at in (offset..).take(bytes.len()) {
934 match *bytes.get(&at)? {
935 0 => return Some(text),
936 byte => text.push(byte),
937 }
938 }
939 None
940 }
941
942 fn before(&self, call: Inst, base: Value) -> Option<HashMap<i128, u8>> {
945 let size = self.extent(base)?;
946 let mut bytes: HashMap<i128, u8> = HashMap::new();
947 let mut block = self.func.block_of(call)?;
948 let mut from = Some(call);
949 'walk: for _ in 0..CHAIN {
950 let insts: Vec<Inst> = match from.take() {
951 Some(call) => self
952 .func
953 .insts_backwards(block)
954 .skip_while(|&inst| inst != call)
955 .skip(1)
956 .collect(),
957 None => self.func.insts_backwards(block).collect(),
958 };
959 for inst in insts {
960 if self.wrote(inst, base, size, &mut bytes).is_none() {
961 break 'walk;
962 }
963 }
964 let Some(pred) = self.only_way_in(block) else { break };
965 block = pred;
966 }
967 Some(bytes)
968 }
969
970 fn only_way_in(&self, block: Block) -> Option<Block> {
977 let mut live = self
978 .cfg
979 .predecessors(block)
980 .iter()
981 .copied()
982 .filter(|&pred| !self.func.insts(pred).any(|inst| self.never_back(inst)));
983 let first = live.next()?;
984 live.next().is_none().then_some(first)
985 }
986
987 fn never_back(&self, inst: Inst) -> bool {
990 if self.func[inst].opcode != Opcode::Call {
991 return false;
992 }
993 let Extra::Call(at) = self.func[inst].extra else { return false };
994 let Some(callee) = self.func[at].callee else { return false };
995 let Some(SymbolRef::Func(id)) = self.module.lookup(callee) else { return false };
996 let target = &self.module[id];
997 target.attrs.set.contains(AttrSet::NORETURN)
998 || (target.entry().is_none()
999 && matches!(self.called(inst), Some("abort" | "exit" | "_Exit" | "quick_exit")))
1000 }
1001
1002 fn held(&self, call: Inst, value: Value, count: usize) -> Option<Vec<u8>> {
1005 if let Some(bytes) = self.raw(value) {
1006 return Some(bytes.get(..count)?.to_vec());
1007 }
1008 let (base, offset) = self.address(value)?;
1009 let bytes = self.before(call, base)?;
1010 (offset..).take(count).map(|at| bytes.get(&at).copied()).collect()
1011 }
1012
1013 fn wrote(
1022 &self,
1023 inst: Inst,
1024 base: Value,
1025 size: u64,
1026 bytes: &mut HashMap<i128, u8>,
1027 ) -> Option<()> {
1028 let data = &self.func[inst];
1029 if !data.opcode.writes_memory() {
1030 return Some(());
1031 }
1032 let args = &self.func[data.args];
1033 let (to, written) = match data.opcode {
1034 Opcode::Store => {
1035 let &[byte, to] = args else { return None };
1036 if self.func[byte].ty != Type::int(8) {
1037 return None;
1038 }
1039 (to, vec![u8::try_from(self.number(byte)?).ok()?])
1040 }
1041 Opcode::Call => {
1042 let &to = args.first()?;
1043 let count =
1044 || self.number(*args.get(2)?).filter(|&count| count <= u128::from(size));
1045 let written = match self.called(inst)? {
1046 "memset" => {
1047 vec![self.character(*args.get(1)?)?; usize::try_from(count()?).ok()?]
1048 }
1049 "memcpy" => {
1050 let count = usize::try_from(count()?).ok()?;
1051 self.raw(*args.get(1)?)?.get(..count)?.to_vec()
1052 }
1053 "strcpy" => {
1054 let mut text = self.one(*args.get(1)?)?;
1055 text.push(0);
1056 text
1057 }
1058 "memcmp" | "memchr" | "strcmp" | "strncmp" | "strlen" | "strchr" => {
1060 return Some(());
1061 }
1062 _ => return None,
1063 };
1064 (to, written)
1065 }
1066 _ => return None,
1067 };
1068 let (root, at) = self.address(to)?;
1069 if root != base {
1070 return self.local(root).then_some(());
1071 }
1072 let end = at.checked_add(i128::try_from(written.len()).ok()?)?;
1073 if at < 0 || end > i128::from(size) {
1074 return None;
1075 }
1076 for (place, byte) in (at..).zip(written) {
1077 bytes.entry(place).or_insert(byte);
1078 }
1079 Some(())
1080 }
1081
1082 fn called(&self, inst: Inst) -> Option<&str> {
1085 let Extra::Call(at) = self.func[inst].extra else { return None };
1086 let callee = self.func[at].callee?;
1087 let name = self.names.resolve(self.standard.get(&callee).copied().unwrap_or(callee));
1088 (!self.no_builtin.iter().any(|it| it == name)).then_some(name)
1089 }
1090
1091 fn extent(&self, value: Value) -> Option<u64> {
1093 let Def::Result { inst, .. } = self.func[value].def else { return None };
1094 let data = &self.func[inst];
1095 if data.opcode != Opcode::Alloca || !self.func[data.args].is_empty() {
1096 return None;
1097 }
1098 let Extra::Mem(mem) = data.extra else { return None };
1099 Some(self.func[mem].size)
1100 }
1101
1102 fn strnlen(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1107 if args.len() != 2 || self.func[args[0]].ty != Type::PTR {
1108 return None;
1109 }
1110 let ty = self.answers(data)?;
1111 if let Some(text) = self.literal(args[0]) {
1116 let len = u64::try_from(text.len()).ok()?;
1117 if let Some((imm, _)) = crate::fold::evaluated(self.func, args[1], DEPTH) {
1118 let least = imm.unsigned().min(u128::from(len));
1119 return Some(Plan::Answer(Answer::Number(i128::try_from(least).ok()?)));
1120 }
1121 if len == 0 {
1123 return Some(Plan::Answer(Answer::Number(0)));
1124 }
1125 (self.func[args[1]].ty == ty).then_some(())?;
1128 return Some(Plan::Answer(Answer::Least { count: args[1], len }));
1129 }
1130 let count = self.count(args[1])?;
1131 let bytes = self.raw(args[0])?;
1132 let window = bytes.get(..count.min(bytes.len()))?;
1133 let len = match window.iter().position(|&byte| byte == 0) {
1134 Some(at) => at,
1135 None if window.len() == count => count,
1138 None => return None,
1139 };
1140 Some(Plan::Answer(Answer::Number(i128::try_from(len).ok()?)))
1141 }
1142
1143 fn memcmp(&self, call: Inst, data: &InstData, args: &[Value]) -> Option<Plan> {
1152 (args.len() == 3).then_some(())?; if self.func[args[0]].ty != Type::PTR || self.func[args[1]].ty != Type::PTR {
1154 return None;
1155 }
1156 let ty = self.answers(data)?;
1157 let count = self.count(args[2])?;
1158 if count == 0 {
1160 return Some(Plan::Answer(Answer::Number(0)));
1161 }
1162 match (self.held(call, args[0], count), self.held(call, args[1], count)) {
1163 (Some(left), Some(right)) => {
1164 let differs = left.iter().zip(&right).find(|(this, that)| this != that);
1165 let sign = differs.map_or(0, |(this, that)| if this < that { -1 } else { 1 });
1166 Some(Plan::Answer(Answer::Number(sign)))
1167 }
1168 (Some(known), None) => self.byte(ty, &known, args[1], true, count),
1169 (None, Some(known)) => self.byte(ty, &known, args[0], false, count),
1170 (None, None) => None,
1171 }
1172 }
1173
1174 fn strcmp(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1176 (args.len() == 2).then_some(())?;
1177 self.compared(data, args, usize::MAX)
1178 }
1179
1180 fn strncmp(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1182 (args.len() == 3).then_some(())?; let count = self.count(args[2])?;
1184 self.compared(data, args, count)
1185 }
1186
1187 fn compared(&self, data: &InstData, args: &[Value], bound: usize) -> Option<Plan> {
1194 if self.func[args[0]].ty != Type::PTR || self.func[args[1]].ty != Type::PTR {
1195 return None;
1196 }
1197 let ty = self.answers(data)?;
1198 if bound == 0 {
1201 return Some(Plan::Answer(Answer::Number(0)));
1202 }
1203 match (self.one(args[0]), self.one(args[1])) {
1204 (Some(left), Some(right)) => {
1205 Some(Plan::Answer(Answer::Number(walk(&left, &right, bound))))
1206 }
1207 (Some(known), None) => self.byte(ty, &known, args[1], true, bound),
1208 (None, Some(known)) => self.byte(ty, &known, args[0], false, bound),
1209 (None, None) => None,
1210 }
1211 }
1212
1213 fn byte(
1220 &self,
1221 ty: Type,
1222 known: &[u8],
1223 other: Value,
1224 leading: bool,
1225 bound: usize,
1226 ) -> Option<Plan> {
1227 (bound == 1 || known.is_empty()).then_some(())?;
1228 (ty.bits() > 8).then_some(())?;
1231 let against = known.first().copied().unwrap_or(0);
1232 Some(Plan::Answer(Answer::Byte { of: other, against, leading }))
1233 }
1234
1235 fn span(&self, data: &InstData, args: &[Value], set: Set) -> Option<Plan> {
1242 if args.len() != 2
1243 || self.func[args[0]].ty != Type::PTR
1244 || self.func[args[1]].ty != Type::PTR
1245 {
1246 return None;
1247 }
1248 let ty = self.answers(data)?;
1249 if self.one(args[0]).is_some_and(|text| text.is_empty()) {
1252 return Some(Plan::Answer(Answer::Number(0)));
1253 }
1254 let accept = self.one(args[1])?;
1255 if let Some(text) = self.one(args[0]) {
1258 let len = text
1259 .iter()
1260 .position(|byte| accept.contains(byte) != matches!(set, Set::Inside))
1261 .unwrap_or(text.len());
1262 return Some(Plan::Answer(Answer::Number(i128::try_from(len).ok()?)));
1263 }
1264 accept.is_empty().then_some(())?;
1266 match set {
1267 Set::Inside => Some(Plan::Answer(Answer::Number(0))),
1268 Set::Outside => {
1272 let (_, signature) = self.shapes.get("strlen")?;
1273 signature.return_types().eq([ty]).then_some(())?;
1274 self.call("strlen", vec![Argument::Have(args[0])])
1275 }
1276 }
1277 }
1278
1279 fn strpbrk(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1281 if args.len() != 2 || self.func[args[0]].ty != Type::PTR || !self.places(data) {
1282 return None;
1283 }
1284 if self.func[args[1]].ty != Type::PTR {
1285 return None;
1286 }
1287 let accept = self.one(args[1])?;
1288 if accept.is_empty() {
1290 return Some(Plan::Answer(Answer::Nowhere));
1291 }
1292 match self.one(args[0]) {
1293 Some(text) => {
1294 Some(Plan::Answer(match text.iter().position(|byte| accept.contains(byte)) {
1295 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
1296 None => Answer::Nowhere,
1297 }))
1298 }
1299 None => match accept.as_slice() {
1302 [one] => self.call("strchr", vec![Argument::Have(args[0]), Argument::Char(*one)]),
1303 _ => None,
1304 },
1305 }
1306 }
1307
1308 fn strstr(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1314 if args.len() != 2 {
1315 return None;
1316 }
1317 let (haystack, needle) = (args[0], args[1]);
1318 if self.func[haystack].ty != Type::PTR || self.func[needle].ty != Type::PTR {
1319 return None;
1320 }
1321 let mut results = data.results();
1324 if !results.next().is_some_and(|result| self.func[result].ty == Type::PTR)
1325 || results.next().is_some()
1326 {
1327 return None;
1328 }
1329 let needle = self.one(needle)?;
1330 if needle.is_empty() {
1332 return Some(Plan::Answer(Answer::Along(haystack, 0)));
1333 }
1334 match self.one(haystack) {
1335 Some(hay) => Some(Plan::Answer(match at(&hay, &needle) {
1336 Some(found) => Answer::Along(haystack, u64::try_from(found).ok()?),
1337 None => Answer::Nowhere,
1338 })),
1339 None => match needle.as_slice() {
1342 [one] => self.call("strchr", vec![Argument::Have(haystack), Argument::Char(*one)]),
1343 _ => None,
1344 },
1345 }
1346 }
1347
1348 fn printf(&self, args: &[Value], quiet: bool) -> Option<Plan> {
1350 let format = self.one(*args.first()?)?;
1351 match args.len() {
1352 1 => self.plain(&format, None, quiet),
1353 2 if format == b"%s\n" && !quiet && self.func[args[1]].ty == Type::PTR => {
1354 self.call("puts", vec![Argument::Have(args[1])])
1355 }
1356 2 if format == b"%c" && !quiet && self.func[args[1]].ty == int() => {
1357 self.call("putchar", vec![Argument::Have(args[1])])
1358 }
1359 2 if format == b"%s" => self.plain(&self.one(args[1])?, None, quiet),
1363 _ => None,
1364 }
1365 }
1366
1367 fn fprintf(&self, args: &[Value], quiet: bool) -> Option<Plan> {
1369 let stream = *args.first()?;
1370 if self.func[stream].ty != Type::PTR {
1371 return None;
1372 }
1373 let format = self.one(*args.get(1)?)?;
1374 match args.len() {
1375 2 => self.plain(&format, Some((args[1], stream)), quiet),
1376 3 if format == b"%c" && !quiet && self.func[args[2]].ty == int() => {
1377 self.call("fputc", vec![Argument::Have(args[2]), Argument::Have(stream)])
1378 }
1379 3 if format == b"%s" && self.func[args[2]].ty == Type::PTR => {
1383 match self.strings(args[2]) {
1384 Some(candidates) => self.string(&candidates, args[2], stream, quiet),
1385 None if quiet => None,
1386 None => {
1387 self.call("fputs", vec![Argument::Have(args[2]), Argument::Have(stream)])
1388 }
1389 }
1390 }
1391 _ => None,
1392 }
1393 }
1394
1395 fn fputs(&self, args: &[Value], quiet: bool) -> Option<Plan> {
1397 if args.len() != 2 {
1398 return None;
1399 }
1400 let (text, stream) = (args[0], args[1]);
1401 if self.func[text].ty != Type::PTR || self.func[stream].ty != Type::PTR {
1402 return None;
1403 }
1404 self.string(&self.strings(text)?, text, stream, quiet)
1405 }
1406
1407 fn plain(&self, format: &[u8], stream: Option<(Value, Value)>, quiet: bool) -> Option<Plan> {
1412 if format.is_empty() {
1413 return Some(Plan::Drop);
1414 }
1415 if quiet || format.contains(&b'%') {
1416 return None;
1417 }
1418 match (format, stream) {
1419 ([one], Some((_, stream))) => {
1420 self.call("fputc", vec![Argument::Char(*one), Argument::Have(stream)])
1421 }
1422 (_, Some((text, stream))) => self.fwrite(Argument::Have(text), format.len(), stream),
1425 ([one], None) => self.call("putchar", vec![Argument::Char(*one)]),
1426 (_, None) => {
1429 let (&last, rest) = format.split_last()?;
1430 match last {
1431 b'\n' => self.call("puts", vec![Argument::Text(rest.to_vec())]),
1432 _ => None,
1433 }
1434 }
1435 }
1436 }
1437
1438 fn string(
1444 &self,
1445 candidates: &[Vec<u8>],
1446 text: Value,
1447 stream: Value,
1448 quiet: bool,
1449 ) -> Option<Plan> {
1450 let first = candidates.first()?;
1451 if candidates.iter().any(|it| it.len() != first.len()) {
1452 return None;
1453 }
1454 if first.is_empty() {
1455 return Some(Plan::Drop);
1456 }
1457 if quiet {
1458 return None;
1459 }
1460 match first.as_slice() {
1461 [one] if candidates.iter().all(|it| it[0] == *one) => {
1462 self.call("fputc", vec![Argument::Char(*one), Argument::Have(stream)])
1463 }
1464 _ => self.fwrite(Argument::Have(text), first.len(), stream),
1469 }
1470 }
1471
1472 fn fwrite(&self, text: Argument, bytes: usize, stream: Value) -> Option<Plan> {
1474 let len = u64::try_from(bytes).ok()?;
1475 self.call(
1476 "fwrite",
1477 vec![text, Argument::Count(1), Argument::Count(len), Argument::Have(stream)],
1478 )
1479 }
1480
1481 fn strcpy(&self, data: &InstData, name: &str, args: &[Value], ignored: bool) -> Option<Plan> {
1487 let [dest, source] = *args else { return None };
1488 if !self.places(data) {
1489 return None;
1490 }
1491 let end = name == "stpcpy";
1492 if end && ignored {
1493 return self.unchecked(data, "strcpy", &[]);
1494 }
1495 let len = self.length(source)?;
1496 let (callee, signature) = self.shapes.get("memcpy")?;
1497 let args =
1498 vec![Argument::Have(dest), Argument::Have(source), Argument::Count(len as u64 + 1)];
1499 let answer = end.then_some(Answer::Along(dest, len as u64));
1500 Some(Plan::Swap { callee, signature, args, answer })
1501 }
1502
1503 fn nothing(&self, data: &InstData, args: &[Value], count: Option<Value>) -> Option<Plan> {
1508 let [dest, source] = *args else { return None };
1509 let nothing = self.one(source).is_some_and(|text| text.is_empty())
1510 || count.is_some_and(|count| self.number(count) == Some(0));
1511 (nothing && self.places(data)).then_some(Plan::Answer(Answer::Along(dest, 0)))
1512 }
1513
1514 fn strcat(&self, inst: Inst, data: &InstData, args: &[Value]) -> Option<Plan> {
1519 if let Some(plan) = self.nothing(data, args, None) {
1520 return Some(plan);
1521 }
1522 let [dest, source] = *args else { return None };
1523 if !self.places(data) {
1524 return None;
1525 }
1526 let len = u64::try_from(self.length(source)?).ok()?;
1527 let before = u64::try_from(self.stored(inst, dest)?.len()).ok()?;
1528 let (callee, signature) = self.shapes.get("memcpy")?;
1529 let args =
1530 vec![Argument::At(dest, before), Argument::Have(source), Argument::Count(len + 1)];
1531 Some(Plan::Swap { callee, signature, args, answer: Some(Answer::Along(dest, 0)) })
1532 }
1533
1534 fn strncat(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1536 let [dest, source, count] = *args else { return None };
1537 if let Some(plan) = self.nothing(data, &[dest, source], Some(count)) {
1538 return Some(plan);
1539 }
1540 let len = self.one(source)?.len() as u128;
1541 (self.number(count)? >= len).then(|| self.unchecked(data, "strcat", &[2]))?
1542 }
1543
1544 fn memmove(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1547 let [dest, source, count] = *args else { return None };
1548 if !self.places(data) {
1549 return None;
1550 }
1551 self.moved(dest, source, count)
1552 }
1553
1554 fn moved(&self, dest: Value, source: Value, count: Value) -> Option<Plan> {
1564 if self.number(count) == Some(0) {
1565 return Some(Plan::Answer(Answer::Along(dest, 0)));
1566 }
1567 let (to, _) = self.address(dest)?;
1568 let (from, _) = self.address(source)?;
1569 let apart = self.largest(count).is_some_and(|count| count <= 1)
1570 || self.fixed(from)
1571 || (to != from && self.object(to) && self.object(from))
1572 && (self.local(to) || self.local(from));
1573 apart.then(|| self.copy(dest, source, count))?
1574 }
1575
1576 fn strncpy(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1580 let [dest, source, count] = *args else { return None };
1581 if !self.places(data) {
1582 return None;
1583 }
1584 let number = self.number(count)?;
1585 if number == 0 {
1586 return Some(Plan::Answer(Answer::Along(dest, 0)));
1587 }
1588 let len = u128::try_from(self.length(source)?).ok()?;
1589 (number <= len + 1).then(|| self.copy(dest, source, count))?
1590 }
1591
1592 fn copy(&self, dest: Value, source: Value, count: Value) -> Option<Plan> {
1594 let (callee, signature) = self.shapes.get("memcpy")?;
1595 let args = vec![Argument::Have(dest), Argument::Have(source), Argument::Have(count)];
1596 Some(Plan::Swap { callee, signature, args, answer: None })
1597 }
1598
1599 fn object(&self, value: Value) -> bool {
1601 self.local(value)
1602 || matches!(self.func[value].def, Def::Result { inst, .. } if self.func[inst].opcode == Opcode::GlobalAddr)
1603 }
1604
1605 fn local(&self, value: Value) -> bool {
1607 matches!(self.func[value].def, Def::Result { inst, .. } if self.func[inst].opcode == Opcode::Alloca)
1608 }
1609
1610 fn fixed(&self, value: Value) -> bool {
1613 let Def::Result { inst, .. } = self.func[value].def else { return false };
1614 if self.func[inst].opcode != Opcode::GlobalAddr {
1615 return false;
1616 }
1617 let Extra::Symbol(name) = self.func[inst].extra else { return false };
1618 let Some(SymbolRef::Global(id)) = self.module.lookup(name) else { return false };
1619 let global = &self.module[id];
1620 global.constant && vouched(global, self.pic)
1621 }
1622
1623 fn mempcpy(&self, data: &InstData, args: &[Value], ignored: bool) -> Option<Plan> {
1628 let [dest, source, count] = *args else { return None };
1629 if ignored {
1630 return self.unchecked(data, "memcpy", &[]);
1631 }
1632 let along = u64::try_from(self.number(count)?).ok()?;
1633 if !self.places(data) {
1634 return None;
1635 }
1636 let (callee, signature) = self.shapes.get("memcpy")?;
1637 let args = vec![Argument::Have(dest), Argument::Have(source), Argument::Have(count)];
1638 Some(Plan::Swap { callee, signature, args, answer: Some(Answer::Along(dest, along)) })
1639 }
1640
1641 fn sprintf(&self, data: &InstData, args: &[Value], ignored: bool) -> Option<Plan> {
1647 let (&dest, &format) = (args.first()?, args.get(1)?);
1648 let text = self.one(format)?;
1649 let source = match *args {
1650 [_, _] if !text.contains(&b'%') => format,
1651 [_, _, arg] if text == b"%s" && self.func[arg].ty == Type::PTR => arg,
1652 _ => return None,
1653 };
1654 let answer = if ignored {
1655 None
1656 } else {
1657 self.answers(data)?;
1658 Some(Answer::Number(i128::try_from(self.one(source)?.len()).ok()?))
1659 };
1660 let (callee, signature) = self.shapes.get("strcpy")?;
1661 Some(Plan::Swap {
1662 callee,
1663 signature,
1664 args: vec![Argument::Have(dest), Argument::Have(source)],
1665 answer,
1666 })
1667 }
1668
1669 fn memory_chk(
1675 &self,
1676 data: &InstData,
1677 name: &str,
1678 args: &[Value],
1679 ignored: bool,
1680 ) -> Option<Plan> {
1681 let [_, _, count, size] = *args else { return None };
1682 if self.fits(count, size) {
1683 return self.unchecked(data, plain(name), &[3]);
1684 }
1685 (name == "__mempcpy_chk" && ignored).then(|| self.unchecked(data, "__memcpy_chk", &[]))?
1686 }
1687
1688 fn strcpy_chk(
1696 &self,
1697 data: &InstData,
1698 name: &str,
1699 args: &[Value],
1700 ignored: bool,
1701 ) -> Option<Plan> {
1702 let [dest, source, size] = *args else { return None };
1703 let end = name == "__stpcpy_chk";
1704 let fits = self.unknown(size)
1705 || self.longest(source).zip(self.number(size)).is_some_and(|(len, size)| len < size);
1706 if fits {
1707 return self.unchecked(data, if end && !ignored { "stpcpy" } else { "strcpy" }, &[2]);
1708 }
1709 if end {
1710 return ignored.then(|| self.unchecked(data, "__strcpy_chk", &[]))?;
1711 }
1712 let len = self.one(source)?.len() as u64;
1713 let args = vec![
1714 Argument::Have(dest),
1715 Argument::Have(source),
1716 Argument::Count(len + 1),
1717 Argument::Have(size),
1718 ];
1719 self.call("__memcpy_chk", args)
1720 }
1721
1722 fn strncpy_chk(
1725 &self,
1726 data: &InstData,
1727 name: &str,
1728 args: &[Value],
1729 ignored: bool,
1730 ) -> Option<Plan> {
1731 let [_, _, count, size] = *args else { return None };
1732 let end = name == "__stpncpy_chk";
1733 if self.fits(count, size) {
1734 return self.unchecked(data, if end && !ignored { "stpncpy" } else { "strncpy" }, &[3]);
1735 }
1736 (end && ignored).then(|| self.unchecked(data, "__strncpy_chk", &[]))?
1737 }
1738
1739 fn strcat_chk(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1744 let [dest, source, size] = *args else { return None };
1745 if let Some(plan) = self.nothing(data, &[dest, source], None) {
1746 return Some(plan);
1747 }
1748 self.unknown(size).then(|| self.unchecked(data, "strcat", &[2]))?
1749 }
1750
1751 fn strncat_chk(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
1754 let [dest, source, count, size] = *args else { return None };
1755 if let Some(plan) = self.nothing(data, &[dest, source], Some(count)) {
1756 return Some(plan);
1757 }
1758 if self.unknown(size) {
1759 return self.unchecked(data, "strncat", &[3]);
1760 }
1761 let len = self.one(source)?.len() as u128;
1762 (self.number(count)? >= len).then(|| self.unchecked(data, "__strcat_chk", &[2]))?
1763 }
1764
1765 fn sprintf_chk(&self, data: &InstData, name: &str, args: &[Value]) -> Option<Plan> {
1772 let (&flag, &size, &format) = (args.get(1)?, args.get(2)?, args.get(3)?);
1773 let text = self.one(format);
1774 let len = match (text.as_deref(), args.get(4..)?) {
1775 (Some(text), rest)
1776 if !text.contains(&b'%') && (name == "__vsprintf_chk" || rest.is_empty()) =>
1777 {
1778 Some(text.len() as u128)
1779 }
1780 (Some(b"%s"), &[arg]) if name == "__sprintf_chk" => {
1781 self.one(arg).map(|arg| arg.len() as u128)
1782 }
1783 _ => None,
1784 };
1785 let fits =
1786 self.unknown(size) || len.zip(self.number(size)).is_some_and(|(len, size)| len < size);
1787 (fits && self.flagless(flag, text.as_deref()))
1788 .then(|| self.unchecked(data, plain(name), &[1, 2]))?
1789 }
1790
1791 fn snprintf_chk(&self, data: &InstData, name: &str, args: &[Value]) -> Option<Plan> {
1794 let (&count, &flag, &size, &format) =
1795 (args.get(1)?, args.get(2)?, args.get(3)?, args.get(4)?);
1796 let text = self.one(format);
1797 (self.fits(count, size) && self.flagless(flag, text.as_deref()))
1798 .then(|| self.unchecked(data, plain(name), &[2, 3]))?
1799 }
1800
1801 fn flagless(&self, flag: Value, text: Option<&[u8]>) -> bool {
1807 self.number(flag) == Some(0)
1808 || text.is_some_and(|text| !text.contains(&b'%') || text == b"%s")
1809 }
1810
1811 fn unchecked(&self, data: &InstData, name: &str, drop: &'static [usize]) -> Option<Plan> {
1814 let Extra::Call(at) = data.extra else { return None };
1815 let want = without(&self.func[self.func[at].signature], drop)?;
1816 let callee = self.shapes.unchecked(name, &want)?;
1817 Some(Plan::Unchecked { callee, drop })
1818 }
1819
1820 fn unknown(&self, size: Value) -> bool {
1825 crate::fold::evaluated(self.func, size, DEPTH).is_some_and(|(imm, ty)| imm.signed(ty) == -1)
1826 }
1827
1828 fn fits(&self, count: Value, size: Value) -> bool {
1830 self.unknown(size)
1831 || self.largest(count).zip(self.number(size)).is_some_and(|(count, size)| count <= size)
1832 }
1833
1834 fn largest(&self, value: Value) -> Option<u128> {
1841 self.largest_on(value, CHAIN, &mut Vec::new())
1842 }
1843
1844 fn largest_on(&self, value: Value, depth: u32, on: &mut Vec<Value>) -> Option<u128> {
1846 if depth == 0 {
1847 return None;
1848 }
1849 match self.func[value].def {
1850 Def::Param { block, index } => {
1851 if on.contains(&value) {
1852 return Some(0);
1853 }
1854 let preds = self.cfg.predecessors(block);
1855 on.push(value);
1856 let mut most = None;
1857 for &pred in preds {
1858 let term = self.func.terminator(pred)?;
1859 for call in self.func.successors(term).collect::<Vec<_>>() {
1860 if call.block != block {
1861 continue;
1862 }
1863 let arg = *self.func[call.args].get(index as usize)?;
1864 most = most.max(Some(self.largest_on(arg, depth - 1, on)?));
1865 }
1866 }
1867 on.pop();
1868 most
1869 }
1870 Def::Result { inst, .. } if self.func[inst].opcode == Opcode::Select => {
1871 let args = &self.func[self.func[inst].args];
1872 let (then, other) = (*args.get(1)?, *args.get(2)?);
1873 let then = self.largest_on(then, depth - 1, on)?;
1874 Some(then.max(self.largest_on(other, depth - 1, on)?))
1875 }
1876 _ => self.number(value).or_else(|| self.bounded(value, depth, on)),
1877 }
1878 }
1879
1880 fn bounded(&self, value: Value, depth: u32, on: &mut Vec<Value>) -> Option<u128> {
1884 let Def::Result { inst, .. } = self.func[value].def else { return None };
1885 let args = &self.func[self.func[inst].args];
1886 let narrow = *args.first()?;
1887 match self.func[inst].opcode {
1888 Opcode::And => self.number(narrow).or_else(|| self.number(*args.get(1)?)),
1889 Opcode::URem => self.number(*args.get(1)?)?.checked_sub(1),
1890 Opcode::ZExt => self.largest_on(narrow, depth - 1, on),
1891 Opcode::SExt => {
1894 let most = self.largest_on(narrow, depth - 1, on)?;
1895 let top = 1u128.checked_shl(self.func[narrow].ty.bits().checked_sub(1)?)?;
1896 (most < top).then_some(most)
1897 }
1898 _ => None,
1899 }
1900 }
1901
1902 fn length(&self, value: Value) -> Option<usize> {
1905 let texts = self.strings(value)?;
1906 let len = texts.first()?.len();
1907 texts.iter().all(|text| text.len() == len).then_some(len)
1908 }
1909
1910 fn number(&self, value: Value) -> Option<u128> {
1912 crate::fold::evaluated(self.func, value, DEPTH).map(|(imm, _)| imm.unsigned())
1913 }
1914
1915 fn longest(&self, value: Value) -> Option<u128> {
1917 self.strings(value)?.iter().map(|text| text.len() as u128).max()
1918 }
1919
1920 fn narrow(&self, data: &InstData, args: &[Value], callee: &'static str) -> Option<Plan> {
1927 let &[wide] = args else { return None };
1928 let double = Type::float(Float::F64);
1929 let float = Type::float(Float::F32);
1930 if self.func[wide].ty != double || self.answers_float(data) != Some(double) {
1931 return None;
1932 }
1933 let Def::Result { inst, .. } = self.func[wide].def else { return None };
1934 let widened = &self.func[inst];
1935 let &[arg] = &self.func[widened.args] else { return None };
1936 if widened.opcode != Opcode::FPExt || self.func[arg].ty != float {
1937 return None;
1938 }
1939 let (callee, signature) = self.shapes.get(callee)?;
1940 Some(Plan::Narrow { callee, signature, arg })
1941 }
1942
1943 fn answers_float(&self, data: &InstData) -> Option<Type> {
1945 let mut results = data.results();
1946 let ty = self.func[results.next()?].ty;
1947 (results.next().is_none() && ty.is_float() && !ty.is_vector()).then_some(ty)
1948 }
1949
1950 fn call(&self, callee: &'static str, args: Vec<Argument>) -> Option<Plan> {
1952 let (callee, signature) = self.shapes.get(callee)?;
1953 Some(Plan::Swap { callee, signature, args, answer: None })
1954 }
1955
1956 fn one(&self, value: Value) -> Option<Vec<u8>> {
1958 let mut candidates = self.strings(value)?;
1959 (candidates.len() == 1).then(|| candidates.pop()).flatten()
1960 }
1961
1962 fn strings(&self, value: Value) -> Option<Vec<Vec<u8>>> {
1969 self.strings_on(value, CHAIN, &mut Vec::new())
1970 }
1971
1972 fn strings_on(&self, value: Value, depth: u32, on: &mut Vec<Value>) -> Option<Vec<Vec<u8>>> {
1979 if depth == 0 {
1980 return None;
1981 }
1982 match self.func[value].def {
1983 Def::Param { block, index } => {
1984 if on.contains(&value) {
1985 return Some(Vec::new());
1986 }
1987 let preds = self.cfg.predecessors(block);
1988 if preds.is_empty() {
1989 return None;
1990 }
1991 on.push(value);
1992 let mut all = Vec::new();
1993 for &pred in preds {
1994 let term = self.func.terminator(pred)?;
1995 for call in self.func.successors(term).collect::<Vec<_>>() {
1996 if call.block != block {
1997 continue;
1998 }
1999 let arg = *self.func[call.args].get(index as usize)?;
2000 all.extend(self.strings_on(arg, depth - 1, on)?);
2001 }
2002 }
2003 on.pop();
2004 (!all.is_empty()).then_some(all)
2005 }
2006 Def::Result { inst, .. } if self.func[inst].opcode == Opcode::Select => {
2007 let args = &self.func[self.func[inst].args];
2008 let (then, other) = (*args.get(1)?, *args.get(2)?);
2009 let mut all = self.strings_on(then, depth - 1, on)?;
2010 all.extend(self.strings_on(other, depth - 1, on)?);
2011 Some(all)
2012 }
2013 _ => Some(vec![self.literal(value)?]),
2014 }
2015 }
2016
2017 fn literal(&self, value: Value) -> Option<Vec<u8>> {
2020 let bytes = self.raw(value)?;
2021 let end = bytes.iter().position(|&byte| byte == 0)?;
2022 Some(bytes[..end].to_vec())
2023 }
2024
2025 fn raw(&self, value: Value) -> Option<Vec<u8>> {
2031 let (base, offset) = self.address(value)?;
2032 let Def::Result { inst, .. } = self.func[base].def else { return None };
2033 if self.func[inst].opcode != Opcode::GlobalAddr {
2034 return None;
2035 }
2036 let Extra::Symbol(name) = self.func[inst].extra else { return None };
2037 let Some(SymbolRef::Global(id)) = self.module.lookup(name) else { return None };
2038 let global = &self.module[id];
2039 if !global.constant || !vouched(global, self.pic) {
2040 return None;
2041 }
2042 let mut bytes = Vec::new();
2043 for &datum in &self.module[global.init?] {
2044 match datum {
2045 Datum::Bytes(range) => bytes.extend_from_slice(&self.module[range]),
2046 Datum::Zero(count) => {
2047 bytes.resize(bytes.len().checked_add(usize::try_from(count).ok()?)?, 0);
2048 }
2049 Datum::Scalar { .. } | Datum::Addr(_) | Datum::Away(_) | Datum::Apart { .. } => {
2053 return None;
2054 }
2055 }
2056 }
2057 let size = usize::try_from(global.size).ok()?;
2060 if bytes.len() < size {
2061 bytes.resize(size, 0);
2062 }
2063 Some(bytes.get(usize::try_from(offset).ok()?..)?.to_vec())
2064 }
2065
2066 fn address(&self, mut value: Value) -> Option<(Value, i128)> {
2072 let mut offset: i128 = 0;
2073 for _ in 0..DEPTH {
2074 let Def::Result { inst, .. } = self.func[value].def else {
2075 return Some((value, offset));
2076 };
2077 if self.func[inst].opcode != Opcode::PtrAdd {
2078 return Some((value, offset));
2079 }
2080 let args = &self.func[self.func[inst].args];
2081 offset = offset.checked_add(self.step(*args.get(1)?)?)?;
2082 value = *args.first()?;
2083 }
2084 None
2085 }
2086
2087 fn step(&self, value: Value) -> Option<i128> {
2097 let (imm, ty) = crate::fold::evaluated(self.func, value, DEPTH)?;
2098 Some(imm.signed(ty))
2099 }
2100}
2101
2102fn apply(
2104 module: &mut Module,
2105 id: FuncId,
2106 names: &mut Interner,
2107 texts: &mut HashMap<Vec<u8>, Symbol>,
2108 inst: Inst,
2109 plan: Plan,
2110) -> HashMap<Value, Value> {
2111 let (callee, signature, args, answer) = match plan {
2112 Plan::Drop => {
2113 module[id].remove_inst(inst);
2114 return HashMap::new();
2115 }
2116 Plan::Answer(answer) => {
2117 let width = size(module);
2118 return answered(&mut module[id], inst, answer, width);
2119 }
2120 Plan::Swap { callee, signature, args, answer } => (callee, signature, args, answer),
2121 Plan::Narrow { callee, signature, arg } => {
2122 let func = &mut module[id];
2123 let Some(old) = func[inst].results().next() else { return HashMap::new() };
2124 let ty = func[old].ty;
2125 let span = func.span(inst);
2126 let varargs = func.push_abis(&[]);
2127 let made = call(func, inst, callee, signature, varargs, &[arg]);
2128 let narrow = func[made].results().next().expect("a rounding is one value");
2129 let args = func.push_values(&[narrow]);
2130 let data = InstData { args, ..InstData::new(Opcode::FPExt) };
2131 let wide = func.create_inst(data, &[ty], span);
2132 func.insert_before(wide, inst);
2133 let value = func[wide].results().next().expect("a conversion is one value");
2134 let forward = HashMap::from([(old, value)]);
2135 uses::substitute(func, &forward);
2136 func.remove_inst(inst);
2137 return forward;
2138 }
2139 Plan::Unchecked { callee, drop } => {
2140 let func = &mut module[id];
2141 let Extra::Call(at) = func[inst].extra else { return HashMap::new() };
2142 let info = func[at];
2143 let Some(signature) = without(&func[info.signature], drop) else {
2144 return HashMap::new();
2145 };
2146 let values: Vec<Value> = func[func[inst].args]
2147 .iter()
2148 .enumerate()
2149 .filter(|(index, _)| !drop.contains(index))
2150 .map(|(_, &value)| value)
2151 .collect();
2152 let made = call(func, inst, callee, signature, info.varargs, &values);
2153 return forward(func, inst, made);
2154 }
2155 };
2156 let symbols: Vec<Option<Symbol>> = args
2159 .iter()
2160 .map(|arg| match arg {
2161 Argument::Text(bytes) => Some(object(module, names, texts, bytes)),
2162 _ => None,
2163 })
2164 .collect();
2165 let width = size(module);
2166 let func = &mut module[id];
2167 let span = func.span(inst);
2168 let mut values = Vec::with_capacity(args.len());
2169 for (arg, symbol) in args.iter().zip(symbols) {
2170 values.push(match arg {
2171 Argument::Have(value) => *value,
2172 Argument::Char(byte) => constant(func, inst, int(), i128::from(*byte)),
2173 Argument::Count(count) => constant(func, inst, width, i128::from(*count)),
2174 Argument::At(value, by) => {
2175 let step = constant(func, inst, width, i128::from(*by));
2176 let args = func.push_values(&[*value, step]);
2177 let data = InstData { args, ..InstData::new(Opcode::PtrAdd) };
2178 let made = func.create_inst(data, &[Type::PTR], span);
2179 func.insert_before(made, inst);
2180 func[made].results().next().expect("an address is one value")
2181 }
2182 Argument::Text(_) => {
2183 let extra = Extra::Symbol(symbol.expect("a text argument has an object"));
2184 let data = InstData { extra, ..InstData::new(Opcode::GlobalAddr) };
2185 let made = func.create_inst(data, &[Type::PTR], span);
2186 func.insert_before(made, inst);
2187 func[made].results().next().expect("an address is one value")
2188 }
2189 });
2190 }
2191 let varargs = func.push_abis(&[]);
2192 let made = call(func, inst, callee, signature, varargs, &values);
2193 match answer {
2194 Some(answer) => answered(func, inst, answer, width),
2195 None => forward(func, inst, made),
2196 }
2197}
2198
2199fn plain(name: &str) -> &str {
2202 name.strip_prefix("__").and_then(|rest| rest.strip_suffix("_chk")).unwrap_or(name)
2203}
2204
2205fn call(
2207 func: &mut Func,
2208 before: Inst,
2209 callee: Symbol,
2210 signature: Signature,
2211 varargs: AbiList,
2212 values: &[Value],
2213) -> Inst {
2214 let span = func.span(before);
2215 let results: Vec<Type> = signature.return_types().collect();
2216 let sig = func.add_signature(signature);
2217 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
2218 let args = func.push_values(values);
2219 let data = InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) };
2220 let made = func.create_inst(data, &results, span);
2221 func.insert_before(made, before);
2222 made
2223}
2224
2225fn forward(func: &mut Func, old: Inst, new: Inst) -> HashMap<Value, Value> {
2228 let forward: HashMap<Value, Value> = func[old]
2232 .results()
2233 .zip(func[new].results().collect::<Vec<Value>>())
2234 .filter(|&(from, to)| func[from].ty == func[to].ty)
2235 .collect();
2236 if !forward.is_empty() {
2237 uses::substitute(func, &forward);
2238 }
2239 func.remove_inst(old);
2240 forward
2241}
2242
2243fn without(signature: &Signature, drop: &[usize]) -> Option<Signature> {
2246 drop.iter().all(|&index| index < signature.params.len()).then_some(())?;
2247 let params = signature
2248 .params
2249 .iter()
2250 .enumerate()
2251 .filter(|(index, _)| !drop.contains(index))
2252 .map(|(_, param)| *param)
2253 .collect();
2254 Some(Signature { params, ..signature.clone() })
2255}
2256
2257fn answered(func: &mut Func, inst: Inst, answer: Answer, width: Type) -> HashMap<Value, Value> {
2260 let span = func.span(inst);
2261 let value = match answer {
2262 Answer::Along(haystack, 0) => haystack,
2265 Answer::Along(haystack, by) => {
2266 let step = constant(func, inst, width, i128::from(by));
2267 let args = func.push_values(&[haystack, step]);
2268 let data = InstData { args, ..InstData::new(Opcode::PtrAdd) };
2269 let made = func.create_inst(data, &[Type::PTR], span);
2270 func.insert_before(made, inst);
2271 func[made].results().next().expect("an address is one value")
2272 }
2273 Answer::Nowhere => {
2274 let zero = constant(func, inst, width, 0);
2275 let args = func.push_values(&[zero]);
2276 let data = InstData { args, ..InstData::new(Opcode::IntToPtr) };
2277 let made = func.create_inst(data, &[Type::PTR], span);
2278 func.insert_before(made, inst);
2279 func[made].results().next().expect("a null pointer is one value")
2280 }
2281 Answer::Number(number) => {
2282 let ty = func[inst]
2283 .results()
2284 .next()
2285 .map(|result| func[result].ty)
2286 .expect("a call whose answer is a number has one");
2287 constant(func, inst, ty, number)
2288 }
2289 Answer::Least { count, len } => {
2290 let ty = func[count].ty;
2291 let len = constant(func, inst, ty, i128::from(len));
2292 let args = func.push_values(&[count, len]);
2293 let data = InstData {
2294 args,
2295 extra: Extra::IntPred(IntPred::Ult),
2296 ..InstData::new(Opcode::ICmp)
2297 };
2298 let made = func.create_inst(data, &[Type::I1], span);
2299 func.insert_before(made, inst);
2300 let shorter = func[made].results().next().expect("a comparison is one value");
2301 let args = func.push_values(&[shorter, count, len]);
2302 let data = InstData { args, ..InstData::new(Opcode::Select) };
2303 let made = func.create_inst(data, &[ty], span);
2304 func.insert_before(made, inst);
2305 func[made].results().next().expect("a choice is one value")
2306 }
2307 Answer::Less { len, step } => {
2308 let ty = func[inst]
2309 .results()
2310 .next()
2311 .map(|result| func[result].ty)
2312 .expect("a call whose answer is a length has one");
2313 let step = resize(func, inst, step, ty);
2314 let len = constant(func, inst, ty, i128::from(len));
2315 let args = func.push_values(&[len, step]);
2316 let data = InstData { args, ..InstData::new(Opcode::Sub) };
2317 let made = func.create_inst(data, &[ty], span);
2318 func.insert_before(made, inst);
2319 func[made].results().next().expect("a difference is one value")
2320 }
2321 Answer::Byte { of, against, leading } => {
2322 let ty = func[inst]
2323 .results()
2324 .next()
2325 .map(|result| func[result].ty)
2326 .expect("a call whose answer is a byte has one");
2327 let read = read(func, inst, of);
2328 let args = func.push_values(&[read]);
2331 let data = InstData { args, ..InstData::new(Opcode::ZExt) };
2332 let made = func.create_inst(data, &[ty], span);
2333 func.insert_before(made, inst);
2334 let wide = func[made].results().next().expect("a conversion is one value");
2335 let other = constant(func, inst, ty, i128::from(against));
2336 let pair = if leading { [other, wide] } else { [wide, other] };
2337 let args = func.push_values(&pair);
2338 let data = InstData { args, ..InstData::new(Opcode::Sub) };
2339 let made = func.create_inst(data, &[ty], span);
2340 func.insert_before(made, inst);
2341 func[made].results().next().expect("a difference is one value")
2342 }
2343 };
2344 let forward: HashMap<Value, Value> =
2345 func[inst].results().map(|result| (result, value)).collect();
2346 uses::substitute(func, &forward);
2347 func.remove_inst(inst);
2348 forward
2349}
2350
2351fn walk(left: &[u8], right: &[u8], bound: usize) -> i128 {
2353 for at in 0..bound.min(left.len() + 1).min(right.len() + 1) {
2356 let (this, that) =
2357 (left.get(at).copied().unwrap_or(0), right.get(at).copied().unwrap_or(0));
2358 if this != that {
2359 return if this < that { -1 } else { 1 };
2360 }
2361 if this == 0 {
2362 break;
2363 }
2364 }
2365 0
2366}
2367
2368fn at(haystack: &[u8], needle: &[u8]) -> Option<usize> {
2370 haystack.windows(needle.len()).position(|window| window == needle)
2371}
2372
2373fn read(func: &mut Func, before: Inst, from: Value) -> Value {
2378 let span = func.span(before);
2379 let mem = func.add_mem(MemInfo {
2380 size: 1,
2381 align: 1,
2382 order: MemOrder::NotAtomic,
2383 tbaa: None,
2384 owns: 0,
2385 restrict: Restrict::NONE,
2386 });
2387 let args = func.push_values(&[from]);
2388 let data = InstData { args, extra: Extra::Mem(mem), ..InstData::new(Opcode::Load) };
2389 let made = func.create_inst(data, &[Type::int(8)], span);
2390 func.insert_before(made, before);
2391 func[made].results().next().expect("a load is one value")
2392}
2393
2394fn resize(func: &mut Func, before: Inst, value: Value, ty: Type) -> Value {
2397 let opcode = match func[value].ty.bits().cmp(&ty.bits()) {
2398 std::cmp::Ordering::Equal => return value,
2399 std::cmp::Ordering::Less => Opcode::ZExt,
2400 std::cmp::Ordering::Greater => Opcode::Trunc,
2401 };
2402 let span = func.span(before);
2403 let args = func.push_values(&[value]);
2404 let made = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[ty], span);
2405 func.insert_before(made, before);
2406 func[made].results().next().expect("a conversion is one value")
2407}
2408
2409fn constant(func: &mut Func, before: Inst, ty: Type, value: i128) -> Value {
2411 let span = func.span(before);
2412 let imm = func.add_imm(Imm::int(value, ty.lane()));
2413 let data = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
2414 let made = func.create_inst(data, &[ty], span);
2415 func.insert_before(made, before);
2416 func[made].results().next().expect("a constant is one value")
2417}
2418
2419fn object(
2425 module: &mut Module,
2426 names: &mut Interner,
2427 texts: &mut HashMap<Vec<u8>, Symbol>,
2428 bytes: &[u8],
2429) -> Symbol {
2430 if let Some(&symbol) = texts.get(bytes) {
2431 return symbol;
2432 }
2433 let mut image = bytes.to_vec();
2434 image.push(0);
2435 let mut symbol = names.intern(&format!(".Lfold.{}", texts.len()));
2436 for next in texts.len().. {
2437 if module.lookup(symbol).is_none() {
2438 break;
2439 }
2440 symbol = names.intern(&format!(".Lfold.{}", next + 1));
2441 }
2442 let mut global = Global::new(symbol, image.len() as u64, 1);
2443 global.linkage = Linkage::Internal;
2444 global.constant = true;
2445 let range = module.push_bytes(&image);
2446 global.init = Some(module.push_data(&[Datum::Bytes(range)]));
2447 module.add_global(global);
2448 texts.insert(bytes.to_vec(), symbol);
2449 symbol
2450}
2451
2452#[cfg(test)]
2453mod tests {
2454 use super::*;
2455
2456 const HEAD: &str = "\
2458; ModuleID = 't.c'
2459; format 0
2460target triple = \"x86_64-unknown-linux-gnu\"
2461target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
2462";
2463
2464 fn folded(body: &str) -> String {
2470 run(body, &[], &mut Fuel::unlimited())
2471 }
2472
2473 fn run(body: &str, no_builtin: &[String], fuel: &mut Fuel) -> String {
2475 let mut names = Interner::new();
2476 let text = format!("{HEAD}{body}");
2477 let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
2478 fold(&mut module, &mut names, no_builtin, Pic::Executable, fuel);
2479 if let Err(errors) = rucc_ir::verify(&module, &names) {
2480 panic!("the fold left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
2481 }
2482 rucc_ir::print(&module, &names)
2483 }
2484
2485 #[test]
2491 fn a_format_that_ends_in_a_newline_is_written_by_puts() {
2492 let out = folded(
2493 r#"
2494global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
2495
2496func @printf(ptr, ...) -> i32, linkage(external);
2497
2498func @g(), linkage(external) {
2499block0:
2500 %0 = global_addr @.Lstr.0
2501 %1 = call @printf(%0) : (ptr, ...) -> i32
2502 return
2503}
2504"#,
2505 );
2506 assert!(out.contains("call @puts("), "{out}");
2507 assert!(!out.contains("call @printf("), "{out}");
2508 assert!(out.contains(r#"@.Lfold.0 : bytes 12 = { bytes "hello world\00" }"#), "{out}");
2509 }
2510
2511 #[test]
2513 fn a_short_format_is_written_by_putchar_or_by_nothing() {
2514 let out = folded(
2515 r#"
2516global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
2517global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2518
2519func @printf(ptr, ...) -> i32, linkage(external);
2520
2521func @g(), linkage(external) {
2522block0:
2523 %0 = global_addr @.Lstr.0
2524 %1 = call @printf(%0) : (ptr, ...) -> i32
2525 %2 = global_addr @.Lstr.1
2526 %3 = call @printf(%2) : (ptr, ...) -> i32
2527 return
2528}
2529"#,
2530 );
2531 assert!(out.contains("iconst.i32 120"), "the character is the argument, {out}");
2532 assert!(out.contains("call @putchar("), "{out}");
2533 assert_eq!(out.matches("call @").count(), 1, "the empty one is gone, {out}");
2534 }
2535
2536 #[test]
2539 fn the_two_formats_that_are_a_call_on_their_own_are_folded_for_any_argument() {
2540 let out = folded(
2541 r#"
2542global @.Lstr.0 : bytes 4 = { bytes "%s\0a\00" }, align 1, linkage(internal), constant
2543global @.Lstr.1 : bytes 3 = { bytes "%c\00" }, align 1, linkage(internal), constant
2544
2545func @printf(ptr, ...) -> i32, linkage(external);
2546
2547func @g(ptr, i32), linkage(external) {
2548block0(%0: ptr, %1: i32):
2549 %2 = global_addr @.Lstr.0
2550 %3 = call @printf(%2, %0) : (ptr, ...) -> i32
2551 %4 = global_addr @.Lstr.1
2552 %5 = call @printf(%4, %1) : (ptr, ...) -> i32
2553 return
2554}
2555"#,
2556 );
2557 assert!(out.contains("call @puts(%0)"), "{out}");
2558 assert!(out.contains("call @putchar(%1)"), "{out}");
2559 }
2560
2561 #[test]
2566 fn a_string_argument_nothing_is_known_about_is_left_to_printf() {
2567 let out = folded(
2568 r#"
2569global @.Lstr.0 : bytes 3 = { bytes "%s\00" }, align 1, linkage(internal), constant
2570
2571func @printf(ptr, ...) -> i32, linkage(external);
2572
2573func @g(ptr), linkage(external) {
2574block0(%0: ptr):
2575 %1 = global_addr @.Lstr.0
2576 %2 = call @printf(%1, %0) : (ptr, ...) -> i32
2577 return
2578}
2579"#,
2580 );
2581 assert!(out.contains("call @printf("), "{out}");
2582 }
2583
2584 #[test]
2589 fn a_stream_takes_the_whole_format_through_fwrite() {
2590 let out = folded(
2591 r#"
2592global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
2593global @.Lstr.1 : bytes 2 = { bytes "q\00" }, align 1, linkage(internal), constant
2594
2595func @fprintf(ptr, ptr, ...) -> i32, linkage(external);
2596
2597func @g(ptr), linkage(external) {
2598block0(%0: ptr):
2599 %1 = global_addr @.Lstr.0
2600 %2 = call @fprintf(%0, %1) : (ptr, ptr, ...) -> i32
2601 %3 = global_addr @.Lstr.1
2602 %4 = call @fprintf(%0, %3) : (ptr, ptr, ...) -> i32
2603 return
2604}
2605"#,
2606 );
2607 assert!(out.contains("call @fwrite("), "{out}");
2608 assert!(out.contains("iconst.i64 12"), "the whole format, newline and all, {out}");
2609 assert!(out.contains("call @fputc("), "{out}");
2610 assert!(!out.contains("call @fprintf("), "{out}");
2611 }
2612
2613 #[test]
2616 fn a_string_argument_with_a_stream_beside_it_becomes_fputs() {
2617 let out = folded(
2618 r#"
2619global @.Lstr.0 : bytes 3 = { bytes "%s\00" }, align 1, linkage(internal), constant
2620
2621func @fprintf(ptr, ptr, ...) -> i32, linkage(external);
2622
2623func @g(ptr, ptr), linkage(external) {
2624block0(%0: ptr, %1: ptr):
2625 %2 = global_addr @.Lstr.0
2626 %3 = call @fprintf(%0, %2, %1) : (ptr, ptr, ...) -> i32
2627 return
2628}
2629"#,
2630 );
2631 assert!(out.contains("call @fputs(%1, %0)"), "{out}");
2632 }
2633
2634 #[test]
2637 fn fputs_of_a_string_this_module_holds_is_folded_by_its_length() {
2638 let out = folded(
2639 r#"
2640global @.Lstr.0 : bytes 7 = { bytes "abcdef\00" }, align 1, linkage(internal), constant
2641global @.Lstr.1 : bytes 2 = { bytes "z\00" }, align 1, linkage(internal), constant
2642global @.Lstr.2 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2643
2644func @fputs(ptr, ptr) -> i32, linkage(external);
2645
2646func @g(ptr), linkage(external) {
2647block0(%0: ptr):
2648 %1 = global_addr @.Lstr.0
2649 %2 = call @fputs(%1, %0) : (ptr, ptr) -> i32
2650 %3 = global_addr @.Lstr.1
2651 %4 = call @fputs(%3, %0) : (ptr, ptr) -> i32
2652 %5 = global_addr @.Lstr.2
2653 %6 = call @fputs(%5, %0) : (ptr, ptr) -> i32
2654 return
2655}
2656"#,
2657 );
2658 assert!(out.contains("call @fwrite("), "{out}");
2659 assert!(out.contains("iconst.i64 6"), "{out}");
2660 assert!(out.contains("iconst.i32 122"), "{out}");
2661 assert!(out.contains("call @fputc("), "{out}");
2662 assert!(!out.contains("call @fputs("), "the empty one is gone too, {out}");
2663 }
2664
2665 #[test]
2671 fn an_index_into_a_literal_is_a_string_of_its_own() {
2672 let out = folded(
2673 r#"
2674global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2675
2676func @fputs(ptr, ptr) -> i32, linkage(external);
2677
2678func @g(ptr), linkage(external) {
2679block0(%0: ptr):
2680 %1 = global_addr @.Lstr.0
2681 %2 = iconst.i32 6
2682 %3 = sext.i64 %2
2683 %4 = ptr_add %1, %3
2684 %5 = call @fputs(%4, %0) : (ptr, ptr) -> i32
2685 %6 = iconst.i32 11
2686 %7 = sext.i64 %6
2687 %8 = ptr_add %1, %7
2688 %9 = call @fputs(%8, %0) : (ptr, ptr) -> i32
2689 return
2690}
2691"#,
2692 );
2693 assert!(out.contains("iconst.i64 5"), "world without its terminator, {out}");
2694 assert!(out.contains("call @fwrite("), "{out}");
2695 assert!(!out.contains("call @fputs("), "and the terminator itself is nothing, {out}");
2696 }
2697
2698 #[test]
2705 fn a_choice_between_two_literals_is_folded_when_they_are_the_same_length() {
2706 let text = r#"
2707global @.Lstr.0 : bytes 2 = { bytes "f\00" }, align 1, linkage(internal), constant
2708global @.Lstr.1 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
2709global @.Lstr.2 : bytes 4 = { bytes "abc\00" }, align 1, linkage(internal), constant
2710
2711func @fputs(ptr, ptr) -> i32, linkage(external);
2712
2713func @g(ptr, i1), linkage(external) {
2714block0(%0: ptr, %1: i1):
2715 %2 = global_addr @.LEFT
2716 %3 = global_addr @.Lstr.1
2717 br_if %1, block1(%2), block1(%3)
2718block1(%4: ptr):
2719 %5 = call @fputs(%4, %0) : (ptr, ptr) -> i32
2720 return
2721}
2722"#;
2723 let same = folded(&text.replace(".LEFT", ".Lstr.0"));
2724 assert!(same.contains("call @fwrite("), "{same}");
2725 assert!(same.contains("iconst.i64 1"), "{same}");
2726
2727 let differing = folded(&text.replace(".LEFT", ".Lstr.2"));
2728 assert!(differing.contains("call @fputs("), "{differing}");
2729 }
2730
2731 #[test]
2736 fn a_call_whose_answer_is_read_is_not_folded() {
2737 let out = folded(
2738 r#"
2739global @n : bytes 4 = { zero 4 }, align 4, linkage(external)
2740global @.Lstr.0 : bytes 3 = { bytes "a\0a\00" }, align 1, linkage(internal), constant
2741
2742func @printf(ptr, ...) -> i32, linkage(external);
2743
2744func @g(), linkage(external) {
2745block0:
2746 %0 = global_addr @.Lstr.0
2747 %1 = call @printf(%0) : (ptr, ...) -> i32
2748 %2 = global_addr @n
2749 store %1 -> %2, align 4
2750 return
2751}
2752"#,
2753 );
2754 assert!(out.contains("call @printf("), "{out}");
2755 }
2756
2757 #[test]
2762 fn a_body_for_a_standard_name_is_not_folded_inside_and_does_not_stop_the_fold() {
2763 let out = folded(
2764 r#"
2765global @.Lstr.0 : bytes 3 = { bytes "a\0a\00" }, align 1, linkage(internal), constant
2766
2767func @printf(ptr, ...) -> i32, linkage(external);
2768
2769func @puts(ptr) -> i32, linkage(external) {
2770block0(%0: ptr):
2771 %1 = global_addr @.Lstr.0
2772 %2 = call @printf(%1) : (ptr, ...) -> i32
2773 %3 = iconst.i32 0
2774 return %3
2775}
2776
2777func @__vprintf_chk(i32, ptr, ptr) -> i32, linkage(external) {
2778block0(%0: i32, %1: ptr, %2: ptr):
2779 %3 = iconst.i32 0
2780 return %3
2781}
2782
2783func @g(ptr), linkage(external) {
2784block0(%0: ptr):
2785 %1 = iconst.i32 1
2786 %2 = global_addr @.Lstr.0
2787 %3 = call @__vprintf_chk(%1, %2, %0) : (i32, ptr, ptr) -> i32
2788 return
2789}
2790"#,
2791 );
2792 assert!(!out.contains("call @__vprintf_chk("), "{out}");
2793 assert_eq!(out.matches("call @puts(").count(), 1, "{out}");
2794 assert!(out.contains("call @printf("), "the body of `puts` keeps its own call, {out}");
2795 }
2796
2797 #[test]
2803 fn a_declaration_of_another_shape_stops_the_fold() {
2804 let out = folded(
2805 r#"
2806global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
2807
2808func @printf(ptr, ...) -> i32, linkage(external);
2809func @puts(ptr, i32) -> i32, linkage(external);
2810
2811func @g(), linkage(external) {
2812block0:
2813 %0 = global_addr @.Lstr.0
2814 %1 = call @printf(%0) : (ptr, ...) -> i32
2815 return
2816}
2817"#,
2818 );
2819 assert!(out.contains("call @printf("), "{out}");
2820 assert!(!out.contains("@.Lfold."), "and no object was left behind either, {out}");
2821 }
2822
2823 #[test]
2826 fn a_variable_by_the_name_of_a_replacement_stops_the_fold() {
2827 let out = folded(
2828 r#"
2829global @putchar : bytes 4 = { zero 4 }, align 4, linkage(external)
2830global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
2831
2832func @printf(ptr, ...) -> i32, linkage(external);
2833
2834func @g(), linkage(external) {
2835block0:
2836 %0 = global_addr @.Lstr.0
2837 %1 = call @printf(%0) : (ptr, ...) -> i32
2838 return
2839}
2840"#,
2841 );
2842 assert!(out.contains("call @printf("), "{out}");
2843 }
2844
2845 #[test]
2850 fn the_unlocked_spellings_are_only_removed_when_they_write_nothing() {
2851 let out = folded(
2852 r#"
2853global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
2854global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2855
2856func @printf_unlocked(ptr, ...) -> i32, linkage(external);
2857
2858func @g(), linkage(external) {
2859block0:
2860 %0 = global_addr @.Lstr.0
2861 %1 = call @printf_unlocked(%0) : (ptr, ...) -> i32
2862 %2 = global_addr @.Lstr.1
2863 %3 = call @printf_unlocked(%2) : (ptr, ...) -> i32
2864 return
2865}
2866"#,
2867 );
2868 assert_eq!(out.matches("call @printf_unlocked(").count(), 1, "{out}");
2869 assert!(!out.contains("call @puts("), "{out}");
2870 }
2871
2872 #[test]
2874 fn one_name_can_be_taken_away_without_taking_the_family_away() {
2875 let body = r#"
2876global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
2877
2878func @printf(ptr, ...) -> i32, linkage(external);
2879func @fputs(ptr, ptr) -> i32, linkage(external);
2880
2881func @g(ptr), linkage(external) {
2882block0(%0: ptr):
2883 %1 = global_addr @.Lstr.0
2884 %2 = call @printf(%1) : (ptr, ...) -> i32
2885 %3 = call @fputs(%1, %0) : (ptr, ptr) -> i32
2886 return
2887}
2888"#;
2889 let out = run(body, &["printf".to_owned()], &mut Fuel::unlimited());
2890 assert!(out.contains("call @printf("), "{out}");
2891 assert!(out.contains("call @fputc("), "and the other one still folded, {out}");
2892 }
2893
2894 #[test]
2897 fn a_run_out_of_fuel_transforms_nothing() {
2898 let body = r#"
2899global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
2900
2901func @printf(ptr, ...) -> i32, linkage(external);
2902
2903func @g(), linkage(external) {
2904block0:
2905 %0 = global_addr @.Lstr.0
2906 %1 = call @printf(%0) : (ptr, ...) -> i32
2907 return
2908}
2909"#;
2910 let mut fuel = Fuel::of(0);
2911 let out = run(body, &[], &mut fuel);
2912 assert!(out.contains("call @printf("), "{out}");
2913 assert_eq!(fuel.spent(), 0);
2914 }
2915
2916 #[test]
2918 fn one_object_serves_every_call_that_prints_the_same_thing() {
2919 let out = folded(
2920 r#"
2921global @.Lstr.0 : bytes 4 = { bytes "hi\0a\00" }, align 1, linkage(internal), constant
2922
2923func @printf(ptr, ...) -> i32, linkage(external);
2924
2925func @g(), linkage(external) {
2926block0:
2927 %0 = global_addr @.Lstr.0
2928 %1 = call @printf(%0) : (ptr, ...) -> i32
2929 %2 = call @printf(%0) : (ptr, ...) -> i32
2930 return
2931}
2932
2933func @h(), linkage(external) {
2934block0:
2935 %0 = global_addr @.Lstr.0
2936 %1 = call @printf(%0) : (ptr, ...) -> i32
2937 return
2938}
2939"#,
2940 );
2941 assert_eq!(out.matches("@.Lfold.0 : bytes").count(), 1, "{out}");
2942 assert!(!out.contains("@.Lfold.1"), "{out}");
2943 assert_eq!(out.matches("call @puts(").count(), 3, "{out}");
2944 }
2945
2946 #[test]
2951 fn a_search_for_nothing_answers_with_the_haystack_itself() {
2952 let out = folded(
2953 r#"
2954global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2955
2956func @strstr(ptr, ptr) -> ptr, linkage(external);
2957
2958func @g(ptr) -> ptr, linkage(external) {
2959block0(%0: ptr):
2960 %1 = global_addr @.Lstr.0
2961 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
2962 return %2
2963}
2964"#,
2965 );
2966 assert!(!out.contains("call @strstr("), "{out}");
2967 assert!(out.contains("return %0"), "{out}");
2968 }
2969
2970 #[test]
2972 fn two_strings_this_module_holds_answer_without_a_call() {
2973 let out = folded(
2974 r#"
2975global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2976global @.Lstr.1 : bytes 2 = { bytes "w\00" }, align 1, linkage(internal), constant
2977global @.Lstr.2 : bytes 3 = { bytes "zz\00" }, align 1, linkage(internal), constant
2978
2979func @strstr(ptr, ptr) -> ptr, linkage(external);
2980func @use(ptr, ptr), linkage(external);
2981
2982func @g(), linkage(external) {
2983block0:
2984 %0 = global_addr @.Lstr.0
2985 %1 = global_addr @.Lstr.1
2986 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
2987 %3 = global_addr @.Lstr.2
2988 %4 = call @strstr(%0, %3) : (ptr, ptr) -> ptr
2989 call @use(%2, %4) : (ptr, ptr)
2990 return
2991}
2992"#,
2993 );
2994 assert!(!out.contains("call @strstr("), "{out}");
2995 assert!(out.contains("ptr_add %0, "), "the w is six bytes along, {out}");
2996 assert!(out.contains("iconst.i64 6"), "{out}");
2997 assert!(out.contains("inttoptr"), "and the zz is nowhere in it, {out}");
2998 }
2999
3000 #[test]
3002 fn a_one_character_needle_becomes_a_search_for_that_character() {
3003 let out = folded(
3004 r#"
3005global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
3006
3007func @strstr(ptr, ptr) -> ptr, linkage(external);
3008
3009func @g(ptr) -> ptr, linkage(external) {
3010block0(%0: ptr):
3011 %1 = global_addr @.Lstr.0
3012 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
3013 return %2
3014}
3015"#,
3016 );
3017 assert!(!out.contains("call @strstr("), "{out}");
3018 assert!(out.contains("call @strchr(%0, "), "{out}");
3019 assert!(out.contains("iconst.i32 111"), "{out}");
3020 }
3021
3022 #[test]
3024 fn a_strchr_of_another_shape_is_not_the_one_to_call() {
3025 let out = folded(
3026 r#"
3027global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
3028
3029func @strstr(ptr, ptr) -> ptr, linkage(external);
3030func @strchr(ptr, ptr) -> ptr, linkage(external);
3031
3032func @g(ptr) -> ptr, linkage(external) {
3033block0(%0: ptr):
3034 %1 = global_addr @.Lstr.0
3035 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
3036 return %2
3037}
3038"#,
3039 );
3040 assert!(out.contains("call @strstr("), "{out}");
3041 }
3042
3043 #[test]
3048 fn a_renamed_declaration_is_still_the_function_it_was_spelled() {
3049 let out = folded(
3050 r#"
3051global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3052
3053func @my_strstr(ptr, ptr) -> ptr, linkage(external), spelled "strstr";
3054
3055func @g(ptr) -> ptr, linkage(external) {
3056block0(%0: ptr):
3057 %1 = global_addr @.Lstr.0
3058 %2 = call @my_strstr(%0, %1) : (ptr, ptr) -> ptr
3059 return %2
3060}
3061"#,
3062 );
3063 assert!(!out.contains("call @my_strstr("), "{out}");
3064 assert!(out.contains("return %0"), "{out}");
3065 }
3066
3067 #[test]
3069 fn a_renamed_replacement_is_called_by_the_symbol_the_rename_asked_for() {
3070 let out = folded(
3071 r#"
3072global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
3073
3074func @strstr(ptr, ptr) -> ptr, linkage(external);
3075func @my_strchr(ptr, i32) -> ptr, linkage(external), spelled "strchr";
3076
3077func @g(ptr) -> ptr, linkage(external) {
3078block0(%0: ptr):
3079 %1 = global_addr @.Lstr.0
3080 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
3081 return %2
3082}
3083"#,
3084 );
3085 assert!(out.contains("call @my_strchr(%0, "), "{out}");
3086 assert!(!out.contains("call @strchr("), "{out}");
3087 }
3088
3089 #[test]
3091 fn a_strstr_taken_away_is_a_call_like_any_other() {
3092 let body = r#"
3093global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3094
3095func @strstr(ptr, ptr) -> ptr, linkage(external);
3096
3097func @g(ptr) -> ptr, linkage(external) {
3098block0(%0: ptr):
3099 %1 = global_addr @.Lstr.0
3100 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
3101 return %2
3102}
3103"#;
3104 let out = run(body, &["strstr".to_owned()], &mut Fuel::unlimited());
3105 assert!(out.contains("call @strstr("), "{out}");
3106 }
3107
3108 #[test]
3110 fn strlen_of_a_string_this_module_holds_is_a_number() {
3111 let out = folded(
3112 r#"
3113global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3114
3115func @strlen(ptr) -> i64, linkage(external);
3116func @use(i64, i64), linkage(external);
3117
3118func @g(), linkage(external) {
3119block0:
3120 %0 = global_addr @.Lstr.0
3121 %1 = call @strlen(%0) : (ptr) -> i64
3122 %2 = iconst.i64 6
3123 %3 = ptr_add %0, %2
3124 %4 = call @strlen(%3) : (ptr) -> i64
3125 call @use(%1, %4) : (i64, i64)
3126 return
3127}
3128"#,
3129 );
3130 assert!(!out.contains("call @strlen("), "{out}");
3131 assert!(out.contains("iconst.i64 11"), "{out}");
3132 assert!(out.contains("iconst.i64 5"), "the world on its own, {out}");
3133 }
3134
3135 #[test]
3138 fn a_move_that_cannot_overlap_is_a_copy() {
3139 let out = folded(
3140 r#"
3141global @.Lstr.0 : bytes 6 = { bytes "abcde\00" }, align 1, linkage(internal), constant
3142global @p : bytes 32 = { zero 32 }, align 16, linkage(external)
3143
3144func @memmove(ptr, ptr, i64) -> ptr, linkage(external);
3145func @bcopy(ptr, ptr, i64), linkage(external);
3146func @use(ptr, ptr, ptr, ptr), linkage(external);
3147
3148func @g(ptr, i64), linkage(external) {
3149block0(%0: ptr, %1: i64):
3150 %2 = global_addr @p
3151 %3 = global_addr @.Lstr.0
3152 %4 = iconst.i64 6
3153 %5 = call @memmove(%2, %3, %4) : (ptr, ptr, i64) -> ptr
3154 %6 = iconst.i64 2
3155 %7 = ptr_add %2, %6
3156 %8 = iconst.i64 3
3157 %9 = ptr_add %2, %8
3158 %10 = iconst.i64 1
3159 %11 = call @memmove(%7, %9, %10) : (ptr, ptr, i64) -> ptr
3160 %12 = iconst.i64 0
3161 %13 = call @memmove(%7, %0, %12) : (ptr, ptr, i64) -> ptr
3162 call @bcopy(%9, %7, %10) : (ptr, ptr, i64)
3163 %14 = alloca, size 8, align 8
3164 %15 = call @memmove(%14, %0, %1) : (ptr, ptr, i64) -> ptr
3165 %16 = call @memmove(%7, %9, %1) : (ptr, ptr, i64) -> ptr
3166 call @use(%5, %11, %13, %16) : (ptr, ptr, ptr, ptr)
3167 return
3168}
3169"#,
3170 );
3171 assert_eq!(out.matches("call @memcpy(").count(), 3, "{out}");
3172 assert!(!out.contains("call @bcopy("), "{out}");
3173 assert_eq!(
3174 out.matches("call @memmove(").count(),
3175 2,
3176 "a local and a pointer from outside, and two places in one object, stay moves, {out}"
3177 );
3178 }
3179
3180 #[test]
3184 fn strcat_onto_what_was_just_written_is_a_copy_to_its_end() {
3185 let text = r#"
3186global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3187global @.Lstr.1 : bytes 6 = { bytes " 1111\00" }, align 1, linkage(internal), constant
3188global @.Lstr.2 : bytes 3 = { bytes "ab\00" }, align 1, linkage(internal), constant
3189
3190func @strcat(ptr, ptr) -> ptr, linkage(external);
3191func @strcpy(ptr, ptr) -> ptr, linkage(external);
3192func @memset(ptr, i32, i64) -> ptr, linkage(external);
3193func @use(ptr), linkage(external);
3194func @touch(ptr), linkage(external);
3195
3196func @g(), linkage(external) {
3197block0:
3198 %0 = alloca, size 64, align 16
3199 jump block1
3200block1:
3201 %1 = iconst.i32 88
3202 %2 = iconst.i64 64
3203 %3 = call @memset(%0, %1, %2) : (ptr, i32, i64) -> ptr
3204 %4 = global_addr @.Lstr.0
3205 %5 = call @strcpy(%0, %4) : (ptr, ptr) -> ptr
3206 TOUCH
3207 jump block2
3208block2:
3209 %6 = global_addr @.Lstr.1
3210 %7 = call @strcat(%0, %6) : (ptr, ptr) -> ptr
3211 %8 = global_addr @.Lstr.2
3212 %9 = call @strcat(%7, %8) : (ptr, ptr) -> ptr
3213 call @use(%9) : (ptr)
3214 return
3215}
3216"#;
3217 let out = folded(&text.replace("TOUCH", ""));
3218 assert!(!out.contains("call @strcat("), "{out}");
3219 assert!(out.contains("iconst.i64 11"), "the first goes where the terminator was, {out}");
3220 assert!(out.contains("iconst.i64 16"), "the second after the first, {out}");
3221 assert!(out.contains("call @use(%0)"), "{out}");
3222
3223 let out = folded(&text.replace("TOUCH", "call @touch(%0) : (ptr)"));
3224 assert_eq!(out.matches("call @strcat(").count(), 2, "{out}");
3225 }
3226
3227 #[test]
3230 fn strncpy_that_pads_nothing_is_a_copy() {
3231 let out = folded(
3232 r#"
3233global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3234
3235func @strncpy(ptr, ptr, i64) -> ptr, linkage(external);
3236func @use(ptr, ptr, ptr, ptr), linkage(external);
3237
3238func @g(ptr), linkage(external) {
3239block0(%0: ptr):
3240 %1 = global_addr @.Lstr.0
3241 %2 = iconst.i64 4
3242 %3 = call @strncpy(%0, %1, %2) : (ptr, ptr, i64) -> ptr
3243 %4 = iconst.i64 12
3244 %5 = call @strncpy(%0, %1, %4) : (ptr, ptr, i64) -> ptr
3245 %6 = iconst.i64 0
3246 %7 = call @strncpy(%0, %1, %6) : (ptr, ptr, i64) -> ptr
3247 %8 = iconst.i64 13
3248 %9 = call @strncpy(%0, %1, %8) : (ptr, ptr, i64) -> ptr
3249 call @use(%3, %5, %7, %9) : (ptr, ptr, ptr, ptr)
3250 return
3251}
3252"#,
3253 );
3254 assert_eq!(out.matches("call @memcpy(").count(), 2, "{out}");
3255 assert_eq!(out.matches("call @strncpy(").count(), 1, "thirteen pads a byte, {out}");
3256 }
3257
3258 #[test]
3261 fn strcpy_of_a_choice_of_one_length_is_a_copy() {
3262 let out = folded(
3263 r#"
3264global @.Lstr.0 : bytes 4 = { bytes "abc\00" }, align 1, linkage(internal), constant
3265global @.Lstr.1 : bytes 4 = { bytes "xyz\00" }, align 1, linkage(internal), constant
3266
3267func @strcpy(ptr, ptr) -> ptr, linkage(external);
3268func @use(ptr), linkage(external);
3269
3270func @g(ptr, i1), linkage(external) {
3271block0(%0: ptr, %1: i1):
3272 %2 = global_addr @.Lstr.0
3273 %3 = global_addr @.Lstr.1
3274 br_if %1, block1(%2), block1(%3)
3275block1(%4: ptr):
3276 %5 = call @strcpy(%0, %4) : (ptr, ptr) -> ptr
3277 call @use(%5) : (ptr)
3278 return
3279}
3280"#,
3281 );
3282 assert!(out.contains("call @memcpy("), "{out}");
3283 assert!(out.contains("iconst.i64 4"), "{out}");
3284 }
3285
3286 #[test]
3289 fn strlen_of_what_stores_just_wrote_is_its_length() {
3290 let text = r#"
3291func @strlen(ptr) -> i64, linkage(external);
3292func @use(i64, i64), linkage(external);
3293func @touch(), linkage(external);
3294
3295func @g(), linkage(external) {
3296block0:
3297 %0 = alloca, size 8, align 1
3298 %1 = alloca, size 8, align 1
3299 %2 = iconst.i8 110
3300 store %2 -> %0, align 1
3301 %3 = iconst.i64 1
3302 %4 = ptr_add %0, %3
3303 %5 = iconst.i8 116
3304 store %5 -> %4, align 1
3305 %6 = iconst.i64 2
3306 %7 = ptr_add %0, %6
3307 %8 = iconst.i8 0
3308 store %8 -> %7, align 1
3309 store %8 -> %1, align 1
3310 CALL
3311 %9 = call @strlen(%0) : (ptr) -> i64
3312 %10 = call @strlen(%4) : (ptr) -> i64
3313 call @use(%9, %10) : (i64, i64)
3314 return
3315}
3316"#;
3317 let out = folded(&text.replace("CALL", ""));
3318 assert!(!out.contains("call @strlen("), "{out}");
3319 assert!(out.contains("iconst.i64 2"), "{out}");
3320
3321 let out = folded(&text.replace("CALL", "call @touch() : ()"));
3322 assert_eq!(out.matches("call @strlen(").count(), 2, "{out}");
3323 }
3324
3325 #[test]
3329 fn memcmp_of_bytes_known_in_front_of_it_is_their_order() {
3330 let text = r#"
3331global @.Lstr.0 : bytes 5 = { bytes "abcd\00" }, align 1, linkage(internal), constant
3332global @.Lstr.1 : bytes 5 = { bytes "efgh\00" }, align 1, linkage(internal), constant
3333global @.Lstr.2 : bytes 5 = { bytes "3141\00" }, align 1, linkage(internal), constant
3334
3335func @memcmp(ptr, ptr, i64) -> i32, linkage(external);
3336func @strcpy(ptr, ptr) -> ptr, linkage(external);
3337func @use(i32, i32, i32, i32, i32), linkage(external);
3338func @touch(ptr), linkage(external);
3339
3340func @g(ptr), linkage(external) {
3341block0(%0: ptr):
3342 %1 = global_addr @.Lstr.0
3343 %2 = global_addr @.Lstr.1
3344 %3 = iconst.i64 4
3345 %4 = call @memcmp(%1, %2, %3) : (ptr, ptr, i64) -> i32
3346 %5 = iconst.i64 0
3347 %6 = call @memcmp(%0, %2, %5) : (ptr, ptr, i64) -> i32
3348 %7 = alloca, size 8, align 1
3349 %8 = global_addr @.Lstr.2
3350 %9 = call @strcpy(%7, %8) : (ptr, ptr) -> ptr
3351 %10 = iconst.i64 2
3352 %11 = ptr_add %7, %10
3353 %12 = iconst.i64 1
3354 %13 = call @memcmp(%7, %11, %12) : (ptr, ptr, i64) -> i32
3355 TOUCH
3356 %14 = call @memcmp(%11, %7, %12) : (ptr, ptr, i64) -> i32
3357 %15 = call @memcmp(%0, %1, %3) : (ptr, ptr, i64) -> i32
3358 call @use(%4, %6, %13, %14, %15) : (i32, i32, i32, i32, i32)
3359 return
3360}
3361"#;
3362 let out = folded(&text.replace("TOUCH", ""));
3363 assert_eq!(out.matches("call @memcmp(").count(), 1, "the unknown one stays, {out}");
3364 assert!(out.contains("iconst.i32 -1"), "{out}");
3365 assert!(out.contains("iconst.i32 1"), "{out}");
3366
3367 let out = folded(&text.replace("TOUCH", "call @touch(%7) : (ptr)"));
3368 assert_eq!(out.matches("call @memcmp(").count(), 2, "{out}");
3369 }
3370
3371 #[test]
3375 fn an_arm_that_calls_abort_is_not_a_way_in() {
3376 let text = r#"
3377global @.Lstr.0 : bytes 5 = { bytes "3141\00" }, align 1, linkage(internal), constant
3378
3379func @memcmp(ptr, ptr, i64) -> i32, linkage(external);
3380func @strcpy(ptr, ptr) -> ptr, linkage(external);
3381func @abort(), linkage(external);
3382func @other(), linkage(external);
3383func @use(i32), linkage(external);
3384
3385func @g(i1), linkage(external) {
3386block0(%0: i1):
3387 %1 = alloca, size 8, align 1
3388 %2 = global_addr @.Lstr.0
3389 %3 = call @strcpy(%1, %2) : (ptr, ptr) -> ptr
3390 br_if %0, block1, block2
3391block1:
3392 call @STOP() : ()
3393 jump block2
3394block2:
3395 %4 = iconst.i64 2
3396 %5 = ptr_add %1, %4
3397 %6 = iconst.i64 1
3398 %7 = call @memcmp(%1, %5, %6) : (ptr, ptr, i64) -> i32
3399 call @use(%7) : (i32)
3400 return
3401}
3402"#;
3403 let out = folded(&text.replace("STOP", "abort"));
3404 assert!(!out.contains("call @memcmp("), "{out}");
3405
3406 let out = folded(&text.replace("STOP", "other"));
3407 assert!(out.contains("call @memcmp("), "{out}");
3408 }
3409
3410 #[test]
3413 fn strlen_of_a_choice_between_literals_of_one_length_is_that_length() {
3414 let text = r#"
3415global @.Lstr.0 : bytes 4 = { bytes "abc\00" }, align 1, linkage(internal), constant
3416global @.Lstr.1 : bytes 4 = { bytes "xyz\00" }, align 1, linkage(internal), constant
3417global @.Lstr.2 : bytes 3 = { bytes "ab\00" }, align 1, linkage(internal), constant
3418
3419func @strlen(ptr) -> i64, linkage(external);
3420func @use(i64), linkage(external);
3421
3422func @g(i1), linkage(external) {
3423block0(%0: i1):
3424 %1 = global_addr @.LEFT
3425 %2 = global_addr @.Lstr.1
3426 br_if %0, block1(%1), block1(%2)
3427block1(%3: ptr):
3428 %4 = call @strlen(%3) : (ptr) -> i64
3429 call @use(%4) : (i64)
3430 return
3431}
3432"#;
3433 let same = folded(&text.replace(".LEFT", ".Lstr.0"));
3434 assert!(!same.contains("call @strlen("), "{same}");
3435 assert!(same.contains("iconst.i64 3"), "{same}");
3436
3437 let differing = folded(&text.replace(".LEFT", ".Lstr.2"));
3438 assert!(differing.contains("call @strlen("), "{differing}");
3439 }
3440
3441 #[test]
3445 fn strlen_at_a_bounded_step_into_a_held_string_is_the_rest() {
3446 let out = folded(
3447 r#"
3448global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3449
3450func @strlen(ptr) -> i64, linkage(external);
3451func @use(i64, i64), linkage(external);
3452
3453func @g(i32), linkage(external) {
3454block0(%0: i32):
3455 %1 = global_addr @.Lstr.0
3456 %2 = iconst.i32 7
3457 %3 = and %0, %2
3458 %4 = sext.i64 %3
3459 %5 = ptr_add %1, %4
3460 %6 = call @strlen(%5) : (ptr) -> i64
3461 %7 = iconst.i32 15
3462 %8 = and %0, %7
3463 %9 = sext.i64 %8
3464 %10 = ptr_add %1, %9
3465 %11 = call @strlen(%10) : (ptr) -> i64
3466 call @use(%6, %11) : (i64, i64)
3467 return
3468}
3469"#,
3470 );
3471 assert_eq!(out.matches("call @strlen(").count(), 1, "{out}");
3472 assert!(out.contains("sub %6, %4"), "eleven less the step, {out}");
3473 assert!(out.contains("iconst.i64 11"), "{out}");
3474 }
3475
3476 #[test]
3479 fn strnlen_answers_the_count_where_the_string_runs_past_it() {
3480 let out = folded(
3481 r#"
3482global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3483
3484func @strnlen(ptr, i64) -> i64, linkage(external);
3485func @use(i64, i64), linkage(external);
3486
3487func @g(), linkage(external) {
3488block0:
3489 %0 = global_addr @.Lstr.0
3490 %1 = iconst.i64 3
3491 %2 = call @strnlen(%0, %1) : (ptr, i64) -> i64
3492 %3 = iconst.i64 40
3493 %4 = call @strnlen(%0, %3) : (ptr, i64) -> i64
3494 call @use(%2, %4) : (i64, i64)
3495 return
3496}
3497"#,
3498 );
3499 assert!(!out.contains("call @strnlen("), "{out}");
3500 assert!(out.contains("iconst.i64 3"), "the count came first, {out}");
3501 assert!(out.contains("iconst.i64 11"), "the terminator came first, {out}");
3502 }
3503
3504 #[test]
3508 fn strnlen_of_a_string_this_module_holds_takes_any_count() {
3509 let out = folded(
3510 r#"
3511global @.Lstr.0 : bytes 4 = { bytes "123\00" }, align 1, linkage(internal), constant
3512global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3513
3514func @strnlen(ptr, i64) -> i64, linkage(external);
3515func @use(i64, i64, i64), linkage(external);
3516
3517func @g(i64), linkage(external) {
3518block0(%0: i64):
3519 %1 = global_addr @.Lstr.0
3520 %2 = call @strnlen(%1, %0) : (ptr, i64) -> i64
3521 %3 = iconst.i32 -2
3522 %4 = sext.i64 %3
3523 %5 = call @strnlen(%1, %4) : (ptr, i64) -> i64
3524 %6 = global_addr @.Lstr.1
3525 %7 = call @strnlen(%6, %0) : (ptr, i64) -> i64
3526 call @use(%2, %5, %7) : (i64, i64, i64)
3527 return
3528}
3529"#,
3530 );
3531 assert!(!out.contains("call @strnlen("), "{out}");
3532 assert!(out.contains("icmp ult %0"), "the count against the length, {out}");
3533 assert!(out.contains("select"), "and the smaller of the two, {out}");
3534 assert!(out.contains("iconst.i64 3"), "a negative count is past the terminator, {out}");
3535 assert!(out.contains("iconst.i64 0"), "an empty string is nothing to count, {out}");
3536 }
3537
3538 #[test]
3541 fn a_count_that_was_widened_on_the_way_in_is_still_a_count() {
3542 let out = folded(
3543 r#"
3544global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3545
3546func @strnlen(ptr, i64) -> i64, linkage(external);
3547
3548func @g() -> i64, linkage(external) {
3549block0:
3550 %0 = global_addr @.Lstr.0
3551 %1 = iconst.i32 4
3552 %2 = sext.i64 %1
3553 %3 = call @strnlen(%0, %2) : (ptr, i64) -> i64
3554 return %3
3555}
3556"#,
3557 );
3558 assert!(!out.contains("call @strnlen("), "{out}");
3559 assert!(out.contains("iconst.i64 4"), "{out}");
3560 }
3561
3562 #[test]
3565 fn memchr_searches_the_object_rather_than_the_string_in_it() {
3566 let out = folded(
3567 r#"
3568global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3569
3570func @memchr(ptr, i32, i64) -> ptr, linkage(external);
3571func @use(ptr, ptr), linkage(external);
3572
3573func @g(), linkage(external) {
3574block0:
3575 %0 = global_addr @.Lstr.0
3576 %1 = iconst.i32 0
3577 %2 = iconst.i64 12
3578 %3 = call @memchr(%0, %1, %2) : (ptr, i32, i64) -> ptr
3579 %4 = iconst.i32 100
3580 %5 = iconst.i64 10
3581 %6 = call @memchr(%0, %4, %5) : (ptr, i32, i64) -> ptr
3582 call @use(%3, %6) : (ptr, ptr)
3583 return
3584}
3585"#,
3586 );
3587 assert!(!out.contains("call @memchr("), "{out}");
3588 assert!(out.contains("iconst.i64 11"), "the terminator is inside the count, {out}");
3589 assert!(out.contains("inttoptr.ptr "), "the d is one byte past the count, {out}");
3590 }
3591
3592 #[test]
3595 fn a_memchr_that_runs_off_the_object_is_left_alone() {
3596 let out = folded(
3597 r#"
3598global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3599
3600func @memchr(ptr, i32, i64) -> ptr, linkage(external);
3601
3602func @g() -> ptr, linkage(external) {
3603block0:
3604 %0 = global_addr @.Lstr.0
3605 %1 = iconst.i32 122
3606 %2 = iconst.i64 13
3607 %3 = call @memchr(%0, %1, %2) : (ptr, i32, i64) -> ptr
3608 return %3
3609}
3610"#,
3611 );
3612 assert!(out.contains("call @memchr("), "{out}");
3613 }
3614
3615 #[test]
3618 fn the_two_character_searches_answer_from_either_end() {
3619 let out = folded(
3620 r#"
3621global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3622
3623func @strchr(ptr, i32) -> ptr, linkage(external);
3624func @strrchr(ptr, i32) -> ptr, linkage(external);
3625func @use(ptr, ptr, ptr, ptr), linkage(external);
3626
3627func @g(), linkage(external) {
3628block0:
3629 %0 = global_addr @.Lstr.0
3630 %1 = iconst.i32 111
3631 %2 = call @strchr(%0, %1) : (ptr, i32) -> ptr
3632 %3 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
3633 %4 = iconst.i32 0
3634 %5 = call @strchr(%0, %4) : (ptr, i32) -> ptr
3635 %6 = iconst.i32 122
3636 %7 = call @strchr(%0, %6) : (ptr, i32) -> ptr
3637 call @use(%2, %3, %5, %7) : (ptr, ptr, ptr, ptr)
3638 return
3639}
3640"#,
3641 );
3642 assert!(!out.contains("call @strchr("), "{out}");
3643 assert!(!out.contains("call @strrchr("), "{out}");
3644 assert!(out.contains("iconst.i64 4"), "the first o, {out}");
3645 assert!(out.contains("iconst.i64 7"), "the last o, {out}");
3646 assert!(out.contains("iconst.i64 11"), "the terminator, {out}");
3647 assert!(out.contains("inttoptr.ptr "), "there is no z in it, {out}");
3648 }
3649
3650 #[test]
3653 fn the_two_comparisons_answer_a_sign() {
3654 let out = folded(
3655 r#"
3656global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3657global @.Lstr.1 : bytes 6 = { bytes "hello\00" }, align 1, linkage(internal), constant
3658
3659func @strcmp(ptr, ptr) -> i32, linkage(external);
3660func @strncmp(ptr, ptr, i64) -> i32, linkage(external);
3661func @use(i32, i32, i32), linkage(external);
3662
3663func @g(), linkage(external) {
3664block0:
3665 %0 = global_addr @.Lstr.0
3666 %1 = global_addr @.Lstr.1
3667 %2 = call @strcmp(%0, %1) : (ptr, ptr) -> i32
3668 %3 = call @strcmp(%1, %0) : (ptr, ptr) -> i32
3669 %4 = iconst.i64 5
3670 %5 = call @strncmp(%0, %1, %4) : (ptr, ptr, i64) -> i32
3671 call @use(%2, %3, %5) : (i32, i32, i32)
3672 return
3673}
3674"#,
3675 );
3676 assert!(!out.contains("call @strcmp("), "{out}");
3677 assert!(!out.contains("call @strncmp("), "{out}");
3678 assert!(out.contains("iconst.i32 1"), "the longer one is the greater, {out}");
3679 assert!(out.contains("iconst.i32 -1"), "and the other way round, {out}");
3680 assert!(out.contains("iconst.i32 0"), "five bytes of each are the same, {out}");
3681 }
3682
3683 #[test]
3686 fn a_comparison_against_the_empty_string_is_a_read_of_one_byte() {
3687 let out = folded(
3688 r#"
3689global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3690
3691func @strcmp(ptr, ptr) -> i32, linkage(external);
3692func @use(i32, i32), linkage(external);
3693
3694func @g(ptr), linkage(external) {
3695block0(%0: ptr):
3696 %1 = global_addr @.Lstr.0
3697 %2 = call @strcmp(%0, %1) : (ptr, ptr) -> i32
3698 %3 = call @strcmp(%1, %0) : (ptr, ptr) -> i32
3699 call @use(%2, %3) : (i32, i32)
3700 return
3701}
3702"#,
3703 );
3704 assert!(!out.contains("call @strcmp("), "{out}");
3705 assert_eq!(out.matches("load.i8 %0").count(), 2, "one read for each call, {out}");
3706 assert_eq!(out.matches("zext").count(), 2, "read as an unsigned char, {out}");
3707 assert_eq!(out.matches("sub").count(), 2, "and the difference each way round, {out}");
3708 }
3709
3710 #[test]
3713 fn a_short_count_settles_a_comparison_without_the_other_string() {
3714 let out = folded(
3715 r#"
3716global @.Lstr.0 : bytes 4 = { bytes "ozz\00" }, align 1, linkage(internal), constant
3717
3718func @strncmp(ptr, ptr, i64) -> i32, linkage(external);
3719func @use(i32, i32), linkage(external);
3720
3721func @g(ptr, ptr), linkage(external) {
3722block0(%0: ptr, %1: ptr):
3723 %2 = global_addr @.Lstr.0
3724 %3 = iconst.i32 0
3725 %4 = sext.i64 %3
3726 %5 = call @strncmp(%0, %1, %4) : (ptr, ptr, i64) -> i32
3727 %6 = iconst.i32 1
3728 %7 = sext.i64 %6
3729 %8 = call @strncmp(%2, %0, %7) : (ptr, ptr, i64) -> i32
3730 call @use(%5, %8) : (i32, i32)
3731 return
3732}
3733"#,
3734 );
3735 assert!(!out.contains("call @strncmp("), "{out}");
3736 assert!(out.contains("iconst.i32 0"), "no bytes to read is no difference, {out}");
3737 assert!(out.contains("load.i8 %0"), "one byte of the other string, {out}");
3738 assert!(out.contains("iconst.i32 111"), "against the first byte of this one, {out}");
3739 }
3740
3741 #[test]
3744 fn an_index_and_a_count_worked_out_from_constants_are_constants() {
3745 let out = folded(
3746 r#"
3747global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3748
3749func @strncmp(ptr, ptr, i64) -> i32, linkage(external);
3750func @use(i32), linkage(external);
3751
3752func @g(), linkage(external) {
3753block0:
3754 %0 = global_addr @.Lstr.0
3755 %1 = iconst.i64 1
3756 %2 = ptr_add %0, %1
3757 %3 = iconst.i32 1
3758 %4 = iconst.i32 3
3759 %5 = and %3, %4
3760 %6 = sext.i64 %5
3761 %7 = ptr_add %0, %6
3762 %8 = iconst.i32 2
3763 %9 = add.nsw %8, %3
3764 %10 = sext.i64 %9
3765 %11 = call @strncmp(%2, %7, %10) : (ptr, ptr, i64) -> i32
3766 call @use(%11) : (i32)
3767 return
3768}
3769"#,
3770 );
3771 assert!(!out.contains("call @strncmp("), "{out}");
3772 }
3773
3774 #[test]
3777 fn a_comparison_with_no_room_for_a_byte_is_left_alone() {
3778 let out = folded(
3779 r#"
3780global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3781
3782func @strcmp(ptr, ptr) -> i8, linkage(external);
3783func @use(i8), linkage(external);
3784
3785func @g(ptr), linkage(external) {
3786block0(%0: ptr):
3787 %1 = global_addr @.Lstr.0
3788 %2 = call @strcmp(%0, %1) : (ptr, ptr) -> i8
3789 call @use(%2) : (i8)
3790 return
3791}
3792"#,
3793 );
3794 assert!(out.contains("call @strcmp("), "{out}");
3795 }
3796
3797 #[test]
3799 fn the_two_spans_are_one_walk_each_way() {
3800 let out = folded(
3801 r#"
3802global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3803global @.Lstr.1 : bytes 4 = { bytes "hel\00" }, align 1, linkage(internal), constant
3804global @.Lstr.2 : bytes 3 = { bytes "wz\00" }, align 1, linkage(internal), constant
3805
3806func @strspn(ptr, ptr) -> i64, linkage(external);
3807func @strcspn(ptr, ptr) -> i64, linkage(external);
3808func @use(i64, i64), linkage(external);
3809
3810func @g(), linkage(external) {
3811block0:
3812 %0 = global_addr @.Lstr.0
3813 %1 = global_addr @.Lstr.1
3814 %2 = call @strspn(%0, %1) : (ptr, ptr) -> i64
3815 %3 = global_addr @.Lstr.2
3816 %4 = call @strcspn(%0, %3) : (ptr, ptr) -> i64
3817 call @use(%2, %4) : (i64, i64)
3818 return
3819}
3820"#,
3821 );
3822 assert!(!out.contains("call @strspn("), "{out}");
3823 assert!(!out.contains("call @strcspn("), "{out}");
3824 assert!(out.contains("iconst.i64 4"), "hello stops at the o, {out}");
3825 assert!(out.contains("iconst.i64 6"), "the w is six bytes along, {out}");
3826 }
3827
3828 #[test]
3831 fn an_empty_set_is_a_span_of_nothing_or_of_all_of_it() {
3832 let out = folded(
3833 r#"
3834global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3835
3836func @strspn(ptr, ptr) -> i64, linkage(external);
3837func @strcspn(ptr, ptr) -> i64, linkage(external);
3838func @strlen(ptr) -> i64, linkage(external);
3839func @use(i64, i64), linkage(external);
3840
3841func @g(ptr), linkage(external) {
3842block0(%0: ptr):
3843 %1 = global_addr @.Lstr.0
3844 %2 = call @strspn(%0, %1) : (ptr, ptr) -> i64
3845 %3 = call @strcspn(%0, %1) : (ptr, ptr) -> i64
3846 call @use(%2, %3) : (i64, i64)
3847 return
3848}
3849"#,
3850 );
3851 assert!(!out.contains("call @strspn("), "{out}");
3852 assert!(!out.contains("call @strcspn("), "{out}");
3853 assert!(out.contains("call @strlen(%0)"), "{out}");
3854 assert!(out.contains("iconst.i64 0"), "{out}");
3855 }
3856
3857 #[test]
3860 fn a_strcspn_of_another_width_than_strlen_is_left_alone() {
3861 let out = folded(
3862 r#"
3863global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3864
3865func @strcspn(ptr, ptr) -> i32, linkage(external);
3866func @strlen(ptr) -> i64, linkage(external);
3867
3868func @g(ptr) -> i32, linkage(external) {
3869block0(%0: ptr):
3870 %1 = global_addr @.Lstr.0
3871 %2 = call @strcspn(%0, %1) : (ptr, ptr) -> i32
3872 return %2
3873}
3874"#,
3875 );
3876 assert!(out.contains("call @strcspn("), "{out}");
3877 }
3878
3879 #[test]
3882 fn strpbrk_of_a_short_set_is_a_search_or_an_answer() {
3883 let out = folded(
3884 r#"
3885global @.Lstr.0 : bytes 2 = { bytes "w\00" }, align 1, linkage(internal), constant
3886global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
3887
3888func @strpbrk(ptr, ptr) -> ptr, linkage(external);
3889func @strchr(ptr, i32) -> ptr, linkage(external);
3890func @use(ptr, ptr), linkage(external);
3891
3892func @g(ptr), linkage(external) {
3893block0(%0: ptr):
3894 %1 = global_addr @.Lstr.0
3895 %2 = call @strpbrk(%0, %1) : (ptr, ptr) -> ptr
3896 %3 = global_addr @.Lstr.1
3897 %4 = call @strpbrk(%0, %3) : (ptr, ptr) -> ptr
3898 call @use(%2, %4) : (ptr, ptr)
3899 return
3900}
3901"#,
3902 );
3903 assert!(!out.contains("call @strpbrk("), "{out}");
3904 assert!(out.contains("call @strchr(%0, "), "{out}");
3905 assert!(out.contains("iconst.i32 119"), "{out}");
3906 assert!(out.contains("inttoptr.ptr "), "the empty set is nowhere, {out}");
3907 }
3908
3909 #[test]
3911 fn strpbrk_over_two_strings_this_module_holds_is_a_place() {
3912 let out = folded(
3913 r#"
3914global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3915global @.Lstr.1 : bytes 3 = { bytes "wz\00" }, align 1, linkage(internal), constant
3916global @.Lstr.2 : bytes 3 = { bytes "qz\00" }, align 1, linkage(internal), constant
3917
3918func @strpbrk(ptr, ptr) -> ptr, linkage(external);
3919func @use(ptr, ptr), linkage(external);
3920
3921func @g(), linkage(external) {
3922block0:
3923 %0 = global_addr @.Lstr.0
3924 %1 = global_addr @.Lstr.1
3925 %2 = call @strpbrk(%0, %1) : (ptr, ptr) -> ptr
3926 %3 = global_addr @.Lstr.2
3927 %4 = call @strpbrk(%0, %3) : (ptr, ptr) -> ptr
3928 call @use(%2, %4) : (ptr, ptr)
3929 return
3930}
3931"#,
3932 );
3933 assert!(!out.contains("call @strpbrk("), "{out}");
3934 assert!(out.contains("iconst.i64 6"), "the w is six bytes along, {out}");
3935 assert!(out.contains("inttoptr.ptr "), "there is neither a q nor a z in it, {out}");
3936 }
3937
3938 #[test]
3940 fn the_older_spellings_of_the_two_searches_are_folded_as_well() {
3941 let out = folded(
3942 r#"
3943global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
3944
3945func @index(ptr, i32) -> ptr, linkage(external);
3946func @rindex(ptr, i32) -> ptr, linkage(external);
3947func @use(ptr, ptr), linkage(external);
3948
3949func @g(), linkage(external) {
3950block0:
3951 %0 = global_addr @.Lstr.0
3952 %1 = iconst.i32 111
3953 %2 = call @index(%0, %1) : (ptr, i32) -> ptr
3954 %3 = call @rindex(%0, %1) : (ptr, i32) -> ptr
3955 call @use(%2, %3) : (ptr, ptr)
3956 return
3957}
3958"#,
3959 );
3960 assert!(!out.contains("call @index("), "{out}");
3961 assert!(!out.contains("call @rindex("), "{out}");
3962 assert!(out.contains("iconst.i64 4"), "the first o, {out}");
3963 assert!(out.contains("iconst.i64 7"), "the last o, {out}");
3964 }
3965
3966 #[test]
3969 fn a_strrchr_of_the_terminator_is_a_strchr_of_it() {
3970 let out = folded(
3971 r#"
3972func @strrchr(ptr, i32) -> ptr, linkage(external);
3973
3974func @g(ptr) -> ptr, linkage(external) {
3975block0(%0: ptr):
3976 %1 = iconst.i32 0
3977 %2 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
3978 return %2
3979}
3980"#,
3981 );
3982 assert!(!out.contains("call @strrchr("), "{out}");
3983 assert!(out.contains("call @strchr(%0, "), "{out}");
3984 }
3985
3986 #[test]
3989 fn a_strrchr_of_another_character_needs_the_string() {
3990 let out = folded(
3991 r#"
3992func @strrchr(ptr, i32) -> ptr, linkage(external);
3993
3994func @g(ptr) -> ptr, linkage(external) {
3995block0(%0: ptr):
3996 %1 = iconst.i32 111
3997 %2 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
3998 return %2
3999}
4000"#,
4001 );
4002 assert!(out.contains("call @strrchr("), "{out}");
4003 }
4004
4005 #[test]
4007 fn a_strlen_that_answers_nothing_is_not_the_one_the_library_has() {
4008 let out = folded(
4009 r#"
4010global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
4011
4012func @strlen(ptr), linkage(external);
4013
4014func @g(), linkage(external) {
4015block0:
4016 %0 = global_addr @.Lstr.0
4017 call @strlen(%0) : (ptr)
4018 return
4019}
4020"#,
4021 );
4022 assert!(out.contains("call @strlen("), "{out}");
4023 }
4024
4025 #[test]
4029 fn a_checking_copy_that_fits_is_the_plain_copy() {
4030 let out = folded(
4031 r#"
4032func @__memcpy_chk(ptr, ptr, i64, i64) -> ptr, linkage(external);
4033func @__mempcpy_chk(ptr, ptr, i64, i64) -> ptr, linkage(external);
4034func @use(ptr, ptr), linkage(external);
4035
4036func @g(ptr, ptr, i64), linkage(external) {
4037block0(%0: ptr, %1: ptr, %2: i64):
4038 %3 = iconst.i64 4
4039 %4 = iconst.i64 32
4040 %5 = call @__memcpy_chk(%0, %1, %3, %4) : (ptr, ptr, i64, i64) -> ptr
4041 %6 = iconst.i64 40
4042 %7 = call @__memcpy_chk(%0, %1, %6, %4) : (ptr, ptr, i64, i64) -> ptr
4043 %8 = call @__mempcpy_chk(%0, %1, %2, %4) : (ptr, ptr, i64, i64) -> ptr
4044 call @use(%5, %7) : (ptr, ptr)
4045 return
4046}
4047"#,
4048 );
4049 assert!(out.contains("call @memcpy(%0, %1, %3)"), "four bytes fit in thirty two, {out}");
4050 assert!(out.contains("call @__memcpy_chk(%0, %1, %6, %4)"), "forty do not, {out}");
4051 assert!(out.contains("call @__memcpy_chk(%0, %1, %2, %4)"), "nothing read the end, {out}");
4052 assert!(!out.contains("call @__mempcpy_chk("), "{out}");
4053 }
4054
4055 #[test]
4059 fn a_checking_string_copy_goes_as_far_as_the_string_is_known() {
4060 let out = folded(
4061 r#"
4062global @.Lstr.0 : bytes 6 = { bytes "abcde\00" }, align 1, linkage(internal), constant
4063
4064func @__stpcpy_chk(ptr, ptr, i64) -> ptr, linkage(external);
4065func @use(ptr, ptr), linkage(external);
4066
4067func @g(ptr, ptr), linkage(external) {
4068block0(%0: ptr, %1: ptr):
4069 %2 = global_addr @.Lstr.0
4070 %3 = iconst.i64 32
4071 %4 = call @__stpcpy_chk(%0, %2, %3) : (ptr, ptr, i64) -> ptr
4072 %5 = call @__stpcpy_chk(%0, %1, %3) : (ptr, ptr, i64) -> ptr
4073 %6 = call @__stpcpy_chk(%0, %1, %3) : (ptr, ptr, i64) -> ptr
4074 call @use(%4, %5) : (ptr, ptr)
4075 return
4076}
4077"#,
4078 );
4079 assert!(out.contains("call @memcpy(%0, %2, "), "{out}");
4080 assert!(out.contains("iconst.i64 6"), "five bytes and a terminator, {out}");
4081 assert!(out.contains("ptr_add %0"), "the answer is the end of the copy, {out}");
4082 assert!(out.contains("call @__stpcpy_chk(%0, %1, %3)"), "{out}");
4083 assert!(out.contains("call @__strcpy_chk(%0, %1, %3)"), "{out}");
4084 }
4085
4086 #[test]
4089 fn a_checking_string_copy_that_does_not_fit_is_a_checking_copy_of_a_count() {
4090 let out = folded(
4091 r#"
4092global @.Lstr.0 : bytes 6 = { bytes "abcde\00" }, align 1, linkage(internal), constant
4093
4094func @__strcpy_chk(ptr, ptr, i64) -> ptr, linkage(external);
4095func @use(ptr), linkage(external);
4096
4097func @g(ptr), linkage(external) {
4098block0(%0: ptr):
4099 %1 = global_addr @.Lstr.0
4100 %2 = iconst.i64 4
4101 %3 = call @__strcpy_chk(%0, %1, %2) : (ptr, ptr, i64) -> ptr
4102 call @use(%3) : (ptr)
4103 return
4104}
4105"#,
4106 );
4107 assert!(out.contains("call @__memcpy_chk(%0, %1, "), "{out}");
4108 assert!(out.contains("iconst.i64 6"), "{out}");
4109 }
4110
4111 #[test]
4114 fn a_checking_append_of_nothing_is_the_destination() {
4115 let out = folded(
4116 r#"
4117global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
4118global @.Lstr.1 : bytes 4 = { bytes "abc\00" }, align 1, linkage(internal), constant
4119
4120func @__strcat_chk(ptr, ptr, i64) -> ptr, linkage(external);
4121func @__strncat_chk(ptr, ptr, i64, i64) -> ptr, linkage(external);
4122func @use(ptr, ptr, ptr, ptr), linkage(external);
4123
4124func @g(ptr, ptr), linkage(external) {
4125block0(%0: ptr, %1: ptr):
4126 %2 = global_addr @.Lstr.0
4127 %3 = global_addr @.Lstr.1
4128 %4 = iconst.i64 32
4129 %5 = call @__strcat_chk(%0, %2, %4) : (ptr, ptr, i64) -> ptr
4130 %6 = iconst.i64 0
4131 %7 = call @__strncat_chk(%0, %1, %6, %4) : (ptr, ptr, i64, i64) -> ptr
4132 %8 = iconst.i64 5
4133 %9 = call @__strncat_chk(%0, %3, %8, %4) : (ptr, ptr, i64, i64) -> ptr
4134 %10 = iconst.i64 2
4135 %11 = call @__strncat_chk(%0, %3, %10, %4) : (ptr, ptr, i64, i64) -> ptr
4136 call @use(%5, %7, %9, %11) : (ptr, ptr, ptr, ptr)
4137 return
4138}
4139"#,
4140 );
4141 assert!(out.contains("call @use(%0, %0, "), "{out}");
4142 assert_eq!(
4143 out.matches("call @__strcat_chk(%0, ").count(),
4144 1,
4145 "five is no limit on three, {out}"
4146 );
4147 assert_eq!(out.matches("call @__strncat_chk(%0, ").count(), 1, "two is, {out}");
4148 }
4149
4150 #[test]
4154 fn a_checking_sprintf_of_a_known_string_is_a_copy() {
4155 let out = folded(
4156 r#"
4157global @.Lstr.0 : bytes 6 = { bytes "hello\00" }, align 1, linkage(internal), constant
4158global @.Lstr.1 : bytes 3 = { bytes "%d\00" }, align 1, linkage(internal), constant
4159
4160func @__sprintf_chk(ptr, i32, i64, ptr, ...) -> i32, linkage(external);
4161func @use(i32, i32), linkage(external);
4162
4163func @g(ptr, i32), linkage(external) {
4164block0(%0: ptr, %1: i32):
4165 %2 = global_addr @.Lstr.0
4166 %3 = global_addr @.Lstr.1
4167 %4 = iconst.i32 0
4168 %5 = iconst.i64 32
4169 %6 = call @__sprintf_chk(%0, %4, %5, %2) : (ptr, i32, i64, ptr, ...) -> i32
4170 %7 = call @__sprintf_chk(%0, %4, %5, %3, %1) : (ptr, i32, i64, ptr, ...) -> i32
4171 call @use(%6, %7) : (i32, i32)
4172 return
4173}
4174"#,
4175 );
4176 assert!(out.contains("call @memcpy(%0, %2, "), "{out}");
4177 assert!(out.contains("iconst.i32 5"), "the length is the answer, {out}");
4178 assert!(out.contains("call @__sprintf_chk(%0, %4, %5, %3, %1)"), "{out}");
4179 }
4180
4181 #[test]
4184 fn a_checking_snprintf_keeps_its_arguments_and_loses_its_check() {
4185 let out = folded(
4186 r#"
4187global @.Lstr.0 : bytes 3 = { bytes "%d\00" }, align 1, linkage(internal), constant
4188
4189func @__snprintf_chk(ptr, i64, i32, i64, ptr, ...) -> i32, linkage(external);
4190func @use(i32, i32), linkage(external);
4191
4192func @g(ptr, i32), linkage(external) {
4193block0(%0: ptr, %1: i32):
4194 %2 = global_addr @.Lstr.0
4195 %3 = iconst.i64 8
4196 %4 = iconst.i32 0
4197 %5 = iconst.i64 32
4198 %6 = call @__snprintf_chk(%0, %3, %4, %5, %2, %1) : (ptr, i64, i32, i64, ptr, ...) -> i32
4199 %7 = iconst.i32 1
4200 %8 = call @__snprintf_chk(%0, %3, %7, %5, %2, %1) : (ptr, i64, i32, i64, ptr, ...) -> i32
4201 call @use(%6, %8) : (i32, i32)
4202 return
4203}
4204"#,
4205 );
4206 assert!(
4207 out.contains("call @snprintf(%0, %3, %2, %1) : (ptr, i64, ptr, ...) -> i32"),
4208 "{out}"
4209 );
4210 assert!(out.contains("call @__snprintf_chk(%0, %3, %7, %5, %2, %1)"), "{out}");
4211 }
4212
4213 #[test]
4215 fn a_plain_function_of_another_shape_keeps_the_check() {
4216 let out = folded(
4217 r#"
4218func @__memcpy_chk(ptr, ptr, i64, i64) -> ptr, linkage(external);
4219func @memcpy(ptr, ptr, i32) -> ptr, linkage(external);
4220func @use(ptr), linkage(external);
4221
4222func @g(ptr, ptr), linkage(external) {
4223block0(%0: ptr, %1: ptr):
4224 %2 = iconst.i64 4
4225 %3 = iconst.i64 32
4226 %4 = call @__memcpy_chk(%0, %1, %2, %3) : (ptr, ptr, i64, i64) -> ptr
4227 call @use(%4) : (ptr)
4228 return
4229}
4230"#,
4231 );
4232 assert!(out.contains("call @__memcpy_chk("), "{out}");
4233 }
4234
4235 #[test]
4239 fn a_copy_into_the_end_of_a_copy_names_the_end_the_first_fold_wrote() {
4240 let out = folded(
4241 r#"
4242global @.Lstr.0 : bytes 8 = { bytes "abcdEFG\00" }, align 1, linkage(internal), constant
4243global @.Lstr.1 : bytes 4 = { bytes "efg\00" }, align 1, linkage(internal), constant
4244
4245func @mempcpy(ptr, ptr, i64) -> ptr, linkage(external);
4246func @use(ptr), linkage(external);
4247
4248func @g(ptr), linkage(external) {
4249block0(%0: ptr):
4250 %1 = global_addr @.Lstr.0
4251 %2 = global_addr @.Lstr.1
4252 %3 = iconst.i64 4
4253 %4 = call @mempcpy(%0, %1, %3) : (ptr, ptr, i64) -> ptr
4254 %5 = call @mempcpy(%4, %2, %3) : (ptr, ptr, i64) -> ptr
4255 call @use(%5) : (ptr)
4256 return
4257}
4258"#,
4259 );
4260 assert!(!out.contains("call @mempcpy("), "{out}");
4261 assert_eq!(out.matches("call @memcpy(").count(), 2, "{out}");
4262 assert_eq!(out.matches("ptr_add").count(), 2, "{out}");
4263 }
4264
4265 #[test]
4268 fn a_counted_append_of_all_of_a_known_string_is_the_uncounted_one() {
4269 let out = folded(
4270 r#"
4271global @.Lstr.0 : bytes 4 = { bytes "foo\00" }, align 1, linkage(internal), constant
4272
4273func @strncat(ptr, ptr, i64) -> ptr, linkage(external);
4274func @use(ptr, ptr), linkage(external);
4275
4276func @g(ptr), linkage(external) {
4277block0(%0: ptr):
4278 %1 = global_addr @.Lstr.0
4279 %2 = iconst.i64 3
4280 %3 = call @strncat(%0, %1, %2) : (ptr, ptr, i64) -> ptr
4281 %4 = iconst.i64 2
4282 %5 = call @strncat(%0, %1, %4) : (ptr, ptr, i64) -> ptr
4283 call @use(%3, %5) : (ptr, ptr)
4284 return
4285}
4286"#,
4287 );
4288 assert_eq!(out.matches("call @strcat(%0, %1)").count(), 1, "{out}");
4289 assert_eq!(out.matches("call @strncat(").count(), 1, "{out}");
4290 }
4291
4292 #[test]
4295 fn a_count_fits_where_the_largest_it_may_be_fits() {
4296 let text = r#"
4297func @__memcpy_chk(ptr, ptr, i64, i64) -> ptr, linkage(external);
4298func @use(ptr), linkage(external);
4299
4300func @g(ptr, ptr, i1), linkage(external) {
4301block0(%0: ptr, %1: ptr, %2: i1):
4302 %3 = iconst.i64 8
4303 %4 = iconst.i64 4
4304 br_if %2, block1(%3), block1(%4)
4305block1(%5: i64):
4306 %6 = iconst.i64 SIZE
4307 %7 = call @__memcpy_chk(%0, %1, %5, %6) : (ptr, ptr, i64, i64) -> ptr
4308 call @use(%7) : (ptr)
4309 return
4310}
4311"#;
4312 let fits = folded(&text.replace("SIZE", "8"));
4313 assert!(fits.contains("call @memcpy("), "{fits}");
4314 let short = folded(&text.replace("SIZE", "7"));
4315 assert!(short.contains("call @__memcpy_chk("), "{short}");
4316 }
4317
4318 #[test]
4321 fn rounding_a_widened_float_is_done_in_float() {
4322 let text = r#"
4323func @floor(f64) -> f64, linkage(external);
4324func @sin(f64) -> f64, linkage(external);
4325func @use(f64, f64, f64), linkage(external);
4326
4327func @g(f32, f64), linkage(external) {
4328block0(%0: f32, %1: f64):
4329 %2 = fpext.f64 %0
4330 %3 = call @floor(%2) : (f64) -> f64
4331 %4 = call @sin(%2) : (f64) -> f64
4332 %5 = call @floor(%1) : (f64) -> f64
4333 call @use(%3, %4, %5) : (f64, f64, f64)
4334 return
4335}
4336"#;
4337 let out = folded(text);
4338 assert!(out.contains("call @floorf(%0) : (f32) -> f32"), "{out}");
4339 assert_eq!(out.matches("call @floor(").count(), 1, "the double one stays, {out}");
4340 assert_eq!(out.matches("call @sin(").count(), 1, "{out}");
4341 }
4342}