use rucc_target::TargetInfo;
use rucc_types::{TypeId, TypeKind, Types, is_arithmetic, is_pointer, is_void};
use crate::expr::{Category, Conversion, Expr, ExprId, ExprKind};
use crate::tast::{Const, Tast};
#[derive(Debug)]
pub struct Conv<'a> {
pub tast: &'a mut Tast,
pub types: &'a mut Types,
pub target: &'a TargetInfo,
}
impl Conv<'_> {
pub fn value(&mut self, expr: ExprId) -> ExprId {
let ty = self.tast[expr].ty;
match self.types.kind(self.types.canonical(ty)) {
TypeKind::Array { elem, .. } => {
let ty = self.types.pointer(elem);
self.write(Conversion::ArrayDecay, expr, ty)
}
TypeKind::Function(_) => {
let ty = self.types.pointer(ty);
self.write(Conversion::FunctionDecay, expr, ty)
}
_ if self.tast[expr].category == Category::Rvalue => expr,
_ => {
let ty = self.read_as(ty);
self.write(Conversion::Lvalue, expr, ty)
}
}
}
pub fn promote(&mut self, expr: ExprId) -> ExprId {
let expr = self.value_promoting_bits(expr);
let ty = self.tast[expr].ty;
let promoted = rucc_types::promote(self.types, ty, self.target);
self.arithmetic(expr, promoted)
}
pub fn promote_bits(&mut self, expr: ExprId, width: u32) -> ExprId {
let expr = self.value(expr);
let ty = self.tast[expr].ty;
let promoted = rucc_types::promote_bit_field(self.types, ty, width, self.target);
self.arithmetic(expr, promoted)
}
fn value_promoting_bits(&mut self, expr: ExprId) -> ExprId {
match self.bit_field_width(expr) {
Some(width) => self.promote_bits(expr, width),
None => self.value(expr),
}
}
fn bit_field_width(&self, expr: ExprId) -> Option<u32> {
let expr = match self.tast[expr].kind {
ExprKind::Convert { kind: Conversion::Lvalue, operand } => operand,
_ => expr,
};
let ExprKind::Member { base, field } = self.tast[expr].kind else { return None };
let base = self.types.canonical(self.tast[base].ty);
let TypeKind::Record(record) = self.types.kind(base) else { return None };
self.types.record_info(record).fields.get(field as usize)?.bits
}
pub fn usual_arithmetic(&mut self, lhs: ExprId, rhs: ExprId) -> Option<(ExprId, ExprId)> {
let (lhs, rhs) = (self.value_promoting_bits(lhs), self.value_promoting_bits(rhs));
let common = rucc_types::usual_arithmetic(
self.types,
self.tast[lhs].ty,
self.tast[rhs].ty,
self.target,
)?;
Some((self.arithmetic(lhs, common), self.arithmetic(rhs, common)))
}
pub fn to_bool(&mut self, expr: ExprId) -> ExprId {
let expr = self.value(expr);
let boolean = self.types.boolean();
if self.tast[expr].ty == boolean {
return expr;
}
self.write(Conversion::Bool, expr, boolean)
}
pub fn to_void(&mut self, expr: ExprId) -> ExprId {
if is_void(self.types, self.tast[expr].ty) {
return expr;
}
let void = self.types.void();
self.write(Conversion::Void, expr, void)
}
pub fn to_type(&mut self, expr: ExprId, ty: TypeId) -> ExprId {
let expr = self.value(expr);
let from = self.tast[expr].ty;
let target = self.read_as(ty);
if from == target {
return expr;
}
if is_void(self.types, target) {
return self.to_void(expr);
}
let boolean = self.types.boolean();
if target == boolean {
return self.to_bool(expr);
}
let kind = if is_pointer(self.types, target) {
if self.is_null_pointer_constant(expr) {
Conversion::NullPointer
} else {
Conversion::Pointer
}
} else if is_arithmetic(self.types, target) && is_arithmetic(self.types, from) {
Conversion::Arithmetic
} else {
Conversion::Pointer
};
self.write(kind, expr, target)
}
#[must_use]
pub fn is_null_pointer_constant(&self, expr: ExprId) -> bool {
match self.tast[expr].kind {
ExprKind::Const(value) => self.tast[value] == Const::Int(0),
ExprKind::Cast(inner) | ExprKind::Convert { operand: inner, .. } => {
self.is_null_pointer_constant(inner)
}
_ => false,
}
}
fn arithmetic(&mut self, expr: ExprId, ty: TypeId) -> ExprId {
if self.tast[expr].ty == ty {
return expr;
}
self.write(Conversion::Arithmetic, expr, ty)
}
pub(crate) fn read_as(&mut self, ty: TypeId) -> TypeId {
let stripped = match self.types.kind(self.types.canonical(ty)) {
TypeKind::Atomic(inner) => inner,
_ => ty,
};
self.types.unqualified(stripped)
}
fn write(&mut self, kind: Conversion, operand: ExprId, ty: TypeId) -> ExprId {
let span = self.tast.expr_span(operand);
let node = Expr::new(ExprKind::Convert { kind, operand }, ty, Category::Rvalue);
self.tast.expr(node, span)
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_diag::Span;
use rucc_target::{TargetInfo, Triple};
use rucc_types::{ArrayLen, FunctionType, IntKind, Qualifiers};
use super::*;
use crate::decl::{Decl, DeclKind, DeclList, Definition, Linkage, StorageDuration};
use crate::print::Printer;
struct Fixture {
tast: Tast,
types: Types,
names: Interner,
target: TargetInfo,
}
impl Fixture {
fn new() -> Fixture {
let target =
TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"));
Fixture { tast: Tast::new(), types: Types::new(), names: Interner::new(), target }
}
fn conv(&mut self) -> Conv<'_> {
Conv { tast: &mut self.tast, types: &mut self.types, target: &self.target }
}
fn object(&mut self, ty: TypeId) -> ExprId {
let decl = self.tast.decl(
Decl {
name: None,
ty,
kind: DeclKind::Object,
linkage: Linkage::None,
duration: StorageDuration::Automatic,
state: Definition::Defined,
alignment: None,
init: None,
params: DeclList::EMPTY,
body: None,
},
Span::DUMMY,
);
self.tast.expr(Expr::new(ExprKind::Decl(decl), ty, Category::Lvalue), Span::DUMMY)
}
fn bit_field(&mut self, ty: TypeId, bits: u32) -> ExprId {
let fields = [rucc_types::FieldDecl::bit_field(None, ty, bits)];
let id = self.types.declare_record(rucc_types::RecordKind::Struct, None);
let laid_out = rucc_types::layout_record(
&self.types,
rucc_types::RecordKind::Struct,
&fields,
&rucc_types::RecordOptions::default(),
&self.target,
)
.expect("a layout");
self.types.complete_record(id, laid_out);
let record = self.types.record(id);
let base = self.object(record);
self.tast.expr(
Expr::new(ExprKind::Member { base, field: 0 }, ty, Category::Bitfield),
Span::DUMMY,
)
}
fn zero(&mut self, ty: TypeId) -> ExprId {
let value = self.tast.add_const(Const::Int(0));
self.tast.expr(Expr::new(ExprKind::Const(value), ty, Category::Rvalue), Span::DUMMY)
}
fn text(&self, expr: ExprId) -> String {
let mut printer = Printer::new(&self.tast, &self.types, &self.names);
printer.expr(expr);
printer.finish()
}
}
#[test]
fn reading_an_object_drops_the_qualifiers_because_they_are_not_part_of_a_value() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let constant = f.types.qualified(int, Qualifiers::CONST);
let object = f.object(constant);
let read = f.conv().value(object);
assert_eq!(f.tast[read].ty, int);
assert_eq!(f.tast[read].category, Category::Rvalue);
assert_eq!(f.text(read), "convert lvalue : int\n decl #0 : const int lvalue\n");
}
#[test]
fn an_atomic_object_reads_as_the_type_it_wraps() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let atomic = f.types.atomic(int);
let object = f.object(atomic);
let read = f.conv().value(object);
assert_eq!(f.tast[read].ty, int);
}
#[test]
fn an_array_decays_and_is_not_read() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let array = f.types.array(int, ArrayLen::Fixed(3));
let object = f.object(array);
let decayed = f.conv().value(object);
assert_eq!(f.text(decayed), "convert array-decay : int *\n decl #0 : int [3] lvalue\n");
}
#[test]
fn a_function_decays_to_a_pointer_to_itself() {
let mut f = Fixture::new();
let void = f.types.void();
let signature =
FunctionType { ret: void, params: Vec::new(), variadic: false, prototyped: true };
let function = f.types.function(signature);
let designator =
f.tast.expr(Expr::new(ExprKind::Error, function, Category::Function), Span::DUMMY);
let decayed = f.conv().value(designator);
assert_eq!(
f.text(decayed),
"convert function-decay : void (*)(void)\n error : void (void) function\n"
);
}
#[test]
fn a_narrow_integer_promotes_and_an_int_does_not_move() {
let mut f = Fixture::new();
let char_type = f.types.int(IntKind::Char);
let int = f.types.int(IntKind::Int);
let narrow = f.object(char_type);
let wide = f.object(int);
let promoted = f.conv().promote(narrow);
assert_eq!(f.tast[promoted].ty, int);
assert_eq!(
f.text(promoted),
"convert arithmetic : int\n convert lvalue : char\n decl #0 : char lvalue\n"
);
let already = f.conv().promote(wide);
assert_eq!(f.text(already), "convert lvalue : int\n decl #1 : int lvalue\n");
}
#[test]
fn a_bit_field_promotes_by_its_width_and_not_by_its_type() {
let mut f = Fixture::new();
let unsigned = f.types.int(IntKind::UInt);
let int = f.types.int(IntKind::Int);
let three = f.object(unsigned);
let full = f.object(unsigned);
let narrow = f.conv().promote_bits(three, 3);
assert_eq!(f.tast[narrow].ty, int);
let wide = f.conv().promote_bits(full, 32);
assert_eq!(f.tast[wide].ty, unsigned);
}
#[test]
fn a_bit_field_operand_promotes_by_its_width_without_being_asked() {
let mut f = Fixture::new();
let unsigned = f.types.int(IntKind::UInt);
let int = f.types.int(IntKind::Int);
let one = f.zero(int);
let again = f.zero(int);
let narrow = f.bit_field(unsigned, 1);
let (lhs, rhs) = f.conv().usual_arithmetic(narrow, one).expect("both are arithmetic");
assert_eq!(f.tast[lhs].ty, int);
assert_eq!(f.tast[rhs].ty, int);
let full = f.bit_field(unsigned, 32);
let (lhs, rhs) = f.conv().usual_arithmetic(full, again).expect("both are arithmetic");
assert_eq!(f.tast[lhs].ty, unsigned);
assert_eq!(f.tast[rhs].ty, unsigned);
}
#[test]
fn a_bit_field_that_has_already_been_read_still_promotes_by_its_width() {
let mut f = Fixture::new();
let unsigned = f.types.int(IntKind::UInt);
let int = f.types.int(IntKind::Int);
let narrow = f.bit_field(unsigned, 1);
let read = f.conv().value(narrow);
let promoted = f.conv().promote(read);
assert_eq!(f.tast[promoted].ty, int);
}
#[test]
fn the_usual_arithmetic_conversions_move_both_sides_to_one_type() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let long = f.types.int(IntKind::Long);
let narrow = f.object(int);
let wide = f.object(long);
let (lhs, rhs) = f.conv().usual_arithmetic(narrow, wide).expect("both are arithmetic");
assert_eq!(f.tast[lhs].ty, long);
assert_eq!(f.tast[rhs].ty, long);
}
#[test]
fn a_pointer_pair_has_no_usual_arithmetic_conversion() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let pointer = f.types.pointer(int);
let left = f.object(pointer);
let right = f.object(int);
assert!(f.conv().usual_arithmetic(left, right).is_none());
}
#[test]
fn a_condition_is_a_comparison_against_zero_and_not_a_truncation() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let object = f.object(int);
let condition = f.conv().to_bool(object);
assert_eq!(
f.text(condition),
"convert bool : _Bool\n convert lvalue : int\n decl #0 : int lvalue\n"
);
}
#[test]
fn a_zero_of_any_integer_type_is_a_null_pointer_constant() {
let mut f = Fixture::new();
let long = f.types.int(IntKind::Long);
let int = f.types.int(IntKind::Int);
let pointer = f.types.pointer(int);
let zero = f.zero(long);
let null = f.conv().to_type(zero, pointer);
assert_eq!(f.text(null), "convert null-pointer : int *\n const 0 : long\n");
}
#[test]
fn a_pointer_that_is_not_a_constant_zero_is_an_ordinary_pointer_conversion() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let void = f.types.void();
let from = f.types.pointer(void);
let to = f.types.pointer(int);
let object = f.object(from);
let converted = f.conv().to_type(object, to);
assert_eq!(
f.text(converted),
"convert pointer : int *\n convert lvalue : void *\n decl #0 : void * lvalue\n"
);
}
#[test]
fn converting_to_the_type_it_already_has_writes_nothing() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let object = f.object(int);
let read = f.conv().value(object);
let again = f.conv().to_type(read, int);
assert_eq!(read, again);
}
#[test]
fn a_value_is_discarded_by_a_node_rather_than_by_being_ignored() {
let mut f = Fixture::new();
let int = f.types.int(IntKind::Int);
let object = f.object(int);
let dropped = f.conv().to_void(object);
assert_eq!(f.text(dropped), "convert void : void\n decl #0 : int lvalue\n");
}
}