diff --git a/stage0/Cargo.toml b/stage0/Cargo.toml index 2ace2d5..af0cafd 100644 --- a/stage0/Cargo.toml +++ b/stage0/Cargo.toml @@ -9,3 +9,4 @@ clap = { version = "4.4", features = ["cargo"] } serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" toml = "0.8" +uuid = "1.4" diff --git a/stage0/src/codegen/mod.rs b/stage0/src/codegen/mod.rs index 4f508f4..dc9bf73 100644 --- a/stage0/src/codegen/mod.rs +++ b/stage0/src/codegen/mod.rs @@ -12,7 +12,9 @@ use crate::ffi::{ llvm_target_lookup, }; use crate::lexer::SyntaxError; -use crate::pkg::{ExportedType, OperatingSystem, PackageMeta, PackageName, PackageVersion, Target}; +use crate::pkg::{ + ExportedType, PackageMeta, PackageName, PackageVersion, PrimitiveTarget, TargetOs, +}; use std::error::Error; use std::ffi::{CStr, CString}; use std::fmt::{Display, Formatter, Write}; @@ -34,7 +36,7 @@ pub struct Codegen<'a> { machine: *mut crate::ffi::LlvmMachine, pkg: &'a PackageName, version: &'a PackageVersion, - target: &'a Target, + target: &'static PrimitiveTarget, namespace: &'a str, resolver: &'a TypeResolver<'a>, } @@ -43,11 +45,11 @@ impl<'a> Codegen<'a> { pub fn new( pkg: &'a PackageName, version: &'a PackageVersion, - target: &'a Target, + target: &'static PrimitiveTarget, resolver: &'a TypeResolver<'a>, ) -> Self { // Get LLVM target. - let triple = CString::new(target.to_llvm()).unwrap(); + let triple = CString::new(target.to_string()).unwrap(); let llvm = { let mut err = String::new(); let ptr = unsafe { llvm_target_lookup(triple.as_ptr(), &mut err) }; @@ -104,17 +106,13 @@ impl<'a> Codegen<'a> { Some(Expression::NotEqual(f, s)) => (false, f.span() + s.span()), Some(Expression::Equal(f, s)) => (true, f.span() + s.span()), Some(e) => return Err(SyntaxError::new(e.span(), "unsupported expression")), - None => match lhs.value() { - "unix" => match os { - OperatingSystem::Darwin | OperatingSystem::Linux => return Ok(true), - OperatingSystem::Win32 => return Ok(false), - }, - "win32" => match os { - OperatingSystem::Darwin | OperatingSystem::Linux => return Ok(false), - OperatingSystem::Win32 => return Ok(true), - }, - _ => return Err(SyntaxError::new(lhs.span().clone(), "unknown argument")), - }, + None => { + return Ok(if lhs.value() == "unix" { + os.is_unix() + } else { + lhs.value() == os.name() + }) + } }; // Check if first expression is "os". @@ -131,21 +129,9 @@ impl<'a> Codegen<'a> { // Compare. let res = if equal { - match rhs.value() { - "windows" => match os { - OperatingSystem::Darwin | OperatingSystem::Linux => false, - OperatingSystem::Win32 => true, - }, - _ => todo!(), - } + rhs.value() == os.name() } else { - match rhs.value() { - "windows" => match os { - OperatingSystem::Darwin | OperatingSystem::Linux => true, - OperatingSystem::Win32 => false, - }, - _ => todo!(), - } + rhs.value() != os.name() }; if expr.next().is_some() { @@ -270,7 +256,7 @@ impl<'a> Codegen<'a> { pub fn build>(self, file: F, exe: bool) -> Result<(), BuildError> { // Generate DllMain for DLL on Windows. - if self.target.os() == OperatingSystem::Win32 && !exe { + if self.target.os() == TargetOs::Win32 && !exe { self.build_dll_main()?; } diff --git a/stage0/src/pkg/mod.rs b/stage0/src/pkg/mod.rs index 4917e2a..aca6ed4 100644 --- a/stage0/src/pkg/mod.rs +++ b/stage0/src/pkg/mod.rs @@ -26,10 +26,11 @@ pub struct Package { } impl Package { - const META_END: u8 = 0; - const META_NAME: u8 = 1; - const META_VERSION: u8 = 2; - const META_DATE: u8 = 3; + const ENTRY_END: u8 = 0; + const ENTRY_NAME: u8 = 1; + const ENTRY_VERSION: u8 = 2; + const ENTRY_DATE: u8 = 3; + const ENTRY_LIB: u8 = 4; pub fn open>(path: P) -> Result { todo!() @@ -54,35 +55,32 @@ impl Package { }; // Write file magic. - file.write_all(b"\x7FNPK") - .map_err(|e| PackagePackError::WriteFailed(e))?; + file.write_all(b"\x7FNPK")?; // Write package name. let meta = &self.meta; - file.write_all(&[Self::META_NAME]).unwrap(); - file.write_all(&meta.name().to_bin()).unwrap(); + file.write_all(&[Self::ENTRY_NAME])?; + file.write_all(&meta.name().to_bin())?; // Write package version. - file.write_all(&[Self::META_VERSION]).unwrap(); - file.write_all(&meta.version().to_bin().to_be_bytes()) - .unwrap(); + file.write_all(&[Self::ENTRY_VERSION])?; + file.write_all(&meta.version().to_bin().to_be_bytes())?; // Write created date. let date = SystemTime::now(); - file.write_all(&[Self::META_DATE]).unwrap(); + file.write_all(&[Self::ENTRY_DATE])?; file.write_all( &date .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs() .to_be_bytes(), - ) - .unwrap(); + )?; // End of meta data. - file.write_all(&[Self::META_END]).unwrap(); + file.write_all(&[Self::ENTRY_END])?; Ok(()) } @@ -117,6 +115,12 @@ pub enum PackagePackError { WriteFailed(#[source] std::io::Error), } +impl From for PackagePackError { + fn from(value: std::io::Error) -> Self { + Self::WriteFailed(value) + } +} + /// Represents an error when a package is failed to export. #[derive(Debug, Error)] pub enum PackageExportError {} diff --git a/stage0/src/pkg/target.rs b/stage0/src/pkg/target.rs index 5f6946b..5418b8b 100644 --- a/stage0/src/pkg/target.rs +++ b/stage0/src/pkg/target.rs @@ -1,111 +1,223 @@ +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::rc::Rc; +use thiserror::Error; +use uuid::{uuid, Uuid}; + +/// A struct to resolve primitive target. +pub struct TargetResolver {} + +impl TargetResolver { + pub fn new() -> Self { + Self {} + } + + pub fn resolve( + &mut self, + target: &Target, + ) -> Result<&'static PrimitiveTarget, TargetResolveError> { + match target { + Target::Primitive(v) => Ok(v), + Target::Custom(_) => todo!(), + } + } +} + /// Output target of the code. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct Target { - arch: Architecture, - vendor: Vendor, - os: OperatingSystem, - env: Option, +#[derive(Debug, Clone)] +pub enum Target { + Primitive(&'static PrimitiveTarget), + Custom(Rc), } impl Target { - pub const SUPPORTED_TARGETS: [Target; 4] = [ - Target { - arch: Architecture::Aarch64, - vendor: Vendor::Apple, - os: OperatingSystem::Darwin, - env: None, + pub fn id(&self) -> &Uuid { + match self { + Self::Primitive(v) => &v.id, + Self::Custom(v) => &v.id, + } + } +} + +impl PartialEq for Target { + fn eq(&self, other: &Self) -> bool { + self.id() == other.id() + } +} + +impl Eq for Target {} + +impl Hash for Target { + fn hash(&self, state: &mut H) { + self.id().hash(state); + } +} + +impl Display for Target { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Primitive(v) => v.fmt(f), + Self::Custom(v) => v.fmt(f), + } + } +} + +/// Contains data for a primitive target. +#[derive(Debug)] +pub struct PrimitiveTarget { + id: Uuid, + arch: TargetArch, + vendor: TargetVendor, + os: TargetOs, + env: Option, +} + +impl PrimitiveTarget { + pub const ALL: [Self; 4] = [ + Self { + id: uuid!("df56f1f4-8bee-4814-b6a7-e8b21ff72669"), + arch: TargetArch::X86_64, + vendor: TargetVendor::Unknown, + os: TargetOs::Linux, + env: Some(TargetEnv::Gnu), }, - Target { - arch: Architecture::X86_64, - vendor: Vendor::Apple, - os: OperatingSystem::Darwin, + Self { + id: uuid!("27155b2c-a146-4c8a-b591-73aad7efb336"), + arch: TargetArch::AArch64, + vendor: TargetVendor::Apple, + os: TargetOs::Darwin, env: None, }, - Target { - arch: Architecture::X86_64, - vendor: Vendor::Pc, - os: OperatingSystem::Win32, - env: Some(Environment::Msvc), + Self { + id: uuid!("99e919be-e464-4e6a-a604-0242e8b751b9"), + arch: TargetArch::X86_64, + vendor: TargetVendor::Apple, + os: TargetOs::Darwin, + env: None, }, - Target { - arch: Architecture::X86_64, - vendor: Vendor::Unknown, - os: OperatingSystem::Linux, - env: Some(Environment::Gnu), + Self { + id: uuid!("69d6f6e5-dc4c-408d-acb8-b2a64db28b8b"), + arch: TargetArch::X86_64, + vendor: TargetVendor::Pc, + os: TargetOs::Win32, + env: Some(TargetEnv::Msvc), }, ]; - pub fn arch(&self) -> Architecture { + pub fn arch(&self) -> TargetArch { self.arch } - pub fn os(&self) -> OperatingSystem { + pub fn os(&self) -> TargetOs { self.os } +} - pub fn to_llvm(&self) -> String { - let mut buf = String::with_capacity(64); - - buf.push_str(self.arch.name()); +impl Display for PrimitiveTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(self.arch.name())?; + f.write_str("-")?; + f.write_str(self.vendor.name())?; + f.write_str("-")?; + f.write_str(self.os.name())?; - match self.vendor { - Vendor::Apple => buf.push_str("-apple"), - Vendor::Pc => buf.push_str("-pc"), - Vendor::Unknown => buf.push_str("-unknown"), + if let Some(env) = &self.env { + f.write_str("-")?; + f.write_str(env.name())?; } - match self.os { - OperatingSystem::Darwin => buf.push_str("-darwin"), - OperatingSystem::Linux => buf.push_str("-linux"), - OperatingSystem::Win32 => buf.push_str("-win32"), - } + Ok(()) + } +} - if let Some(env) = self.env { - match env { - Environment::Gnu => buf.push_str("-gnu"), - Environment::Msvc => buf.push_str("-msvc"), - } - } +/// Contains data for a custom target. +#[derive(Debug)] +pub struct CustomTarget { + id: Uuid, + parent: Uuid, +} - buf +impl Display for CustomTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.id.fmt(f) } } /// Architecture CPU of the target. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum Architecture { - Aarch64, +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TargetArch { + AArch64, X86_64, } -impl Architecture { +impl TargetArch { pub fn name(self) -> &'static str { match self { - Architecture::Aarch64 => "aarch64", - Architecture::X86_64 => "x86_64", + Self::AArch64 => "aarch64", + Self::X86_64 => "x86_64", } } } /// Vendor of the target. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum Vendor { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TargetVendor { Apple, Pc, Unknown, } +impl TargetVendor { + pub fn name(self) -> &'static str { + match self { + Self::Apple => "apple", + Self::Pc => "pc", + Self::Unknown => "unknown", + } + } +} + /// OS of the target. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum OperatingSystem { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TargetOs { Darwin, Linux, Win32, } +impl TargetOs { + pub fn name(self) -> &'static str { + match self { + Self::Darwin => "darwin", + Self::Linux => "linux", + Self::Win32 => "win32", + } + } + + pub fn is_unix(self) -> bool { + match self { + Self::Darwin | Self::Linux => true, + Self::Win32 => false, + } + } +} + /// Environment of the target. -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -pub enum Environment { +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TargetEnv { Gnu, Msvc, } + +impl TargetEnv { + pub fn name(self) -> &'static str { + match self { + Self::Gnu => "gnu", + Self::Msvc => "msvc", + } + } +} + +/// Represents an error when [`TargetResolver`] is failed. +#[derive(Debug, Error)] +pub enum TargetResolveError {} diff --git a/stage0/src/project/mod.rs b/stage0/src/project/mod.rs index 2544a32..3599c9a 100644 --- a/stage0/src/project/mod.rs +++ b/stage0/src/project/mod.rs @@ -5,8 +5,8 @@ use crate::codegen::{BuildError, Codegen, TypeResolver}; use crate::dep::DepResolver; use crate::lexer::SyntaxError; use crate::pkg::{ - Architecture, ExportedFunc, ExportedType, FunctionParam, Library, OperatingSystem, Package, - PackageMeta, Target, Type, + ExportedFunc, ExportedType, FunctionParam, Library, Package, PackageMeta, PrimitiveTarget, + Target, TargetArch, TargetOs, TargetResolveError, TargetResolver, Type, }; use std::borrow::Cow; use std::collections::{HashMap, VecDeque}; @@ -112,16 +112,17 @@ impl<'a> Project<'a> { pub fn build(&self) -> Result { // Setup type resolver. - let mut resolver = TypeResolver::new(); + let mut types = TypeResolver::new(); - resolver.populate_project_types(&self.sources); + types.populate_project_types(&self.sources); // Build. + let mut targets = TargetResolver::new(); let mut exes = HashMap::new(); let mut libs = HashMap::new(); - for target in &Target::SUPPORTED_TARGETS { - let bins = self.build_for(target, &resolver)?; + for target in PrimitiveTarget::ALL.iter().map(|t| Target::Primitive(t)) { + let bins = self.build_for(&target, &mut types, &mut targets)?; if let Some(exe) = bins.exe { assert!(exes.insert(target.clone(), exe).is_none()); @@ -180,11 +181,18 @@ impl<'a> Project<'a> { fn build_for( &self, target: &Target, - resolver: &TypeResolver<'_>, + types: &mut TypeResolver<'_>, + targets: &mut TargetResolver, ) -> Result { + // Get primitive target. + let pt = match targets.resolve(target) { + Ok(v) => v, + Err(e) => return Err(ProjectBuildError::ResolveTargetFailed(target.clone(), e)), + }; + // Setup codegen context. let pkg = self.meta.package(); - let mut cx = Codegen::new(pkg.name(), pkg.version(), &target, resolver); + let mut cx = Codegen::new(pkg.name(), pkg.version(), pt, types); // Enumerate the sources. let mut lib = Library::new(); @@ -271,7 +279,7 @@ impl<'a> Project<'a> { // Create output directory. let mut dir = self.artifacts(); - dir.push(target.to_llvm()); + dir.push(target.to_string()); if let Err(e) = create_dir_all(&dir) { return Err(ProjectBuildError::CreateDirectoryFailed(dir, e)); @@ -286,20 +294,20 @@ impl<'a> Project<'a> { // Prepare to link. let mut args: Vec> = Vec::new(); - let out = dir.join(match target.os() { - OperatingSystem::Darwin => format!("lib{}.dylib", pkg.name()), - OperatingSystem::Linux => format!("lib{}.so", pkg.name()), - OperatingSystem::Win32 => format!("{}.dll", pkg.name()), + let out = dir.join(match pt.os() { + TargetOs::Darwin => format!("lib{}.dylib", pkg.name()), + TargetOs::Linux => format!("lib{}.so", pkg.name()), + TargetOs::Win32 => format!("{}.dll", pkg.name()), }); - let linker = match target.os() { - OperatingSystem::Darwin => { + let linker = match pt.os() { + TargetOs::Darwin => { args.push("-o".into()); args.push(out.to_str().unwrap().to_owned().into()); args.push("-arch".into()); - args.push(match target.arch() { - Architecture::Aarch64 => "arm64".into(), - Architecture::X86_64 => "x86_64".into(), + args.push(match pt.arch() { + TargetArch::AArch64 => "arm64".into(), + TargetArch::X86_64 => "x86_64".into(), }); args.push("-platform_version".into()); args.push("macos".into()); @@ -308,13 +316,13 @@ impl<'a> Project<'a> { args.push("-dylib".into()); "ld64.lld" } - OperatingSystem::Linux => { + TargetOs::Linux => { args.push("-o".into()); args.push(out.to_str().unwrap().to_owned().into()); args.push("--shared".into()); "ld.lld" } - OperatingSystem::Win32 => { + TargetOs::Win32 => { let def = dir.join(format!("{}.def", pkg.name())); if let Err(e) = lib.write_module_definition(pkg.name(), pkg.version(), &def) { @@ -411,6 +419,9 @@ pub enum ProjectLoadError { /// Represents an error when a [`Project`] is failed to build. #[derive(Debug, Error)] pub enum ProjectBuildError { + #[error("cannot resolve end target of {0}")] + ResolveTargetFailed(Target, #[source] TargetResolveError), + #[error("invalid syntax in {0}")] InvalidSyntax(PathBuf, #[source] SyntaxError), diff --git a/std/Allocator.nt b/std/Allocator.nt index 34c4251..cf18309 100644 --- a/std/Allocator.nt +++ b/std/Allocator.nt @@ -11,10 +11,10 @@ impl Allocator { let ptr = _aligned_malloc(size, align); if ptr == null { - @if(os != "windows") + @if(os != "win32") abort(); - @if(os == "windows") + @if(os == "win32") asm("int 0x29", in("ecx") 7, out(!) _); }