use std::cell::{Cell, RefCell};
use std::collections::{BTreeSet, HashMap, HashSet};
use std::str::FromStr;
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use quote::quote_spanned;
use crate::Options;
use crate::capture::{SourceMap, SourceRange};
use crate::cfg::{BasicBlock, BlockId, Cfg, Terminator};
use crate::ir::{
self, AtomicClass, BinOp, Body, BreakTarget, Callee, CmpOp, ConstValue, Expr, ExprKind,
Function, LogicalOp, LoopId, NEVER_RAW, Place, PlaceKind, Program, RecordKind, Stmt, Storage,
Switch, Ty,
};
use crate::reloop;
pub fn generate(program: &Program, map: &SourceMap, options: &Options) -> TokenStream {
let mut cg = Codegen::new(program, map, options);
let mut out = cg.type_items();
out.extend(cg.extern_block());
for var in &program.statics {
out.extend(cg.static_item(var));
}
let mut initialisers = TokenStream::new();
for func in &program.functions {
if func.body.is_some() {
out.extend(cg.function_item(func));
if let Some(kind) = func.init_kind {
initialisers.extend(cg.init_array_item(func, kind));
}
}
}
if !initialisers.is_empty() {
out.extend(cg.init_array_guard());
out.extend(initialisers);
}
if out.is_empty() {
return out;
}
let mut items = cg.data_model_check();
if cg.uses_cleanup.get() {
items.extend(cg.cleanup_guard_item(Span::call_site()));
}
if cg.uses_arena.get() {
let span = cg.arena_span.get().unwrap_or_else(Span::call_site);
items.extend(cg.arena_items(span));
}
items.extend(cg.intrinsic_shim_items());
items.extend(out);
items
}
pub fn generate_stubs(program: &Program, map: &SourceMap, options: &Options) -> TokenStream {
let mut cg = Codegen::new(program, map, options);
let mut out = cg.type_items();
out.extend(cg.extern_block());
for var in &program.statics {
out.extend(cg.static_item(var));
}
for func in &program.functions {
if !func.is_extern() {
out.extend(cg.stub_item(func));
}
}
out
}
mod prec {
pub const BLOCK: u8 = 0;
pub const LOWEST: u8 = 0;
pub const OR: u8 = 2;
pub const AND: u8 = 3;
pub const CMP: u8 = 4;
pub const BIT_OR: u8 = 5;
pub const BIT_XOR: u8 = 6;
pub const BIT_AND: u8 = 7;
pub const SUM: u8 = 9;
pub const PRODUCT: u8 = 10;
pub const CAST: u8 = 11;
pub const UNARY: u8 = 12;
pub const CALL: u8 = 13;
pub const ATOM: u8 = 14;
}
struct Value {
tokens: TokenStream,
prec: u8,
bare_integer: bool,
ends_with_type: bool,
}
impl Value {
fn new(tokens: TokenStream, prec: u8) -> Self {
Self {
tokens,
prec,
bare_integer: false,
ends_with_type: false,
}
}
fn atom(tokens: TokenStream) -> Self {
Self::new(tokens, prec::ATOM)
}
fn type_end(mut self, flag: bool) -> Self {
self.ends_with_type = flag;
self
}
fn at(self, min: u8, span: Span) -> TokenStream {
if self.prec >= min {
return self.tokens;
}
parenthesize(self.tokens, span)
}
fn at_condition(self, span: Span) -> TokenStream {
if self.prec > prec::BLOCK {
return self.tokens;
}
parenthesize(self.tokens, span)
}
}
fn parenthesize(tokens: TokenStream, span: Span) -> TokenStream {
let mut group = Group::new(Delimiter::Parenthesis, tokens);
group.set_span(span);
TokenStream::from(TokenTree::Group(group))
}
fn starts_with_minus(tokens: &TokenStream) -> bool {
matches!(
tokens.clone().into_iter().next(),
Some(TokenTree::Punct(punct)) if punct.as_char() == '-'
)
}
fn braced(tokens: TokenStream, span: Span) -> TokenStream {
let mut group = Group::new(Delimiter::Brace, tokens);
group.set_span(span);
TokenStream::from(TokenTree::Group(group))
}
fn bracketed(tokens: TokenStream, span: Span) -> TokenStream {
let mut group = Group::new(Delimiter::Bracket, tokens);
group.set_span(span);
TokenStream::from(TokenTree::Group(group))
}
const LEGACY_STDIO_DEFINITIONS: &str = "legacy_stdio_definitions";
const LEGACY_STDIO: &[(&str, &str)] = &[
("printf", LEGACY_STDIO_DEFINITIONS),
("fprintf", LEGACY_STDIO_DEFINITIONS),
("sprintf", LEGACY_STDIO_DEFINITIONS),
("snprintf", LEGACY_STDIO_DEFINITIONS),
("vprintf", LEGACY_STDIO_DEFINITIONS),
("vfprintf", LEGACY_STDIO_DEFINITIONS),
("vsprintf", LEGACY_STDIO_DEFINITIONS),
("vsnprintf", LEGACY_STDIO_DEFINITIONS),
("scanf", LEGACY_STDIO_DEFINITIONS),
("fscanf", LEGACY_STDIO_DEFINITIONS),
("sscanf", LEGACY_STDIO_DEFINITIONS),
("vscanf", LEGACY_STDIO_DEFINITIONS),
("vfscanf", LEGACY_STDIO_DEFINITIONS),
("vsscanf", LEGACY_STDIO_DEFINITIONS),
("wprintf", LEGACY_STDIO_DEFINITIONS),
("fwprintf", LEGACY_STDIO_DEFINITIONS),
("swprintf", LEGACY_STDIO_DEFINITIONS),
("vwprintf", LEGACY_STDIO_DEFINITIONS),
("vfwprintf", LEGACY_STDIO_DEFINITIONS),
("vswprintf", LEGACY_STDIO_DEFINITIONS),
("wscanf", LEGACY_STDIO_DEFINITIONS),
("fwscanf", LEGACY_STDIO_DEFINITIONS),
("swscanf", LEGACY_STDIO_DEFINITIONS),
("vwscanf", LEGACY_STDIO_DEFINITIONS),
("vfwscanf", LEGACY_STDIO_DEFINITIONS),
("vswscanf", LEGACY_STDIO_DEFINITIONS),
];
const MSVC_RENAMED: &[(&str, &str)] = &[
("time", "_time64"),
("difftime", "_difftime64"),
("mktime", "_mktime64"),
("localtime", "_localtime64"),
("gmtime", "_gmtime64"),
("ctime", "_ctime64"),
("timespec_get", "_timespec64_get"),
("hypotf", "_hypotf"),
];
const RUST_KEYWORDS: &[&str] = &[
"abstract", "as", "async", "await", "become", "box", "break", "const", "continue", "crate",
"do", "dyn", "else", "enum", "extern", "false", "final", "fn", "for", "gen", "if", "impl",
"in", "let", "loop", "macro", "match", "mod", "move", "mut", "override", "priv", "pub", "ref",
"return", "self", "static", "struct", "super", "trait", "true", "try", "type", "typeof",
"unsafe", "unsized", "use", "virtual", "where", "while", "yield", "Self",
];
const PRELUDE_PATTERNS: &[&str] = &["Some", "None", "Ok", "Err"];
fn is_path_segment(segment: &str) -> bool {
let mut chars = segment.chars();
chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !RUST_KEYWORDS.contains(&segment)
&& !NEVER_RAW.contains(&segment)
}
pub fn is_crate_path(path: &str) -> bool {
const PATH_KEYWORDS: &[&str] = &["crate", "self", "super"];
let body = path.strip_prefix("::").unwrap_or(path);
if body.is_empty() {
return false;
}
body.split("::")
.all(|segment| PATH_KEYWORDS.contains(&segment) || is_path_segment(segment))
}
fn c_ident(name: &str, span: Span) -> Ident {
let spelled = rust_spelling(name);
let name = spelled.as_ref();
if NEVER_RAW.contains(&name) {
return Ident::new(&format!("{name}_"), span);
}
if RUST_KEYWORDS.contains(&name) {
return Ident::new_raw(name, span);
}
Ident::new(name, span)
}
pub const DOLLAR: &str = "_dollar_";
fn rust_spelling(name: &str) -> std::borrow::Cow<'_, str> {
if name.contains('$') {
std::borrow::Cow::Owned(name.replace('$', DOLLAR))
} else {
std::borrow::Cow::Borrowed(name)
}
}
fn respelled(name: &str) -> bool {
name.contains('$') || NEVER_RAW.contains(&name)
}
fn plain_spelling(name: &str) -> String {
let spelled = rust_spelling(name);
if NEVER_RAW.contains(&spelled.as_ref()) {
return format!("{spelled}_");
}
spelled.into_owned()
}
struct Names {
renamed: HashMap<String, String>,
}
impl Names {
fn new(program: &Program) -> Self {
let mut spelled: Vec<&str> = Vec::new();
for object in &program.objects {
spelled.push(&object.name);
match &object.storage {
Storage::Static { item_name, .. }
| Storage::ThreadLocal { item_name, .. }
| Storage::Extern { item_name } => spelled.push(item_name),
Storage::Automatic => {}
}
}
for func in &program.functions {
spelled.push(&func.name);
spelled.push(func.item_name());
spelled.extend(func.param_names.iter().flatten().map(String::as_str));
if let Some(Body::Cfg(cfg)) = &func.body {
spelled.extend(cfg.locals.iter().map(|local| local.rust_name.as_str()));
}
}
for record in program.types.records() {
spelled.extend(record.tag.as_deref());
spelled.push(&record.rust_name);
for field in &record.fields {
spelled.push(&field.name);
if let Some(bits) = &field.bits {
spelled.push(&bits.getter);
spelled.push(&bits.setter);
}
}
for field in &record.rust_fields {
match field {
ir::RustField::Bits { name, .. }
| ir::RustField::Pad { name, .. }
| ir::RustField::Align { name, .. } => spelled.push(name),
ir::RustField::Member(_) => {}
}
}
}
for def in program.types.enums() {
spelled.extend(def.tag.as_deref());
spelled.push(&def.rust_name);
}
for constant in &program.enum_constants {
spelled.push(&constant.name);
spelled.push(&constant.rust_name);
}
for typedef in &program.typedefs {
spelled.push(&typedef.rust_name);
}
Self {
renamed: unique_spellings(spelled),
}
}
fn ident(&self, name: &str, span: Span) -> Ident {
match self.renamed.get(name) {
Some(unique) => Ident::new(unique, span),
None => c_ident(name, span),
}
}
fn spelling<'n>(&'n self, name: &'n str) -> std::borrow::Cow<'n, str> {
match self.renamed.get(name) {
Some(unique) => std::borrow::Cow::Borrowed(unique.as_str()),
None => std::borrow::Cow::Owned(plain_spelling(name)),
}
}
}
fn unique_spellings<'n>(spelled: impl IntoIterator<Item = &'n str>) -> HashMap<String, String> {
let mut taken: HashSet<&str> = HashSet::new();
let mut changing: BTreeSet<&str> = BTreeSet::new();
for name in spelled {
if respelled(name) {
changing.insert(name);
} else {
taken.insert(name);
}
}
let mut renamed: HashMap<String, String> = HashMap::new();
let mut assigned: HashSet<String> = HashSet::new();
for name in changing {
let mut candidate = plain_spelling(name);
while taken.contains(candidate.as_str()) || assigned.contains(&candidate) {
candidate.push('_');
}
assigned.insert(candidate.clone());
renamed.insert(name.to_owned(), candidate);
}
renamed
}
fn is_label_name(name: &str) -> bool {
let mut chars = name.chars();
let starts = chars
.next()
.is_some_and(|c| c.is_ascii_alphabetic() || c == '_');
starts
&& chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
&& !RUST_KEYWORDS.contains(&name)
&& !NEVER_RAW.contains(&name)
&& !is_generated_label(name)
}
fn is_generated_label(name: &str) -> bool {
fn digits(text: &str) -> bool {
!text.is_empty() && text.bytes().all(|b| b.is_ascii_digit())
}
if name == "cfg" {
return true;
}
if let Some(rest) = name.strip_prefix('l')
&& digits(rest.strip_suffix("_body").unwrap_or(rest))
{
return true;
}
for prefix in ['b', 'r'] {
if let Some(rest) = name.strip_prefix(prefix)
&& digits(rest)
{
return true;
}
}
if let Some(rest) = name.strip_prefix("sw") {
if digits(rest) {
return true;
}
if let Some((switch, case)) = rest.split_once("_c") {
return digits(switch) && digits(case);
}
}
false
}
fn collect_regions(stmts: &[Stmt], out: &mut Vec<(ir::LabelId, String)>) {
for stmt in stmts {
match stmt {
Stmt::Region(region) => {
out.push((region.label, region.name.clone()));
collect_regions(®ion.body, out);
}
Stmt::Block(items) => collect_regions(items, out),
Stmt::Label { body, .. } | Stmt::Case { body, .. } => {
collect_regions(std::slice::from_ref(body), out);
}
Stmt::If {
then_branch,
else_branch,
..
} => {
collect_regions(std::slice::from_ref(then_branch), out);
if let Some(branch) = else_branch {
collect_regions(std::slice::from_ref(branch), out);
}
}
Stmt::While { body, .. } | Stmt::DoWhile { body, .. } => {
collect_regions(std::slice::from_ref(body), out);
}
Stmt::For { init, body, .. } => {
collect_regions(init, out);
collect_regions(std::slice::from_ref(body), out);
}
Stmt::Switch(switch) => {
collect_regions(&switch.prelude, out);
for group in &switch.groups {
collect_regions(&group.body, out);
}
}
Stmt::SwitchTree(switch) => collect_regions(std::slice::from_ref(&switch.body), out),
_ => {}
}
}
}
fn label_state(expr: &Expr) -> Option<ir::LabelId> {
match &expr.kind {
ExprKind::LabelAddr(id) => Some(*id),
ExprKind::Cast(inner) => label_state(inner),
_ => None,
}
}
fn align_wrapper_ident(align: u64, span: Span) -> Ident {
Ident::new(&format!("__cinrs_align_{align}"), span)
}
fn array_len(types: &ir::Types, ty: Ty) -> Option<u64> {
match ty {
Ty::Array(id) => Some(types.array_type(id).len),
_ => None,
}
}
fn cleanup_guard_ty() -> Ident {
Ident::new("__cinrs_cleanup", Span::mixed_site())
}
fn arena_ty() -> Ident {
Ident::new("__cinrs_vla_arena", Span::mixed_site())
}
fn vla_marks_ident() -> Ident {
Ident::new("__cinrs_vla_marks", Span::mixed_site())
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum ContinueStyle {
Head,
BodyLabel,
}
struct LoweredPlace {
setup: TokenStream,
access: TokenStream,
bits: Option<BitAccess>,
unaligned: bool,
atomic: Option<(AtomicClass, Ty)>,
}
impl LoweredPlace {
fn plain(setup: TokenStream, access: TokenStream) -> Self {
Self {
setup,
access,
bits: None,
unaligned: false,
atomic: None,
}
}
}
enum PlaceRmw<'a> {
Compound {
op: BinOp,
value: &'a Expr,
compute: Ty,
},
Step {
dec: bool,
},
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RmwValue {
None,
Old,
New,
}
fn rmw_of_binop(op: BinOp) -> Option<ir::AtomicRmw> {
Some(match op {
BinOp::Add => ir::AtomicRmw::Add,
BinOp::Sub => ir::AtomicRmw::Sub,
BinOp::BitAnd => ir::AtomicRmw::And,
BinOp::BitOr => ir::AtomicRmw::Or,
BinOp::BitXor => ir::AtomicRmw::Xor,
_ => return None,
})
}
struct BitAccess {
getter: Ident,
setter: Ident,
}
struct BitWindow {
read: TokenStream,
shift: u32,
mask: u128,
word: TokenStream,
word_bits: u32,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum VaSource {
None,
Ellipsis,
Param(ir::ObjectId),
PtrParam(ir::ObjectId),
}
struct Codegen<'a> {
program: &'a Program,
map: &'a SourceMap,
options: &'a Options,
continue_styles: HashMap<LoopId, ContinueStyle>,
local_names: HashMap<ir::ObjectId, String>,
env: HashMap<ir::ObjectId, ir::ObjectId>,
reserved: HashSet<String>,
names: Names,
va_source: VaSource,
ret_ty: Ty,
in_cfg: bool,
in_safe: bool,
temporaries: u32,
uses_int128: Cell<bool>,
uses_complex: Cell<bool>,
uses_cleanup: Cell<bool>,
uses_arena: Cell<bool>,
arena_span: Cell<Option<Span>>,
vla_slots: HashMap<ir::ObjectId, usize>,
address_taken: RefCell<Vec<ir::FuncId>>,
label_states: HashMap<ir::LabelId, u32>,
region_names: HashMap<ir::LabelId, String>,
region_kinds: HashMap<ir::LabelId, ir::RegionKind>,
loop_names: HashMap<u32, String>,
shape_scopes: Vec<ShapeScope>,
shape_labels: u32,
used_labels: HashSet<u32>,
}
#[derive(Clone)]
struct ShapeScope {
id: u32,
name: String,
repeats: bool,
exit: reloop::Exit,
}
impl<'a> Codegen<'a> {
fn new(program: &'a Program, map: &'a SourceMap, options: &'a Options) -> Self {
let mut reserved: HashSet<String> =
PRELUDE_PATTERNS.iter().map(|s| (*s).to_owned()).collect();
for var in &program.statics {
if let Some(item_name) = program.object(var.object).storage.item_name() {
reserved.insert(item_name.to_owned());
}
}
for constant in &program.enum_constants {
reserved.insert(constant.rust_name.clone());
}
let mut label_states = HashMap::new();
for func in &program.functions {
if let Some(ir::Body::Cfg(cfg)) = &func.body {
label_states.extend(cfg.labels.iter().map(|(id, number)| (*id, *number)));
}
}
Self {
program,
map,
options,
continue_styles: HashMap::new(),
local_names: HashMap::new(),
env: HashMap::new(),
reserved,
names: Names::new(program),
va_source: VaSource::None,
ret_ty: Ty::Void,
in_cfg: false,
in_safe: false,
temporaries: 0,
uses_int128: Cell::new(false),
uses_complex: Cell::new(false),
uses_cleanup: Cell::new(false),
uses_arena: Cell::new(false),
arena_span: Cell::new(None),
vla_slots: HashMap::new(),
address_taken: RefCell::new(Vec::new()),
label_states,
region_names: HashMap::new(),
region_kinds: HashMap::new(),
loop_names: HashMap::new(),
shape_scopes: Vec::new(),
shape_labels: 0,
used_labels: HashSet::new(),
}
}
fn uses_va_list(&self, ty: Ty) -> bool {
match ty {
Ty::VaList => true,
Ty::Pointer(id) => self.uses_va_list(self.program.types.pointer_type(id).pointee),
Ty::Array(id) => self.uses_va_list(self.program.types.array_type(id).elem),
Ty::Func(id) => {
let func = self.program.types.func_type(id);
self.uses_va_list(func.ret) || func.params.iter().any(|ty| self.uses_va_list(*ty))
}
_ => false,
}
}
fn sp(&self, range: SourceRange) -> Span {
self.map.span(range)
}
fn c_ident(&self, name: &str, span: Span) -> Ident {
self.names.ident(name, span)
}
fn flexible_ident(&self, rust_name: &str, len: u64, span: Span) -> Ident {
Ident::new(
&format!("__cinrs_{}_{len}", self.names.spelling(rust_name)),
span,
)
}
fn extern_object_ident(&self, symbol: &str, span: Span) -> Ident {
let spelling = self.names.spelling(symbol);
Ident::new(&self.program.extern_object_name(&spelling), span)
}
fn temporary(&mut self) -> Ident {
let name = format!("__cinrs_tmp{}", self.temporaries);
self.temporaries += 1;
Ident::new(&name, Span::mixed_site())
}
fn temporary_at(&mut self, span: Span) -> Ident {
let name = format!("__cinrs_tmp{}", self.temporaries);
self.temporaries += 1;
Ident::new(&name, span.resolved_at(Span::mixed_site()))
}
fn label(&self, name: &str, span: Span) -> TokenStream {
let mut tick = Punct::new('\'', Spacing::Joint);
tick.set_span(span);
let mut out = TokenStream::new();
out.extend([
TokenTree::Punct(tick),
TokenTree::Ident(Ident::new(name, span)),
]);
out
}
fn ty(&self, ty: Ty, span: Span) -> TokenStream {
let name = match ty {
Ty::Void | Ty::Error => return quote_spanned! {span=> () },
Ty::Bool => return primitive_ty("bool", span),
Ty::Char => "c_char",
Ty::SChar => "c_schar",
Ty::UChar => "c_uchar",
Ty::Short => "c_short",
Ty::UShort => "c_ushort",
Ty::Int => "c_int",
Ty::UInt => "c_uint",
Ty::Long => "c_long",
Ty::ULong => "c_ulong",
Ty::LongLong => "c_longlong",
Ty::ULongLong => "c_ulonglong",
Ty::Int128 => {
self.uses_int128.set(true);
return primitive_ty("i128", span);
}
Ty::UInt128 => {
self.uses_int128.set(true);
return primitive_ty("u128", span);
}
Ty::Float => "c_float",
Ty::Double => "c_double",
Ty::ComplexFloat | Ty::ComplexDouble => {
self.uses_complex.set(true);
let component =
primitive_ty(if ty == Ty::ComplexFloat { "f32" } else { "f64" }, span);
let rt = self.rt_path(span);
return quote_spanned! {span=> #rt::Complex<#component> };
}
Ty::VaList => "VaList",
Ty::Vector(vec) => {
let module = self.arch_module(span);
let name = Ident::new(vec.name(), span);
return quote_spanned! {span=> ::core::arch::#module::#name };
}
Ty::Pointer(id) => {
let pointer = self.program.types.pointer_type(id);
if let Ty::Func(func) = pointer.pointee {
let signature = self.fn_ty(func, span);
return quote_spanned! {span=> ::core::option::Option<#signature> };
}
let pointee = self.pointee_ty(pointer.pointee, span);
return if pointer.konst {
quote_spanned! {span=> *const #pointee }
} else {
quote_spanned! {span=> *mut #pointee }
};
}
Ty::Array(id) => {
if self.program.types.is_vm(ty) {
let step = self.ty(self.program.types.vm_step_ty(ty), span);
return quote_spanned! {span=> *mut #step };
}
let array = self.program.types.array_type(id);
let elem = self.ty(array.elem, span);
let len = usize_literal(array.len, span);
let inner = quote_spanned! {span=> #elem ; #len };
return bracketed(inner, span);
}
Ty::Func(id) => return self.fn_ty(id, span),
Ty::Record(id) => {
let name = self.c_ident(&self.program.types.record(id).rust_name, span);
return quote_spanned! {span=> #name };
}
Ty::Enum(id) => {
let name = self.c_ident(&self.program.types.enum_def(id).rust_name, span);
return quote_spanned! {span=> #name };
}
Ty::Atomic(id) => {
let inner = self.program.types.atomic_inner(id);
return self.ty(inner, span);
}
};
let ident = Ident::new(name, span);
quote_spanned! {span=> ::core::ffi::#ident }
}
fn arch_module(&self, span: Span) -> Ident {
let name = if self.options.target.arch == crate::target::Arch::X86 {
"x86"
} else {
"x86_64"
};
Ident::new(name, span)
}
fn rt_path(&self, span: Span) -> TokenStream {
let path = TokenStream::from_str(&self.program.crate_path)
.unwrap_or_else(|_| TokenStream::from_str(ir::DEFAULT_CRATE_PATH).expect("valid"));
let path = respan(path, span);
quote_spanned! {span=> #path::rt }
}
fn rt_complex(&self, name: &str, span: Span) -> TokenStream {
let rt = self.rt_path(span);
let ident = Ident::new(name, span);
quote_spanned! {span=> #rt::complex::#ident }
}
fn complex_suffix(ty: Ty) -> &'static str {
if ty.complex_component() == Ty::Float {
"f32"
} else {
"f64"
}
}
fn fn_ty(&self, id: ir::FuncTyId, span: Span) -> TokenStream {
let func = self.program.types.func_type(id).clone();
self.fn_ptr_ty(&func.params, func.variadic, func.ret, span)
}
fn alloc_crate(&self, span: Span) -> Ident {
let name = if self.program.no_std { "alloc" } else { "std" };
Ident::new(name, span)
}
fn vec_ty(&self, elem: TokenStream, span: Span) -> TokenStream {
let krate = self.alloc_crate(span);
quote_spanned! {span=> ::#krate::vec::Vec<#elem> }
}
fn vec_new(&self, span: Span) -> TokenStream {
let krate = self.alloc_crate(span);
quote_spanned! {span=> ::#krate::vec::Vec::new() }
}
fn vec_of(&self, value: TokenStream, len: TokenStream, span: Span) -> TokenStream {
let krate = self.alloc_crate(span);
quote_spanned! {span=> ::#krate::vec::from_elem(#value, #len) }
}
fn pointee_ty(&self, ty: Ty, span: Span) -> TokenStream {
if ty.is_void() {
let ident = Ident::new("c_void", span);
return quote_spanned! {span=> ::core::ffi::#ident };
}
if self.program.types.is_vm(ty) {
return self.ty(self.program.types.vm_step_ty(ty), span);
}
self.ty(ty, span)
}
fn type_items(&mut self) -> TokenStream {
let mut out = self.align_wrapper_items();
for record in self.program.types.records() {
if !record.emit {
continue;
}
out.extend(self.record_item(record));
}
out.extend(self.flexible_items());
for def in self.program.types.enums() {
if !def.emit {
continue;
}
let span = self.sp(def.range);
let name = self.c_ident(&def.rust_name, span);
let int = self.ty(Ty::Int, span);
out.extend(quote_spanned! {span=> pub type #name = #int; });
}
for constant in &self.program.enum_constants {
let span = self.sp(constant.range);
let name = self.c_ident(&constant.rust_name, span);
let ty = self.ty(constant.ty, span);
let value = bare_int_literal(constant.value, constant.ty, span);
out.extend(quote_spanned! {span=> pub const #name: #ty = #value; });
}
for typedef in &self.program.typedefs {
if self.uses_va_list(typedef.ty) {
continue;
}
let span = self.sp(typedef.range);
let name = self.c_ident(&typedef.rust_name, span);
let ty = self.ty(typedef.ty, span);
out.extend(quote_spanned! {span=> pub type #name = #ty; });
}
out
}
fn align_wrapper_items(&self) -> TokenStream {
let mut wanted: Vec<(u64, SourceRange)> = self
.program
.objects
.iter()
.filter_map(|object| Some((object.align?, object.range)))
.collect();
wanted.sort_by_key(|(align, _)| *align);
wanted.dedup_by_key(|(align, _)| *align);
let mut out = TokenStream::new();
for (align, range) in wanted {
let span = self.sp(range);
let name = align_wrapper_ident(align, span);
let literal = Literal::u64_unsuffixed(align);
out.extend(quote_spanned! {span=>
#[repr(C, align(#literal))]
#[derive(Copy, Clone)]
pub struct #name<T>(pub T);
});
}
out
}
fn flexible_items(&self) -> TokenStream {
let mut wanted: Vec<(ir::RecordId, u64)> = self
.program
.objects
.iter()
.filter_map(|object| match (object.flexible_len, object.ty) {
(Some(len), Ty::Record(record)) => Some((record, len)),
_ => None,
})
.collect();
wanted.sort_unstable_by_key(|(record, len)| (record.0, *len));
wanted.dedup_by_key(|(record, len)| (record.0, *len));
let mut out = TokenStream::new();
for (record, len) in wanted {
let def = self.program.types.record(record);
let span = self.sp(def.range);
let name = self.flexible_ident(&def.rust_name, len, span);
out.extend(self.record_body(def, &name, Some(len)));
}
out
}
fn record_item(&self, record: &ir::RecordDef) -> TokenStream {
let span = self.sp(record.range);
let name = self.c_ident(&record.rust_name, span);
let item = self.record_body(record, &name, None);
let accessors = self.bit_field_accessors(record, span);
quote_spanned! {span=> #item #accessors }
}
fn record_body(&self, record: &ir::RecordDef, name: &Ident, tail: Option<u64>) -> TokenStream {
let span = self.sp(record.range);
let derives = match (record.align, record.packed) {
(_, Some(1)) => quote_spanned! {span=> #[repr(C, packed)] #[derive(Copy, Clone)] },
(_, Some(pack)) => {
let pack = usize_literal(pack, span);
quote_spanned! {span=>
#[repr(C, packed(#pack))] #[derive(Copy, Clone)]
}
}
(Some(align), None) => {
let align = usize_literal(align, span);
quote_spanned! {span=>
#[repr(C, align(#align))] #[derive(Copy, Clone)]
}
}
(None, None) => quote_spanned! {span=> #[repr(C)] #[derive(Copy, Clone)] },
};
let byte = primitive_ty("u8", span);
if !record.complete {
return quote_spanned! {span=>
#derives pub struct #name { _incomplete: [#byte; 0] }
};
}
let mut fields = TokenStream::new();
for rust_field in &record.rust_fields {
match rust_field {
ir::RustField::Member(index) => {
let field = &record.fields[*index];
let fspan = self.sp(field.range);
let fname = self.c_ident(&field.name, fspan);
let fty = match (tail, field.flexible) {
(Some(len), true) => {
let elem = self
.program
.types
.elem(field.ty)
.expect("a flexible member is an array");
let elem = self.ty(elem, fspan);
let len = usize_literal(len, fspan);
bracketed(quote_spanned! {fspan=> #elem ; #len }, fspan)
}
_ => self.ty(field.ty, fspan),
};
fields.extend(quote_spanned! {fspan=> pub #fname: #fty, });
}
ir::RustField::Bits { name, bytes, .. } | ir::RustField::Pad { name, bytes } => {
let fname = Ident::new(name, span);
let len = usize_literal(*bytes, span);
fields.extend(quote_spanned! {span=> pub #fname: [#byte; #len], });
}
ir::RustField::Align { name, align } => {
let fname = Ident::new(name, span);
let unit = unsigned_rust_ty((*align * 8) as u32, span);
fields.extend(quote_spanned! {span=> pub #fname: [#unit; 0], });
}
}
}
if record.rust_fields.is_empty() && record.kind == RecordKind::Union {
fields.extend(quote_spanned! {span=> pub __cinrs_empty: [#byte; 0], });
}
let body = braced(fields, span);
match record.kind {
RecordKind::Struct => quote_spanned! {span=> #derives pub struct #name #body },
RecordKind::Union => quote_spanned! {span=> #derives pub union #name #body },
}
}
fn bit_field_accessors(&self, record: &ir::RecordDef, span: Span) -> TokenStream {
let mut methods = TokenStream::new();
for field in &record.fields {
let Some(bits) = &field.bits else {
continue;
};
let fspan = self.sp(field.range);
methods.extend(self.bit_field_getter(record, field, bits, fspan));
methods.extend(self.bit_field_setter(record, field, bits, fspan));
}
if methods.is_empty() {
return TokenStream::new();
}
let name = self.c_ident(&record.rust_name, span);
quote_spanned! {span=> impl #name { #methods } }
}
fn bit_field_window(&self, bits: &ir::BitField, span: Span) -> BitWindow {
let storage = Ident::new(&bits.storage, span);
let start = bits.offset_in_storage();
let first = start / 8;
let shift = (start % 8) as u32;
let count = (shift + bits.width).div_ceil(8);
let word_bits: u32 = if shift + bits.width > 64 { 128 } else { 64 };
let word = window_ty(word_bits, false, span);
let mut read = TokenStream::new();
for step in 0..count {
let index = usize_literal(first + u64::from(step), span);
let byte = if count == 1 {
quote_spanned! {span=> self.#storage[#index] as #word }
} else {
quote_spanned! {span=> (self.#storage[#index] as #word) }
};
read.extend(if step == 0 {
byte
} else {
let by = usize_literal(u64::from(step) * 8, span);
quote_spanned! {span=> | (#byte << #by) }
});
}
let mask = mask_of(shift + bits.width, word_bits) & !mask_of(shift, word_bits);
BitWindow {
read,
shift,
mask,
word,
word_bits,
}
}
fn bit_field_getter(
&self,
record: &ir::RecordDef,
field: &ir::Field,
bits: &ir::BitField,
span: Span,
) -> TokenStream {
let BitWindow {
read,
shift,
word,
word_bits,
..
} = self.bit_field_window(bits, span);
let ty = self.ty(field.ty, span);
let name = self.c_ident(&bits.getter, span);
let mask = word_literal(mask_of(bits.width, word_bits), word_bits, span);
let shifted = if shift == 0 {
quote_spanned! {span=> raw & #mask }
} else {
let by = usize_literal(u64::from(shift), span);
quote_spanned! {span=> (raw >> #by) & #mask }
};
let value = if field.ty.is_bool() {
quote_spanned! {span=> value != 0 }
} else if !bits.signed || bits.width == word_bits {
quote_spanned! {span=> value as #ty }
} else {
let signed = window_ty(word_bits, true, span);
let by = usize_literal(u64::from(word_bits - bits.width), span);
quote_spanned! {span=> (((value << #by) as #signed) >> #by) as #ty }
};
let body = self.accessor_body(
record,
quote_spanned! {span=>
let raw: #word = #read;
let value: #word = #shifted;
#value
},
span,
);
quote_spanned! {span=>
#[inline]
pub fn #name(&self) -> #ty { #body }
}
}
fn bit_field_setter(
&self,
record: &ir::RecordDef,
field: &ir::Field,
bits: &ir::BitField,
span: Span,
) -> TokenStream {
let BitWindow {
read,
shift,
mask,
word,
word_bits,
} = self.bit_field_window(bits, span);
let ty = self.ty(field.ty, span);
let name = self.c_ident(&bits.setter, span);
let storage = Ident::new(&bits.storage, span);
let value = Ident::new("value", span);
let field_mask = word_literal(mask, word_bits, span);
let keep = word_literal(!mask & mask_of(word_bits, word_bits), word_bits, span);
let shifted = if shift == 0 {
quote_spanned! {span=> (#value as #word) & #field_mask }
} else {
let by = usize_literal(u64::from(shift), span);
quote_spanned! {span=> ((#value as #word) << #by) & #field_mask }
};
let start = bits.offset_in_storage();
let first = start / 8;
let count = (shift + bits.width).div_ceil(8);
let mut writes = TokenStream::new();
let byte = primitive_ty("u8", span);
for step in 0..count {
let index = usize_literal(first + u64::from(step), span);
if step == 0 {
writes.extend(quote_spanned! {span=> self.#storage[#index] = raw as #byte; });
} else {
let by = usize_literal(u64::from(step) * 8, span);
writes.extend(
quote_spanned! {span=> self.#storage[#index] = (raw >> #by) as #byte; },
);
}
}
let body = self.accessor_body(
record,
quote_spanned! {span=>
let bits: #word = #shifted;
let raw: #word = #read;
let raw: #word = (raw & #keep) | bits;
#writes
},
span,
);
quote_spanned! {span=>
#[inline]
pub fn #name(&mut self, #value: #ty) { #body }
}
}
fn accessor_body(&self, record: &ir::RecordDef, body: TokenStream, span: Span) -> TokenStream {
if record.kind == RecordKind::Union {
let block = braced(body, span);
return quote_spanned! {span=> unsafe #block };
}
body
}
fn data_model_check(&self) -> TokenStream {
let span = self.map.span(SourceRange::at(0));
let target = &self.options.target;
let chosen = format!(
"Translated for {}; set CINRS_TARGET from a build script \
(cargo:rustc-env=CINRS_TARGET=$TARGET) or write #pragma cinrs target.",
target.describe(&self.options.target_source)
);
let mut body = TokenStream::new();
let mut width = |ty: TokenStream, bits: u32, what: &str| {
let bytes = usize_literal(u64::from(bits).div_ceil(8), span);
let message = message_literal(
&format!(
"cinrs: {what} is {} bytes in the data model this unit was translated for, \
and is not on this target. {chosen}",
bits.div_ceil(8)
),
span,
);
body.extend(quote_spanned! {span=>
assert!(::core::mem::size_of::<#ty>() == #bytes, #message);
});
};
width(
quote_spanned! {span=> ::core::ffi::c_short },
target.short_bits,
"'short'",
);
width(
quote_spanned! {span=> ::core::ffi::c_int },
target.int_bits,
"'int'",
);
width(
quote_spanned! {span=> ::core::ffi::c_long },
target.long_bits,
"'long'",
);
width(
quote_spanned! {span=> ::core::ffi::c_longlong },
target.long_long_bits,
"'long long'",
);
width(
quote_spanned! {span=> *const ::core::ffi::c_void },
target.ptr_bits,
"a pointer",
);
let (test, said) = if target.char_signed {
(
quote_spanned! {span=> ::core::ffi::c_char::MIN != 0 },
"signed",
)
} else {
(
quote_spanned! {span=> ::core::ffi::c_char::MIN == 0 },
"unsigned",
)
};
let message = message_literal(
&format!(
"cinrs: plain 'char' is {said} in the data model this unit was translated \
for, and is not on this target. {chosen}"
),
span,
);
body.extend(quote_spanned! {span=> assert!(#test, #message); });
let align = usize_literal(target.max_scalar_align.min(8), span);
let message = message_literal(
&format!(
"cinrs: 'long long' and 'double' are {}-byte aligned in the data model this \
unit was translated for, and are not on this target — so every 'sizeof' and \
member offset in it would be wrong. {chosen}",
target.max_scalar_align.min(8)
),
span,
);
body.extend(quote_spanned! {span=>
assert!(
::core::mem::align_of::<::core::ffi::c_longlong>() == #align
&& ::core::mem::align_of::<::core::ffi::c_double>() == #align,
#message
);
});
if self.uses_int128.get() {
let align = usize_literal(target.int128_align, span);
let message = message_literal(
&format!(
"cinrs: '__int128' is {}-byte aligned in the data model this unit was \
translated for, and is not on this target. {chosen}",
target.int128_align
),
span,
);
let i128 = primitive_ty("i128", span);
body.extend(quote_spanned! {span=>
assert!(::core::mem::align_of::<#i128>() == #align, #message);
});
}
if self.uses_complex.get() {
for (ty, name) in [
(Ty::ComplexFloat, "'float _Complex'"),
(Ty::ComplexDouble, "'double _Complex'"),
] {
let layout = self
.program
.types
.size_align(ty, target)
.expect("a complex type has a layout");
let rust = self.ty(ty, span);
let size = usize_literal(layout.size, span);
let align = usize_literal(layout.align, span);
let message = message_literal(
&format!(
"cinrs: {name} is {} bytes and {}-byte aligned in the data model this \
unit was translated for, and is not on this target. {chosen}",
layout.size, layout.align
),
span,
);
body.extend(quote_spanned! {span=>
assert!(
::core::mem::size_of::<#rust>() == #size
&& ::core::mem::align_of::<#rust>() == #align,
#message
);
});
}
}
let block = braced(body, span);
quote_spanned! {span=> const _: () = #block; }
}
fn extern_block(&mut self) -> TokenStream {
if !self.program.has_externs() {
return TokenStream::new();
}
let span = self.map.span(SourceRange::at(0));
let mut items = TokenStream::new();
let mut symbols: Vec<&str> = Vec::new();
for id in &self.program.externs {
let object = self.program.object(*id);
let Storage::Extern { item_name } = &object.storage else {
continue;
};
let ospan = self.sp(object.range);
let rust_name = self.extern_object_ident(item_name, ospan);
let ty = self.ty(object.ty, ospan);
let symbol = object.asm_label.as_deref().unwrap_or(item_name);
symbols.push(symbol);
let link = link_name(symbol, ospan);
items.extend(quote_spanned! {ospan=> #link pub static mut #rust_name: #ty; });
}
for func in &self.program.functions {
if !func.is_extern() {
continue;
}
if func.intrinsic.is_some() {
continue;
}
let fspan = self.sp(func.range);
let rust_name = self.c_ident(func.item_name(), fspan);
let params = self.extern_params(func, fspan);
let ret = if func.sig.ret.is_void() {
TokenStream::new()
} else {
let ty = self.ty(func.sig.ret, fspan);
quote_spanned! {fspan=> -> #ty }
};
let symbol = match func.asm_label.as_deref() {
Some(label) => label,
None => self.msvc_symbol(&func.name),
};
symbols.push(symbol);
let link = link_name(symbol, fspan);
items.extend(quote_spanned! {fspan=> #link pub fn #rust_name(#params) #ret; });
}
let links = self.link_blocks(&symbols, span);
quote_spanned! {span=> #links unsafe extern "C" { #items } }
}
fn link_blocks(&self, symbols: &[&str], span: Span) -> TokenStream {
let mut libraries: Vec<&str> = Vec::new();
for name in &self.program.link_libraries {
if !libraries.contains(&name.as_str()) {
libraries.push(name);
}
}
for library in self.legacy_stdio_libraries(symbols) {
if !libraries.contains(&library) {
libraries.push(library);
}
}
let mut out = TokenStream::new();
for name in libraries {
let mut literal = Literal::string(name);
literal.set_span(span);
out.extend(quote_spanned! {span=> #[link(name = #literal)] unsafe extern "C" {} });
}
out
}
fn msvc_symbol<'name>(&self, name: &'name str) -> &'name str {
if !self.options.target.is_msvc() {
return name;
}
for (c_name, symbol) in MSVC_RENAMED {
if *c_name == name {
return symbol;
}
}
name
}
fn legacy_stdio_libraries(&self, symbols: &[&str]) -> Vec<&'static str> {
if !self.options.target.is_msvc() {
return Vec::new();
}
let mut out: Vec<&'static str> = Vec::new();
for (symbol, library) in LEGACY_STDIO {
if symbols.contains(symbol) && !out.contains(library) {
out.push(library);
}
}
out
}
fn extern_params(&self, func: &Function, span: Span) -> TokenStream {
let mut params = TokenStream::new();
for (index, ty) in func.sig.params.iter().enumerate() {
if index > 0 {
params.extend(quote_spanned! {span=> , });
}
let ty = self.ty(*ty, span);
match func.param_names.get(index).and_then(|n| n.as_ref()) {
Some(name) => {
let name = self.c_ident(name, span);
params.extend(quote_spanned! {span=> #name: #ty });
}
None => params.extend(quote_spanned! {span=> _: #ty }),
}
}
if func.sig.variadic {
if !func.sig.params.is_empty() {
params.extend(quote_spanned! {span=> , });
}
params.extend(quote_spanned! {span=> ... });
}
params
}
fn static_item(&mut self, var: &ir::StaticVar) -> TokenStream {
let object = self.program.object(var.object);
let span = self.sp(object.range);
if object.storage.is_thread_local() {
return self.thread_local_item(var);
}
let Storage::Static {
item_name,
exported,
} = &object.storage
else {
return TokenStream::new();
};
let name = self.c_ident(item_name, span);
let ty = self.binding_ty(var.object, self.storage_ty(var.object, span), span);
let init = self.static_init(&var.init, object.ty, span);
let init = self.binding_init(var.object, init, span);
let (vis, export) = if *exported {
let export = if self.program.export {
let symbol = object.asm_label.as_deref().unwrap_or(&object.name);
export_attr(symbol, &name, span)
} else {
TokenStream::new()
};
(quote_spanned! {span=> pub }, export)
} else {
(TokenStream::new(), TokenStream::new())
};
let section = match &object.section {
Some(section) => {
let mut literal = Literal::string(section);
literal.set_span(span);
quote_spanned! {span=> #[unsafe(link_section = #literal)] }
}
None => TokenStream::new(),
};
quote_spanned! {span=>
#export
#section
#vis static mut #name: #ty = #init;
}
}
fn thread_local_item(&mut self, var: &ir::StaticVar) -> TokenStream {
let object = self.program.object(var.object);
let span = self.sp(object.range);
let Storage::ThreadLocal {
item_name,
exported,
} = &object.storage
else {
return TokenStream::new();
};
if self.program.no_std {
return TokenStream::new();
}
let name = self.c_ident(item_name, span);
let ty = self.binding_ty(var.object, self.storage_ty(var.object, span), span);
let init = self.static_init(&var.init, object.ty, span);
let init = self.binding_init(var.object, init, span);
let vis = if *exported {
quote_spanned! {span=> pub }
} else {
TokenStream::new()
};
let cell = quote_spanned! {span=> ::core::cell::UnsafeCell<#ty> };
let value = quote_spanned! {span=> ::core::cell::UnsafeCell::new(#init) };
let value = if self.const_initialisable(&var.init) {
quote_spanned! {span=> const { #value } }
} else {
value
};
quote_spanned! {span=>
::std::thread_local! {
#vis static #name: #cell = #value;
}
}
}
fn const_initialisable(&self, expr: &Expr) -> bool {
match &expr.kind {
ExprKind::FuncAddr(_) => false,
ExprKind::AddrOf(place) | ExprKind::Load(place) => match &place.kind {
PlaceKind::Str(id) => {
let elem = self.program.string(*id).elem;
elem.size_bytes(&self.options.target) == 1
}
_ => !rooted_in_static(place, self.program),
},
ExprKind::Cast(inner) => self.const_initialisable(inner),
ExprKind::PtrOffset { ptr, .. } => self.const_initialisable(ptr),
ExprKind::RecordLit { fields, .. } => {
fields.iter().all(|f| self.const_initialisable(f))
}
ExprKind::UnionLit { value, .. } => self.const_initialisable(value),
ExprKind::ArrayLit(items) => items.iter().all(|i| self.const_initialisable(i)),
ExprKind::ArrayRepeat { value, .. } => self.const_initialisable(value),
_ => true,
}
}
fn static_init(&mut self, expr: &Expr, ty: Ty, span: Span) -> TokenStream {
let tokens = self.expr_at(expr, ty);
if needs_unsafe(&self.program.types, expr) {
let block = braced(tokens, span);
return quote_spanned! {span=> unsafe #block };
}
tokens
}
fn signature(&mut self, func: &Function) -> TokenStream {
let span = self.sp(func.range);
let name = self.c_ident(func.item_name(), span);
let mut params = TokenStream::new();
for entry in &func.env {
let object = self.program.object(entry.param);
let pspan = self.sp(object.range);
let pname = self.object_ident(entry.param, pspan);
let pty = self.ty(object.ty, pspan);
params.extend(quote_spanned! {pspan=> #pname: #pty , });
}
let named = func.params.len() == func.sig.params.len();
for (index, ty) in func.sig.params.iter().enumerate() {
if index > 0 {
params.extend(quote_spanned! {span=> , });
}
if named {
let id = func.params[index];
let object = self.program.object(id);
let pspan = self.sp(object.range);
let pname = self.object_ident(id, pspan);
let pty = self.ty(*ty, pspan);
params.extend(quote_spanned! {pspan=> mut #pname: #pty });
} else {
let pname = Ident::new(&format!("__cinrs_arg{index}"), Span::mixed_site());
let pty = self.ty(*ty, span);
params.extend(quote_spanned! {span=> #pname: #pty });
}
}
if func.sig.variadic {
if !func.sig.params.is_empty() {
params.extend(quote_spanned! {span=> , });
}
let name = self.va_ident();
params.extend(quote_spanned! {span=> #name: ... });
}
let ret = if func.sig.ret.is_void() {
TokenStream::new()
} else {
let ty = self.ty(func.sig.ret, span);
quote_spanned! {span=> -> #ty }
};
let exported = !func.is_static && self.program.export;
let vis = if func.is_static {
TokenStream::new()
} else {
quote_spanned! {span=> pub }
};
let export = if exported {
let symbol = func.asm_label.as_deref().unwrap_or(&func.name);
export_attr(symbol, &name, span)
} else {
TokenStream::new()
};
let helper = func.inline_helper();
let inline = match func.inline_hint {
Some(_) if exported => TokenStream::new(),
_ if helper => quote_spanned! {span=> #[inline(always)] },
Some(ir::InlineHint::Always) if !func.target_features.is_empty() => {
quote_spanned! {span=> #[inline] }
}
Some(ir::InlineHint::Always) => quote_spanned! {span=> #[inline(always)] },
Some(ir::InlineHint::Never) => quote_spanned! {span=> #[inline(never)] },
None if func.is_inline && !exported => quote_spanned! {span=> #[inline] },
None => TokenStream::new(),
};
let cold = if func.cold {
quote_spanned! {span=> #[cold] }
} else {
TokenStream::new()
};
let deprecated = match &func.deprecated {
Some(Some(message)) => {
let mut literal = Literal::string(message);
literal.set_span(span);
quote_spanned! {span=> #[deprecated(note = #literal)] }
}
Some(None) => quote_spanned! {span=> #[deprecated] },
None => TokenStream::new(),
};
let section = match &func.section {
Some(section) => {
let mut literal = Literal::string(section);
literal.set_span(span);
quote_spanned! {span=> #[unsafe(link_section = #literal)] }
}
None => TokenStream::new(),
};
let target_feature = if helper {
TokenStream::new()
} else {
self.target_feature_attrs(func, span)
};
let abi = if helper {
TokenStream::new()
} else {
quote_spanned! {span=> extern "C" }
};
let unsafety = if func.is_safe() {
TokenStream::new()
} else {
quote_spanned! {span=> unsafe }
};
quote_spanned! {span=>
#export
#inline
#cold
#deprecated
#section
#target_feature
#vis #unsafety #abi fn #name(#params) #ret
}
}
fn target_feature_attrs(&self, func: &Function, span: Span) -> TokenStream {
let mut out = TokenStream::new();
for feature in &func.target_features {
let mut literal = Literal::string(feature);
literal.set_span(span);
out.extend(quote_spanned! {span=> #[target_feature(enable = #literal)] });
}
out
}
fn init_array_item(&mut self, func: &Function, kind: ir::InitKind) -> TokenStream {
let span = self.sp(func.range);
let name = self.c_ident(func.item_name(), span);
let signature = self.function_pointer_ty(func, span);
let item = Ident::new(
&format!(
"__CINRS_INIT_{:08x}_{}",
self.program.unit_id as u32,
func.item_name()
),
span,
);
let elf = match kind {
ir::InitKind::Constructor => ".init_array",
ir::InitKind::Destructor => ".fini_array",
};
let apple = match kind {
ir::InitKind::Constructor => "__DATA,__mod_init_func",
ir::InitKind::Destructor => "__DATA,__mod_term_func",
};
let mut elf_literal = Literal::string(elf);
elf_literal.set_span(span);
let mut apple_literal = Literal::string(apple);
apple_literal.set_span(span);
quote_spanned! {span=>
#[used]
#[cfg_attr(target_vendor = "apple", unsafe(link_section = #apple_literal))]
#[cfg_attr(not(target_vendor = "apple"), unsafe(link_section = #elf_literal))]
static #item: #signature = #name;
}
}
fn init_array_guard(&self) -> TokenStream {
let span = self.map.span(SourceRange::at(0));
quote_spanned! {span=>
const _: () = {
#[cfg(not(any(target_os = "linux", target_os = "android",
target_os = "freebsd", target_os = "netbsd",
target_os = "openbsd", target_os = "dragonfly",
target_vendor = "apple")))]
::core::compile_error!(
"'constructor' and 'destructor' need a target whose runtime walks an initialiser table (ELF or Mach-O)"
);
};
}
}
fn function_item(&mut self, func: &Function) -> TokenStream {
let span = self.sp(func.range);
let Some(body) = &func.body else {
return TokenStream::new();
};
self.enter_function(func);
let signature = self.signature(func);
let body = match body {
Body::Structured(stmts) => self.stmts(stmts),
Body::Cfg(cfg) => self.cfg_body(cfg, span),
};
let arena = if func.uses_arena {
self.uses_arena.set(true);
let name = self.arena_ident(span);
let ty = arena_ty();
quote_spanned! {span=> let #name = #ty::new(); }
} else {
TokenStream::new()
};
if func.is_safe() {
return quote_spanned! {span=>
#signature { #arena #body }
};
}
quote_spanned! {span=>
#signature {
unsafe { #arena #body }
}
}
}
fn arena_ident(&self, span: Span) -> Ident {
if self.arena_span.get().is_none() {
self.arena_span.set(Some(span));
}
Ident::new("__cinrs_vla", Span::mixed_site())
}
fn arena_items(&self, span: Span) -> TokenStream {
let arena = arena_ty();
let frame = Ident::new("__cinrs_vla_frame", Span::mixed_site());
let t = Ident::new("T", Span::mixed_site());
let usize_ty = primitive_ty("usize", span);
let u128_ty = primitive_ty("u128", span);
let u8_ty = primitive_ty("u8", span);
let chunk_ty = self.vec_ty(u128_ty.clone(), span);
let chunks_ty = self.vec_ty(chunk_ty, span);
let empty = self.vec_new(span);
let new_chunk = self.vec_of(
quote_spanned! {span=> 0 },
quote_spanned! {span=> size.div_ceil(::core::mem::size_of::<#u128_ty>()) },
span,
);
let void = self.pointee_ty(Ty::Void, span);
quote_spanned! {span=>
#[allow(
unknown_lints,
elided_lifetimes_in_paths,
missing_debug_implementations,
single_use_lifetimes,
unused_qualifications,
clippy::pedantic,
clippy::nursery
)]
struct #arena {
chunks: ::core::cell::UnsafeCell<#chunks_ty>,
cur: ::core::cell::Cell<#usize_ty>,
top: ::core::cell::Cell<#usize_ty>,
floor: ::core::cell::Cell<(#usize_ty, #usize_ty)>,
}
#[allow(
unknown_lints,
elided_lifetimes_in_paths,
single_use_lifetimes,
unused_qualifications,
clippy::pedantic,
clippy::nursery
)]
impl #arena {
#[inline(always)]
fn new() -> Self {
Self {
chunks: ::core::cell::UnsafeCell::new(#empty),
cur: ::core::cell::Cell::new(0),
top: ::core::cell::Cell::new(0),
floor: ::core::cell::Cell::new((0, 0)),
}
}
#[inline(always)]
fn mark(&self) -> (#usize_ty, #usize_ty) {
(self.cur.get(), self.top.get())
}
#[inline(always)]
fn frame(&self) -> #frame<'_> {
#frame(self, self.mark())
}
#[inline(always)]
fn release(&self, mark: (#usize_ty, #usize_ty)) {
let floor = self.floor.get();
let to = if mark > floor { mark } else { floor };
if to < self.mark() {
self.cur.set(to.0);
self.top.set(to.1);
}
}
#[inline(always)]
fn bytes(&self, bytes: #usize_ty, align: #usize_ty) -> *mut #u8_ty {
unsafe {
let chunks = &mut *self.chunks.get();
let top = self.top.get();
if let ::core::option::Option::Some(chunk) = chunks.get_mut(self.cur.get()) {
let base = chunk.as_mut_ptr().cast::<#u8_ty>();
let size = chunk.len() * ::core::mem::size_of::<#u128_ty>();
let start = top + (base.addr().wrapping_add(top).wrapping_neg() & (align - 1));
if start <= size && bytes <= size - start {
self.top.set(start + bytes);
let first = base.add(start);
::core::ptr::write_bytes(first, 0, bytes);
return first;
}
}
}
self.grow(bytes, align)
}
#[cold]
#[inline(never)]
fn grow(&self, bytes: #usize_ty, align: #usize_ty) -> *mut #u8_ty {
let need = bytes
.checked_add(align)
.expect("a variable length array or alloca is larger than the address space");
{
let chunks = unsafe { &mut *self.chunks.get() };
let next = if chunks.is_empty() { 0 } else { self.cur.get() + 1 };
let unit = ::core::mem::size_of::<#u128_ty>();
let fits = match chunks.get(next) {
::core::option::Option::Some(chunk) => chunk.len() * unit >= need,
::core::option::Option::None => false,
};
if !fits {
let last = match chunks.last() {
::core::option::Option::Some(chunk) => chunk.len() * unit,
::core::option::Option::None => 0,
};
chunks.truncate(next);
let size = need.max(last.saturating_mul(2)).max(4096);
chunks.push(#new_chunk);
}
self.cur.set(next);
self.top.set(0);
}
self.bytes(bytes, align)
}
#[inline(always)]
fn alloc<#t>(&self, count: #usize_ty) -> *mut #t {
self.alloc_aligned::<#t>(count, 1)
}
#[inline(always)]
fn alloc_aligned<#t>(&self, count: #usize_ty, align: #usize_ty) -> *mut #t {
let bytes = count
.checked_mul(::core::mem::size_of::<#t>())
.expect("a variable length array is larger than the address space");
let natural = ::core::mem::align_of::<#t>();
self.bytes(bytes, if align > natural { align } else { natural })
.cast::<#t>()
}
#[inline(always)]
fn alloca(&self, size: #usize_ty) -> *mut #void {
let first = self.bytes(size, 16);
self.floor.set(self.mark());
first.cast::<#void>()
}
#[inline]
fn redefine(
&self,
marks: &mut [::core::option::Option<(#usize_ty, #usize_ty)>],
slot: #usize_ty,
) {
if let ::core::option::Option::Some(mine) = marks[slot] {
for mark in marks.iter_mut() {
if let ::core::option::Option::Some(other) = *mark {
if other >= mine {
*mark = ::core::option::Option::None;
}
}
}
self.release(mine);
}
marks[slot] = ::core::option::Option::Some(self.mark());
}
}
#[allow(
unknown_lints,
elided_lifetimes_in_paths,
missing_debug_implementations,
single_use_lifetimes,
clippy::pedantic,
clippy::nursery
)]
struct #frame<'a>(&'a #arena, (#usize_ty, #usize_ty));
#[allow(
unknown_lints,
elided_lifetimes_in_paths,
single_use_lifetimes,
clippy::pedantic,
clippy::nursery
)]
impl ::core::ops::Drop for #frame<'_> {
#[inline(always)]
fn drop(&mut self) {
self.0.release(self.1);
}
}
}
}
fn stub_item(&mut self, func: &Function) -> TokenStream {
let span = self.sp(func.range);
self.enter_function(func);
let signature = self.signature(func);
if func.is_safe() {
return quote_spanned! {span=>
#signature { ::core::unreachable!() }
};
}
quote_spanned! {span=>
#signature {
unsafe { ::core::unreachable!() }
}
}
}
fn enter_function(&mut self, func: &Function) {
self.ret_ty = func.sig.ret;
self.in_cfg = matches!(func.body, Some(Body::Cfg(_)));
self.in_safe = func.is_safe();
self.temporaries = 0;
self.continue_styles.clear();
self.local_names.clear();
self.region_names.clear();
self.region_kinds.clear();
self.loop_names.clear();
self.shape_scopes.clear();
self.used_labels.clear();
self.shape_labels = 0;
self.vla_slots.clear();
if let Some(Body::Structured(stmts)) = &func.body {
self.name_regions(stmts);
}
if let Some(Body::Cfg(cfg)) = &func.body
&& let Some(plan) = &cfg.shape
{
let mut taken = HashSet::new();
self.name_loops(&plan.body, &mut taken);
}
self.env = func
.env
.iter()
.map(|entry| (entry.owner, entry.param))
.collect();
let mut bound: Vec<ir::ObjectId> = func.env.iter().map(|entry| entry.param).collect();
bound.extend(func.params.iter().copied());
if let Some(Body::Cfg(cfg)) = &func.body {
for local in &cfg.locals {
self.local_names
.insert(local.object, local.rust_name.clone());
}
}
bound.extend(func.locals.iter().copied());
self.rename_shadowing(&bound);
self.va_source = if func.sig.variadic {
VaSource::Ellipsis
} else {
func.params
.iter()
.find_map(|id| {
let ty = self.program.object(*id).ty;
if ty.is_va_list() {
return Some(VaSource::Param(*id));
}
let pointee = self.program.types.pointee(ty)?;
pointee.is_va_list().then_some(VaSource::PtrParam(*id))
})
.unwrap_or(VaSource::None)
};
}
fn plain_local_name(&self, id: ir::ObjectId) -> String {
match self.local_names.get(&id) {
Some(name) => name.clone(),
None => self.program.object(id).name.clone(),
}
}
fn rename_shadowing(&mut self, bound: &[ir::ObjectId]) {
if self.reserved.is_empty() {
return;
}
let mut used: HashSet<String> = bound
.iter()
.map(|id| {
self.names
.spelling(&self.plain_local_name(*id))
.into_owned()
})
.collect();
for id in bound {
let base = self.plain_local_name(*id);
if !self.reserved.contains(&base) {
continue;
}
let name = (1u32..)
.map(|n| format!("{base}_{n}"))
.find(|c| {
!used.contains(self.names.spelling(c).as_ref()) && !self.reserved.contains(c)
})
.expect("the sequence of candidates is unbounded");
used.insert(self.names.spelling(&name).into_owned());
self.local_names.insert(*id, name);
}
}
fn va_ident(&self) -> Ident {
Ident::new("__cinrs_va", Span::mixed_site())
}
fn va_pristine(&mut self, span: Span) -> TokenStream {
match self.va_source {
VaSource::Param(id) => {
let name = self.object_ident(id, span);
quote_spanned! {span=> #name.clone() }
}
VaSource::PtrParam(id) => {
let name = self.object_ident(id, span);
quote_spanned! {span=> (*#name).clone() }
}
_ => {
let name = self.va_ident();
quote_spanned! {span=> #name.clone() }
}
}
}
fn object_align(&self, id: ir::ObjectId) -> Option<u64> {
self.program.object(id).align
}
fn binding_ty(&self, id: ir::ObjectId, ty: TokenStream, span: Span) -> TokenStream {
match self.object_align(id) {
Some(align) => {
let wrapper = align_wrapper_ident(align, span);
quote_spanned! {span=> #wrapper<#ty> }
}
None => ty,
}
}
fn binding_init(&self, id: ir::ObjectId, init: TokenStream, span: Span) -> TokenStream {
match self.object_align(id) {
Some(align) => {
let wrapper = align_wrapper_ident(align, span);
quote_spanned! {span=> #wrapper(#init) }
}
None => init,
}
}
fn storage_ty(&self, id: ir::ObjectId, span: Span) -> TokenStream {
let object = self.program.object(id);
match (object.flexible_len, object.ty) {
(Some(len), Ty::Record(record)) => {
let name =
self.flexible_ident(&self.program.types.record(record).rust_name, len, span);
quote_spanned! {span=> #name }
}
_ => self.ty(object.ty, span),
}
}
fn object_access(&self, id: ir::ObjectId, span: Span) -> TokenStream {
let name = self.object_ident(id, span);
self.through_storage(id, quote_spanned! {span=> #name }, span)
}
fn through_storage(&self, id: ir::ObjectId, base: TokenStream, span: Span) -> TokenStream {
let object = self.program.object(id);
let mut access = base;
if object.align.is_some() {
let field = Literal::usize_unsuffixed(0);
access = quote_spanned! {span=> #access.#field };
}
if object.flexible_len.is_some() {
let ty = self.ty(object.ty, span);
access = parenthesize(
quote_spanned! {span=> *(&raw mut #access).cast::<#ty>() },
span,
);
}
access
}
#[inline(never)]
fn va_list_load(&mut self, place: &Place, span: Span) -> Value {
let lowered = self.place(place, false);
let access = lowered.access;
let value = Value::new(quote_spanned! {span=> #access.clone() }, prec::CALL);
if lowered.setup.is_empty() {
return value;
}
let setup = lowered.setup;
let tokens = value.at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> { #setup #tokens } }, prec::BLOCK)
}
#[inline(never)]
fn label_address(&self, id: ir::LabelId, ty: Ty, span: Span) -> Value {
let state = self.label_states.get(&id).copied().unwrap_or(0);
let mut literal = Literal::usize_suffixed(state as usize);
literal.set_span(span);
let target = self.ty(ty, span);
Value::new(quote_spanned! {span=> #literal as #target }, prec::CAST).type_end(true)
}
#[inline(never)]
fn label_difference(&self, lhs: &Expr, rhs: &Expr, ty: Ty, span: Span) -> Value {
let left = self.label_state_literal(lhs, span);
let right = self.label_state_literal(rhs, span);
let target = self.ty(ty, span);
Value::new(
quote_spanned! {span=> (#left - #right) as #target },
prec::CAST,
)
.type_end(true)
}
fn label_state_literal(&self, expr: &Expr, span: Span) -> TokenStream {
let id = label_state(expr).expect("a label address");
let state = self.label_states.get(&id).copied().unwrap_or(0);
let mut literal = Literal::isize_suffixed(state as isize);
literal.set_span(span);
quote_spanned! {span=> #literal }
}
fn object_ident(&self, id: ir::ObjectId, span: Span) -> Ident {
let object = self.program.object(id);
match &object.storage {
Storage::Automatic => match self.local_names.get(&id) {
Some(name) => self.c_ident(name, span),
None => self.c_ident(&object.name, span),
},
Storage::Static { item_name, .. } | Storage::ThreadLocal { item_name, .. } => {
self.c_ident(item_name, span)
}
Storage::Extern { item_name } => self.extern_object_ident(item_name, span),
}
}
fn cfg_body(&mut self, cfg: &Cfg, span: Span) -> TokenStream {
let mut out = self.cfg_locals(cfg);
match &cfg.shape {
Some(plan) => {
out.extend(self.shape_seq(cfg, &plan.body, &reloop::Exit::nowhere(), span))
}
None => out.extend(self.state_machine(cfg, span)),
}
out
}
fn cfg_locals(&mut self, cfg: &Cfg) -> TokenStream {
let mut out = TokenStream::new();
let mut slots = 0;
let mut slots_span = None;
for local in &cfg.locals {
let object = self.program.object(local.object);
let ospan = self.sp(object.range);
if object.vla_storage {
self.vla_slots.insert(local.object, slots);
slots += 1;
slots_span.get_or_insert(ospan);
continue;
}
let name = self.object_ident(local.object, ospan);
let (ty, init) = if object.ty.is_va_list() {
(self.ty(object.ty, ospan), self.va_pristine(ospan))
} else {
(
self.ty(object.ty, ospan),
self.zero_tokens(object.ty, ospan),
)
};
let ty = self.binding_ty(local.object, ty, ospan);
let init = self.binding_init(local.object, init, ospan);
out.extend(quote_spanned! {ospan=> let mut #name: #ty = #init; });
}
if let Some(span) = slots_span {
let marks = vla_marks_ident();
let usize_ty = primitive_ty("usize", span);
let len = Literal::usize_unsuffixed(slots);
out.extend(quote_spanned! {span=>
let mut #marks: [::core::option::Option<(#usize_ty, #usize_ty)>; #len] =
[::core::option::Option::None; #len];
});
}
out
}
fn state_machine(&mut self, cfg: &Cfg, span: Span) -> TokenStream {
let state = self.state_ident();
let label = self.cfg_label();
let mut arms = TokenStream::new();
for (index, block) in cfg.blocks.iter().enumerate() {
let bspan = self.block_span(block, span);
let pattern = state_literal(index, bspan);
let body = self.block_tokens(block, bspan);
arms.extend(quote_spanned! {bspan=> #pattern => { #body } });
}
arms.extend(quote_spanned! {span=> _ => ::core::unreachable!(), });
let u32_ty = primitive_ty("u32", span);
quote_spanned! {span=>
let mut #state: #u32_ty = 0;
#label: loop {
match #state { #arms }
}
}
}
fn state_ident(&self) -> Ident {
Ident::new("__cinrs_state", Span::mixed_site())
}
fn cfg_label(&self) -> TokenStream {
self.label("cfg", Span::mixed_site())
}
fn block_span(&self, block: &BasicBlock, fallback: Span) -> Span {
if let Some(range) = block.stmts.iter().find_map(|s| self.stmt_range(s)) {
return self.sp(range);
}
match &block.term {
Terminator::Jump { range, .. }
| Terminator::Switch { range, .. }
| Terminator::Return { range, .. } => self.sp(*range),
Terminator::Branch { cond, .. } => self.sp(cond.range),
Terminator::Unreachable | Terminator::InvalidTarget => fallback,
}
}
fn block_tokens(&mut self, block: &BasicBlock, span: Span) -> TokenStream {
let mut out = self.stmts(&block.stmts);
let label = self.cfg_label();
match &block.term {
Terminator::Jump { target, range } => {
let jump = self.enter_block(*target, self.sp(*range));
out.extend(quote_spanned! {span=> #jump continue #label; });
}
Terminator::Branch {
cond,
then_blk,
else_blk,
} => {
let cspan = self.sp(cond.range);
let test = self.condition(cond).at_condition(cspan);
let then_tokens = self.enter_block(*then_blk, cspan);
let else_tokens = self.enter_block(*else_blk, cspan);
out.extend(quote_spanned! {cspan=>
if #test { #then_tokens } else { #else_tokens }
continue #label;
});
}
Terminator::Switch {
value,
cases,
default,
range,
} => {
let sspan = self.sp(*range);
let scrutinee = self.expr(value).at(prec::UNARY, sspan);
let mut arms = TokenStream::new();
for (target, values) in group_cases(cases) {
let mut pattern = TokenStream::new();
for (index, case) in values.iter().enumerate() {
if index > 0 {
pattern.extend(quote_spanned! {sspan=> | });
}
pattern.extend(case_pattern(*case, value.ty, sspan));
}
let enter = self.enter_block(target, sspan);
arms.extend(quote_spanned! {sspan=> #pattern => { #enter } });
}
let enter = self.enter_block(*default, sspan);
arms.extend(quote_spanned! {sspan=> _ => { #enter } });
out.extend(quote_spanned! {sspan=>
match #scrutinee { #arms }
continue #label;
});
}
Terminator::Return { value, range } => {
let rspan = self.sp(*range);
match value {
Some(value) => {
let ret = self.ret_ty;
let tokens = self.expr_at(value, ret);
out.extend(quote_spanned! {rspan=> return #tokens; });
}
None => out.extend(quote_spanned! {rspan=> return; }),
}
}
Terminator::Unreachable => {
out.extend(quote_spanned! {span=> ::core::unreachable!(); });
}
Terminator::InvalidTarget => out.extend(invalid_target(span)),
}
out
}
fn enter_block(&self, target: BlockId, span: Span) -> TokenStream {
let state = self.state_ident();
let value = state_literal(target.0 as usize, span);
quote_spanned! {span=> #state = #value; }
}
fn name_loops(&mut self, seq: &reloop::Seq, taken: &mut HashSet<String>) {
for shape in seq {
match shape {
reloop::Shape::Loop { id, name, body, .. } => {
let mut candidate = match name {
Some(name) => rust_spelling(name).into_owned(),
None => String::new(),
};
if !is_label_name(&candidate) {
candidate = format!("r{id}");
}
while taken.contains(&candidate) {
candidate.push('_');
}
taken.insert(candidate.clone());
self.loop_names.insert(*id, candidate);
self.name_loops(body, taken);
}
reloop::Shape::Simple { arms, .. } | reloop::Shape::Dispatch { arms, .. } => {
for arm in arms {
self.name_loops(&arm.body, taken);
}
}
}
}
}
fn shape_seq(
&mut self,
cfg: &Cfg,
seq: &reloop::Seq,
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
let Some(first) = seq.first() else {
return TokenStream::new();
};
let exits: Vec<reloop::Exit> = seq.iter().map(reloop::Shape::exit).collect();
let mut out = TokenStream::new();
for shape in seq {
if let reloop::Shape::Loop {
state: Some(state), ..
} = shape
{
let name = entry_ident(*state);
let u32_ty = primitive_ty("u32", span);
out.extend(quote_spanned! {span=> let mut #name: #u32_ty = 0; });
}
}
let ids: Vec<u32> = (1..seq.len())
.map(|_| {
let id = self.shape_labels;
self.shape_labels += 1;
id
})
.collect();
for (index, id) in ids.iter().enumerate().rev() {
self.shape_scopes.push(ShapeScope {
id: *id,
name: format!("b{id}"),
repeats: false,
exit: exits[index + 1].clone(),
});
}
let mut code = self.shape(cfg, first, exits.get(1).unwrap_or(fall), span);
for (index, shape) in seq.iter().enumerate().skip(1) {
let scope = self.shape_scopes.pop().expect("one scope per shape");
if self.used_labels.contains(&scope.id) {
let label = self.label(&scope.name, span);
code = quote_spanned! {span=> #label: { #code } };
}
code.extend(self.shape(cfg, shape, exits.get(index + 1).unwrap_or(fall), span));
}
out.extend(code);
out
}
fn shape(
&mut self,
cfg: &Cfg,
shape: &reloop::Shape,
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
match shape {
reloop::Shape::Simple { block, arms } => {
self.shape_simple(cfg, *block, arms, fall, span)
}
reloop::Shape::Loop {
id,
entries,
state,
body,
..
} => {
let name = self
.loop_names
.get(id)
.cloned()
.unwrap_or_else(|| format!("r{id}"));
let bspan = entries.first().map_or(span, |entry| {
self.block_span(&cfg.blocks[entry.index()], span)
});
let head = reloop::Exit {
targets: entries.clone(),
state: *state,
};
self.shape_scopes.push(ShapeScope {
id: u32::MAX,
name: name.clone(),
repeats: false,
exit: fall.clone(),
});
self.shape_scopes.push(ShapeScope {
id: u32::MAX,
name: name.clone(),
repeats: true,
exit: head.clone(),
});
let body = self.shape_seq(cfg, body, &head, bspan);
self.shape_scopes.pop();
self.shape_scopes.pop();
let label = self.label(&name, bspan);
quote_spanned! {bspan=> #label: loop { #body } }
}
reloop::Shape::Dispatch { state, arms } => {
self.shape_dispatch(cfg, *state, arms, fall, span)
}
}
}
fn shape_dispatch(
&mut self,
cfg: &Cfg,
state: u32,
arms: &[reloop::Arm],
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
let Some((last, rest)) = arms.split_last() else {
return TokenStream::new();
};
if rest.is_empty() {
return self.shape_seq(cfg, &last.body, fall, span);
}
let name = entry_ident(state);
if rest.len() == 1 {
let first = self.shape_seq(cfg, &rest[0].body, fall, span);
let second = self.shape_seq(cfg, &last.body, fall, span);
let zero = state_literal(0, span);
return quote_spanned! {span=>
if #name == #zero { #first } else { #second }
};
}
let mut cases = TokenStream::new();
for (index, arm) in rest.iter().enumerate() {
let body = self.shape_seq(cfg, &arm.body, fall, span);
let pattern = state_literal(index, span);
cases.extend(quote_spanned! {span=> #pattern => { #body } });
}
let body = self.shape_seq(cfg, &last.body, fall, span);
cases.extend(quote_spanned! {span=> _ => { #body } });
quote_spanned! {span=> match #name { #cases } }
}
fn shape_simple(
&mut self,
cfg: &Cfg,
block: BlockId,
arms: &[reloop::Arm],
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
let basic = &cfg.blocks[block.index()];
let span = self.block_span(basic, span);
let mut out = self.stmts(&basic.stmts);
match &basic.term {
Terminator::Jump { target, range } => {
let jspan = self.sp(*range);
out.extend(self.enter_shape(cfg, arms, *target, fall, jspan));
}
Terminator::Branch {
cond,
then_blk,
else_blk,
} => out.extend(self.shape_branch(cfg, arms, cond, *then_blk, *else_blk, fall)),
Terminator::Switch {
value,
cases,
default,
range,
} => {
let sspan = self.sp(*range);
out.extend(self.shape_switch(cfg, arms, value, cases, *default, fall, sspan));
}
Terminator::Return { value, range } => {
let rspan = self.sp(*range);
match value {
Some(value) => {
let ret = self.ret_ty;
let tokens = self.expr_at(value, ret);
out.extend(quote_spanned! {rspan=> return #tokens; });
}
None => out.extend(quote_spanned! {rspan=> return; }),
}
}
Terminator::Unreachable => {
out.extend(quote_spanned! {span=> ::core::unreachable!(); });
}
Terminator::InvalidTarget => out.extend(invalid_target(span)),
}
out
}
fn shape_branch(
&mut self,
cfg: &Cfg,
arms: &[reloop::Arm],
cond: &Expr,
then_blk: BlockId,
else_blk: BlockId,
fall: &reloop::Exit,
) -> TokenStream {
let span = self.sp(cond.range);
if then_blk == else_blk {
let effects = self.expr_stmt(cond);
let jump = self.enter_shape(cfg, arms, then_blk, fall, span);
return quote_spanned! {span=> #effects #jump };
}
let then_tokens = self.enter_shape(cfg, arms, then_blk, fall, span);
let else_tokens = self.enter_shape(cfg, arms, else_blk, fall, span);
if then_tokens.is_empty() && else_tokens.is_empty() {
return self.expr_stmt(cond);
}
if else_tokens.is_empty() {
let test = self.condition(cond).at_condition(span);
return quote_spanned! {span=> if #test { #then_tokens } };
}
if then_tokens.is_empty() {
let test = self.condition(cond).at(prec::UNARY, span);
return quote_spanned! {span=> if !#test { #else_tokens } };
}
let test = self.condition(cond).at_condition(span);
quote_spanned! {span=> if #test { #then_tokens } else { #else_tokens } }
}
#[allow(clippy::too_many_arguments)]
fn shape_switch(
&mut self,
cfg: &Cfg,
arms: &[reloop::Arm],
value: &Expr,
cases: &[(ir::CaseRange, BlockId)],
default: BlockId,
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
let groups = group_cases(cases);
let mut lowered: Vec<(Vec<ir::CaseRange>, TokenStream)> = Vec::new();
for (target, values) in groups {
let tokens = self.enter_shape(cfg, arms, target, fall, span);
lowered.push((values, tokens));
}
let fallback = self.enter_shape(cfg, arms, default, fall, span);
if lowered.iter().all(|(_, tokens)| tokens.is_empty()) && fallback.is_empty() {
return self.expr_stmt(value);
}
if lowered.is_empty() {
let effects = self.expr_stmt(value);
return quote_spanned! {span=> #effects #fallback };
}
let scrutinee = self.expr(value).at(prec::UNARY, span);
let mut out = TokenStream::new();
for (values, tokens) in lowered {
let mut pattern = TokenStream::new();
for (index, case) in values.iter().enumerate() {
if index > 0 {
pattern.extend(quote_spanned! {span=> | });
}
pattern.extend(case_pattern(*case, value.ty, span));
}
out.extend(quote_spanned! {span=> #pattern => { #tokens } });
}
out.extend(quote_spanned! {span=> _ => { #fallback } });
quote_spanned! {span=> match #scrutinee { #out } }
}
fn enter_shape(
&mut self,
cfg: &Cfg,
arms: &[reloop::Arm],
target: BlockId,
fall: &reloop::Exit,
span: Span,
) -> TokenStream {
match arms.iter().find(|arm| arm.entry == target) {
Some(arm) => self.shape_seq(cfg, &arm.body, fall, span),
None => self.goto_shape(target, fall, span),
}
}
fn goto_shape(&mut self, target: BlockId, fall: &reloop::Exit, span: Span) -> TokenStream {
if let Some(index) = fall.index_of(target) {
return entry_assignment(fall, index, span);
}
for depth in (0..self.shape_scopes.len()).rev() {
let Some(index) = self.shape_scopes[depth].exit.index_of(target) else {
continue;
};
let scope = self.shape_scopes[depth].clone();
self.used_labels.insert(scope.id);
let label = self.label(&scope.name, span);
let set = entry_assignment(&scope.exit, index, span);
return if scope.repeats {
quote_spanned! {span=> #set continue #label; }
} else {
quote_spanned! {span=> #set break #label; }
};
}
unreachable!("the shapes were checked before they were generated")
}
fn stmts(&mut self, stmts: &[Stmt]) -> TokenStream {
let mut out = TokenStream::new();
for stmt in stmts {
out.extend(self.stmt(stmt));
}
out
}
fn block_of(&mut self, stmt: &Stmt, span: Span) -> TokenStream {
match stmt {
Stmt::Block(items) => {
let items = self.stmts(items);
braced(items, span)
}
other => {
let tokens = self.stmt(other);
braced(tokens, span)
}
}
}
fn stmt(&mut self, stmt: &Stmt) -> TokenStream {
match stmt {
Stmt::Nop => TokenStream::new(),
Stmt::Asm(asm) => self.asm_stmt(asm),
Stmt::Expr(expr) => self.expr_stmt(expr),
Stmt::Let { object, init, .. } => {
let id = *object;
let name = self.object_ident(id, self.sp(self.program.object(id).range));
let object = self.program.object(id);
let span = self.sp(object.range);
let object_ty = object.ty;
let ty = self.binding_ty(id, self.ty(object_ty, span), span);
let init = self.expr_at(init, object_ty);
let init = self.binding_init(id, init, span);
quote_spanned! {span=> let mut #name: #ty = #init; }
}
Stmt::Vla(def) => self.vla_def(def),
Stmt::Cleanup(def) => self.cleanup_def(def),
Stmt::Block(items) => {
let span = self.stmts_span(items);
let items = self.stmts(items);
braced(items, span)
}
Stmt::If {
cond,
then_branch,
else_branch,
} => {
let span = self.sp(cond.range);
let cond_tokens = self.condition(cond).at_condition(span);
let then_tokens = self.block_of(then_branch, span);
let else_tokens = match else_branch {
Some(branch) => {
let tokens = self.block_of(branch, span);
quote_spanned! {span=> else #tokens }
}
None => TokenStream::new(),
};
quote_spanned! {span=> if #cond_tokens #then_tokens #else_tokens }
}
Stmt::While {
id,
cond,
body,
range,
} => {
let span = self.sp(*range);
self.continue_styles.insert(*id, ContinueStyle::Head);
let label = self.loop_label(*id, span);
let body_tokens = self.block_of(body, span);
if ir::is_always_true(cond) {
return quote_spanned! {span=> #label: loop #body_tokens };
}
let cond_tokens = self.condition(cond).at_condition(span);
quote_spanned! {span=> #label: while #cond_tokens #body_tokens }
}
Stmt::DoWhile {
id,
body,
cond,
range,
} => {
let span = self.sp(*range);
self.continue_styles.insert(*id, ContinueStyle::BodyLabel);
let label = self.loop_label(*id, span);
let body_label = self.loop_body_label(*id, span);
let body_tokens = self.block_of(body, span);
let test = if ir::is_always_true(cond) {
TokenStream::new()
} else {
let cond_tokens = self.condition(cond).at(prec::LOWEST, span);
quote_spanned! {span=> if !(#cond_tokens) { break #label; } }
};
quote_spanned! {span=>
#label: loop {
#body_label: #body_tokens
#test
}
}
}
Stmt::For {
id,
init,
cond,
step,
body,
range,
} => {
let span = self.sp(*range);
self.continue_styles.insert(*id, ContinueStyle::BodyLabel);
let label = self.loop_label(*id, span);
let body_label = self.loop_body_label(*id, span);
let init_tokens = self.stmts(init);
let test = match cond {
Some(cond) if !ir::is_always_true(cond) => {
let tokens = self.condition(cond).at(prec::LOWEST, span);
quote_spanned! {span=> if !(#tokens) { break #label; } }
}
_ => TokenStream::new(),
};
let body_tokens = self.block_of(body, span);
let step_tokens = match step {
Some(step) => self.expr_stmt(step),
None => TokenStream::new(),
};
quote_spanned! {span=>
{
#init_tokens
#label: loop {
#test
#body_label: #body_tokens
#step_tokens
}
}
}
}
Stmt::Switch(switch) => self.switch(switch),
Stmt::Region(region) => self.region(region),
Stmt::Label { body, .. } | Stmt::Case { body, .. } => self.stmt(body),
Stmt::Goto { id, range } => {
let span = self.sp(*range);
match self.region_kinds.get(id).copied() {
Some(kind) => {
let label = self.region_label(*id, span);
match kind {
ir::RegionKind::Block => quote_spanned! {span=> break #label; },
ir::RegionKind::Loop => quote_spanned! {span=> continue #label; },
}
}
None => TokenStream::new(),
}
}
Stmt::GotoPtr { .. } => TokenStream::new(),
Stmt::SwitchTree(switch) => self.stmt(&switch.body),
Stmt::Break { target, range } => {
let span = self.sp(*range);
let label = match target {
BreakTarget::Loop(id) => self.loop_label(*id, span),
BreakTarget::Switch(id) => self.switch_label(*id, span),
};
quote_spanned! {span=> break #label; }
}
Stmt::Continue { id, range } => {
let span = self.sp(*range);
match self.continue_styles.get(id) {
Some(ContinueStyle::BodyLabel) => {
let label = self.loop_body_label(*id, span);
quote_spanned! {span=> break #label; }
}
_ => {
let label = self.loop_label(*id, span);
quote_spanned! {span=> continue #label; }
}
}
}
Stmt::Return { value, range } => {
let span = self.sp(*range);
match value {
Some(value) => {
let ret = self.ret_ty;
let tokens = self.expr_at(value, ret);
quote_spanned! {span=> return #tokens; }
}
None => quote_spanned! {span=> return; },
}
}
}
}
fn vla_def(&mut self, def: &ir::VlaDef) -> TokenStream {
let span = self.sp(def.range);
let object = self.program.object(def.object);
let elem = self.program.types.vm_step_ty(object.ty);
let name = self.object_ident(def.object, span);
let count = self.expr(&def.count).at(prec::CAST, span);
let elem_ty = self.ty(elem, span);
let usize_ty = primitive_ty("usize", span);
let arena = self.arena_ident(span);
let first = match def.align {
Some(align) => {
let align = Literal::usize_unsuffixed(align as usize);
quote_spanned! {span=>
#arena.alloc_aligned::<#elem_ty>(#count as #usize_ty, #align)
}
}
None => quote_spanned! {span=> #arena.alloc::<#elem_ty>(#count as #usize_ty) },
};
if self.in_cfg {
let marks = vla_marks_ident();
let slot = Literal::usize_unsuffixed(self.vla_slots[&def.storage]);
return quote_spanned! {span=>
#arena.redefine(&mut #marks, #slot);
#name = #first;
};
}
let frame = self.object_ident(def.storage, span);
quote_spanned! {span=>
let #frame = #arena.frame();
let mut #name: *mut #elem_ty = #first;
}
}
fn cleanup_def(&mut self, def: &ir::CleanupDef) -> TokenStream {
let span = self.sp(def.range);
let guard = cleanup_guard_ty();
let name = Ident::new(
&format!("__cinrs_cleanup{}", def.object.0),
Span::mixed_site(),
);
let object = self.program.object(def.object);
let place = ir::Place {
kind: PlaceKind::Object(def.object),
ty: object.ty,
is_const: object.is_const,
range: def.range,
};
let address = self
.address_of(&place, def.param, span)
.at(prec::LOWEST, span);
let function = self.func_pointer(def.func, span);
self.uses_cleanup.set(true);
quote_spanned! {span=>
let #name = #guard(#address, #function);
}
}
fn cleanup_guard_item(&self, span: Span) -> TokenStream {
let name = cleanup_guard_ty();
let p = Ident::new("P", Span::mixed_site());
let r = Ident::new("R", Span::mixed_site());
quote_spanned! {span=>
struct #name<#p: ::core::marker::Copy, #r>(#p, unsafe extern "C" fn(#p) -> #r);
impl<#p: ::core::marker::Copy, #r> ::core::ops::Drop for #name<#p, #r> {
fn drop(&mut self) {
unsafe {
(self.1)(self.0);
}
}
}
}
}
fn func_addr(&self, id: ir::FuncId, span: Span) -> Value {
let function = self.program.function(id);
let name = if function.intrinsic.is_some() {
self.intrinsic_shim_name(id, span)
} else {
self.function_path(function, span)
};
let signature = self.function_pointer_ty(function, span);
Value::new(
quote_spanned! {span=> ::core::option::Option::Some(#name as #signature) },
prec::CALL,
)
}
fn intrinsic_shim_name(&self, id: ir::FuncId, span: Span) -> TokenStream {
let function = self.program.function(id);
{
let mut wanted = self.address_taken.borrow_mut();
if !wanted.contains(&id) {
wanted.push(id);
}
}
let name = Ident::new(
&format!("__cinrs_intrinsic_{}", function.name),
Span::mixed_site(),
);
quote_spanned! {span=> #name }
}
fn intrinsic_shim_items(&self) -> TokenStream {
let mut out = TokenStream::new();
for id in self.address_taken.borrow().iter() {
let function = self.program.function(*id);
let Some(intr) = function.intrinsic else {
continue;
};
let span = self.sp(function.range);
let name = Ident::new(
&format!("__cinrs_intrinsic_{}", function.name),
Span::mixed_site(),
);
let mut params = TokenStream::new();
let mut args = TokenStream::new();
for (index, ty) in function.sig.params.iter().enumerate() {
if index > 0 {
params.extend(quote_spanned! {span=> , });
args.extend(quote_spanned! {span=> , });
}
let pname = Ident::new(&format!("__cinrs_arg{index}"), Span::mixed_site());
let pty = self.ty(*ty, span);
params.extend(quote_spanned! {span=> #pname: #pty });
let cast = self.intrinsic_pointer_arg(*ty, span).unwrap_or_default();
args.extend(quote_spanned! {span=> #pname #cast });
}
let ret = if function.sig.ret.is_void() {
TokenStream::new()
} else {
let ty = self.ty(function.sig.ret, span);
quote_spanned! {span=> -> #ty }
};
let feature = if intr.feature.is_empty() {
TokenStream::new()
} else {
let mut literal = Literal::string(intr.feature);
literal.set_span(span);
quote_spanned! {span=> #[target_feature(enable = #literal)] }
};
let module = self.arch_module(span);
let call = Ident::new(intr.name, span);
out.extend(quote_spanned! {span=>
#[inline]
#feature
unsafe extern "C" fn #name(#params) #ret {
unsafe { ::core::arch::#module::#call(#args) }
}
});
}
out
}
fn func_pointer(&self, id: ir::FuncId, span: Span) -> TokenStream {
let function = self.program.function(id);
let name = self.function_path(function, span);
let signature = self.function_pointer_ty(function, span);
quote_spanned! {span=> #name as #signature }
}
fn stmts_span(&self, stmts: &[Stmt]) -> Span {
stmts
.iter()
.find_map(|s| self.stmt_range(s))
.map_or_else(Span::call_site, |range| self.sp(range))
}
fn asm_stmt(&mut self, asm: &ir::AsmStmt) -> TokenStream {
let span = self.sp(asm.range);
let mut setup = TokenStream::new();
let mut store_back = TokenStream::new();
let mut operands = TokenStream::new();
for (index, operand) in asm.operands.iter().enumerate() {
let op_span = self.sp(operand.range);
let head = asm_operand_head(operand, op_span);
let value = match &operand.kind {
ir::AsmOperandKind::In(expr) => self.expr(expr).at(prec::LOWEST, op_span),
ir::AsmOperandKind::Scratch(expr) => {
let value = self.expr(expr).at(prec::LOWEST, op_span);
quote_spanned! {op_span=> #value => _ }
}
ir::AsmOperandKind::Const(value) => asm_const_literal(*value, op_span),
ir::AsmOperandKind::Out { place, .. } => {
self.asm_output(place, None, index, &mut setup, &mut store_back)
}
ir::AsmOperandKind::InOut { input, output } => {
let input = input
.as_ref()
.map(|expr| self.expr(expr).at(prec::LOWEST, op_span));
let read_place = input.is_none();
let target = self.asm_output(
output,
Some(read_place),
index,
&mut setup,
&mut store_back,
);
match input {
Some(input) => quote_spanned! {op_span=> #input => #target },
None => target,
}
}
};
operands.extend(quote_spanned! {op_span=> #head #value, });
}
for clobber in &asm.clobbers {
let name = Literal::string(clobber);
operands.extend(quote_spanned! {span=> out(#name) _, });
}
let mut template = Literal::string(&asm.template);
template.set_span(span);
let call = quote_spanned! {span=>
::core::arch::asm!(#template, #operands options(att_syntax));
};
if setup.is_empty() && store_back.is_empty() {
return call;
}
quote_spanned! {span=> { #setup #call #store_back } }
}
fn asm_output(
&mut self,
place: &Place,
read: Option<bool>,
index: usize,
setup: &mut TokenStream,
store_back: &mut TokenStream,
) -> TokenStream {
let span = self.sp(place.range);
let lowered = self.place(place, true);
setup.extend(lowered.setup.clone());
if !lowered.unaligned {
let access = &lowered.access;
return quote_spanned! {span=> #access };
}
let temp = Ident::new(&format!("__cinrs_asm{index}"), span);
let ty = self.ty(place.ty, span);
let access = &lowered.access;
setup.extend(if read == Some(true) {
quote_spanned! {span=> let mut #temp: #ty = (&raw const #access).read_unaligned(); }
} else {
quote_spanned! {span=> let #temp: #ty; }
});
store_back.extend(self.write(&lowered, quote_spanned! {span=> #temp }, span));
quote_spanned! {span=> #temp }
}
fn stmt_range(&self, stmt: &Stmt) -> Option<SourceRange> {
Some(match stmt {
Stmt::Expr(expr) => expr.range,
Stmt::Let { object, .. } => self.program.object(*object).range,
Stmt::Vla(def) => def.range,
Stmt::Cleanup(def) => def.range,
Stmt::If { cond, .. } => cond.range,
Stmt::While { range, .. }
| Stmt::DoWhile { range, .. }
| Stmt::For { range, .. }
| Stmt::Break { range, .. }
| Stmt::Continue { range, .. }
| Stmt::Return { range, .. }
| Stmt::Label { range, .. }
| Stmt::Case { range, .. }
| Stmt::Goto { range, .. }
| Stmt::GotoPtr { range, .. } => *range,
Stmt::Switch(switch) => switch.range,
Stmt::SwitchTree(switch) => switch.range,
Stmt::Region(region) => region.range,
Stmt::Asm(asm) => asm.range,
Stmt::Block(items) => return items.iter().find_map(|s| self.stmt_range(s)),
Stmt::Nop => return None,
})
}
fn loop_label(&self, id: LoopId, span: Span) -> TokenStream {
self.label(&format!("l{}", id.0), span)
}
fn loop_body_label(&self, id: LoopId, span: Span) -> TokenStream {
self.label(&format!("l{}_body", id.0), span)
}
fn switch_label(&self, id: ir::SwitchId, span: Span) -> TokenStream {
self.label(&format!("sw{}", id.0), span)
}
fn switch_case_label(&self, id: ir::SwitchId, index: usize, span: Span) -> TokenStream {
self.label(&format!("sw{}_c{index}", id.0), span)
}
fn region_label(&self, id: ir::LabelId, span: Span) -> TokenStream {
match self.region_names.get(&id) {
Some(name) => self.label(name, span),
None => self.label(&format!("cinrs_label{}", id.0), span),
}
}
fn region(&mut self, region: &ir::Region) -> TokenStream {
let span = self.sp(region.range);
let label = self.region_label(region.label, span);
let previous = self.region_kinds.insert(region.label, region.kind);
let body = self.stmts(®ion.body);
match previous {
Some(kind) => self.region_kinds.insert(region.label, kind),
None => self.region_kinds.remove(®ion.label),
};
match region.kind {
ir::RegionKind::Block => quote_spanned! {span=> #label: { #body } },
ir::RegionKind::Loop if region.falls_out => quote_spanned! {span=>
#label: loop { #body break #label; }
},
ir::RegionKind::Loop => quote_spanned! {span=> #label: loop { #body } },
}
}
fn name_regions(&mut self, stmts: &[Stmt]) {
let mut taken: HashSet<String> = HashSet::new();
let mut found = Vec::new();
collect_regions(stmts, &mut found);
for (id, name) in found {
if self.region_names.contains_key(&id) {
continue;
}
let mut candidate = rust_spelling(&name).into_owned();
if !is_label_name(&candidate) {
candidate = format!("{candidate}_{}", id.0);
}
if !is_label_name(&candidate) {
candidate = format!("cinrs_label{}", id.0);
}
while taken.contains(&candidate) {
candidate.push('_');
}
taken.insert(candidate.clone());
self.region_names.insert(id, candidate);
}
}
fn switch(&mut self, switch: &Switch) -> TokenStream {
let span = self.sp(switch.range);
let scrutinee_ty = switch.scrutinee.ty;
let scrutinee = self.expr(&switch.scrutinee).at(prec::UNARY, span);
let mut arms = TokenStream::new();
for (index, group) in switch.groups.iter().enumerate() {
if group.values.is_empty() {
continue;
}
let label = self.switch_case_label(switch.id, index, span);
let mut pattern = TokenStream::new();
for (i, value) in group.values.iter().enumerate() {
if i > 0 {
pattern.extend(quote_spanned! {span=> | });
}
pattern.extend(case_pattern(*value, scrutinee_ty, span));
}
arms.extend(quote_spanned! {span=> #pattern => break #label, });
}
let fallback = match switch.default_group {
Some(index) => self.switch_case_label(switch.id, index, span),
None => self.switch_label(switch.id, span),
};
arms.extend(quote_spanned! {span=> _ => break #fallback, });
let prelude = self.stmts(&switch.prelude);
let mut inner = quote_spanned! {span=> match #scrutinee { #arms } #prelude };
for (index, group) in switch.groups.iter().enumerate() {
let label = self.switch_case_label(switch.id, index, span);
let body = self.stmts(&group.body);
inner = quote_spanned! {span=> #label: { #inner } #body };
}
let mut hoisted = TokenStream::new();
for id in &switch.hoisted {
let object = self.program.object(*id);
let ospan = self.sp(object.range);
let name = self.object_ident(*id, ospan);
let ty = self.binding_ty(*id, self.ty(object.ty, ospan), ospan);
let zero = self.zero_tokens(object.ty, ospan);
let zero = self.binding_init(*id, zero, ospan);
hoisted.extend(quote_spanned! {ospan=> let mut #name: #ty = #zero; });
}
let label = self.switch_label(switch.id, span);
quote_spanned! {span=>
{
#hoisted
#label: { #inner }
}
}
}
fn expr_stmt(&mut self, expr: &Expr) -> TokenStream {
let span = self.sp(expr.range);
match &expr.kind {
ExprKind::Assign { place, value } => {
let lowered = self.place(place, true);
let value = self.expr_at(value, self.program.types.unatomic(place.ty));
let store = self.write(&lowered, value, span);
let setup = &lowered.setup;
quote_spanned! {span=> #setup #store }
}
ExprKind::CompoundAssign {
place,
op,
value,
compute,
} => {
let lowered = self.place(place, true);
if lowered.atomic.is_some() {
let kind = PlaceRmw::Compound {
op: *op,
value,
compute: *compute,
};
return self.atomic_place_rmw(&lowered, kind, RmwValue::None, span);
}
let (hoist, rhs) = self.compound_rhs(value);
let current = self.read(&lowered, span);
let updated = self.compound_value(current, place.ty, *op, value, rhs, *compute);
let updated = updated.at(prec::LOWEST, span);
let store = self.write(&lowered, updated, span);
let setup = &lowered.setup;
quote_spanned! {span=> #setup #hoist #store }
}
ExprKind::IncDec { place, dec, .. } => {
let lowered = self.place(place, true);
if lowered.atomic.is_some() {
let kind = PlaceRmw::Step { dec: *dec };
return self.atomic_place_rmw(&lowered, kind, RmwValue::None, span);
}
let current = self.read(&lowered, span);
let next = self.step_value(current, place.ty, *dec, span);
let store = self.write(&lowered, next, span);
let setup = &lowered.setup;
quote_spanned! {span=> #setup #store }
}
ExprKind::Call { .. } => {
let diverges = ir::expr_never_returns(expr, &self.program.functions);
let tokens = self.expr(expr).at(prec::LOWEST, span);
if diverges {
quote_spanned! {span=> #tokens; ::core::unreachable!(); }
} else {
quote_spanned! {span=> #tokens; }
}
}
ExprKind::Unreachable => {
quote_spanned! {span=> ::core::hint::unreachable_unchecked(); }
}
ExprKind::Atomic(_) if expr.ty.is_void() => {
let tokens = self.expr(expr).at(prec::LOWEST, span);
quote_spanned! {span=> #tokens; }
}
ExprKind::Comma { .. } => {
let mut out = TokenStream::new();
for operand in comma_operands(expr) {
out.extend(self.expr_stmt(operand));
}
out
}
ExprKind::Cond { .. } => {
let mut spine = Vec::new();
let mut node = expr;
while let ExprKind::Cond {
cond,
then_expr,
else_expr,
} = &node.kind
{
let span = self.sp(node.range);
let cond_tokens = self.condition(cond).at_condition(span);
let then_tokens = self.expr_stmt(then_expr);
spine.push((cond_tokens, then_tokens, span));
node = else_expr;
}
let mut tokens = self.expr_stmt(node);
while let Some((cond_tokens, then_tokens, span)) = spine.pop() {
tokens = quote_spanned! {span=>
if #cond_tokens { #then_tokens } else { #tokens }
};
}
tokens
}
ExprKind::Cast(inner) if expr.ty.is_void() => self.expr_stmt(inner),
ExprKind::VaEnd => TokenStream::new(),
_ => {
let tokens = self.expr(expr).at(prec::LOWEST, span);
quote_spanned! {span=> let _ = #tokens; }
}
}
}
fn expr_at(&mut self, expr: &Expr, expected: Ty) -> TokenStream {
let span = self.sp(expr.range);
if expr.ty == expected {
match &expr.kind {
ExprKind::Int(value) => return bare_int_literal(*value, expected, span),
ExprKind::Float(value) if value.is_finite() => {
return bare_float_literal(*value, span);
}
ExprKind::Cond { .. } if !expected.is_void() => {
return self.cond_chain_at(expr, expected);
}
_ => {}
}
}
self.expr(expr).at(prec::LOWEST, span)
}
fn expr(&mut self, expr: &Expr) -> Value {
let value = self.expr_value(expr);
if matches!(expr.kind, ExprKind::Binary { .. }) {
return value;
}
self.reduce_bits(value, expr)
}
fn reduce_bits(&mut self, value: Value, expr: &Expr) -> Value {
let Some(bits) = expr.bits else {
return value;
};
let width = expr.ty.bits(&self.options.target);
let overflows = match &expr.kind {
ExprKind::Binary { op, .. } => {
matches!(op, BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Shl)
}
ExprKind::Neg(_) | ExprKind::BitNot(_) => true,
_ => false,
};
if !overflows || !expr.ty.is_integer() || bits == 0 || bits >= width {
return value;
}
let span = self.sp(expr.range);
if expr.ty.is_signed(&self.options.target) {
let shift = Literal::u32_unsuffixed(width - bits);
let tokens = value.at(prec::CALL, span);
return Value::new(
quote_spanned! {span=> #tokens.wrapping_shl(#shift).wrapping_shr(#shift) },
prec::CALL,
);
}
let mask = bare_int_literal(((1u128 << bits) - 1) as i128, expr.ty, span);
let tokens = value.at(prec::BIT_AND, span);
Value::new(quote_spanned! {span=> #tokens & #mask }, prec::BIT_AND)
}
fn expr_value(&mut self, expr: &Expr) -> Value {
let span = self.sp(expr.range);
match &expr.kind {
ExprKind::Int(value) => self.int_literal(*value, expr.ty, span),
ExprKind::Float(value) => self.float_literal(*value, expr.ty, span),
ExprKind::Zeroed => Value::new(self.zero_tokens(expr.ty, span), zero_prec(expr.ty)),
ExprKind::Load(place) if place.ty.is_va_list() => self.va_list_load(place, span),
ExprKind::Load(place) => {
let lowered = self.place(place, false);
let value = self.read(&lowered, span);
if lowered.setup.is_empty() {
value
} else {
let setup = &lowered.setup;
let tokens = value.at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> { #setup #tokens } }, prec::BLOCK)
}
}
ExprKind::AddrOf(place) => self.address_of(place, expr.ty, span),
ExprKind::FuncAddr(id) => self.func_addr(*id, span),
ExprKind::LabelAddr(id) => self.label_address(*id, expr.ty, span),
ExprKind::Assign { .. } => self.assign_chain(expr),
ExprKind::CompoundAssign {
place,
op,
value,
compute,
} => {
let lowered = self.place(place, true);
if lowered.atomic.is_some() {
let kind = PlaceRmw::Compound {
op: *op,
value,
compute: *compute,
};
let tokens = self.atomic_place_rmw(&lowered, kind, RmwValue::New, span);
return Value::new(tokens, prec::BLOCK);
}
let (hoist, rhs) = self.compound_rhs(value);
let current = self.read(&lowered, span);
let updated = self.compound_value(current, place.ty, *op, value, rhs, *compute);
let updated = updated.at(prec::LOWEST, span);
let store = self.write(&lowered, updated, span);
let read = self.read(&lowered, span).at(prec::LOWEST, span);
let setup = &lowered.setup;
Value::new(
quote_spanned! {span=> { #setup #hoist #store #read } },
prec::BLOCK,
)
}
ExprKind::IncDec {
place,
dec,
postfix,
} => {
let lowered = self.place(place, true);
if lowered.atomic.is_some() {
let want = if *postfix {
RmwValue::Old
} else {
RmwValue::New
};
let tokens =
self.atomic_place_rmw(&lowered, PlaceRmw::Step { dec: *dec }, want, span);
return Value::new(tokens, prec::BLOCK);
}
let current = self.read(&lowered, span);
let next = self.step_value(current, place.ty, *dec, span);
let store = self.write(&lowered, next, span);
let read = self.read(&lowered, span).at(prec::LOWEST, span);
let setup = &lowered.setup;
if *postfix {
let tmp = self.temporary();
Value::new(
quote_spanned! {span=>
{ #setup let #tmp = #read; #store #tmp }
},
prec::BLOCK,
)
} else {
Value::new(
quote_spanned! {span=> { #setup #store #read } },
prec::BLOCK,
)
}
}
ExprKind::ComplexOf { re, im } => self.complex_literal(expr.ty, re, im, span),
ExprKind::Neg(operand) if expr.ty.is_complex() => self.complex_neg(operand, span),
ExprKind::BitNot(operand) if expr.ty.is_complex() => {
self.complex_conj(operand, expr.ty, span)
}
ExprKind::Neg(operand) => {
let value = self.expr(operand);
if expr.ty.is_floating() {
let ends_with_type = value.ends_with_type;
let tokens = value.at(prec::UNARY, span);
Value::new(quote_spanned! {span=> -#tokens }, prec::UNARY)
.type_end(ends_with_type)
} else {
let tokens = value.at(prec::CALL, span);
Value::new(quote_spanned! {span=> #tokens.wrapping_neg() }, prec::CALL)
}
}
ExprKind::BitNot(operand) => {
let value = self.expr(operand);
let ends_with_type = value.ends_with_type;
let tokens = value.at(prec::UNARY, span);
Value::new(quote_spanned! {span=> !#tokens }, prec::UNARY).type_end(ends_with_type)
}
ExprKind::Binary { .. } => self.binary_chain(expr),
ExprKind::PtrOffset { ptr, index, sub } => {
let pointee = self.program.types.pointee(ptr.ty).unwrap_or(Ty::Void);
let base = self.expr(ptr).at(prec::CALL, span);
let offset = self.scaled_offset(pointee, index, *sub, span);
Value::new(quote_spanned! {span=> #base.offset(#offset) }, prec::CALL)
}
ExprKind::PtrDiff { lhs, rhs }
if label_state(lhs).is_some() && label_state(rhs).is_some() =>
{
self.label_difference(lhs, rhs, expr.ty, span)
}
ExprKind::PtrDiff { lhs, rhs } => {
let pointee = self.program.types.pointee(lhs.ty).unwrap_or(Ty::Void);
let scale = self.vm_scale(pointee, span);
let left = self.expr(lhs).at(prec::CALL, span);
let right = self.expr(rhs).at(prec::LOWEST, span);
let target = self.ty(expr.ty, span);
let difference = match scale {
None => quote_spanned! {span=> #left.offset_from(#right) },
Some(scale) => {
quote_spanned! {span=> (#left.offset_from(#right) / (#scale)) }
}
};
Value::new(quote_spanned! {span=> #difference as #target }, prec::CAST)
.type_end(true)
}
ExprKind::Compare { .. } | ExprKind::Logical { .. } => {
let condition = self.condition(expr).at(prec::LOWEST, span);
let ty = self.ty(Ty::Int, span);
let parens = parenthesize(condition, span);
Value::new(quote_spanned! {span=> #parens as #ty }, prec::CAST).type_end(true)
}
ExprKind::Cast(inner) if expr.ty.is_void() => {
let tokens = self.expr_stmt(inner);
Value::new(quote_spanned! {span=> { #tokens } }, prec::BLOCK)
}
ExprKind::Cond { .. } if expr.ty.is_void() => {
let tokens = self.expr_stmt(expr);
Value::new(quote_spanned! {span=> { #tokens } }, prec::BLOCK)
}
ExprKind::Cast(inner) => {
let from = inner.ty;
let value = self.expr(inner);
self.cast(value, from, expr.ty, span)
}
ExprKind::Cond { .. } => self.cond_chain(expr),
ExprKind::Comma { lhs, rhs } => {
let lhs = self.expr_stmt(lhs);
let rhs = self.expr(rhs).at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> { #lhs #rhs } }, prec::BLOCK)
}
ExprKind::Call { callee, args } => self.call(callee, args, span),
ExprKind::RecordLit { record, fields } => self.record_literal(*record, fields, span),
ExprKind::UnionLit {
record,
index,
value,
} => self.union_literal(*record, *index, value, span),
ExprKind::ArrayLit(items) => {
let elem = self.program.types.elem(expr.ty).unwrap_or(Ty::Int);
let mut tokens = TokenStream::new();
for (index, item) in items.iter().enumerate() {
if index > 0 {
tokens.extend(quote_spanned! {span=> , });
}
tokens.extend(self.expr_at(item, elem));
}
Value::atom(bracketed(tokens, span))
}
ExprKind::ArrayRepeat { value, len } => {
let elem = self.program.types.elem(expr.ty).unwrap_or(value.ty);
let tokens = self.expr_at(value, elem);
let len = usize_literal(*len, span);
Value::atom(bracketed(quote_spanned! {span=> #tokens ; #len }, span))
}
ExprKind::CondDefault { value, else_expr } => {
let ty = expr.ty;
let first = self.expr_at(value, ty);
let other = self.expr_at(else_expr, ty);
let tmp = self.temporary();
let target = self.ty(ty, span);
let test = if ty.is_pointer() {
quote_spanned! {span=> !#tmp.is_null() }
} else {
let zero = self.zero_tokens(ty, span);
quote_spanned! {span=> #tmp != #zero }
};
Value::new(
quote_spanned! {span=>
{ let #tmp: #target = #first; if #test { #tmp } else { #other } }
},
prec::BLOCK,
)
}
ExprKind::StmtExpr { stmts, value } => {
let body = self.stmts(stmts);
let tail = match value {
Some(value) => {
let ty = expr.ty;
self.expr_at(value, ty)
}
None => TokenStream::new(),
};
Value::new(quote_spanned! {span=> { #body #tail } }, prec::BLOCK)
}
ExprKind::Builtin { op, args } => self.builtin(*op, args, span),
ExprKind::Atomic(atomic) => self.atomic(atomic, span),
ExprKind::VaListPristine => Value::new(self.va_pristine(span), prec::CALL),
ExprKind::VaArg { ap, record } => self.va_arg(ap, record.as_deref(), expr.ty, span),
ExprKind::VaEnd => Value::atom(quote_spanned! {span=> () }),
ExprKind::Unreachable => Value::new(
quote_spanned! {span=> ::core::hint::unreachable_unchecked() },
prec::CALL,
),
}
}
fn complex_new(&self, ty: Ty, re: TokenStream, im: TokenStream, span: Span) -> Value {
self.uses_complex.set(true);
let rt = self.rt_path(span);
let component = primitive_ty(if ty == Ty::ComplexFloat { "f32" } else { "f64" }, span);
Value::new(
quote_spanned! {span=> #rt::Complex::<#component>::new(#re, #im) },
prec::CALL,
)
}
fn complex_literal(&mut self, ty: Ty, re: &Expr, im: &Expr, span: Span) -> Value {
let re = self.expr(re).at(prec::LOWEST, span);
let im = self.expr(im).at(prec::LOWEST, span);
self.complex_new(ty, re, im, span)
}
fn complex_neg(&mut self, operand: &Expr, span: Span) -> Value {
let tokens = self.expr(operand).at(prec::UNARY, span);
Value::new(quote_spanned! {span=> -#tokens }, prec::UNARY)
}
fn complex_conj(&mut self, operand: &Expr, ty: Ty, span: Span) -> Value {
let tokens = self.expr(operand).at(prec::LOWEST, span);
let conj = self.rt_complex(&format!("conj_{}", Self::complex_suffix(ty)), span);
Value::new(quote_spanned! {span=> #conj(#tokens) }, prec::CALL)
}
fn complex_binary(
&mut self,
op: BinOp,
(lhs, lhs_ty): (Value, Ty),
(rhs, rhs_ty): (Value, Ty),
ty: Ty,
span: Span,
) -> Value {
self.uses_complex.set(true);
let suffix = Self::complex_suffix(ty);
let both = lhs_ty.is_complex() && rhs_ty.is_complex();
if both && matches!(op, BinOp::Add | BinOp::Sub) {
let (level, tokens) = match op {
BinOp::Add => (prec::SUM, quote_spanned! {span=> + }),
_ => (prec::SUM, quote_spanned! {span=> - }),
};
let mut out = lhs.at(level, span);
let rhs = rhs.at(level + 1, span);
out.extend(quote_spanned! {span=> #tokens #rhs });
return Value::new(out, level);
}
let name = match (op, lhs_ty.is_complex(), rhs_ty.is_complex()) {
(BinOp::Add, true, false) => "add_real",
(BinOp::Add, false, true) => "real_add",
(BinOp::Sub, true, false) => "sub_real",
(BinOp::Sub, false, true) => "real_sub",
(BinOp::Mul, true, true) => "mul",
(BinOp::Mul, true, false) => "mul_real",
(BinOp::Mul, false, true) => "real_mul",
(BinOp::Div, true, true) => "div",
(BinOp::Div, true, false) => "div_real",
(BinOp::Div, false, true) => "real_div",
_ => unreachable!("'{}' does not reach complex code generation", op.as_str()),
};
let func = self.rt_complex(&format!("{name}_{suffix}"), span);
let lhs = lhs.at(prec::LOWEST, span);
let rhs = rhs.at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> #func(#lhs, #rhs) }, prec::CALL)
}
fn complex_cast(&mut self, value: Value, from: Ty, to: Ty, span: Span) -> Value {
self.uses_complex.set(true);
if from.is_complex() && to.is_complex() {
let name = if to == Ty::ComplexDouble {
"widen_f32"
} else {
"narrow_f64"
};
let func = self.rt_complex(name, span);
let tokens = value.at(prec::LOWEST, span);
return Value::new(quote_spanned! {span=> #func(#tokens) }, prec::CALL);
}
if to.is_complex() {
let component = to.complex_component();
let re = self
.cast(value, from, component, span)
.at(prec::LOWEST, span);
let zero = bare_float_literal(0.0, span);
return self.complex_new(to, re, zero, span);
}
if to.is_bool() {
let func = self.rt_complex(&format!("nonzero_{}", Self::complex_suffix(from)), span);
let tokens = value.at(prec::LOWEST, span);
return Value::new(quote_spanned! {span=> #func(#tokens) }, prec::CALL);
}
let tokens = value.at(prec::CALL, span);
let real = Value::new(quote_spanned! {span=> #tokens.re }, prec::CALL);
self.cast(real, from.complex_component(), to, span)
}
fn complex_equality(&mut self, op: CmpOp, lhs: &Expr, rhs: &Expr, span: Span) -> Value {
self.uses_complex.set(true);
let name = match op {
CmpOp::Eq => "eq",
CmpOp::Ne => "ne",
other => unreachable!("{other:?} does not reach a complex comparison"),
};
let func = self.rt_complex(&format!("{name}_{}", Self::complex_suffix(lhs.ty)), span);
let (lhs, rhs) = self.operands(lhs, rhs, BinOp::BitOr);
let lhs = lhs.at(prec::LOWEST, span);
let rhs = rhs.at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> #func(#lhs, #rhs) }, prec::CALL)
}
fn complex_condition(&mut self, expr: &Expr, span: Span) -> Value {
self.uses_complex.set(true);
let func = self.rt_complex(&format!("nonzero_{}", Self::complex_suffix(expr.ty)), span);
let tokens = self.expr(expr).at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> #func(#tokens) }, prec::CALL)
}
fn record_literal(&mut self, record: ir::RecordId, fields: &[Expr], span: Span) -> Value {
let def = self.program.types.record(record).clone();
let tail = def
.fields
.iter()
.position(|field| field.flexible)
.and_then(|index| {
Some((
index,
array_len(&self.program.types, fields.get(index)?.ty)?,
))
})
.filter(|(_, len)| *len > 0);
let name = match tail {
Some((_, len)) => self.flexible_ident(&def.rust_name, len, span),
None => self.c_ident(&def.rust_name, span),
};
let mut packed: HashMap<String, Vec<u8>> = HashMap::new();
let mut dynamic: Vec<usize> = Vec::new();
for rust_field in &def.rust_fields {
if let ir::RustField::Bits { name, bytes, .. } = rust_field {
packed.insert(name.clone(), vec![0u8; *bytes as usize]);
}
}
for (index, field) in def.fields.iter().enumerate() {
let Some(bits) = &field.bits else { continue };
match fields.get(index).and_then(constant_bits) {
Some(value) => {
if let Some(storage) = packed.get_mut(&bits.storage) {
pack_bits(storage, bits, value);
}
}
None => dynamic.push(index),
}
}
let mut items = TokenStream::new();
for rust_field in &def.rust_fields {
match rust_field {
ir::RustField::Member(index) => {
let field = &def.fields[*index];
let fname = self.c_ident(&field.name, span);
let value = &fields[*index];
let want = match tail {
Some((flexible, _)) if flexible == *index => value.ty,
_ => field.ty,
};
let tokens = self.expr_at(value, want);
items.extend(quote_spanned! {span=> #fname: #tokens, });
}
ir::RustField::Bits { name, .. } => {
let fname = Ident::new(name, span);
let value = byte_array(&packed[name], span);
items.extend(quote_spanned! {span=> #fname: #value, });
}
ir::RustField::Pad { name, bytes } => {
let fname = Ident::new(name, span);
let len = usize_literal(*bytes, span);
items.extend(quote_spanned! {span=> #fname: [0; #len], });
}
ir::RustField::Align { name, .. } => {
let fname = Ident::new(name, span);
items.extend(quote_spanned! {span=> #fname: [], });
}
}
}
let body = braced(items, span);
let literal = quote_spanned! {span=> #name #body };
if dynamic.is_empty() {
return Value::new(literal, prec::ATOM);
}
let tmp = self.temporary();
let ty = self.ty(Ty::Record(record), span);
let mut stores = TokenStream::new();
for index in dynamic {
let field = &def.fields[index];
let bits = field.bits.as_ref().expect("only bit-fields are deferred");
let setter = self.c_ident(&bits.setter, span);
let value = self.expr_at(&fields[index], field.ty);
stores.extend(quote_spanned! {span=> #tmp.#setter(#value); });
}
Value::new(
quote_spanned! {span=>
{ let mut #tmp: #ty = #literal; #stores #tmp }
},
prec::BLOCK,
)
}
fn union_literal(
&mut self,
record: ir::RecordId,
index: usize,
value: &Expr,
span: Span,
) -> Value {
let def = self.program.types.record(record).clone();
let name = self.c_ident(&def.rust_name, span);
let field = &def.fields[index];
let Some(bits) = &field.bits else {
let fname = self.c_ident(&field.name, span);
let tokens = self.expr_at(value, field.ty);
let body = braced(quote_spanned! {span=> #fname: #tokens }, span);
return Value::new(quote_spanned! {span=> #name #body }, prec::ATOM);
};
let bytes = def
.rust_fields
.iter()
.find_map(|rust_field| match rust_field {
ir::RustField::Bits { name, bytes, .. } if *name == bits.storage => Some(*bytes),
_ => None,
})
.unwrap_or(0);
let mut packed = vec![0u8; bytes as usize];
let constant = constant_bits(value);
if let Some(constant) = constant {
pack_bits(&mut packed, bits, constant);
}
let storage = Ident::new(&bits.storage, span);
let array = byte_array(&packed, span);
let body = braced(quote_spanned! {span=> #storage: #array }, span);
let literal = quote_spanned! {span=> #name #body };
if constant.is_some() {
return Value::new(literal, prec::ATOM);
}
let tmp = self.temporary();
let ty = self.ty(Ty::Record(record), span);
let setter = self.c_ident(&bits.setter, span);
let value = self.expr_at(value, field.ty);
Value::new(
quote_spanned! {span=>
{ let mut #tmp: #ty = #literal; #tmp.#setter(#value); #tmp }
},
prec::BLOCK,
)
}
fn builtin(&mut self, op: ir::BuiltinOp, args: &[Expr], span: Span) -> Value {
use ir::BuiltinOp;
let int = self.ty(Ty::Int, span);
match op {
BuiltinOp::ComplexProj => {
let ty = args[0].ty;
let func = self.rt_complex(&format!("proj_{}", Self::complex_suffix(ty)), span);
let value = self.expr(&args[0]).at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> #func(#value) }, prec::CALL)
}
BuiltinOp::Discard => {
let mut out = TokenStream::new();
for arg in args {
out.extend(self.expr_stmt(arg));
}
Value::new(quote_spanned! {span=> { #out } }, prec::BLOCK)
}
BuiltinOp::Prefetch(packed) => {
let (write, locality) = ir::prefetch_hint(packed);
let arch = self.options.target.arch;
let pointer = self.expr(&args[0]).at(prec::LOWEST, span);
let tmp = self.temporary();
let prefetch = match arch {
_ if self.in_safe => TokenStream::new(),
crate::target::Arch::X86 | crate::target::Arch::X86_64 => {
let module = self.arch_module(span);
let hint = Ident::new(
["_MM_HINT_NTA", "_MM_HINT_T2", "_MM_HINT_T1", "_MM_HINT_T0"]
[usize::from(locality)],
span,
);
let sse = if arch == crate::target::Arch::X86 {
quote_spanned! {span=> #[cfg(target_feature = "sse")] }
} else {
TokenStream::new()
};
quote_spanned! {span=>
#sse
::core::arch::#module::_mm_prefetch::<{ ::core::arch::#module::#hint }>(
#tmp.cast::<i8>()
);
}
}
crate::target::Arch::Aarch64 => {
let kind = if write { "pst" } else { "pld" };
let level = ["l1strm", "l3keep", "l2keep", "l1keep"][usize::from(locality)];
let mut template = Literal::string(&format!("prfm {kind}{level}, [{{0}}]"));
template.set_span(span);
quote_spanned! {span=>
::core::arch::asm!(
#template,
in(reg) #tmp,
options(nostack, preserves_flags, readonly)
);
}
}
_ => TokenStream::new(),
};
Value::new(
quote_spanned! {span=> { let #tmp = #pointer; #prefetch } },
prec::BLOCK,
)
}
BuiltinOp::CpuSupports(row) => {
let mut test = TokenStream::new();
for (index, feature) in crate::x86::detect_features(row).iter().enumerate() {
if index > 0 {
test.extend(quote_spanned! {span=> && });
}
let mut literal = Literal::string(feature);
literal.set_span(span);
test.extend(quote_spanned! {span=> ::std::is_x86_feature_detected!(#literal) });
}
Value::new(quote_spanned! {span=> ((#test) as #int) }, prec::LOWEST)
}
BuiltinOp::Alloca => {
let arena = self.arena_ident(span);
let size = self.expr(&args[0]).at(prec::CAST, span);
let usize_ty = primitive_ty("usize", span);
Value::new(
quote_spanned! {span=> #arena.alloca(#size as #usize_ty) },
prec::CALL,
)
}
BuiltinOp::Bswap => {
let operand = args[0].ty;
let value = self.unsigned_operand(&args[0], span);
let target = self.ty(operand, span);
Value::new(
quote_spanned! {span=> #value.swap_bytes() as #target },
prec::CAST,
)
.type_end(true)
}
BuiltinOp::Popcount => {
let value = self.unsigned_operand(&args[0], span);
Value::new(
quote_spanned! {span=> #value.count_ones() as #int },
prec::CAST,
)
.type_end(true)
}
BuiltinOp::Parity => {
let value = self.unsigned_operand(&args[0], span);
Value::new(
quote_spanned! {span=> (#value.count_ones() & 1) as #int },
prec::CAST,
)
.type_end(true)
}
BuiltinOp::Clz => {
let value = self.unsigned_operand(&args[0], span);
Value::new(
quote_spanned! {span=> #value.leading_zeros() as #int },
prec::CAST,
)
.type_end(true)
}
BuiltinOp::Ctz => {
let value = self.unsigned_operand(&args[0], span);
Value::new(
quote_spanned! {span=> #value.trailing_zeros() as #int },
prec::CAST,
)
.type_end(true)
}
BuiltinOp::Ffs => {
let value = self.unsigned_operand(&args[0], span);
let tmp = self.temporary();
Value::new(
quote_spanned! {span=>
{ let #tmp = #value;
if #tmp == 0 { 0 } else { #tmp.trailing_zeros() as #int + 1 } }
},
prec::BLOCK,
)
}
BuiltinOp::Clrsb => {
let width = args[0].ty.bits(&self.options.target);
let signed = signed_rust_ty(width, span);
let value = self.expr(&args[0]).at(prec::CAST, span);
let tmp = self.temporary();
let bits = usize_literal(u64::from(width), span);
Value::new(
quote_spanned! {span=>
{ let #tmp = #value as #signed;
((#tmp ^ (#tmp << 1)).leading_zeros() as #int)
.min(#bits as #int - 1) }
},
prec::BLOCK,
)
}
BuiltinOp::Overflow(bin) | BuiltinOp::OverflowP(bin) => {
let store = matches!(op, BuiltinOp::Overflow(_));
let result_ty = if store {
self.program.types.pointee(args[2].ty).unwrap_or(Ty::Int)
} else {
args[2].ty
};
self.overflow_builtin(bin, args, store, result_ty, span)
}
BuiltinOp::Fabs => {
let bits = self.float_bits_of(&args[0], span);
let (float_ty, mask) = float_bit_ty(args[0].ty, span);
Value::new(
quote_spanned! {span=> <#float_ty>::from_bits(#bits & #mask) },
prec::CALL,
)
}
BuiltinOp::Copysign => {
let magnitude = self.float_bits_of(&args[0], span);
let sign = self.float_bits_of(&args[1], span);
let (float_ty, mask) = float_bit_ty(args[0].ty, span);
Value::new(
quote_spanned! {span=>
<#float_ty>::from_bits((#magnitude & #mask) | (#sign & !#mask))
},
prec::CALL,
)
}
BuiltinOp::FloatOrder(order) => self.float_order(order, args, span),
BuiltinOp::FloatClass(class) => self.float_class(class, &args[0], span),
BuiltinOp::Fpclassify => {
let value = self.expr(&args[5]).at(prec::CALL, span);
let arms = ["Nan", "Infinite", "Normal", "Subnormal", "Zero"]
.iter()
.zip(args)
.map(|(name, answer)| {
let variant = Ident::new(name, span);
let answer = self.expr_at(answer, Ty::Int);
quote_spanned! {span=>
::core::num::FpCategory::#variant => #answer,
}
})
.collect::<TokenStream>();
Value::new(
quote_spanned! {span=> match #value.classify() { #arms } },
prec::BLOCK,
)
}
}
}
fn float_bits_of(&mut self, arg: &Expr, span: Span) -> TokenStream {
let value = self.expr(arg).at(prec::CALL, span);
parenthesize(quote_spanned! {span=> #value.to_bits() }, span)
}
fn float_order(&mut self, order: ir::FloatOrder, args: &[Expr], span: Span) -> Value {
use ir::FloatOrder;
let int = self.ty(Ty::Int, span);
let lhs = self.expr(&args[0]).at(prec::LOWEST, span);
let rhs = self.expr(&args[1]).at(prec::LOWEST, span);
let (a, b) = (self.temporary(), self.temporary());
let test = match order {
FloatOrder::Greater => quote_spanned! {span=> #a > #b },
FloatOrder::GreaterEqual => quote_spanned! {span=> #a >= #b },
FloatOrder::Less => quote_spanned! {span=> #a < #b },
FloatOrder::LessEqual => quote_spanned! {span=> #a <= #b },
FloatOrder::LessGreater => quote_spanned! {span=> #a < #b || #a > #b },
FloatOrder::Unordered => quote_spanned! {span=> #a.is_nan() || #b.is_nan() },
};
Value::new(
quote_spanned! {span=>
{ let #a = #lhs; let #b = #rhs; (#test) as #int }
},
prec::BLOCK,
)
}
fn float_class(&mut self, class: ir::FloatClass, arg: &Expr, span: Span) -> Value {
use ir::FloatClass;
let int = self.ty(Ty::Int, span);
let method = |name: &str| Ident::new(name, span);
let test = match class {
FloatClass::IsNan => Some(method("is_nan")),
FloatClass::IsInf => Some(method("is_infinite")),
FloatClass::IsFinite => Some(method("is_finite")),
FloatClass::IsNormal => Some(method("is_normal")),
FloatClass::SignBit => Some(method("is_sign_negative")),
FloatClass::IsInfSign | FloatClass::IsSignaling => None,
};
if let Some(test) = test {
let value = self.expr(arg).at(prec::CALL, span);
return Value::new(quote_spanned! {span=> #value.#test() as #int }, prec::CAST)
.type_end(true);
}
let tmp = self.temporary();
let value = self.expr(arg).at(prec::LOWEST, span);
if class == FloatClass::IsInfSign {
return Value::new(
quote_spanned! {span=>
{ let #tmp = #value;
if #tmp.is_infinite() {
if #tmp.is_sign_negative() { -1 as #int } else { 1 as #int }
} else { 0 as #int } }
},
prec::BLOCK,
);
}
let quiet = quiet_bit_literal(arg.ty, span);
Value::new(
quote_spanned! {span=>
{ let #tmp = #value;
(#tmp.is_nan() && (#tmp.to_bits() & #quiet) == 0) as #int }
},
prec::BLOCK,
)
}
fn atomic_path(&self, class: AtomicClass, span: Span) -> TokenStream {
if matches!(class, AtomicClass::Ptr | AtomicClass::FnPtr) {
let void = self.pointee_ty(Ty::Void, span);
return quote_spanned! {span=>
::core::sync::atomic::AtomicPtr::<#void>
};
}
let name = Ident::new(class.rust_name(), span);
quote_spanned! {span=> ::core::sync::atomic::#name }
}
fn atomic_repr_ty(&self, class: AtomicClass, span: Span) -> TokenStream {
if matches!(class, AtomicClass::Ptr | AtomicClass::FnPtr) {
let void = self.pointee_ty(Ty::Void, span);
return quote_spanned! {span=> *mut #void };
}
primitive_ty(class.repr_name(), span)
}
fn atomic_ref(&self, class: AtomicClass, ptr: TokenStream, span: Span) -> TokenStream {
let path = self.atomic_path(class, span);
let repr = self.atomic_repr_ty(class, span);
quote_spanned! {span=> #path::from_ptr(#ptr as *mut #repr) }
}
fn ordering(&self, order: ir::MemOrder, span: Span) -> TokenStream {
let name = Ident::new(order.rust_name(), span);
quote_spanned! {span=> ::core::sync::atomic::Ordering::#name }
}
fn repr_to_value(&self, class: AtomicClass, ty: Ty, value: TokenStream, span: Span) -> Value {
match class {
AtomicClass::Bool => Value::atom(value),
AtomicClass::Float { bytes } => {
let float = primitive_ty(if bytes == 4 { "f32" } else { "f64" }, span);
Value::new(
quote_spanned! {span=> <#float>::from_bits(#value) },
prec::CALL,
)
}
AtomicClass::Int { .. } | AtomicClass::Ptr => {
let target = self.ty(ty, span);
Value::new(quote_spanned! {span=> #value as #target }, prec::CAST).type_end(true)
}
AtomicClass::FnPtr => {
let repr = self.atomic_repr_ty(class, span);
let target = self.ty(ty, span);
Value::new(
quote_spanned! {span=>
::core::mem::transmute::<#repr, #target>(#value)
},
prec::CALL,
)
}
}
}
fn value_to_repr(&self, class: AtomicClass, ty: Ty, value: Value, span: Span) -> TokenStream {
match class {
AtomicClass::Bool => value.at(prec::LOWEST, span),
AtomicClass::Float { bytes } => {
let float = primitive_ty(if bytes == 4 { "f32" } else { "f64" }, span);
let value = value.at(prec::LOWEST, span);
quote_spanned! {span=> <#float>::to_bits(#value) }
}
AtomicClass::Int { .. } | AtomicClass::Ptr => {
let repr = self.atomic_repr_ty(class, span);
let value = value.at(prec::CAST, span);
quote_spanned! {span=> #value as #repr }
}
AtomicClass::FnPtr => {
let repr = self.atomic_repr_ty(class, span);
let source = self.ty(ty, span);
let value = value.at(prec::LOWEST, span);
quote_spanned! {span=>
::core::mem::transmute::<#source, #repr>(#value)
}
}
}
}
fn either_way(&mut self, result: TokenStream, span: Span) -> TokenStream {
let value = self.temporary();
quote_spanned! {span=>
match #result {
::core::result::Result::Ok(#value) | ::core::result::Result::Err(#value) => #value,
}
}
}
fn atomic_cas_loop(
&mut self,
object: &TokenStream,
current: &Ident,
updated: TokenStream,
order: ir::MemOrder,
span: Span,
) -> TokenStream {
let success = self.ordering(order, span);
let failure = self.ordering(order.failure_order(), span);
let slot = self.temporary();
let fresh = self.temporary();
let seen = self.temporary();
quote_spanned! {span=>
{
let #slot = #object;
let mut #current = #slot.load(#failure);
loop {
let #fresh = #updated;
match #slot.compare_exchange_weak(#current, #fresh, #success, #failure) {
::core::result::Result::Ok(_) => break #current,
::core::result::Result::Err(#seen) => #current = #seen,
}
}
}
}
}
fn atomic(&mut self, atomic: &ir::AtomicExpr, span: Span) -> Value {
let class = atomic.class;
let success = self.ordering(atomic.success, span);
if let ir::AtomicOp::Fence { signal } = atomic.op {
if atomic.success == ir::MemOrder::Relaxed {
return Value::atom(quote_spanned! {span=> () });
}
let name = Ident::new(if signal { "compiler_fence" } else { "fence" }, span);
return Value::new(
quote_spanned! {span=> ::core::sync::atomic::#name(#success) },
prec::CALL,
);
}
let ptr = match &atomic.ptr {
Some(ptr) => self.expr(ptr).at(prec::CAST, span),
None => return Value::atom(quote_spanned! {span=> () }),
};
let object = self.atomic_ref(class, ptr, span);
let ty = atomic.value_ty;
match atomic.op {
ir::AtomicOp::Fence { .. } => unreachable!("handled above"),
ir::AtomicOp::Load => self.repr_to_value(
class,
ty,
quote_spanned! {span=> #object.load(#success) },
span,
),
ir::AtomicOp::Store => {
let value = self.atomic_operand(atomic, span);
Value::new(
quote_spanned! {span=> #object.store(#value, #success) },
prec::CALL,
)
}
ir::AtomicOp::Exchange => {
let value = self.atomic_operand(atomic, span);
self.repr_to_value(
class,
ty,
quote_spanned! {span=> #object.swap(#value, #success) },
span,
)
}
ir::AtomicOp::Clear => Value::new(
quote_spanned! {span=> #object.store(0, #success) },
prec::CALL,
),
ir::AtomicOp::TestAndSet => Value::new(
quote_spanned! {span=> #object.swap(1, #success) != 0 },
prec::CMP,
),
ir::AtomicOp::CompareExchange { weak } => {
self.compare_exchange(atomic, object, weak, span)
}
ir::AtomicOp::SyncCompareSwap { value_is_old } => {
self.sync_compare_swap(atomic, object, value_is_old, span)
}
ir::AtomicOp::Rmw { op, returns_new } => {
self.atomic_rmw(atomic, object, op, returns_new, span)
}
}
}
fn atomic_operand(&mut self, atomic: &ir::AtomicExpr, span: Span) -> TokenStream {
let Some(value) = &atomic.value else {
return quote_spanned! {span=> () };
};
let value = self.expr(value);
self.value_to_repr(atomic.class, atomic.value_ty, value, span)
}
fn compare_exchange(
&mut self,
atomic: &ir::AtomicExpr,
object: TokenStream,
weak: bool,
span: Span,
) -> Value {
let class = atomic.class;
let ty = atomic.value_ty;
let expected = match &atomic.expected {
Some(expected) => self.expr(expected).at(prec::CALL, span),
None => return Value::atom(quote_spanned! {span=> false }),
};
let desired = self.atomic_operand(atomic, span);
let slot = self.temporary();
let seen = self.temporary();
let current = self.value_to_repr(
class,
ty,
Value::atom(quote_spanned! {span=> *#slot }),
span,
);
let method = Ident::new(
if weak {
"compare_exchange_weak"
} else {
"compare_exchange"
},
span,
);
let success = self.ordering(atomic.success, span);
let failure = self.ordering(atomic.failure, span);
let observed = self
.repr_to_value(class, ty, quote_spanned! {span=> #seen }, span)
.at(prec::LOWEST, span);
Value::new(
quote_spanned! {span=>
{
let #slot = #expected;
match #object.#method(#current, #desired, #success, #failure) {
::core::result::Result::Ok(_) => true,
::core::result::Result::Err(#seen) => {
*#slot = #observed;
false
}
}
}
},
prec::BLOCK,
)
}
fn sync_compare_swap(
&mut self,
atomic: &ir::AtomicExpr,
object: TokenStream,
value_is_old: bool,
span: Span,
) -> Value {
let class = atomic.class;
let ty = atomic.value_ty;
let expected = match &atomic.expected {
Some(expected) => {
let value = self.expr(expected);
self.value_to_repr(class, ty, value, span)
}
None => return Value::atom(quote_spanned! {span=> false }),
};
let desired = self.atomic_operand(atomic, span);
let seq = self.ordering(ir::MemOrder::SeqCst, span);
let call = quote_spanned! {span=>
#object.compare_exchange(#expected, #desired, #seq, #seq)
};
if !value_is_old {
return Value::new(quote_spanned! {span=> #call.is_ok() }, prec::CALL);
}
let old = self.either_way(call, span);
self.repr_to_value(class, ty, parenthesize(old, span), span)
}
fn atomic_rmw(
&mut self,
atomic: &ir::AtomicExpr,
object: TokenStream,
op: ir::AtomicRmw,
returns_new: bool,
span: Span,
) -> Value {
let class = atomic.class;
let ty = atomic.value_ty;
let success = self.ordering(atomic.success, span);
let operand = self.temporary();
let old = self.temporary();
if class == AtomicClass::Ptr {
let delta = match &atomic.value {
Some(value) => self.expr(value).at(prec::CAST, span),
None => quote_spanned! {span=> 0 },
};
let isize_ty = primitive_ty("isize", span);
let signed = if op == ir::AtomicRmw::Sub {
quote_spanned! {span=> -(#delta as #isize_ty) }
} else {
quote_spanned! {span=> #delta as #isize_ty }
};
let step = self.temporary();
let updated = self.atomic_cas_loop(
&object,
&step,
quote_spanned! {span=> #step.wrapping_byte_offset(#operand) },
atomic.success,
span,
);
let tail = if returns_new {
quote_spanned! {span=> #old.wrapping_byte_offset(#operand) }
} else {
quote_spanned! {span=> #old }
};
let tail = self
.repr_to_value(class, ty, tail, span)
.at(prec::LOWEST, span);
return Value::new(
quote_spanned! {span=>
{
let #operand: #isize_ty = #signed;
let #old = #updated;
#tail
}
},
prec::BLOCK,
);
}
let value = self.atomic_operand(atomic, span);
let repr = self.atomic_repr_ty(class, span);
let update = match op.rust_method() {
Some(method) if op != ir::AtomicRmw::Nand || class == AtomicClass::Bool => {
let method = Ident::new(method, span);
quote_spanned! {span=> #object.#method(#operand, #success) }
}
_ if class == AtomicClass::Bool => {
let method = Ident::new("fetch_nand", span);
quote_spanned! {span=> #object.#method(#operand, #success) }
}
_ => {
let current = self.temporary();
self.atomic_cas_loop(
&object,
¤t,
quote_spanned! {span=> !(#current & #operand) },
atomic.success,
span,
)
}
};
let combined = self.atomic_combine(op, &old, &operand, span);
let tail = if returns_new {
combined
} else {
quote_spanned! {span=> #old }
};
let tail = self
.repr_to_value(class, ty, tail, span)
.at(prec::LOWEST, span);
Value::new(
quote_spanned! {span=>
{
let #operand: #repr = #value;
let #old = #update;
#tail
}
},
prec::BLOCK,
)
}
fn atomic_place_rmw(
&mut self,
lowered: &LoweredPlace,
kind: PlaceRmw<'_>,
want: RmwValue,
span: Span,
) -> TokenStream {
let (class, ty) = lowered.atomic.expect("an atomic place");
let object = self.atomic_object_of(lowered, span);
let setup = &lowered.setup;
let operand = self.temporary();
let old = self.temporary();
let success = self.ordering(ir::MemOrder::SeqCst, span);
let method = match (class, &kind) {
(
AtomicClass::Int { .. },
PlaceRmw::Compound {
op,
compute,
value: _,
},
) if *compute == ty => rmw_of_binop(*op),
(AtomicClass::Int { .. }, PlaceRmw::Step { dec }) => Some(if *dec {
ir::AtomicRmw::Sub
} else {
ir::AtomicRmw::Add
}),
_ => None,
};
if let Some(op) = method.filter(|op| op.rust_method().is_some()) {
let repr = self.atomic_repr_ty(class, span);
let value = match &kind {
PlaceRmw::Compound { value, compute, .. } => {
let tokens = self.expr_at(value, *compute);
self.value_to_repr(class, ty, Value::new(tokens, prec::LOWEST), span)
}
PlaceRmw::Step { .. } => quote_spanned! {span=> 1 },
};
let name = Ident::new(op.rust_method().expect("filtered"), span);
let tail = self.atomic_rmw_tail((class, ty), op, &old, &operand, want, span);
return quote_spanned! {span=>
{ #setup
let #operand: #repr = #value;
let #old = #object.#name(#operand, #success);
#tail }
};
}
let hoisted = match &kind {
PlaceRmw::Compound { value, compute, .. } => {
let tokens = self.expr_at(value, *compute);
let rhs_ty = self.ty(*compute, span);
Some(quote_spanned! {span=> let #operand: #rhs_ty = #tokens; })
}
PlaceRmw::Step { .. } => None,
};
let param = self.temporary();
let current = self.repr_to_value(class, ty, quote_spanned! {span=> #param }, span);
let updated = self.apply_place_rmw(&kind, current, ty, &operand, span);
let updated = self.value_to_repr(class, ty, updated, span);
let loop_result =
self.atomic_cas_loop(&object, ¶m, updated, ir::MemOrder::SeqCst, span);
let tail = match want {
RmwValue::None => TokenStream::new(),
RmwValue::Old => self
.repr_to_value(class, ty, quote_spanned! {span=> #old }, span)
.at(prec::LOWEST, span),
RmwValue::New => {
let previous = self.repr_to_value(class, ty, quote_spanned! {span=> #old }, span);
let value = self.apply_place_rmw(&kind, previous, ty, &operand, span);
value.at(prec::LOWEST, span)
}
};
quote_spanned! {span=>
{ #setup #hoisted
let #old = #loop_result;
#tail }
}
}
fn apply_place_rmw(
&mut self,
kind: &PlaceRmw<'_>,
current: Value,
ty: Ty,
operand: &Ident,
span: Span,
) -> Value {
match kind {
PlaceRmw::Compound { op, value, compute } => {
let rhs = Value::atom(quote_spanned! {span=> #operand });
self.compound_value(current, ty, *op, value, Some(rhs), *compute)
}
PlaceRmw::Step { dec } => {
Value::new(self.step_value(current, ty, *dec, span), prec::LOWEST)
}
}
}
fn atomic_rmw_tail(
&mut self,
atomic: (AtomicClass, Ty),
op: ir::AtomicRmw,
old: &Ident,
operand: &Ident,
want: RmwValue,
span: Span,
) -> TokenStream {
let (class, ty) = atomic;
let value = match want {
RmwValue::None => return TokenStream::new(),
RmwValue::Old => quote_spanned! {span=> #old },
RmwValue::New => self.atomic_combine(op, old, operand, span),
};
self.repr_to_value(class, ty, value, span)
.at(prec::LOWEST, span)
}
fn atomic_combine(
&self,
op: ir::AtomicRmw,
old: &Ident,
operand: &Ident,
span: Span,
) -> TokenStream {
match op {
ir::AtomicRmw::Add => quote_spanned! {span=> #old.wrapping_add(#operand) },
ir::AtomicRmw::Sub => quote_spanned! {span=> #old.wrapping_sub(#operand) },
ir::AtomicRmw::And => quote_spanned! {span=> (#old & #operand) },
ir::AtomicRmw::Or => quote_spanned! {span=> (#old | #operand) },
ir::AtomicRmw::Xor => quote_spanned! {span=> (#old ^ #operand) },
ir::AtomicRmw::Nand => quote_spanned! {span=> !(#old & #operand) },
}
}
fn unsigned_operand(&mut self, arg: &Expr, span: Span) -> TokenStream {
let width = arg.ty.bits(&self.options.target);
let target = unsigned_rust_ty(width, span);
let value = self.expr(arg).at(prec::CAST, span);
parenthesize(quote_spanned! {span=> #value as #target }, span)
}
fn overflow_builtin(
&mut self,
op: BinOp,
args: &[Expr],
store: bool,
result_ty: Ty,
span: Span,
) -> Value {
let method = match op {
BinOp::Add => "wrapping_add",
BinOp::Sub => "wrapping_sub",
_ => "wrapping_mul",
};
let method = Ident::new(method, span);
let checked = match op {
BinOp::Add => "checked_add",
BinOp::Sub => "checked_sub",
_ => "checked_mul",
};
let checked = Ident::new(checked, span);
let lhs = self.expr(&args[0]).at(prec::CAST, span);
let rhs = self.expr(&args[1]).at(prec::CAST, span);
let target = self.ty(result_ty, span);
let a = self.temporary();
let b = self.temporary();
let wide = self.temporary();
let narrow = self.temporary();
let i128 = primitive_ty("i128", span);
let compute = quote_spanned! {span=>
let #a: #i128 = #lhs as #i128;
let #b: #i128 = #rhs as #i128;
let #wide: #i128 = #a.#method(#b);
let #narrow: #target = #wide as #target;
};
let fits = match result_ty {
Ty::Int128 => quote_spanned! {span=> false },
Ty::UInt128 => quote_spanned! {span=> #wide < 0 },
_ => quote_spanned! {span=> (#narrow as #i128) != #wide },
};
let flag = quote_spanned! {span=>
#a.#checked(#b).is_none() || #fits
};
if !store {
let third = self.expr_stmt(&args[2]);
return Value::new(
quote_spanned! {span=> { #third #compute #flag } },
prec::BLOCK,
);
}
let place = self.expr(&args[2]).at(prec::CALL, span);
let out = self.temporary();
Value::new(
quote_spanned! {span=>
{ #compute let #out = #place; *#out = #narrow; #flag }
},
prec::BLOCK,
)
}
fn va_arg(
&mut self,
ap: &Place,
record: Option<&[ir::Eightbyte]>,
ty: Ty,
span: Span,
) -> Value {
let access = self.place(ap, true).access;
if let Some(classes) = record {
let target = self.ty(ty, span);
let u64_ty = primitive_ty("u64", span);
let f64_ty = primitive_ty("f64", span);
let words = classes.iter().map(|class| match class {
ir::Eightbyte::Int => quote_spanned! {span=> #access.next_arg::<#u64_ty>() },
ir::Eightbyte::Sse => {
quote_spanned! {span=> #access.next_arg::<#f64_ty>().to_bits() }
}
ir::Eightbyte::None => quote_spanned! {span=> 0 },
});
let count = Literal::usize_unsuffixed(classes.len());
let value = self.temporary();
return Value::new(
quote_spanned! {span=>
{
let #value: [#u64_ty; #count] = [#(#words),*];
::core::ptr::read_unaligned(#value.as_ptr().cast::<#target>())
}
},
prec::BLOCK,
);
}
if self.program.types.is_func_pointer(ty) {
let target = self.ty(ty, span);
let void = self.pointee_ty(Ty::Void, span);
return Value::new(
quote_spanned! {span=>
::core::mem::transmute::<*mut #void, #target>(
#access.next_arg::<*mut #void>()
)
},
prec::CALL,
);
}
let target = self.ty(ty, span);
Value::new(
quote_spanned! {span=> #access.next_arg::<#target>() },
prec::CALL,
)
}
fn vm_scale(&self, pointee: Ty, span: Span) -> Option<TokenStream> {
if !self.program.types.is_vm(pointee) {
return None;
}
let isize_ty = primitive_ty("isize", span);
let mut product: Option<TokenStream> = None;
for dim in self.program.types.vm_dims(pointee).iter().rev() {
let factor = match dim {
ir::VmDim::Fixed(len) => {
let literal = Literal::isize_unsuffixed(*len as isize);
quote_spanned! {span=> #literal }
}
ir::VmDim::Len(id) => {
let name = self.object_ident(*id, span);
quote_spanned! {span=> #name as #isize_ty }
}
ir::VmDim::Unknown => quote_spanned! {span=> 1 },
};
product = Some(match product {
None => factor,
Some(left) => quote_spanned! {span=> (#left).wrapping_mul(#factor) },
});
}
product
}
fn scaled_offset(&mut self, pointee: Ty, index: &Expr, sub: bool, span: Span) -> TokenStream {
let Some(scale) = self.vm_scale(pointee, span) else {
return self.offset_argument(index, sub, span);
};
let isize_ty = primitive_ty("isize", span);
let offset = match &index.kind {
ExprKind::Int(value) => {
let value = if sub { -*value } else { *value };
let literal = int_literal_token(value, span);
quote_spanned! {span=> (#literal as #isize_ty) }
}
_ => {
let inner = self.offset_argument(index, sub, span);
quote_spanned! {span=> (#inner) }
}
};
quote_spanned! {span=> #offset.wrapping_mul(#scale) }
}
fn offset_argument(&mut self, index: &Expr, sub: bool, span: Span) -> TokenStream {
if let ExprKind::Int(value) = &index.kind {
let value = if sub { value.wrapping_neg() } else { *value };
let literal = int_literal_token(value, span);
if value.unsigned_abs() > UNSUFFIXED_LIMIT as u128 {
let isize_ty = primitive_ty("isize", span);
return quote_spanned! {span=> (#literal as #isize_ty) };
}
return literal;
}
let tokens = self.expr(index).at(prec::CAST, span);
let isize_ty = primitive_ty("isize", span);
if sub {
quote_spanned! {span=> -(#tokens as #isize_ty) }
} else {
quote_spanned! {span=> #tokens as #isize_ty }
}
}
fn call(&mut self, callee: &Callee, args: &[Expr], span: Span) -> Value {
if let Callee::Direct(id) = callee
&& let Some(intr) = self.program.function(*id).intrinsic
{
return self.intrinsic_call(intr, *id, args, span);
}
let sig = self.callee_signature(callee);
let reinterpreted = !sig.variadic
&& (args.len() != sig.params.len()
|| args.iter().zip(&sig.params).any(|(arg, param)| {
arg.ty != *param && !arg.ty.is_error() && !param.is_error()
}));
let promoted: Vec<Ty> = if reinterpreted {
args.iter().map(|arg| arg.ty).collect()
} else {
Vec::new()
};
let params = if reinterpreted {
&promoted
} else {
&sig.params
};
let mut target = match callee {
Callee::Direct(id) => {
let function = self.program.function(*id);
let path = self.function_path(function, span);
if reinterpreted {
let source = self.function_pointer_ty(function, span);
parenthesize(quote_spanned! {span=> #path as #source }, span)
} else {
path
}
}
Callee::Indirect(expr) => {
let value = self.expr(expr).at(prec::CALL, span);
parenthesize(
quote_spanned! {span=> #value.expect("null function pointer") },
span,
)
}
};
if reinterpreted {
let source = self.fn_ptr_ty(&sig.params, sig.variadic, sig.ret, span);
let wanted = self.fn_ptr_ty(params, false, sig.ret, span);
target = parenthesize(
quote_spanned! {span=>
::core::mem::transmute::<#source, #wanted>(#target)
},
span,
);
}
let mut tokens = self.env_arguments(callee, span);
let hidden = !tokens.is_empty();
for (index, arg) in args.iter().enumerate() {
if index > 0 || hidden {
tokens.extend(quote_spanned! {span=> , });
}
match params.get(index) {
Some(expected) => tokens.extend(self.expr_at(arg, *expected)),
None => {
let arg_span = self.sp(arg.range);
tokens.extend(self.expr(arg).at(prec::LOWEST, arg_span));
}
}
}
let call = parenthesize(tokens, span);
Value::new(quote_spanned! {span=> #target #call }, prec::CALL)
}
fn intrinsic_call(
&mut self,
intr: &'static crate::x86::Intrinsic,
id: ir::FuncId,
args: &[Expr],
span: Span,
) -> Value {
let module = self.arch_module(span);
let name = Ident::new(intr.name, span);
let mut generics = TokenStream::new();
for (index, imm) in intr.imm.iter().enumerate() {
if index > 0 {
generics.extend(quote_spanned! {span=> , });
}
let value = match args.get(usize::from(imm.index)).map(|arg| &arg.kind) {
Some(ExprKind::Int(value)) => *value,
_ => 0,
};
let literal = suffixed_int_literal(value, imm.rust_ty, span);
generics.extend(quote_spanned! {span=> { #literal } });
}
let turbofish = if generics.is_empty() {
TokenStream::new()
} else {
quote_spanned! {span=> ::<#generics> }
};
let mut tokens = TokenStream::new();
let mut written = 0usize;
for (index, arg) in args.iter().enumerate() {
if intr.immediate_at(index).is_some() {
continue;
}
if written > 0 {
tokens.extend(quote_spanned! {span=> , });
}
written += 1;
let param = self.program.function(id).sig.params.get(index).copied();
match param.and_then(|ty| self.intrinsic_pointer_arg(ty, span)) {
Some(cast) => {
let value = self.expr(arg).at(prec::CAST, span);
tokens.extend(quote_spanned! {span=> #value #cast });
}
None => tokens.extend(self.expr_at(arg, arg.ty)),
}
}
let call = parenthesize(tokens, span);
Value::new(
quote_spanned! {span=> ::core::arch::#module::#name #turbofish #call },
prec::CALL,
)
}
fn intrinsic_pointer_arg(&self, param: Ty, span: Span) -> Option<TokenStream> {
let Ty::Pointer(pointer) = param else {
return None;
};
Some(if self.program.types.pointer_type(pointer).konst {
quote_spanned! {span=> as *const _ }
} else {
quote_spanned! {span=> as *mut _ }
})
}
fn env_arguments(&self, callee: &Callee, span: Span) -> TokenStream {
let Callee::Direct(id) = callee else {
return TokenStream::new();
};
let mut tokens = TokenStream::new();
for (index, entry) in self.program.function(*id).env.iter().enumerate() {
if index > 0 {
tokens.extend(quote_spanned! {span=> , });
}
match self.env.get(&entry.owner) {
Some(param) => {
let name = self.object_ident(*param, span);
tokens.extend(quote_spanned! {span=> #name });
}
None => {
let object = self.program.object(entry.owner);
let name = self.object_access(entry.owner, span);
tokens.extend(if object.is_const {
quote_spanned! {span=> &raw const #name }
} else {
quote_spanned! {span=> &raw mut #name }
});
}
}
}
tokens
}
fn callee_signature(&self, callee: &Callee) -> ir::Signature {
match callee {
Callee::Direct(id) => self.program.function(*id).sig.clone(),
Callee::Indirect(expr) => match self.program.types.pointee(expr.ty) {
Some(Ty::Func(id)) => {
let func = self.program.types.func_type(id);
ir::Signature {
ret: func.ret,
params: func.params.clone(),
variadic: func.variadic,
prototyped: func.prototyped,
}
}
_ => ir::Signature {
ret: Ty::Void,
params: Vec::new(),
variadic: false,
prototyped: true,
},
},
}
}
fn fn_ptr_ty(&self, params: &[Ty], variadic: bool, ret: Ty, span: Span) -> TokenStream {
let mut list = TokenStream::new();
for (index, param) in params.iter().enumerate() {
if index > 0 {
list.extend(quote_spanned! {span=> , });
}
list.extend(self.ty(*param, span));
}
if variadic {
if !params.is_empty() {
list.extend(quote_spanned! {span=> , });
}
list.extend(quote_spanned! {span=> ... });
}
let list = parenthesize(list, span);
let ret = if ret.is_void() {
TokenStream::new()
} else {
let ty = self.ty(ret, span);
quote_spanned! {span=> -> #ty }
};
quote_spanned! {span=> unsafe extern "C" fn #list #ret }
}
fn function_path(&self, function: &Function, span: Span) -> TokenStream {
let name = self.c_ident(function.item_name(), span);
quote_spanned! {span=> #name }
}
fn function_pointer_ty(&self, function: &Function, span: Span) -> TokenStream {
let sig = &function.sig;
self.fn_ptr_ty(&sig.params, sig.variadic, sig.ret, span)
}
fn condition(&mut self, expr: &Expr) -> Value {
let span = self.sp(expr.range);
match &expr.kind {
ExprKind::Compare { op, lhs, rhs } => {
if let Some(value) = self.null_test(*op, lhs, rhs, span) {
return value;
}
if lhs.ty.is_complex() && rhs.ty.is_complex() {
return self.complex_equality(*op, lhs, rhs, span);
}
let (lhs, rhs) = self.operands(lhs, rhs, BinOp::BitOr);
let left_min = if *op == CmpOp::Lt && lhs.ends_with_type {
prec::CAST + 1
} else {
prec::CMP + 1
};
let ends_with_type = rhs.ends_with_type;
let lhs = lhs.at(left_min, span);
let rhs = rhs.at(prec::CMP + 1, span);
let op = cmp_tokens(*op, span);
Value::new(quote_spanned! {span=> #lhs #op #rhs }, prec::CMP)
.type_end(ends_with_type)
}
ExprKind::Logical { .. } => self.logical_chain(expr),
ExprKind::Int(value) => {
let ident = Ident::new(if *value != 0 { "true" } else { "false" }, span);
Value::atom(quote_spanned! {span=> #ident })
}
_ if expr.ty.is_bool() => self.expr(expr),
_ if expr.ty.is_complex() => self.complex_condition(expr, span),
_ if expr.ty.is_pointer() => self.not_null(expr, span),
_ => {
let ty = expr.ty;
let value = self.expr(expr).at(prec::CMP + 1, span);
let zero = self.zero_tokens(ty, span);
Value::new(quote_spanned! {span=> #value != #zero }, prec::CMP)
}
}
}
fn null_test(&mut self, op: CmpOp, lhs: &Expr, rhs: &Expr, span: Span) -> Option<Value> {
if !matches!(op, CmpOp::Eq | CmpOp::Ne) {
return None;
}
let (pointer, _) = match (&lhs.kind, &rhs.kind) {
(ExprKind::Zeroed, _) if rhs.ty.is_pointer() => (rhs, lhs),
(_, ExprKind::Zeroed) if lhs.ty.is_pointer() => (lhs, rhs),
_ => return None,
};
let test = self.is_null(pointer, span);
if op == CmpOp::Eq {
return Some(test);
}
let tokens = test.at(prec::UNARY, span);
Some(Value::new(quote_spanned! {span=> !#tokens }, prec::UNARY))
}
fn is_null(&mut self, expr: &Expr, span: Span) -> Value {
if self.program.types.is_func_pointer(expr.ty) {
let tokens = self.copied_receiver(expr, span);
return Value::new(quote_spanned! {span=> #tokens.is_none() }, prec::CALL);
}
let tokens = self.expr(expr).at(prec::CALL, span);
Value::new(quote_spanned! {span=> #tokens.is_null() }, prec::CALL)
}
fn not_null(&mut self, expr: &Expr, span: Span) -> Value {
if self.program.types.is_func_pointer(expr.ty) {
let tokens = self.copied_receiver(expr, span);
return Value::new(quote_spanned! {span=> #tokens.is_some() }, prec::CALL);
}
let tokens = self.expr(expr).at(prec::CALL, span);
Value::new(quote_spanned! {span=> !#tokens.is_null() }, prec::UNARY)
}
fn copied_receiver(&mut self, expr: &Expr, span: Span) -> TokenStream {
if self.reads_a_static(expr) {
let tokens = self.expr(expr).at(prec::LOWEST, span);
return braced(tokens, span);
}
self.expr(expr).at(prec::CALL, span)
}
fn reads_a_static(&self, expr: &Expr) -> bool {
let ExprKind::Load(place) = &expr.kind else {
return false;
};
let mut place = place;
loop {
match &place.kind {
PlaceKind::Object(id) => {
let storage = &self.program.object(*id).storage;
return !matches!(storage, Storage::Automatic) && !storage.is_thread_local();
}
PlaceKind::Field { base, .. } => place = base,
_ => return false,
}
}
}
fn operands(&mut self, lhs: &Expr, rhs: &Expr, op: BinOp) -> (Value, Value) {
self.operands_with(None, lhs, rhs, op)
}
fn operands_with(
&mut self,
folded: Option<Value>,
lhs: &Expr,
rhs: &Expr,
op: BinOp,
) -> (Value, Value) {
let uses_method = matches!(
op,
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Shl | BinOp::Shr
);
let lhs_constant = constant_of(lhs);
let rhs_constant = constant_of(rhs);
let lhs_bare = lhs_constant.is_some() && !uses_method && rhs_constant.is_none();
let lhs_value = match (folded, lhs_constant) {
(Some(value), _) => value,
(None, Some(value)) if lhs_bare => self.bare_value(value, lhs.ty, self.sp(lhs.range)),
(None, _) => self.expr(lhs),
};
let rhs_value = match rhs_constant {
Some(ConstValue::Int(value)) if op.is_shift() => self.bare_value(
ConstValue::Int(i128::from(value as u32)),
Ty::UInt,
self.sp(rhs.range),
),
Some(value) if op.is_shift() => self.bare_value(value, rhs.ty, self.sp(rhs.range)),
Some(value) if !lhs_bare => self.bare_value(value, rhs.ty, self.sp(rhs.range)),
_ => self.expr(rhs),
};
(lhs_value, rhs_value)
}
fn bare_value(&mut self, value: ConstValue, ty: Ty, span: Span) -> Value {
match value {
ConstValue::Int(v) => {
let tokens = bare_int_literal(v, ty, span);
let mut out = Value::new(tokens, if v < 0 { prec::UNARY } else { prec::ATOM });
out.bare_integer = v >= 0 && !ty.is_bool();
out
}
ConstValue::Float(v) => Value::new(
bare_float_literal(v, span),
if v.is_sign_negative() {
prec::UNARY
} else {
prec::ATOM
},
),
ConstValue::Complex(re, im) => {
let re = bare_float_literal(re, span);
let im = bare_float_literal(im, span);
self.complex_new(ty, re, im, span)
}
}
}
fn cond_chain(&mut self, expr: &Expr) -> Value {
let mut spine = Vec::new();
let mut node = expr;
while let ExprKind::Cond {
cond,
then_expr,
else_expr,
} = &node.kind
{
let span = self.sp(node.range);
let cond_tokens = self.condition(cond).at_condition(span);
let then_tokens = self.expr(then_expr).at(prec::LOWEST, span);
spine.push((cond_tokens, then_tokens, span));
node = else_expr;
}
let inner = spine
.last()
.map_or_else(|| self.sp(expr.range), |(_, _, span)| *span);
let mut tokens = self.expr(node).at(prec::LOWEST, inner);
while let Some((cond_tokens, then_tokens, span)) = spine.pop() {
tokens = quote_spanned! {span=>
if #cond_tokens { #then_tokens } else { #tokens }
};
}
Value::new(tokens, prec::BLOCK)
}
fn cond_chain_at(&mut self, expr: &Expr, expected: Ty) -> TokenStream {
let mut spine = Vec::new();
let mut node = expr;
while let ExprKind::Cond {
cond,
then_expr,
else_expr,
} = &node.kind
{
if node.ty != expected {
break;
}
let span = self.sp(node.range);
let cond_tokens = self.condition(cond).at_condition(span);
let then_tokens = self.expr_at(then_expr, expected);
spine.push((cond_tokens, then_tokens, span));
node = else_expr;
}
let mut tokens = self.expr_at(node, expected);
while let Some((cond_tokens, then_tokens, span)) = spine.pop() {
tokens = quote_spanned! {span=>
if #cond_tokens { #then_tokens } else { #tokens }
};
}
tokens
}
fn assign_chain(&mut self, expr: &Expr) -> Value {
let mut spine = Vec::new();
let mut node = expr;
while let ExprKind::Assign { place, value } = &node.kind {
let span = self.sp(node.range);
let lowered = self.place(place, true);
spine.push((lowered, self.program.types.unatomic(place.ty), span));
node = value;
}
let (_, innermost, _) = spine
.last()
.expect("assign_chain is only entered on an assignment");
let mut tokens = self.expr_at(node, *innermost);
while let Some((lowered, ty, span)) = spine.pop() {
if lowered.atomic.is_some() {
let tmp = self.temporary();
let target = self.ty(ty, span);
let store = self.write(&lowered, quote_spanned! {span=> #tmp }, span);
let setup = &lowered.setup;
tokens = quote_spanned! {span=>
{ #setup let #tmp: #target = #tokens; #store #tmp }
};
continue;
}
let store = self.write(&lowered, tokens, span);
let read = self.read(&lowered, span).at(prec::LOWEST, span);
let setup = &lowered.setup;
tokens = quote_spanned! {span=> { #setup #store #read } };
}
Value::new(tokens, prec::BLOCK)
}
fn binary_chain(&mut self, expr: &Expr) -> Value {
let mut spine = vec![expr];
let mut node = expr;
while let ExprKind::Binary { lhs, .. } = &node.kind {
node = lhs;
if matches!(node.kind, ExprKind::Binary { .. }) {
spine.push(node);
}
}
let mut folded = None;
while let Some(node) = spine.pop() {
let ExprKind::Binary { op, lhs, rhs } = &node.kind else {
unreachable!("the spine holds binary operations only");
};
let span = self.sp(node.range);
let (lhs_value, rhs_value) = self.operands_with(folded, lhs, rhs, *op);
let value = if node.ty.is_complex() {
self.complex_binary(*op, (lhs_value, lhs.ty), (rhs_value, rhs.ty), node.ty, span)
} else {
self.binary(*op, lhs_value, rhs_value, node.ty, span)
};
folded = Some(self.reduce_bits(value, node));
}
folded.expect("the chain has at least the node it started from")
}
fn logical_chain(&mut self, expr: &Expr) -> Value {
let mut spine = vec![expr];
let mut node = expr;
while let ExprKind::Logical { lhs, .. } = &node.kind {
node = lhs;
if matches!(node.kind, ExprKind::Logical { .. }) {
spine.push(node);
}
}
let mut folded = self.condition(node);
while let Some(node) = spine.pop() {
let ExprKind::Logical { op, rhs, .. } = &node.kind else {
unreachable!("the spine holds logical operations only");
};
let span = self.sp(node.range);
let (level, tokens) = match op {
LogicalOp::And => (prec::AND, quote_spanned! {span=> && }),
LogicalOp::Or => (prec::OR, quote_spanned! {span=> || }),
};
let mut out = folded.at(level, span);
let rhs = self.condition(rhs);
let ends_with_type = rhs.ends_with_type;
let rhs = rhs.at(level + 1, span);
out.extend(quote_spanned! {span=> #tokens #rhs });
folded = Value::new(out, level).type_end(ends_with_type);
}
folded
}
fn binary(&mut self, op: BinOp, lhs: Value, rhs: Value, ty: Ty, span: Span) -> Value {
if ty.is_floating() {
let (level, tokens) = match op {
BinOp::Add => (prec::SUM, quote_spanned! {span=> + }),
BinOp::Sub => (prec::SUM, quote_spanned! {span=> - }),
BinOp::Mul => (prec::PRODUCT, quote_spanned! {span=> * }),
BinOp::Div => (prec::PRODUCT, quote_spanned! {span=> / }),
_ => (prec::PRODUCT, quote_spanned! {span=> % }),
};
let ends_with_type = rhs.ends_with_type;
let mut out = lhs.at(level, span);
let rhs = rhs.at(level + 1, span);
out.extend(quote_spanned! {span=> #tokens #rhs });
return Value::new(out, level).type_end(ends_with_type);
}
let method = match op {
BinOp::Add => Some("wrapping_add"),
BinOp::Sub => Some("wrapping_sub"),
BinOp::Mul => Some("wrapping_mul"),
BinOp::Shl => Some("wrapping_shl"),
BinOp::Shr => Some("wrapping_shr"),
_ => None,
};
if let Some(method) = method {
let mut receiver = lhs.at(prec::CALL, span);
let method = Ident::new(method, span);
let argument = if op.is_shift() && !rhs.bare_integer {
let amount = rhs.at(prec::CAST, span);
let amount = if starts_with_minus(&amount) {
parenthesize(amount, span)
} else {
amount
};
let u32_ty = primitive_ty("u32", span);
quote_spanned! {span=> #amount as #u32_ty }
} else {
rhs.at(prec::LOWEST, span)
};
let args = parenthesize(argument, span);
receiver.extend(quote_spanned! {span=> .#method #args });
return Value::new(receiver, prec::CALL);
}
let (level, tokens) = match op {
BinOp::Div => (prec::PRODUCT, quote_spanned! {span=> / }),
BinOp::Rem => (prec::PRODUCT, quote_spanned! {span=> % }),
BinOp::BitAnd => (prec::BIT_AND, quote_spanned! {span=> & }),
BinOp::BitXor => (prec::BIT_XOR, quote_spanned! {span=> ^ }),
BinOp::BitOr => (prec::BIT_OR, quote_spanned! {span=> | }),
_ => unreachable!("every other operator was handled above"),
};
let ends_with_type = rhs.ends_with_type;
let mut out = lhs.at(level, span);
let rhs = rhs.at(level + 1, span);
out.extend(quote_spanned! {span=> #tokens #rhs });
Value::new(out, level).type_end(ends_with_type)
}
fn compound_value(
&mut self,
current: Value,
place_ty: Ty,
op: BinOp,
value: &Expr,
hoisted: Option<Value>,
compute: Ty,
) -> Value {
let span = self.sp(value.range);
if place_ty.is_pointer() {
let access = current.at(prec::CALL, span);
let offset = match hoisted {
Some(index) => self.offset_of_value(index, op == BinOp::Sub, span),
None => self.offset_argument(value, op == BinOp::Sub, span),
};
let pointee = self.program.types.pointee(place_ty).unwrap_or(Ty::Void);
let offset = match self.vm_scale(pointee, span) {
None => offset,
Some(scale) => quote_spanned! {span=> (#offset).wrapping_mul(#scale) },
};
return Value::new(quote_spanned! {span=> #access.offset(#offset) }, prec::CALL);
}
if compute.is_complex() {
let lhs_ty = if place_ty.is_complex() {
compute
} else {
compute.complex_component()
};
let current = self.cast(current, place_ty, lhs_ty, span);
let rhs = self.compound_operand(value, hoisted, span);
let result = self.complex_binary(op, (current, lhs_ty), (rhs, value.ty), compute, span);
return self.cast(result, compute, place_ty, span);
}
let current = self.cast(current, place_ty, compute, span);
let rhs = self.compound_operand(value, hoisted, span);
let result = self.binary(op, current, rhs, compute, span);
self.cast(result, compute, place_ty, span)
}
fn compound_operand(&mut self, value: &Expr, hoisted: Option<Value>, span: Span) -> Value {
match hoisted {
Some(rhs) => rhs,
None => match constant_of(value) {
Some(constant) => self.bare_value(constant, value.ty, span),
None => self.expr(value),
},
}
}
fn compound_rhs(&mut self, value: &Expr) -> (TokenStream, Option<Value>) {
if !ir::calls_a_function(value) {
return (TokenStream::new(), None);
}
let span = self.sp(value.range);
let tokens = self.expr(value).at(prec::LOWEST, span);
let tmp = self.temporary();
(
quote_spanned! {span=> let #tmp = #tokens; },
Some(Value::atom(quote_spanned! {span=> #tmp })),
)
}
fn offset_of_value(&mut self, index: Value, sub: bool, span: Span) -> TokenStream {
let tokens = index.at(prec::CAST, span);
let isize_ty = primitive_ty("isize", span);
if sub {
quote_spanned! {span=> -(#tokens as #isize_ty) }
} else {
quote_spanned! {span=> #tokens as #isize_ty }
}
}
fn step_value(&mut self, current: Value, ty: Ty, dec: bool, span: Span) -> TokenStream {
if ty.is_pointer() {
let access = current.at(prec::CALL, span);
let pointee = self.program.types.pointee(ty).unwrap_or(Ty::Void);
let one = match (self.vm_scale(pointee, span), dec) {
(None, true) => quote_spanned! {span=> -1 },
(None, false) => quote_spanned! {span=> 1 },
(Some(scale), true) => quote_spanned! {span=> -(#scale) },
(Some(scale), false) => scale,
};
return quote_spanned! {span=> #access.offset(#one) };
}
if ty.is_floating() {
let access = current.at(prec::SUM, span);
let one = Literal::f64_unsuffixed(1.0);
let op = if dec {
quote_spanned! {span=> - }
} else {
quote_spanned! {span=> + }
};
return quote_spanned! {span=> #access #op #one };
}
if ty.is_complex() {
let name = if dec { "sub_real" } else { "add_real" };
let func = self.rt_complex(&format!("{name}_{}", Self::complex_suffix(ty)), span);
let access = current.at(prec::LOWEST, span);
let one = Literal::f64_unsuffixed(1.0);
return quote_spanned! {span=> #func(#access, #one) };
}
if ty.is_bool() {
let access = current.at(prec::CAST, span);
let int = self.ty(Ty::Int, span);
let method = Ident::new(if dec { "wrapping_sub" } else { "wrapping_add" }, span);
return quote_spanned! {span=> (#access as #int).#method(1) != 0 };
}
let access = current.at(prec::CALL, span);
let method = Ident::new(if dec { "wrapping_sub" } else { "wrapping_add" }, span);
quote_spanned! {span=> #access.#method(1) }
}
fn cast(&mut self, value: Value, from: Ty, to: Ty, span: Span) -> Value {
if from == to {
return value;
}
if from.is_complex() || to.is_complex() {
return self.complex_cast(value, from, to, span);
}
let types = &self.program.types;
let from_fn = types.is_func_pointer(from);
let to_fn = types.is_func_pointer(to);
if to.is_bool() {
if from.is_pointer() {
let tokens = value.at(prec::CALL, span);
return if from_fn {
let braced = braced(tokens, span);
Value::new(quote_spanned! {span=> #braced.is_some() }, prec::CALL)
} else {
Value::new(quote_spanned! {span=> !#tokens.is_null() }, prec::UNARY)
};
}
let zero = self.zero_tokens(from, span);
let tokens = value.at(prec::CMP + 1, span);
return Value::new(quote_spanned! {span=> #tokens != #zero }, prec::CMP);
}
if from_fn || to_fn {
let target = self.ty(to, span);
let source = self.ty(from, span);
if from_fn && to_fn && source.to_string() == target.to_string() {
return value;
}
let usize_ty = primitive_ty("usize", span);
if to_fn && !from.is_pointer() {
let tokens = value.at(prec::CAST, span);
return Value::new(
quote_spanned! {span=>
::core::mem::transmute::<#usize_ty, #target>(#tokens as #usize_ty)
},
prec::CALL,
);
}
if from_fn && !to.is_pointer() {
let tokens = value.at(prec::LOWEST, span);
return Value::new(
quote_spanned! {span=>
::core::mem::transmute::<#source, #usize_ty>(#tokens) as #target
},
prec::CAST,
)
.type_end(true);
}
let tokens = value.at(prec::LOWEST, span);
return Value::new(
quote_spanned! {span=>
::core::mem::transmute::<#source, #target>(#tokens)
},
prec::CALL,
);
}
let target = self.ty(to, span);
if from.is_pointer() && to.is_integer() {
let tokens = value.at(prec::CAST, span);
let usize_ty = primitive_ty("usize", span);
return Value::new(
quote_spanned! {span=> #tokens as #usize_ty as #target },
prec::CAST,
)
.type_end(true);
}
if from.is_bool() && to.is_floating() {
let int = self.ty(Ty::Int, span);
let tokens = value.at(prec::CAST, span);
return Value::new(
quote_spanned! {span=> #tokens as #int as #target },
prec::CAST,
)
.type_end(true);
}
let tokens = value.at(prec::CAST, span);
Value::new(quote_spanned! {span=> #tokens as #target }, prec::CAST).type_end(true)
}
fn place(&mut self, place: &Place, mutable: bool) -> LoweredPlace {
let atomic = matches!(place.ty, Ty::Atomic(_));
let mut lowered = self.place_access(place, mutable || atomic);
if lowered.bits.is_none() && self.place_underaligned(place) {
lowered.unaligned = true;
}
if let Ty::Atomic(id) = place.ty {
let inner = self.program.types.atomic_inner(id);
lowered.atomic = ir::atomic_class(&self.program.types, inner, &self.options.target)
.map(|c| (c, inner));
if lowered.atomic.is_some() {
lowered.unaligned = false;
}
}
lowered
}
fn place_underaligned(&self, place: &Place) -> bool {
match &place.kind {
PlaceKind::Deref(_) | PlaceKind::Index { .. } => {
self.place_align(place) < self.type_align(place.ty)
}
PlaceKind::Field { base, .. } => self.place_align(base) < self.type_align(base.ty),
PlaceKind::ComplexPart { .. } => self.place_align(place) < self.type_align(place.ty),
_ => false,
}
}
fn place_align(&self, place: &Place) -> u64 {
match &place.kind {
PlaceKind::Deref(ptr) => self.pointer_align(ptr),
PlaceKind::Index { base, .. } => {
self.pointer_align(base).min(self.type_align(place.ty))
}
PlaceKind::Field {
base,
record,
index,
} => {
let base_align = self.place_align(base);
let offset = self.program.types.record(*record).fields[*index].offset;
if offset == 0 {
base_align
} else {
base_align.min(1 << offset.trailing_zeros())
}
}
PlaceKind::ComplexPart { base, imag } => {
let base_align = self.place_align(base);
if *imag {
base_align.min(place.ty.size_bytes(&self.options.target).max(1))
} else {
base_align
}
}
PlaceKind::Object(id) => self
.object_align(*id)
.unwrap_or(1)
.max(self.type_align(place.ty)),
_ => self.type_align(place.ty),
}
}
fn pointer_align(&self, ptr: &Expr) -> u64 {
match &ptr.kind {
ExprKind::AddrOf(place) => self.place_align(place),
ExprKind::Cast(inner) if inner.ty.is_pointer() || inner.ty.is_array() => {
let from = self.pointer_align(inner);
match &ptr.ty {
Ty::Pointer(id) => match self.program.types.pointer_type(*id).align {
Some(align) => from.min(align),
None => from,
},
_ => from,
}
}
ExprKind::PtrOffset { ptr, .. } => self
.pointer_align(ptr)
.min(self.pointee_align(ptr.ty).unwrap_or(1)),
_ => self.pointee_align(ptr.ty).unwrap_or(u64::MAX),
}
}
fn pointee_align(&self, ty: Ty) -> Option<u64> {
if let Ty::Pointer(id) = ty
&& let Some(align) = self.program.types.pointer_type(id).align
{
return Some(align);
}
let pointee = self.program.types.pointee(ty)?;
(!pointee.is_void() && !pointee.is_func()).then(|| self.type_align(pointee))
}
fn type_align(&self, ty: Ty) -> u64 {
match ty {
Ty::Record(id) => self.program.types.record(id).rust_align,
Ty::Array(id) => self.type_align(self.program.types.array_type(id).elem),
_ => self
.program
.types
.size_align(ty, &self.options.target)
.map_or(1, |layout| layout.align),
}
}
fn place_access(&mut self, place: &Place, mutable: bool) -> LoweredPlace {
let span = self.sp(place.range);
match &place.kind {
PlaceKind::Object(id) if self.program.object(*id).storage.is_thread_local() => {
let name = self.object_ident(*id, span);
let tmp = self.temporary_at(span);
let cell = Ident::new("__cinrs_cell", Span::mixed_site());
let object = parenthesize(quote_spanned! {span=> *#tmp }, span);
let object = self.through_storage(*id, object, span);
LoweredPlace::plain(
quote_spanned! {span=>
let #tmp = #name.with(|#cell| ::core::cell::UnsafeCell::get(#cell));
},
object,
)
}
PlaceKind::Object(id) => {
let access = self.object_access(*id, span);
LoweredPlace::plain(TokenStream::new(), access)
}
PlaceKind::Deref(ptr) => self.deref_place(ptr, place.ty, mutable, span),
PlaceKind::Index { base, index } => {
let pointer = self.pointer_operand(base, place.ty, mutable, span);
let offset = self.scaled_offset(place.ty, index, false, span);
let tmp = self.temporary_at(span);
LoweredPlace::plain(
quote_spanned! {span=> let #tmp = #pointer.offset(#offset); },
parenthesize(quote_spanned! {span=> *#tmp }, span),
)
}
PlaceKind::Field {
base,
record,
index,
} => {
let field = self.program.types.record(*record).fields[*index].clone();
let lowered = self.place(base, mutable);
let access = lowered.access;
let Some(bits) = &field.bits else {
let name = self.c_ident(&field.name, span);
return LoweredPlace::plain(
lowered.setup,
quote_spanned! {span=> #access.#name },
);
};
let access = if rooted_in_static(base, self.program) {
parenthesize(quote_spanned! {span=> *(&raw mut #access) }, span)
} else {
access
};
LoweredPlace {
setup: lowered.setup,
access,
bits: Some(BitAccess {
getter: self.c_ident(&bits.getter, span),
setter: self.c_ident(&bits.setter, span),
}),
unaligned: false,
atomic: None,
}
}
PlaceKind::ComplexPart { base, imag } => {
let lowered = self.place(base, mutable);
let access = lowered.access;
let field = Ident::new(if *imag { "im" } else { "re" }, span);
LoweredPlace::plain(lowered.setup, quote_spanned! {span=> #access.#field })
}
PlaceKind::Str(id) => {
let pointer = self.string_pointer(*id, !mutable, span);
let tmp = self.temporary_at(span);
LoweredPlace::plain(
quote_spanned! {span=> let #tmp = #pointer; },
parenthesize(quote_spanned! {span=> *#tmp }, span),
)
}
PlaceKind::Temporary(expr) => {
let ty = expr.ty;
let value = self.expr_at(expr, ty);
let tmp = self.temporary();
LoweredPlace::plain(
quote_spanned! {span=> let mut #tmp = #value; },
quote_spanned! {span=> #tmp },
)
}
PlaceKind::CompoundLiteral { object, init } => {
let name = self.object_ident(*object, span);
let value = self.expr_at(init, place.ty);
LoweredPlace::plain(
quote_spanned! {span=> #name = #value; },
quote_spanned! {span=> #name },
)
}
}
}
fn read(&self, place: &LoweredPlace, span: Span) -> Value {
let access = &place.access;
if let Some((class, ty)) = place.atomic {
let object = self.atomic_object_of(place, span);
let order = self.ordering(ir::MemOrder::SeqCst, span);
return self.repr_to_value(
class,
ty,
quote_spanned! {span=> #object.load(#order) },
span,
);
}
match &place.bits {
Some(bits) => {
let getter = &bits.getter;
Value::new(quote_spanned! {span=> #access.#getter() }, prec::CALL)
}
None if place.unaligned => Value::new(
quote_spanned! {span=> (&raw const #access).read_unaligned() },
prec::CALL,
),
None => Value::atom(access.clone()),
}
}
fn write(&self, place: &LoweredPlace, value: TokenStream, span: Span) -> TokenStream {
let access = &place.access;
if let Some((class, ty)) = place.atomic {
let object = self.atomic_object_of(place, span);
let order = self.ordering(ir::MemOrder::SeqCst, span);
let value = self.value_to_repr(class, ty, Value::new(value, prec::LOWEST), span);
return quote_spanned! {span=> #object.store(#value, #order); };
}
match &place.bits {
Some(bits) => {
let setter = &bits.setter;
quote_spanned! {span=> #access.#setter(#value); }
}
None if place.unaligned => {
quote_spanned! {span=> (&raw mut #access).write_unaligned(#value); }
}
None => quote_spanned! {span=> #access = #value; },
}
}
fn atomic_object_of(&self, place: &LoweredPlace, span: Span) -> TokenStream {
let access = &place.access;
let class = place.atomic.expect("an atomic place").0;
self.atomic_ref(class, quote_spanned! {span=> (&raw mut #access) }, span)
}
fn deref_place(&mut self, ptr: &Expr, pointee: Ty, mutable: bool, span: Span) -> LoweredPlace {
let simple =
matches!(&ptr.kind, ExprKind::Load(p) if matches!(p.kind, PlaceKind::Object(_)));
if simple {
let tokens = self.pointer_operand(ptr, pointee, mutable, span);
return LoweredPlace::plain(
TokenStream::new(),
parenthesize(quote_spanned! {span=> *#tokens }, span),
);
}
let value = self.pointer_operand(ptr, pointee, mutable, span);
let tmp = self.temporary_at(span);
LoweredPlace::plain(
quote_spanned! {span=> let #tmp = #value; },
parenthesize(quote_spanned! {span=> *#tmp }, span),
)
}
fn pointer_operand(
&mut self,
ptr: &Expr,
pointee: Ty,
mutable: bool,
span: Span,
) -> TokenStream {
if mutable && self.program.types.points_to_const(ptr.ty) {
let target = self.pointee_ty(pointee, span);
let tokens = self.expr(ptr).at(prec::CAST, span);
return parenthesize(quote_spanned! {span=> #tokens as *mut #target }, span);
}
self.expr(ptr).at(prec::CALL, span)
}
fn address_of(&mut self, place: &Place, want: Ty, span: Span) -> Value {
if let PlaceKind::Object(id) = &place.kind
&& self.program.types.is_vm(place.ty)
{
let name = self.object_ident(*id, span);
let value = Value::atom(quote_spanned! {span=> #name });
let elem = self.program.types.elem(place.ty);
let natural = self.program.types.pointee(want) == elem
&& !self.program.types.points_to_const(want);
if natural {
return value;
}
let target = self.ty(want, span);
let tokens = value.at(prec::CAST, span);
return Value::new(quote_spanned! {span=> #tokens as #target }, prec::CAST)
.type_end(true);
}
match &place.kind {
PlaceKind::Deref(ptr) => {
let from = ptr.ty;
let value = self.expr(ptr);
return self.pointer_cast(value, from, want, span);
}
PlaceKind::Index { base, index } => {
let from = base.ty;
let pointer = self.expr(base).at(prec::CALL, span);
let offset = self.scaled_offset(place.ty, index, false, span);
let value = Value::new(
quote_spanned! {span=> #pointer.offset(#offset) },
prec::CALL,
);
return self.pointer_cast(value, from, want, span);
}
PlaceKind::Str(id) => {
let konst = self.program.types.points_to_const(want);
let tokens = self.string_pointer(*id, konst, span);
return Value::new(tokens, prec::CALL);
}
_ => {}
}
let lowered = self.place(place, true);
let access = lowered.access;
let address = quote_spanned! {span=> &raw mut #access };
let natural = Value::new(parenthesize(address, span), prec::ATOM);
let value = self.array_or_pointer_cast(natural, place.ty, want, span);
if lowered.setup.is_empty() {
return value;
}
let setup = lowered.setup;
let tokens = value.at(prec::LOWEST, span);
Value::new(quote_spanned! {span=> { #setup #tokens } }, prec::BLOCK)
}
fn array_or_pointer_cast(&mut self, value: Value, from: Ty, want: Ty, span: Span) -> Value {
if let Ty::Array(id) = from {
let array = self.program.types.array_type(id);
let wanted_pointee = self.program.types.pointee(want);
if wanted_pointee == Some(array.elem) {
let elem = self.pointee_ty(array.elem, span);
let tokens = value.at(prec::CALL, span);
let cast = Value::new(quote_spanned! {span=> #tokens.cast::<#elem>() }, prec::CALL);
return self.constify(cast, want, span);
}
}
let natural_mut = matches!(self.program.types.pointee(want), Some(pointee) if pointee == from)
&& !self.program.types.points_to_const(want);
if natural_mut {
return value;
}
let target = self.ty(want, span);
let tokens = value.at(prec::CAST, span);
Value::new(quote_spanned! {span=> #tokens as #target }, prec::CAST).type_end(true)
}
fn constify(&mut self, value: Value, want: Ty, span: Span) -> Value {
if !self.program.types.points_to_const(want) {
return value;
}
let target = self.ty(want, span);
let tokens = value.at(prec::CAST, span);
Value::new(quote_spanned! {span=> #tokens as #target }, prec::CAST).type_end(true)
}
fn pointer_cast(&mut self, value: Value, from: Ty, want: Ty, span: Span) -> Value {
if from == want {
return value;
}
let target = self.ty(want, span);
let tokens = value.at(prec::CAST, span);
Value::new(quote_spanned! {span=> #tokens as #target }, prec::CAST).type_end(true)
}
fn string_pointer(&mut self, id: ir::StrId, konst: bool, span: Span) -> TokenStream {
let data = self.program.string(id);
let element = data.elem;
if element.size_bytes(&self.options.target) > 1 {
let elem = self.ty(element, span);
let mut items = TokenStream::new();
for value in data.values.iter().chain(std::iter::once(&0)) {
let literal =
int_literal_token(element.wrap(i128::from(*value), &self.options.target), span);
items.extend(quote_spanned! {span=> #literal, });
}
let len = usize_literal(data.len_with_nul(), span);
let name = Ident::new("__CINRS_WIDE", Span::mixed_site());
let array = bracketed(items, span);
let ty = bracketed(quote_spanned! {span=> #elem ; #len }, span);
let pointer = if konst {
quote_spanned! {span=> (&raw const #name).cast::<#elem>() }
} else {
quote_spanned! {span=> (&raw const #name).cast::<#elem>().cast_mut() }
};
return quote_spanned! {span=>
{ static #name: #ty = #array; #pointer }
};
}
let mut literal = Literal::byte_string(&nul_terminated(&data.values));
literal.set_span(span);
let elem = self.ty(element, span);
if konst {
quote_spanned! {span=> #literal.as_ptr().cast::<#elem>() }
} else {
quote_spanned! {span=> #literal.as_ptr().cast::<#elem>().cast_mut() }
}
}
fn int_literal(&self, value: i128, ty: Ty, span: Span) -> Value {
if ty.is_bool() {
return Value::atom(bare_int_literal(value, ty, span));
}
if ty == Ty::UInt128 {
let literal = u128_literal_token(value as u128, span);
let target = self.ty(ty, span);
return Value::new(quote_spanned! {span=> #literal as #target }, prec::CAST)
.type_end(true);
}
let literal = int_literal_token(value, span);
let target = self.ty(ty, span);
Value::new(quote_spanned! {span=> #literal as #target }, prec::CAST).type_end(true)
}
fn float_literal(&self, value: f64, ty: Ty, span: Span) -> Value {
if !value.is_finite() {
return Value::new(self.non_finite_literal(value, ty, span), prec::CAST).type_end(true);
}
let literal = float_literal_token(value, span);
let target = self.ty(ty, span);
Value::new(quote_spanned! {span=> #literal as #target }, prec::CAST).type_end(true)
}
fn non_finite_literal(&self, value: f64, ty: Ty, span: Span) -> TokenStream {
let target = self.ty(ty, span);
let f64_ty = primitive_ty("f64", span);
if value.is_nan() {
let bits = value.to_bits();
if bits == f64::NAN.to_bits() {
return quote_spanned! {span=> <#f64_ty>::NAN as #target };
}
if ty == Ty::Float {
let f32_ty = primitive_ty("f32", span);
let literal =
unsigned_hex_literal(u64::from(ir::narrow_nan_bits(bits)), "u32", span);
return quote_spanned! {span=> <#f32_ty>::from_bits(#literal) };
}
let literal = unsigned_hex_literal(bits, "u64", span);
return quote_spanned! {span=> <#f64_ty>::from_bits(#literal) as #target };
}
if value.is_sign_negative() {
quote_spanned! {span=> -<#f64_ty>::INFINITY as #target }
} else {
quote_spanned! {span=> <#f64_ty>::INFINITY as #target }
}
}
fn zero_tokens(&self, ty: Ty, span: Span) -> TokenStream {
match ty {
Ty::Atomic(id) => self.zero_tokens(self.program.types.atomic_inner(id), span),
_ if ty.is_complex() => {
let zero = bare_float_literal(0.0, span);
self.complex_new(ty, zero.clone(), zero, span)
.at(prec::LOWEST, span)
}
_ if ty.is_floating() => bare_float_literal(0.0, span),
_ if ty.is_integer() => bare_int_literal(0, ty, span),
Ty::Array(_) if self.program.types.is_vm(ty) => {
let step = self.ty(self.program.types.vm_step_ty(ty), span);
quote_spanned! {span=> ::core::ptr::null_mut::<#step>() }
}
Ty::Pointer(id) => {
let pointer = self.program.types.pointer_type(id);
if let Ty::Func(func) = pointer.pointee {
let signature = self.fn_ty(func, span);
return quote_spanned! {span=>
::core::option::Option::<#signature>::None
};
}
let pointee = self.pointee_ty(pointer.pointee, span);
if pointer.konst {
quote_spanned! {span=> ::core::ptr::null::<#pointee>() }
} else {
quote_spanned! {span=> ::core::ptr::null_mut::<#pointee>() }
}
}
other => {
let target = self.ty(other, span);
quote_spanned! {span=> ::core::mem::zeroed::<#target>() }
}
}
}
}
fn constant_bits(expr: &Expr) -> Option<i128> {
match &expr.kind {
ExprKind::Int(value) => Some(*value),
ExprKind::Zeroed if expr.ty.is_integer() => Some(0),
_ => None,
}
}
fn pack_bits(storage: &mut [u8], bits: &ir::BitField, value: i128) {
let start = bits.offset_in_storage();
for bit in 0..u64::from(bits.width) {
if (value as u128) >> bit & 1 == 0 {
continue;
}
let at = start + bit;
if let Some(byte) = storage.get_mut((at / 8) as usize) {
*byte |= 1 << (at % 8);
}
}
}
fn byte_array(bytes: &[u8], span: Span) -> TokenStream {
if bytes.iter().all(|byte| *byte == 0) {
let len = usize_literal(bytes.len() as u64, span);
return bracketed(quote_spanned! {span=> 0; #len }, span);
}
let mut items = TokenStream::new();
for byte in bytes {
let value = hex_literal(u64::from(*byte), span);
items.extend(quote_spanned! {span=> #value, });
}
bracketed(items, span)
}
fn rooted_in_static(place: &Place, program: &Program) -> bool {
let mut place = place;
loop {
match &place.kind {
PlaceKind::Object(id) => {
let storage = &program.object(*id).storage;
return !matches!(storage, Storage::Automatic) && !storage.is_thread_local();
}
PlaceKind::Field { base, .. } => place = base,
_ => return false,
}
}
}
fn unsigned_rust_ty(width: u32, span: Span) -> TokenStream {
primitive_ty(
match width {
0..=8 => "u8",
9..=16 => "u16",
17..=32 => "u32",
_ => "u64",
},
span,
)
}
fn signed_rust_ty(width: u32, span: Span) -> TokenStream {
primitive_ty(
match width {
0..=8 => "i8",
9..=16 => "i16",
17..=32 => "i32",
_ => "i64",
},
span,
)
}
fn mask_of(width: u32, word_bits: u32) -> u128 {
let width = width.min(word_bits);
if width >= 128 {
u128::MAX
} else {
(1u128 << width) - 1
}
}
fn word_literal(value: u128, word_bits: u32, span: Span) -> TokenStream {
if word_bits <= 64 {
return hex_literal(value as u64, span);
}
let mut literal = Literal::from_str(&format!("0x{value:x}u128"))
.unwrap_or_else(|_| Literal::u128_suffixed(value));
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn hex_literal(value: u64, span: Span) -> TokenStream {
let mut literal = Literal::from_str(&format!("0x{value:x}"))
.unwrap_or_else(|_| Literal::u64_unsuffixed(value));
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn entry_ident(state: u32) -> Ident {
Ident::new(&format!("__cinrs_entry{state}"), Span::mixed_site())
}
fn entry_assignment(exit: &reloop::Exit, index: usize, span: Span) -> TokenStream {
let Some(state) = exit.state else {
return TokenStream::new();
};
let name = entry_ident(state);
let value = state_literal(index, span);
quote_spanned! {span=> #name = #value; }
}
fn invalid_target(span: Span) -> TokenStream {
quote_spanned! {span=>
if ::core::cfg!(debug_assertions) {
::core::unreachable!()
} else {
unsafe { ::core::hint::unreachable_unchecked() }
}
}
}
fn state_literal(value: usize, span: Span) -> TokenStream {
let mut literal = Literal::u32_unsuffixed(value as u32);
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn group_cases(cases: &[(ir::CaseRange, BlockId)]) -> Vec<(BlockId, Vec<ir::CaseRange>)> {
let mut out: Vec<(BlockId, Vec<ir::CaseRange>)> = Vec::new();
for (value, target) in cases {
match out.iter_mut().find(|(block, _)| block == target) {
Some((_, values)) => values.push(*value),
None => out.push((*target, vec![*value])),
}
}
out
}
fn case_pattern(value: ir::CaseRange, ty: Ty, span: Span) -> TokenStream {
let low = bare_int_literal(value.low, ty, span);
if value.is_single() {
return low;
}
let high = bare_int_literal(value.high, ty, span);
quote_spanned! {span=> #low ..= #high }
}
fn zero_prec(ty: Ty) -> u8 {
if ty.is_arithmetic() {
prec::ATOM
} else {
prec::CALL
}
}
fn link_name(symbol: &str, span: Span) -> TokenStream {
let mut literal = Literal::string(symbol);
literal.set_span(span);
quote_spanned! {span=> #[link_name = #literal] }
}
fn export_attr(symbol: &str, item: &Ident, span: Span) -> TokenStream {
if item.to_string().trim_start_matches("r#") == symbol {
return quote_spanned! {span=> #[unsafe(no_mangle)] };
}
let mut literal = Literal::string(symbol);
literal.set_span(span);
quote_spanned! {span=> #[unsafe(export_name = #literal)] }
}
fn needs_unsafe(types: &ir::Types, expr: &Expr) -> bool {
let recurse = |inner| needs_unsafe(types, inner);
match &expr.kind {
ExprKind::Zeroed => !expr.ty.is_scalar(),
ExprKind::AddrOf(place) => !matches!(place.kind, PlaceKind::Str(_)),
ExprKind::Cast(inner) => {
let transmuted = types.is_func_pointer(expr.ty) || types.is_func_pointer(inner.ty);
transmuted || recurse(inner)
}
ExprKind::PtrOffset { .. } => true,
ExprKind::ComplexOf { re, im } => recurse(re) || recurse(im),
ExprKind::RecordLit { fields, .. } => fields.iter().any(recurse),
ExprKind::UnionLit { value, .. } => recurse(value),
ExprKind::ArrayLit(items) => items.iter().any(recurse),
ExprKind::ArrayRepeat { value, .. } => recurse(value),
_ => false,
}
}
fn nul_terminated(values: &[u32]) -> Vec<u8> {
let mut bytes: Vec<u8> = values.iter().map(|v| *v as u8).collect();
bytes.push(0);
bytes
}
const UNSUFFIXED_LIMIT: i128 = i32::MAX as i128;
fn u128_literal_token(value: u128, span: Span) -> TokenStream {
let mut literal = Literal::u128_suffixed(value);
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn int_literal_token(value: i128, span: Span) -> TokenStream {
if value == i128::MIN {
let mut literal = Literal::from_str("170141183460469231731687303715884105728i128")
.expect("a decimal literal followed by a suffix is a token");
literal.set_span(span);
return quote_spanned! {span=> -#literal };
}
let magnitude = value.unsigned_abs();
let mut literal = if magnitude <= UNSUFFIXED_LIMIT as u128 {
Literal::u128_unsuffixed(magnitude)
} else if value >= 0 {
if magnitude <= u32::MAX as u128 {
Literal::u32_suffixed(magnitude as u32)
} else if magnitude <= u64::MAX as u128 {
Literal::u64_suffixed(magnitude as u64)
} else {
Literal::u128_suffixed(magnitude)
}
} else if magnitude <= i64::MAX as u128 {
Literal::i64_suffixed(magnitude as i64)
} else {
Literal::i128_suffixed(magnitude as i128)
};
literal.set_span(span);
if value < 0 {
quote_spanned! {span=> -#literal }
} else {
TokenStream::from(TokenTree::Literal(literal))
}
}
fn bare_int_literal(value: i128, ty: Ty, span: Span) -> TokenStream {
if ty.is_bool() {
let ident = Ident::new(if value != 0 { "true" } else { "false" }, span);
return quote_spanned! {span=> #ident };
}
if ty == Ty::UInt128 {
let mut literal = Literal::u128_unsuffixed(value as u128);
literal.set_span(span);
return TokenStream::from(TokenTree::Literal(literal));
}
let mut literal = Literal::u128_unsuffixed(value.unsigned_abs());
literal.set_span(span);
if value < 0 {
quote_spanned! {span=> -#literal }
} else {
TokenStream::from(TokenTree::Literal(literal))
}
}
fn window_ty(word_bits: u32, signed: bool, span: Span) -> TokenStream {
match (word_bits, signed) {
(128, false) => primitive_ty("u128", span),
(128, true) => primitive_ty("i128", span),
(_, false) => primitive_ty("u64", span),
(_, true) => primitive_ty("i64", span),
}
}
fn primitive_ty(name: &str, span: Span) -> TokenStream {
let ident = Ident::new(name, span);
quote_spanned! {span=> ::core::primitive::#ident }
}
fn respan(tokens: TokenStream, span: Span) -> TokenStream {
tokens
.into_iter()
.map(|tree| {
let mut tree = match tree {
TokenTree::Group(group) => {
TokenTree::Group(Group::new(group.delimiter(), respan(group.stream(), span)))
}
other => other,
};
tree.set_span(span);
tree
})
.collect()
}
fn message_literal(text: &str, span: Span) -> TokenStream {
let mut literal = Literal::string(text);
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn usize_literal(value: u64, span: Span) -> TokenStream {
let mut literal = Literal::usize_unsuffixed(value as usize);
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn asm_operand_head(operand: &ir::AsmOperand, span: Span) -> TokenStream {
let dir = match &operand.kind {
ir::AsmOperandKind::In(_) => "in",
ir::AsmOperandKind::Out { late: true, .. } => "lateout",
ir::AsmOperandKind::Out { late: false, .. } => "out",
ir::AsmOperandKind::InOut { .. } | ir::AsmOperandKind::Scratch(_) => "inout",
ir::AsmOperandKind::Const(_) => "const",
};
let dir = Ident::new(dir, span);
let reg = match (&operand.kind, operand.reg) {
(ir::AsmOperandKind::Const(_), _) => TokenStream::new(),
(_, ir::AsmReg::Class(class)) => {
let class = Ident::new(class, span);
quote_spanned! {span=> (#class) }
}
(_, ir::AsmReg::Explicit(name)) => {
let mut name = Literal::string(name);
name.set_span(span);
quote_spanned! {span=> (#name) }
}
};
match &operand.name {
Some(name) => {
let name = Ident::new(name, span);
quote_spanned! {span=> #name = #dir #reg }
}
None => quote_spanned! {span=> #dir #reg },
}
}
fn asm_const_literal(value: i128, span: Span) -> TokenStream {
let mut literal = if i64::try_from(value).is_ok() {
Literal::i64_suffixed(value as i64)
} else {
Literal::u64_suffixed(value as u64)
};
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn suffixed_int_literal(value: i128, ty: &str, span: Span) -> TokenStream {
let mut literal = match ty {
"u32" => Literal::u32_suffixed(value as u32),
"u64" => Literal::u64_suffixed(value as u64),
"i64" => Literal::i64_suffixed(value as i64),
_ => Literal::i32_suffixed(value as i32),
};
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn float_bit_ty(ty: Ty, span: Span) -> (TokenStream, TokenStream) {
if ty == Ty::Float {
return (
primitive_ty("f32", span),
unsigned_hex_literal(0x7fff_ffff, "u32", span),
);
}
(
primitive_ty("f64", span),
unsigned_hex_literal(0x7fff_ffff_ffff_ffff, "u64", span),
)
}
fn quiet_bit_literal(ty: Ty, span: Span) -> TokenStream {
if ty == Ty::Float {
return unsigned_hex_literal(1 << 22, "u32", span);
}
unsigned_hex_literal(1 << 51, "u64", span)
}
fn unsigned_hex_literal(value: u64, suffix: &str, span: Span) -> TokenStream {
let mut literal = Literal::from_str(&format!("0x{value:x}{suffix}"))
.expect("a hexadecimal literal followed by a suffix is a token");
literal.set_span(span);
TokenStream::from(TokenTree::Literal(literal))
}
fn float_literal_token(value: f64, span: Span) -> TokenStream {
let mut literal = Literal::f64_unsuffixed(value.abs());
literal.set_span(span);
if value.is_sign_negative() {
quote_spanned! {span=> -#literal }
} else {
TokenStream::from(TokenTree::Literal(literal))
}
}
fn bare_float_literal(value: f64, span: Span) -> TokenStream {
float_literal_token(value, span)
}
fn comma_operands(expr: &Expr) -> Vec<&Expr> {
let mut out = Vec::new();
let mut node = expr;
while let ExprKind::Comma { lhs, rhs } = &node.kind {
out.push(&**rhs);
node = lhs;
}
out.push(node);
out.reverse();
out
}
fn constant_of(expr: &Expr) -> Option<ConstValue> {
match &expr.kind {
ExprKind::Int(value) => Some(ConstValue::Int(*value)),
ExprKind::Float(value) if value.is_finite() => Some(ConstValue::Float(*value)),
_ => None,
}
}
fn cmp_tokens(op: CmpOp, span: Span) -> TokenStream {
match op {
CmpOp::Lt => quote_spanned! {span=> < },
CmpOp::Gt => quote_spanned! {span=> > },
CmpOp::Le => quote_spanned! {span=> <= },
CmpOp::Ge => quote_spanned! {span=> >= },
CmpOp::Eq => quote_spanned! {span=> == },
CmpOp::Ne => quote_spanned! {span=> != },
}
}
#[cfg(test)]
mod tests {
use super::*;
fn spellings(names: &[&str]) -> Vec<(String, String)> {
let mut pairs: Vec<(String, String)> = unique_spellings(names.iter().copied())
.into_iter()
.collect();
pairs.sort();
pairs
}
fn assert_spellings(names: &[&str], expected: &[(&str, &str)]) {
let renamed = unique_spellings(names.iter().copied());
let want: Vec<(String, String)> = expected
.iter()
.map(|(c, rust)| ((*c).to_owned(), (*rust).to_owned()))
.collect();
assert_eq!(spellings(names), want);
let mut seen: HashMap<String, &str> = HashMap::new();
for name in names {
let spelling = match renamed.get(*name) {
Some(unique) => unique.clone(),
None => plain_spelling(name),
};
if let Some(other) = seen.insert(spelling.clone(), name) {
assert_eq!(
other, *name,
"{other} and {name} are both spelled {spelling}"
);
}
}
}
#[test]
fn a_name_rust_can_spell_keeps_it() {
assert_spellings(&["counter", "match", "loop", "café"], &[]);
}
#[test]
fn the_five_names_that_cannot_be_raw_grow_an_underscore() {
assert_spellings(
&["self", "Self", "super", "crate", "_"],
&[
("Self", "Self_"),
("_", "__"),
("crate", "crate_"),
("self", "self_"),
("super", "super_"),
],
);
}
#[test]
fn a_dollar_is_spelled_out() {
assert_spellings(
&["a$b", "$", "x$"],
&[
("$", "_dollar_"),
("a$b", "a_dollar_b"),
("x$", "x_dollar_"),
],
);
}
#[test]
fn a_spelling_the_program_already_uses_grows_another_underscore() {
assert_spellings(&["self", "self_"], &[("self", "self__")]);
assert_spellings(&["self", "self_", "self__"], &[("self", "self___")]);
assert_spellings(&["a$b", "a_dollar_b"], &[("a$b", "a_dollar_b_")]);
}
#[test]
fn the_answer_does_not_depend_on_the_order_the_names_arrive_in() {
let forwards = spellings(&["self", "self_", "crate", "crate_", "a$b", "a_dollar_b"]);
let backwards = spellings(&["a_dollar_b", "a$b", "crate_", "crate", "self_", "self"]);
assert_eq!(forwards, backwards);
}
#[test]
fn a_name_is_spelled_the_same_way_however_often_it_is_collected() {
assert_spellings(&["self", "self", "self_", "self"], &[("self", "self__")]);
}
}