mod async_lower;
mod lower;
mod mono;
pub use mono::lower_module;
pub use async_lower::{
FUTURE_SLOT_POLL, FUTURE_SLOT_STATE, POLL_PENDING, POLL_READY, STATE_MIN_SIZE,
STATE_SLOT_OUTPUT, STATE_SLOT_TAG, STATE_SLOT_TEMPS,
};
use nova_hir as hir;
use nova_resolver::DefId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MirTy {
I64,
F64,
I8,
Ptr,
Unit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Temp(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockId(pub u32);
#[derive(Debug, Default)]
pub struct Module {
pub functions: Vec<Function>,
pub externs: Vec<ExternDecl>,
}
#[derive(Debug, Clone)]
pub struct ExternDecl {
pub symbol: String,
pub params: Vec<MirTy>,
pub ret: MirTy,
}
#[derive(Debug)]
pub struct Function {
pub name: String,
pub params: u32,
pub takes_env: bool,
pub capture_count: u32,
pub temps: Vec<MirTy>,
pub ret: MirTy,
pub is_async: bool,
pub blocks: Vec<Block>,
}
#[derive(Debug)]
pub struct Block {
pub stmts: Vec<Stmt>,
pub term: Terminator,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperandClass {
Int,
Float,
Bool,
}
macro_rules! rt_funcs {
($($(#[$doc:meta])* $variant:ident),+ $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RtFunc {
$($(#[$doc])* $variant,)+
}
impl RtFunc {
pub const ALL: [RtFunc; rt_funcs!(@count $($variant)+)] = [
$(RtFunc::$variant),+
];
}
};
(@count $($v:ident)+) => {
[$(rt_funcs!(@one $v)),+].len()
};
(@one $v:ident) => { () };
}
rt_funcs! {
Println,
Print,
EPrint,
EPrintln,
StrConcat,
StrConcatN,
IntToStr,
FloatToStr,
FloatFixed,
StrToFloat,
BoolToStr,
CharToStr,
StrEq,
StrCmp,
StrHash,
StrLenChars,
StrChars,
StrFromChars,
StrJoin,
StrIndexOf,
JsonQuote,
StrToUpper,
StrToLower,
Alloc,
CheckBounds,
Panic,
TestSelector,
TaskSpawn,
TaskBlockOn,
TaskIsDone,
TaskTakeOutput,
TaskRelease,
TaskYieldFuture,
TaskSleepFutureNanos,
TaskJoinFuture,
TaskTimeoutFuture,
FsReadToString,
FsWriteString,
FsTakeString,
FsLastErrorMessage,
FsTempDir,
FsExists,
FsCreateDir,
FsCreateDirAll,
FsRemoveFile,
FsRemoveDirAll,
FsReadDir,
FsTakeStringArray,
FsKind,
FsRead,
FsTakeBytes,
FsWrite,
FileOpen,
FileClose,
FileRead,
FileWrite,
FileFlush,
IoStdinRead,
IoStdoutWrite,
IoStderrWrite,
IoStdoutFlush,
IoStderrFlush,
NetConnect,
NetClose,
NetRead,
NetWrite,
NetReadTimeout,
NetListen,
NetLocalPort,
NetAccept,
BytesLen,
BytesFromString,
BytesIsUtf8,
BytesToStringUnchecked,
BytesAt,
BytesSlice,
BytesConcat,
BytesToInts,
BytesFromInts,
BytesEq,
IntHashSeed,
HttpParseRequest,
CryptoHash,
CryptoRandomBytes,
CryptoRandomInt,
TimeNowNanos,
TimeNowEpochNanos,
LogConfigLevel,
LogConfigToStderr,
LogSetConfig,
ShutdownRequested,
ProcessArgCount,
ProcessArg,
ProcessExit,
}
impl RtFunc {
pub fn symbol(self) -> &'static str {
match self {
RtFunc::Println => "nova_rt_println",
RtFunc::Print => "nova_rt_print",
RtFunc::EPrint => "nova_rt_eprint",
RtFunc::EPrintln => "nova_rt_eprintln",
RtFunc::StrConcat => "nova_rt_str_concat",
RtFunc::StrConcatN => "nova_rt_str_concat_n",
RtFunc::IntToStr => "nova_rt_int_to_str",
RtFunc::FloatToStr => "nova_rt_float_to_str",
RtFunc::FloatFixed => "nova_rt_float_fixed",
RtFunc::StrToFloat => "nova_rt_str_to_float",
RtFunc::BoolToStr => "nova_rt_bool_to_str",
RtFunc::CharToStr => "nova_rt_char_to_str",
RtFunc::StrEq => "nova_rt_str_eq",
RtFunc::StrCmp => "nova_rt_str_cmp",
RtFunc::StrHash => "nova_rt_str_hash",
RtFunc::StrLenChars => "nova_rt_str_len_chars",
RtFunc::StrChars => "nova_rt_str_chars",
RtFunc::StrFromChars => "nova_rt_str_from_chars",
RtFunc::StrJoin => "nova_rt_str_join",
RtFunc::StrIndexOf => "nova_rt_str_index_of",
RtFunc::JsonQuote => "nova_rt_json_quote",
RtFunc::StrToUpper => "nova_rt_str_to_upper",
RtFunc::StrToLower => "nova_rt_str_to_lower",
RtFunc::Alloc => "nova_rt_alloc",
RtFunc::CheckBounds => "nova_rt_check_bounds",
RtFunc::Panic => "nova_rt_panic_str",
RtFunc::TestSelector => "nova_rt_test_selector",
RtFunc::TaskSpawn => "nova_rt_task_spawn",
RtFunc::TaskBlockOn => "nova_rt_task_block_on",
RtFunc::TaskIsDone => "nova_rt_task_is_done",
RtFunc::TaskTakeOutput => "nova_rt_task_take_output",
RtFunc::TaskRelease => "nova_rt_task_release",
RtFunc::TaskYieldFuture => "nova_rt_task_yield_future",
RtFunc::TaskSleepFutureNanos => "nova_rt_task_sleep_future_nanos",
RtFunc::TaskJoinFuture => "nova_rt_task_join_future",
RtFunc::TaskTimeoutFuture => "nova_rt_task_timeout_future",
RtFunc::FsReadToString => "nova_rt_fs_read_to_string",
RtFunc::FsWriteString => "nova_rt_fs_write_string",
RtFunc::FsTakeString => "nova_rt_fs_take_string",
RtFunc::FsLastErrorMessage => "nova_rt_fs_last_error_message",
RtFunc::FsTempDir => "nova_rt_fs_temp_dir",
RtFunc::FsExists => "nova_rt_fs_exists",
RtFunc::FsCreateDir => "nova_rt_fs_create_dir",
RtFunc::FsCreateDirAll => "nova_rt_fs_create_dir_all",
RtFunc::FsRemoveFile => "nova_rt_fs_remove_file",
RtFunc::FsRemoveDirAll => "nova_rt_fs_remove_dir_all",
RtFunc::FsReadDir => "nova_rt_fs_read_dir",
RtFunc::FsTakeStringArray => "nova_rt_fs_take_string_array",
RtFunc::FsKind => "nova_rt_fs_kind",
RtFunc::FsRead => "nova_rt_fs_read",
RtFunc::FsTakeBytes => "nova_rt_fs_take_bytes",
RtFunc::FsWrite => "nova_rt_fs_write",
RtFunc::FileOpen => "nova_rt_file_open",
RtFunc::FileClose => "nova_rt_file_close",
RtFunc::FileRead => "nova_rt_file_read",
RtFunc::FileWrite => "nova_rt_file_write",
RtFunc::FileFlush => "nova_rt_file_flush",
RtFunc::IoStdinRead => "nova_rt_io_stdin_read",
RtFunc::IoStdoutWrite => "nova_rt_io_stdout_write",
RtFunc::IoStderrWrite => "nova_rt_io_stderr_write",
RtFunc::IoStdoutFlush => "nova_rt_io_stdout_flush",
RtFunc::IoStderrFlush => "nova_rt_io_stderr_flush",
RtFunc::NetConnect => "nova_rt_net_connect_future",
RtFunc::NetClose => "nova_rt_net_close",
RtFunc::NetRead => "nova_rt_net_read_future",
RtFunc::NetWrite => "nova_rt_net_write_future",
RtFunc::NetReadTimeout => "nova_rt_net_read_timeout_future",
RtFunc::NetListen => "nova_rt_net_listen",
RtFunc::NetLocalPort => "nova_rt_net_local_port",
RtFunc::NetAccept => "nova_rt_net_accept_future",
RtFunc::BytesLen => "nova_rt_bytes_len",
RtFunc::BytesFromString => "nova_rt_bytes_from_string",
RtFunc::BytesIsUtf8 => "nova_rt_bytes_is_utf8",
RtFunc::BytesToStringUnchecked => "nova_rt_bytes_to_string_unchecked",
RtFunc::BytesAt => "nova_rt_bytes_at",
RtFunc::BytesSlice => "nova_rt_bytes_slice",
RtFunc::BytesConcat => "nova_rt_bytes_concat",
RtFunc::BytesToInts => "nova_rt_bytes_to_ints",
RtFunc::BytesFromInts => "nova_rt_bytes_from_ints",
RtFunc::BytesEq => "nova_rt_bytes_eq",
RtFunc::IntHashSeed => "nova_rt_int_hash_seed",
RtFunc::HttpParseRequest => "nova_rt_http_parse_request",
RtFunc::CryptoHash => "nova_rt_crypto_hash",
RtFunc::CryptoRandomBytes => "nova_rt_crypto_random_bytes",
RtFunc::CryptoRandomInt => "nova_rt_crypto_random_int",
RtFunc::TimeNowNanos => "nova_rt_time_now_nanos",
RtFunc::TimeNowEpochNanos => "nova_rt_time_now_epoch_nanos",
RtFunc::LogConfigLevel => "nova_rt_log_config_level",
RtFunc::LogConfigToStderr => "nova_rt_log_config_to_stderr",
RtFunc::LogSetConfig => "nova_rt_log_set_config",
RtFunc::ShutdownRequested => "nova_rt_shutdown_requested",
RtFunc::ProcessArgCount => "nova_rt_process_arg_count",
RtFunc::ProcessArg => "nova_rt_process_arg",
RtFunc::ProcessExit => "nova_rt_process_exit",
}
}
pub fn signature(self) -> (Vec<MirTy>, MirTy) {
match self {
RtFunc::Println | RtFunc::Print | RtFunc::EPrint | RtFunc::EPrintln => {
(vec![MirTy::Ptr], MirTy::Unit)
}
RtFunc::StrConcat => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::Ptr),
RtFunc::StrConcatN => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::IntToStr => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::FloatToStr => (vec![MirTy::F64], MirTy::Ptr),
RtFunc::FloatFixed => (vec![MirTy::F64, MirTy::I64], MirTy::Ptr),
RtFunc::StrToFloat => (vec![MirTy::Ptr], MirTy::F64),
RtFunc::BoolToStr => (vec![MirTy::I8], MirTy::Ptr),
RtFunc::CharToStr => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::StrEq => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I8),
RtFunc::StrCmp => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I64),
RtFunc::StrHash => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::StrLenChars => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::StrChars => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::StrFromChars => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::StrJoin => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::Ptr),
RtFunc::StrIndexOf => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I64),
RtFunc::JsonQuote => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::StrToUpper | RtFunc::StrToLower => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::Alloc => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::CheckBounds => (vec![MirTy::I64, MirTy::I64], MirTy::Unit),
RtFunc::Panic => (vec![MirTy::Ptr], MirTy::Unit),
RtFunc::TestSelector => (vec![], MirTy::I64),
RtFunc::TaskSpawn | RtFunc::TaskBlockOn => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::TaskIsDone => (vec![MirTy::Ptr], MirTy::I8),
RtFunc::TaskTakeOutput => (vec![MirTy::I64], MirTy::I64),
RtFunc::TaskRelease => (vec![MirTy::Ptr], MirTy::Unit),
RtFunc::TaskYieldFuture => (vec![], MirTy::Ptr),
RtFunc::TaskSleepFutureNanos => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::TaskJoinFuture => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::TaskTimeoutFuture => (vec![MirTy::I64, MirTy::Ptr], MirTy::Ptr),
RtFunc::FsReadToString => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::FsWriteString => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I64),
RtFunc::FsTakeString
| RtFunc::FsLastErrorMessage
| RtFunc::FsTempDir
| RtFunc::FsTakeStringArray => (vec![], MirTy::Ptr),
RtFunc::FsExists => (vec![MirTy::Ptr], MirTy::I8),
RtFunc::FsCreateDir
| RtFunc::FsCreateDirAll
| RtFunc::FsRemoveFile
| RtFunc::FsRemoveDirAll
| RtFunc::FsReadDir
| RtFunc::FsKind
| RtFunc::FsRead => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::FsTakeBytes => (vec![], MirTy::Ptr),
RtFunc::FsWrite => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I64),
RtFunc::FileOpen => (
vec![
MirTy::Ptr,
MirTy::I8,
MirTy::I8,
MirTy::I8,
MirTy::I8,
MirTy::I8,
MirTy::I8,
],
MirTy::I64,
),
RtFunc::FileClose | RtFunc::FileFlush => (vec![MirTy::I64], MirTy::I64),
RtFunc::FileRead => (vec![MirTy::I64, MirTy::I64], MirTy::I64),
RtFunc::FileWrite => (vec![MirTy::I64, MirTy::Ptr], MirTy::I64),
RtFunc::IoStdinRead => (vec![MirTy::I64], MirTy::I64),
RtFunc::IoStdoutWrite | RtFunc::IoStderrWrite => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::IoStdoutFlush | RtFunc::IoStderrFlush => (vec![], MirTy::I64),
RtFunc::NetConnect => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::NetClose => (vec![MirTy::I64], MirTy::I64),
RtFunc::NetRead => (vec![MirTy::I64, MirTy::I64], MirTy::Ptr),
RtFunc::NetWrite => (vec![MirTy::I64, MirTy::Ptr], MirTy::Ptr),
RtFunc::NetReadTimeout => (vec![MirTy::I64, MirTy::I64, MirTy::I64], MirTy::Ptr),
RtFunc::NetListen => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::NetLocalPort => (vec![MirTy::I64], MirTy::I64),
RtFunc::NetAccept => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::BytesLen => (vec![MirTy::Ptr], MirTy::I64),
RtFunc::BytesFromString => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::BytesIsUtf8 => (vec![MirTy::Ptr], MirTy::I8),
RtFunc::BytesToStringUnchecked => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::BytesAt => (vec![MirTy::Ptr, MirTy::I64], MirTy::I64),
RtFunc::BytesSlice => (vec![MirTy::Ptr, MirTy::I64, MirTy::I64], MirTy::Ptr),
RtFunc::BytesConcat => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::Ptr),
RtFunc::BytesToInts => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::BytesFromInts => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::BytesEq => (vec![MirTy::Ptr, MirTy::Ptr], MirTy::I8),
RtFunc::IntHashSeed => (vec![], MirTy::I64),
RtFunc::HttpParseRequest => (vec![MirTy::Ptr], MirTy::Ptr),
RtFunc::CryptoHash => (
vec![MirTy::I64, MirTy::Ptr, MirTy::Ptr, MirTy::Ptr],
MirTy::I64,
),
RtFunc::CryptoRandomBytes => (vec![MirTy::I64], MirTy::I64),
RtFunc::CryptoRandomInt => (vec![MirTy::I64, MirTy::I64], MirTy::I64),
RtFunc::TimeNowNanos => (vec![], MirTy::I64),
RtFunc::TimeNowEpochNanos => (vec![], MirTy::I64),
RtFunc::LogConfigLevel => (vec![], MirTy::I64),
RtFunc::LogConfigToStderr => (vec![], MirTy::I64),
RtFunc::LogSetConfig => (vec![MirTy::I64, MirTy::I64], MirTy::Unit),
RtFunc::ShutdownRequested => (vec![], MirTy::I8),
RtFunc::ProcessArgCount => (vec![], MirTy::I64),
RtFunc::ProcessArg => (vec![MirTy::I64], MirTy::Ptr),
RtFunc::ProcessExit => (vec![MirTy::I64], MirTy::Unit),
}
}
}
#[cfg(test)]
mod rt_func_tests {
use super::RtFunc;
#[test]
fn all_lists_every_variant_exactly_once() {
let symbols: Vec<&str> = RtFunc::ALL.iter().map(|f| f.symbol()).collect();
let mut sorted = symbols.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(
sorted.len(),
symbols.len(),
"RtFunc::ALL has a duplicate entry: {symbols:?}"
);
for f in RtFunc::ALL {
let _ = f.signature();
}
}
}
pub const MAX_ARRAY_LEN: i64 = (i64::MAX - 8) / 8;
#[derive(Debug)]
pub enum Stmt {
ConstInt(Temp, i64),
ConstFloat(Temp, f64),
ConstBool(Temp, bool),
ConstStr(Temp, String),
ConstUnit(Temp),
Copy {
dst: Temp,
src: Temp,
},
Bin {
dst: Temp,
op: hir::BinOp,
class: OperandClass,
lhs: Temp,
rhs: Temp,
},
Neg {
dst: Temp,
class: OperandClass,
src: Temp,
},
Not {
dst: Temp,
src: Temp,
},
BitNot {
dst: Temp,
src: Temp,
},
Call {
dst: Option<Temp>,
callee: String,
args: Vec<Temp>,
},
CallIndirect {
dst: Option<Temp>,
callee: Temp,
params: Vec<MirTy>,
ret: MirTy,
args: Vec<Temp>,
},
Await {
dst: Option<Temp>,
future: Temp,
},
CallRuntime {
dst: Option<Temp>,
func: RtFunc,
args: Vec<Temp>,
},
MakeClosure {
dst: Temp,
code: String,
captures: Vec<(Temp, MirTy)>,
},
MakeSum {
dst: Temp,
tag: u32,
fields: Vec<(Temp, MirTy)>,
},
SumTag {
dst: Temp,
sum: Temp,
},
SumField {
dst: Temp,
sum: Temp,
index: u32,
ty: MirTy,
},
MakeRecord {
dst: Temp,
fields: Vec<(Temp, MirTy)>,
},
RecordField {
dst: Temp,
record: Temp,
index: u32,
ty: MirTy,
},
SetField {
record: Temp,
index: u32,
value: Temp,
ty: MirTy,
},
MakeArray {
dst: Temp,
elems: Vec<(Temp, MirTy)>,
},
ArrayAlloc {
dst: Temp,
len: Temp,
},
ArrayLen {
dst: Temp,
arr: Temp,
},
ArrayGet {
dst: Temp,
arr: Temp,
index: Temp,
ty: MirTy,
},
ArraySet {
arr: Temp,
index: Temp,
value: Temp,
ty: MirTy,
},
}
#[derive(Debug)]
pub enum Terminator {
Goto(BlockId),
Branch {
cond: Temp,
then_: BlockId,
else_: BlockId,
},
Switch {
disc: Temp,
arms: Vec<(i64, BlockId)>,
default: BlockId,
},
Return(Option<Temp>),
Trap,
}
pub fn mir_ty(ty: &hir::Ty) -> MirTy {
match ty {
hir::Ty::Int | hir::Ty::Char => MirTy::I64,
hir::Ty::Float => MirTy::F64,
hir::Ty::Bool => MirTy::I8,
hir::Ty::String
| hir::Ty::Bytes
| hir::Ty::Fn { .. }
| hir::Ty::Sum { .. }
| hir::Ty::Record { .. }
| hir::Ty::Array(_)
| hir::Ty::Future(_) => MirTy::Ptr,
hir::Ty::Unit | hir::Ty::Never => MirTy::Unit,
hir::Ty::Param(_) | hir::Ty::Var(_) | hir::Ty::Error | hir::Ty::Assoc { .. } => MirTy::Unit,
}
}
pub fn mangle(def_id: DefId, name: &str, type_args: &[hir::Ty]) -> String {
if type_args.is_empty() {
return format!("{name}.{}", def_id.0);
}
let args: Vec<String> = type_args.iter().map(mangle_ty).collect();
format!("{name}.{}${}", def_id.0, args.join("_"))
}
fn mangle_ty(ty: &hir::Ty) -> String {
match ty {
hir::Ty::Int => "i".to_string(),
hir::Ty::Float => "f".to_string(),
hir::Ty::Bool => "b".to_string(),
hir::Ty::Char => "c".to_string(),
hir::Ty::String => "s".to_string(),
hir::Ty::Bytes => "y".to_string(),
hir::Ty::Unit => "u".to_string(),
hir::Ty::Never => "n".to_string(),
hir::Ty::Fn { params, ret } => {
let ps: Vec<String> = params.iter().map(mangle_ty).collect();
format!("F{}R{}E", ps.join(""), mangle_ty(ret))
}
hir::Ty::Sum { def_id, args } => {
if args.is_empty() {
format!("S{}", def_id.0)
} else {
let a: Vec<String> = args.iter().map(mangle_ty).collect();
format!("S{}L{}E", def_id.0, a.join(""))
}
}
hir::Ty::Record { def_id, args } => {
if args.is_empty() {
format!("R{}", def_id.0)
} else {
let a: Vec<String> = args.iter().map(mangle_ty).collect();
format!("R{}L{}E", def_id.0, a.join(""))
}
}
hir::Ty::Array(elem) => format!("A{}E", mangle_ty(elem)),
hir::Ty::Future(out) => format!("U{}E", mangle_ty(out)),
hir::Ty::Param(_) | hir::Ty::Var(_) | hir::Ty::Error | hir::Ty::Assoc { .. } => {
"X".to_string()
}
}
}