From 2a5e6ab172afac078d6a8236ac5462478c0efec2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 21 May 2022 23:11:44 -0400 Subject: [PATCH 01/13] Add PartialEq, Eq, and EnumIter to Architecture --- Cargo.lock | 4 ++++ compiler/roc_target/Cargo.toml | 2 ++ compiler/roc_target/src/lib.rs | 10 +++++++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 06e6898ca6..dd56adb04e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3473,6 +3473,8 @@ dependencies = [ "roc_target", "roc_test_utils", "roc_types", + "strum", + "strum_macros", "target-lexicon", "tempfile", ] @@ -4113,6 +4115,8 @@ dependencies = [ name = "roc_target" version = "0.1.0" dependencies = [ + "strum", + "strum_macros", "target-lexicon", ] diff --git a/compiler/roc_target/Cargo.toml b/compiler/roc_target/Cargo.toml index 6e3bffe5df..cbef2efa5a 100644 --- a/compiler/roc_target/Cargo.toml +++ b/compiler/roc_target/Cargo.toml @@ -7,3 +7,5 @@ edition = "2021" [dependencies] target-lexicon = "0.12.3" +strum = "0.24.0" +strum_macros = "0.24" diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs index 3235d212c3..6b031f6756 100644 --- a/compiler/roc_target/src/lib.rs +++ b/compiler/roc_target/src/lib.rs @@ -2,6 +2,8 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] +use strum_macros::EnumIter; + #[derive(Debug, Clone, Copy)] pub struct TargetInfo { pub architecture: Architecture, @@ -50,6 +52,12 @@ impl From<&target_lexicon::Triple> for TargetInfo { } } +impl From for TargetInfo { + fn from(architecture: Architecture) -> Self { + Self { architecture } + } +} + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PtrWidth { @@ -57,7 +65,7 @@ pub enum PtrWidth { Bytes8 = 8, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumIter)] pub enum Architecture { X86_64, X86_32, From 13fbd17bace80b449e23b3403cdc8eb1e3885652 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 21 May 2022 23:14:03 -0400 Subject: [PATCH 02/13] Bindgen for different architectures --- bindgen/Cargo.toml | 2 + bindgen/src/load.rs | 95 +++++++++++++++++++----------------- bindgen/src/main.rs | 42 +++++++++++----- bindgen/src/types.rs | 2 +- bindgen/tests/helpers/mod.rs | 9 +++- 5 files changed, 90 insertions(+), 60 deletions(-) diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index eedb8cbcc9..87e389bcf9 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -28,6 +28,8 @@ roc_error_macros = { path = "../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } target-lexicon = "0.12.3" clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] } +strum = "0.24.0" +strum_macros = "0.24" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/bindgen/src/load.rs b/bindgen/src/load.rs index 069c72d3a5..a6b1eeffad 100644 --- a/bindgen/src/load.rs +++ b/bindgen/src/load.rs @@ -8,15 +8,17 @@ use roc_can::{ use roc_load::{LoadedModule, Threading}; use roc_mono::layout::LayoutCache; use roc_reporting::report::RenderTarget; +use roc_target::Architecture; use std::io; use std::path::{Path, PathBuf}; +use strum::IntoEnumIterator; use target_lexicon::Triple; pub fn load_types( full_file_path: PathBuf, dir: &Path, threading: Threading, -) -> Result { +) -> Result, io::Error> { // TODO: generate both 32-bit and 64-bit #[cfg] macros if structs are different // depending on 32-bit vs 64-bit targets. let target_info = (&Triple::host()).into(); @@ -56,56 +58,59 @@ pub fn load_types( ); } - let mut layout_cache = LayoutCache::new(target_info); - let mut env = Env { - arena, - layout_cache: &mut layout_cache, - interns: &interns, - struct_names: Default::default(), - enum_names: Default::default(), - subs, - }; + let mut answer = Vec::with_capacity(Architecture::iter().size_hint().0); - let mut types = Types::default(); + for architecture in Architecture::iter() { + let mut layout_cache = LayoutCache::new(architecture.into()); + let mut env = Env { + arena, + layout_cache: &mut layout_cache, + interns: &interns, + struct_names: Default::default(), + enum_names: Default::default(), + subs, + }; + let mut types = Types::default(); - for decl in decls.into_iter() { - let defs = match decl { - Declaration::Declare(def) => { - vec![def] - } - Declaration::DeclareRec(defs, cycle_mark) => { - if cycle_mark.is_illegal(subs) { - vec![] + for decl in decls.iter() { + let defs = match decl { + Declaration::Declare(def) => { + vec![def.clone()] + } + Declaration::DeclareRec(defs, cycle_mark) => { + if cycle_mark.is_illegal(subs) { + Vec::new() + } else { + defs.clone() + } + } + Declaration::Builtin(..) => { + unreachable!("Builtin decl in userspace module?") + } + Declaration::InvalidCycle(..) => Vec::new(), + }; + + for Def { + loc_pattern, + pattern_vars, + .. + } in defs.into_iter() + { + if let Pattern::Identifier(sym) = loc_pattern.value { + let var = pattern_vars + .get(&sym) + .expect("Indetifier known but it has no var?"); + + bindgen::add_type(&mut env, *var, &mut types); } else { - defs + // figure out if we need to export non-identifier defs - when would that + // happen? } } - Declaration::Builtin(..) => { - unreachable!("Builtin decl in userspace module?") - } - Declaration::InvalidCycle(..) => { - vec![] - } - }; - - for Def { - loc_pattern, - pattern_vars, - .. - } in defs.into_iter() - { - if let Pattern::Identifier(sym) = loc_pattern.value { - let var = pattern_vars - .get(&sym) - .expect("Indetifier known but it has no var?"); - - bindgen::add_type(&mut env, *var, &mut types); - } else { - // figure out if we need to export non-identifier defs - when would that - // happen? - } } + + answer.push((architecture, types)); } - Ok(types) + Ok(answer) } diff --git a/bindgen/src/main.rs b/bindgen/src/main.rs index 978ffcb63f..650230dc76 100644 --- a/bindgen/src/main.rs +++ b/bindgen/src/main.rs @@ -2,6 +2,7 @@ use clap::Parser; use roc_bindgen::bindgen_rs; use roc_bindgen::load::load_types; use roc_load::Threading; +use roc_target::Architecture; use std::ffi::OsStr; use std::fs::File; use std::io::{ErrorKind, Write}; @@ -57,30 +58,45 @@ pub fn main() { }; match load_types(input_path.clone(), &cwd, Threading::AllAvailable) { - Ok(types) => { + Ok(types_by_architecture) => { let mut buf; - let result = match output_type { + match output_type { OutputType::Rust => { buf = std::str::from_utf8(bindgen_rs::HEADER).unwrap().to_string(); - bindgen_rs::write_types(&types, &mut buf) + for (architecture, types) in types_by_architecture { + use std::fmt::Write; + + let arch_cfg_str = match architecture { + Architecture::X86_64 => "x86_64", + Architecture::X86_32 => "x86", + Architecture::Aarch64 => "aarch64", + Architecture::Arm => "arm", + Architecture::Wasm32 => "wasm32", + }; + + let result = + writeln!(buf, "if cfg!(target_arch = \"{arch_cfg_str}\")]\n{{") + .and_then(|()| bindgen_rs::write_types(&types, &mut buf)) + .and_then(|()| buf.write_str("}\n")); + + if let Err(err) = result { + eprintln!( + "Unable to generate binding string {} - {:?}", + output_path.display(), + err + ); + + process::exit(1); + } + } } OutputType::C => todo!("TODO: Generate bindings for C"), OutputType::Zig => todo!("TODO: Generate bindings for Zig"), OutputType::Json => todo!("TODO: Generate bindings for JSON"), }; - if let Err(err) = result { - eprintln!( - "Unable to generate binding string {} - {:?}", - output_path.display(), - err - ); - - process::exit(1); - } - let mut file = File::create(output_path.clone()).unwrap_or_else(|err| { eprintln!( "Unable to create output file {} - {:?}", diff --git a/bindgen/src/types.rs b/bindgen/src/types.rs index 94a50d71d8..aac96933be 100644 --- a/bindgen/src/types.rs +++ b/bindgen/src/types.rs @@ -9,7 +9,7 @@ use std::convert::TryInto; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TypeId(usize); -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct Types { by_id: Vec, diff --git a/bindgen/tests/helpers/mod.rs b/bindgen/tests/helpers/mod.rs index 6150763def..2e44330f4b 100644 --- a/bindgen/tests/helpers/mod.rs +++ b/bindgen/tests/helpers/mod.rs @@ -1,6 +1,7 @@ use roc_bindgen::bindgen_rs; use roc_bindgen::load::load_types; use roc_load::Threading; +use roc_target::Architecture; use std::env; use std::fs::File; use std::io::Write; @@ -38,7 +39,13 @@ pub fn generate_bindings(decl_src: &str) -> String { dir.close().expect("Unable to close tempdir"); result.expect("had problems loading") - }; + } + .iter() + // Only use the X86_64 types for these tests + .find(|(architecture, _)| *architecture == Architecture::X86_64) + .unwrap() + .1 + .clone(); // Reuse the `src` allocation since we're done with it. let mut buf = src; From 9325aeb614a77c7f0954c45b75dd5091523c11f4 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 May 2022 08:45:16 -0400 Subject: [PATCH 03/13] Try writing bindgen cfg annotations all at once --- bindgen/src/bindgen_rs.rs | 63 ++++++++++++++++--- bindgen/src/main.rs | 18 +----- .../fixtures/union-with-padding/src/lib.rs | 17 ++--- bindgen/tests/helpers/mod.rs | 8 ++- 4 files changed, 68 insertions(+), 38 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index bce623a34c..d33c91d5d7 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1,5 +1,5 @@ use roc_mono::layout::UnionLayout; -use roc_target::TargetInfo; +use roc_target::{Architecture, TargetInfo}; use crate::types::{RocTagUnion, RocType, TypeId, Types}; use std::{ @@ -11,17 +11,24 @@ pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); const INDENT: &str = " "; -pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result { +pub fn write_types(architecture: Architecture, types: &Types, buf: &mut String) -> fmt::Result { for id in types.sorted_ids() { - write_type(id, types, buf)?; + write_type(architecture, id, types, buf)?; } Ok(()) } -fn write_type(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { +fn write_type( + architecture: Architecture, + id: TypeId, + types: &Types, + buf: &mut String, +) -> fmt::Result { match types.get(id) { - RocType::Struct { name, fields } => write_struct(name, fields, id, types, buf), + RocType::Struct { name, fields } => { + write_struct(name, architecture, fields, id, types, buf) + } RocType::TagUnion(tag_union) => { match tag_union { RocTagUnion::Enumeration { tags, name } => { @@ -31,14 +38,21 @@ fn write_type(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { write_derive(types.get(id), types, buf)?; writeln!(buf, "\nstruct {}();", type_name(id, types)) } else { - write_enumeration(name, types.get(id), tags.iter(), types, buf) + write_enumeration( + name, + architecture, + types.get(id), + tags.iter(), + types, + buf, + ) } } RocTagUnion::NonRecursive { tags, name } => { // Empty tag unions can never come up at runtime, // and so don't need declared types. if !tags.is_empty() { - write_tag_union(name, id, tags, types, buf) + write_tag_union(name, architecture, id, tags, types, buf) } else { Ok(()) } @@ -56,6 +70,7 @@ fn write_type(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { non_null_payload, } => write_nullable_unwrapped( name, + architecture, id, null_tag, non_null_tag, @@ -102,6 +117,7 @@ fn write_type(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { fn write_discriminant( name: &str, + architecture: Architecture, tag_names: Vec, types: &Types, buf: &mut String, @@ -121,6 +137,7 @@ fn write_discriminant( write_enumeration( &discriminant_name, + architecture, &discriminant_type, tag_names.into_iter(), types, @@ -132,13 +149,14 @@ fn write_discriminant( fn write_tag_union( name: &str, + architecture: Architecture, type_id: TypeId, tags: &[(String, Option)], types: &Types, buf: &mut String, ) -> fmt::Result { let tag_names = tags.iter().map(|(name, _)| name).cloned().collect(); - let discriminant_name = write_discriminant(name, tag_names, types, buf)?; + let discriminant_name = write_discriminant(name, architecture, tag_names, types, buf)?; let typ = types.get(type_id); // TODO also do this for other targets. Remember, these can change based on more // than just pointer width; e.g. on wasm, the alignments of U16 and U8 are both 4! @@ -683,6 +701,7 @@ fn write_impl_tags< fn write_enumeration, S: AsRef + fmt::Display>( name: &str, + architecture: Architecture, typ: &RocType, tags: I, types: &Types, @@ -693,6 +712,7 @@ fn write_enumeration, S: AsRef + fmt::Displa .try_into() .unwrap(); + write_arch_cfg(architecture, 0, buf)?; write_derive(typ, types, buf)?; // e.g. "#[repr(u8)]\npub enum Foo {\n" @@ -724,6 +744,7 @@ impl core::fmt::Debug for {name} {{ fn write_struct( name: &str, + architecture: Architecture, fields: &[(String, TypeId)], struct_id: TypeId, types: &Types, @@ -736,9 +757,10 @@ fn write_struct( } 1 => { // Unwrap single-field records - write_type(fields.first().unwrap().1, types, buf) + write_type(architecture, fields.first().unwrap().1, types, buf) } _ => { + write_arch_cfg(architecture, 0, buf)?; write_derive(types.get(struct_id), types, buf)?; writeln!(buf, "#[repr(C)]\npub struct {name} {{")?; @@ -814,6 +836,7 @@ fn write_derive(typ: &RocType, types: &Types, buf: &mut String) -> fmt::Result { fn write_nullable_unwrapped( name: &str, + architecture: Architecture, id: TypeId, null_tag: &str, non_null_tag: &str, @@ -825,7 +848,7 @@ fn write_nullable_unwrapped( tag_names.sort(); - let discriminant_name = write_discriminant(name, tag_names, types, buf)?; + let discriminant_name = write_discriminant(name, architecture, tag_names, types, buf)?; let payload_type = types.get(non_null_payload); let payload_type_name = type_name(non_null_payload, types); let has_pointer = payload_type.has_pointer(types); @@ -1036,3 +1059,23 @@ impl core::fmt::Debug for {name} {{ Ok(()) } + +fn write_arch_cfg( + architecture: Architecture, + indentations: usize, + buf: &mut String, +) -> fmt::Result { + let arch_cfg_str = match architecture { + Architecture::X86_64 => "x86_64", + Architecture::X86_32 => "x86", + Architecture::Aarch64 => "aarch64", + Architecture::Arm => "arm", + Architecture::Wasm32 => "wasm32", + }; + + for _ in 0..indentations { + buf.write_str(INDENT)?; + } + + write!(buf, "\n#[cfg(target_arch = \"{arch_cfg_str}\")]") +} diff --git a/bindgen/src/main.rs b/bindgen/src/main.rs index 650230dc76..9263fcb924 100644 --- a/bindgen/src/main.rs +++ b/bindgen/src/main.rs @@ -2,7 +2,6 @@ use clap::Parser; use roc_bindgen::bindgen_rs; use roc_bindgen::load::load_types; use roc_load::Threading; -use roc_target::Architecture; use std::ffi::OsStr; use std::fs::File; use std::io::{ErrorKind, Write}; @@ -66,22 +65,7 @@ pub fn main() { buf = std::str::from_utf8(bindgen_rs::HEADER).unwrap().to_string(); for (architecture, types) in types_by_architecture { - use std::fmt::Write; - - let arch_cfg_str = match architecture { - Architecture::X86_64 => "x86_64", - Architecture::X86_32 => "x86", - Architecture::Aarch64 => "aarch64", - Architecture::Arm => "arm", - Architecture::Wasm32 => "wasm32", - }; - - let result = - writeln!(buf, "if cfg!(target_arch = \"{arch_cfg_str}\")]\n{{") - .and_then(|()| bindgen_rs::write_types(&types, &mut buf)) - .and_then(|()| buf.write_str("}\n")); - - if let Err(err) = result { + if let Err(err) = bindgen_rs::write_types(architecture, &types, &mut buf) { eprintln!( "Unable to generate binding string {} - {:?}", output_path.display(), diff --git a/bindgen/tests/fixtures/union-with-padding/src/lib.rs b/bindgen/tests/fixtures/union-with-padding/src/lib.rs index 495063820c..8a7056aeda 100644 --- a/bindgen/tests/fixtures/union-with-padding/src/lib.rs +++ b/bindgen/tests/fixtures/union-with-padding/src/lib.rs @@ -1,8 +1,10 @@ mod bindings; +use bindings::non_recursive::{NonRecursive, Tag}; + extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(_: *mut bindings::NonRecursive); + fn roc_main(_: *mut NonRecursive); } #[no_mangle] @@ -11,8 +13,7 @@ pub extern "C" fn rust_main() -> i32 { use std::collections::hash_set::HashSet; let tag_union = unsafe { - let mut ret: core::mem::MaybeUninit = - core::mem::MaybeUninit::uninit(); + let mut ret: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); roc_main(ret.as_mut_ptr()); @@ -30,11 +31,11 @@ pub extern "C" fn rust_main() -> i32 { println!( "tag_union was: {:?}\n`Foo \"small str\"` is: {:?}\n`Foo \"A long enough string to not be small\"` is: {:?}\n`Bar 123` is: {:?}\n`Baz` is: {:?}\n`Blah 456` is: {:?}", tag_union, - bindings::NonRecursive::Foo("small str".into()), - bindings::NonRecursive::Foo("A long enough string to not be small".into()), - bindings::NonRecursive::Bar(123.into()), - bindings::NonRecursive::Baz, - bindings::NonRecursive::Blah(456), + NonRecursive::Foo("small str".into()), + NonRecursive::Foo("A long enough string to not be small".into()), + NonRecursive::Bar(123.into()), + NonRecursive::Baz, + NonRecursive::Blah(456), ); // Debug let mut set = HashSet::new(); diff --git a/bindgen/tests/helpers/mod.rs b/bindgen/tests/helpers/mod.rs index 2e44330f4b..2f96c9de01 100644 --- a/bindgen/tests/helpers/mod.rs +++ b/bindgen/tests/helpers/mod.rs @@ -26,6 +26,8 @@ pub fn generate_bindings(decl_src: &str) -> String { src.push_str(decl_src); + // Only use the X86_64 types for these tests + let target_arch = Architecture::X86_64; let types = { let dir = tempdir().expect("Unable to create tempdir"); let filename = PathBuf::from("Package-Config.roc"); @@ -41,8 +43,7 @@ pub fn generate_bindings(decl_src: &str) -> String { result.expect("had problems loading") } .iter() - // Only use the X86_64 types for these tests - .find(|(architecture, _)| *architecture == Architecture::X86_64) + .find(|(architecture, _)| *architecture == target_arch) .unwrap() .1 .clone(); @@ -51,7 +52,8 @@ pub fn generate_bindings(decl_src: &str) -> String { let mut buf = src; buf.clear(); - bindgen_rs::write_types(&types, &mut buf).expect("I/O error when writing bindgen string"); + bindgen_rs::write_types(target_arch, &types, &mut buf) + .expect("I/O error when writing bindgen string"); buf } From 706e5c26dbe3e83a6f987e29033be0d500205829 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 May 2022 12:50:59 -0400 Subject: [PATCH 04/13] Cross-arch bindgen for records and enumerations --- bindgen/src/bindgen_rs.rs | 337 +++++++++++++++++++++-------------- bindgen/src/main.rs | 13 +- bindgen/tests/helpers/mod.rs | 9 +- 3 files changed, 204 insertions(+), 155 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index d33c91d5d7..14509da8a0 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1,33 +1,96 @@ +use crate::types::{RocTagUnion, RocType, TypeId, Types}; use roc_mono::layout::UnionLayout; use roc_target::{Architecture, TargetInfo}; - -use crate::types::{RocTagUnion, RocType, TypeId, Types}; +use std::collections::hash_map::HashMap; use std::{ convert::TryInto, fmt::{self, Write}, }; +use strum::IntoEnumIterator; pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); const INDENT: &str = " "; -pub fn write_types(architecture: Architecture, types: &Types, buf: &mut String) -> fmt::Result { - for id in types.sorted_ids() { - write_type(architecture, id, types, buf)?; - } +type Impls = HashMap>>; +type Impl = Option; - Ok(()) +/// Add the given declaration body, along with the architecture, to the Impls. +/// This can optionally be within an `impl`, or if no `impl` is specified, +/// then it's added at the top level. +fn add_decl(impls: &mut Impls, opt_impl: Impl, architecture: Architecture, body: String) { + let decls = impls.entry(opt_impl).or_default(); + let architectures = decls.entry(body).or_default(); + + architectures.push(architecture); } -fn write_type( - architecture: Architecture, - id: TypeId, - types: &Types, - buf: &mut String, -) -> fmt::Result { +pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { + let mut buf = String::new(); + + emit_help(types_by_architecture, &mut buf); + + buf +} + +fn emit_help(types_by_architecture: &[(Architecture, Types)], buf: &mut String) { + let mut impls: Impls = HashMap::default(); + + for (architecture, types) in types_by_architecture.into_iter() { + for id in types.sorted_ids() { + add_type(*architecture, id, types, &mut impls); + } + } + + for (opt_impl, decls) in impls { + let has_impl; + + if let Some(impl_str) = opt_impl { + has_impl = true; + + buf.push_str(&impl_str); + buf.push_str(" {{"); + } else { + has_impl = false; + } + + for (decl, architectures) in decls { + // If we're inside an `impl` block, indent the cfg annotation + if has_impl { + buf.push_str(INDENT); + } + + match architectures.len() { + 1 => { + let arch = arch_to_str(architectures.get(0).unwrap()); + + buf.push_str(&format!("\n#[cfg(target_arch = \"{arch}\")]\n{decl}")); + } + _ => { + // We should never have a decl recorded with 0 architectures! + debug_assert_ne!(architectures.len(), 0); + + let alternatives = architectures + .iter() + .map(|arch| format!("{INDENT}target_arch = \"{}\"", arch_to_str(arch))) + .collect::>() + .join(",\n"); + + buf.push_str(&format!("\n#[cfg(any(\n{alternatives}\n))]\n{decl}")); + } + } + } + + if has_impl { + buf.push_str("}\n"); + } + } +} + +fn add_type(architecture: Architecture, id: TypeId, types: &Types, impls: &mut Impls) { match types.get(id) { RocType::Struct { name, fields } => { - write_struct(name, architecture, fields, id, types, buf) + add_struct(name, architecture, fields, id, types, impls) } RocType::TagUnion(tag_union) => { match tag_union { @@ -35,27 +98,31 @@ fn write_type( if tags.len() == 1 { // An enumeration with one tag is a zero-sized unit type, so // represent it as a zero-sized struct (e.g. "struct Foo()"). - write_derive(types.get(id), types, buf)?; - writeln!(buf, "\nstruct {}();", type_name(id, types)) + let derive = derive_str(types.get(id), types); + let struct_name = type_name(id, types); + let body = format!("{derive}\nstruct {struct_name}();"); + + add_decl(impls, None, architecture, body); } else { - write_enumeration( + add_enumeration( name, architecture, types.get(id), tags.iter(), types, - buf, + impls, ) } } RocTagUnion::NonRecursive { tags, name } => { + let todo_reminder = todo!(); // Empty tag unions can never come up at runtime, // and so don't need declared types. - if !tags.is_empty() { - write_tag_union(name, architecture, id, tags, types, buf) - } else { - Ok(()) - } + // if !tags.is_empty() { + // write_tag_union(name, architecture, id, tags, types, buf) + // } else { + // Ok(()) + // } } RocTagUnion::Recursive { .. } => { todo!(); @@ -68,16 +135,19 @@ fn write_type( null_tag, non_null_tag, non_null_payload, - } => write_nullable_unwrapped( - name, - architecture, - id, - null_tag, - non_null_tag, - *non_null_payload, - types, - buf, - ), + } => { + let todo_reminder = todo!(); + // write_nullable_unwrapped( + // name, + // architecture, + // id, + // null_tag, + // non_null_tag, + // *non_null_payload, + // types, + // buf, + // ) + } RocTagUnion::NonNullableUnwrapped { .. } => { todo!(); } @@ -103,18 +173,20 @@ fn write_type( | RocType::RocDict(_, _) | RocType::RocSet(_) | RocType::RocList(_) - | RocType::RocBox(_) => Ok(()), + | RocType::RocBox(_) => {} RocType::TransparentWrapper { name, content } => { - write_derive(types.get(id), types, buf)?; - writeln!( - buf, - "#[repr(transparent)]\npub struct {name}({});", + let derive = derive_str(types.get(id), types); + let body = format!( + "{derive}\n#[repr(transparent)]\npub struct {name}({});", type_name(*content, types) - ) + ); + + add_decl(impls, None, architecture, body); } } } +#[cfg(debug_assertions = "false")] // TODO REMOVE fn write_discriminant( name: &str, architecture: Architecture, @@ -129,13 +201,13 @@ fn write_discriminant( // Bar, // Foo, // } - let discriminant_name = format!("tag_{name}"); + let discriminant_name = format!("variant_{name}"); let discriminant_type = RocType::TagUnion(RocTagUnion::Enumeration { name: discriminant_name.clone(), tags: tag_names.clone(), }); - write_enumeration( + add_enumeration( &discriminant_name, architecture, &discriminant_type, @@ -147,13 +219,14 @@ fn write_discriminant( Ok(discriminant_name) } +#[cfg(debug_assertions = "false")] // TODO REMOVE fn write_tag_union( name: &str, architecture: Architecture, type_id: TypeId, tags: &[(String, Option)], types: &Types, - buf: &mut String, + impls: &mut Impls, ) -> fmt::Result { let tag_names = tags.iter().map(|(name, _)| name).cloned().collect(); let discriminant_name = write_discriminant(name, architecture, tag_names, types, buf)?; @@ -165,14 +238,8 @@ fn write_tag_union( let size = typ.size(types, target_info); { - // No deriving for unions; we have to add the impls ourselves! - - writeln!( - buf, - r#" -#[repr(C)] -pub union {name} {{"# - )?; + // No #[derive(...)] for unions; we have to generate each impl ourselves! + let mut buf = format!("#[repr(C)]\npub union {name} {{"); for (tag_name, opt_payload_id) in tags { // If there's no payload, we don't need a variant for it. @@ -204,13 +271,15 @@ pub union {name} {{"# // (Do this even if theoretically shouldn't be necessary, since // there's no runtime cost and it more explicitly syncs the // union's size with what we think it should be.) - writeln!(buf, " _size_with_discriminant: [u8; {size}],")?; + writeln!(buf, "{INDENT}_sizer: [u8; {size}],\n}}")?; - buf.write_str("}\n")?; + add_decl(impls, None, architecture, buf); } // The impl for the tag union { + let opt_impl = Some(format!("impl {name}")); + // An old design, which ended up not working out, was that the tag union // was a struct containing two fields: one for the `union`, and another // for the discriminant. @@ -224,28 +293,38 @@ pub union {name} {{"# // be 32B, and the discriminant will appear at offset 24 - right after the end of // the RocStr. The current design recognizes this and works with it, by representing // the entire structure as a union and manually setting the tag at the appropriate offset. - write!( - buf, - r#" -impl {name} {{ - pub fn tag(&self) -> {discriminant_name} {{ + add_decl( + impls, + opt_impl, + architecture, + format!( + r#" + /// Returns which variant this tag union holds. Note that this never includes a payload! + pub fn variant(&self) -> {discriminant_name} {{ unsafe {{ let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); core::mem::transmute::(*bytes.as_ptr().add({discriminant_offset})) }} - }} + }}"# + ), + ); - /// Internal helper - fn set_discriminant(&mut self, tag: {discriminant_name}) {{ + add_decl( + impls, + opt_impl, + architecture, + format!( + r#"/// Internal helper + fn set_discriminant(&mut self, discriminant: {discriminant_name}) {{ let discriminant_ptr: *mut {discriminant_name} = (self as *mut {name}).cast(); unsafe {{ - *(discriminant_ptr.add({discriminant_offset})) = tag; + *(discriminant_ptr.add({discriminant_offset})) = discriminant; }} - }} -"# - )?; + }}"# + ), + ); for (tag_name, opt_payload_id) in tags { // Add a convenience constructor function to the impl, e.g. @@ -365,24 +444,18 @@ impl {name} {{ )?; } } - - buf.write_str("}\n")?; } // The Drop impl for the tag union { - writeln!( - buf, - r#" -impl Drop for {name} {{ - fn drop(&mut self) {{"# - )?; + let opt_impl = Some(format!("impl Drop for {name}")); + let mut buf = "fn drop(&mut self) {".to_string(); write_impl_tags( 2, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { match opt_payload_id { Some(payload_id) if types.get(payload_id).has_pointer(types) => { @@ -674,107 +747,100 @@ fn write_impl_tags< buf: &mut String, to_branch_str: F, ) -> fmt::Result { - for _ in 0..indentations { - buf.write_str(INDENT)?; - } + write_indents(indentations, buf); - buf.write_str("match self.tag() {\n")?; + buf.push_str("match self.tag() {\n"); for (tag_name, opt_payload_id) in tags { let branch_str = to_branch_str(tag_name, *opt_payload_id); - for _ in 0..(indentations + 1) { - buf.write_str(INDENT)?; - } + write_indents(indentations + 1, buf); writeln!(buf, "{discriminant_name}::{tag_name} => {branch_str}")?; } - for _ in 0..indentations { - buf.write_str(INDENT)?; - } + write_indents(indentations, buf); - buf.write_str("}\n")?; + buf.push_str("}\n"); Ok(()) } -fn write_enumeration, S: AsRef + fmt::Display>( +fn add_enumeration, S: AsRef + fmt::Display>( name: &str, architecture: Architecture, typ: &RocType, tags: I, types: &Types, - buf: &mut String, -) -> fmt::Result { + impls: &mut Impls, +) { let tag_bytes: usize = UnionLayout::discriminant_size(tags.len()) .stack_size() .try_into() .unwrap(); - write_arch_cfg(architecture, 0, buf)?; - write_derive(typ, types, buf)?; + let derive = derive_str(typ, types); + let repr_bytes = tag_bytes * 8; // e.g. "#[repr(u8)]\npub enum Foo {\n" - writeln!(buf, "#[repr(u{})]\npub enum {name} {{", tag_bytes * 8)?; + let mut buf = format!("{derive}\n#[repr(u{repr_bytes})]\npub enum {name} {{\n"); - let mut debug_buf = String::new(); + // Debug impls should never vary by architecture. + let mut debug_buf = format!( + r#"impl core::fmt::Debug for {name} {{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ + match self {{ +"# + ); for (index, tag_name) in tags.enumerate() { - writeln!(buf, "{INDENT}{tag_name} = {index},")?; + buf.push_str(&format!("{INDENT}{tag_name} = {index},\n")); + + write_indents(3, &mut debug_buf); debug_buf.push_str(&format!( - r#"{INDENT}{INDENT}{INDENT}Self::{tag_name} => f.write_str("{name}::{tag_name}"), -"# + "Self::{tag_name} => f.write_str(\"{name}::{tag_name}\"),\n" )); } - writeln!( - buf, - r#"}} + buf.push_str(&format!( + "}}\n\n{debug_buf}{INDENT}{INDENT}}}\n{INDENT}}}\n}}" + )); -impl core::fmt::Debug for {name} {{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ - match self {{ -{debug_buf} }} - }} -}}"# - ) + add_decl(impls, None, architecture, buf); } -fn write_struct( +fn add_struct( name: &str, architecture: Architecture, fields: &[(String, TypeId)], struct_id: TypeId, types: &Types, - buf: &mut String, -) -> fmt::Result { + impls: &mut Impls, +) { match fields.len() { 0 => { // An empty record is zero-sized and won't end up being passed to/from the host. - Ok(()) } 1 => { // Unwrap single-field records - write_type(architecture, fields.first().unwrap().1, types, buf) + add_type(architecture, fields.first().unwrap().1, types, impls) } _ => { - write_arch_cfg(architecture, 0, buf)?; - write_derive(types.get(struct_id), types, buf)?; - - writeln!(buf, "#[repr(C)]\npub struct {name} {{")?; + let derive = derive_str(types.get(struct_id), types); + let mut buf = format!("{derive}\n#[repr(C)]\npub struct {name} {{\n"); for (label, field_id) in fields { - writeln!( - buf, + buf.push_str(&format!( "{INDENT}pub {}: {},", label.as_str(), type_name(*field_id, types) - )?; + )); } - buf.write_str("}\n") + buf.push_str("}"); + + add_decl(impls, None, architecture, buf); } } } @@ -816,24 +882,27 @@ fn type_name(id: TypeId, types: &Types) -> String { } } -fn write_derive(typ: &RocType, types: &Types, buf: &mut String) -> fmt::Result { - buf.write_str("\n#[derive(Clone, ")?; +fn derive_str(typ: &RocType, types: &Types) -> String { + let mut buf = "#[derive(Clone, ".to_string(); if !typ.has_pointer(types) { - buf.write_str("Copy, ")?; + buf.push_str("Copy, "); } if !typ.has_enumeration(types) { - buf.write_str("Debug, Default, ")?; + buf.push_str("Debug, Default, "); } if !typ.has_float(types) { - buf.write_str("Eq, Ord, Hash, ")?; + buf.push_str("Eq, Ord, Hash, "); } - buf.write_str("PartialEq, PartialOrd)]\n") + buf.push_str("PartialEq, PartialOrd)]"); + + buf } +#[cfg(debug_assertions = "false")] // TODO REMOVE fn write_nullable_unwrapped( name: &str, architecture: Architecture, @@ -1009,7 +1078,7 @@ impl {name} {{ )?; } - buf.write_str("}\n")?; + buf.push_str("}\n"); // The Drop impl for the tag union { @@ -1060,22 +1129,18 @@ impl core::fmt::Debug for {name} {{ Ok(()) } -fn write_arch_cfg( - architecture: Architecture, - indentations: usize, - buf: &mut String, -) -> fmt::Result { - let arch_cfg_str = match architecture { +fn arch_to_str(architecture: &Architecture) -> &'static str { + match architecture { Architecture::X86_64 => "x86_64", Architecture::X86_32 => "x86", Architecture::Aarch64 => "aarch64", Architecture::Arm => "arm", Architecture::Wasm32 => "wasm32", - }; - - for _ in 0..indentations { - buf.write_str(INDENT)?; } - - write!(buf, "\n#[cfg(target_arch = \"{arch_cfg_str}\")]") +} + +fn write_indents(indentations: usize, buf: &mut String) { + for _ in 0..indentations { + buf.push_str(INDENT); + } } diff --git a/bindgen/src/main.rs b/bindgen/src/main.rs index 9263fcb924..1a5d91752d 100644 --- a/bindgen/src/main.rs +++ b/bindgen/src/main.rs @@ -63,18 +63,9 @@ pub fn main() { match output_type { OutputType::Rust => { buf = std::str::from_utf8(bindgen_rs::HEADER).unwrap().to_string(); + let body = bindgen_rs::emit(&types_by_architecture); - for (architecture, types) in types_by_architecture { - if let Err(err) = bindgen_rs::write_types(architecture, &types, &mut buf) { - eprintln!( - "Unable to generate binding string {} - {:?}", - output_path.display(), - err - ); - - process::exit(1); - } - } + buf.push_str(&body); } OutputType::C => todo!("TODO: Generate bindings for C"), OutputType::Zig => todo!("TODO: Generate bindings for Zig"), diff --git a/bindgen/tests/helpers/mod.rs b/bindgen/tests/helpers/mod.rs index 2f96c9de01..e9aad3c1cd 100644 --- a/bindgen/tests/helpers/mod.rs +++ b/bindgen/tests/helpers/mod.rs @@ -48,14 +48,7 @@ pub fn generate_bindings(decl_src: &str) -> String { .1 .clone(); - // Reuse the `src` allocation since we're done with it. - let mut buf = src; - buf.clear(); - - bindgen_rs::write_types(target_arch, &types, &mut buf) - .expect("I/O error when writing bindgen string"); - - buf + bindgen_rs::emit(&[(target_arch, types)]) } #[allow(dead_code)] From 5647e6673e57d69e6cdf9a2657d296cd2d5efd26 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 May 2022 21:20:14 -0400 Subject: [PATCH 05/13] Cross-arch bindgen for nonrecursive tag unions --- bindgen/src/bindgen_rs.rs | 464 +++++++++--------- .../fixtures/union-with-padding/src/lib.rs | 2 +- 2 files changed, 234 insertions(+), 232 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index 14509da8a0..f56a7b9edc 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -2,11 +2,8 @@ use crate::types::{RocTagUnion, RocType, TypeId, Types}; use roc_mono::layout::UnionLayout; use roc_target::{Architecture, TargetInfo}; use std::collections::hash_map::HashMap; -use std::{ - convert::TryInto, - fmt::{self, Write}, -}; -use strum::IntoEnumIterator; +use std::convert::TryInto; +use std::fmt::Display; pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); @@ -27,13 +24,6 @@ fn add_decl(impls: &mut Impls, opt_impl: Impl, architecture: Architecture, body: pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { let mut buf = String::new(); - - emit_help(types_by_architecture, &mut buf); - - buf -} - -fn emit_help(types_by_architecture: &[(Architecture, Types)], buf: &mut String) { let mut impls: Impls = HashMap::default(); for (architecture, types) in types_by_architecture.into_iter() { @@ -48,23 +38,26 @@ fn emit_help(types_by_architecture: &[(Architecture, Types)], buf: &mut String) if let Some(impl_str) = opt_impl { has_impl = true; + buf.push('\n'); buf.push_str(&impl_str); - buf.push_str(" {{"); + buf.push_str(" {"); } else { has_impl = false; } for (decl, architectures) in decls { + buf.push('\n'); + // If we're inside an `impl` block, indent the cfg annotation - if has_impl { - buf.push_str(INDENT); - } + let indent = if has_impl { INDENT } else { "" }; + + buf.push_str(indent); match architectures.len() { 1 => { let arch = arch_to_str(architectures.get(0).unwrap()); - buf.push_str(&format!("\n#[cfg(target_arch = \"{arch}\")]\n{decl}")); + buf.push_str(&format!("r#[cfg(target_arch = \"{arch}\")]")); } _ => { // We should never have a decl recorded with 0 architectures! @@ -72,19 +65,28 @@ fn emit_help(types_by_architecture: &[(Architecture, Types)], buf: &mut String) let alternatives = architectures .iter() - .map(|arch| format!("{INDENT}target_arch = \"{}\"", arch_to_str(arch))) + .map(|arch| { + format!("{indent}{INDENT}target_arch = \"{}\"", arch_to_str(arch)) + }) .collect::>() .join(",\n"); - buf.push_str(&format!("\n#[cfg(any(\n{alternatives}\n))]\n{decl}")); + buf.push_str(&format!("#[cfg(any(\n{alternatives}\n{indent}))]")); } } + + buf.push('\n'); + buf.push_str(indent); + buf.push_str(&decl); + buf.push('\n'); } if has_impl { buf.push_str("}\n"); } } + + buf } fn add_type(architecture: Architecture, id: TypeId, types: &Types, impls: &mut Impls) { @@ -115,14 +117,11 @@ fn add_type(architecture: Architecture, id: TypeId, types: &Types, impls: &mut I } } RocTagUnion::NonRecursive { tags, name } => { - let todo_reminder = todo!(); // Empty tag unions can never come up at runtime, // and so don't need declared types. - // if !tags.is_empty() { - // write_tag_union(name, architecture, id, tags, types, buf) - // } else { - // Ok(()) - // } + if !tags.is_empty() { + add_tag_union(name, architecture, id, tags, types, impls); + } } RocTagUnion::Recursive { .. } => { todo!(); @@ -186,14 +185,13 @@ fn add_type(architecture: Architecture, id: TypeId, types: &Types, impls: &mut I } } -#[cfg(debug_assertions = "false")] // TODO REMOVE -fn write_discriminant( +fn add_discriminant( name: &str, architecture: Architecture, tag_names: Vec, types: &Types, - buf: &mut String, -) -> Result { + impls: &mut Impls, +) -> String { // The tag union's discriminant, e.g. // // #[repr(u8)] @@ -213,23 +211,22 @@ fn write_discriminant( &discriminant_type, tag_names.into_iter(), types, - buf, - )?; + impls, + ); - Ok(discriminant_name) + discriminant_name } -#[cfg(debug_assertions = "false")] // TODO REMOVE -fn write_tag_union( +fn add_tag_union( name: &str, architecture: Architecture, type_id: TypeId, tags: &[(String, Option)], types: &Types, impls: &mut Impls, -) -> fmt::Result { +) { let tag_names = tags.iter().map(|(name, _)| name).cloned().collect(); - let discriminant_name = write_discriminant(name, architecture, tag_names, types, buf)?; + let discriminant_name = add_discriminant(name, architecture, tag_names, types, impls); let typ = types.get(type_id); // TODO also do this for other targets. Remember, these can change based on more // than just pointer width; e.g. on wasm, the alignments of U16 and U8 are both 4! @@ -239,26 +236,26 @@ fn write_tag_union( { // No #[derive(...)] for unions; we have to generate each impl ourselves! - let mut buf = format!("#[repr(C)]\npub union {name} {{"); + let mut buf = format!("#[repr(C)]\npub union {name} {{\n"); for (tag_name, opt_payload_id) in tags { // If there's no payload, we don't need a variant for it. if let Some(payload_id) = opt_payload_id { let payload_type = types.get(*payload_id); - write!(buf, "{INDENT}{tag_name}: ")?; + buf.push_str(&format!("{INDENT}{tag_name}: ")); if payload_type.has_pointer(types) { // types with pointers need ManuallyDrop // because rust unions don't (and can't) // know how to drop them automatically! - writeln!( - buf, - "core::mem::ManuallyDrop<{}>,", + buf.push_str(&format!( + "core::mem::ManuallyDrop<{}>,\n", type_name(*payload_id, types) - )?; + )); } else { - writeln!(buf, "{},", type_name(*payload_id, types))?; + buf.push_str(&type_name(*payload_id, types)); + buf.push_str(",\n"); } } } @@ -271,7 +268,7 @@ fn write_tag_union( // (Do this even if theoretically shouldn't be necessary, since // there's no runtime cost and it more explicitly syncs the // union's size with what we think it should be.) - writeln!(buf, "{INDENT}_sizer: [u8; {size}],\n}}")?; + buf.push_str(&format!("{INDENT}_sizer: [u8; {size}],\n}}")); add_decl(impls, None, architecture, buf); } @@ -295,11 +292,10 @@ fn write_tag_union( // the entire structure as a union and manually setting the tag at the appropriate offset. add_decl( impls, - opt_impl, + opt_impl.clone(), architecture, format!( - r#" - /// Returns which variant this tag union holds. Note that this never includes a payload! + r#"/// Returns which variant this tag union holds. Note that this never includes a payload! pub fn variant(&self) -> {discriminant_name} {{ unsafe {{ let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); @@ -312,7 +308,7 @@ fn write_tag_union( add_decl( impls, - opt_impl, + opt_impl.clone(), architecture, format!( r#"/// Internal helper @@ -365,11 +361,12 @@ fn write_tag_union( ) }; - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// Construct a tag named {tag_name}, with the appropriate payload + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// Construct a tag named {tag_name}, with the appropriate payload pub fn {tag_name}(payload: {payload_type_name}) -> Self {{ let mut answer = Self {{ {tag_name}: {init_payload} @@ -378,40 +375,46 @@ fn write_tag_union( answer.set_discriminant({discriminant_name}::{tag_name}); answer - }}"#, - )?; + }}"# + ), + ); - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// Unsafely assume the given {name} has a .tag() of {tag_name} and convert it to {tag_name}'s payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return {tag_name}. + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// Unsafely assume the given {name} has a .variant() of {tag_name} and convert it to {tag_name}'s payload. + /// (Always examine .variant() first to make sure this is the correct variant!) + /// Panics in debug builds if the .variant() doesn't return {tag_name}. pub unsafe fn into_{tag_name}({self_for_into}) -> {payload_type_name} {{ - debug_assert_eq!(self.tag(), {discriminant_name}::{tag_name}); + debug_assert_eq!(self.variant(), {discriminant_name}::{tag_name}); {get_payload} }}"#, - )?; + ), + ); - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// Unsafely assume the given {name} has a .tag() of {tag_name} and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return {tag_name}. + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// Unsafely assume the given {name} has a .variant() of {tag_name} and return its payload. + /// (Always examine .variant() first to make sure this is the correct variant!) + /// Panics in debug builds if the .variant() doesn't return {tag_name}. pub unsafe fn as_{tag_name}(&self) -> {ref_if_needed}{payload_type_name} {{ - debug_assert_eq!(self.tag(), {discriminant_name}::{tag_name}); + debug_assert_eq!(self.variant(), {discriminant_name}::{tag_name}); {ref_if_needed}self.{tag_name} }}"#, - )?; + ), + ); } else { - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// A tag named {tag_name}, which has no payload. + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// A tag named {tag_name}, which has no payload. pub const {tag_name}: Self = unsafe {{ let mut bytes = [0; core::mem::size_of::<{name}>()]; @@ -419,29 +422,34 @@ fn write_tag_union( core::mem::transmute::<[u8; core::mem::size_of::<{name}>()], {name}>(bytes) }};"#, - )?; + ), + ); - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// Other `into_` methods return a payload, but since the {tag_name} tag + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// Other `into_` methods return a payload, but since the {tag_name} tag /// has no payload, this does nothing and is only here for completeness. pub fn into_{tag_name}(self) {{ () }}"#, - )?; + ), + ); - writeln!( - buf, - // Don't use indoc because this must be indented once! - r#" - /// Other `as` methods return a payload, but since the {tag_name} tag + add_decl( + impls, + opt_impl.clone(), + architecture, + format!( + r#"/// Other `as` methods return a payload, but since the {tag_name} tag /// has no payload, this does nothing and is only here for completeness. pub unsafe fn as_{tag_name}(&self) {{ () }}"#, - )?; + ), + ); } } } @@ -449,7 +457,7 @@ fn write_tag_union( // The Drop impl for the tag union { let opt_impl = Some(format!("impl Drop for {name}")); - let mut buf = "fn drop(&mut self) {".to_string(); + let mut buf = String::new(); write_impl_tags( 2, @@ -468,34 +476,39 @@ fn write_tag_union( } } }, - )?; + ); - writeln!( - buf, - r#" }} -}}"# - )?; + add_decl( + impls, + opt_impl, + architecture, + format!("fn drop(&mut self) {{\n{buf}{INDENT}}}"), + ); } // The PartialEq impl for the tag union { - writeln!( - buf, - r#" -impl PartialEq for {name} {{ - fn eq(&self, other: &Self) -> bool {{ - if self.tag() != other.tag() {{ - return false; - }} + let opt_impl_prefix = if typ.has_float(types) { + String::new() + } else { + format!("impl Eq for {name} {{}}\n\n") + }; + let opt_impl = Some(format!("{opt_impl_prefix}impl PartialEq for {name}")); + let mut buf = format!( + r#"fn eq(&self, other: &Self) -> bool {{ + if self.variant() != other.variant() {{ + return false; + }} - unsafe {{"# - )?; + unsafe {{ +"# + ); write_impl_tags( 3, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { format!("self.{tag_name} == other.{tag_name},") @@ -506,40 +519,36 @@ impl PartialEq for {name} {{ "true,".to_string() } }, - )?; + ); - writeln!( - buf, - r#" }} - }} -}}"# - )?; - } + buf.push_str(INDENT); + buf.push_str(INDENT); + buf.push_str("}\n"); + buf.push_str(INDENT); + buf.push('}'); - if !typ.has_float(types) { - writeln!(buf, "\nimpl Eq for {name} {{}}")?; + add_decl(impls, opt_impl, architecture, buf); } // The PartialOrd impl for the tag union { - writeln!( - buf, - r#" -impl PartialOrd for {name} {{ - fn partial_cmp(&self, other: &Self) -> Option {{ - match self.tag().partial_cmp(&other.tag()) {{ - Some(core::cmp::Ordering::Equal) => {{}} - not_eq => return not_eq, - }} + let opt_impl = Some(format!("impl PartialOrd for {name}")); + let mut buf = format!( + r#"fn partial_cmp(&self, other: &Self) -> Option {{ + match self.variant().partial_cmp(&other.variant()) {{ + Some(core::cmp::Ordering::Equal) => {{}} + not_eq => return not_eq, + }} - unsafe {{"# - )?; + unsafe {{ +"# + ); write_impl_tags( 3, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { format!("self.{tag_name}.partial_cmp(&other.{tag_name}),",) @@ -550,36 +559,36 @@ impl PartialOrd for {name} {{ "Some(core::cmp::Ordering::Equal),".to_string() } }, - )?; + ); - writeln!( - buf, - r#" }} - }} -}}"# - )?; + buf.push_str(INDENT); + buf.push_str(INDENT); + buf.push_str("}\n"); + buf.push_str(INDENT); + buf.push('}'); + + add_decl(impls, opt_impl, architecture, buf); } // The Ord impl for the tag union { - writeln!( - buf, - r#" -impl Ord for {name} {{ - fn cmp(&self, other: &Self) -> core::cmp::Ordering {{ - match self.tag().cmp(&other.tag()) {{ - core::cmp::Ordering::Equal => {{}} - not_eq => return not_eq, - }} + let opt_impl = Some(format!("impl Ord for {name}")); + let mut buf = format!( + r#"fn cmp(&self, other: &Self) -> core::cmp::Ordering {{ + match self.variant().cmp(&other.variant()) {{ + core::cmp::Ordering::Equal => {{}} + not_eq => return not_eq, + }} - unsafe {{"# - )?; + unsafe {{ +"# + ); write_impl_tags( 3, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { format!("self.{tag_name}.cmp(&other.{tag_name}),",) @@ -590,121 +599,116 @@ impl Ord for {name} {{ "core::cmp::Ordering::Equal,".to_string() } }, - )?; + ); - writeln!( - buf, - r#" }} - }} -}}"# - )?; + buf.push_str(INDENT); + buf.push_str(INDENT); + buf.push_str("}\n"); + buf.push_str(INDENT); + buf.push('}'); + + add_decl(impls, opt_impl, architecture, buf); } // The Clone impl for the tag union { - writeln!( - buf, - r#" -impl Clone for {name} {{ - fn clone(&self) -> Self {{ - let mut answer = unsafe {{"# - )?; + let opt_impl_prefix = if typ.has_pointer(types) { + String::new() + } else { + format!("impl Copy for {name} {{}}\n\n") + }; + let opt_impl = Some(format!("{opt_impl_prefix}impl Clone for {name}")); + let mut buf = format!( + r#"fn clone(&self) -> Self {{ + let mut answer = unsafe {{ +"# + ); write_impl_tags( 3, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { format!( r#"Self {{ - {tag_name}: self.{tag_name}.clone(), - }},"#, + {tag_name}: self.{tag_name}.clone(), + }},"#, ) } else { // when there's no payload, initialize to garbage memory. format!( r#"core::mem::transmute::< - core::mem::MaybeUninit<{name}>, - {name}, - >(core::mem::MaybeUninit::uninit()),"#, + core::mem::MaybeUninit<{name}>, + {name}, + >(core::mem::MaybeUninit::uninit()),"#, ) } }, - )?; + ); - writeln!( - buf, + buf.push_str(&format!( r#" }}; - answer.set_discriminant(self.tag()); + answer.set_discriminant(self.variant()); answer - }} -}}"# - )?; - } + }}"# + )); - if !typ.has_pointer(types) { - writeln!(buf, "impl Copy for {name} {{}}\n")?; + add_decl(impls, opt_impl, architecture, buf); } // The Hash impl for the tag union { - writeln!( - buf, - r#" -impl core::hash::Hash for {name} {{ - fn hash(&self, state: &mut H) {{"# - )?; + let opt_impl = Some(format!("impl core::hash::Hash for {name}")); + let mut buf = format!(r#"fn hash(&self, state: &mut H) {{"#); write_impl_tags( 2, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| { let hash_tag = format!("{discriminant_name}::{tag_name}.hash(state)"); if opt_payload_id.is_some() { format!( r#"unsafe {{ - {hash_tag}; - self.{tag_name}.hash(state); - }},"# + {hash_tag}; + self.{tag_name}.hash(state); + }},"# ) } else { format!("{},", hash_tag) } }, - )?; + ); - writeln!( - buf, - r#" }} -}}"# - )?; + buf.push_str(INDENT); + buf.push('}'); + + add_decl(impls, opt_impl, architecture, buf); } // The Debug impl for the tag union { - writeln!( - buf, - r#" -impl core::fmt::Debug for {name} {{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ - f.write_str("{name}::")?; + let opt_impl = Some(format!("impl core::fmt::Debug for {name}")); + let mut buf = format!( + r#"fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ + f.write_str("{name}::")?; - unsafe {{"# - )?; + unsafe {{ +"# + ); write_impl_tags( 3, tags.iter(), &discriminant_name, - buf, + &mut buf, |tag_name, opt_payload_id| match opt_payload_id { Some(payload_id) => { // If it's a ManuallyDrop, we need a `*` prefix to dereference it @@ -722,18 +726,16 @@ impl core::fmt::Debug for {name} {{ } None => format!(r#"f.write_str("{tag_name}"),"#), }, - )?; + ); - writeln!( - buf, - r#" }} - }} -}} -"# - )?; + buf.push_str(INDENT); + buf.push_str(INDENT); + buf.push_str("}\n"); + buf.push_str(INDENT); + buf.push('}'); + + add_decl(impls, opt_impl, architecture, buf); } - - Ok(()) } fn write_impl_tags< @@ -746,27 +748,27 @@ fn write_impl_tags< discriminant_name: &str, buf: &mut String, to_branch_str: F, -) -> fmt::Result { +) { write_indents(indentations, buf); - buf.push_str("match self.tag() {\n"); + buf.push_str("match self.variant() {\n"); for (tag_name, opt_payload_id) in tags { let branch_str = to_branch_str(tag_name, *opt_payload_id); write_indents(indentations + 1, buf); - writeln!(buf, "{discriminant_name}::{tag_name} => {branch_str}")?; + buf.push_str(&format!( + "{discriminant_name}::{tag_name} => {branch_str}\n" + )); } write_indents(indentations, buf); buf.push_str("}\n"); - - Ok(()) } -fn add_enumeration, S: AsRef + fmt::Display>( +fn add_enumeration, S: AsRef + Display>( name: &str, architecture: Architecture, typ: &RocType, @@ -832,13 +834,13 @@ fn add_struct( for (label, field_id) in fields { buf.push_str(&format!( - "{INDENT}pub {}: {},", + "{INDENT}pub {}: {},\n", label.as_str(), type_name(*field_id, types) )); } - buf.push_str("}"); + buf.push_str("}\n"); add_decl(impls, None, architecture, buf); } @@ -1005,11 +1007,11 @@ impl {name} {{ buf, // Don't use indoc because this must be indented once! r#" - /// Unsafely assume the given {name} has a .tag() of {non_null_tag} and convert it to {non_null_tag}'s payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return {non_null_tag}. + /// Unsafely assume the given {name} has a .variant() of {non_null_tag} and convert it to {non_null_tag}'s payload. + /// (Always examine .variant() first to make sure this is the correct variant!) + /// Panics in debug builds if the .variant() doesn't return {non_null_tag}. pub unsafe fn into_{non_null_tag}(self) -> {payload_type_name} {{ - debug_assert_eq!(self.tag(), {discriminant_name}::{non_null_tag}); + debug_assert_eq!(self.variant(), {discriminant_name}::{non_null_tag}); let payload = {assign_payload}; let align = core::mem::align_of::<{payload_type_name}>() as u32; @@ -1024,11 +1026,11 @@ impl {name} {{ buf, // Don't use indoc because this must be indented once! r#" - /// Unsafely assume the given {name} has a .tag() of {non_null_tag} and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return {non_null_tag}. + /// Unsafely assume the given {name} has a .variant() of {non_null_tag} and return its payload. + /// (Always examine .variant() first to make sure this is the correct variant!) + /// Panics in debug builds if the .variant() doesn't return {non_null_tag}. pub unsafe fn as_{non_null_tag}(&self) -> {ref_if_needed}{payload_type_name} {{ - debug_assert_eq!(self.tag(), {discriminant_name}::{non_null_tag}); + debug_assert_eq!(self.variant(), {discriminant_name}::{non_null_tag}); {ref_if_needed}*self.pointer }}"#, )?; diff --git a/bindgen/tests/fixtures/union-with-padding/src/lib.rs b/bindgen/tests/fixtures/union-with-padding/src/lib.rs index 8a7056aeda..408f574a4c 100644 --- a/bindgen/tests/fixtures/union-with-padding/src/lib.rs +++ b/bindgen/tests/fixtures/union-with-padding/src/lib.rs @@ -1,6 +1,6 @@ mod bindings; -use bindings::non_recursive::{NonRecursive, Tag}; +use bindings::NonRecursive; extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] From 282c2580c03ed5e2ec8f9b5a1402b42573e9a8aa Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 May 2022 21:48:40 -0400 Subject: [PATCH 06/13] Disable NulableUnwrapped bindgen for now. I'll re-enable it while writing tests for it. --- bindgen/src/bindgen_rs.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index f56a7b9edc..7be18322a3 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -129,13 +129,8 @@ fn add_type(architecture: Architecture, id: TypeId, types: &Types, impls: &mut I RocTagUnion::NullableWrapped { .. } => { todo!(); } - RocTagUnion::NullableUnwrapped { - name, - null_tag, - non_null_tag, - non_null_payload, - } => { - let todo_reminder = todo!(); + RocTagUnion::NullableUnwrapped { .. } => { + todo!(); // write_nullable_unwrapped( // name, // architecture, From 98a4ce231dd1767bd6c724b127fd06d8c447c38f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 23 May 2022 22:58:16 -0400 Subject: [PATCH 07/13] Use IndexMap in bindgen_rs --- Cargo.lock | 1 + bindgen/Cargo.toml | 1 + bindgen/src/bindgen_rs.rs | 6 +++--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd56adb04e..e01432b1ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3459,6 +3459,7 @@ dependencies = [ "cli_utils", "ctor", "dircpy", + "indexmap", "indoc", "pretty_assertions", "roc_builtins", diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index 87e389bcf9..3274ed76de 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -30,6 +30,7 @@ target-lexicon = "0.12.3" clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] } strum = "0.24.0" strum_macros = "0.24" +indexmap = "1.8.1" [dev-dependencies] pretty_assertions = "1.0.0" diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index 7be18322a3..011ba11596 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1,7 +1,7 @@ use crate::types::{RocTagUnion, RocType, TypeId, Types}; +use indexmap::IndexMap; use roc_mono::layout::UnionLayout; use roc_target::{Architecture, TargetInfo}; -use std::collections::hash_map::HashMap; use std::convert::TryInto; use std::fmt::Display; @@ -9,7 +9,7 @@ pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); const INDENT: &str = " "; -type Impls = HashMap>>; +type Impls = IndexMap>>; type Impl = Option; /// Add the given declaration body, along with the architecture, to the Impls. @@ -24,7 +24,7 @@ fn add_decl(impls: &mut Impls, opt_impl: Impl, architecture: Architecture, body: pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { let mut buf = String::new(); - let mut impls: Impls = HashMap::default(); + let mut impls: Impls = IndexMap::default(); for (architecture, types) in types_by_architecture.into_iter() { for id in types.sorted_ids() { From f56e07669c162fb43b40813e1743d35ca99d1132 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 10:49:14 -0400 Subject: [PATCH 08/13] Don't print an excess newline for structs --- bindgen/src/bindgen_rs.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index 011ba11596..ae45129b39 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -46,11 +46,11 @@ pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { } for (decl, architectures) in decls { - buf.push('\n'); - // If we're inside an `impl` block, indent the cfg annotation let indent = if has_impl { INDENT } else { "" }; + // Push a newline and potentially an indent before the #[cfg(...)] line + buf.push('\n'); buf.push_str(indent); match architectures.len() { @@ -75,12 +75,15 @@ pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { } } - buf.push('\n'); + buf.push('\n'); // newline after the #[cfg(...)] line + + // indent and print the decl (e.g. a `fn`), with a newline at the end buf.push_str(indent); buf.push_str(&decl); buf.push('\n'); } + // If this was an impl, it needs a closing brace at the end. if has_impl { buf.push_str("}\n"); } @@ -835,7 +838,7 @@ fn add_struct( )); } - buf.push_str("}\n"); + buf.push('}'); add_decl(impls, None, architecture, buf); } From 777625be21aefb28f10aef6cf966f48e1c10bdcc Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 10:49:36 -0400 Subject: [PATCH 09/13] Cover all architecture variations in gen_rs tests --- bindgen/tests/gen_rs.rs | 5 +++++ bindgen/tests/helpers/mod.rs | 14 +++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index 7521dcce92..cb6a1b8147 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -107,6 +107,11 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "arm" + ))] #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct R1 { diff --git a/bindgen/tests/helpers/mod.rs b/bindgen/tests/helpers/mod.rs index e9aad3c1cd..93624e6956 100644 --- a/bindgen/tests/helpers/mod.rs +++ b/bindgen/tests/helpers/mod.rs @@ -1,7 +1,6 @@ use roc_bindgen::bindgen_rs; use roc_bindgen::load::load_types; use roc_load::Threading; -use roc_target::Architecture; use std::env; use std::fs::File; use std::io::Write; @@ -26,9 +25,7 @@ pub fn generate_bindings(decl_src: &str) -> String { src.push_str(decl_src); - // Only use the X86_64 types for these tests - let target_arch = Architecture::X86_64; - let types = { + let pairs = { let dir = tempdir().expect("Unable to create tempdir"); let filename = PathBuf::from("Package-Config.roc"); let file_path = dir.path().join(filename); @@ -41,14 +38,9 @@ pub fn generate_bindings(decl_src: &str) -> String { dir.close().expect("Unable to close tempdir"); result.expect("had problems loading") - } - .iter() - .find(|(architecture, _)| *architecture == target_arch) - .unwrap() - .1 - .clone(); + }; - bindgen_rs::emit(&[(target_arch, types)]) + bindgen_rs::emit(&pairs) } #[allow(dead_code)] From fb5f30e5edd0404815d24f904cc0ebf1f09ec27f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 10:54:45 -0400 Subject: [PATCH 10/13] Rename Arm to Aarch32, fix some of its code gen As far as I can tell, Aarch32 only supports 32-bit registers, so its pointers should be 4 bytes and its F64 alignment should be 4 bytes as well (because it's emulated in software). --- bindgen/src/bindgen_rs.rs | 2 +- compiler/builtins/src/bitcode.rs | 9 +++------ compiler/roc_target/src/lib.rs | 8 ++++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index ae45129b39..4849f91ff0 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1134,7 +1134,7 @@ fn arch_to_str(architecture: &Architecture) -> &'static str { Architecture::X86_64 => "x86_64", Architecture::X86_32 => "x86", Architecture::Aarch64 => "aarch64", - Architecture::Arm => "arm", + Architecture::Aarch32 => "arm", Architecture::Wasm32 => "wasm32", } } diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 8862661791..308ba2eba9 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -60,11 +60,8 @@ impl FloatWidth { match self { F32 => 4, F64 | F128 => match target_info.architecture { - Architecture::X86_64 - | Architecture::Aarch64 - | Architecture::Arm - | Architecture::Wasm32 => 8, - Architecture::X86_32 => 4, + Architecture::X86_64 | Architecture::Aarch64 | Architecture::Wasm32 => 8, + Architecture::X86_32 | Architecture::Aarch32 => 4, }, } } @@ -129,7 +126,7 @@ impl IntWidth { U64 | I64 => match target_info.architecture { Architecture::X86_64 | Architecture::Aarch64 - | Architecture::Arm + | Architecture::Aarch32 | Architecture::Wasm32 => 8, Architecture::X86_32 => 4, }, diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs index 6b031f6756..1106a45805 100644 --- a/compiler/roc_target/src/lib.rs +++ b/compiler/roc_target/src/lib.rs @@ -70,7 +70,7 @@ pub enum Architecture { X86_64, X86_32, Aarch64, - Arm, + Aarch32, Wasm32, } @@ -79,8 +79,8 @@ impl Architecture { use Architecture::*; match self { - X86_64 | Aarch64 | Arm => PtrWidth::Bytes8, - X86_32 | Wasm32 => PtrWidth::Bytes4, + X86_64 | Aarch64 => PtrWidth::Bytes8, + X86_32 | Aarch32 | Wasm32 => PtrWidth::Bytes4, } } @@ -95,7 +95,7 @@ impl From for Architecture { target_lexicon::Architecture::X86_64 => Architecture::X86_64, target_lexicon::Architecture::X86_32(_) => Architecture::X86_32, target_lexicon::Architecture::Aarch64(_) => Architecture::Aarch64, - target_lexicon::Architecture::Arm(_) => Architecture::Arm, + target_lexicon::Architecture::Arm(_) => Architecture::Aarch32, target_lexicon::Architecture::Wasm32 => Architecture::Wasm32, _ => unreachable!("unsupported architecture"), } From c4288869d4c8665e22bb041f56538f3a6756ba5d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 10:49:58 -0400 Subject: [PATCH 11/13] Update gen_rs tests --- bindgen/tests/gen_rs.rs | 91 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index cb6a1b8147..caac40a5e9 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -27,6 +27,13 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct MyRcd { @@ -57,6 +64,10 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64" + ))] #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct Outer { @@ -65,12 +76,32 @@ mod test_gen_rs { pub x: Inner, } + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct Inner { pub b: f32, pub a: u16, } + + #[cfg(any( + target_arch = "x86", + target_arch = "arm", + target_arch = "wasm32" + ))] + #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] + #[repr(C)] + pub struct Outer { + pub x: Inner, + pub y: roc_std::RocStr, + pub z: roc_std::RocList, + } "# ) ); @@ -86,6 +117,13 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct R1 { @@ -109,8 +147,7 @@ mod test_gen_rs { r#" #[cfg(any( target_arch = "x86_64", - target_arch = "aarch64", - target_arch = "arm" + target_arch = "aarch64" ))] #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] @@ -120,12 +157,32 @@ mod test_gen_rs { pub x: R2, } + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct R2 { pub b: f32, pub a: u16, } + + #[cfg(any( + target_arch = "x86", + target_arch = "arm", + target_arch = "wasm32" + ))] + #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] + #[repr(C)] + pub struct R1 { + pub x: R2, + pub y: roc_std::RocStr, + pub z: roc_std::RocList, + } "# ) ); @@ -447,6 +504,13 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(u8)] pub enum Enumeration { @@ -486,12 +550,28 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "aarch64" + ))] #[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct UserId { pub f1: roc_std::RocStr, pub f0: u32, } + + #[cfg(any( + target_arch = "x86", + target_arch = "arm", + target_arch = "wasm32" + ))] + #[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] + #[repr(C)] + pub struct UserId { + pub f0: u32, + pub f1: roc_std::RocStr, + } "# ) ); @@ -514,6 +594,13 @@ mod test_gen_rs { .unwrap_or_default(), indoc!( r#" + #[cfg(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "arm", + target_arch = "wasm32" + ))] #[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(transparent)] pub struct UserId(roc_std::RocStr); From 9177ec1344b2f65022119e3ea1c1416fc4d61584 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 11:02:10 -0400 Subject: [PATCH 12/13] Drop some obsolete tests These aren't really adding value now that we have integration tests for them, and they are arduous to maintain. --- bindgen/tests/gen_rs.rs | 571 ---------------------------------------- 1 file changed, 571 deletions(-) diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index caac40a5e9..5642520e47 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -188,305 +188,6 @@ mod test_gen_rs { ); } - #[test] - fn tag_union_aliased() { - let module = indoc!( - r#" - NonRecursive : [Foo Str, Bar U128, Blah I32, Baz] - - main : NonRecursive - main = Foo "blah" - "# - ); - - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] - #[repr(u8)] - pub enum tag_NonRecursive { - Bar = 0, - Baz = 1, - Blah = 2, - Foo = 3, - } - - impl core::fmt::Debug for tag_NonRecursive { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Bar => f.write_str("tag_NonRecursive::Bar"), - Self::Baz => f.write_str("tag_NonRecursive::Baz"), - Self::Blah => f.write_str("tag_NonRecursive::Blah"), - Self::Foo => f.write_str("tag_NonRecursive::Foo"), - } - } - } - - #[repr(C)] - pub union NonRecursive { - Bar: roc_std::U128, - Blah: i32, - Foo: core::mem::ManuallyDrop, - _size_with_discriminant: [u8; 32], - } - - impl NonRecursive { - pub fn tag(&self) -> tag_NonRecursive { - unsafe { - let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); - - core::mem::transmute::(*bytes.as_ptr().add(24)) - } - } - - /// Internal helper - fn set_discriminant(&mut self, tag: tag_NonRecursive) { - let discriminant_ptr: *mut tag_NonRecursive = (self as *mut NonRecursive).cast(); - - unsafe { - *(discriminant_ptr.add(24)) = tag; - } - } - - /// Construct a tag named Bar, with the appropriate payload - pub fn Bar(payload: roc_std::U128) -> Self { - let mut answer = Self { - Bar: payload - }; - - answer.set_discriminant(tag_NonRecursive::Bar); - - answer - } - - /// Unsafely assume the given NonRecursive has a .tag() of Bar and convert it to Bar's payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Bar. - pub unsafe fn into_Bar(self) -> roc_std::U128 { - debug_assert_eq!(self.tag(), tag_NonRecursive::Bar); - self.Bar - } - - /// Unsafely assume the given NonRecursive has a .tag() of Bar and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Bar. - pub unsafe fn as_Bar(&self) -> roc_std::U128 { - debug_assert_eq!(self.tag(), tag_NonRecursive::Bar); - self.Bar - } - - /// A tag named Baz, which has no payload. - pub const Baz: Self = unsafe { - let mut bytes = [0; core::mem::size_of::()]; - - bytes[24] = tag_NonRecursive::Baz as u8; - - core::mem::transmute::<[u8; core::mem::size_of::()], NonRecursive>(bytes) - }; - - /// Other `into_` methods return a payload, but since the Baz tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_Baz(self) { - () - } - - /// Other `as` methods return a payload, but since the Baz tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Baz(&self) { - () - } - - /// Construct a tag named Blah, with the appropriate payload - pub fn Blah(payload: i32) -> Self { - let mut answer = Self { - Blah: payload - }; - - answer.set_discriminant(tag_NonRecursive::Blah); - - answer - } - - /// Unsafely assume the given NonRecursive has a .tag() of Blah and convert it to Blah's payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Blah. - pub unsafe fn into_Blah(self) -> i32 { - debug_assert_eq!(self.tag(), tag_NonRecursive::Blah); - self.Blah - } - - /// Unsafely assume the given NonRecursive has a .tag() of Blah and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Blah. - pub unsafe fn as_Blah(&self) -> i32 { - debug_assert_eq!(self.tag(), tag_NonRecursive::Blah); - self.Blah - } - - /// Construct a tag named Foo, with the appropriate payload - pub fn Foo(payload: roc_std::RocStr) -> Self { - let mut answer = Self { - Foo: core::mem::ManuallyDrop::new(payload) - }; - - answer.set_discriminant(tag_NonRecursive::Foo); - - answer - } - - /// Unsafely assume the given NonRecursive has a .tag() of Foo and convert it to Foo's payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Foo. - pub unsafe fn into_Foo(mut self) -> roc_std::RocStr { - debug_assert_eq!(self.tag(), tag_NonRecursive::Foo); - core::mem::ManuallyDrop::take(&mut self.Foo) - } - - /// Unsafely assume the given NonRecursive has a .tag() of Foo and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Foo. - pub unsafe fn as_Foo(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.tag(), tag_NonRecursive::Foo); - &self.Foo - } - } - - impl Drop for NonRecursive { - fn drop(&mut self) { - match self.tag() { - tag_NonRecursive::Bar => {} - tag_NonRecursive::Baz => {} - tag_NonRecursive::Blah => {} - tag_NonRecursive::Foo => unsafe { core::mem::ManuallyDrop::drop(&mut self.Foo) }, - } - } - } - - impl PartialEq for NonRecursive { - fn eq(&self, other: &Self) -> bool { - if self.tag() != other.tag() { - return false; - } - - unsafe { - match self.tag() { - tag_NonRecursive::Bar => self.Bar == other.Bar, - tag_NonRecursive::Baz => true, - tag_NonRecursive::Blah => self.Blah == other.Blah, - tag_NonRecursive::Foo => self.Foo == other.Foo, - } - } - } - } - - impl Eq for NonRecursive {} - - impl PartialOrd for NonRecursive { - fn partial_cmp(&self, other: &Self) -> Option { - match self.tag().partial_cmp(&other.tag()) { - Some(core::cmp::Ordering::Equal) => {} - not_eq => return not_eq, - } - - unsafe { - match self.tag() { - tag_NonRecursive::Bar => self.Bar.partial_cmp(&other.Bar), - tag_NonRecursive::Baz => Some(core::cmp::Ordering::Equal), - tag_NonRecursive::Blah => self.Blah.partial_cmp(&other.Blah), - tag_NonRecursive::Foo => self.Foo.partial_cmp(&other.Foo), - } - } - } - } - - impl Ord for NonRecursive { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.tag().cmp(&other.tag()) { - core::cmp::Ordering::Equal => {} - not_eq => return not_eq, - } - - unsafe { - match self.tag() { - tag_NonRecursive::Bar => self.Bar.cmp(&other.Bar), - tag_NonRecursive::Baz => core::cmp::Ordering::Equal, - tag_NonRecursive::Blah => self.Blah.cmp(&other.Blah), - tag_NonRecursive::Foo => self.Foo.cmp(&other.Foo), - } - } - } - } - - impl Clone for NonRecursive { - fn clone(&self) -> Self { - let mut answer = unsafe { - match self.tag() { - tag_NonRecursive::Bar => Self { - Bar: self.Bar.clone(), - }, - tag_NonRecursive::Baz => core::mem::transmute::< - core::mem::MaybeUninit, - NonRecursive, - >(core::mem::MaybeUninit::uninit()), - tag_NonRecursive::Blah => Self { - Blah: self.Blah.clone(), - }, - tag_NonRecursive::Foo => Self { - Foo: self.Foo.clone(), - }, - } - - }; - - answer.set_discriminant(self.tag()); - - answer - } - } - - impl core::hash::Hash for NonRecursive { - fn hash(&self, state: &mut H) { - match self.tag() { - tag_NonRecursive::Bar => unsafe { - tag_NonRecursive::Bar.hash(state); - self.Bar.hash(state); - }, - tag_NonRecursive::Baz => tag_NonRecursive::Baz.hash(state), - tag_NonRecursive::Blah => unsafe { - tag_NonRecursive::Blah.hash(state); - self.Blah.hash(state); - }, - tag_NonRecursive::Foo => unsafe { - tag_NonRecursive::Foo.hash(state); - self.Foo.hash(state); - }, - } - } - } - - impl core::fmt::Debug for NonRecursive { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str("NonRecursive::")?; - - unsafe { - match self.tag() { - tag_NonRecursive::Bar => f.debug_tuple("Bar").field(&self.Bar).finish(), - tag_NonRecursive::Baz => f.write_str("Baz"), - tag_NonRecursive::Blah => f.debug_tuple("Blah").field(&self.Blah).finish(), - tag_NonRecursive::Foo => f.debug_tuple("Foo").field(&*self.Foo).finish(), - } - } - } - } - - "# - ) - ); - } - #[test] fn tag_union_enumeration() { let module = indoc!( @@ -608,276 +309,4 @@ mod test_gen_rs { ) ); } - - #[test] - fn cons_list_of_strings() { - let module = indoc!( - r#" - StrConsList : [Nil, Cons Str StrConsList] - - main : StrConsList - main = Cons "Hello, " (Cons "World!" Nil) - "# - ); - - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] - #[repr(u8)] - pub enum tag_StrConsList { - Cons = 0, - Nil = 1, - } - - impl core::fmt::Debug for tag_StrConsList { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Cons => f.write_str("tag_StrConsList::Cons"), - Self::Nil => f.write_str("tag_StrConsList::Nil"), - } - } - } - - #[derive(Clone, Eq, Ord, Hash, PartialEq, PartialOrd)] - #[repr(C)] - pub struct StrConsList { - pointer: *mut core::mem::ManuallyDrop, - } - - impl StrConsList { - pub fn tag(&self) -> tag_StrConsList { - if self.pointer.is_null() { - tag_StrConsList::Nil - } else { - tag_StrConsList::Cons - } - } - - /// Construct a tag named Cons, with the appropriate payload - pub fn Cons(payload: roc_std::RocStr) -> Self { - let size = core::mem::size_of::(); - let align = core::mem::align_of::(); - - unsafe { - let pointer = crate::roc_alloc(size, align as u32) as *mut core::mem::ManuallyDrop; - - *pointer = core::mem::ManuallyDrop::new(payload); - - Self { pointer } - } - } - - /// Unsafely assume the given StrConsList has a .tag() of Cons and convert it to Cons's payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Cons. - pub unsafe fn into_Cons(self) -> roc_std::RocStr { - debug_assert_eq!(self.tag(), tag_StrConsList::Cons); - - let payload = core::mem::ManuallyDrop::take(&mut *self.pointer); - let align = core::mem::align_of::() as u32; - - crate::roc_dealloc(self.pointer as *mut core::ffi::c_void, align); - - payload - } - - /// Unsafely assume the given StrConsList has a .tag() of Cons and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Cons. - pub unsafe fn as_Cons(&self) -> &roc_std::RocStr { - debug_assert_eq!(self.tag(), tag_StrConsList::Cons); - &*self.pointer - } - - /// Construct a tag named Nil - pub fn Nil() -> Self { - Self { - pointer: core::ptr::null_mut(), - } - } - - /// Other `into_` methods return a payload, but since the Nil tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_Nil(self) { - () - } - - /// Other `as` methods return a payload, but since the Nil tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Nil(&self) { - () - } - } - - impl Drop for StrConsList { - fn drop(&mut self) { - if !self.pointer.is_null() { - let payload = unsafe { &*self.pointer }; - let align = core::mem::align_of::() as u32; - - unsafe { - crate::roc_dealloc(self.pointer as *mut core::ffi::c_void, align); - } - - drop(payload); - } - } - } - - impl core::fmt::Debug for StrConsList { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if self.pointer.is_null() { - f.write_str("StrConsList::Nil") - } else { - f.write_str("StrConsList::")?; - - unsafe { f.debug_tuple("Cons").field(&**self.pointer).finish() } - } - } - } - - "# - ) - ); - } - - #[test] - fn cons_list_of_ints() { - let module = indoc!( - r#" - IntConsList : [Empty, Prepend U16 IntConsList] - - main : IntConsList - main = Prepend 42 (Prepend 26 Empty) - "# - ); - - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] - #[repr(u8)] - pub enum tag_IntConsList { - Empty = 0, - Prepend = 1, - } - - impl core::fmt::Debug for tag_IntConsList { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Empty => f.write_str("tag_IntConsList::Empty"), - Self::Prepend => f.write_str("tag_IntConsList::Prepend"), - } - } - } - - #[derive(Clone, Eq, Ord, Hash, PartialEq, PartialOrd)] - #[repr(C)] - pub struct IntConsList { - pointer: *mut u16, - } - - impl IntConsList { - pub fn tag(&self) -> tag_IntConsList { - if self.pointer.is_null() { - tag_IntConsList::Empty - } else { - tag_IntConsList::Prepend - } - } - - /// Construct a tag named Prepend, with the appropriate payload - pub fn Prepend(payload: u16) -> Self { - let size = core::mem::size_of::(); - let align = core::mem::align_of::(); - - unsafe { - let pointer = crate::roc_alloc(size, align as u32) as *mut u16; - - *pointer = payload; - - Self { pointer } - } - } - - /// Unsafely assume the given IntConsList has a .tag() of Prepend and convert it to Prepend's payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Prepend. - pub unsafe fn into_Prepend(self) -> u16 { - debug_assert_eq!(self.tag(), tag_IntConsList::Prepend); - - let payload = *self.pointer; - let align = core::mem::align_of::() as u32; - - crate::roc_dealloc(self.pointer as *mut core::ffi::c_void, align); - - payload - } - - /// Unsafely assume the given IntConsList has a .tag() of Prepend and return its payload. - /// (Always examine .tag() first to make sure this is the correct variant!) - /// Panics in debug builds if the .tag() doesn't return Prepend. - pub unsafe fn as_Prepend(&self) -> u16 { - debug_assert_eq!(self.tag(), tag_IntConsList::Prepend); - *self.pointer - } - - /// Construct a tag named Empty - pub fn Empty() -> Self { - Self { - pointer: core::ptr::null_mut(), - } - } - - /// Other `into_` methods return a payload, but since the Empty tag - /// has no payload, this does nothing and is only here for completeness. - pub fn into_Empty(self) { - () - } - - /// Other `as` methods return a payload, but since the Empty tag - /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Empty(&self) { - () - } - } - - impl Drop for IntConsList { - fn drop(&mut self) { - if !self.pointer.is_null() { - let payload = unsafe { &*self.pointer }; - let align = core::mem::align_of::() as u32; - - unsafe { - crate::roc_dealloc(self.pointer as *mut core::ffi::c_void, align); - } - - drop(payload); - } - } - } - - impl core::fmt::Debug for IntConsList { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if self.pointer.is_null() { - f.write_str("IntConsList::Empty") - } else { - f.write_str("IntConsList::")?; - - unsafe { f.debug_tuple("Prepend").field(&*self.pointer).finish() } - } - } - } - - "# - ) - ); - } } From 826c8220909e3741deae13406ed297844d85b68a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 24 May 2022 12:11:52 -0400 Subject: [PATCH 13/13] c l i p p y --- bindgen/src/bindgen_rs.rs | 56 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index 4849f91ff0..f1794f8066 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -26,7 +26,7 @@ pub fn emit(types_by_architecture: &[(Architecture, Types)]) -> String { let mut buf = String::new(); let mut impls: Impls = IndexMap::default(); - for (architecture, types) in types_by_architecture.into_iter() { + for (architecture, types) in types_by_architecture.iter() { for id in types.sorted_ids() { add_type(*architecture, id, types, &mut impls); } @@ -492,15 +492,14 @@ fn add_tag_union( format!("impl Eq for {name} {{}}\n\n") }; let opt_impl = Some(format!("{opt_impl_prefix}impl PartialEq for {name}")); - let mut buf = format!( - r#"fn eq(&self, other: &Self) -> bool {{ - if self.variant() != other.variant() {{ + let mut buf = r#"fn eq(&self, other: &Self) -> bool { + if self.variant() != other.variant() { return false; - }} + } - unsafe {{ + unsafe { "# - ); + .to_string(); write_impl_tags( 3, @@ -531,16 +530,15 @@ fn add_tag_union( // The PartialOrd impl for the tag union { let opt_impl = Some(format!("impl PartialOrd for {name}")); - let mut buf = format!( - r#"fn partial_cmp(&self, other: &Self) -> Option {{ - match self.variant().partial_cmp(&other.variant()) {{ - Some(core::cmp::Ordering::Equal) => {{}} + let mut buf = r#"fn partial_cmp(&self, other: &Self) -> Option { + match self.variant().partial_cmp(&other.variant()) { + Some(core::cmp::Ordering::Equal) => {} not_eq => return not_eq, - }} + } - unsafe {{ + unsafe { "# - ); + .to_string(); write_impl_tags( 3, @@ -571,16 +569,15 @@ fn add_tag_union( // The Ord impl for the tag union { let opt_impl = Some(format!("impl Ord for {name}")); - let mut buf = format!( - r#"fn cmp(&self, other: &Self) -> core::cmp::Ordering {{ - match self.variant().cmp(&other.variant()) {{ - core::cmp::Ordering::Equal => {{}} + let mut buf = r#"fn cmp(&self, other: &Self) -> core::cmp::Ordering { + match self.variant().cmp(&other.variant()) { + core::cmp::Ordering::Equal => {} not_eq => return not_eq, - }} + } - unsafe {{ + unsafe { "# - ); + .to_string(); write_impl_tags( 3, @@ -616,11 +613,10 @@ fn add_tag_union( format!("impl Copy for {name} {{}}\n\n") }; let opt_impl = Some(format!("{opt_impl_prefix}impl Clone for {name}")); - let mut buf = format!( - r#"fn clone(&self) -> Self {{ - let mut answer = unsafe {{ + let mut buf = r#"fn clone(&self) -> Self { + let mut answer = unsafe { "# - ); + .to_string(); write_impl_tags( 3, @@ -646,15 +642,15 @@ fn add_tag_union( }, ); - buf.push_str(&format!( + buf.push_str( r#" - }}; + }; answer.set_discriminant(self.variant()); answer - }}"# - )); + }"#, + ); add_decl(impls, opt_impl, architecture, buf); } @@ -662,7 +658,7 @@ fn add_tag_union( // The Hash impl for the tag union { let opt_impl = Some(format!("impl core::hash::Hash for {name}")); - let mut buf = format!(r#"fn hash(&self, state: &mut H) {{"#); + let mut buf = r#"fn hash(&self, state: &mut H) {"#.to_string(); write_impl_tags( 2,