Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Cargo.lock
*.wat
out
test_data/**/*.json
.*
Binary file added bin/linux/inf-llc
Binary file not shown.
Binary file added bin/linux/rust-lld
Binary file not shown.
10 changes: 8 additions & 2 deletions core/ast/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::nodes::{
TypeMemberAccessExpression,
};
use crate::type_infer::TypeChecker;
use crate::type_info::TypeInfo;
use crate::{
arena::Arena,
nodes::{
Expand Down Expand Up @@ -111,10 +112,12 @@ impl<'a> Builder<'a, InitState> {
let mut type_checker = TypeChecker::new();
let _ = type_checker.infer_types(&mut res);

// let symbol_table = SymbolTable::build(&res, &self.types, &self.arena);
// let mut type_checker = TypeChecker::new();
let t_ast = TypedAst::new(res, self.arena.clone());
t_ast.infer_expression_types();
// run type inference over all expressions
// type_infer::traverse_source_files(&t_ast.source_files, &t_ast.symbol_table)
// type_checker
// .infer_types(&t_ast.source_files)
// .map_err(|e| anyhow::Error::msg(format!("Type error: {e:?}")))?;
Ok(Builder {
arena: Arena::default(),
Expand Down Expand Up @@ -1257,6 +1260,9 @@ impl<'a> Builder<'a, InitState> {
node.utf8_text(code).unwrap().to_string()
};
let node = Rc::new(SimpleType::new(id, location, name));
node.type_info
.borrow_mut()
.replace(TypeInfo::new(&Type::Simple(node.clone())));
self.arena.add_node(
AstNode::Expression(Expression::Type(Type::Simple(node.clone()))),
parent_id,
Expand Down
88 changes: 87 additions & 1 deletion core/ast/src/nodes_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
nodes::{
ArgumentType, IgnoreArgument, SelfReference, StructExpression, TypeMemberAccessExpression,
},
type_info::{TypeInfo, TypeInfoKind},
type_info::{NumberTypeKindNumberType, TypeInfo, TypeInfoKind},
};

use super::nodes::{
Expand Down Expand Up @@ -120,6 +120,63 @@ impl BlockType {
| BlockType::Unique(block) => block.statements.clone(),
}
}
#[must_use]
pub fn is_non_det(&self) -> bool {
match self {
BlockType::Block(block) => block
.statements
.iter()
.any(super::nodes::Statement::is_non_det),
_ => true,
}
}
#[must_use]
pub fn is_void(&self) -> bool {
let fn_find_ret_stmt = |statements: &Vec<Statement>| -> bool {
for stmt in statements {
match stmt {
Statement::Return(_) => return true,
Statement::Block(block_type) => {
if block_type.is_void() {
return true;
}
}
_ => {}
}
}
false
};
!fn_find_ret_stmt(&self.statements())
}
}

impl Statement {
#[must_use]
pub fn is_non_det(&self) -> bool {
match self {
Statement::Block(block_type) => !matches!(block_type, BlockType::Block(_)),
Statement::Expression(expr_stmt) => expr_stmt.is_non_det(),
Statement::Return(ret_stmt) => ret_stmt.expression.borrow().is_non_det(),
Statement::Loop(loop_stmt) => loop_stmt
.condition
.borrow()
.as_ref()
.is_some_and(super::nodes::Expression::is_non_det),
Statement::If(if_stmt) => {
if_stmt.condition.borrow().is_non_det()
|| if_stmt.if_arm.is_non_det()
|| if_stmt
.else_arm
.as_ref()
.is_some_and(super::nodes::BlockType::is_non_det)
}
Statement::VariableDefinition(var_def) => var_def
.value
.as_ref()
.is_some_and(|value| value.borrow().is_non_det()),
_ => false,
}
}
}

impl Expression {
Expand All @@ -140,6 +197,10 @@ impl Expression {
Expression::Uzumaki(e) => e.type_info.borrow().clone(),
}
}
#[must_use]
pub fn is_non_det(&self) -> bool {
matches!(self, Expression::Uzumaki(_))
}
}

impl Literal {
Expand Down Expand Up @@ -345,6 +406,11 @@ impl FunctionDefinition {
.as_ref()
.is_none_or(super::nodes::Type::is_unit_type)
}

#[must_use]
pub fn is_non_det(&self) -> bool {
self.body.is_non_det()
}
}

impl ExternalFunctionDefinition {
Expand Down Expand Up @@ -690,6 +756,26 @@ impl UzumakiExpression {
type_info: RefCell::new(None),
}
}
#[must_use]
pub fn is_i32(&self) -> bool {
if let Some(type_info) = self.type_info.borrow().as_ref() {
return matches!(
type_info.kind,
TypeInfoKind::Number(NumberTypeKindNumberType::I32)
);
}
false
}
#[must_use]
pub fn is_i64(&self) -> bool {
if let Some(type_info) = self.type_info.borrow().as_ref() {
return matches!(
type_info.kind,
TypeInfoKind::Number(NumberTypeKindNumberType::I64)
);
}
false
}
}

impl AssertStatement {
Expand Down
34 changes: 33 additions & 1 deletion core/ast/src/t_ast.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
arena::Arena,
nodes::{AstNode, SourceFile},
nodes::{AstNode, Definition, Expression, SourceFile, Statement},
type_info::TypeInfo,
};

#[derive(Clone, Default)]
Expand All @@ -26,4 +27,35 @@ impl TypedAst {
.cloned()
.collect()
}

pub fn infer_expression_types(&self) {
//FIXME: very hacky way to infer Uzumaki expression types in return statements
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment acknowledges the implementation is hacky. Since type inference is a critical part of the compiler, consider filing an issue to refactor this properly or documenting what a proper solution would look like.

Copilot uses AI. Check for mistakes.
for function_def_node in
self.filter_nodes(|node| matches!(node, AstNode::Definition(Definition::Function(_))))
{
let AstNode::Definition(Definition::Function(function_def)) = function_def_node else {
unreachable!()
};
if function_def.is_void() {
continue;
}
if let Some(Statement::Return(last_stmt)) = function_def.body.statements().last() {
if !matches!(*last_stmt.expression.borrow(), Expression::Uzumaki(_)) {
continue;
}

match &*last_stmt.expression.borrow() {
Expression::Uzumaki(expr) => {
if expr.type_info.borrow().is_some() {
continue;
}
if let Some(return_type) = &function_def.returns {
expr.type_info.replace(Some(TypeInfo::new(return_type)));
}
}
_ => unreachable!(),
}
}
}
}
}
7 changes: 3 additions & 4 deletions core/wasm-codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ homepage = { workspace = true }
repository = { workspace = true }

[dependencies]
wasm-encoder="0.240.0"
inkwell = { version = "0.7.1", features = ["llvm21-1"] }
tempfile = "3.3.0"
which = "8.0.0"
inference-ast.workspace = true
anyhow.workspace = true

[dev-dependencies]
wasmtime="38.0.4"
109 changes: 109 additions & 0 deletions core/wasm-codegen/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::env;
use std::fs;
use std::path::PathBuf;

fn main() {
let platform = if cfg!(target_os = "linux") {
"linux"
} else if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "windows") {
"windows"
} else {
panic!("Unsupported platform");
};

let exe_suffix = std::env::consts::EXE_SUFFIX;
let llc_binary = format!("inf-llc{exe_suffix}");
let rust_lld_binary = format!("rust-lld{exe_suffix}");

let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_root = manifest_dir
.parent() // core/
.and_then(|p| p.parent()) // workspace root
.expect("Failed to determine workspace root");

let source_llc = workspace_root.join("bin").join(platform).join(&llc_binary);
let source_rust_lld = workspace_root
.join("bin")
.join(platform)
.join(&rust_lld_binary);

let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let target_profile_dir = out_dir
.parent() // build/<crate-name>-<hash>
.and_then(|p| p.parent()) // build/
.and_then(|p| p.parent()) // target/<profile>/
.expect("Failed to determine target profile directory");

let bin_dir = target_profile_dir.join("bin");
let dest_llc = bin_dir.join(&llc_binary);
let dest_rust_lld = bin_dir.join(&rust_lld_binary);

if source_llc.exists() {
if !bin_dir.exists() {
fs::create_dir_all(&bin_dir).expect("Failed to create bin directory");
}

fs::copy(&source_llc, &dest_llc).unwrap_or_else(|e| {
panic!(
"Failed to copy inf-llc from {} to {}: {}",
source_llc.display(),
dest_llc.display(),
e
)
});

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&dest_llc)
.expect("Failed to read inf-llc metadata")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&dest_llc, perms).expect("Failed to set executable permissions");
}

println!("cargo:info=Copied inf-llc to {}", dest_llc.display());
} else {
println!(
"cargo:info=inf-llc not found at {}, skipping copy",
source_llc.display()
);
}

if source_rust_lld.exists() {
if !bin_dir.exists() {
fs::create_dir_all(&bin_dir).expect("Failed to create bin directory");
}

fs::copy(&source_rust_lld, &dest_rust_lld).unwrap_or_else(|e| {
panic!(
"Failed to copy rust-lld from {} to {}: {}",
source_rust_lld.display(),
dest_rust_lld.display(),
e
)
});

#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&dest_rust_lld)
.expect("Failed to read rust-lld metadata")
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&dest_rust_lld, perms)
.expect("Failed to set executable permissions");
}

println!("cargo:info=Copied rust-lld to {}", dest_rust_lld.display());
} else {
println!(
"cargo:info=rust-lld not found at {}, skipping copy",
source_rust_lld.display()
);
}

println!("cargo:rerun-if-changed={}", source_llc.display());
}
Loading
Loading