forked from libre-chip/fayalite
WIP adding FPGA support
This commit is contained in:
parent
c06ef56482
commit
32f713f849
3 changed files with 1021 additions and 0 deletions
817
crates/fayalite/src/build.rs
Normal file
817
crates/fayalite/src/build.rs
Normal file
|
|
@ -0,0 +1,817 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{
|
||||
bundle::Bundle,
|
||||
intern::Interned,
|
||||
module::Module,
|
||||
util::{HashMap, HashSet, job_server::AcquiredJob},
|
||||
};
|
||||
use hashbrown::hash_map::Entry;
|
||||
use petgraph::{
|
||||
algo::{DfsSpace, kosaraju_scc, toposort},
|
||||
graph::DiGraph,
|
||||
visit::{GraphBase, Visitable},
|
||||
};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::SerializeSeq};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::BTreeSet,
|
||||
fmt::{self, Write},
|
||||
hash::{Hash, Hasher},
|
||||
iter, mem,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
|
||||
pub enum JobItem {
|
||||
Module { value: Module<Bundle> },
|
||||
File { path: Interned<str> },
|
||||
}
|
||||
|
||||
impl JobItem {
|
||||
pub fn name(&self) -> JobItemName {
|
||||
match self {
|
||||
JobItem::Module { value } => JobItemName::Module { name: value.name() },
|
||||
&JobItem::File { path } => JobItemName::File { path },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
|
||||
pub enum JobItemName {
|
||||
Module { name: Interned<str> },
|
||||
File { path: Interned<str> },
|
||||
}
|
||||
|
||||
pub struct CommandLine {}
|
||||
|
||||
pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug {
|
||||
type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug;
|
||||
fn inputs(&self, job: &Self::Job) -> Interned<[JobItemName]>;
|
||||
fn outputs(&self, job: &Self::Job) -> Interned<[JobItemName]>;
|
||||
fn to_command_line(&self, job: &Self::Job) -> Interned<[Interned<str>]>;
|
||||
fn parse_command_line(
|
||||
&self,
|
||||
command_line: Interned<[Interned<str>]>,
|
||||
) -> clap::error::Result<Self::Job>;
|
||||
fn run(
|
||||
&self,
|
||||
job: &Self::Job,
|
||||
inputs: &[JobItem],
|
||||
acquired_job: &mut AcquiredJob,
|
||||
) -> eyre::Result<Vec<JobItem>>;
|
||||
}
|
||||
|
||||
trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
|
||||
fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool;
|
||||
fn hash_dyn(&self, state: &mut dyn Hasher);
|
||||
fn parse_command_line_dyn(
|
||||
self: Arc<Self>,
|
||||
command_line: Interned<[Interned<str>]>,
|
||||
) -> clap::error::Result<DynJob>;
|
||||
}
|
||||
|
||||
impl<T: JobKind> DynJobKindTrait for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_arc_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
|
||||
self
|
||||
}
|
||||
|
||||
fn eq_dyn(&self, other: &dyn DynJobKindTrait) -> bool {
|
||||
other
|
||||
.as_any()
|
||||
.downcast_ref::<T>()
|
||||
.is_some_and(|other| self == other)
|
||||
}
|
||||
|
||||
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
|
||||
self.hash(&mut state)
|
||||
}
|
||||
|
||||
fn parse_command_line_dyn(
|
||||
self: std::sync::Arc<T>,
|
||||
command_line: Interned<[Interned<str>]>,
|
||||
) -> clap::error::Result<DynJob> {
|
||||
let job = self.parse_command_line(command_line)?;
|
||||
let inputs = self.inputs(&job);
|
||||
let outputs = self.outputs(&job);
|
||||
Ok(DynJob(Arc::new(inner::DynJob {
|
||||
kind: self,
|
||||
job,
|
||||
inputs,
|
||||
outputs,
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynJobKind(Arc<dyn DynJobKindTrait>);
|
||||
|
||||
impl DynJobKind {
|
||||
pub fn from_arc<T: JobKind>(job_kind: Arc<T>) -> Self {
|
||||
if TypeId::of::<T>() == TypeId::of::<Self>() {
|
||||
Self::clone(
|
||||
&Arc::downcast::<Self>(job_kind.as_arc_any())
|
||||
.ok()
|
||||
.expect("already checked type"),
|
||||
)
|
||||
} else {
|
||||
Self(job_kind)
|
||||
}
|
||||
}
|
||||
pub fn new<T: JobKind>(job_kind: T) -> Self {
|
||||
if let Some(job_kind) = DynJobKindTrait::as_any(&job_kind).downcast_ref::<Self>() {
|
||||
job_kind.clone()
|
||||
} else {
|
||||
Self(Arc::new(job_kind))
|
||||
}
|
||||
}
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
DynJobKindTrait::as_any(&*self.0).type_id()
|
||||
}
|
||||
pub fn downcast_ref<T: JobKind>(&self) -> Option<&T> {
|
||||
DynJobKindTrait::as_any(&*self.0).downcast_ref()
|
||||
}
|
||||
pub fn downcast_arc<T: JobKind>(self) -> Result<Arc<T>, Self> {
|
||||
if self.downcast_ref::<T>().is_some() {
|
||||
Ok(Arc::downcast::<T>(self.0.as_arc_any())
|
||||
.ok()
|
||||
.expect("already checked type"))
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
pub fn parse_command_line(
|
||||
&self,
|
||||
command_line: Interned<[Interned<str>]>,
|
||||
) -> clap::error::Result<DynJob> {
|
||||
self.0.clone().parse_command_line_dyn(command_line)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for DynJobKind {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
DynJobKindTrait::as_any(&*self.0).type_id().hash(state);
|
||||
DynJobKindTrait::hash_dyn(&*self.0, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for DynJobKind {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
DynJobKindTrait::eq_dyn(&*self.0, &*other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DynJobKind {}
|
||||
|
||||
impl fmt::Debug for DynJobKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
trait DynJobTrait: 'static + Send + Sync + fmt::Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool;
|
||||
fn hash_dyn(&self, state: &mut dyn Hasher);
|
||||
fn kind_type_id(&self) -> TypeId;
|
||||
fn kind(&self) -> DynJobKind;
|
||||
fn inputs(&self) -> Interned<[JobItemName]>;
|
||||
fn outputs(&self) -> Interned<[JobItemName]>;
|
||||
fn to_command_line(&self) -> Interned<[Interned<str>]>;
|
||||
fn run(&self, inputs: &[JobItem], acquired_job: &mut AcquiredJob)
|
||||
-> eyre::Result<Vec<JobItem>>;
|
||||
}
|
||||
|
||||
mod inner {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct DynJob<T: JobKind> {
|
||||
pub(crate) kind: Arc<T>,
|
||||
pub(crate) job: T::Job,
|
||||
pub(crate) inputs: Interned<[JobItemName]>,
|
||||
pub(crate) outputs: Interned<[JobItemName]>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JobKind> DynJobTrait for inner::DynJob<T> {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn eq_dyn(&self, other: &dyn DynJobTrait) -> bool {
|
||||
other
|
||||
.as_any()
|
||||
.downcast_ref::<inner::DynJob<T>>()
|
||||
.is_some_and(|other| self == other)
|
||||
}
|
||||
|
||||
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
|
||||
self.hash(&mut state);
|
||||
}
|
||||
|
||||
fn kind_type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
}
|
||||
|
||||
fn kind(&self) -> DynJobKind {
|
||||
DynJobKind(self.kind.clone())
|
||||
}
|
||||
|
||||
fn inputs(&self) -> Interned<[JobItemName]> {
|
||||
self.inputs
|
||||
}
|
||||
|
||||
fn outputs(&self) -> Interned<[JobItemName]> {
|
||||
self.outputs
|
||||
}
|
||||
|
||||
fn to_command_line(&self) -> Interned<[Interned<str>]> {
|
||||
self.kind.to_command_line(&self.job)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
inputs: &[JobItem],
|
||||
acquired_job: &mut AcquiredJob,
|
||||
) -> eyre::Result<Vec<JobItem>> {
|
||||
self.kind.run(&self.job, inputs, acquired_job)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DynJob(Arc<dyn DynJobTrait>);
|
||||
|
||||
impl DynJob {
|
||||
pub fn kind_type_id(&self) -> TypeId {
|
||||
self.0.kind_type_id()
|
||||
}
|
||||
pub fn downcast<T: JobKind>(&self) -> Option<(&T, &T::Job)> {
|
||||
let inner::DynJob { kind, job, .. } = self.0.as_any().downcast_ref()?;
|
||||
Some((kind, job))
|
||||
}
|
||||
pub fn kind(&self) -> DynJobKind {
|
||||
DynJobTrait::kind(&*self.0)
|
||||
}
|
||||
pub fn inputs(&self) -> Interned<[JobItemName]> {
|
||||
DynJobTrait::inputs(&*self.0)
|
||||
}
|
||||
pub fn outputs(&self) -> Interned<[JobItemName]> {
|
||||
DynJobTrait::outputs(&*self.0)
|
||||
}
|
||||
pub fn to_command_line(&self) -> Interned<[Interned<str>]> {
|
||||
DynJobTrait::to_command_line(&*self.0)
|
||||
}
|
||||
pub fn run(
|
||||
&self,
|
||||
inputs: &[JobItem],
|
||||
acquired_job: &mut AcquiredJob,
|
||||
) -> eyre::Result<Vec<JobItem>> {
|
||||
DynJobTrait::run(&*self.0, inputs, acquired_job)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for DynJob {}
|
||||
|
||||
impl PartialEq for DynJob {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
DynJobTrait::eq_dyn(&*self.0, &*other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for DynJob {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
DynJobTrait::hash_dyn(&*self.0, state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename = "DynJob")]
|
||||
struct DynJobSerde {
|
||||
kind: DynJobKind,
|
||||
command_line: Interned<[Interned<str>]>,
|
||||
}
|
||||
|
||||
impl Serialize for DynJob {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
DynJobSerde {
|
||||
kind: self.kind(),
|
||||
command_line: self.to_command_line(),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DynJob {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let DynJobSerde { kind, command_line } = Deserialize::deserialize(deserializer)?;
|
||||
kind.parse_command_line(command_line)
|
||||
.map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum JobGraphNode {
|
||||
Job(DynJob),
|
||||
Item(#[allow(dead_code, reason = "used for Debug")] JobItemName),
|
||||
}
|
||||
|
||||
type JobGraphInner = DiGraph<JobGraphNode, ()>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct JobGraph {
|
||||
jobs: HashMap<DynJob, <JobGraphInner as GraphBase>::NodeId>,
|
||||
items: HashMap<JobItemName, <JobGraphInner as GraphBase>::NodeId>,
|
||||
graph: JobGraphInner,
|
||||
topological_order: Vec<<JobGraphInner as GraphBase>::NodeId>,
|
||||
space: DfsSpace<<JobGraphInner as GraphBase>::NodeId, <JobGraphInner as Visitable>::Map>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for JobGraph {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
jobs: _,
|
||||
items: _,
|
||||
graph,
|
||||
topological_order,
|
||||
space: _,
|
||||
} = self;
|
||||
f.debug_struct("JobGraph")
|
||||
.field("graph", graph)
|
||||
.field("topological_order", topological_order)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CycleError {
|
||||
pub job: DynJob,
|
||||
pub output: JobItemName,
|
||||
}
|
||||
|
||||
impl std::error::Error for CycleError {}
|
||||
|
||||
impl fmt::Display for CycleError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self { job, output } = self;
|
||||
write!(
|
||||
f,
|
||||
"adding job to job graph would introduce a cyclic dependency through job output:\n\
|
||||
{output:?}\n\
|
||||
job:\n{job:?}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum EscapeForUnixShellState {
|
||||
DollarSingleQuote,
|
||||
SingleQuote,
|
||||
Unquoted,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EscapeForUnixShell<'a> {
|
||||
state: EscapeForUnixShellState,
|
||||
prefix: [u8; 3],
|
||||
bytes: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for EscapeForUnixShell<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for EscapeForUnixShell<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for c in self.clone() {
|
||||
f.write_char(c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EscapeForUnixShell<'a> {
|
||||
pub fn new(s: &'a str) -> Self {
|
||||
Self::from_bytes(s.as_bytes())
|
||||
}
|
||||
fn make_prefix(bytes: &[u8]) -> [u8; 3] {
|
||||
let mut prefix = [0; 3];
|
||||
prefix[..bytes.len()].copy_from_slice(bytes);
|
||||
prefix
|
||||
}
|
||||
pub fn from_bytes(bytes: &'a [u8]) -> Self {
|
||||
let mut needs_single_quote = bytes.is_empty();
|
||||
for &b in bytes {
|
||||
match b {
|
||||
b'!' | b'\'' | b'\"' | b' ' => needs_single_quote = true,
|
||||
0..0x20 | 0x7F.. => {
|
||||
return Self {
|
||||
state: EscapeForUnixShellState::DollarSingleQuote,
|
||||
prefix: Self::make_prefix(b"$'"),
|
||||
bytes,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if needs_single_quote {
|
||||
Self {
|
||||
state: EscapeForUnixShellState::SingleQuote,
|
||||
prefix: Self::make_prefix(b"'"),
|
||||
bytes,
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
state: EscapeForUnixShellState::Unquoted,
|
||||
prefix: Self::make_prefix(b""),
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for EscapeForUnixShell<'_> {
|
||||
type Item = char;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match &mut self.prefix {
|
||||
[0, 0, 0] => {}
|
||||
[0, 0, v] | // find first
|
||||
[0, v, _] | // non-zero byte
|
||||
[v, _, _] => {
|
||||
let retval = *v as char;
|
||||
*v = 0;
|
||||
return Some(retval);
|
||||
}
|
||||
}
|
||||
let Some(&next_byte) = self.bytes.split_off_first() else {
|
||||
return match self.state {
|
||||
EscapeForUnixShellState::DollarSingleQuote
|
||||
| EscapeForUnixShellState::SingleQuote => {
|
||||
self.state = EscapeForUnixShellState::Unquoted;
|
||||
Some('\'')
|
||||
}
|
||||
EscapeForUnixShellState::Unquoted => None,
|
||||
};
|
||||
};
|
||||
match self.state {
|
||||
EscapeForUnixShellState::DollarSingleQuote => match next_byte {
|
||||
b'\'' | b'\\' => {
|
||||
self.prefix = Self::make_prefix(&[next_byte]);
|
||||
Some('\\')
|
||||
}
|
||||
b'\t' => {
|
||||
self.prefix = Self::make_prefix(b"t");
|
||||
Some('\\')
|
||||
}
|
||||
b'\n' => {
|
||||
self.prefix = Self::make_prefix(b"n");
|
||||
Some('\\')
|
||||
}
|
||||
b'\r' => {
|
||||
self.prefix = Self::make_prefix(b"r");
|
||||
Some('\\')
|
||||
}
|
||||
0x20..=0x7E => Some(next_byte as char),
|
||||
_ => {
|
||||
self.prefix = [
|
||||
b'x',
|
||||
char::from_digit(next_byte as u32 >> 4, 0x10).expect("known to be in range")
|
||||
as u8,
|
||||
char::from_digit(next_byte as u32 & 0xF, 0x10)
|
||||
.expect("known to be in range") as u8,
|
||||
];
|
||||
Some('\\')
|
||||
}
|
||||
},
|
||||
EscapeForUnixShellState::SingleQuote => {
|
||||
if next_byte == b'\'' {
|
||||
self.prefix = Self::make_prefix(b"\\''");
|
||||
Some('\'')
|
||||
} else {
|
||||
Some(next_byte as char)
|
||||
}
|
||||
}
|
||||
EscapeForUnixShellState::Unquoted => match next_byte {
|
||||
b' ' | b'!' | b'"' | b'#' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b','
|
||||
| b';' | b'<' | b'>' | b'?' | b'[' | b'\\' | b']' | b'^' | b'`' | b'{' | b'|'
|
||||
| b'}' | b'~' => {
|
||||
self.prefix = Self::make_prefix(&[next_byte]);
|
||||
Some('\\')
|
||||
}
|
||||
_ => Some(next_byte as char),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum UnixMakefileEscapeKind {
|
||||
NonRecipe,
|
||||
RecipeWithoutShellEscaping,
|
||||
RecipeWithShellEscaping,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct EscapeForUnixMakefile<'a> {
|
||||
s: &'a str,
|
||||
kind: UnixMakefileEscapeKind,
|
||||
}
|
||||
|
||||
impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> fmt::Display for EscapeForUnixMakefile<'a> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| {
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EscapeForUnixMakefile<'a> {
|
||||
fn do_write<S: ?Sized, E>(
|
||||
&self,
|
||||
state: &mut S,
|
||||
write_str: impl Fn(&mut S, &str) -> Result<(), E>,
|
||||
write_char: impl Fn(&mut S, char) -> Result<(), E>,
|
||||
add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>,
|
||||
) -> Result<(), E> {
|
||||
let escape_recipe_char = |c| match c {
|
||||
'$' => write_str(state, "$$"),
|
||||
'\0'..='\x1F' | '\x7F' => {
|
||||
panic!("can't escape a control character for Unix Makefile: {c:?}");
|
||||
}
|
||||
_ => write_char(state, c),
|
||||
};
|
||||
match self.kind {
|
||||
UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c {
|
||||
'=' => {
|
||||
add_variable(state, "EQUALS = =")?;
|
||||
write_str(state, "$(EQUALS)")
|
||||
}
|
||||
';' => panic!("can't escape a semicolon (;) for Unix Makefile"),
|
||||
'$' => write_str(state, "$$"),
|
||||
'\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => {
|
||||
write_char(state, '\\')?;
|
||||
write_char(state, c)
|
||||
}
|
||||
'\0'..='\x1F' | '\x7F' => {
|
||||
panic!("can't escape a control character for Unix Makefile: {c:?}");
|
||||
}
|
||||
_ => write_char(state, c),
|
||||
}),
|
||||
UnixMakefileEscapeKind::RecipeWithoutShellEscaping => {
|
||||
self.s.chars().try_for_each(escape_recipe_char)
|
||||
}
|
||||
UnixMakefileEscapeKind::RecipeWithShellEscaping => {
|
||||
EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char)
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn new(
|
||||
s: &'a str,
|
||||
kind: UnixMakefileEscapeKind,
|
||||
needed_variables: &mut BTreeSet<&'static str>,
|
||||
) -> Self {
|
||||
let retval = Self { s, kind };
|
||||
let Ok(()) = retval.do_write(
|
||||
needed_variables,
|
||||
|_, _| Ok(()),
|
||||
|_, _| Ok(()),
|
||||
|needed_variables, variable| -> Result<(), std::convert::Infallible> {
|
||||
needed_variables.insert(variable);
|
||||
Ok(())
|
||||
},
|
||||
);
|
||||
retval
|
||||
}
|
||||
}
|
||||
|
||||
impl JobGraph {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn try_add_jobs<I: IntoIterator<Item = DynJob>>(
|
||||
&mut self,
|
||||
jobs: I,
|
||||
) -> Result<(), CycleError> {
|
||||
let jobs = jobs.into_iter();
|
||||
let mut new_nodes =
|
||||
HashSet::with_capacity_and_hasher(jobs.size_hint().0, Default::default());
|
||||
for job in jobs {
|
||||
let Entry::Vacant(entry) = self.jobs.entry(job) else {
|
||||
continue;
|
||||
};
|
||||
let job_node_id = self.graph.add_node(JobGraphNode::Job(entry.key().clone()));
|
||||
new_nodes.insert(job_node_id);
|
||||
let entry = entry.insert_entry(job_node_id);
|
||||
for (item, is_input) in entry
|
||||
.key()
|
||||
.inputs()
|
||||
.iter()
|
||||
.zip(iter::repeat(true))
|
||||
.chain(entry.key().outputs().iter().zip(iter::repeat(false)))
|
||||
{
|
||||
let item_node_id;
|
||||
match self.items.entry(*item) {
|
||||
Entry::Occupied(entry) => item_node_id = *entry.get(),
|
||||
Entry::Vacant(entry) => {
|
||||
item_node_id = self.graph.add_node(JobGraphNode::Item(*item));
|
||||
new_nodes.insert(item_node_id);
|
||||
entry.insert(item_node_id);
|
||||
}
|
||||
}
|
||||
let mut source = job_node_id;
|
||||
let mut dest = item_node_id;
|
||||
if is_input {
|
||||
mem::swap(&mut source, &mut dest);
|
||||
}
|
||||
self.graph.add_edge(source, dest, ());
|
||||
}
|
||||
}
|
||||
match toposort(&self.graph, Some(&mut self.space)) {
|
||||
Ok(v) => {
|
||||
self.topological_order = v;
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => {
|
||||
// there's at least one cycle, find one!
|
||||
let cycle = kosaraju_scc(&self.graph)
|
||||
.into_iter()
|
||||
.find_map(|scc| {
|
||||
if scc.len() <= 1 {
|
||||
// can't be a cycle since our graph is bipartite --
|
||||
// jobs only connect to items, never jobs to jobs or items to items
|
||||
None
|
||||
} else {
|
||||
Some(scc)
|
||||
}
|
||||
})
|
||||
.expect("we know there's a cycle");
|
||||
let cycle_set = HashSet::from_iter(cycle.iter().copied());
|
||||
let job = cycle
|
||||
.into_iter()
|
||||
.find_map(|node_id| {
|
||||
if let JobGraphNode::Job(job) = &self.graph[node_id] {
|
||||
Some(job.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.expect("a job must be part of the cycle");
|
||||
let output = job
|
||||
.outputs()
|
||||
.into_iter()
|
||||
.find(|output| cycle_set.contains(&self.items[output]))
|
||||
.expect("an output must be part of the cycle");
|
||||
for node in new_nodes {
|
||||
self.graph.remove_node(node);
|
||||
}
|
||||
Err(CycleError { job, output })
|
||||
}
|
||||
}
|
||||
}
|
||||
#[track_caller]
|
||||
pub fn add_jobs<I: IntoIterator<Item = DynJob>>(&mut self, jobs: I) {
|
||||
match self.try_add_jobs(jobs) {
|
||||
Ok(()) => {}
|
||||
Err(e) => panic!("error: {e}"),
|
||||
}
|
||||
}
|
||||
pub fn to_unix_makefile(&self) -> String {
|
||||
macro_rules! write_str {
|
||||
($s:expr, $($rest:tt)*) => {
|
||||
String::write_fmt(&mut $s, format_args!($($rest)*)).expect("String::write_fmt can't fail")
|
||||
};
|
||||
}
|
||||
let mut retval = String::new();
|
||||
let mut needed_variables = BTreeSet::new();
|
||||
for &node_id in &self.topological_order {
|
||||
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||
continue;
|
||||
};
|
||||
for (index, output) in job.outputs().into_iter().enumerate() {
|
||||
match output {
|
||||
JobItemName::Module { .. } => continue,
|
||||
JobItemName::File { path } => {
|
||||
if index != 0 {
|
||||
retval.push_str(" ");
|
||||
}
|
||||
write_str!(
|
||||
retval,
|
||||
"{}",
|
||||
EscapeForUnixMakefile::new(
|
||||
&path,
|
||||
UnixMakefileEscapeKind::NonRecipe,
|
||||
&mut needed_variables
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
retval.push_str(":");
|
||||
for input in job.inputs() {
|
||||
match input {
|
||||
JobItemName::Module { .. } => continue,
|
||||
JobItemName::File { path } => {
|
||||
write_str!(
|
||||
retval,
|
||||
" {}",
|
||||
EscapeForUnixMakefile::new(
|
||||
&path,
|
||||
UnixMakefileEscapeKind::NonRecipe,
|
||||
&mut needed_variables
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
retval.push_str("\n\t");
|
||||
for (index, arg) in job.to_command_line().into_iter().enumerate() {
|
||||
if index != 0 {
|
||||
retval.push_str(" ");
|
||||
}
|
||||
write_str!(
|
||||
retval,
|
||||
"{}",
|
||||
EscapeForUnixMakefile::new(
|
||||
&arg,
|
||||
UnixMakefileEscapeKind::RecipeWithShellEscaping,
|
||||
&mut needed_variables
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
if !needed_variables.is_empty() {
|
||||
retval.insert_str(
|
||||
0,
|
||||
&String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))),
|
||||
);
|
||||
}
|
||||
retval
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<DynJob> for JobGraph {
|
||||
#[track_caller]
|
||||
fn extend<T: IntoIterator<Item = DynJob>>(&mut self, iter: T) {
|
||||
self.add_jobs(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<DynJob> for JobGraph {
|
||||
#[track_caller]
|
||||
fn from_iter<T: IntoIterator<Item = DynJob>>(iter: T) -> Self {
|
||||
let mut retval = Self::new();
|
||||
retval.add_jobs(iter);
|
||||
retval
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for JobGraph {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut serializer = serializer.serialize_seq(Some(self.jobs.len()))?;
|
||||
for &node_id in &self.topological_order {
|
||||
let JobGraphNode::Job(job) = &self.graph[node_id] else {
|
||||
continue;
|
||||
};
|
||||
serializer.serialize_element(job)?;
|
||||
}
|
||||
serializer.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for JobGraph {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let jobs = Vec::<DynJob>::deserialize(deserializer)?;
|
||||
let mut retval = JobGraph::new();
|
||||
retval.try_add_jobs(jobs).map_err(D::Error::custom)?;
|
||||
Ok(retval)
|
||||
}
|
||||
}
|
||||
|
|
@ -87,6 +87,7 @@ pub mod _docs;
|
|||
|
||||
pub mod annotations;
|
||||
pub mod array;
|
||||
pub mod build;
|
||||
pub mod bundle;
|
||||
pub mod cli;
|
||||
pub mod clock;
|
||||
|
|
@ -104,6 +105,7 @@ pub mod reg;
|
|||
pub mod reset;
|
||||
pub mod sim;
|
||||
pub mod source_location;
|
||||
pub mod target;
|
||||
pub mod testing;
|
||||
pub mod ty;
|
||||
pub mod util;
|
||||
|
|
|
|||
202
crates/fayalite/src/target.rs
Normal file
202
crates/fayalite/src/target.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use crate::{intern::Interned, util::job_server::AcquiredJob};
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt,
|
||||
iter::FusedIterator,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
pub trait Peripheral: Any + Send + Sync + fmt::Debug {}
|
||||
|
||||
pub trait Tool: Any + Send + Sync + fmt::Debug {
|
||||
fn name(&self) -> Interned<str>;
|
||||
fn run(&self, acquired_job: &mut AcquiredJob);
|
||||
}
|
||||
|
||||
pub trait Target: Any + Send + Sync + fmt::Debug {
|
||||
fn name(&self) -> Interned<str>;
|
||||
fn peripherals(&self) -> Interned<[Interned<dyn Peripheral>]>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TargetsMap(Vec<(Interned<str>, Interned<dyn Target>)>);
|
||||
|
||||
impl TargetsMap {
|
||||
fn sort(&mut self) {
|
||||
self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2));
|
||||
self.0.dedup_by_key(|(k, _)| *k);
|
||||
}
|
||||
fn from_unsorted_vec(unsorted_vec: Vec<(Interned<str>, Interned<dyn Target>)>) -> Self {
|
||||
let mut retval = Self(unsorted_vec);
|
||||
retval.sort();
|
||||
retval
|
||||
}
|
||||
fn extend_from_unsorted_slice(&mut self, additional: &[(Interned<str>, Interned<dyn Target>)]) {
|
||||
self.0.extend_from_slice(additional);
|
||||
self.sort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TargetsMap {
|
||||
fn default() -> Self {
|
||||
Self::from_unsorted_vec(vec![
|
||||
// TODO: add default targets here
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn access_targets<F: FnOnce(&mut Option<Arc<TargetsMap>>) -> R, R>(f: F) -> R {
|
||||
static TARGETS: Mutex<Option<Arc<TargetsMap>>> = Mutex::new(None);
|
||||
let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned");
|
||||
f(&mut targets_lock)
|
||||
}
|
||||
|
||||
pub fn add_targets<I: IntoIterator<Item = Interned<dyn Target>>>(additional: I) {
|
||||
// run iterator and target methods outside of lock
|
||||
let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v)));
|
||||
access_targets(|targets| {
|
||||
Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn targets() -> TargetsSnapshot {
|
||||
access_targets(|targets| match targets {
|
||||
Some(targets) => TargetsSnapshot {
|
||||
targets: targets.clone(),
|
||||
},
|
||||
None => {
|
||||
let new_targets = Arc::<TargetsMap>::default();
|
||||
*targets = Some(new_targets.clone());
|
||||
TargetsSnapshot {
|
||||
targets: new_targets,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TargetsSnapshot {
|
||||
targets: Arc<TargetsMap>,
|
||||
}
|
||||
|
||||
impl TargetsSnapshot {
|
||||
pub fn get(&self, key: &str) -> Option<Interned<dyn Target>> {
|
||||
let index = self
|
||||
.targets
|
||||
.0
|
||||
.binary_search_by_key(&key, |(k, _v)| k)
|
||||
.ok()?;
|
||||
Some(self.targets.0[index].1)
|
||||
}
|
||||
pub fn iter(&self) -> TargetsIter {
|
||||
self.into_iter()
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.targets.0.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for TargetsSnapshot {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("TargetsSnapshot ")?;
|
||||
f.debug_map().entries(self).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &'_ mut TargetsSnapshot {
|
||||
type Item = (Interned<str>, Interned<dyn Target>);
|
||||
type IntoIter = TargetsIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for &'_ TargetsSnapshot {
|
||||
type Item = (Interned<str>, Interned<dyn Target>);
|
||||
type IntoIter = TargetsIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for TargetsSnapshot {
|
||||
type Item = (Interned<str>, Interned<dyn Target>);
|
||||
type IntoIter = TargetsIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
TargetsIter {
|
||||
indexes: 0..self.targets.0.len(),
|
||||
targets: self.targets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TargetsIter {
|
||||
targets: Arc<TargetsMap>,
|
||||
indexes: std::ops::Range<usize>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for TargetsIter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("TargetsIter ")?;
|
||||
f.debug_map().entries(self.clone()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for TargetsIter {
|
||||
type Item = (Interned<str>, Interned<dyn Target>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
Some(self.targets.0[self.indexes.next()?])
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.indexes.size_hint()
|
||||
}
|
||||
|
||||
fn count(self) -> usize {
|
||||
self.indexes.len()
|
||||
}
|
||||
|
||||
fn last(mut self) -> Option<Self::Item> {
|
||||
self.next_back()
|
||||
}
|
||||
|
||||
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
||||
Some(self.targets.0[self.indexes.nth(n)?])
|
||||
}
|
||||
|
||||
fn fold<B, F: FnMut(B, Self::Item) -> B>(self, init: B, mut f: F) -> B {
|
||||
self.indexes
|
||||
.fold(init, move |retval, index| f(retval, self.targets.0[index]))
|
||||
}
|
||||
}
|
||||
|
||||
impl FusedIterator for TargetsIter {}
|
||||
|
||||
impl DoubleEndedIterator for TargetsIter {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
Some(self.targets.0[self.indexes.next_back()?])
|
||||
}
|
||||
|
||||
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
|
||||
Some(self.targets.0[self.indexes.nth_back(n)?])
|
||||
}
|
||||
|
||||
fn rfold<B, F: FnMut(B, Self::Item) -> B>(self, init: B, mut f: F) -> B {
|
||||
self.indexes
|
||||
.rfold(init, move |retval, index| f(retval, self.targets.0[index]))
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for TargetsIter {
|
||||
fn len(&self) -> usize {
|
||||
self.indexes.len()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue