1use std::collections::HashMap;
47
48use rucc_base::Interner;
49use rucc_ir::{
50 CallInfo, Datum, Extra, Flags, Func, Global, Imm, Inst, InstData, Linkage, Meta, Module,
51 Opcode, Signature, Type, Value,
52};
53
54use crate::plane;
55
56pub const WIDTH: u64 = 16;
58
59pub const SECTION: &str = ".rucc_safety_desc";
61
62const DESCRIPTOR: &str = "__rucc_safety_desc";
67
68const ACCESS: u8 = 1;
70
71const DERIVE: u8 = 2;
73
74const RESTRICT: u8 = 8;
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub struct Descriptor {
85 pub judgement: u8,
87 pub class: u8,
89 pub size: u16,
92}
93
94pub fn lower(module: &mut Module, names: &mut Interner) -> usize {
103 let word = Type::int(module.datalayout.pointer_bits);
106 let mut written: Vec<Descriptor> = Vec::new();
111 let numbers = plane::numbers(module, names);
115 for id in module.funcs() {
116 if module[id].is_declaration() {
117 continue;
118 }
119 calls(&mut module[id], names, word, &numbers, &mut written);
120 }
121 for (index, row) in written.iter().enumerate() {
122 emit(module, names, index, *row);
123 }
124 written.len()
125}
126
127fn calls(
129 func: &mut Func,
130 names: &mut Interner,
131 word: Type,
132 numbers: &HashMap<Meta, u32>,
133 table: &mut Vec<Descriptor>,
134) {
135 let insts: Vec<Inst> =
136 func.blocks().flat_map(|block| func.insts(block).collect::<Vec<_>>()).collect();
137 for &inst in &insts {
138 match func[inst].opcode {
139 Opcode::CheckBounds => bounds(func, names, word, table, inst),
140 Opcode::CheckLive => live(func, names, table, inst),
141 Opcode::CheckDeriv => deriv(func, names, word, table, inst),
142 Opcode::CheckType => typed(func, names, word, numbers, table, inst),
143 Opcode::CheckInit => began(func, names, word, table, inst),
144 Opcode::CheckRestrictRead => promised(func, names, word, table, inst, false),
145 Opcode::CheckRestrictWrite => promised(func, names, word, table, inst, true),
146 Opcode::RestrictEnter => opened(func, names, inst),
147 Opcode::RestrictLeave => closed(func, names, inst),
148 Opcode::MetaType => judgement(func, names, word, numbers, inst),
149 Opcode::MetaTypeCopy => carriage(func, names, word, inst),
150 Opcode::MetaInit => written(func, names, word, inst),
151 Opcode::MetaInitCopy => carried(func, names, word, inst),
152 Opcode::CapExtent => extent(func, names, word, inst, "__rucc_extent"),
153 Opcode::CapExtentBack => extent(func, names, word, inst, "__rucc_extent_back"),
154 _ => {}
155 }
156 }
157 for &inst in &insts {
161 if func[inst].opcode == Opcode::CapOf {
162 func.remove_inst(inst);
163 }
164 }
165}
166
167fn bounds(
179 func: &mut Func,
180 names: &mut Interner,
181 word: Type,
182 table: &mut Vec<Descriptor>,
183 inst: Inst,
184) {
185 let args = &func[func[inst].args];
186 let (Some(&pointer), computed) = (args.get(1), args.get(2).copied()) else { return };
187 let Extra::Mem(mem) = func[inst].extra else { return };
188 let size = func[mem].size;
189
190 let row = Descriptor {
191 judgement: ACCESS,
192 class: 0,
193 size: if computed.is_some() { 0 } else { u16::try_from(size).unwrap_or(u16::MAX) },
196 };
197 let desc = record(func, names, table, inst, row);
198 let bytes = match computed {
199 Some(value) => fitted(func, inst, value, word),
200 None => konst(func, inst, Imm::int(i128::from(size), word), word),
201 };
202 let claim = if computed.is_some() { 1 } else { i128::from(func[mem].align) };
203 let align = konst(func, inst, Imm::int(claim, word), word);
204 let params = &[Type::PTR, word, word, Type::PTR];
205 call(func, names, inst, "__rucc_check_bounds", params, &[], &[pointer, bytes, align, desc]);
206}
207
208fn fitted(func: &mut Func, inst: Inst, value: Value, word: Type) -> Value {
215 let ty = func[value].ty;
216 if ty == word {
217 return value;
218 }
219 let opcode = if ty.bits() > word.bits() { Opcode::Trunc } else { Opcode::ZExt };
220 let span = func.span(inst);
221 let args = func.push_values(&[value]);
222 let made = func.create_inst(InstData { args, ..InstData::new(opcode) }, &[word], span);
223 func.insert_before(made, inst);
224 func[made].results().next().expect("a cast created with one result has one")
225}
226
227fn live(func: &mut Func, names: &mut Interner, table: &mut Vec<Descriptor>, inst: Inst) {
229 let [_capability, pointer] = func[func[inst].args] else { return };
230 let row = Descriptor { judgement: ACCESS, class: 0, size: 0 };
233 let desc = record(func, names, table, inst, row);
234 call(func, names, inst, "__rucc_check_live", &[Type::PTR, Type::PTR], &[], &[pointer, desc]);
235}
236
237fn deriv(
242 func: &mut Func,
243 names: &mut Interner,
244 word: Type,
245 table: &mut Vec<Descriptor>,
246 inst: Inst,
247) {
248 let [_capability, base, derived, stride] = func[func[inst].args] else { return };
249 let row = Descriptor { judgement: DERIVE, class: 0, size: 0 };
250 let desc = record(func, names, table, inst, row);
251 let params = &[Type::PTR, Type::PTR, word, Type::PTR];
252 call(func, names, inst, "__rucc_check_deriv", params, &[], &[base, derived, stride, desc]);
253}
254
255fn typed(
265 func: &mut Func,
266 names: &mut Interner,
267 word: Type,
268 numbers: &HashMap<Meta, u32>,
269 table: &mut Vec<Descriptor>,
270 inst: Inst,
271) {
272 let [_capability, pointer] = func[func[inst].args] else { return };
273 let Extra::Mem(mem) = func[inst].extra else { return };
274 let size = func[mem].size;
275 let Some(node) = func[mem].tbaa else { return };
276 let Some(&number) = numbers.get(&node) else { return };
277
278 let row = Descriptor {
279 judgement: ACCESS,
280 class: 0,
281 size: u16::try_from(size).unwrap_or(u16::MAX),
283 };
284 let desc = record(func, names, table, inst, row);
285 let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
286 let small = Type::int(32);
287 let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
288 let params = &[Type::PTR, word, small, Type::PTR];
289 call(func, names, inst, "__rucc_check_type", params, &[], &[pointer, bytes, ty, desc]);
290}
291
292fn began(
302 func: &mut Func,
303 names: &mut Interner,
304 word: Type,
305 table: &mut Vec<Descriptor>,
306 inst: Inst,
307) {
308 let [_capability, pointer] = func[func[inst].args] else { return };
309 let Extra::Mem(mem) = func[inst].extra else { return };
310 let size = func[mem].size;
311
312 let row = Descriptor {
313 judgement: ACCESS,
314 class: 0,
315 size: u16::try_from(size).unwrap_or(u16::MAX),
317 };
318 let desc = record(func, names, table, inst, row);
319 let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
320 let params = &[Type::PTR, word, Type::PTR];
321 call(func, names, inst, "__rucc_check_init", params, &[], &[pointer, bytes, desc]);
322}
323
324fn tag(clique: u16, base: u16) -> u32 {
331 (u32::from(clique) << 16) | u32::from(base)
332}
333
334fn promised(
346 func: &mut Func,
347 names: &mut Interner,
348 word: Type,
349 table: &mut Vec<Descriptor>,
350 inst: Inst,
351 write: bool,
352) {
353 let [pointer] = func[func[inst].args] else { return };
354 let Extra::Mem(mem) = func[inst].extra else { return };
355 let size = func[mem].size;
356 let named = func[mem].restrict;
357
358 let row = Descriptor {
359 judgement: RESTRICT,
360 class: 0,
361 size: u16::try_from(size).unwrap_or(u16::MAX),
363 };
364 let desc = record(func, names, table, inst, row);
365 let bytes = konst(func, inst, Imm::int(i128::from(size), word), word);
366 let small = Type::int(32);
367 let which =
368 konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
369 let wrote = konst(func, inst, Imm::int(i128::from(u8::from(write)), small), small);
370 let params = &[Type::PTR, word, small, small, Type::PTR];
371 let args = &[pointer, bytes, which, wrote, desc];
372 call(func, names, inst, "__rucc_check_restrict", params, &[], args);
373}
374
375fn opened(func: &mut Func, names: &mut Interner, inst: Inst) {
382 let [scope] = func[func[inst].args] else { return };
383 let Extra::Mem(mem) = func[inst].extra else { return };
384 let named = func[mem].restrict;
385 let small = Type::int(32);
386 let which =
387 konst(func, inst, Imm::int(i128::from(tag(named.clique, named.base)), small), small);
388 call(func, names, inst, "__rucc_restrict_enter", &[Type::PTR, small], &[], &[scope, which]);
389}
390
391fn closed(func: &mut Func, names: &mut Interner, inst: Inst) {
396 let [scope] = func[func[inst].args] else { return };
397 call(func, names, inst, "__rucc_restrict_leave", &[Type::PTR], &[], &[scope]);
398}
399
400fn judgement(
411 func: &mut Func,
412 names: &mut Interner,
413 word: Type,
414 numbers: &HashMap<Meta, u32>,
415 inst: Inst,
416) {
417 let [pointer, length] = func[func[inst].args] else { return };
418 let Extra::Node(node) = func[inst].extra else { return };
419 let Some(&number) = numbers.get(&node) else { return };
420 let bytes = fitted(func, inst, length, word);
421 let small = Type::int(32);
422 let ty = konst(func, inst, Imm::int(i128::from(number), small), small);
423 let params = &[Type::PTR, word, small];
424 call(func, names, inst, "__rucc_meta_type", params, &[], &[pointer, bytes, ty]);
425}
426
427fn carriage(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
434 let [to, from, length] = func[func[inst].args] else { return };
435 let bytes = fitted(func, inst, length, word);
436 let params = &[Type::PTR, Type::PTR, word];
437 call(func, names, inst, "__rucc_meta_type_copy", params, &[], &[to, from, bytes]);
438}
439
440fn written(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
446 let [pointer, length] = func[func[inst].args] else { return };
447 let bytes = fitted(func, inst, length, word);
448 let params = &[Type::PTR, word];
449 call(func, names, inst, "__rucc_meta_init", params, &[], &[pointer, bytes]);
450}
451
452fn carried(func: &mut Func, names: &mut Interner, word: Type, inst: Inst) {
458 let [to, from, length] = func[func[inst].args] else { return };
459 let bytes = fitted(func, inst, length, word);
460 let params = &[Type::PTR, Type::PTR, word];
461 call(func, names, inst, "__rucc_meta_init_copy", params, &[], &[to, from, bytes]);
462}
463
464fn extent(func: &mut Func, names: &mut Interner, word: Type, inst: Inst, called: &str) {
476 let [_capability, address, want] = func[func[inst].args] else { return };
477 let asked = fitted(func, inst, want, word);
478 let result = func[inst].results().next().expect("an extent query produces one value");
479 let ty = func[result].ty;
480 let params = &[Type::PTR, word];
481 if ty == word {
482 call(func, names, inst, called, params, &[word], &[address, asked]);
483 return;
484 }
485 let made = calling(func, names, called, params, &[word], &[address, asked]);
490 let holder = func.create_inst(made, &[word], func.span(inst));
491 func.insert_before(holder, inst);
492 let got = func[holder].results().next().expect("a call returning one value produces one");
493 let opcode = if word.bits() > ty.bits() { Opcode::Trunc } else { Opcode::ZExt };
494 let args = func.push_values(&[got]);
495 func[inst] = InstData { args, ..InstData::new(opcode) };
496}
497
498fn record(
504 func: &mut Func,
505 names: &mut Interner,
506 table: &mut Vec<Descriptor>,
507 inst: Inst,
508 row: Descriptor,
509) -> Value {
510 let name = names.intern(&label(table.len()));
511 table.push(row);
512 let span = func.span(inst);
513 let data = InstData { extra: Extra::Symbol(name), ..InstData::new(Opcode::GlobalAddr) };
514 let made = func.create_inst(data, &[Type::PTR], span);
515 func.insert_before(made, inst);
516 func[made].results().next().expect("an address created with one result has one")
517}
518
519fn label(index: usize) -> String {
521 format!("{DESCRIPTOR}_{index}")
522}
523
524fn konst(func: &mut Func, inst: Inst, imm: Imm, ty: Type) -> Value {
526 let span = func.span(inst);
527 let extra = Extra::Imm(func.add_imm(imm));
528 let made = func.create_inst(InstData { extra, ..InstData::new(Opcode::IConst) }, &[ty], span);
529 func.insert_before(made, inst);
530 func[made].results().next().expect("a constant created with one result has one")
531}
532
533fn call(
539 func: &mut Func,
540 names: &mut Interner,
541 inst: Inst,
542 routine: &str,
543 params: &[Type],
544 returns: &[Type],
545 args: &[Value],
546) {
547 let made = calling(func, names, routine, params, returns, args);
548 let data = &mut func[inst];
549 data.opcode = made.opcode;
550 data.args = made.args;
551 data.extra = made.extra;
552 data.flags = data.flags.intersection(Flags::legal_on(Opcode::Call));
553}
554
555fn calling(
561 func: &mut Func,
562 names: &mut Interner,
563 routine: &str,
564 params: &[Type],
565 returns: &[Type],
566 args: &[Value],
567) -> InstData {
568 let sig = func.add_signature(Signature::new().with_params(params).with_returns(returns));
569 let callee = names.intern(routine);
570 let varargs = func.push_abis(&[]);
573 let info = func.add_call(CallInfo { callee: Some(callee), signature: sig, varargs });
574 let args = func.push_values(args);
575 InstData { args, extra: Extra::Call(info), ..InstData::new(Opcode::Call) }
576}
577
578fn emit(module: &mut Module, names: &mut Interner, index: usize, row: Descriptor) {
585 let byte = Type::int(8);
586 let half = Type::int(16);
587 let judgement = module.add_imm(Imm::int(i128::from(row.judgement), byte));
588 let class = module.add_imm(Imm::int(i128::from(row.class), byte));
589 let size = module.add_imm(Imm::int(i128::from(row.size), half));
590 let image = [
591 Datum::Scalar { ty: byte, value: judgement },
592 Datum::Scalar { ty: byte, value: class },
593 Datum::Scalar { ty: half, value: size },
594 Datum::Zero(4),
598 Datum::Zero(8),
599 ];
600 let init = module.push_data(&image);
601 let mut global = Global::new(names.intern(&label(index)), WIDTH, 8);
602 global.linkage = Linkage::Internal;
603 global.constant = true;
604 global.section = Some(names.intern(SECTION));
605 global.init = Some(init);
606 module.add_global(global);
607}
608
609#[cfg(test)]
610mod tests {
611 use rucc_ir::{
612 Builder, MemInfo, MemOrder, MetaNode, Restrict, TbaaNode, print_func, verify_func,
613 };
614 use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
615
616 use super::*;
617 use crate::{Plane, Promise, Subobject, insert};
618
619 fn target() -> TargetInfo {
620 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
621 }
622
623 fn checked(names: &mut Interner) -> Module {
625 let i32_ = Type::int(32);
626 let mut func = Func::new(
627 names.intern("read"),
628 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
629 );
630 let entry = func.create_block();
631 let p = func.append_param(entry, Type::PTR);
632
633 let info = MemInfo {
634 size: 4,
635 align: 4,
636 order: MemOrder::NotAtomic,
637 tbaa: None,
638 owns: 0,
639 restrict: Restrict::NONE,
640 };
641 let mut b = Builder::new(&mut func, entry);
642 let args = b.func().push_values(&[p]);
643 let extra = Extra::Mem(b.func().add_mem(info));
644 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
645 b.ret(&[loaded]);
646
647 insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
648 let mut module = Module::new(names.intern("read.c"), &target());
649 module.add_func(func);
650 module
651 }
652
653 fn unaligned(names: &mut Interner) -> Module {
656 let i32_ = Type::int(32);
657 let mut func = Func::new(
658 names.intern("read"),
659 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
660 );
661 let entry = func.create_block();
662 let p = func.append_param(entry, Type::PTR);
663
664 let info = MemInfo {
665 size: 4,
666 align: 1,
667 order: MemOrder::NotAtomic,
668 tbaa: None,
669 owns: 0,
670 restrict: Restrict::NONE,
671 };
672 let mut b = Builder::new(&mut func, entry);
673 let args = b.func().push_values(&[p]);
674 let extra = Extra::Mem(b.func().add_mem(info));
675 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
676 b.ret(&[loaded]);
677
678 insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
679 let mut module = Module::new(names.intern("read.c"), &target());
680 module.add_func(func);
681 module
682 }
683
684 fn planeless(names: &mut Interner) -> (Plane, HashMap<Meta, u32>) {
690 let mut module = Module::new(names.intern("planeless.c"), &target());
691 let plane = Plane::build(&mut module);
692 let numbers = plane::numbers(&module, names);
693 (plane, numbers)
694 }
695
696 fn copied(names: &mut Interner) -> Module {
698 let mut func =
699 Func::new(names.intern("move"), Signature::new().with_params(&[Type::PTR, Type::PTR]));
700 let entry = func.create_block();
701 let to = func.append_param(entry, Type::PTR);
702 let from = func.append_param(entry, Type::PTR);
703
704 let info = MemInfo {
705 size: 24,
706 align: 8,
707 order: MemOrder::NotAtomic,
708 tbaa: None,
709 owns: 0,
710 restrict: Restrict::NONE,
711 };
712 let mut b = Builder::new(&mut func, entry);
713 let args = b.func().push_values(&[to, from]);
714 let extra = Extra::Mem(b.func().add_mem(info));
715 b.inst(InstData { args, extra, ..InstData::new(Opcode::Memcpy) }, &[]);
716 b.ret(&[]);
717
718 insert(&mut func, &planeless(names).0, 8, Subobject::Off, Promise::Off);
719 let mut module = Module::new(names.intern("move.c"), &target());
720 module.add_func(func);
721 module
722 }
723
724 fn stored(names: &mut Interner) -> Module {
730 let mut module = Module::new(names.intern("write.c"), &target());
731 let plane = Plane::build(&mut module);
732
733 let i64_ = Type::int(64);
734 let mut func =
735 Func::new(names.intern("write"), Signature::new().with_params(&[Type::PTR, i64_]));
736 let entry = func.create_block();
737 let p = func.append_param(entry, Type::PTR);
738 let v = func.append_param(entry, i64_);
739
740 let info = MemInfo {
741 size: 8,
742 align: 8,
743 order: MemOrder::NotAtomic,
744 tbaa: None,
745 owns: 0,
746 restrict: Restrict::NONE,
747 };
748 let mut b = Builder::new(&mut func, entry);
749 let args = b.func().push_values(&[v, p]);
750 let extra = Extra::Mem(b.func().add_mem(info));
751 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
752 b.ret(&[]);
753
754 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
755 module.add_func(func);
756 module
757 }
758
759 fn asking_the_plane(names: &mut Interner) -> Module {
765 let mut module = Module::new(names.intern("read.c"), &target());
766 let root = names.intern("char");
767 let root =
768 module.add_meta(MetaNode::Tbaa(TbaaNode { name: root, parent: None, offset: 0 }));
769 let int = names.intern("int");
770 let int =
771 module.add_meta(MetaNode::Tbaa(TbaaNode { name: int, parent: Some(root), offset: 0 }));
772 let plane = Plane::build(&mut module);
773
774 let i32_ = Type::int(32);
775 let mut func = Func::new(
776 names.intern("read"),
777 Signature::new().with_params(&[Type::PTR]).with_returns(&[i32_]),
778 );
779 let entry = func.create_block();
780 let p = func.append_param(entry, Type::PTR);
781 let info = MemInfo {
782 size: 4,
783 align: 4,
784 order: MemOrder::NotAtomic,
785 tbaa: Some(int),
786 owns: 0,
787 restrict: Restrict::NONE,
788 };
789 let mut b = Builder::new(&mut func, entry);
790 let args = b.func().push_values(&[p]);
791 let extra = Extra::Mem(b.func().add_mem(info));
792 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
793 b.ret(&[loaded]);
794
795 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
796 module.add_func(func);
797 module
798 }
799
800 fn marker(b: &mut Builder<'_>, opcode: Opcode, info: Option<MemInfo>, on: &[Value]) {
802 let args = b.func().push_values(on);
803 let extra = match info {
804 Some(info) => Extra::Mem(b.func().add_mem(info)),
805 None => Extra::None,
806 };
807 b.inst(InstData { args, extra, ..InstData::new(opcode) }, &[]);
808 }
809
810 fn promising(names: &mut Interner) -> Module {
816 let i32_ = Type::int(32);
817 let mut func = Func::new(
818 names.intern("kernel"),
819 Signature::new().with_params(&[Type::PTR, Type::PTR]),
820 );
821 let entry = func.create_block();
822 let to = func.append_param(entry, Type::PTR);
823 let from = func.append_param(entry, Type::PTR);
824
825 let empty = MemInfo {
826 size: 0,
827 align: 1,
828 order: MemOrder::NotAtomic,
829 tbaa: None,
830 owns: 0,
831 restrict: Restrict::NONE,
832 };
833 let slot = MemInfo { size: 112, align: 8, ..empty };
835 let mut b = Builder::new(&mut func, entry);
836 let extra = Extra::Mem(b.func().add_mem(slot));
837 let scope = b.value(InstData { extra, ..InstData::new(Opcode::Alloca) }, Type::PTR);
838
839 let read =
841 MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 2 }, ..empty };
842 let writ =
843 MemInfo { size: 4, align: 4, restrict: Restrict { clique: 1, base: 1 }, ..empty };
844 let opening = MemInfo { restrict: Restrict { clique: 1, base: 2 }, ..slot };
845 marker(&mut b, Opcode::RestrictEnter, Some(opening), &[scope]);
846 marker(&mut b, Opcode::CheckRestrictRead, Some(read), &[from]);
847 let args = b.func().push_values(&[from]);
848 let extra = Extra::Mem(b.func().add_mem(read));
849 let loaded = b.value(InstData { args, extra, ..InstData::new(Opcode::Load) }, i32_);
850 marker(&mut b, Opcode::CheckRestrictWrite, Some(writ), &[to]);
851 let args = b.func().push_values(&[loaded, to]);
852 let extra = Extra::Mem(b.func().add_mem(writ));
853 b.inst(InstData { args, extra, ..InstData::new(Opcode::Store) }, &[]);
854 marker(&mut b, Opcode::RestrictLeave, None, &[scope]);
855 b.ret(&[]);
856
857 let mut module = Module::new(names.intern("kernel.c"), &target());
858 module.add_func(func);
859 module
860 }
861
862 #[test]
863 fn a_restrict_check_becomes_the_call_that_says_which_pointer_reached_where() {
864 let mut names = Interner::new();
868 let mut module = promising(&mut names);
869 assert_eq!(lower(&mut module, &mut names), 2);
870
871 let id = module.funcs().next().expect("the module has one function");
872 assert_eq!(
873 print_func(&module, &module[id], &names),
874 "func @kernel(ptr, ptr), linkage(external) {\n\
875 block0(%0: ptr, %1: ptr):\n \
876 %2 = alloca, size 112, align 8\n \
877 %3 = iconst.i32 65538\n \
878 call @__rucc_restrict_enter(%2, %3) : (ptr, i32)\n \
879 %4 = global_addr @__rucc_safety_desc_0\n \
880 %5 = iconst.i64 4\n \
881 %6 = iconst.i32 65538\n \
882 %7 = iconst.i32 0\n \
883 call @__rucc_check_restrict(%1, %5, %6, %7, %4) : (ptr, i64, i32, i32, ptr)\n \
884 %8 = load.i32 %1, size 4, align 4, restrict(1, 2)\n \
885 %9 = global_addr @__rucc_safety_desc_1\n \
886 %10 = iconst.i64 4\n \
887 %11 = iconst.i32 65537\n \
888 %12 = iconst.i32 1\n \
889 call @__rucc_check_restrict(%0, %10, %11, %12, %9) : (ptr, i64, i32, i32, ptr)\n \
890 store %8 -> %0, size 4, align 4, restrict(1, 1)\n \
891 call @__rucc_restrict_leave(%2) : (ptr)\n \
892 return\n\
893 }\n"
894 );
895
896 if let Err(errors) = verify_func(&module, &module[id], &names) {
897 panic!("that was expected to be believed: {errors:#?}");
898 }
899 }
900
901 #[test]
902 fn the_judgement_a_restrict_check_names_is_the_one_about_the_pair() {
903 let mut names = Interner::new();
907 let mut module = promising(&mut names);
908 lower(&mut module, &mut names);
909
910 let rows: Vec<u8> = module
911 .globals()
912 .map(|id| {
913 let init = module[id].init.expect("a descriptor is a definition");
914 match module[init][0] {
915 Datum::Scalar { value, .. } => {
916 u8::try_from(module[value].bits()).expect("a judgement is one byte")
917 }
918 _ => panic!("a descriptor starts with its judgement"),
919 }
920 })
921 .collect();
922 assert_eq!(rows, [RESTRICT, RESTRICT]);
923 }
924
925 #[test]
926 fn the_two_numbers_are_packed_the_way_the_runtime_unpacks_them() {
927 assert_eq!(tag(1, 2), 0x0001_0002);
932 assert_eq!(tag(0xffff, 0xffff), u32::MAX);
933 assert_eq!(tag(0, 0), 0);
934 }
935
936 #[test]
937 fn a_read_of_the_plane_becomes_the_call_that_carries_the_type_asked_about() {
938 let mut names = Interner::new();
943 let mut module = asking_the_plane(&mut names);
944 assert_eq!(lower(&mut module, &mut names), 4);
945
946 let number = i32::from_ne_bytes(plane::identifier("int").to_ne_bytes());
949 let id = module.funcs().next().expect("the module has one function");
950 assert_eq!(
951 print_func(&module, &module[id], &names),
952 format!(
953 "func @read(ptr) -> i32, linkage(external) {{\n\
954 block0(%0: ptr):\n \
955 %1 = global_addr @__rucc_safety_desc_0\n \
956 %2 = iconst.i64 4\n \
957 %3 = iconst.i64 4\n \
958 call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n \
959 %4 = global_addr @__rucc_safety_desc_1\n \
960 call @__rucc_check_live(%0, %4) : (ptr, ptr)\n \
961 %5 = global_addr @__rucc_safety_desc_2\n \
962 %6 = iconst.i64 4\n \
963 %7 = iconst.i32 {number}\n \
964 call @__rucc_check_type(%0, %6, %7, %5) : (ptr, i64, i32, ptr)\n \
965 %8 = global_addr @__rucc_safety_desc_3\n \
966 %9 = iconst.i64 4\n \
967 call @__rucc_check_init(%0, %9, %8) : (ptr, i64, ptr)\n \
968 %10 = load.i32 %0, size 4, align 4, tbaa !1\n \
969 return %10\n\
970 }}\n"
971 )
972 );
973
974 if let Err(errors) = verify_func(&module, &module[id], &names) {
975 panic!("that was expected to be believed: {errors:#?}");
976 }
977 }
978
979 #[test]
980 fn the_judgement_a_type_check_names_is_the_one_about_the_planes() {
981 let mut names = Interner::new();
986 let mut module = asking_the_plane(&mut names);
987 lower(&mut module, &mut names);
988
989 let rows: Vec<u8> = module
990 .globals()
991 .map(|id| {
992 let init = module[id].init.expect("a descriptor is a definition");
993 match module[init][0] {
994 Datum::Scalar { value, .. } => {
995 u8::try_from(module[value].bits()).expect("a judgement is one byte")
996 }
997 _ => panic!("a descriptor starts with its judgement"),
998 }
999 })
1000 .collect();
1001 assert_eq!(rows, [ACCESS, ACCESS, ACCESS, ACCESS]);
1002 }
1003
1004 #[test]
1005 fn a_store_becomes_the_calls_that_record_what_it_wrote() {
1006 let mut names = Interner::new();
1011 let mut module = stored(&mut names);
1012 assert_eq!(lower(&mut module, &mut names), 2);
1013
1014 let id = module.funcs().next().expect("the module has one function");
1015 let printed = print_func(&module, &module[id], &names);
1016 assert!(
1017 printed.contains("call @__rucc_meta_type(%0, %6, %7) : (ptr, i64, i32)\n"),
1018 "{printed}"
1019 );
1020 assert!(printed.contains("call @__rucc_meta_init(%0, %8) : (ptr, i64)\n"), "{printed}");
1021
1022 if let Err(errors) = verify_func(&module, &module[id], &names) {
1023 panic!("that was expected to be believed: {errors:#?}");
1024 }
1025 }
1026
1027 #[test]
1028 fn a_copy_becomes_the_calls_that_move_the_planes_across() {
1029 let mut names = Interner::new();
1034 let mut module = copied(&mut names);
1035 assert_eq!(lower(&mut module, &mut names), 0);
1036
1037 let id = module.funcs().next().expect("the module has one function");
1038 assert_eq!(
1039 print_func(&module, &module[id], &names),
1040 "func @move(ptr, ptr), linkage(external) {\n\
1041 block0(%0: ptr, %1: ptr):\n \
1042 memcpy %0, %1, size 24, align 8\n \
1043 %2 = iconst.i64 24\n \
1044 call @__rucc_meta_type_copy(%0, %1, %2) : (ptr, ptr, i64)\n \
1045 %3 = iconst.i64 24\n \
1046 call @__rucc_meta_init_copy(%0, %1, %3) : (ptr, ptr, i64)\n \
1047 return\n\
1048 }\n"
1049 );
1050
1051 if let Err(errors) = verify_func(&module, &module[id], &names) {
1052 panic!("that was expected to be believed: {errors:#?}");
1053 }
1054 }
1055
1056 #[test]
1063 fn the_alignment_that_goes_through_is_the_one_the_access_may_assume() {
1064 let mut names = Interner::new();
1065 let mut module = unaligned(&mut names);
1066 assert_eq!(lower(&mut module, &mut names), 3);
1067
1068 let id = module.funcs().next().expect("the module has one function");
1069 let printed = print_func(&module, &module[id], &names);
1070 assert!(printed.contains("%2 = iconst.i64 4\n"), "{printed}");
1071 assert!(printed.contains("%3 = iconst.i64 1\n"), "{printed}");
1072 assert!(
1073 printed.contains("call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n"),
1074 "{printed}"
1075 );
1076 }
1077
1078 #[test]
1079 fn every_check_becomes_a_call_carrying_the_descriptor_it_is_described_by() {
1080 let mut names = Interner::new();
1081 let mut module = checked(&mut names);
1082 assert_eq!(lower(&mut module, &mut names), 3);
1083
1084 let id = module.funcs().next().expect("the module has one function");
1085 assert_eq!(
1086 print_func(&module, &module[id], &names),
1087 "func @read(ptr) -> i32, linkage(external) {\n\
1088 block0(%0: ptr):\n \
1089 %1 = global_addr @__rucc_safety_desc_0\n \
1090 %2 = iconst.i64 4\n \
1091 %3 = iconst.i64 4\n \
1092 call @__rucc_check_bounds(%0, %2, %3, %1) : (ptr, i64, i64, ptr)\n \
1093 %4 = global_addr @__rucc_safety_desc_1\n \
1094 call @__rucc_check_live(%0, %4) : (ptr, ptr)\n \
1095 %5 = global_addr @__rucc_safety_desc_2\n \
1096 %6 = iconst.i64 4\n \
1097 call @__rucc_check_init(%0, %6, %5) : (ptr, i64, ptr)\n \
1098 %7 = load.i32 %0, size 4, align 4\n \
1099 return %7\n\
1100 }\n"
1101 );
1102 }
1103
1104 #[test]
1105 fn the_capabilities_the_checks_were_reading_are_taken_out() {
1106 let mut names = Interner::new();
1109 let mut module = checked(&mut names);
1110 lower(&mut module, &mut names);
1111
1112 let id = module.funcs().next().expect("the module has one function");
1113 let func = &module[id];
1114 let left: Vec<Opcode> = func
1115 .blocks()
1116 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1117 .map(|inst| func[inst].opcode)
1118 .collect();
1119 assert!(!left.contains(&Opcode::CapOf), "{left:?}");
1120 }
1121
1122 #[test]
1123 fn what_it_produces_is_a_module_the_verifier_believes() {
1124 let mut names = Interner::new();
1125 let mut module = checked(&mut names);
1126 lower(&mut module, &mut names);
1127
1128 let id = module.funcs().next().expect("the module has one function");
1129 if let Err(errors) = verify_func(&module, &module[id], &names) {
1130 panic!("that was expected to be believed: {errors:#?}");
1131 }
1132 }
1133
1134 #[test]
1135 fn the_section_is_one_descriptor_per_check_and_nothing_else() {
1136 let mut names = Interner::new();
1141 let mut module = checked(&mut names);
1142 let rows = lower(&mut module, &mut names);
1143
1144 let globals: Vec<_> = module.globals().collect();
1145 assert_eq!(globals.len(), rows);
1146 for (index, id) in globals.iter().enumerate() {
1147 let desc = &module[*id];
1148 assert_eq!(names.resolve(desc.name), label(index));
1149 assert_eq!(
1150 names.resolve(desc.section.expect("a descriptor names its section")),
1151 SECTION
1152 );
1153 assert_eq!(desc.linkage, Linkage::Internal);
1154 assert!(desc.constant);
1155 assert_eq!(desc.align, 8);
1156 assert_eq!(desc.size, WIDTH);
1157
1158 let init = desc.init.expect("a descriptor is a definition");
1161 let written: u64 = module[init].iter().map(|datum| datum.size(&module)).sum();
1162 assert_eq!(written, WIDTH);
1163 }
1164 }
1165
1166 #[test]
1167 fn the_judgement_a_descriptor_names_is_the_one_the_check_decides() {
1168 let mut names = Interner::new();
1171 let mut func = Func::new(
1172 names.intern("walk"),
1173 Signature::new().with_params(&[Type::PTR, Type::int(64)]).with_returns(&[Type::PTR]),
1174 );
1175 let entry = func.create_block();
1176 let p = func.append_param(entry, Type::PTR);
1177 let n = func.append_param(entry, Type::int(64));
1178 let mut b = Builder::new(&mut func, entry);
1179 let args = b.func().push_values(&[p, n]);
1180 let moved = b.value(InstData { args, ..InstData::new(Opcode::PtrAdd) }, Type::PTR);
1181 b.ret(&[moved]);
1182 let (plane, numbers) = planeless(&mut names);
1183 insert(&mut func, &plane, 8, Subobject::Off, Promise::Off);
1184
1185 let mut table = Vec::new();
1186 calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1187 assert_eq!(table, [Descriptor { judgement: DERIVE, class: 0, size: 0 }]);
1188 }
1189
1190 #[test]
1191 fn a_check_over_a_length_the_program_worked_out_passes_that_length_along() {
1192 let mut names = Interner::new();
1196 let mut func = Func::new(
1197 names.intern("sweep"),
1198 Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1199 );
1200 let entry = func.create_block();
1201 let p = func.append_param(entry, Type::PTR);
1202 let n = func.append_param(entry, Type::int(64));
1203 let info = MemInfo {
1204 size: 4,
1205 align: 4,
1206 order: MemOrder::NotAtomic,
1207 tbaa: None,
1208 owns: 0,
1209 restrict: Restrict::NONE,
1210 };
1211 let mut b = Builder::new(&mut func, entry);
1212 let of = b.unary(Opcode::CapOf, p, Type::CAP);
1213 let args = b.func().push_values(&[of, p, n]);
1214 let extra = Extra::Mem(b.func().add_mem(info));
1215 b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1216 b.ret(&[]);
1217
1218 let mut table = Vec::new();
1219 let numbers = planeless(&mut names).1;
1220 calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1221 assert_eq!(table, [Descriptor { judgement: ACCESS, class: 0, size: 0 }]);
1222
1223 let mut module = Module::new(names.intern("sweep.c"), &target());
1224 module.add_func(func);
1225 let id = module.funcs().next().expect("the module has one function");
1226 assert_eq!(
1227 print_func(&module, &module[id], &names),
1228 "func @sweep(ptr, i64), linkage(external) {\n\
1229 block0(%0: ptr, %1: i64):\n \
1230 %2 = global_addr @__rucc_safety_desc_0\n \
1231 %3 = iconst.i64 1\n \
1232 call @__rucc_check_bounds(%0, %1, %3, %2) : (ptr, i64, i64, ptr)\n \
1233 return\n\
1234 }\n"
1235 );
1236 }
1237
1238 #[test]
1239 fn a_length_wider_than_the_word_is_cut_down_to_it() {
1240 let mut names = Interner::new();
1244 let mut func = Func::new(
1245 names.intern("sweep"),
1246 Signature::new().with_params(&[Type::PTR, Type::int(64)]),
1247 );
1248 let entry = func.create_block();
1249 let p = func.append_param(entry, Type::PTR);
1250 let n = func.append_param(entry, Type::int(64));
1251 let info = MemInfo {
1252 size: 4,
1253 align: 4,
1254 order: MemOrder::NotAtomic,
1255 tbaa: None,
1256 owns: 0,
1257 restrict: Restrict::NONE,
1258 };
1259 let mut b = Builder::new(&mut func, entry);
1260 let of = b.unary(Opcode::CapOf, p, Type::CAP);
1261 let args = b.func().push_values(&[of, p, n]);
1262 let extra = Extra::Mem(b.func().add_mem(info));
1263 b.inst(InstData { args, extra, ..InstData::new(Opcode::CheckBounds) }, &[]);
1264 b.ret(&[]);
1265
1266 let mut table = Vec::new();
1267 let numbers = planeless(&mut names).1;
1268 calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1269 let opcodes: Vec<Opcode> = func
1270 .blocks()
1271 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1272 .map(|inst| func[inst].opcode)
1273 .collect();
1274 assert!(opcodes.contains(&Opcode::Trunc), "{opcodes:?}");
1275 }
1276
1277 fn asking(names: &mut Interner, ty: Type) -> Func {
1282 let mut func = Func::new(
1283 names.intern("cover"),
1284 Signature::new().with_params(&[Type::PTR, ty]).with_returns(&[ty]),
1285 );
1286 let entry = func.create_block();
1287 let p = func.append_param(entry, Type::PTR);
1288 let want = func.append_param(entry, ty);
1289 let mut b = Builder::new(&mut func, entry);
1290 let of = b.unary(Opcode::CapOf, p, Type::CAP);
1291 let args = b.func().push_values(&[of, p, want]);
1292 let got = b.value(InstData { args, ..InstData::new(Opcode::CapExtent) }, ty);
1293 b.ret(&[got]);
1294 func
1295 }
1296
1297 #[test]
1298 fn the_extent_query_becomes_a_call_that_carries_no_descriptor() {
1299 let mut names = Interner::new();
1302 let mut func = asking(&mut names, Type::int(64));
1303
1304 let mut table = Vec::new();
1305 let numbers = planeless(&mut names).1;
1306 calls(&mut func, &mut names, Type::int(64), &numbers, &mut table);
1307 assert!(table.is_empty(), "{table:?}");
1308
1309 let mut module = Module::new(names.intern("cover.c"), &target());
1310 module.add_func(func);
1311 let id = module.funcs().next().expect("the module has one function");
1312 assert_eq!(
1313 print_func(&module, &module[id], &names),
1314 "func @cover(ptr, i64) -> i64, linkage(external) {\n\
1315 block0(%0: ptr, %1: i64):\n \
1316 %2 = call @__rucc_extent(%0, %1) : (ptr, i64) -> i64\n \
1317 return %2\n\
1318 }\n"
1319 );
1320 if let Err(errors) = verify_func(&module, &module[id], &names) {
1321 panic!("that was expected to be believed: {errors:#?}");
1322 }
1323 }
1324
1325 #[test]
1326 fn an_extent_asked_for_in_a_width_the_target_does_not_have_is_converted_back() {
1327 let mut names = Interner::new();
1332 let mut func = asking(&mut names, Type::int(64));
1333
1334 let mut table = Vec::new();
1335 let numbers = planeless(&mut names).1;
1336 calls(&mut func, &mut names, Type::int(32), &numbers, &mut table);
1337 let opcodes: Vec<Opcode> = func
1338 .blocks()
1339 .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1340 .map(|inst| func[inst].opcode)
1341 .collect();
1342 assert!(opcodes.contains(&Opcode::Trunc), "the limit goes in narrowed: {opcodes:?}");
1343 assert!(opcodes.contains(&Opcode::ZExt), "and the answer comes back widened: {opcodes:?}");
1344 assert!(
1345 !opcodes.contains(&Opcode::CapExtent),
1346 "with nothing left of the query: {opcodes:?}"
1347 );
1348 }
1349
1350 #[test]
1351 fn a_module_with_nothing_to_check_gets_no_section_at_all() {
1352 let mut names = Interner::new();
1355 let mut module = Module::new(names.intern("empty.c"), &target());
1356 assert_eq!(lower(&mut module, &mut names), 0);
1357 assert_eq!(module.globals().count(), 0);
1358 }
1359}