1use std::collections::{HashMap, HashSet};
106
107use rucc_base::{Interner, Symbol};
108use rucc_ir::{
109 CallInfo, Datum, Def, Extra, Func, FuncId, Global, Imm, Inst, InstData, Linkage, Module,
110 Opcode, Pic, Signature, SymbolRef, Type, Value,
111};
112
113use crate::extents::vouched;
114use crate::{Cfg, Fuel, Stats, uses};
115
116pub const NAME: &str = "libcall";
118
119const DEPTH: u32 = 4;
126
127const REPLACEMENTS: [&str; 7] = ["fputc", "fputs", "fwrite", "putchar", "puts", "strchr", "strlen"];
129
130const SOURCES: [&str; 19] = [
132 "fprintf",
133 "fprintf_unlocked",
134 "fputs",
135 "fputs_unlocked",
136 "index",
137 "memchr",
138 "printf",
139 "printf_unlocked",
140 "rindex",
141 "strchr",
142 "strcmp",
143 "strcspn",
144 "strlen",
145 "strncmp",
146 "strnlen",
147 "strpbrk",
148 "strrchr",
149 "strspn",
150 "strstr",
151];
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155enum Plan {
156 Drop,
158 Answer(Answer),
161 Swap {
163 callee: Symbol,
165 signature: Signature,
167 args: Vec<Argument>,
169 },
170}
171
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174enum Answer {
175 Along(Value, u64),
177 Nowhere,
179 Number(i128),
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189enum Side {
190 First,
192 Last,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198enum Set {
199 Inside,
201 Outside,
203}
204
205#[derive(Debug, Clone, PartialEq, Eq)]
207enum Argument {
208 Have(Value),
210 Char(u8),
212 Count(u64),
214 Text(Vec<u8>),
216}
217
218struct Shapes {
225 held: HashMap<&'static str, Option<(Symbol, Signature)>>,
228}
229
230impl Shapes {
231 fn of(module: &Module, names: &mut Interner) -> Self {
233 let mut held: HashMap<&'static str, Option<(Symbol, Signature)>> = REPLACEMENTS
234 .iter()
235 .map(|&name| (name, Some((names.intern(name), canonical(module, name)))))
236 .collect();
237 for id in module.funcs() {
238 let func = &module[id];
241 let spelled = func.spelled.unwrap_or(func.name);
242 let Some(slot) = held.get_mut(names.resolve(spelled)) else { continue };
243 let declared = func.signature();
244 let agrees = slot.as_ref().is_some_and(|(_, want)| {
245 !declared.variadic
246 && declared.param_types().eq(want.param_types())
247 && declared.return_types().eq(want.return_types())
248 });
249 *slot = agrees.then(|| (func.name, declared.clone()));
250 }
251 for id in module.globals() {
254 if let Some(slot) = held.get_mut(names.resolve(module[id].name)) {
255 *slot = None;
256 }
257 }
258 for id in module.aliases() {
259 if let Some(slot) = held.get_mut(names.resolve(module[id].name)) {
260 *slot = None;
261 }
262 }
263 Self { held }
264 }
265
266 fn get(&self, name: &'static str) -> Option<(Symbol, Signature)> {
268 self.held.get(name)?.clone()
269 }
270}
271
272fn canonical(module: &Module, name: &str) -> Signature {
277 let int = int();
278 let size = size(module);
279 match name {
280 "puts" => Signature::new().with_params(&[Type::PTR]).with_returns(&[int]),
281 "putchar" => Signature::new().with_params(&[int]).with_returns(&[int]),
282 "fputc" => Signature::new().with_params(&[int, Type::PTR]).with_returns(&[int]),
283 "fputs" => Signature::new().with_params(&[Type::PTR, Type::PTR]).with_returns(&[int]),
284 "strchr" => Signature::new().with_params(&[Type::PTR, int]).with_returns(&[Type::PTR]),
285 "strlen" => Signature::new().with_params(&[Type::PTR]).with_returns(&[size]),
286 _ => {
289 Signature::new().with_params(&[Type::PTR, size, size, Type::PTR]).with_returns(&[size])
290 }
291 }
292}
293
294const fn int() -> Type {
300 Type::int(32)
301}
302
303fn size(module: &Module) -> Type {
305 Type::int(module.datalayout.pointer_bits)
306}
307
308pub fn fold(
313 module: &mut Module,
314 names: &mut Interner,
315 no_builtin: &[String],
316 pic: Pic,
317 fuel: &mut Fuel,
318) -> Vec<(FuncId, Stats)> {
319 let shapes = Shapes::of(module, names);
320 let standard: HashMap<Symbol, Symbol> =
324 module.funcs().filter_map(|id| Some((module[id].name, module[id].spelled?))).collect();
325 let defined: HashSet<Symbol> = module
328 .funcs()
329 .filter(|&id| !module[id].is_declaration())
330 .map(|id| module[id].name)
331 .collect();
332 let mut texts: HashMap<Vec<u8>, Symbol> = HashMap::new();
335 let mut done = Vec::new();
336 for id in module.funcs().collect::<Vec<FuncId>>() {
337 if module[id].is_declaration() || !mentions(&module[id], names, &standard) {
342 continue;
343 }
344 let mut stats = Stats::new();
345 let plans = {
349 let func = &module[id];
350 let site = Site {
351 module,
352 func,
353 cfg: &Cfg::new(func),
354 shapes: &shapes,
355 counts: &uses::count(func),
356 defined: &defined,
357 standard: &standard,
358 names,
359 no_builtin,
360 pic,
361 };
362 site.survey(fuel, &mut stats)
363 };
364 for (inst, plan) in plans {
365 apply(module, id, names, &mut texts, inst, plan);
366 }
367 if stats.changed() {
368 done.push((id, stats));
369 }
370 }
371 done
372}
373
374fn mentions(func: &Func, names: &Interner, standard: &HashMap<Symbol, Symbol>) -> bool {
376 func.blocks().flat_map(|block| func.insts(block)).any(|inst| {
377 let data = &func[inst];
378 let Extra::Call(at) = data.extra else { return false };
379 data.opcode == Opcode::Call
380 && func[at].callee.is_some_and(|callee| {
381 let spelled = standard.get(&callee).copied().unwrap_or(callee);
382 SOURCES.contains(&names.resolve(spelled))
383 })
384 })
385}
386
387struct Site<'a> {
389 module: &'a Module,
391 func: &'a Func,
393 cfg: &'a Cfg,
395 shapes: &'a Shapes,
397 counts: &'a [u32],
399 defined: &'a HashSet<Symbol>,
401 standard: &'a HashMap<Symbol, Symbol>,
403 names: &'a Interner,
405 no_builtin: &'a [String],
407 pic: Pic,
409}
410
411impl Site<'_> {
412 fn survey(&self, fuel: &mut Fuel, stats: &mut Stats) -> Vec<(Inst, Plan)> {
414 let mut plans = Vec::new();
415 for block in self.func.blocks().collect::<Vec<_>>() {
416 for inst in self.func.insts(block).collect::<Vec<Inst>>() {
417 let Some(plan) = self.plan(inst) else { continue };
418 if !fuel.take() {
419 stats.missed("call to the library folded");
420 continue;
421 }
422 stats.optimized(match &plan {
423 Plan::Drop => "call to the library that writes nothing removed",
424 Plan::Answer(_) => "call to the library whose answer is known folded",
425 Plan::Swap { .. } => "call to the library folded",
426 });
427 plans.push((inst, plan));
428 }
429 }
430 plans
431 }
432
433 fn plan(&self, inst: Inst) -> Option<Plan> {
436 let data = &self.func[inst];
437 if data.opcode != Opcode::Call || self.func.mem_in(inst).is_some() {
438 return None;
439 }
440 let ignored = data.results().all(|result| self.counts[result.index()] == 0);
444 let Extra::Call(at) = data.extra else { return None };
445 let callee = self.func[at].callee?;
446 if self.defined.contains(&callee) {
447 return None;
448 }
449 let name = self.names.resolve(self.standard.get(&callee).copied().unwrap_or(callee));
450 if self.no_builtin.iter().any(|it| it == name) {
451 return None;
452 }
453 let args: Vec<Value> = self.func[data.args].to_vec();
454 match name {
457 "printf" if ignored => self.printf(&args, false),
458 "printf_unlocked" if ignored => self.printf(&args, true),
459 "fprintf" if ignored => self.fprintf(&args, false),
460 "fprintf_unlocked" if ignored => self.fprintf(&args, true),
461 "fputs" if ignored => self.fputs(&args, false),
462 "fputs_unlocked" if ignored => self.fputs(&args, true),
463 "strstr" => self.strstr(data, &args),
464 "strchr" | "index" => self.strchr(data, &args, Side::First),
467 "strrchr" | "rindex" => self.strchr(data, &args, Side::Last),
468 "memchr" => self.memchr(data, &args),
469 "strlen" => self.strlen(data, &args),
470 "strnlen" => self.strnlen(data, &args),
471 "strcmp" => self.strcmp(data, &args),
472 "strncmp" => self.strncmp(data, &args),
473 "strcspn" => self.span(data, &args, Set::Outside),
474 "strspn" => self.span(data, &args, Set::Inside),
475 "strpbrk" => self.strpbrk(data, &args),
476 _ => None,
477 }
478 }
479
480 fn answers(&self, data: &InstData) -> Option<Type> {
485 let mut results = data.results();
486 let ty = self.func[results.next()?].ty;
487 (results.next().is_none() && ty.is_int() && !ty.is_vector()).then_some(ty)
488 }
489
490 fn places(&self, data: &InstData) -> bool {
492 let mut results = data.results();
493 results.next().is_some_and(|result| self.func[result].ty == Type::PTR)
494 && results.next().is_none()
495 }
496
497 fn character(&self, value: Value) -> Option<u8> {
500 let (imm, ty) = crate::fold::constant(self.func, value)?;
501 u8::try_from(imm.signed(ty).rem_euclid(256)).ok()
502 }
503
504 fn count(&self, value: Value) -> Option<usize> {
510 let narrow = self.widened(value);
511 let (imm, ty) = crate::fold::constant(self.func, narrow)?;
512 (narrow == value || imm.signed(ty) >= 0).then_some(())?;
515 usize::try_from(imm.unsigned()).ok()
516 }
517
518 fn widened(&self, value: Value) -> Value {
523 let Def::Result { inst, .. } = self.func[value].def else { return value };
524 if !matches!(self.func[inst].opcode, Opcode::SExt | Opcode::ZExt) {
525 return value;
526 }
527 self.func[self.func[inst].args].first().copied().unwrap_or(value)
528 }
529
530 fn strchr(&self, data: &InstData, args: &[Value], side: Side) -> Option<Plan> {
536 if args.len() != 2 || self.func[args[0]].ty != Type::PTR || !self.places(data) {
537 return None;
538 }
539 let wanted = self.character(args[1])?;
540 let Some(text) = self.one(args[0]) else {
541 return match (wanted, side) {
545 (0, Side::Last) => {
546 self.call("strchr", vec![Argument::Have(args[0]), Argument::Char(0)])
547 }
548 _ => None,
549 };
550 };
551 let found = match (wanted, side) {
552 (0, _) => Some(text.len()),
553 (_, Side::First) => text.iter().position(|&byte| byte == wanted),
554 (_, Side::Last) => text.iter().rposition(|&byte| byte == wanted),
555 };
556 Some(Plan::Answer(match found {
557 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
558 None => Answer::Nowhere,
559 }))
560 }
561
562 fn memchr(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
569 (args.len() == 3).then_some(())?; if self.func[args[0]].ty != Type::PTR || !self.places(data) {
571 return None;
572 }
573 let wanted = self.character(args[1])?;
574 let count = self.count(args[2])?;
575 let bytes = self.raw(args[0])?;
576 let window = bytes.get(..count)?;
577 Some(Plan::Answer(match window.iter().position(|&byte| byte == wanted) {
578 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
579 None => Answer::Nowhere,
580 }))
581 }
582
583 fn strlen(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
585 if args.len() != 1 || self.func[args[0]].ty != Type::PTR {
586 return None;
587 }
588 self.answers(data)?;
589 let text = self.one(args[0])?;
590 Some(Plan::Answer(Answer::Number(i128::try_from(text.len()).ok()?)))
591 }
592
593 fn strnlen(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
598 if args.len() != 2 || self.func[args[0]].ty != Type::PTR {
599 return None;
600 }
601 self.answers(data)?;
602 let count = self.count(args[1])?;
603 let bytes = self.raw(args[0])?;
604 let window = bytes.get(..count.min(bytes.len()))?;
605 let len = match window.iter().position(|&byte| byte == 0) {
606 Some(at) => at,
607 None if window.len() == count => count,
610 None => return None,
611 };
612 Some(Plan::Answer(Answer::Number(i128::try_from(len).ok()?)))
613 }
614
615 fn strcmp(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
617 (args.len() == 2).then_some(())?;
618 self.compared(data, args, usize::MAX)
619 }
620
621 fn strncmp(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
623 (args.len() == 3).then_some(())?; let count = self.count(args[2])?;
625 self.compared(data, args, count)
626 }
627
628 fn compared(&self, data: &InstData, args: &[Value], bound: usize) -> Option<Plan> {
633 if self.func[args[0]].ty != Type::PTR || self.func[args[1]].ty != Type::PTR {
634 return None;
635 }
636 self.answers(data)?;
637 let (mut left, mut right) = (self.one(args[0])?, self.one(args[1])?);
638 left.push(0);
641 right.push(0);
642 let mut answer = 0;
643 for at in 0..bound.min(left.len()).min(right.len()) {
644 if left[at] != right[at] {
645 answer = if left[at] < right[at] { -1 } else { 1 };
646 break;
647 }
648 if left[at] == 0 {
649 break;
650 }
651 }
652 Some(Plan::Answer(Answer::Number(answer)))
653 }
654
655 fn span(&self, data: &InstData, args: &[Value], set: Set) -> Option<Plan> {
662 if args.len() != 2
663 || self.func[args[0]].ty != Type::PTR
664 || self.func[args[1]].ty != Type::PTR
665 {
666 return None;
667 }
668 let ty = self.answers(data)?;
669 if let Some(text) = self.one(args[0])
672 && text.is_empty()
673 {
674 return Some(Plan::Answer(Answer::Number(0)));
675 }
676 let accept = self.one(args[1])?;
677 if let Some(text) = self.one(args[0]) {
680 let len = text
681 .iter()
682 .position(|byte| accept.contains(byte) != matches!(set, Set::Inside))
683 .unwrap_or(text.len());
684 return Some(Plan::Answer(Answer::Number(i128::try_from(len).ok()?)));
685 }
686 accept.is_empty().then_some(())?;
688 match set {
689 Set::Inside => Some(Plan::Answer(Answer::Number(0))),
690 Set::Outside => {
694 let (_, signature) = self.shapes.get("strlen")?;
695 signature.return_types().eq([ty]).then_some(())?;
696 self.call("strlen", vec![Argument::Have(args[0])])
697 }
698 }
699 }
700
701 fn strpbrk(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
703 if args.len() != 2 || self.func[args[0]].ty != Type::PTR || !self.places(data) {
704 return None;
705 }
706 if self.func[args[1]].ty != Type::PTR {
707 return None;
708 }
709 let accept = self.one(args[1])?;
710 if accept.is_empty() {
712 return Some(Plan::Answer(Answer::Nowhere));
713 }
714 match self.one(args[0]) {
715 Some(text) => {
716 Some(Plan::Answer(match text.iter().position(|byte| accept.contains(byte)) {
717 Some(at) => Answer::Along(args[0], u64::try_from(at).ok()?),
718 None => Answer::Nowhere,
719 }))
720 }
721 None => match accept.as_slice() {
724 [one] => self.call("strchr", vec![Argument::Have(args[0]), Argument::Char(*one)]),
725 _ => None,
726 },
727 }
728 }
729
730 fn strstr(&self, data: &InstData, args: &[Value]) -> Option<Plan> {
736 if args.len() != 2 {
737 return None;
738 }
739 let (haystack, needle) = (args[0], args[1]);
740 if self.func[haystack].ty != Type::PTR || self.func[needle].ty != Type::PTR {
741 return None;
742 }
743 let mut results = data.results();
746 if !results.next().is_some_and(|result| self.func[result].ty == Type::PTR)
747 || results.next().is_some()
748 {
749 return None;
750 }
751 let needle = self.one(needle)?;
752 if needle.is_empty() {
754 return Some(Plan::Answer(Answer::Along(haystack, 0)));
755 }
756 match self.one(haystack) {
757 Some(hay) => Some(Plan::Answer(match at(&hay, &needle) {
758 Some(found) => Answer::Along(haystack, u64::try_from(found).ok()?),
759 None => Answer::Nowhere,
760 })),
761 None => match needle.as_slice() {
764 [one] => self.call("strchr", vec![Argument::Have(haystack), Argument::Char(*one)]),
765 _ => None,
766 },
767 }
768 }
769
770 fn printf(&self, args: &[Value], quiet: bool) -> Option<Plan> {
772 let format = self.one(*args.first()?)?;
773 match args.len() {
774 1 => self.plain(&format, None, quiet),
775 2 if format == b"%s\n" && !quiet && self.func[args[1]].ty == Type::PTR => {
776 self.call("puts", vec![Argument::Have(args[1])])
777 }
778 2 if format == b"%c" && !quiet && self.func[args[1]].ty == int() => {
779 self.call("putchar", vec![Argument::Have(args[1])])
780 }
781 2 if format == b"%s" => self.plain(&self.one(args[1])?, None, quiet),
785 _ => None,
786 }
787 }
788
789 fn fprintf(&self, args: &[Value], quiet: bool) -> Option<Plan> {
791 let stream = *args.first()?;
792 if self.func[stream].ty != Type::PTR {
793 return None;
794 }
795 let format = self.one(*args.get(1)?)?;
796 match args.len() {
797 2 => self.plain(&format, Some((args[1], stream)), quiet),
798 3 if format == b"%c" && !quiet && self.func[args[2]].ty == int() => {
799 self.call("fputc", vec![Argument::Have(args[2]), Argument::Have(stream)])
800 }
801 3 if format == b"%s" && self.func[args[2]].ty == Type::PTR => {
805 match self.strings(args[2], DEPTH) {
806 Some(candidates) => self.string(&candidates, args[2], stream, quiet),
807 None if quiet => None,
808 None => {
809 self.call("fputs", vec![Argument::Have(args[2]), Argument::Have(stream)])
810 }
811 }
812 }
813 _ => None,
814 }
815 }
816
817 fn fputs(&self, args: &[Value], quiet: bool) -> Option<Plan> {
819 if args.len() != 2 {
820 return None;
821 }
822 let (text, stream) = (args[0], args[1]);
823 if self.func[text].ty != Type::PTR || self.func[stream].ty != Type::PTR {
824 return None;
825 }
826 self.string(&self.strings(text, DEPTH)?, text, stream, quiet)
827 }
828
829 fn plain(&self, format: &[u8], stream: Option<(Value, Value)>, quiet: bool) -> Option<Plan> {
834 if format.is_empty() {
835 return Some(Plan::Drop);
836 }
837 if quiet || format.contains(&b'%') {
838 return None;
839 }
840 match (format, stream) {
841 ([one], Some((_, stream))) => {
842 self.call("fputc", vec![Argument::Char(*one), Argument::Have(stream)])
843 }
844 (_, Some((text, stream))) => self.fwrite(Argument::Have(text), format.len(), stream),
847 ([one], None) => self.call("putchar", vec![Argument::Char(*one)]),
848 (_, None) => {
851 let (&last, rest) = format.split_last()?;
852 match last {
853 b'\n' => self.call("puts", vec![Argument::Text(rest.to_vec())]),
854 _ => None,
855 }
856 }
857 }
858 }
859
860 fn string(
866 &self,
867 candidates: &[Vec<u8>],
868 text: Value,
869 stream: Value,
870 quiet: bool,
871 ) -> Option<Plan> {
872 let first = candidates.first()?;
873 if candidates.iter().any(|it| it.len() != first.len()) {
874 return None;
875 }
876 if first.is_empty() {
877 return Some(Plan::Drop);
878 }
879 if quiet {
880 return None;
881 }
882 match first.as_slice() {
883 [one] if candidates.iter().all(|it| it[0] == *one) => {
884 self.call("fputc", vec![Argument::Char(*one), Argument::Have(stream)])
885 }
886 _ => self.fwrite(Argument::Have(text), first.len(), stream),
891 }
892 }
893
894 fn fwrite(&self, text: Argument, bytes: usize, stream: Value) -> Option<Plan> {
896 let len = u64::try_from(bytes).ok()?;
897 self.call(
898 "fwrite",
899 vec![text, Argument::Count(1), Argument::Count(len), Argument::Have(stream)],
900 )
901 }
902
903 fn call(&self, callee: &'static str, args: Vec<Argument>) -> Option<Plan> {
905 let (callee, signature) = self.shapes.get(callee)?;
906 Some(Plan::Swap { callee, signature, args })
907 }
908
909 fn one(&self, value: Value) -> Option<Vec<u8>> {
911 let mut candidates = self.strings(value, DEPTH)?;
912 (candidates.len() == 1).then(|| candidates.pop()).flatten()
913 }
914
915 fn strings(&self, value: Value, depth: u32) -> Option<Vec<Vec<u8>>> {
923 if depth == 0 {
924 return None;
925 }
926 match self.func[value].def {
927 Def::Param { block, index } => {
928 let preds = self.cfg.predecessors(block);
929 if preds.is_empty() {
930 return None;
931 }
932 let mut all = Vec::new();
933 for &pred in preds {
934 let term = self.func.terminator(pred)?;
935 for call in self.func.successors(term).collect::<Vec<_>>() {
936 if call.block != block {
937 continue;
938 }
939 let arg = *self.func[call.args].get(index as usize)?;
940 all.extend(self.strings(arg, depth - 1)?);
941 }
942 }
943 (!all.is_empty()).then_some(all)
944 }
945 Def::Result { inst, .. } if self.func[inst].opcode == Opcode::Select => {
946 let args = &self.func[self.func[inst].args];
947 let (then, other) = (*args.get(1)?, *args.get(2)?);
948 let mut all = self.strings(then, depth - 1)?;
949 all.extend(self.strings(other, depth - 1)?);
950 Some(all)
951 }
952 _ => Some(vec![self.literal(value)?]),
953 }
954 }
955
956 fn literal(&self, value: Value) -> Option<Vec<u8>> {
959 let bytes = self.raw(value)?;
960 let end = bytes.iter().position(|&byte| byte == 0)?;
961 Some(bytes[..end].to_vec())
962 }
963
964 fn raw(&self, value: Value) -> Option<Vec<u8>> {
970 let (base, offset) = self.address(value)?;
971 let Def::Result { inst, .. } = self.func[base].def else { return None };
972 if self.func[inst].opcode != Opcode::GlobalAddr {
973 return None;
974 }
975 let Extra::Symbol(name) = self.func[inst].extra else { return None };
976 let Some(SymbolRef::Global(id)) = self.module.lookup(name) else { return None };
977 let global = &self.module[id];
978 if !global.constant || !vouched(global, self.pic) {
979 return None;
980 }
981 let mut bytes = Vec::new();
982 for &datum in &self.module[global.init?] {
983 match datum {
984 Datum::Bytes(range) => bytes.extend_from_slice(&self.module[range]),
985 Datum::Zero(count) => {
986 bytes.resize(bytes.len().checked_add(usize::try_from(count).ok()?)?, 0);
987 }
988 Datum::Scalar { .. } | Datum::Addr(_) | Datum::Away(_) => return None,
992 }
993 }
994 let size = usize::try_from(global.size).ok()?;
997 if bytes.len() < size {
998 bytes.resize(size, 0);
999 }
1000 Some(bytes.get(usize::try_from(offset).ok()?..)?.to_vec())
1001 }
1002
1003 fn address(&self, mut value: Value) -> Option<(Value, i128)> {
1009 let mut offset: i128 = 0;
1010 for _ in 0..DEPTH {
1011 let Def::Result { inst, .. } = self.func[value].def else {
1012 return Some((value, offset));
1013 };
1014 if self.func[inst].opcode != Opcode::PtrAdd {
1015 return Some((value, offset));
1016 }
1017 let args = &self.func[self.func[inst].args];
1018 offset = offset.checked_add(self.step(*args.get(1)?)?)?;
1019 value = *args.first()?;
1020 }
1021 None
1022 }
1023
1024 fn step(&self, mut value: Value) -> Option<i128> {
1032 for _ in 0..DEPTH {
1033 if let Some((imm, ty)) = crate::fold::constant(self.func, value) {
1034 return Some(imm.signed(ty));
1035 }
1036 let Def::Result { inst, .. } = self.func[value].def else { return None };
1037 match self.func[inst].opcode {
1038 Opcode::SExt => value = *self.func[self.func[inst].args].first()?,
1042 Opcode::ZExt => {
1043 let arg = *self.func[self.func[inst].args].first()?;
1044 let (imm, _) = crate::fold::constant(self.func, arg)?;
1045 return i128::try_from(imm.unsigned()).ok();
1046 }
1047 _ => return None,
1048 }
1049 }
1050 None
1051 }
1052}
1053
1054fn apply(
1056 module: &mut Module,
1057 id: FuncId,
1058 names: &mut Interner,
1059 texts: &mut HashMap<Vec<u8>, Symbol>,
1060 inst: Inst,
1061 plan: Plan,
1062) {
1063 let (callee, signature, args) = match plan {
1064 Plan::Drop => {
1065 module[id].remove_inst(inst);
1066 return;
1067 }
1068 Plan::Answer(answer) => {
1069 let width = size(module);
1070 answered(&mut module[id], inst, answer, width);
1071 return;
1072 }
1073 Plan::Swap { callee, signature, args } => (callee, signature, args),
1074 };
1075 let symbols: Vec<Option<Symbol>> = args
1078 .iter()
1079 .map(|arg| match arg {
1080 Argument::Text(bytes) => Some(object(module, names, texts, bytes)),
1081 _ => None,
1082 })
1083 .collect();
1084 let width = size(module);
1085 let func = &mut module[id];
1086 let span = func.span(inst);
1087 let mut values = Vec::with_capacity(args.len());
1088 for (arg, symbol) in args.iter().zip(symbols) {
1089 values.push(match arg {
1090 Argument::Have(value) => *value,
1091 Argument::Char(byte) => constant(func, inst, int(), i128::from(*byte)),
1092 Argument::Count(count) => constant(func, inst, width, i128::from(*count)),
1093 Argument::Text(_) => {
1094 let extra = Extra::Symbol(symbol.expect("a text argument has an object"));
1095 let data = InstData { extra, ..InstData::new(Opcode::GlobalAddr) };
1096 let made = func.create_inst(data, &[Type::PTR], span);
1097 func.insert_before(made, inst);
1098 func[made].results().next().expect("an address is one value")
1099 }
1100 });
1101 }
1102 let results: Vec<Type> = signature.return_types().collect();
1103 let sig = func.add_signature(signature);
1104 let varargs = func.push_abis(&[]);
1105 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
1106 let args = func.push_values(&values);
1107 let data = InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) };
1108 let made = func.create_inst(data, &results, span);
1109 func.insert_before(made, inst);
1110 let forward: HashMap<Value, Value> = func[inst]
1114 .results()
1115 .zip(func[made].results().collect::<Vec<Value>>())
1116 .filter(|&(from, to)| func[from].ty == func[to].ty)
1117 .collect();
1118 if !forward.is_empty() {
1119 uses::substitute(func, &forward);
1120 }
1121 func.remove_inst(inst);
1122}
1123
1124fn answered(func: &mut Func, inst: Inst, answer: Answer, width: Type) {
1126 let span = func.span(inst);
1127 let value = match answer {
1128 Answer::Along(haystack, 0) => haystack,
1131 Answer::Along(haystack, by) => {
1132 let step = constant(func, inst, width, i128::from(by));
1133 let args = func.push_values(&[haystack, step]);
1134 let data = InstData { args, ..InstData::new(Opcode::PtrAdd) };
1135 let made = func.create_inst(data, &[Type::PTR], span);
1136 func.insert_before(made, inst);
1137 func[made].results().next().expect("an address is one value")
1138 }
1139 Answer::Nowhere => {
1140 let zero = constant(func, inst, width, 0);
1141 let args = func.push_values(&[zero]);
1142 let data = InstData { args, ..InstData::new(Opcode::IntToPtr) };
1143 let made = func.create_inst(data, &[Type::PTR], span);
1144 func.insert_before(made, inst);
1145 func[made].results().next().expect("a null pointer is one value")
1146 }
1147 Answer::Number(number) => {
1148 let ty = func[inst]
1149 .results()
1150 .next()
1151 .map(|result| func[result].ty)
1152 .expect("a call whose answer is a number has one");
1153 constant(func, inst, ty, number)
1154 }
1155 };
1156 let forward: HashMap<Value, Value> =
1157 func[inst].results().map(|result| (result, value)).collect();
1158 uses::substitute(func, &forward);
1159 func.remove_inst(inst);
1160}
1161
1162fn at(haystack: &[u8], needle: &[u8]) -> Option<usize> {
1164 haystack.windows(needle.len()).position(|window| window == needle)
1165}
1166
1167fn constant(func: &mut Func, before: Inst, ty: Type, value: i128) -> Value {
1169 let span = func.span(before);
1170 let imm = func.add_imm(Imm::int(value, ty.lane()));
1171 let data = InstData { extra: Extra::Imm(imm), ..InstData::new(Opcode::IConst) };
1172 let made = func.create_inst(data, &[ty], span);
1173 func.insert_before(made, before);
1174 func[made].results().next().expect("a constant is one value")
1175}
1176
1177fn object(
1183 module: &mut Module,
1184 names: &mut Interner,
1185 texts: &mut HashMap<Vec<u8>, Symbol>,
1186 bytes: &[u8],
1187) -> Symbol {
1188 if let Some(&symbol) = texts.get(bytes) {
1189 return symbol;
1190 }
1191 let mut image = bytes.to_vec();
1192 image.push(0);
1193 let mut symbol = names.intern(&format!(".Lfold.{}", texts.len()));
1194 for next in texts.len().. {
1195 if module.lookup(symbol).is_none() {
1196 break;
1197 }
1198 symbol = names.intern(&format!(".Lfold.{}", next + 1));
1199 }
1200 let mut global = Global::new(symbol, image.len() as u64, 1);
1201 global.linkage = Linkage::Internal;
1202 global.constant = true;
1203 let range = module.push_bytes(&image);
1204 global.init = Some(module.push_data(&[Datum::Bytes(range)]));
1205 module.add_global(global);
1206 texts.insert(bytes.to_vec(), symbol);
1207 symbol
1208}
1209
1210#[cfg(test)]
1211mod tests {
1212 use super::*;
1213
1214 const HEAD: &str = "\
1216; ModuleID = 't.c'
1217; format 0
1218target triple = \"x86_64-unknown-linux-gnu\"
1219target datalayout = \"e-p:64:64-i64:64-f80:128-S128\"
1220";
1221
1222 fn folded(body: &str) -> String {
1228 run(body, &[], &mut Fuel::unlimited())
1229 }
1230
1231 fn run(body: &str, no_builtin: &[String], fuel: &mut Fuel) -> String {
1233 let mut names = Interner::new();
1234 let text = format!("{HEAD}{body}");
1235 let mut module = rucc_ir::parse(&text, &mut names).expect("the fixture parses");
1236 fold(&mut module, &mut names, no_builtin, Pic::Executable, fuel);
1237 if let Err(errors) = rucc_ir::verify(&module, &names) {
1238 panic!("the fold left invalid IR, {errors:?}\n{}", rucc_ir::print(&module, &names));
1239 }
1240 rucc_ir::print(&module, &names)
1241 }
1242
1243 #[test]
1249 fn a_format_that_ends_in_a_newline_is_written_by_puts() {
1250 let out = folded(
1251 r#"
1252global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
1253
1254func @printf(ptr, ...) -> i32, linkage(external);
1255
1256func @g(), linkage(external) {
1257block0:
1258 %0 = global_addr @.Lstr.0
1259 %1 = call @printf(%0) : (ptr, ...) -> i32
1260 return
1261}
1262"#,
1263 );
1264 assert!(out.contains("call @puts("), "{out}");
1265 assert!(!out.contains("call @printf("), "{out}");
1266 assert!(out.contains(r#"@.Lfold.0 : bytes 12 = { bytes "hello world\00" }"#), "{out}");
1267 }
1268
1269 #[test]
1271 fn a_short_format_is_written_by_putchar_or_by_nothing() {
1272 let out = folded(
1273 r#"
1274global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
1275global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1276
1277func @printf(ptr, ...) -> i32, linkage(external);
1278
1279func @g(), linkage(external) {
1280block0:
1281 %0 = global_addr @.Lstr.0
1282 %1 = call @printf(%0) : (ptr, ...) -> i32
1283 %2 = global_addr @.Lstr.1
1284 %3 = call @printf(%2) : (ptr, ...) -> i32
1285 return
1286}
1287"#,
1288 );
1289 assert!(out.contains("iconst.i32 120"), "the character is the argument, {out}");
1290 assert!(out.contains("call @putchar("), "{out}");
1291 assert_eq!(out.matches("call @").count(), 1, "the empty one is gone, {out}");
1292 }
1293
1294 #[test]
1297 fn the_two_formats_that_are_a_call_on_their_own_are_folded_for_any_argument() {
1298 let out = folded(
1299 r#"
1300global @.Lstr.0 : bytes 4 = { bytes "%s\0a\00" }, align 1, linkage(internal), constant
1301global @.Lstr.1 : bytes 3 = { bytes "%c\00" }, align 1, linkage(internal), constant
1302
1303func @printf(ptr, ...) -> i32, linkage(external);
1304
1305func @g(ptr, i32), linkage(external) {
1306block0(%0: ptr, %1: i32):
1307 %2 = global_addr @.Lstr.0
1308 %3 = call @printf(%2, %0) : (ptr, ...) -> i32
1309 %4 = global_addr @.Lstr.1
1310 %5 = call @printf(%4, %1) : (ptr, ...) -> i32
1311 return
1312}
1313"#,
1314 );
1315 assert!(out.contains("call @puts(%0)"), "{out}");
1316 assert!(out.contains("call @putchar(%1)"), "{out}");
1317 }
1318
1319 #[test]
1324 fn a_string_argument_nothing_is_known_about_is_left_to_printf() {
1325 let out = folded(
1326 r#"
1327global @.Lstr.0 : bytes 3 = { bytes "%s\00" }, align 1, linkage(internal), constant
1328
1329func @printf(ptr, ...) -> i32, linkage(external);
1330
1331func @g(ptr), linkage(external) {
1332block0(%0: ptr):
1333 %1 = global_addr @.Lstr.0
1334 %2 = call @printf(%1, %0) : (ptr, ...) -> i32
1335 return
1336}
1337"#,
1338 );
1339 assert!(out.contains("call @printf("), "{out}");
1340 }
1341
1342 #[test]
1347 fn a_stream_takes_the_whole_format_through_fwrite() {
1348 let out = folded(
1349 r#"
1350global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
1351global @.Lstr.1 : bytes 2 = { bytes "q\00" }, align 1, linkage(internal), constant
1352
1353func @fprintf(ptr, ptr, ...) -> i32, linkage(external);
1354
1355func @g(ptr), linkage(external) {
1356block0(%0: ptr):
1357 %1 = global_addr @.Lstr.0
1358 %2 = call @fprintf(%0, %1) : (ptr, ptr, ...) -> i32
1359 %3 = global_addr @.Lstr.1
1360 %4 = call @fprintf(%0, %3) : (ptr, ptr, ...) -> i32
1361 return
1362}
1363"#,
1364 );
1365 assert!(out.contains("call @fwrite("), "{out}");
1366 assert!(out.contains("iconst.i64 12"), "the whole format, newline and all, {out}");
1367 assert!(out.contains("call @fputc("), "{out}");
1368 assert!(!out.contains("call @fprintf("), "{out}");
1369 }
1370
1371 #[test]
1374 fn a_string_argument_with_a_stream_beside_it_becomes_fputs() {
1375 let out = folded(
1376 r#"
1377global @.Lstr.0 : bytes 3 = { bytes "%s\00" }, align 1, linkage(internal), constant
1378
1379func @fprintf(ptr, ptr, ...) -> i32, linkage(external);
1380
1381func @g(ptr, ptr), linkage(external) {
1382block0(%0: ptr, %1: ptr):
1383 %2 = global_addr @.Lstr.0
1384 %3 = call @fprintf(%0, %2, %1) : (ptr, ptr, ...) -> i32
1385 return
1386}
1387"#,
1388 );
1389 assert!(out.contains("call @fputs(%1, %0)"), "{out}");
1390 }
1391
1392 #[test]
1395 fn fputs_of_a_string_this_module_holds_is_folded_by_its_length() {
1396 let out = folded(
1397 r#"
1398global @.Lstr.0 : bytes 7 = { bytes "abcdef\00" }, align 1, linkage(internal), constant
1399global @.Lstr.1 : bytes 2 = { bytes "z\00" }, align 1, linkage(internal), constant
1400global @.Lstr.2 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1401
1402func @fputs(ptr, ptr) -> i32, linkage(external);
1403
1404func @g(ptr), linkage(external) {
1405block0(%0: ptr):
1406 %1 = global_addr @.Lstr.0
1407 %2 = call @fputs(%1, %0) : (ptr, ptr) -> i32
1408 %3 = global_addr @.Lstr.1
1409 %4 = call @fputs(%3, %0) : (ptr, ptr) -> i32
1410 %5 = global_addr @.Lstr.2
1411 %6 = call @fputs(%5, %0) : (ptr, ptr) -> i32
1412 return
1413}
1414"#,
1415 );
1416 assert!(out.contains("call @fwrite("), "{out}");
1417 assert!(out.contains("iconst.i64 6"), "{out}");
1418 assert!(out.contains("iconst.i32 122"), "{out}");
1419 assert!(out.contains("call @fputc("), "{out}");
1420 assert!(!out.contains("call @fputs("), "the empty one is gone too, {out}");
1421 }
1422
1423 #[test]
1429 fn an_index_into_a_literal_is_a_string_of_its_own() {
1430 let out = folded(
1431 r#"
1432global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1433
1434func @fputs(ptr, ptr) -> i32, linkage(external);
1435
1436func @g(ptr), linkage(external) {
1437block0(%0: ptr):
1438 %1 = global_addr @.Lstr.0
1439 %2 = iconst.i32 6
1440 %3 = sext.i64 %2
1441 %4 = ptr_add %1, %3
1442 %5 = call @fputs(%4, %0) : (ptr, ptr) -> i32
1443 %6 = iconst.i32 11
1444 %7 = sext.i64 %6
1445 %8 = ptr_add %1, %7
1446 %9 = call @fputs(%8, %0) : (ptr, ptr) -> i32
1447 return
1448}
1449"#,
1450 );
1451 assert!(out.contains("iconst.i64 5"), "world without its terminator, {out}");
1452 assert!(out.contains("call @fwrite("), "{out}");
1453 assert!(!out.contains("call @fputs("), "and the terminator itself is nothing, {out}");
1454 }
1455
1456 #[test]
1463 fn a_choice_between_two_literals_is_folded_when_they_are_the_same_length() {
1464 let text = r#"
1465global @.Lstr.0 : bytes 2 = { bytes "f\00" }, align 1, linkage(internal), constant
1466global @.Lstr.1 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
1467global @.Lstr.2 : bytes 4 = { bytes "abc\00" }, align 1, linkage(internal), constant
1468
1469func @fputs(ptr, ptr) -> i32, linkage(external);
1470
1471func @g(ptr, i1), linkage(external) {
1472block0(%0: ptr, %1: i1):
1473 %2 = global_addr @.LEFT
1474 %3 = global_addr @.Lstr.1
1475 br_if %1, block1(%2), block1(%3)
1476block1(%4: ptr):
1477 %5 = call @fputs(%4, %0) : (ptr, ptr) -> i32
1478 return
1479}
1480"#;
1481 let same = folded(&text.replace(".LEFT", ".Lstr.0"));
1482 assert!(same.contains("call @fwrite("), "{same}");
1483 assert!(same.contains("iconst.i64 1"), "{same}");
1484
1485 let differing = folded(&text.replace(".LEFT", ".Lstr.2"));
1486 assert!(differing.contains("call @fputs("), "{differing}");
1487 }
1488
1489 #[test]
1494 fn a_call_whose_answer_is_read_is_not_folded() {
1495 let out = folded(
1496 r#"
1497global @n : bytes 4 = { zero 4 }, align 4, linkage(external)
1498global @.Lstr.0 : bytes 3 = { bytes "a\0a\00" }, align 1, linkage(internal), constant
1499
1500func @printf(ptr, ...) -> i32, linkage(external);
1501
1502func @g(), linkage(external) {
1503block0:
1504 %0 = global_addr @.Lstr.0
1505 %1 = call @printf(%0) : (ptr, ...) -> i32
1506 %2 = global_addr @n
1507 store %1 -> %2, align 4
1508 return
1509}
1510"#,
1511 );
1512 assert!(out.contains("call @printf("), "{out}");
1513 }
1514
1515 #[test]
1517 fn a_name_this_module_defines_is_that_definition() {
1518 let out = folded(
1519 r#"
1520global @.Lstr.0 : bytes 3 = { bytes "a\0a\00" }, align 1, linkage(internal), constant
1521
1522func @printf(ptr, ...) -> i32, linkage(external) {
1523block0(%0: ptr):
1524 %1 = iconst.i32 0
1525 return %1
1526}
1527
1528func @g(), linkage(external) {
1529block0:
1530 %0 = global_addr @.Lstr.0
1531 %1 = call @printf(%0) : (ptr, ...) -> i32
1532 return
1533}
1534"#,
1535 );
1536 assert!(out.contains("call @printf("), "{out}");
1537 }
1538
1539 #[test]
1545 fn a_declaration_of_another_shape_stops_the_fold() {
1546 let out = folded(
1547 r#"
1548global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
1549
1550func @printf(ptr, ...) -> i32, linkage(external);
1551func @puts(ptr, i32) -> i32, linkage(external);
1552
1553func @g(), linkage(external) {
1554block0:
1555 %0 = global_addr @.Lstr.0
1556 %1 = call @printf(%0) : (ptr, ...) -> i32
1557 return
1558}
1559"#,
1560 );
1561 assert!(out.contains("call @printf("), "{out}");
1562 assert!(!out.contains("@.Lfold."), "and no object was left behind either, {out}");
1563 }
1564
1565 #[test]
1568 fn a_variable_by_the_name_of_a_replacement_stops_the_fold() {
1569 let out = folded(
1570 r#"
1571global @putchar : bytes 4 = { zero 4 }, align 4, linkage(external)
1572global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
1573
1574func @printf(ptr, ...) -> i32, linkage(external);
1575
1576func @g(), linkage(external) {
1577block0:
1578 %0 = global_addr @.Lstr.0
1579 %1 = call @printf(%0) : (ptr, ...) -> i32
1580 return
1581}
1582"#,
1583 );
1584 assert!(out.contains("call @printf("), "{out}");
1585 }
1586
1587 #[test]
1592 fn the_unlocked_spellings_are_only_removed_when_they_write_nothing() {
1593 let out = folded(
1594 r#"
1595global @.Lstr.0 : bytes 13 = { bytes "hello world\0a\00" }, align 1, linkage(internal), constant
1596global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1597
1598func @printf_unlocked(ptr, ...) -> i32, linkage(external);
1599
1600func @g(), linkage(external) {
1601block0:
1602 %0 = global_addr @.Lstr.0
1603 %1 = call @printf_unlocked(%0) : (ptr, ...) -> i32
1604 %2 = global_addr @.Lstr.1
1605 %3 = call @printf_unlocked(%2) : (ptr, ...) -> i32
1606 return
1607}
1608"#,
1609 );
1610 assert_eq!(out.matches("call @printf_unlocked(").count(), 1, "{out}");
1611 assert!(!out.contains("call @puts("), "{out}");
1612 }
1613
1614 #[test]
1616 fn one_name_can_be_taken_away_without_taking_the_family_away() {
1617 let body = r#"
1618global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
1619
1620func @printf(ptr, ...) -> i32, linkage(external);
1621func @fputs(ptr, ptr) -> i32, linkage(external);
1622
1623func @g(ptr), linkage(external) {
1624block0(%0: ptr):
1625 %1 = global_addr @.Lstr.0
1626 %2 = call @printf(%1) : (ptr, ...) -> i32
1627 %3 = call @fputs(%1, %0) : (ptr, ptr) -> i32
1628 return
1629}
1630"#;
1631 let out = run(body, &["printf".to_owned()], &mut Fuel::unlimited());
1632 assert!(out.contains("call @printf("), "{out}");
1633 assert!(out.contains("call @fputc("), "and the other one still folded, {out}");
1634 }
1635
1636 #[test]
1639 fn a_run_out_of_fuel_transforms_nothing() {
1640 let body = r#"
1641global @.Lstr.0 : bytes 2 = { bytes "x\00" }, align 1, linkage(internal), constant
1642
1643func @printf(ptr, ...) -> i32, linkage(external);
1644
1645func @g(), linkage(external) {
1646block0:
1647 %0 = global_addr @.Lstr.0
1648 %1 = call @printf(%0) : (ptr, ...) -> i32
1649 return
1650}
1651"#;
1652 let mut fuel = Fuel::of(0);
1653 let out = run(body, &[], &mut fuel);
1654 assert!(out.contains("call @printf("), "{out}");
1655 assert_eq!(fuel.spent(), 0);
1656 }
1657
1658 #[test]
1660 fn one_object_serves_every_call_that_prints_the_same_thing() {
1661 let out = folded(
1662 r#"
1663global @.Lstr.0 : bytes 4 = { bytes "hi\0a\00" }, align 1, linkage(internal), constant
1664
1665func @printf(ptr, ...) -> i32, linkage(external);
1666
1667func @g(), linkage(external) {
1668block0:
1669 %0 = global_addr @.Lstr.0
1670 %1 = call @printf(%0) : (ptr, ...) -> i32
1671 %2 = call @printf(%0) : (ptr, ...) -> i32
1672 return
1673}
1674
1675func @h(), linkage(external) {
1676block0:
1677 %0 = global_addr @.Lstr.0
1678 %1 = call @printf(%0) : (ptr, ...) -> i32
1679 return
1680}
1681"#,
1682 );
1683 assert_eq!(out.matches("@.Lfold.0 : bytes").count(), 1, "{out}");
1684 assert!(!out.contains("@.Lfold.1"), "{out}");
1685 assert_eq!(out.matches("call @puts(").count(), 3, "{out}");
1686 }
1687
1688 #[test]
1693 fn a_search_for_nothing_answers_with_the_haystack_itself() {
1694 let out = folded(
1695 r#"
1696global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1697
1698func @strstr(ptr, ptr) -> ptr, linkage(external);
1699
1700func @g(ptr) -> ptr, linkage(external) {
1701block0(%0: ptr):
1702 %1 = global_addr @.Lstr.0
1703 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1704 return %2
1705}
1706"#,
1707 );
1708 assert!(!out.contains("call @strstr("), "{out}");
1709 assert!(out.contains("return %0"), "{out}");
1710 }
1711
1712 #[test]
1714 fn two_strings_this_module_holds_answer_without_a_call() {
1715 let out = folded(
1716 r#"
1717global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1718global @.Lstr.1 : bytes 2 = { bytes "w\00" }, align 1, linkage(internal), constant
1719global @.Lstr.2 : bytes 3 = { bytes "zz\00" }, align 1, linkage(internal), constant
1720
1721func @strstr(ptr, ptr) -> ptr, linkage(external);
1722func @use(ptr, ptr), linkage(external);
1723
1724func @g(), linkage(external) {
1725block0:
1726 %0 = global_addr @.Lstr.0
1727 %1 = global_addr @.Lstr.1
1728 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1729 %3 = global_addr @.Lstr.2
1730 %4 = call @strstr(%0, %3) : (ptr, ptr) -> ptr
1731 call @use(%2, %4) : (ptr, ptr)
1732 return
1733}
1734"#,
1735 );
1736 assert!(!out.contains("call @strstr("), "{out}");
1737 assert!(out.contains("ptr_add %0, "), "the w is six bytes along, {out}");
1738 assert!(out.contains("iconst.i64 6"), "{out}");
1739 assert!(out.contains("inttoptr"), "and the zz is nowhere in it, {out}");
1740 }
1741
1742 #[test]
1744 fn a_one_character_needle_becomes_a_search_for_that_character() {
1745 let out = folded(
1746 r#"
1747global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
1748
1749func @strstr(ptr, ptr) -> ptr, linkage(external);
1750
1751func @g(ptr) -> ptr, linkage(external) {
1752block0(%0: ptr):
1753 %1 = global_addr @.Lstr.0
1754 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1755 return %2
1756}
1757"#,
1758 );
1759 assert!(!out.contains("call @strstr("), "{out}");
1760 assert!(out.contains("call @strchr(%0, "), "{out}");
1761 assert!(out.contains("iconst.i32 111"), "{out}");
1762 }
1763
1764 #[test]
1766 fn a_strchr_of_another_shape_is_not_the_one_to_call() {
1767 let out = folded(
1768 r#"
1769global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
1770
1771func @strstr(ptr, ptr) -> ptr, linkage(external);
1772func @strchr(ptr, ptr) -> ptr, linkage(external);
1773
1774func @g(ptr) -> ptr, linkage(external) {
1775block0(%0: ptr):
1776 %1 = global_addr @.Lstr.0
1777 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1778 return %2
1779}
1780"#,
1781 );
1782 assert!(out.contains("call @strstr("), "{out}");
1783 }
1784
1785 #[test]
1790 fn a_renamed_declaration_is_still_the_function_it_was_spelled() {
1791 let out = folded(
1792 r#"
1793global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1794
1795func @my_strstr(ptr, ptr) -> ptr, linkage(external), spelled "strstr";
1796
1797func @g(ptr) -> ptr, linkage(external) {
1798block0(%0: ptr):
1799 %1 = global_addr @.Lstr.0
1800 %2 = call @my_strstr(%0, %1) : (ptr, ptr) -> ptr
1801 return %2
1802}
1803"#,
1804 );
1805 assert!(!out.contains("call @my_strstr("), "{out}");
1806 assert!(out.contains("return %0"), "{out}");
1807 }
1808
1809 #[test]
1811 fn a_renamed_replacement_is_called_by_the_symbol_the_rename_asked_for() {
1812 let out = folded(
1813 r#"
1814global @.Lstr.0 : bytes 2 = { bytes "o\00" }, align 1, linkage(internal), constant
1815
1816func @strstr(ptr, ptr) -> ptr, linkage(external);
1817func @my_strchr(ptr, i32) -> ptr, linkage(external), spelled "strchr";
1818
1819func @g(ptr) -> ptr, linkage(external) {
1820block0(%0: ptr):
1821 %1 = global_addr @.Lstr.0
1822 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1823 return %2
1824}
1825"#,
1826 );
1827 assert!(out.contains("call @my_strchr(%0, "), "{out}");
1828 assert!(!out.contains("call @strchr("), "{out}");
1829 }
1830
1831 #[test]
1833 fn a_strstr_taken_away_is_a_call_like_any_other() {
1834 let body = r#"
1835global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
1836
1837func @strstr(ptr, ptr) -> ptr, linkage(external);
1838
1839func @g(ptr) -> ptr, linkage(external) {
1840block0(%0: ptr):
1841 %1 = global_addr @.Lstr.0
1842 %2 = call @strstr(%0, %1) : (ptr, ptr) -> ptr
1843 return %2
1844}
1845"#;
1846 let out = run(body, &["strstr".to_owned()], &mut Fuel::unlimited());
1847 assert!(out.contains("call @strstr("), "{out}");
1848 }
1849
1850 #[test]
1852 fn strlen_of_a_string_this_module_holds_is_a_number() {
1853 let out = folded(
1854 r#"
1855global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1856
1857func @strlen(ptr) -> i64, linkage(external);
1858func @use(i64, i64), linkage(external);
1859
1860func @g(), linkage(external) {
1861block0:
1862 %0 = global_addr @.Lstr.0
1863 %1 = call @strlen(%0) : (ptr) -> i64
1864 %2 = iconst.i64 6
1865 %3 = ptr_add %0, %2
1866 %4 = call @strlen(%3) : (ptr) -> i64
1867 call @use(%1, %4) : (i64, i64)
1868 return
1869}
1870"#,
1871 );
1872 assert!(!out.contains("call @strlen("), "{out}");
1873 assert!(out.contains("iconst.i64 11"), "{out}");
1874 assert!(out.contains("iconst.i64 5"), "the world on its own, {out}");
1875 }
1876
1877 #[test]
1880 fn strnlen_answers_the_count_where_the_string_runs_past_it() {
1881 let out = folded(
1882 r#"
1883global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1884
1885func @strnlen(ptr, i64) -> i64, linkage(external);
1886func @use(i64, i64), linkage(external);
1887
1888func @g(), linkage(external) {
1889block0:
1890 %0 = global_addr @.Lstr.0
1891 %1 = iconst.i64 3
1892 %2 = call @strnlen(%0, %1) : (ptr, i64) -> i64
1893 %3 = iconst.i64 40
1894 %4 = call @strnlen(%0, %3) : (ptr, i64) -> i64
1895 call @use(%2, %4) : (i64, i64)
1896 return
1897}
1898"#,
1899 );
1900 assert!(!out.contains("call @strnlen("), "{out}");
1901 assert!(out.contains("iconst.i64 3"), "the count came first, {out}");
1902 assert!(out.contains("iconst.i64 11"), "the terminator came first, {out}");
1903 }
1904
1905 #[test]
1908 fn a_count_that_was_widened_on_the_way_in_is_still_a_count() {
1909 let out = folded(
1910 r#"
1911global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1912
1913func @strnlen(ptr, i64) -> i64, linkage(external);
1914
1915func @g() -> i64, linkage(external) {
1916block0:
1917 %0 = global_addr @.Lstr.0
1918 %1 = iconst.i32 4
1919 %2 = sext.i64 %1
1920 %3 = call @strnlen(%0, %2) : (ptr, i64) -> i64
1921 return %3
1922}
1923"#,
1924 );
1925 assert!(!out.contains("call @strnlen("), "{out}");
1926 assert!(out.contains("iconst.i64 4"), "{out}");
1927 }
1928
1929 #[test]
1932 fn memchr_searches_the_object_rather_than_the_string_in_it() {
1933 let out = folded(
1934 r#"
1935global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1936
1937func @memchr(ptr, i32, i64) -> ptr, linkage(external);
1938func @use(ptr, ptr), linkage(external);
1939
1940func @g(), linkage(external) {
1941block0:
1942 %0 = global_addr @.Lstr.0
1943 %1 = iconst.i32 0
1944 %2 = iconst.i64 12
1945 %3 = call @memchr(%0, %1, %2) : (ptr, i32, i64) -> ptr
1946 %4 = iconst.i32 100
1947 %5 = iconst.i64 10
1948 %6 = call @memchr(%0, %4, %5) : (ptr, i32, i64) -> ptr
1949 call @use(%3, %6) : (ptr, ptr)
1950 return
1951}
1952"#,
1953 );
1954 assert!(!out.contains("call @memchr("), "{out}");
1955 assert!(out.contains("iconst.i64 11"), "the terminator is inside the count, {out}");
1956 assert!(out.contains("inttoptr.ptr "), "the d is one byte past the count, {out}");
1957 }
1958
1959 #[test]
1962 fn a_memchr_that_runs_off_the_object_is_left_alone() {
1963 let out = folded(
1964 r#"
1965global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1966
1967func @memchr(ptr, i32, i64) -> ptr, linkage(external);
1968
1969func @g() -> ptr, linkage(external) {
1970block0:
1971 %0 = global_addr @.Lstr.0
1972 %1 = iconst.i32 122
1973 %2 = iconst.i64 13
1974 %3 = call @memchr(%0, %1, %2) : (ptr, i32, i64) -> ptr
1975 return %3
1976}
1977"#,
1978 );
1979 assert!(out.contains("call @memchr("), "{out}");
1980 }
1981
1982 #[test]
1985 fn the_two_character_searches_answer_from_either_end() {
1986 let out = folded(
1987 r#"
1988global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
1989
1990func @strchr(ptr, i32) -> ptr, linkage(external);
1991func @strrchr(ptr, i32) -> ptr, linkage(external);
1992func @use(ptr, ptr, ptr, ptr), linkage(external);
1993
1994func @g(), linkage(external) {
1995block0:
1996 %0 = global_addr @.Lstr.0
1997 %1 = iconst.i32 111
1998 %2 = call @strchr(%0, %1) : (ptr, i32) -> ptr
1999 %3 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
2000 %4 = iconst.i32 0
2001 %5 = call @strchr(%0, %4) : (ptr, i32) -> ptr
2002 %6 = iconst.i32 122
2003 %7 = call @strchr(%0, %6) : (ptr, i32) -> ptr
2004 call @use(%2, %3, %5, %7) : (ptr, ptr, ptr, ptr)
2005 return
2006}
2007"#,
2008 );
2009 assert!(!out.contains("call @strchr("), "{out}");
2010 assert!(!out.contains("call @strrchr("), "{out}");
2011 assert!(out.contains("iconst.i64 4"), "the first o, {out}");
2012 assert!(out.contains("iconst.i64 7"), "the last o, {out}");
2013 assert!(out.contains("iconst.i64 11"), "the terminator, {out}");
2014 assert!(out.contains("inttoptr.ptr "), "there is no z in it, {out}");
2015 }
2016
2017 #[test]
2020 fn the_two_comparisons_answer_a_sign() {
2021 let out = folded(
2022 r#"
2023global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2024global @.Lstr.1 : bytes 6 = { bytes "hello\00" }, align 1, linkage(internal), constant
2025
2026func @strcmp(ptr, ptr) -> i32, linkage(external);
2027func @strncmp(ptr, ptr, i64) -> i32, linkage(external);
2028func @use(i32, i32, i32), linkage(external);
2029
2030func @g(), linkage(external) {
2031block0:
2032 %0 = global_addr @.Lstr.0
2033 %1 = global_addr @.Lstr.1
2034 %2 = call @strcmp(%0, %1) : (ptr, ptr) -> i32
2035 %3 = call @strcmp(%1, %0) : (ptr, ptr) -> i32
2036 %4 = iconst.i64 5
2037 %5 = call @strncmp(%0, %1, %4) : (ptr, ptr, i64) -> i32
2038 call @use(%2, %3, %5) : (i32, i32, i32)
2039 return
2040}
2041"#,
2042 );
2043 assert!(!out.contains("call @strcmp("), "{out}");
2044 assert!(!out.contains("call @strncmp("), "{out}");
2045 assert!(out.contains("iconst.i32 1"), "the longer one is the greater, {out}");
2046 assert!(out.contains("iconst.i32 -1"), "and the other way round, {out}");
2047 assert!(out.contains("iconst.i32 0"), "five bytes of each are the same, {out}");
2048 }
2049
2050 #[test]
2052 fn the_two_spans_are_one_walk_each_way() {
2053 let out = folded(
2054 r#"
2055global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2056global @.Lstr.1 : bytes 4 = { bytes "hel\00" }, align 1, linkage(internal), constant
2057global @.Lstr.2 : bytes 3 = { bytes "wz\00" }, align 1, linkage(internal), constant
2058
2059func @strspn(ptr, ptr) -> i64, linkage(external);
2060func @strcspn(ptr, ptr) -> i64, linkage(external);
2061func @use(i64, i64), linkage(external);
2062
2063func @g(), linkage(external) {
2064block0:
2065 %0 = global_addr @.Lstr.0
2066 %1 = global_addr @.Lstr.1
2067 %2 = call @strspn(%0, %1) : (ptr, ptr) -> i64
2068 %3 = global_addr @.Lstr.2
2069 %4 = call @strcspn(%0, %3) : (ptr, ptr) -> i64
2070 call @use(%2, %4) : (i64, i64)
2071 return
2072}
2073"#,
2074 );
2075 assert!(!out.contains("call @strspn("), "{out}");
2076 assert!(!out.contains("call @strcspn("), "{out}");
2077 assert!(out.contains("iconst.i64 4"), "hello stops at the o, {out}");
2078 assert!(out.contains("iconst.i64 6"), "the w is six bytes along, {out}");
2079 }
2080
2081 #[test]
2084 fn an_empty_set_is_a_span_of_nothing_or_of_all_of_it() {
2085 let out = folded(
2086 r#"
2087global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2088
2089func @strspn(ptr, ptr) -> i64, linkage(external);
2090func @strcspn(ptr, ptr) -> i64, linkage(external);
2091func @strlen(ptr) -> i64, linkage(external);
2092func @use(i64, i64), linkage(external);
2093
2094func @g(ptr), linkage(external) {
2095block0(%0: ptr):
2096 %1 = global_addr @.Lstr.0
2097 %2 = call @strspn(%0, %1) : (ptr, ptr) -> i64
2098 %3 = call @strcspn(%0, %1) : (ptr, ptr) -> i64
2099 call @use(%2, %3) : (i64, i64)
2100 return
2101}
2102"#,
2103 );
2104 assert!(!out.contains("call @strspn("), "{out}");
2105 assert!(!out.contains("call @strcspn("), "{out}");
2106 assert!(out.contains("call @strlen(%0)"), "{out}");
2107 assert!(out.contains("iconst.i64 0"), "{out}");
2108 }
2109
2110 #[test]
2113 fn a_strcspn_of_another_width_than_strlen_is_left_alone() {
2114 let out = folded(
2115 r#"
2116global @.Lstr.0 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2117
2118func @strcspn(ptr, ptr) -> i32, linkage(external);
2119func @strlen(ptr) -> i64, linkage(external);
2120
2121func @g(ptr) -> i32, linkage(external) {
2122block0(%0: ptr):
2123 %1 = global_addr @.Lstr.0
2124 %2 = call @strcspn(%0, %1) : (ptr, ptr) -> i32
2125 return %2
2126}
2127"#,
2128 );
2129 assert!(out.contains("call @strcspn("), "{out}");
2130 }
2131
2132 #[test]
2135 fn strpbrk_of_a_short_set_is_a_search_or_an_answer() {
2136 let out = folded(
2137 r#"
2138global @.Lstr.0 : bytes 2 = { bytes "w\00" }, align 1, linkage(internal), constant
2139global @.Lstr.1 : bytes 1 = { bytes "\00" }, align 1, linkage(internal), constant
2140
2141func @strpbrk(ptr, ptr) -> ptr, linkage(external);
2142func @strchr(ptr, i32) -> ptr, linkage(external);
2143func @use(ptr, ptr), linkage(external);
2144
2145func @g(ptr), linkage(external) {
2146block0(%0: ptr):
2147 %1 = global_addr @.Lstr.0
2148 %2 = call @strpbrk(%0, %1) : (ptr, ptr) -> ptr
2149 %3 = global_addr @.Lstr.1
2150 %4 = call @strpbrk(%0, %3) : (ptr, ptr) -> ptr
2151 call @use(%2, %4) : (ptr, ptr)
2152 return
2153}
2154"#,
2155 );
2156 assert!(!out.contains("call @strpbrk("), "{out}");
2157 assert!(out.contains("call @strchr(%0, "), "{out}");
2158 assert!(out.contains("iconst.i32 119"), "{out}");
2159 assert!(out.contains("inttoptr.ptr "), "the empty set is nowhere, {out}");
2160 }
2161
2162 #[test]
2164 fn strpbrk_over_two_strings_this_module_holds_is_a_place() {
2165 let out = folded(
2166 r#"
2167global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2168global @.Lstr.1 : bytes 3 = { bytes "wz\00" }, align 1, linkage(internal), constant
2169global @.Lstr.2 : bytes 3 = { bytes "qz\00" }, align 1, linkage(internal), constant
2170
2171func @strpbrk(ptr, ptr) -> ptr, linkage(external);
2172func @use(ptr, ptr), linkage(external);
2173
2174func @g(), linkage(external) {
2175block0:
2176 %0 = global_addr @.Lstr.0
2177 %1 = global_addr @.Lstr.1
2178 %2 = call @strpbrk(%0, %1) : (ptr, ptr) -> ptr
2179 %3 = global_addr @.Lstr.2
2180 %4 = call @strpbrk(%0, %3) : (ptr, ptr) -> ptr
2181 call @use(%2, %4) : (ptr, ptr)
2182 return
2183}
2184"#,
2185 );
2186 assert!(!out.contains("call @strpbrk("), "{out}");
2187 assert!(out.contains("iconst.i64 6"), "the w is six bytes along, {out}");
2188 assert!(out.contains("inttoptr.ptr "), "there is neither a q nor a z in it, {out}");
2189 }
2190
2191 #[test]
2193 fn the_older_spellings_of_the_two_searches_are_folded_as_well() {
2194 let out = folded(
2195 r#"
2196global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2197
2198func @index(ptr, i32) -> ptr, linkage(external);
2199func @rindex(ptr, i32) -> ptr, linkage(external);
2200func @use(ptr, ptr), linkage(external);
2201
2202func @g(), linkage(external) {
2203block0:
2204 %0 = global_addr @.Lstr.0
2205 %1 = iconst.i32 111
2206 %2 = call @index(%0, %1) : (ptr, i32) -> ptr
2207 %3 = call @rindex(%0, %1) : (ptr, i32) -> ptr
2208 call @use(%2, %3) : (ptr, ptr)
2209 return
2210}
2211"#,
2212 );
2213 assert!(!out.contains("call @index("), "{out}");
2214 assert!(!out.contains("call @rindex("), "{out}");
2215 assert!(out.contains("iconst.i64 4"), "the first o, {out}");
2216 assert!(out.contains("iconst.i64 7"), "the last o, {out}");
2217 }
2218
2219 #[test]
2222 fn a_strrchr_of_the_terminator_is_a_strchr_of_it() {
2223 let out = folded(
2224 r#"
2225func @strrchr(ptr, i32) -> ptr, linkage(external);
2226
2227func @g(ptr) -> ptr, linkage(external) {
2228block0(%0: ptr):
2229 %1 = iconst.i32 0
2230 %2 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
2231 return %2
2232}
2233"#,
2234 );
2235 assert!(!out.contains("call @strrchr("), "{out}");
2236 assert!(out.contains("call @strchr(%0, "), "{out}");
2237 }
2238
2239 #[test]
2242 fn a_strrchr_of_another_character_needs_the_string() {
2243 let out = folded(
2244 r#"
2245func @strrchr(ptr, i32) -> ptr, linkage(external);
2246
2247func @g(ptr) -> ptr, linkage(external) {
2248block0(%0: ptr):
2249 %1 = iconst.i32 111
2250 %2 = call @strrchr(%0, %1) : (ptr, i32) -> ptr
2251 return %2
2252}
2253"#,
2254 );
2255 assert!(out.contains("call @strrchr("), "{out}");
2256 }
2257
2258 #[test]
2260 fn a_strlen_that_answers_nothing_is_not_the_one_the_library_has() {
2261 let out = folded(
2262 r#"
2263global @.Lstr.0 : bytes 12 = { bytes "hello world\00" }, align 1, linkage(internal), constant
2264
2265func @strlen(ptr), linkage(external);
2266
2267func @g(), linkage(external) {
2268block0:
2269 %0 = global_addr @.Lstr.0
2270 call @strlen(%0) : (ptr)
2271 return
2272}
2273"#,
2274 );
2275 assert!(out.contains("call @strlen("), "{out}");
2276 }
2277}