diff --git a/README.md b/README.md index f10968cd..a3e2b982 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,49 @@ fun main() { --- +## Build From Source + +```bash +git clone https://github.com/wavefnd/Wave.git +cd Wave +cargo build +``` + +Compiler binary path: + +- `target/debug/wavec` (development build) +- `target/release/wavec` (release build) + +--- + +## Target Support + +- Linux `x86_64` +- macOS (Darwin) `arm64` (Apple Silicon) +- Windows: not supported yet + +--- + +## CLI Usage + +```bash +wavec run +wavec build +wavec build -o +wavec img +``` + +Useful global options: + +- `-O0..-O3`, `-Os`, `-Oz`, `-Ofast` +- `--debug-wave=tokens,ast,ir,mc,hex,all` +- `--link=` +- `-L ` +- `--dep-root=` +- `--dep==` + +--- +

Star History Chart diff --git a/front/lexer/src/ident.rs b/front/lexer/src/ident.rs index 4789c73c..d6dc47ac 100644 --- a/front/lexer/src/ident.rs +++ b/front/lexer/src/ident.rs @@ -14,7 +14,11 @@ use crate::{Lexer, Token}; impl<'a> Lexer<'a> { pub(crate) fn identifier(&mut self) -> String { - let start = if self.current > 0 { self.current - 1 } else { 0 }; + let start = if self.current > 0 { + self.current - 1 + } else { + 0 + }; while !self.is_at_end() { let c = self.peek(); @@ -50,6 +54,11 @@ impl<'a> Lexer<'a> { lexeme: "enum".to_string(), line: self.line, }, + "static" => Token { + token_type: TokenType::Static, + lexeme: "static".to_string(), + line: self.line, + }, "var" => Token { token_type: TokenType::Var, lexeme: "var".to_string(), @@ -135,6 +144,11 @@ impl<'a> Lexer<'a> { lexeme: "is".to_string(), line: self.line, }, + "as" => Token { + token_type: TokenType::As, + lexeme: "as".to_string(), + line: self.line, + }, "asm" => Token { token_type: TokenType::Asm, lexeme: "asm".to_string(), diff --git a/front/lexer/src/token.rs b/front/lexer/src/token.rs index 7a128f2c..ba1178e2 100644 --- a/front/lexer/src/token.rs +++ b/front/lexer/src/token.rs @@ -83,6 +83,7 @@ pub enum TokenType { Extern, Type, Enum, + Static, Var, Let, Mut, @@ -118,6 +119,7 @@ pub enum TokenType { In, // in Out, // out Is, // is + As, // as Asm, Rol, Ror, diff --git a/front/parser/src/ast.rs b/front/parser/src/ast.rs index 5a36e937..eb300bae 100644 --- a/front/parser/src/ast.rs +++ b/front/parser/src/ast.rs @@ -178,6 +178,10 @@ pub enum Expression { operator: Operator, expr: Box, }, + Cast { + expr: Box, + target_type: WaveType, + }, IncDec { kind: IncDecKind, target: Box, @@ -300,6 +304,7 @@ pub enum StatementNode { #[derive(Debug, Clone, Copy, PartialEq)] pub enum Mutability { + Static, Var, Let, LetMut, @@ -354,17 +359,16 @@ impl Expression { Expression::Unary { operator, expr } => { let t = expr.get_wave_type(variables); match operator { - Operator::Neg => { - match &t { - WaveType::Int(_) | WaveType::Uint(_) | WaveType::Float(_) => t, - _ => panic!("unary '-' not allowed for type {:?}", t), - } - } + Operator::Neg => match &t { + WaveType::Int(_) | WaveType::Uint(_) | WaveType::Float(_) => t, + _ => panic!("unary '-' not allowed for type {:?}", t), + }, Operator::Not | Operator::LogicalNot => WaveType::Bool, Operator::BitwiseNot => t, _ => panic!("unary op type inference not supported: {:?}", operator), } } + Expression::Cast { target_type, .. } => target_type.clone(), _ => panic!("get_wave_type not implemented for {:?}", self), } } diff --git a/front/parser/src/expr/binary.rs b/front/parser/src/expr/binary.rs index 8f36b4f2..9784828d 100644 --- a/front/parser/src/expr/binary.rs +++ b/front/parser/src/expr/binary.rs @@ -9,10 +9,11 @@ // // SPDX-License-Identifier: MPL-2.0 -use lexer::Token; -use lexer::token::TokenType; use crate::ast::{Expression, Operator}; use crate::expr::unary::parse_unary_expression; +use crate::types::parse_type_from_stream; +use lexer::token::TokenType; +use lexer::Token; pub fn parse_logical_or_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option where @@ -20,7 +21,10 @@ where { let mut left = parse_logical_and_expression(tokens)?; - while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::LogicalOr)) { + while matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::LogicalOr) + ) { tokens.next(); let right = parse_logical_and_expression(tokens)?; left = Expression::BinaryExpression { @@ -33,13 +37,18 @@ where Some(left) } -pub fn parse_logical_and_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option +pub fn parse_logical_and_expression<'a, T>( + tokens: &mut std::iter::Peekable, +) -> Option where T: Iterator, { let mut left = parse_bitwise_or_expression(tokens)?; - while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::LogicalAnd)) { + while matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::LogicalAnd) + ) { tokens.next(); let right = parse_bitwise_or_expression(tokens)?; left = Expression::BinaryExpression { @@ -58,7 +67,10 @@ where { let mut left = parse_bitwise_xor_expression(tokens)?; - while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::BitwiseOr)) { + while matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::BitwiseOr) + ) { tokens.next(); let right = parse_bitwise_xor_expression(tokens)?; left = Expression::BinaryExpression { @@ -71,7 +83,9 @@ where Some(left) } -pub fn parse_bitwise_xor_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option +pub fn parse_bitwise_xor_expression<'a, T>( + tokens: &mut std::iter::Peekable, +) -> Option where T: Iterator, { @@ -90,13 +104,18 @@ where Some(left) } -pub fn parse_bitwise_and_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option +pub fn parse_bitwise_and_expression<'a, T>( + tokens: &mut std::iter::Peekable, +) -> Option where T: Iterator, { let mut left = parse_equality_expression(tokens)?; - while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::AddressOf)) { + while matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::AddressOf) + ) { tokens.next(); let right = parse_equality_expression(tokens)?; left = Expression::BinaryExpression { @@ -208,11 +227,13 @@ where Some(left) } -pub fn parse_multiplicative_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option +pub fn parse_multiplicative_expression<'a, T>( + tokens: &mut std::iter::Peekable, +) -> Option where T: Iterator, { - let mut left = parse_unary_expression(tokens)?; + let mut left = parse_cast_expression(tokens)?; while let Some(token) = tokens.peek() { let op = match token.token_type { @@ -222,7 +243,7 @@ where _ => break, }; tokens.next(); - let right = parse_unary_expression(tokens)?; + let right = parse_cast_expression(tokens)?; left = Expression::BinaryExpression { left: Box::new(left), operator: op, @@ -231,4 +252,22 @@ where } Some(left) -} \ No newline at end of file +} + +fn parse_cast_expression<'a, T>(tokens: &mut std::iter::Peekable) -> Option +where + T: Iterator, +{ + let mut expr = parse_unary_expression(tokens)?; + + while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::As)) { + tokens.next(); // consume `as` + let target_type = parse_type_from_stream(tokens)?; + expr = Expression::Cast { + expr: Box::new(expr), + target_type, + }; + } + + Some(expr) +} diff --git a/front/parser/src/import.rs b/front/parser/src/import.rs index 33920402..0ca6d850 100644 --- a/front/parser/src/import.rs +++ b/front/parser/src/import.rs @@ -10,7 +10,7 @@ // SPDX-License-Identifier: MPL-2.0 use crate::ast::{ASTNode}; -use crate::{parse, ParseError}; +use crate::{parse_syntax_only, ParseError}; use error::error::{WaveError, WaveErrorKind}; use lexer::Lexer; use std::collections::{HashMap, HashSet}; @@ -347,7 +347,7 @@ fn parse_wave_file( let mut lexer = Lexer::new_with_file(&content, abs_path.display().to_string()); let tokens = lexer.tokenize()?; - let ast = parse(&tokens).map_err(|e| { + let ast = parse_syntax_only(&tokens).map_err(|e| { let (kind, phase, code) = match &e { ParseError::Syntax(_) => (WaveErrorKind::SyntaxError(e.message().to_string()), "syntax", "E2001"), ParseError::Semantic(_) => (WaveErrorKind::InvalidStatement(e.message().to_string()), "semantic", "E3001"), diff --git a/front/parser/src/parser/control.rs b/front/parser/src/parser/control.rs index 3dbaed85..a33ab3fd 100644 --- a/front/parser/src/parser/control.rs +++ b/front/parser/src/parser/control.rs @@ -230,8 +230,12 @@ fn parse_for_initializer(tokens: &mut Peekable>) -> Option parse_typed_for_initializer(tokens, mutability) } Some(TokenType::Const) => { - tokens.next(); // consume `const` - parse_typed_for_initializer(tokens, Mutability::Const) + println!("Error: `const` is not allowed in local for-loop initializer"); + None + } + Some(TokenType::Static) => { + println!("Error: `static` is not allowed in local for-loop initializer"); + None } _ if is_typed_for_initializer(tokens) => parse_typed_for_initializer(tokens, Mutability::Var), _ => { diff --git a/front/parser/src/parser/decl.rs b/front/parser/src/parser/decl.rs index 0d92bc39..a822b056 100644 --- a/front/parser/src/parser/decl.rs +++ b/front/parser/src/parser/decl.rs @@ -9,16 +9,22 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::TokenType; -use crate::ast::{ASTNode, EnumNode, EnumVariantNode, Expression, ExternFunctionNode, Mutability, TypeAliasNode, VariableNode, WaveType}; +use crate::ast::{ + ASTNode, EnumNode, EnumVariantNode, Expression, ExternFunctionNode, Mutability, TypeAliasNode, + VariableNode, WaveType, +}; use crate::expr::parse_expression; use crate::parser::types::{parse_type, token_type_to_wave_type}; use crate::types::parse_type_from_stream; +use lexer::token::TokenType; +use lexer::Token; +use std::iter::Peekable; +use std::slice::Iter; -pub fn collect_generic_inner(tokens: &mut Peekable>) -> Option { +pub fn collect_generic_inner<'a, T>(tokens: &mut Peekable) -> Option +where + T: Iterator, +{ let mut inner = String::new(); let mut depth: i32 = 1; @@ -70,8 +76,10 @@ pub fn collect_generic_inner(tokens: &mut Peekable>) -> Option>, is_const: bool) -> Option { +pub fn parse_variable_decl( + tokens: &mut Peekable>, + is_const: bool, +) -> Option { let mut mutability = if is_const { Mutability::Const } else { @@ -80,9 +88,9 @@ pub fn parse_variable_decl(tokens: &mut Peekable>, is_const: boo if !is_const { if let Some(Token { - token_type: TokenType::Mut, - .. - }) = tokens.peek() + token_type: TokenType::Mut, + .. + }) = tokens.peek() { tokens.next(); // consume `mut` mutability = Mutability::LetMut; @@ -91,9 +99,9 @@ pub fn parse_variable_decl(tokens: &mut Peekable>, is_const: boo let name = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), _ => { println!( "Expected identifier after `{}`", @@ -118,9 +126,9 @@ pub fn parse_variable_decl(tokens: &mut Peekable>, is_const: boo let wave_type = if let TokenType::Identifier(ref name) = type_token.token_type { if let Some(Token { - token_type: TokenType::Lchevr, - .. - }) = tokens.peek() + token_type: TokenType::Lchevr, + .. + }) = tokens.peek() { tokens.next(); // consume '<' @@ -160,9 +168,9 @@ pub fn parse_variable_decl(tokens: &mut Peekable>, is_const: boo }; let initial_value = if let Some(Token { - token_type: TokenType::Equal, - .. - }) = tokens.peek() + token_type: TokenType::Equal, + .. + }) = tokens.peek() { tokens.next(); // consume '=' let expr = parse_expression(tokens)?; @@ -202,6 +210,15 @@ pub fn parse_const(tokens: &mut Peekable>) -> Option { parse_variable_decl(tokens, true) } +pub fn parse_static(tokens: &mut Peekable>) -> Option { + let node = parse_var(tokens)?; + let ASTNode::Variable(mut v) = node else { + return None; + }; + v.mutability = Mutability::Static; + Some(ASTNode::Variable(v)) +} + pub fn parse_let(tokens: &mut Peekable>) -> Option { parse_variable_decl(tokens, false) } @@ -212,9 +229,9 @@ pub fn parse_var(tokens: &mut Peekable>) -> Option { let name = match tokens.next() { Some(Token { - token_type: TokenType::Identifier(name), - .. - }) => name.clone(), + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), _ => { println!("Expected identifier"); return None; @@ -236,9 +253,9 @@ pub fn parse_var(tokens: &mut Peekable>) -> Option { let wave_type = if let TokenType::Identifier(ref name) = type_token.token_type { if let Some(Token { - token_type: TokenType::Lchevr, - .. - }) = tokens.peek() + token_type: TokenType::Lchevr, + .. + }) = tokens.peek() { tokens.next(); // consume '<' @@ -278,9 +295,9 @@ pub fn parse_var(tokens: &mut Peekable>) -> Option { }; let initial_value = if let Some(Token { - token_type: TokenType::Equal, - .. - }) = tokens.peek() + token_type: TokenType::Equal, + .. + }) = tokens.peek() { tokens.next(); // consume '=' let expr = parse_expression(tokens)?; @@ -348,9 +365,15 @@ fn parse_extern_header(tokens: &mut Peekable>) -> Option<(String skip_ws(tokens); let abi = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(name), .. }) => name.clone(), + Some(Token { + token_type: TokenType::Identifier(name), + .. + }) => name.clone(), other => { - println!("Error: Expected ABI identifier in extern(...), found {:?}", other); + println!( + "Error: Expected ABI identifier in extern(...), found {:?}", + other + ); return None; } }; @@ -364,9 +387,15 @@ fn parse_extern_header(tokens: &mut Peekable>) -> Option<(String skip_ws(tokens); global_symbol = match tokens.next() { - Some(Token { token_type: TokenType::String(s), .. }) => Some(s.clone()), + Some(Token { + token_type: TokenType::String(s), + .. + }) => Some(s.clone()), other => { - println!("Error: Expected string literal after ',' in extern(...), found {:?}", other); + println!( + "Error: Expected string literal after ',' in extern(...), found {:?}", + other + ); return None; } }; @@ -374,7 +403,11 @@ fn parse_extern_header(tokens: &mut Peekable>) -> Option<(String skip_ws(tokens); } - if !expect(tokens, TokenType::Rparen, "Expected ')' to close extern(...)") { + if !expect( + tokens, + TokenType::Rparen, + "Expected ')' to close extern(...)", + ) { return None; } @@ -403,7 +436,12 @@ fn parse_extern_fun_decl( // 'fun' match tokens.peek() { - Some(Token { token_type: TokenType::Fun, .. }) => { tokens.next(); } + Some(Token { + token_type: TokenType::Fun, + .. + }) => { + tokens.next(); + } other => { println!("Error: Expected 'fun' in extern block, found {:?}", other); return None; @@ -414,16 +452,26 @@ fn parse_extern_fun_decl( // name let name = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + Some(Token { + token_type: TokenType::Identifier(n), + .. + }) => n.clone(), other => { - println!("Error: Expected function name after 'fun', found {:?}", other); + println!( + "Error: Expected function name after 'fun', found {:?}", + other + ); return None; } }; skip_ws(tokens); - if !expect(tokens, TokenType::Lparen, "Expected '(' after extern function name") { + if !expect( + tokens, + TokenType::Lparen, + "Expected '(' after extern function name", + ) { return None; } @@ -441,14 +489,20 @@ fn parse_extern_fun_decl( // named param? (Identifier ... :) let is_named = match tokens.peek() { - Some(Token { token_type: TokenType::Identifier(_), .. }) => { + Some(Token { + token_type: TokenType::Identifier(_), + .. + }) => { let _next_ty = peek_non_ws_token_type(tokens); - if let Some(TokenType::Identifier(_)) = tokens.peek().map(|t| t.token_type.clone()) { + if let Some(TokenType::Identifier(_)) = tokens.peek().map(|t| t.token_type.clone()) + { let mut la = tokens.clone(); la.next(); // consume identifier while let Some(t) = la.peek() { match t.token_type { - TokenType::Whitespace | TokenType::Newline => { la.next(); } + TokenType::Whitespace | TokenType::Newline => { + la.next(); + } _ => break, } } @@ -462,7 +516,11 @@ fn parse_extern_fun_decl( if is_named { // name : - let param_name = if let Some(Token { token_type: TokenType::Identifier(n), .. }) = tokens.next() { + let param_name = if let Some(Token { + token_type: TokenType::Identifier(n), + .. + }) = tokens.next() + { n.clone() } else { unreachable!(); @@ -470,7 +528,11 @@ fn parse_extern_fun_decl( skip_ws(tokens); - if !expect(tokens, TokenType::Colon, "Expected ':' after parameter name in extern function") { + if !expect( + tokens, + TokenType::Colon, + "Expected ':' after parameter name in extern function", + ) { return None; } @@ -512,7 +574,10 @@ fn parse_extern_fun_decl( break; } other => { - println!("Error: Expected ',' or ')' in extern parameter list, found {:?}", other); + println!( + "Error: Expected ',' or ')' in extern parameter list, found {:?}", + other + ); return None; } } @@ -541,7 +606,11 @@ fn parse_extern_fun_decl( // per-function symbol: optional string literal let mut symbol: Option = None; - if let Some(Token { token_type: TokenType::String(s), .. }) = tokens.peek() { + if let Some(Token { + token_type: TokenType::String(s), + .. + }) = tokens.peek() + { let s = s.clone(); tokens.next(); symbol = Some(s); @@ -554,7 +623,11 @@ fn parse_extern_fun_decl( skip_ws(tokens); - if !expect(tokens, TokenType::SemiColon, "Expected ';' after extern function declaration") { + if !expect( + tokens, + TokenType::SemiColon, + "Expected ';' after extern function declaration", + ) { return None; } @@ -618,7 +691,10 @@ pub fn parse_extern(tokens: &mut Peekable>) -> Option>) -> Option { // type = ; let name = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + Some(Token { + token_type: TokenType::Identifier(n), + .. + }) => n.clone(), other => { println!("Error: Expected identifier after 'type', found {:?}", other); return None; @@ -626,7 +702,10 @@ pub fn parse_type_alias(tokens: &mut Peekable>) -> Option {} + Some(Token { + token_type: TokenType::Equal, + .. + }) => {} other => { println!("Error: Expected '=' in type alias, found {:?}", other); return None; @@ -642,7 +721,10 @@ pub fn parse_type_alias(tokens: &mut Peekable>) -> Option {} + Some(Token { + token_type: TokenType::SemiColon, + .. + }) => {} other => { println!("Error: Expected ';' after type alias, found {:?}", other); return None; @@ -665,7 +747,10 @@ fn token_text(tok: &Token) -> Option { pub fn parse_enum(tokens: &mut Peekable>) -> Option { // enum -> { (=)? (, ...)* } let name = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + Some(Token { + token_type: TokenType::Identifier(n), + .. + }) => n.clone(), other => { println!("Error: Expected enum name after 'enum', found {:?}", other); return None; @@ -673,7 +758,10 @@ pub fn parse_enum(tokens: &mut Peekable>) -> Option { }; match tokens.next() { - Some(Token { token_type: TokenType::Arrow, .. }) => {} + Some(Token { + token_type: TokenType::Arrow, + .. + }) => {} other => { println!("Error: Expected '->' after enum name, found {:?}", other); return None; @@ -689,7 +777,10 @@ pub fn parse_enum(tokens: &mut Peekable>) -> Option { }; match tokens.next() { - Some(Token { token_type: TokenType::Lbrace, .. }) => {} + Some(Token { + token_type: TokenType::Lbrace, + .. + }) => {} other => { println!("Error: Expected '{{' to start enum body, found {:?}", other); return None; @@ -715,7 +806,10 @@ pub fn parse_enum(tokens: &mut Peekable>) -> Option { TokenType::Identifier(_) => { // variant name let vname = match tokens.next() { - Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + Some(Token { + token_type: TokenType::Identifier(n), + .. + }) => n.clone(), _ => unreachable!(), }; @@ -727,7 +821,10 @@ pub fn parse_enum(tokens: &mut Peekable>) -> Option { let val_tok = match tokens.next() { Some(t) => t, None => { - println!("Error: Expected integer literal after '=' in enum '{}'", name); + println!( + "Error: Expected integer literal after '=' in enum '{}'", + name + ); return None; } }; diff --git a/front/parser/src/parser/functions.rs b/front/parser/src/parser/functions.rs index 4755eaf3..52fa50f5 100644 --- a/front/parser/src/parser/functions.rs +++ b/front/parser/src/parser/functions.rs @@ -203,6 +203,14 @@ pub fn extract_body(tokens: &mut Peekable>) -> Option> tokens.next(); // consume 'let' body.push(parse_let(tokens)?); } + TokenType::Const => { + println!("Error: `const` is only allowed at top level"); + return None; + } + TokenType::Static => { + println!("Error: `static` is only allowed at top level"); + return None; + } TokenType::Println => { tokens.next(); // consume 'println' let node = parse_println(tokens)?; diff --git a/front/parser/src/parser/mod.rs b/front/parser/src/parser/mod.rs index 7687afd3..f0f2cec5 100644 --- a/front/parser/src/parser/mod.rs +++ b/front/parser/src/parser/mod.rs @@ -20,4 +20,4 @@ pub mod items; pub mod stmt; pub mod types; -pub use parse::{parse, ParseError}; +pub use parse::{parse, parse_syntax_only, ParseError}; diff --git a/front/parser/src/parser/parse.rs b/front/parser/src/parser/parse.rs index 33f4681f..7d8eea92 100644 --- a/front/parser/src/parser/parse.rs +++ b/front/parser/src/parser/parse.rs @@ -188,7 +188,7 @@ impl ParseError { } } -pub fn parse(tokens: &[Token]) -> Result, ParseError> { +pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { let mut iter = tokens.iter().peekable(); let mut nodes = vec![]; @@ -246,6 +246,21 @@ pub fn parse(tokens: &[Token]) -> Result, ParseError> { ); } } + TokenType::Static => { + let anchor = (*token).clone(); + iter.next(); + if let Some(var) = parse_static(&mut iter) { + nodes.push(var); + } else { + return Err( + ParseError::syntax_at(Some(&anchor), "failed to parse static declaration") + .with_context("top-level static declaration") + .with_expected("static name: type = value;") + .with_found_token(iter.peek().copied()) + .with_help("static declarations require an explicit type"), + ); + } + } TokenType::Proto => { let anchor = (*token).clone(); iter.next(); @@ -332,6 +347,7 @@ pub fn parse(tokens: &[Token]) -> Result, ParseError> { "import", "extern", "const", + "static", "type", "enum", "struct", @@ -345,6 +361,12 @@ pub fn parse(tokens: &[Token]) -> Result, ParseError> { } } + Ok(nodes) +} + +pub fn parse(tokens: &[Token]) -> Result, ParseError> { + let nodes = parse_syntax_only(tokens)?; + if let Err(e) = validate_program(&nodes) { return Err( ParseError::semantic(e) diff --git a/front/parser/src/parser/stmt.rs b/front/parser/src/parser/stmt.rs index 5a053952..aec71553 100644 --- a/front/parser/src/parser/stmt.rs +++ b/front/parser/src/parser/stmt.rs @@ -16,7 +16,7 @@ use lexer::token::TokenType; use crate::ast::{ASTNode, AssignOperator, Expression, Operator, StatementNode}; use crate::expr::{is_assignable, parse_expression, parse_expression_from_token}; use crate::parser::control::{parse_for, parse_if, parse_match, parse_while}; -use crate::parser::decl::{parse_const, parse_let, parse_var}; +use crate::parser::decl::{parse_let, parse_var}; use crate::parser::io::*; use crate::parser::types::is_expression_start; @@ -174,8 +174,12 @@ pub fn parse_statement(tokens: &mut Peekable>) -> Option { parse_let(tokens) } TokenType::Const => { - tokens.next(); - parse_const(tokens) + println!("Error: `const` is only allowed at top level"); + None + } + TokenType::Static => { + println!("Error: `static` is only allowed at top level"); + None } TokenType::Println => { tokens.next(); diff --git a/front/parser/src/parser/types.rs b/front/parser/src/parser/types.rs index a61b9275..a694df04 100644 --- a/front/parser/src/parser/types.rs +++ b/front/parser/src/parser/types.rs @@ -9,12 +9,11 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::iter::Peekable; -use std::slice::Iter; -use lexer::Token; -use lexer::token::*; use crate::ast::WaveType; use crate::decl::collect_generic_inner; +use lexer::token::*; +use lexer::Token; +use std::iter::Peekable; pub fn token_type_to_wave_type(token_type: &TokenType) -> Option { match token_type { @@ -223,21 +222,31 @@ pub fn parse_type_from_token(token_opt: Option<&&Token>) -> Option { } } -pub fn parse_type_from_stream(tokens: &mut Peekable>) -> Option { - while matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Whitespace)) { +pub fn parse_type_from_stream<'a, T>(tokens: &mut Peekable) -> Option +where + T: Iterator, +{ + while matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::Whitespace) + ) { tokens.next(); } let type_token = tokens.next()?; if let TokenType::Identifier(name) = &type_token.token_type { - while matches!(tokens.peek().map(|t| &t.token_type), + while matches!( + tokens.peek().map(|t| &t.token_type), Some(TokenType::Whitespace | TokenType::Newline) ) { tokens.next(); } - if matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Lchevr)) { + if matches!( + tokens.peek().map(|t| &t.token_type), + Some(TokenType::Lchevr) + ) { tokens.next(); // consume '<' let inner = collect_generic_inner(tokens)?; diff --git a/front/parser/src/verification.rs b/front/parser/src/verification.rs index 06327a0b..549d588b 100644 --- a/front/parser/src/verification.rs +++ b/front/parser/src/verification.rs @@ -9,8 +9,8 @@ // // SPDX-License-Identifier: MPL-2.0 -use std::collections::{HashMap, HashSet}; use crate::ast::{ASTNode, Expression, MatchPattern, Mutability, StatementNode}; +use std::collections::{HashMap, HashSet}; fn lookup_mutability( name: &str, @@ -56,7 +56,10 @@ fn ensure_mutable_write_target( if let Some(m) = lookup_mutability(&base, scopes, globals) { match m { Mutability::Let | Mutability::Const => { - return Err(format!("cannot {} immutable binding `{}` ({:?})", why, base, m)); + return Err(format!( + "cannot {} immutable binding `{}` ({:?})", + why, base, m + )); } _ => {} } @@ -94,6 +97,7 @@ fn validate_expr( } Expression::Unary { expr, .. } => validate_expr(expr, scopes, globals)?, + Expression::Cast { expr, .. } => validate_expr(expr, scopes, globals)?, Expression::FunctionCall { args, .. } => { for a in args { @@ -126,7 +130,9 @@ fn validate_expr( } } - Expression::AsmBlock { inputs, outputs, .. } => { + Expression::AsmBlock { + inputs, outputs, .. + } => { for (_, e) in inputs { validate_expr(e, scopes, globals)?; } @@ -177,8 +183,7 @@ fn validate_node( validate_expr(value, scopes, globals)?; } - StatementNode::PrintlnFormat { args, .. } - | StatementNode::PrintFormat { args, .. } => { + StatementNode::PrintlnFormat { args, .. } | StatementNode::PrintFormat { args, .. } => { for a in args { validate_expr(a, scopes, globals)?; } @@ -312,8 +317,8 @@ pub fn validate_program(nodes: &Vec) -> Result<(), String> { for n in nodes { match n { ASTNode::Variable(v) => { - if v.mutability == Mutability::Const { - globals.insert(v.name.clone(), Mutability::Const); + if matches!(v.mutability, Mutability::Const | Mutability::Static) { + globals.insert(v.name.clone(), v.mutability); } } diff --git a/llvm/src/codegen/abi_c.rs b/llvm/src/codegen/abi_c.rs index 406ff393..4e103e6e 100644 --- a/llvm/src/codegen/abi_c.rs +++ b/llvm/src/codegen/abi_c.rs @@ -10,16 +10,17 @@ // SPDX-License-Identifier: MPL-2.0 // src/llvm_temporary/llvm_codegen/abi_c.rs -use std::collections::HashMap; -use inkwell::AddressSpace; use inkwell::attributes::{Attribute, AttributeLoc}; use inkwell::context::Context; use inkwell::targets::TargetData; use inkwell::types::{AnyType, AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; use inkwell::values::FunctionValue; +use inkwell::AddressSpace; +use std::collections::HashMap; use parser::ast::{ExternFunctionNode, WaveType}; +use super::target::CodegenTarget; use super::types::{wave_type_to_llvm_type, TypeFlavor}; #[derive(Clone)] @@ -38,8 +39,8 @@ pub enum RetLowering<'ctx> { #[derive(Clone)] pub struct ExternCInfo<'ctx> { - pub llvm_name: String, // actual LLVM symbol name - pub wave_ret: WaveType, // Wave-level return type (needed when sret => llvm void) + pub llvm_name: String, // actual LLVM symbol name + pub wave_ret: WaveType, // Wave-level return type (needed when sret => llvm void) pub ret: RetLowering<'ctx>, pub params: Vec>, // per-wave param pub llvm_param_types: Vec>, // final lowered param list (including sret ptr, split, byval ptr) @@ -61,11 +62,11 @@ fn is_float_ty<'ctx>(td: &TargetData, t: BasicTypeEnum<'ctx>) -> Option { fn any_ptr_basic<'ctx>(ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> { let aspace = AddressSpace::default(); match ty { - AnyTypeEnum::ArrayType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::FloatType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::FunctionType(t)=> t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::IntType(t) => t.ptr_type(aspace).as_basic_type_enum(), - AnyTypeEnum::PointerType(t)=> t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::ArrayType(t) => t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::FloatType(t) => t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::FunctionType(t) => t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::IntType(t) => t.ptr_type(aspace).as_basic_type_enum(), + AnyTypeEnum::PointerType(t) => t.ptr_type(aspace).as_basic_type_enum(), AnyTypeEnum::StructType(t) => t.ptr_type(aspace).as_basic_type_enum(), AnyTypeEnum::VectorType(t) => t.ptr_type(aspace).as_basic_type_enum(), _ => panic!("unsupported AnyTypeEnum for ptr"), @@ -90,7 +91,7 @@ fn flatten_leaf_types<'ctx>(t: BasicTypeEnum<'ctx>, out: &mut Vec( +fn classify_param_x86_64_sysv<'ctx>( context: &'ctx Context, td: &TargetData, t: BasicTypeEnum<'ctx>, @@ -98,13 +99,24 @@ fn classify_param<'ctx>( let size = td.get_store_size(&t) as u64; // large aggregates => byval - if matches!(t, BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)) && size > 16 { + if matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ) && size > 16 + { let align = td.get_abi_alignment(&t) as u32; - return ParamLowering::ByVal { ty: t.as_any_type_enum(), align }; + return ParamLowering::ByVal { + ty: t.as_any_type_enum(), + align, + }; } // small aggregates: try integer-only or homogeneous float - if matches!(t, BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)) && size <= 16 { + if matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ) && size <= 16 + { let mut leaves = vec![]; flatten_leaf_types(t, &mut leaves); @@ -136,20 +148,29 @@ fn classify_param<'ctx>( f.vec_type(2).as_basic_type_enum(), f.as_basic_type_enum(), ]), - 4 => ParamLowering::Direct(f.vec_type(4).as_basic_type_enum()), + 4 => ParamLowering::Split(vec![ + f.vec_type(2).as_basic_type_enum(), + f.vec_type(2).as_basic_type_enum(), + ]), _ => { let align = td.get_abi_alignment(&t) as u32; - ParamLowering::ByVal { ty: t.as_any_type_enum(), align } + ParamLowering::ByVal { + ty: t.as_any_type_enum(), + align, + } } }; } else if fsz == 8 { let f = context.f64_type(); return match count { 1 => ParamLowering::Direct(f.as_basic_type_enum()), - 2 => ParamLowering::Direct(f.vec_type(2).as_basic_type_enum()), + 2 => ParamLowering::Split(vec![f.as_basic_type_enum(), f.as_basic_type_enum()]), _ => { let align = td.get_abi_alignment(&t) as u32; - ParamLowering::ByVal { ty: t.as_any_type_enum(), align } + ParamLowering::ByVal { + ty: t.as_any_type_enum(), + align, + } } }; } @@ -160,13 +181,27 @@ fn classify_param<'ctx>( for lt in &leaves { match lt { BasicTypeEnum::IntType(_) | BasicTypeEnum::PointerType(_) => {} - _ => { all_intlike = false; break; } + _ => { + all_intlike = false; + break; + } } } if all_intlike { - let bits = (size * 8) as u32; - let it = context.custom_width_int_type(bits); - return ParamLowering::Direct(it.as_basic_type_enum()); + if size <= 8 { + let bits = (size * 8) as u32; + let it = context.custom_width_int_type(bits); + return ParamLowering::Direct(it.as_basic_type_enum()); + } + + let rem_bits = ((size - 8) * 8) as u32; + let hi = context.i64_type().as_basic_type_enum(); + let lo = if rem_bits == 64 { + context.i64_type().as_basic_type_enum() + } else { + context.custom_width_int_type(rem_bits).as_basic_type_enum() + }; + return ParamLowering::Split(vec![hi, lo]); } // mixed small aggregate: keep as direct aggregate value. @@ -178,19 +213,27 @@ fn classify_param<'ctx>( ParamLowering::Direct(t) } -fn classify_ret<'ctx>( +fn classify_ret_x86_64_sysv<'ctx>( context: &'ctx Context, td: &TargetData, t: Option>, ) -> RetLowering<'ctx> { - let Some(t) = t else { return RetLowering::Void; }; + let Some(t) = t else { + return RetLowering::Void; + }; let size = td.get_store_size(&t) as u64; - let is_agg = matches!(t, BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)); + let is_agg = matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ); if is_agg && size > 16 { let align = td.get_abi_alignment(&t) as u32; - return RetLowering::SRet { ty: t.as_any_type_enum(), align }; + return RetLowering::SRet { + ty: t.as_any_type_enum(), + align, + }; } if is_agg && size <= 16 { @@ -202,13 +245,28 @@ fn classify_ret<'ctx>( for lt in &leaves { match lt { BasicTypeEnum::IntType(_) | BasicTypeEnum::PointerType(_) => {} - _ => { all_intlike = false; break; } + _ => { + all_intlike = false; + break; + } } } if all_intlike { - let bits = (size * 8) as u32; - let it = context.custom_width_int_type(bits); - return RetLowering::Direct(it.as_basic_type_enum()); + if size <= 8 { + let bits = (size * 8) as u32; + let it = context.custom_width_int_type(bits); + return RetLowering::Direct(it.as_basic_type_enum()); + } + + let rem_bits = ((size - 8) * 8) as u32; + let hi = context.i64_type().as_basic_type_enum(); + let lo = if rem_bits == 64 { + context.i64_type().as_basic_type_enum() + } else { + context.custom_width_int_type(rem_bits).as_basic_type_enum() + }; + let tuple = context.struct_type(&[hi, lo], false).as_basic_type_enum(); + return RetLowering::Direct(tuple); } // homogeneous float ret (2 or 4 only to avoid multi-reg return complexity) @@ -217,8 +275,14 @@ fn classify_ret<'ctx>( for lt in &leaves { if let Some(sz) = is_float_ty(td, *lt) { float_kind.get_or_insert(sz); - if float_kind != Some(sz) { all_float = false; break; } - } else { all_float = false; break; } + if float_kind != Some(sz) { + all_float = false; + break; + } + } else { + all_float = false; + break; + } } if all_float { let count = leaves.len(); @@ -227,10 +291,33 @@ fn classify_ret<'ctx>( return match count { 1 => RetLowering::Direct(f.as_basic_type_enum()), 2 => RetLowering::Direct(f.vec_type(2).as_basic_type_enum()), - 4 => RetLowering::Direct(f.vec_type(4).as_basic_type_enum()), + 3 => { + let tuple = context + .struct_type( + &[f.vec_type(2).as_basic_type_enum(), f.as_basic_type_enum()], + false, + ) + .as_basic_type_enum(); + RetLowering::Direct(tuple) + } + 4 => { + let tuple = context + .struct_type( + &[ + f.vec_type(2).as_basic_type_enum(), + f.vec_type(2).as_basic_type_enum(), + ], + false, + ) + .as_basic_type_enum(); + RetLowering::Direct(tuple) + } _ => { let align = td.get_abi_alignment(&t) as u32; - RetLowering::SRet { ty: t.as_any_type_enum(), align } + RetLowering::SRet { + ty: t.as_any_type_enum(), + align, + } } }; } @@ -238,10 +325,18 @@ fn classify_ret<'ctx>( let f = context.f64_type(); return match count { 1 => RetLowering::Direct(f.as_basic_type_enum()), - 2 => RetLowering::Direct(f.vec_type(2).as_basic_type_enum()), + 2 => { + let tuple = context + .struct_type(&[f.as_basic_type_enum(), f.as_basic_type_enum()], false) + .as_basic_type_enum(); + RetLowering::Direct(tuple) + } _ => { let align = td.get_abi_alignment(&t) as u32; - RetLowering::SRet { ty: t.as_any_type_enum(), align } + RetLowering::SRet { + ty: t.as_any_type_enum(), + align, + } } }; } @@ -255,29 +350,110 @@ fn classify_ret<'ctx>( RetLowering::Direct(t) } +fn classify_param_arm64_darwin<'ctx>( + td: &TargetData, + t: BasicTypeEnum<'ctx>, +) -> ParamLowering<'ctx> { + let size = td.get_store_size(&t) as u64; + let is_agg = matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ); + + if is_agg && size > 16 { + let align = td.get_abi_alignment(&t) as u32; + return ParamLowering::ByVal { + ty: t.as_any_type_enum(), + align, + }; + } + + ParamLowering::Direct(t) +} + +fn classify_ret_arm64_darwin<'ctx>( + td: &TargetData, + t: Option>, +) -> RetLowering<'ctx> { + let Some(t) = t else { + return RetLowering::Void; + }; + let size = td.get_store_size(&t) as u64; + let is_agg = matches!( + t, + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_) + ); + + if is_agg && size > 16 { + let align = td.get_abi_alignment(&t) as u32; + return RetLowering::SRet { + ty: t.as_any_type_enum(), + align, + }; + } + + RetLowering::Direct(t) +} + +fn classify_param<'ctx>( + context: &'ctx Context, + td: &TargetData, + target: CodegenTarget, + t: BasicTypeEnum<'ctx>, +) -> ParamLowering<'ctx> { + match target { + CodegenTarget::LinuxX86_64 => classify_param_x86_64_sysv(context, td, t), + CodegenTarget::DarwinArm64 => classify_param_arm64_darwin(td, t), + } +} + +fn classify_ret<'ctx>( + context: &'ctx Context, + td: &TargetData, + target: CodegenTarget, + t: Option>, +) -> RetLowering<'ctx> { + match target { + CodegenTarget::LinuxX86_64 => classify_ret_x86_64_sysv(context, td, t), + CodegenTarget::DarwinArm64 => classify_ret_arm64_darwin(td, t), + } +} + pub fn lower_extern_c<'ctx>( context: &'ctx Context, td: &TargetData, + target: CodegenTarget, ext: &ExternFunctionNode, struct_types: &HashMap>, ) -> LoweredExtern<'ctx> { - let llvm_name = ext.symbol.as_deref().unwrap_or(ext.name.as_str()).to_string(); + let llvm_name = ext + .symbol + .as_deref() + .unwrap_or(ext.name.as_str()) + .to_string(); let info_llvm_name = llvm_name.clone(); // wave types -> layout types - let wave_param_layout: Vec> = ext.params.iter() + let wave_param_layout: Vec> = ext + .params + .iter() .map(|(_, ty)| wave_type_to_llvm_type(context, ty, struct_types, TypeFlavor::AbiC)) .collect(); let wave_ret_layout: Option> = match &ext.return_type { WaveType::Void => None, - ty => Some(wave_type_to_llvm_type(context, ty, struct_types, TypeFlavor::AbiC)), + ty => Some(wave_type_to_llvm_type( + context, + ty, + struct_types, + TypeFlavor::AbiC, + )), }; - let ret = classify_ret(context, td, wave_ret_layout); + let ret = classify_ret(context, td, target, wave_ret_layout); let mut params: Vec> = vec![]; for p in wave_param_layout { - params.push(classify_param(context, td, p)); + params.push(classify_param(context, td, target, p)); } // build lowered param list (sret first, then params possibly split) @@ -305,7 +481,9 @@ pub fn lower_extern_c<'ctx>( } let fn_type = match &ret { - RetLowering::Void | RetLowering::SRet { .. } => context.void_type().fn_type(&llvm_param_types, false), + RetLowering::Void | RetLowering::SRet { .. } => { + context.void_type().fn_type(&llvm_param_types, false) + } RetLowering::Direct(t) => t.fn_type(&llvm_param_types, false), }; diff --git a/llvm/src/codegen/consts.rs b/llvm/src/codegen/consts.rs index c0ecbcbc..6cbc60b5 100644 --- a/llvm/src/codegen/consts.rs +++ b/llvm/src/codegen/consts.rs @@ -10,7 +10,7 @@ // SPDX-License-Identifier: MPL-2.0 use inkwell::context::Context; -use inkwell::types::{BasicType, BasicTypeEnum, StringRadix, StructType}; +use inkwell::types::{BasicTypeEnum, StringRadix, StructType}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{Expression, Literal, WaveType}; @@ -95,6 +95,31 @@ fn strip_struct_prefix(raw: &str) -> &str { raw.strip_prefix("struct.").unwrap_or(raw) } +fn cast_const_int_to_int<'ctx>( + iv: inkwell::values::IntValue<'ctx>, + int_ty: inkwell::types::IntType<'ctx>, +) -> Result, ConstEvalError> { + let src_bw = iv.get_type().get_bit_width(); + let dst_bw = int_ty.get_bit_width(); + + if src_bw == dst_bw { + return Ok(iv.as_basic_value_enum()); + } + + if src_bw > dst_bw { + return Ok(iv.const_truncate(int_ty).as_basic_value_enum()); + } + + if let Some(sext) = iv.get_sign_extended_constant() { + return Ok(int_ty.const_int(sext as u64, true).as_basic_value_enum()); + } + + Err(ConstEvalError::Unsupported(format!( + "cannot sign-extend {}-bit const integer to {}-bit at compile time", + src_bw, dst_bw + ))) +} + fn const_from_expected<'ctx>( context: &'ctx Context, expected: BasicTypeEnum<'ctx>, @@ -134,6 +159,79 @@ fn const_from_expected<'ctx>( }), }, + Expression::Cast { expr: inner, target_type } => { + let cast_ty = wave_type_to_llvm_type(context, target_type, struct_types, TypeFlavor::AbiC); + if cast_ty != expected { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: type_name(cast_ty), + note: format!( + "cast target type {:?} does not match declaration type", + target_type + ), + }); + } + + match (inner.as_ref(), expected) { + (Expression::Literal(Literal::Int(s)), BasicTypeEnum::PointerType(ptr_ty)) => { + let (neg, radix, digits) = parse_signed_and_radix(s); + let mut iv = context + .i64_type() + .const_int_from_string(&digits, radix) + .ok_or_else(|| ConstEvalError::InvalidLiteral(s.clone()))?; + if neg { + iv = iv.const_neg(); + } + Ok(iv.const_to_pointer(ptr_ty).as_basic_value_enum()) + } + + (Expression::Variable(name), BasicTypeEnum::PointerType(ptr_ty)) => { + let src = *const_env + .get(name) + .ok_or_else(|| ConstEvalError::UnknownIdentifier(name.clone()))?; + match src { + BasicValueEnum::IntValue(iv) => { + Ok(iv.const_to_pointer(ptr_ty).as_basic_value_enum()) + } + BasicValueEnum::PointerValue(pv) => { + Ok(pv.const_cast(ptr_ty).as_basic_value_enum()) + } + other => Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: value_type_name(other), + note: format!("cannot cast const `{}` to pointer", name), + }), + } + } + + (Expression::Variable(name), BasicTypeEnum::IntType(int_ty)) => { + let src = *const_env + .get(name) + .ok_or_else(|| ConstEvalError::UnknownIdentifier(name.clone()))?; + match src { + BasicValueEnum::IntValue(iv) => cast_const_int_to_int(iv, int_ty), + BasicValueEnum::PointerValue(pv) => { + Ok(pv.const_to_int(int_ty).as_basic_value_enum()) + } + other => Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: value_type_name(other), + note: format!("cannot cast const `{}` to integer", name), + }), + } + } + + _ => const_from_expected( + context, + expected, + inner, + struct_types, + struct_field_indices, + const_env, + ), + } + } + // --- ints --- Expression::Literal(Literal::Int(s)) => match expected { BasicTypeEnum::IntType(int_ty) => { diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs index 41815640..01128ecc 100644 --- a/llvm/src/codegen/ir.rs +++ b/llvm/src/codegen/ir.rs @@ -10,23 +10,27 @@ // SPDX-License-Identifier: MPL-2.0 use inkwell::context::Context; -use inkwell::passes::{PassBuilderOptions}; +use inkwell::passes::PassBuilderOptions; use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; -use inkwell::values::{BasicValueEnum, FunctionValue}; +use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue}; use inkwell::OptimizationLevel; -use parser::ast::{ASTNode, EnumNode, ExternFunctionNode, FunctionNode, Mutability, ParameterNode, ProtoImplNode, StructNode, TypeAliasNode, VariableNode, WaveType}; +use inkwell::targets::{ + CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine, +}; +use parser::ast::{ + ASTNode, EnumNode, ExternFunctionNode, FunctionNode, Mutability, ParameterNode, ProtoImplNode, + StructNode, TypeAliasNode, VariableNode, WaveType, +}; use std::collections::{HashMap, HashSet}; -use inkwell::targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetData, TargetMachine}; +use crate::codegen::target::require_supported_target_from_triple; use crate::statement::generate_statement_ir; use super::consts::{create_llvm_const_value, ConstEvalError}; use super::types::{wave_type_to_llvm_type, TypeFlavor, VariableInfo}; -use crate::codegen::abi_c::{ - ExternCInfo, lower_extern_c, apply_extern_c_attrs, -}; +use crate::codegen::abi_c::{apply_extern_c_attrs, lower_extern_c, ExternCInfo}; fn is_implicit_i32_main(name: &str, return_type: &Option) -> bool { name == "main" && matches!(return_type, None | Some(WaveType::Void)) @@ -57,6 +61,7 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { Target::initialize_native(&InitializationConfig::default()).unwrap(); let triple = TargetMachine::get_default_triple(); + let abi_target = require_supported_target_from_triple(&triple); let target = Target::from_triple(&triple).unwrap(); let tm = target @@ -78,8 +83,8 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { let mut extern_c_info: HashMap> = HashMap::new(); - let mut global_consts: HashMap> = HashMap::new(); + let mut global_statics: HashMap> = HashMap::new(); let mut struct_types: HashMap = HashMap::new(); let mut struct_field_indices: HashMap> = HashMap::new(); @@ -139,9 +144,10 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { let mut next_pending: Vec<&VariableNode> = Vec::new(); for v in pending { - let init = v.initial_value.as_ref().unwrap_or_else(|| { - panic!("Constant must be initialized: {}", v.name) - }); + let init = v + .initial_value + .as_ref() + .unwrap_or_else(|| panic!("Constant must be initialized: {}", v.name)); match create_llvm_const_value( context, @@ -178,6 +184,44 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { pending = next_pending; } + for ast in &ast_nodes { + let ASTNode::Variable(v) = ast else { + continue; + }; + if v.mutability != Mutability::Static { + continue; + } + + let llvm_ty = wave_type_to_llvm_type(context, &v.type_name, &struct_types, TypeFlavor::AbiC); + let g = module.add_global(llvm_ty, None, &v.name); + + let init = if let Some(expr) = &v.initial_value { + create_llvm_const_value( + context, + &v.type_name, + expr, + &struct_types, + &struct_field_indices, + &global_consts, + ) + .unwrap_or_else(|e| panic!("static '{}' initialization failed: {}", v.name, e)) + } else { + llvm_ty.const_zero().as_basic_value_enum() + }; + + g.set_initializer(&init); + g.set_constant(false); + + global_statics.insert( + v.name.clone(), + VariableInfo { + ptr: g.as_pointer_value(), + mutability: Mutability::Static, + ty: v.type_name.clone(), + }, + ); + } + let mut proto_functions: Vec<(String, FunctionNode)> = Vec::new(); for ast in &ast_nodes { if let ASTNode::ProtoImpl(proto_impl) = ast { @@ -194,19 +238,40 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { let function_nodes: Vec = ast_nodes .iter() - .filter_map(|ast| if let ASTNode::Function(f) = ast { Some(f.clone()) } else { None }) + .filter_map(|ast| { + if let ASTNode::Function(f) = ast { + Some(f.clone()) + } else { + None + } + }) .chain(proto_functions.iter().map(|(_, f)| f.clone())) .collect(); let extern_functions: Vec<&ExternFunctionNode> = ast_nodes .iter() - .filter_map(|ast| if let ASTNode::ExternFunction(ext) = ast { Some(ext) } else { None }) + .filter_map(|ast| { + if let ASTNode::ExternFunction(ext) = ast { + Some(ext) + } else { + None + } + }) .collect(); - for FunctionNode { name, parameters, return_type, .. } in &function_nodes { + for FunctionNode { + name, + parameters, + return_type, + .. + } in &function_nodes + { let param_types: Vec = parameters .iter() - .map(|p| wave_type_to_llvm_type(context, &p.param_type, &struct_types, TypeFlavor::AbiC).into()) + .map(|p| { + wave_type_to_llvm_type(context, &p.param_type, &struct_types, TypeFlavor::AbiC) + .into() + }) .collect(); let fn_type = if is_implicit_i32_main(name, return_type) { @@ -215,7 +280,12 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { match return_type { None | Some(WaveType::Void) => context.void_type().fn_type(¶m_types, false), Some(wave_ret_ty) => { - let llvm_ret_type = wave_type_to_llvm_type(context, wave_ret_ty, &struct_types, TypeFlavor::AbiC); + let llvm_ret_type = wave_type_to_llvm_type( + context, + wave_ret_ty, + &struct_types, + TypeFlavor::AbiC, + ); llvm_ret_type.fn_type(¶m_types, false) } } @@ -233,7 +303,7 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { ); } - let lowered = lower_extern_c(context, td, ext, &struct_types); + let lowered = lower_extern_c(context, td, abi_target, ext, &struct_types); let f = module.add_function(&lowered.llvm_name, lowered.fn_type, None); apply_extern_c_attrs(context, f, &lowered.info); @@ -248,13 +318,14 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { let entry_block = context.append_basic_block(function, "entry"); builder.position_at_end(entry_block); - let mut variables: HashMap = HashMap::new(); + let mut variables: HashMap = global_statics.clone(); let mut string_counter = 0; let mut loop_exit_stack = vec![]; let mut loop_continue_stack = vec![]; for (i, param) in func_node.parameters.iter().enumerate() { - let llvm_type = wave_type_to_llvm_type(context, ¶m.param_type, &struct_types, TypeFlavor::AbiC); + let llvm_type = + wave_type_to_llvm_type(context, ¶m.param_type, &struct_types, TypeFlavor::AbiC); let alloca = builder.build_alloca(llvm_type, ¶m.name).unwrap(); let param_val = function.get_nth_param(i as u32).unwrap(); builder.build_store(alloca, param_val).unwrap(); @@ -308,7 +379,10 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode], opt_flag: &str) -> String { } else if is_void_like { builder.build_return(None).unwrap(); } else { - panic!("Non-void function '{}' is missing a return statement", func_node.name); + panic!( + "Non-void function '{}' is missing a return statement", + func_node.name + ); } } } @@ -337,7 +411,9 @@ fn pipeline_from_opt_flag(opt_flag: &str) -> &'static str { fn parse_int_literal(raw: &str) -> Option { let mut s = raw.trim().replace('_', ""); - if s.is_empty() { return None; } + if s.is_empty() { + return None; + } let neg = if let Some(rest) = s.strip_prefix('-') { s = rest.to_string(); @@ -349,7 +425,8 @@ fn parse_int_literal(raw: &str) -> Option { false }; - let (radix, digits) = if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) { + let (radix, digits) = if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) + { (16, rest) } else if let Some(rest) = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")) { (2, rest) @@ -384,10 +461,12 @@ fn fits_in_int(v: i128, bits: u32, signed: bool) -> bool { return v >= i64::MIN as i128 && v <= i64::MAX as i128; } let min = -(1i128 << (bits - 1)); - let max = (1i128 << (bits - 1)) - 1; + let max = (1i128 << (bits - 1)) - 1; v >= min && v <= max } else { - if v < 0 { return false; } + if v < 0 { + return false; + } if bits == 64 { return (v as u128) <= u64::MAX as u128; } @@ -403,7 +482,9 @@ fn collect_named_types(nodes: &[ASTNode]) -> HashMap { ASTNode::TypeAlias(TypeAliasNode { name, target }) => { m.insert(name.clone(), target.clone()); } - ASTNode::Enum(EnumNode { name, repr_type, .. }) => { + ASTNode::Enum(EnumNode { + name, repr_type, .. + }) => { m.insert(name.clone(), repr_type.clone()); } _ => {} @@ -453,9 +534,20 @@ fn resolve_parameter(p: &ParameterNode, named: &HashMap) -> Pa fn resolve_function(f: &FunctionNode, named: &HashMap) -> FunctionNode { let mut out = f.clone(); - out.parameters = out.parameters.iter().map(|p| resolve_parameter(p, named)).collect(); - out.return_type = out.return_type.as_ref().map(|t| resolve_wave_type(t, named)); - out.body = out.body.iter().map(|n| resolve_ast_node(n, named)).collect(); + out.parameters = out + .parameters + .iter() + .map(|p| resolve_parameter(p, named)) + .collect(); + out.return_type = out + .return_type + .as_ref() + .map(|t| resolve_wave_type(t, named)); + out.body = out + .body + .iter() + .map(|n| resolve_ast_node(n, named)) + .collect(); out } @@ -466,13 +558,21 @@ fn resolve_struct(s: &StructNode, named: &HashMap) -> StructNo .iter() .map(|(n, t)| (n.clone(), resolve_wave_type(t, named))) .collect(); - out.methods = out.methods.iter().map(|m| resolve_function(m, named)).collect(); + out.methods = out + .methods + .iter() + .map(|m| resolve_function(m, named)) + .collect(); out } fn resolve_proto(p: &ProtoImplNode, named: &HashMap) -> ProtoImplNode { let mut out = p.clone(); - out.methods = out.methods.iter().map(|m| resolve_function(m, named)).collect(); + out.methods = out + .methods + .iter() + .map(|m| resolve_function(m, named)) + .collect(); out } @@ -519,8 +619,12 @@ fn add_enum_consts_to_globals( e: &EnumNode, global_consts: &mut HashMap>, ) { - let (bits, signed) = repr_bits_signed(&e.repr_type) - .unwrap_or_else(|| panic!("enum '{}' repr type must be an integer type, got {:?}", e.name, e.repr_type)); + let (bits, signed) = repr_bits_signed(&e.repr_type).unwrap_or_else(|| { + panic!( + "enum '{}' repr type must be an integer type, got {:?}", + e.name, e.repr_type + ) + }); if bits > 64 || bits == 0 { panic!("enum '{}' repr bit-width unsupported: {}", e.name, bits); @@ -533,7 +637,10 @@ fn add_enum_consts_to_globals( for v in &e.variants { if let Some(raw) = &v.explicit_value { next = parse_int_literal(raw).unwrap_or_else(|| { - panic!("enum '{}' variant '{}' has invalid integer literal: {}", e.name, v.name, raw) + panic!( + "enum '{}' variant '{}' has invalid integer literal: {}", + e.name, v.name, raw + ) }); } diff --git a/llvm/src/codegen/mod.rs b/llvm/src/codegen/mod.rs index 0f65b7ac..b7890842 100644 --- a/llvm/src/codegen/mod.rs +++ b/llvm/src/codegen/mod.rs @@ -9,14 +9,15 @@ // // SPDX-License-Identifier: MPL-2.0 -pub mod ir; +pub mod abi_c; +pub mod address; pub mod consts; pub mod format; -pub mod types; -pub mod address; +pub mod ir; pub mod legacy; pub mod plan; -pub mod abi_c; +pub mod target; +pub mod types; pub use address::{generate_address_and_type_ir, generate_address_ir}; pub use format::{wave_format_to_c, wave_format_to_scanf}; diff --git a/llvm/src/codegen/plan.rs b/llvm/src/codegen/plan.rs index 54034e3d..460fac77 100644 --- a/llvm/src/codegen/plan.rs +++ b/llvm/src/codegen/plan.rs @@ -10,6 +10,7 @@ // SPDX-License-Identifier: MPL-2.0 // codegen/asm/plan.rs +use crate::codegen::target::CodegenTarget; use parser::ast::Expression; use std::collections::{HashMap, HashSet}; @@ -31,15 +32,15 @@ pub struct AsmPlan<'a> { #[derive(Debug, Clone)] pub struct AsmOut<'a> { - pub reg_raw: String, // user wrote (e.g. "rax", "%rax", "RAX", "r") - pub reg_norm: String, // normalized token (e.g. "rax", "r") + pub reg_raw: String, // user wrote (e.g. "rax", "%rax", "RAX", "r") + pub reg_norm: String, // normalized token (e.g. "rax", "r") pub phys_group: Option, // Some("rax") for real regs (al/ax/eax/rax -> rax), None for constraint classes (r/rm/m/..) pub target: &'a Expression, } #[derive(Debug, Clone)] pub struct AsmIn<'a> { - pub constraint: String, // "{rax}" or "r" or "0" (tied) + pub constraint: String, // "{rax}" or "r" or "0" (tied) pub phys_group: Option, // Some("rax") if it binds a concrete reg token, None if it is a class constraint pub value: &'a Expression, } @@ -51,14 +52,8 @@ pub enum AsmSafetyMode { #[derive(Debug, Clone)] struct RegToken { - raw_norm: String, // normalized token (no %, no braces, lowercase) - phys_group: Option, // physical register group for real regs -} - -impl RegToken { - fn is_real_reg(&self) -> bool { - self.phys_group.is_some() - } + raw_norm: String, // normalized token (no %, no braces, lowercase) + phys_group: Option, // physical register group for real regs } /// Normalize user reg/constraint token: @@ -79,32 +74,16 @@ fn normalize_token(s: &str) -> String { s.trim().to_ascii_lowercase() } -/// If token is a real x86_64 GPR/subreg, return its *physical group*: -/// - al/ax/eax/rax -> "rax" -/// - dl/dx/edx/rdx -> "rdx" -/// - r8b/r8w/r8d/r8 -> "r8" -fn reg_phys_group(token: &str) -> Option<&'static str> { +fn reg_phys_group_x86_64(token: &str) -> Option<&'static str> { match token { - // rax group "al" | "ah" | "ax" | "eax" | "rax" => Some("rax"), - // rbx group "bl" | "bh" | "bx" | "ebx" | "rbx" => Some("rbx"), - // rcx group "cl" | "ch" | "cx" | "ecx" | "rcx" => Some("rcx"), - // rdx group "dl" | "dh" | "dx" | "edx" | "rdx" => Some("rdx"), - - // rsi group "sil" | "si" | "esi" | "rsi" => Some("rsi"), - // rdi group "dil" | "di" | "edi" | "rdi" => Some("rdi"), - - // rbp group "bpl" | "bp" | "ebp" | "rbp" => Some("rbp"), - // rsp group "spl" | "sp" | "esp" | "rsp" => Some("rsp"), - - // r8~r15 groups "r8b" | "r8w" | "r8d" | "r8" => Some("r8"), "r9b" | "r9w" | "r9d" | "r9" => Some("r9"), "r10b" | "r10w" | "r10d" | "r10" => Some("r10"), @@ -118,13 +97,45 @@ fn reg_phys_group(token: &str) -> Option<&'static str> { } } -/// Decide whether user string is: -/// - real register (rax/eax/al/r8d/...) -/// - or a constraint class (r/rm/m/i/n/g/...) -fn parse_token(raw: &str) -> RegToken { +fn reg_phys_group_arm64(token: &str) -> Option { + match token { + "fp" => return Some("x29".to_string()), + "lr" => return Some("x30".to_string()), + "ip0" => return Some("x16".to_string()), + "ip1" => return Some("x17".to_string()), + "sp" => return Some("sp".to_string()), + "xzr" | "wzr" => return Some("xzr".to_string()), + _ => {} + } + + if token.len() >= 2 { + let (prefix, num) = token.split_at(1); + if (prefix == "x" || prefix == "w") + && num.chars().all(|c| c.is_ascii_digit()) + && !num.is_empty() + { + if let Ok(n) = num.parse::() { + if n <= 30 { + return Some(format!("x{}", n)); + } + } + } + } + + None +} + +/// Decide whether user token is a real register or a constraint class. +fn parse_token(target: CodegenTarget, raw: &str) -> RegToken { let raw_norm = normalize_token(raw); - let phys_group = reg_phys_group(&raw_norm).map(|s| s.to_string()); - RegToken { raw_norm, phys_group } + let phys_group = match target { + CodegenTarget::LinuxX86_64 => reg_phys_group_x86_64(&raw_norm).map(|s| s.to_string()), + CodegenTarget::DarwinArm64 => reg_phys_group_arm64(&raw_norm), + }; + RegToken { + raw_norm, + phys_group, + } } /// For conservative kernel mode: @@ -133,25 +144,29 @@ fn parse_token(raw: &str) -> RegToken { /// DO NOT auto-clobber GPRs (otherwise allocator can't satisfy "r"/"rm"). /// - If all operands are concrete regs only, you may clobber the rest GPRs safely. fn build_default_clobbers( + target: CodegenTarget, mode: AsmSafetyMode, inputs: &[(String, Expression)], outputs: &[(String, Expression)], ) -> Vec { match mode { AsmSafetyMode::ConservativeKernel => { - let mut clobbers = vec![ - "~{memory}".to_string(), - "~{dirflag}".to_string(), - "~{fpsr}".to_string(), - "~{flags}".to_string(), - ]; + let mut clobbers = match target { + CodegenTarget::LinuxX86_64 => vec![ + "~{memory}".to_string(), + "~{dirflag}".to_string(), + "~{fpsr}".to_string(), + "~{flags}".to_string(), + ], + CodegenTarget::DarwinArm64 => vec!["~{memory}".to_string(), "~{cc}".to_string()], + }; // Collect concrete used physical register groups let mut used_phys: HashSet = HashSet::new(); let mut has_class_constraint = false; for (r, _) in inputs { - let t = parse_token(r); + let t = parse_token(target, r); if let Some(pg) = t.phys_group { used_phys.insert(pg); } else { @@ -159,7 +174,7 @@ fn build_default_clobbers( } } for (r, _) in outputs { - let t = parse_token(r); + let t = parse_token(target, r); if let Some(pg) = t.phys_group { used_phys.insert(pg); } else { @@ -172,15 +187,29 @@ fn build_default_clobbers( return clobbers; } - // Otherwise: clobber all GPRs not explicitly used. - const GPRS: [&str; 16] = [ - "rax","rbx","rcx","rdx","rsi","rdi","rbp","rsp", - "r8","r9","r10","r11","r12","r13","r14","r15", - ]; - - for r in GPRS { - if !used_phys.contains(r) { - clobbers.push(format!("~{{{}}}", r)); + match target { + CodegenTarget::LinuxX86_64 => { + const GPRS: [&str; 16] = [ + "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", + "r11", "r12", "r13", "r14", "r15", + ]; + + for r in GPRS { + if !used_phys.contains(r) { + clobbers.push(format!("~{{{}}}", r)); + } + } + } + CodegenTarget::DarwinArm64 => { + for n in 0..=30u32 { + if n == 18 { + continue; + } + let r = format!("x{}", n); + if !used_phys.contains(&r) { + clobbers.push(format!("~{{{}}}", r)); + } + } } } @@ -223,21 +252,35 @@ fn gcc_percent_to_llvm_dollar(s: &str) -> String { out } -fn normalize_clobber_item(s: &str) -> String { +fn normalize_special_clobber(target: CodegenTarget, token: &str) -> Option { + match target { + CodegenTarget::LinuxX86_64 => match token { + "memory" => Some("~{memory}".to_string()), + "cc" | "flags" | "eflags" | "rflags" => Some("~{flags}".to_string()), + "dirflag" => Some("~{dirflag}".to_string()), + "fpsr" => Some("~{fpsr}".to_string()), + _ => None, + }, + CodegenTarget::DarwinArm64 => match token { + "memory" => Some("~{memory}".to_string()), + "cc" | "flags" | "eflags" | "rflags" => Some("~{cc}".to_string()), + _ => None, + }, + } +} + +fn normalize_clobber_item(target: CodegenTarget, s: &str) -> String { let t = s.trim(); if let Some(inner) = t.strip_prefix("~{").and_then(|x| x.strip_suffix('}')) { let n = normalize_token(inner); - match n.as_str() { - "memory" => return "~{memory}".to_string(), - "cc" | "flags" | "eflags" | "rflags" => return "~{flags}".to_string(), - "dirflag" => return "~{dirflag}".to_string(), - "fpsr" => return "~{fpsr}".to_string(), - _ => {} + if let Some(special) = normalize_special_clobber(target, &n) { + return special; } - if let Some(pg) = reg_phys_group(&n) { + let reg = parse_token(target, &n); + if let Some(pg) = reg.phys_group { return format!("~{{{}}}", pg); } @@ -247,15 +290,12 @@ fn normalize_clobber_item(s: &str) -> String { if let Some(inner) = t.strip_prefix('{').and_then(|x| x.strip_suffix('}')) { let n = normalize_token(inner); - match n.as_str() { - "memory" => return "~{memory}".to_string(), - "cc" | "flags" | "eflags" | "rflags" => return "~{flags}".to_string(), - "dirflag" => return "~{dirflag}".to_string(), - "fpsr" => return "~{fpsr}".to_string(), - _ => {} + if let Some(special) = normalize_special_clobber(target, &n) { + return special; } - if let Some(pg) = reg_phys_group(&n) { + let reg = parse_token(target, &n); + if let Some(pg) = reg.phys_group { return format!("~{{{}}}", pg); } @@ -264,15 +304,11 @@ fn normalize_clobber_item(s: &str) -> String { // specials (plain) let lower = t.to_ascii_lowercase(); - match lower.as_str() { - "memory" => return "~{memory}".to_string(), - "cc" | "flags" | "eflags" | "rflags" => return "~{flags}".to_string(), - "dirflag" => return "~{dirflag}".to_string(), - "fpsr" => return "~{fpsr}".to_string(), - _ => {} + if let Some(special) = normalize_special_clobber(target, &lower) { + return special; } - let rt = parse_token(t); + let rt = parse_token(target, t); if let Some(pg) = rt.phys_group { return format!("~{{{}}}", pg); } @@ -280,8 +316,8 @@ fn normalize_clobber_item(s: &str) -> String { panic!("Invalid clobber token: '{}'", t); } - fn merge_clobbers( + target: CodegenTarget, mut base: Vec, user: &[String], used_phys: &HashSet, @@ -289,7 +325,7 @@ fn merge_clobbers( let mut seen: HashSet = base.iter().cloned().collect(); for raw in user { - let c = normalize_clobber_item(raw); + let c = normalize_clobber_item(target, raw); if let Some(inner) = c.strip_prefix("~{").and_then(|x| x.strip_suffix('}')) { let inner_norm = normalize_token(inner); @@ -311,6 +347,7 @@ fn merge_clobbers( impl<'a> AsmPlan<'a> { pub fn build( + target: CodegenTarget, instructions: &'a [String], inputs_raw: &'a [(String, Expression)], outputs_raw: &'a [(String, Expression)], @@ -325,13 +362,16 @@ impl<'a> AsmPlan<'a> { let mut out_index_by_exact_reg: HashMap = HashMap::new(); let mut outputs: Vec> = Vec::with_capacity(outputs_raw.len()); - for (reg, target) in outputs_raw { - let t = parse_token(reg); + for (reg, out_target) in outputs_raw { + let t = parse_token(target, reg); // real reg outputs: disallow duplicates by physical group if let Some(pg) = &t.phys_group { if !used_out_phys.insert(pg.clone()) { - panic!("Register '{}' duplicated in asm outputs (same phys group '{}')", reg, pg); + panic!( + "Register '{}' duplicated in asm outputs (same phys group '{}')", + reg, pg + ); } // enable tied input only when exact same token used (ex: out("rax") + in("rax")) out_index_by_exact_reg.insert(t.raw_norm.clone(), outputs.len()); @@ -342,7 +382,7 @@ impl<'a> AsmPlan<'a> { reg_raw: reg.clone(), reg_norm: t.raw_norm, phys_group: t.phys_group, - target, + target: out_target, }); } @@ -351,12 +391,15 @@ impl<'a> AsmPlan<'a> { let mut inputs: Vec> = Vec::with_capacity(inputs_raw.len()); for (reg, expr) in inputs_raw { - let t = parse_token(reg); + let t = parse_token(target, reg); // real reg inputs: disallow duplicates by physical group if let Some(pg) = &t.phys_group { if !used_in_phys.insert(pg.clone()) { - panic!("Register '{}' duplicated in asm inputs (same phys group '{}')", reg, pg); + panic!( + "Register '{}' duplicated in asm inputs (same phys group '{}')", + reg, pg + ); } // tied only when exact same reg token matches a real-reg output token @@ -397,8 +440,8 @@ impl<'a> AsmPlan<'a> { } } - let default_clobbers = build_default_clobbers(mode, inputs_raw, outputs_raw); - let clobbers = merge_clobbers(default_clobbers, user_clobbers_raw, &used_phys); + let default_clobbers = build_default_clobbers(target, mode, inputs_raw, outputs_raw); + let clobbers = merge_clobbers(target, default_clobbers, user_clobbers_raw, &used_phys); Self { asm_code, diff --git a/llvm/src/codegen/target.rs b/llvm/src/codegen/target.rs new file mode 100644 index 00000000..5d2b4c91 --- /dev/null +++ b/llvm/src/codegen/target.rs @@ -0,0 +1,81 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024–2026 Wave Foundation +// Copyright (c) 2024–2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +use inkwell::module::Module; +use inkwell::targets::TargetTriple; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CodegenTarget { + LinuxX86_64, + DarwinArm64, +} + +impl CodegenTarget { + pub fn from_triple_str(triple: &str) -> Option { + let t = triple.to_ascii_lowercase(); + + let is_x86_64 = t.starts_with("x86_64"); + let is_arm64 = t.starts_with("arm64") || t.starts_with("aarch64"); + let is_linux = t.contains("linux"); + let is_darwin = t.contains("darwin"); + + if is_x86_64 && is_linux { + return Some(Self::LinuxX86_64); + } + if is_arm64 && is_darwin { + return Some(Self::DarwinArm64); + } + + None + } + + pub fn from_target_triple(triple: &TargetTriple) -> Option { + let raw = triple.as_str().to_string_lossy(); + Self::from_triple_str(raw.as_ref()) + } + + pub fn from_module(module: &Module<'_>) -> Option { + let triple = module.get_triple(); + Self::from_target_triple(&triple) + } + + pub fn desc(self) -> &'static str { + match self { + Self::LinuxX86_64 => "linux x86_64", + Self::DarwinArm64 => "darwin arm64", + } + } +} + +pub fn require_supported_target_from_triple(triple: &TargetTriple) -> CodegenTarget { + if let Some(t) = CodegenTarget::from_target_triple(triple) { + return t; + } + + let raw = triple.as_str().to_string_lossy(); + panic!( + "unsupported target triple '{}': Wave currently supports only linux x86_64 and darwin arm64 (Windows not supported yet)", + raw + ); +} + +pub fn require_supported_target_from_module(module: &Module<'_>) -> CodegenTarget { + if let Some(t) = CodegenTarget::from_module(module) { + return t; + } + + let triple = module.get_triple(); + let raw = triple.as_str().to_string_lossy(); + panic!( + "unsupported target triple '{}': Wave currently supports only linux x86_64 and darwin arm64 (Windows not supported yet)", + raw + ); +} diff --git a/llvm/src/expression/rvalue/asm.rs b/llvm/src/expression/rvalue/asm.rs index b87e0aae..66d3b85c 100644 --- a/llvm/src/expression/rvalue/asm.rs +++ b/llvm/src/expression/rvalue/asm.rs @@ -11,6 +11,7 @@ use super::ExprGenEnv; use crate::codegen::plan::*; +use crate::codegen::target::{require_supported_target_from_module, CodegenTarget}; use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum, StringRadix}; use inkwell::values::{ @@ -19,6 +20,13 @@ use inkwell::values::{ use inkwell::InlineAsmDialect; use parser::ast::{Expression, Literal, WaveType}; +fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { + match target { + CodegenTarget::LinuxX86_64 => InlineAsmDialect::Intel, + CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + } +} + pub(crate) fn gen<'ctx, 'a>( env: &mut ExprGenEnv<'ctx, 'a>, instructions: &[String], @@ -26,7 +34,9 @@ pub(crate) fn gen<'ctx, 'a>( outputs: &[(String, Expression)], clobbers: &[String], ) -> BasicValueEnum<'ctx> { + let target = require_supported_target_from_module(env.module); let plan = AsmPlan::build( + target, instructions, inputs, outputs, @@ -35,8 +45,7 @@ pub(crate) fn gen<'ctx, 'a>( ); let constraints_str = plan.constraints_string(); - let mut operand_vals: Vec> = - Vec::with_capacity(plan.inputs.len()); + let mut operand_vals: Vec> = Vec::with_capacity(plan.inputs.len()); for inp in &plan.inputs { let v = eval_asm_in_expr(env, inp.value); operand_vals.push(v.into()); @@ -55,7 +64,7 @@ pub(crate) fn gen<'ctx, 'a>( constraints_str, plan.has_side_effects, false, - Some(InlineAsmDialect::Intel), + Some(inline_asm_dialect_for_target(target)), false, ); @@ -65,7 +74,11 @@ pub(crate) fn gen<'ctx, 'a>( .build_indirect_call(fn_type, callee, &operand_vals, "inline_asm_void") .unwrap(); - return env.context.i64_type().const_int(0, false).as_basic_value_enum(); + return env + .context + .i64_type() + .const_int(0, false) + .as_basic_value_enum(); } // asm expr must have exactly 1 output @@ -85,13 +98,14 @@ pub(crate) fn gen<'ctx, 'a>( constraints_str, plan.has_side_effects, false, - Some(InlineAsmDialect::Intel), + Some(inline_asm_dialect_for_target(target)), false, ); let callee = unsafe { PointerValue::new(inline_asm.as_value_ref()) }; - let call = env.builder + let call = env + .builder .build_indirect_call(fn_type, callee, &operand_vals, "inline_asm_expr") .unwrap(); @@ -103,10 +117,7 @@ pub(crate) fn gen<'ctx, 'a>( } } -fn llvm_type_of_wave<'ctx, 'a>( - env: &ExprGenEnv<'ctx, 'a>, - wt: &WaveType, -) -> BasicTypeEnum<'ctx> { +fn llvm_type_of_wave<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, wt: &WaveType) -> BasicTypeEnum<'ctx> { wave_type_to_llvm_type(env.context, wt, env.struct_types, TypeFlavor::Value) } diff --git a/llvm/src/expression/rvalue/binary.rs b/llvm/src/expression/rvalue/binary.rs index c4f29364..33331f09 100644 --- a/llvm/src/expression/rvalue/binary.rs +++ b/llvm/src/expression/rvalue/binary.rs @@ -10,10 +10,11 @@ // SPDX-License-Identifier: MPL-2.0 use super::{utils::to_bool, ExprGenEnv}; +use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; use inkwell::types::{BasicType, BasicTypeEnum}; -use inkwell::values::{BasicValue, BasicValueEnum}; +use inkwell::values::{BasicValue, BasicValueEnum, IntValue, PointerValue}; use inkwell::{FloatPredicate, IntPredicate}; -use parser::ast::{Expression, Literal, Operator}; +use parser::ast::{Expression, Literal, Operator, WaveType}; fn is_numeric_literal(expr: &Expression) -> bool { match expr { @@ -31,6 +32,89 @@ fn value_numeric_basic_type<'ctx>(v: BasicValueEnum<'ctx>) -> Option( + env: &ExprGenEnv<'ctx, 'a>, + v: IntValue<'ctx>, + tag: &str, +) -> IntValue<'ctx> { + let i64_ty = env.context.i64_type(); + let src_bits = v.get_type().get_bit_width(); + + if src_bits == 64 { + v + } else if src_bits < 64 { + env.builder + .build_int_s_extend(v, i64_ty, &format!("{}_sext", tag)) + .unwrap() + } else { + env.builder + .build_int_truncate(v, i64_ty, &format!("{}_trunc", tag)) + .unwrap() + } +} + +fn infer_ptr_pointee_ty<'ctx, 'a>( + env: &ExprGenEnv<'ctx, 'a>, + expr: &Expression, +) -> BasicTypeEnum<'ctx> { + match expr { + Expression::Grouped(inner) => infer_ptr_pointee_ty(env, inner), + + Expression::Variable(name) => { + if let Some(vi) = env.variables.get(name) { + match &vi.ty { + WaveType::Pointer(inner) => { + wave_type_to_llvm_type(env.context, inner, env.struct_types, TypeFlavor::AbiC) + } + WaveType::String => env.context.i8_type().as_basic_type_enum(), + _ => env.context.i8_type().as_basic_type_enum(), + } + } else { + env.context.i8_type().as_basic_type_enum() + } + } + + Expression::AddressOf(inner) => { + if let Expression::Variable(name) = &**inner { + if let Some(vi) = env.variables.get(name) { + return wave_type_to_llvm_type( + env.context, + &vi.ty, + env.struct_types, + TypeFlavor::AbiC, + ); + } + } + env.context.i8_type().as_basic_type_enum() + } + + Expression::Cast { target_type, .. } => match target_type { + WaveType::Pointer(inner) => { + wave_type_to_llvm_type(env.context, inner, env.struct_types, TypeFlavor::AbiC) + } + WaveType::String => env.context.i8_type().as_basic_type_enum(), + _ => env.context.i8_type().as_basic_type_enum(), + }, + + _ => env.context.i8_type().as_basic_type_enum(), + } +} + +fn gep_with_i64_offset<'ctx, 'a>( + env: &ExprGenEnv<'ctx, 'a>, + ptr: PointerValue<'ctx>, + ptr_expr: &Expression, + idx_i64: IntValue<'ctx>, + tag: &str, +) -> PointerValue<'ctx> { + let pointee_ty = infer_ptr_pointee_ty(env, ptr_expr); + unsafe { + env.builder + .build_in_bounds_gep(pointee_ty, ptr, &[idx_i64], tag) + .unwrap() + } +} + pub(crate) fn gen<'ctx, 'a>( env: &mut ExprGenEnv<'ctx, 'a>, left: &Expression, @@ -234,34 +318,55 @@ pub(crate) fn gen<'ctx, 'a>( let mut result = match operator { Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq").unwrap(), Operator::NotEqual => env.builder.build_int_compare(IntPredicate::NE, li, ri, "ptrne").unwrap(), + Operator::Subtract => env.builder.build_int_sub(li, ri, "ptrdiff").unwrap(), _ => panic!("Unsupported pointer operator: {:?}", operator), }; - if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type { - if result.get_type() != target_ty { - if result.get_type().get_bit_width() > target_ty.get_bit_width() { - panic!( - "implicit integer narrowing is forbidden in binary result: i{} -> i{}", - result.get_type().get_bit_width(), - target_ty.get_bit_width() - ); + match operator { + Operator::Equal | Operator::NotEqual => { + if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type { + if result.get_type() != target_ty { + if result.get_type().get_bit_width() > target_ty.get_bit_width() { + panic!( + "implicit integer narrowing is forbidden in binary result: i{} -> i{}", + result.get_type().get_bit_width(), + target_ty.get_bit_width() + ); + } + result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + } + } + } + Operator::Subtract => { + if let Some(inkwell::types::BasicTypeEnum::IntType(target_ty)) = expected_type { + if result.get_type() != target_ty { + result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); + } } - result = env.builder.build_int_cast(result, target_ty, "cast_result").unwrap(); } + _ => {} } return result.as_basic_value_enum(); } (BasicValueEnum::PointerValue(lp), BasicValueEnum::IntValue(ri)) => { + match operator { + Operator::Add | Operator::Subtract => { + let mut idx = cast_int_to_i64(env, ri, "ptr_idx"); + if matches!(operator, Operator::Subtract) { + idx = env.builder.build_int_neg(idx, "ptr_idx_neg").unwrap(); + } + let p = gep_with_i64_offset(env, lp, left, idx, "ptr_gep"); + return p.as_basic_value_enum(); + } + _ => {} + }; + let i64_ty = env.context.i64_type(); let li = env.builder.build_ptr_to_int(lp, i64_ty, "l_ptr2int").unwrap(); - let ri = if ri.get_type().get_bit_width() == 64 { - ri - } else { - env.builder.build_int_cast(ri, i64_ty, "r_i64").unwrap() - }; + let ri = cast_int_to_i64(env, ri, "r_i64"); let mut result = match operator { Operator::Equal => env.builder.build_int_compare(IntPredicate::EQ, li, ri, "ptreq0").unwrap(), @@ -286,12 +391,14 @@ pub(crate) fn gen<'ctx, 'a>( } (BasicValueEnum::IntValue(li), BasicValueEnum::PointerValue(rp)) => { + if matches!(operator, Operator::Add) { + let idx = cast_int_to_i64(env, li, "ptr_idx"); + let p = gep_with_i64_offset(env, rp, right, idx, "ptr_gep"); + return p.as_basic_value_enum(); + } + let i64_ty = env.context.i64_type(); - let li = if li.get_type().get_bit_width() == 64 { - li - } else { - env.builder.build_int_cast(li, i64_ty, "l_i64").unwrap() - }; + let li = cast_int_to_i64(env, li, "l_i64"); let ri = env.builder.build_ptr_to_int(rp, i64_ty, "r_ptr2int").unwrap(); diff --git a/llvm/src/expression/rvalue/calls.rs b/llvm/src/expression/rvalue/calls.rs index b77acf80..c41cc833 100644 --- a/llvm/src/expression/rvalue/calls.rs +++ b/llvm/src/expression/rvalue/calls.rs @@ -9,12 +9,14 @@ // // SPDX-License-Identifier: MPL-2.0 -use inkwell::types::{AnyTypeEnum, AsTypeRef, BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; use super::ExprGenEnv; -use inkwell::values::{BasicMetadataValueEnum, BasicValue, BasicValueEnum, PointerValue, ValueKind}; -use parser::ast::{Expression, WaveType}; use crate::codegen::abi_c::{ParamLowering, RetLowering}; use crate::statement::variable::{coerce_basic_value, CoercionMode}; +use inkwell::types::{AnyTypeEnum, AsTypeRef, BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; +use inkwell::values::{ + BasicMetadataValueEnum, BasicValue, BasicValueEnum, PointerValue, ValueKind, +}; +use parser::ast::{Expression, WaveType}; fn meta_to_basic<'ctx>(m: BasicMetadataTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> { match m { @@ -66,10 +68,16 @@ fn pack_agg_to_int<'ctx, 'a>( ) -> BasicValueEnum<'ctx> { let agg_ty = agg.get_type(); - let agg_tmp = env.builder.build_alloca(agg_ty, &format!("{}_agg_tmp", tag)).unwrap(); + let agg_tmp = env + .builder + .build_alloca(agg_ty, &format!("{}_agg_tmp", tag)) + .unwrap(); env.builder.build_store(agg_tmp, agg).unwrap(); - let int_tmp = env.builder.build_alloca(dst, &format!("{}_int_tmp", tag)).unwrap(); + let int_tmp = env + .builder + .build_alloca(dst, &format!("{}_int_tmp", tag)) + .unwrap(); let bytes = env.target_data.get_store_size(&agg_ty) as u64; let size_v = env.context.i64_type().const_int(bytes, false); @@ -90,8 +98,14 @@ fn unpack_int_to_agg<'ctx, 'a>( dst_agg_ty: BasicTypeEnum<'ctx>, tag: &str, ) -> BasicValueEnum<'ctx> { - let agg_tmp = env.builder.build_alloca(dst_agg_ty, &format!("{}_agg_tmp", tag)).unwrap(); - let int_tmp = env.builder.build_alloca(iv.get_type(), &format!("{}_int_tmp", tag)).unwrap(); + let agg_tmp = env + .builder + .build_alloca(dst_agg_ty, &format!("{}_agg_tmp", tag)) + .unwrap(); + let int_tmp = env + .builder + .build_alloca(iv.get_type(), &format!("{}_int_tmp", tag)) + .unwrap(); env.builder.build_store(int_tmp, iv).unwrap(); @@ -109,7 +123,9 @@ fn unpack_int_to_agg<'ctx, 'a>( } fn normalize_struct_name(raw: &str) -> &str { - raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') + raw.strip_prefix("struct.") + .unwrap_or(raw) + .trim_start_matches('%') } fn resolve_struct_key<'ctx>( @@ -134,8 +150,9 @@ fn wave_type_of_expr<'ctx, 'a>(env: &ExprGenEnv<'ctx, 'a>, e: &Expression) -> Op match e { Expression::Variable(name) => env.variables.get(name).map(|vi| vi.ty.clone()), Expression::Grouped(inner) => wave_type_of_expr(env, inner), - Expression::AddressOf(inner) => wave_type_of_expr(env, inner) - .map(|t| WaveType::Pointer(Box::new(t))), + Expression::AddressOf(inner) => { + wave_type_of_expr(env, inner).map(|t| WaveType::Pointer(Box::new(t))) + } Expression::Deref(inner) => { // *p -> T (p: ptr) if let Expression::Variable(name) = &**inner { @@ -205,8 +222,12 @@ pub(crate) fn gen_method_call<'ctx, 'a>( let mut arg_val = env.gen(arg_expr, expected_ty); if let Some(et) = expected_ty { arg_val = coerce_basic_value( - env.context, env.builder, arg_val, et, &format!("arg{}_cast", i), - CoercionMode::Implicit + env.context, + env.builder, + arg_val, + et, + &format!("arg{}_cast", i), + CoercionMode::Implicit, ); } call_args.push(arg_val.into()); @@ -218,8 +239,7 @@ pub(crate) fn gen_method_call<'ctx, 'a>( .unwrap(); if function.get_type().get_return_type().is_some() { - return callsite_to_ret(call_site, true, "struct method") - .unwrap(); + return callsite_to_ret(call_site, true, "struct method").unwrap(); } else { return env.context.i32_type().const_zero().as_basic_value_enum(); } @@ -259,8 +279,12 @@ pub(crate) fn gen_method_call<'ctx, 'a>( let mut arg_val = env.gen(arg_expr, expected_ty); if let Some(et) = expected_ty { arg_val = coerce_basic_value( - env.context, env.builder, arg_val, et, &format!("arg{}_cast", i), - CoercionMode::Implicit + env.context, + env.builder, + arg_val, + et, + &format!("arg{}_cast", i), + CoercionMode::Implicit, ); } call_args.push(arg_val.into()); @@ -272,8 +296,7 @@ pub(crate) fn gen_method_call<'ctx, 'a>( .unwrap(); if function.get_type().get_return_type().is_some() { - return callsite_to_ret(call_site, true, "method dispatch") - .unwrap(); + return callsite_to_ret(call_site, true, "method dispatch").unwrap(); } else { return env.context.i32_type().const_zero().as_basic_value_enum(); } @@ -308,8 +331,12 @@ pub(crate) fn gen_method_call<'ctx, 'a>( let mut arg_val = env.gen(arg_expr, expected_ty); if let Some(et) = expected_ty { arg_val = coerce_basic_value( - env.context, env.builder, arg_val, et, &format!("arg{}_cast", i), - CoercionMode::Implicit + env.context, + env.builder, + arg_val, + et, + &format!("arg{}_cast", i), + CoercionMode::Implicit, ); } call_args.push(arg_val.into()); @@ -333,16 +360,20 @@ pub(crate) fn gen_function_call<'ctx, 'a>( args: &[Expression], expected_type: Option>, ) -> BasicValueEnum<'ctx> { - if let Some(info) = env.extern_c_info.get(name) { - let function = env.module - .get_function(&info.llvm_name) - .unwrap_or_else(|| panic!("Extern function '{}' not found in module (symbol alias?)", name)); + let function = env.module.get_function(&info.llvm_name).unwrap_or_else(|| { + panic!( + "Extern function '{}' not found in module (symbol alias?)", + name + ) + }); if args.len() != info.params.len() { panic!( "Extern `{}` expects {} arguments (wave-level), got {}", - name, info.params.len(), args.len() + name, + info.params.len(), + args.len() ); } @@ -356,7 +387,10 @@ pub(crate) fn gen_function_call<'ctx, 'a>( let mut sret_tmp: Option> = None; if let RetLowering::SRet { ty, .. } = &info.ret { let agg = any_agg_to_basic(*ty); - let tmp = env.builder.build_alloca(agg, &format!("{}_sret_tmp", name)).unwrap(); + let tmp = env + .builder + .build_alloca(agg, &format!("{}_sret_tmp", name)) + .unwrap(); let expected_ptr = meta_into_ptr(llvm_param_types[0]); let tmp2 = coerce_ptr_to(env, tmp, expected_ptr, &format!("{}_sret_ptrcast", name)); @@ -379,42 +413,42 @@ pub(crate) fn gen_function_call<'ctx, 'a>( ParamLowering::ByVal { ty, .. } => { let agg = any_agg_to_basic(*ty); let v = env.gen(arg_expr, Some(agg)); - let tmp = env.builder.build_alloca(agg, &format!("{}_byval_tmp_{}", name, i)).unwrap(); + let tmp = env + .builder + .build_alloca(agg, &format!("{}_byval_tmp_{}", name, i)) + .unwrap(); env.builder.build_store(tmp, v).unwrap(); let expected_ptr = meta_into_ptr(llvm_param_types[llvm_pi]); - let tmp2 = coerce_ptr_to(env, tmp, expected_ptr, &format!("{}_byval_ptrcast_{}", name, i)); + let tmp2 = coerce_ptr_to( + env, + tmp, + expected_ptr, + &format!("{}_byval_ptrcast_{}", name, i), + ); lowered_args.push(tmp2.as_basic_value_enum().into()); llvm_pi += 1; } ParamLowering::Split(parts) => { - let mut agg_val = env.gen(arg_expr, None); - - if let BasicValueEnum::PointerValue(pv) = agg_val { - let bytes: u64 = parts - .iter() - .map(|t| env.target_data.get_store_size(t) as u64) - .sum(); - - if bytes == 0 { - panic!("Split lowering got zero-sized parts for {}", name); - } - - let bits = (bytes * 8) as u32; - let it = env.context.custom_width_int_type(bits); - - agg_val = env.builder - .build_load(it, pv, &format!("{}_split_load_{}", name, i)) - .unwrap() - .as_basic_value_enum(); - } - - let split_vals = split_hfa_from_agg(env, agg_val, parts, &format!("{}_split_{}", name, i)); + let agg_val = env.gen(arg_expr, None); + let split_vals = split_agg_parts_from_agg( + env, + agg_val, + parts, + &format!("{}_split_{}", name, i), + ); for sv in split_vals { let et = meta_to_basic(llvm_param_types[llvm_pi]); - let vv = coerce_basic_value(env.context, env.builder, sv, et, "split_cast", CoercionMode::Implicit); + let vv = coerce_basic_value( + env.context, + env.builder, + sv, + et, + "split_cast", + CoercionMode::Implicit, + ); lowered_args.push(vv.into()); llvm_pi += 1; } @@ -427,7 +461,8 @@ pub(crate) fn gen_function_call<'ctx, 'a>( _ => format!("call_{}", name), }; - let call_site = env.builder + let call_site = env + .builder .build_call(function, &lowered_args, &call_name) .unwrap(); @@ -435,7 +470,10 @@ pub(crate) fn gen_function_call<'ctx, 'a>( match &info.ret { RetLowering::Void => { if expected_type.is_some() { - panic!("Extern '{}' returns void and cannot be used as a value", name); + panic!( + "Extern '{}' returns void and cannot be used as a value", + name + ); } return env.context.i32_type().const_zero().as_basic_value_enum(); } @@ -443,12 +481,18 @@ pub(crate) fn gen_function_call<'ctx, 'a>( RetLowering::SRet { ty, .. } => { let tmp = sret_tmp.expect("SRet lowering requires sret tmp"); let agg = any_agg_to_basic(*ty); - let v = env.builder + let v = env + .builder .build_load(agg, tmp, &format!("{}_sret_load", name)) .unwrap(); if let Some(et) = expected_type { - return coerce_lowered_ret_to_expected(env, v.as_basic_value_enum(), et, "sret_ret"); + return coerce_lowered_ret_to_expected( + env, + v.as_basic_value_enum(), + et, + "sret_ret", + ); } return v.as_basic_value_enum(); } @@ -495,7 +539,11 @@ pub(crate) fn gen_function_call<'ctx, 'a>( call_args.push(val.into()); } - let call_name = if ret_type.is_some() { format!("call_{}", name) } else { String::new() }; + let call_name = if ret_type.is_some() { + format!("call_{}", name) + } else { + String::new() + }; let call_site = env .builder @@ -506,7 +554,10 @@ pub(crate) fn gen_function_call<'ctx, 'a>( Some(_) => callsite_to_ret(call_site, true, "function call").unwrap(), None => { if expected_type.is_some() { - panic!("Function '{}' returns void and cannot be used as a value", name); + panic!( + "Function '{}' returns void and cannot be used as a value", + name + ); } env.context.i32_type().const_zero().as_basic_value_enum() } @@ -528,25 +579,25 @@ fn coerce_to_expected<'ctx, 'a>( match (got, expected) { // 0) ptr -> int (ptrtoint) (needed for syscall wrappers that take i64 registers) (BasicTypeEnum::PointerType(_), BasicTypeEnum::IntType(dst)) - if dst.get_bit_width() == 64 && name.starts_with("syscall") => - { - let pv = val.into_pointer_value(); - env.builder - .build_ptr_to_int(pv, dst, &format!("arg{}_p2i", arg_index)) - .unwrap() - .as_basic_value_enum() - } + if dst.get_bit_width() == 64 && name.starts_with("syscall") => + { + let pv = val.into_pointer_value(); + env.builder + .build_ptr_to_int(pv, dst, &format!("arg{}_p2i", arg_index)) + .unwrap() + .as_basic_value_enum() + } // 0.1) int -> ptr (inttoptr) (useful when passing raw addresses) (BasicTypeEnum::IntType(src), BasicTypeEnum::PointerType(dst)) - if src.get_bit_width() == 64 && name.starts_with("syscall") => - { - let iv = val.into_int_value(); - env.builder - .build_int_to_ptr(iv, dst, &format!("arg{}_i2p", arg_index)) - .unwrap() - .as_basic_value_enum() - } + if src.get_bit_width() == 64 && name.starts_with("syscall") => + { + let iv = val.into_int_value(); + env.builder + .build_int_to_ptr(iv, dst, &format!("arg{}_i2p", arg_index)) + .unwrap() + .as_basic_value_enum() + } // 1) int -> int (BasicTypeEnum::IntType(src), BasicTypeEnum::IntType(dst)) => { @@ -588,79 +639,90 @@ fn coerce_to_expected<'ctx, 'a>( } // 4) ptr -> ptr (bitcast) - (BasicTypeEnum::PointerType(_), BasicTypeEnum::PointerType(dst)) => { - env.builder - .build_bit_cast(val, dst, &format!("arg{}_ptrcast", arg_index)) - .unwrap() - .as_basic_value_enum() - } + (BasicTypeEnum::PointerType(_), BasicTypeEnum::PointerType(dst)) => env + .builder + .build_bit_cast(val, dst, &format!("arg{}_ptrcast", arg_index)) + .unwrap() + .as_basic_value_enum(), // 4.4) agg(struct/array) -> int (ABI: small structs passed as INTEGER) - (got_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), - BasicTypeEnum::IntType(dst)) => - { - let sz = env.target_data.get_store_size(&got_agg) as u64; - let bits = (sz * 8) as u32; - - if bits == dst.get_bit_width() { - return pack_agg_to_int(env, val, dst, &format!("arg{}_pack", arg_index)); - } + ( + got_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), + BasicTypeEnum::IntType(dst), + ) => { + let sz = env.target_data.get_store_size(&got_agg) as u64; + let bits = (sz * 8) as u32; - panic!( - "Cannot pack aggregate to int: agg bits {} != dst bits {} (arg {} of {})", - bits, dst.get_bit_width(), arg_index, name - ); + if bits == dst.get_bit_width() { + return pack_agg_to_int(env, val, dst, &format!("arg{}_pack", arg_index)); } + panic!( + "Cannot pack aggregate to int: agg bits {} != dst bits {} (arg {} of {})", + bits, + dst.get_bit_width(), + arg_index, + name + ); + } + // 4.5) agg(struct/array) -> vector (HFA/ABI: e.g. Vector2 passed as <2 x float>) - (got_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), - BasicTypeEnum::VectorType(vt)) => - { - // size check (ABI layout must match) - let got_sz = env.target_data.get_store_size(&got_agg); - let exp_sz = env.target_data.get_store_size(&BasicTypeEnum::VectorType(vt)); - if got_sz != exp_sz { - panic!( - "Cannot coerce agg->vector: size mismatch {} vs {} (arg {} of {})", - got_sz, exp_sz, arg_index, name - ); - } + ( + got_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), + BasicTypeEnum::VectorType(vt), + ) => { + // size check (ABI layout must match) + let got_sz = env.target_data.get_store_size(&got_agg); + let exp_sz = env + .target_data + .get_store_size(&BasicTypeEnum::VectorType(vt)); + if got_sz != exp_sz { + panic!( + "Cannot coerce agg->vector: size mismatch {} vs {} (arg {} of {})", + got_sz, exp_sz, arg_index, name + ); + } - let tmp = env.builder - .build_alloca(got_agg, &format!("arg{}_agg_tmp", arg_index)) - .unwrap(); - env.builder.build_store(tmp, val).unwrap(); + let tmp = env + .builder + .build_alloca(got_agg, &format!("arg{}_agg_tmp", arg_index)) + .unwrap(); + env.builder.build_store(tmp, val).unwrap(); - env.builder - .build_load(vt, tmp, &format!("arg{}_agg2v", arg_index)) - .unwrap() - .as_basic_value_enum() - } + env.builder + .build_load(vt, tmp, &format!("arg{}_agg2v", arg_index)) + .unwrap() + .as_basic_value_enum() + } // 4.6) vector -> agg(struct/array) (reverse of above) - (BasicTypeEnum::VectorType(vt), - dst_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_))) => - { - let got_sz = env.target_data.get_store_size(&BasicTypeEnum::VectorType(vt)); - let exp_sz = env.target_data.get_store_size(&dst_agg); - if got_sz != exp_sz { - panic!( - "Cannot coerce vector->agg: size mismatch {} vs {} (arg {} of {})", - got_sz, exp_sz, arg_index, name - ); - } + ( + BasicTypeEnum::VectorType(vt), + dst_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), + ) => { + let got_sz = env + .target_data + .get_store_size(&BasicTypeEnum::VectorType(vt)); + let exp_sz = env.target_data.get_store_size(&dst_agg); + if got_sz != exp_sz { + panic!( + "Cannot coerce vector->agg: size mismatch {} vs {} (arg {} of {})", + got_sz, exp_sz, arg_index, name + ); + } - let tmp = env.builder - .build_alloca(dst_agg, &format!("arg{}_v2agg_tmp", arg_index)) - .unwrap(); + let tmp = env + .builder + .build_alloca(dst_agg, &format!("arg{}_v2agg_tmp", arg_index)) + .unwrap(); - env.builder.build_store(tmp, val).unwrap(); + env.builder.build_store(tmp, val).unwrap(); - env.builder - .build_load(dst_agg, tmp, &format!("arg{}_v2agg", arg_index)) - .unwrap() - .as_basic_value_enum() - } + env.builder + .build_load(dst_agg, tmp, &format!("arg{}_v2agg", arg_index)) + .unwrap() + .as_basic_value_enum() + } // 4.7) ptr -> vector (used for ptr-to-agg cases) (BasicTypeEnum::PointerType(_), BasicTypeEnum::VectorType(vt)) => { @@ -671,7 +733,10 @@ fn coerce_to_expected<'ctx, 'a>( .as_basic_value_enum() } - (BasicTypeEnum::IntType(src), dst_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_))) => { + ( + BasicTypeEnum::IntType(src), + dst_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), + ) => { let sz = env.target_data.get_store_size(&dst_agg) as u64; let bits = (sz * 8) as u32; if bits == src.get_bit_width() { @@ -680,7 +745,10 @@ fn coerce_to_expected<'ctx, 'a>( } panic!( "Cannot unpack int to aggregate: int bits {} != agg bits {} (arg {} of {})", - src.get_bit_width(), bits, arg_index, name + src.get_bit_width(), + bits, + arg_index, + name ); } @@ -697,7 +765,10 @@ fn any_agg_to_basic<'ctx>(ty: AnyTypeEnum<'ctx>) -> BasicTypeEnum<'ctx> { match ty { AnyTypeEnum::StructType(st) => st.as_basic_type_enum(), AnyTypeEnum::ArrayType(at) => at.as_basic_type_enum(), - _ => panic!("Expected aggregate AnyTypeEnum (struct/array), got {:?}", ty), + _ => panic!( + "Expected aggregate AnyTypeEnum (struct/array), got {:?}", + ty + ), } } @@ -720,74 +791,81 @@ fn coerce_ptr_to<'ctx, 'a>( .into_pointer_value() } -fn split_hfa_from_agg<'ctx, 'a>( +fn split_agg_parts_from_agg<'ctx, 'a>( env: &ExprGenEnv<'ctx, 'a>, agg_val: BasicValueEnum<'ctx>, parts: &[BasicTypeEnum<'ctx>], tag: &str, ) -> Vec> { - let agg_ty = agg_val.get_type(); - let tmp = env.builder.build_alloca(agg_ty, &format!("{tag}_hfa_tmp")).unwrap(); - env.builder.build_store(tmp, agg_val).unwrap(); - - let float_ty: inkwell::types::FloatType<'ctx> = match parts.first().unwrap() { - BasicTypeEnum::FloatType(ft) => *ft, - BasicTypeEnum::VectorType(vt) => match vt.get_element_type() { - BasicTypeEnum::FloatType(ft) => ft, - other => panic!("HFA vector elem is not float: {:?}", other), - }, - other => panic!("HFA part is not float/vector: {:?}", other), - }; - let mut out = Vec::with_capacity(parts.len()); - let mut idx: u32 = 0; + let total_bytes: u64 = parts + .iter() + .map(|t| env.target_data.get_store_size(t) as u64) + .sum(); + if total_bytes == 0 { + panic!("Split lowering got zero-sized parts"); + } + + let i8_ptr_ty = env + .context + .ptr_type(inkwell::AddressSpace::default()) + .as_basic_type_enum(); + + let src_i8_ptr = match agg_val { + BasicValueEnum::PointerValue(pv) => env + .builder + .build_bit_cast(pv, i8_ptr_ty, &format!("{tag}_src_ptrcast")) + .unwrap() + .into_pointer_value(), + _ => { + let agg_ty = agg_val.get_type(); + let tmp = env + .builder + .build_alloca(agg_ty, &format!("{tag}_src_tmp")) + .unwrap(); + env.builder.build_store(tmp, agg_val).unwrap(); + env.builder + .build_bit_cast(tmp, i8_ptr_ty, &format!("{tag}_src_i8")) + .unwrap() + .into_pointer_value() + } + }; + let mut offset: u64 = 0; for (pi, part_ty) in parts.iter().enumerate() { - match part_ty { - BasicTypeEnum::FloatType(_) => { - let i = env.context.i32_type().const_int(idx as u64, false); - let fptr_ty = float_ty.ptr_type(tmp.get_type().get_address_space()); - let base_fptr = env.builder - .build_bit_cast(tmp.as_basic_value_enum(), fptr_ty.as_basic_type_enum(), &format!("{tag}_hfa_fptr")) - .unwrap() - .into_pointer_value(); - let ep = unsafe { - env.builder - .build_gep(float_ty, base_fptr, &[i], &format!("{tag}_hfa_gep_{pi}")) - .unwrap() - }; - let fv = env.builder - .build_load(float_ty, ep, &format!("{tag}_hfa_f_{pi}")) - .unwrap(); - out.push(fv.as_basic_value_enum()); - idx += 1; - } - BasicTypeEnum::VectorType(vt) => { - let n = vt.get_size(); // element count - let mut v = vt.get_undef(); - - for j in 0..n { - let i = env.context.i32_type().const_int((idx + j) as u64, false); - let ep = unsafe { - env.builder - .build_gep(float_ty, tmp, &[i], &format!("{tag}_hfa_gep_{pi}_{j}")) - .unwrap() - }; - let fv = env.builder - .build_load(float_ty, ep, &format!("{tag}_hfa_f_{pi}_{j}")) - .unwrap(); + let part_size = env.target_data.get_store_size(part_ty) as u64; + let dst = env + .builder + .build_alloca(*part_ty, &format!("{tag}_part_dst_{pi}")) + .unwrap(); + let dst_i8 = env + .builder + .build_bit_cast(dst, i8_ptr_ty, &format!("{tag}_dst_i8_{pi}")) + .unwrap() + .into_pointer_value(); + + let off = env.context.i64_type().const_int(offset, false); + let src_off = unsafe { + env.builder + .build_gep( + env.context.i8_type(), + src_i8_ptr, + &[off], + &format!("{tag}_src_gep_{pi}"), + ) + .unwrap() + }; - let jv = env.context.i32_type().const_int(j as u64, false); - v = env.builder - .build_insert_element(v, fv, jv, &format!("{tag}_hfa_ins_{pi}_{j}")) - .unwrap(); - } + let sz = env.context.i64_type().const_int(part_size, false); + env.builder.build_memcpy(dst_i8, 1, src_off, 1, sz).unwrap(); - out.push(v.as_basic_value_enum()); - idx += n; - } - other => panic!("Unsupported Split part type: {:?}", other), - } + let part_val = env + .builder + .build_load(*part_ty, dst, &format!("{tag}_part_load_{pi}")) + .unwrap() + .as_basic_value_enum(); + out.push(part_val); + offset += part_size; } out @@ -810,8 +888,14 @@ fn coerce_lowered_ret_to_expected<'ctx, 'a>( } // vector(float) -> struct/array (HFA ret) - (BasicTypeEnum::VectorType(_vt), BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)) => { - let tmp = env.builder.build_alloca(expected, &format!("{tag}_v2agg_tmp")).unwrap(); + ( + BasicTypeEnum::VectorType(_vt), + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_), + ) => { + let tmp = env + .builder + .build_alloca(expected, &format!("{tag}_v2agg_tmp")) + .unwrap(); env.builder.build_store(tmp, lowered_ret).unwrap(); env.builder .build_load(expected, tmp, &format!("{tag}_v2agg_load")) @@ -819,8 +903,14 @@ fn coerce_lowered_ret_to_expected<'ctx, 'a>( .as_basic_value_enum() } - (BasicTypeEnum::FloatType(_ft), BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)) => { - let tmp = env.builder.build_alloca(expected, &format!("{tag}_f2agg_tmp")).unwrap(); + ( + BasicTypeEnum::FloatType(_ft), + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_), + ) => { + let tmp = env + .builder + .build_alloca(expected, &format!("{tag}_f2agg_tmp")) + .unwrap(); env.builder.build_store(tmp, lowered_ret).unwrap(); env.builder .build_load(expected, tmp, &format!("{tag}_f2agg_load")) @@ -828,6 +918,39 @@ fn coerce_lowered_ret_to_expected<'ctx, 'a>( .as_basic_value_enum() } + // lowered tuple-struct (e.g. {double,double}, {<2xf>,float}) -> expected aggregate + ( + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_), + BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_), + ) => { + let got = lowered_ret.get_type(); + let got_sz = env.target_data.get_store_size(&got); + let exp_sz = env.target_data.get_store_size(&expected); + + if got_sz < exp_sz { + return lowered_ret; + } + + let src = env + .builder + .build_alloca(got, &format!("{tag}_agg_src")) + .unwrap(); + env.builder.build_store(src, lowered_ret).unwrap(); + + let dst = env + .builder + .build_alloca(expected, &format!("{tag}_agg_dst")) + .unwrap(); + + let bytes = env.context.i64_type().const_int(exp_sz as u64, false); + env.builder.build_memcpy(dst, 1, src, 1, bytes).unwrap(); + + env.builder + .build_load(expected, dst, &format!("{tag}_agg_cast")) + .unwrap() + .as_basic_value_enum() + } + _ => lowered_ret, } } diff --git a/llvm/src/expression/rvalue/cast.rs b/llvm/src/expression/rvalue/cast.rs new file mode 100644 index 00000000..e7d6edc4 --- /dev/null +++ b/llvm/src/expression/rvalue/cast.rs @@ -0,0 +1,49 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024–2026 Wave Foundation +// Copyright (c) 2024–2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 + +use super::ExprGenEnv; +use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; +use crate::statement::variable::{coerce_basic_value, CoercionMode}; +use inkwell::types::{BasicType, BasicTypeEnum}; +use inkwell::values::BasicValueEnum; +use parser::ast::{Expression, Literal, WaveType}; + +pub(crate) fn gen<'ctx, 'a>( + env: &mut ExprGenEnv<'ctx, 'a>, + expr: &Expression, + target_type: &WaveType, +) -> BasicValueEnum<'ctx> { + let dst_ty = wave_type_to_llvm_type( + env.context, + target_type, + env.struct_types, + TypeFlavor::Value, + ); + + // Integer literals default to i32 with no context. + // For explicit cast to pointer, prefer i64 source width. + let src_hint = match (expr, dst_ty) { + (Expression::Literal(Literal::Int(_)), BasicTypeEnum::PointerType(_)) => { + Some(env.context.i64_type().as_basic_type_enum()) + } + _ => None, + }; + + let src = env.gen(expr, src_hint); + coerce_basic_value( + env.context, + env.builder, + src, + dst_ty, + "as_cast", + CoercionMode::Explicit, + ) +} diff --git a/llvm/src/expression/rvalue/dispatch.rs b/llvm/src/expression/rvalue/dispatch.rs index b0824914..0101df42 100644 --- a/llvm/src/expression/rvalue/dispatch.rs +++ b/llvm/src/expression/rvalue/dispatch.rs @@ -27,31 +27,47 @@ pub(crate) fn gen_expr<'ctx, 'a>( Expression::Deref(inner) => pointers::gen_deref(env, inner, expected_type), Expression::AddressOf(inner) => pointers::gen_addressof(env, inner, expected_type), - Expression::MethodCall { object, name, args } => calls::gen_method_call(env, object, name, args), - Expression::FunctionCall { name, args } => calls::gen_function_call(env, name, args, expected_type), - - Expression::AssignOperation { target, operator, value } => { - assign::gen_assign_operation(env, target, operator, value) + Expression::MethodCall { object, name, args } => { + calls::gen_method_call(env, object, name, args) + } + Expression::FunctionCall { name, args } => { + calls::gen_function_call(env, name, args, expected_type) } + Expression::Cast { expr, target_type } => cast::gen(env, expr, target_type), + + Expression::AssignOperation { + target, + operator, + value, + } => assign::gen_assign_operation(env, target, operator, value), Expression::Assignment { target, value } => assign::gen_assignment(env, target, value), - Expression::BinaryExpression { left, operator, right } => { - binary::gen(env, left, operator, right, expected_type) - } + Expression::BinaryExpression { + left, + operator, + right, + } => binary::gen(env, left, operator, right, expected_type), Expression::IndexAccess { target, index } => index::gen(env, target, index), - Expression::AsmBlock { instructions, inputs, outputs, clobbers } => { - asm::gen(env, instructions, inputs, outputs, clobbers) - } + Expression::AsmBlock { + instructions, + inputs, + outputs, + clobbers, + } => asm::gen(env, instructions, inputs, outputs, clobbers), - Expression::StructLiteral { name, fields } => structs::gen_struct_literal(env, name, fields), + Expression::StructLiteral { name, fields } => { + structs::gen_struct_literal(env, name, fields) + } Expression::FieldAccess { object, field } => structs::gen_field_access(env, object, field), Expression::Unary { operator, expr } => unary::gen(env, operator, expr, expected_type), Expression::IncDec { kind, target } => incdec::gen(env, kind, target), Expression::Grouped(inner) => env.gen(inner, expected_type), - Expression::ArrayLiteral(elements) => arrays::gen_array_literal(env, elements, expected_type), + Expression::ArrayLiteral(elements) => { + arrays::gen_array_literal(env, elements, expected_type) + } } } diff --git a/llvm/src/expression/rvalue/mod.rs b/llvm/src/expression/rvalue/mod.rs index b9679fcb..5b7aee7e 100644 --- a/llvm/src/expression/rvalue/mod.rs +++ b/llvm/src/expression/rvalue/mod.rs @@ -9,32 +9,33 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::codegen::VariableInfo; use crate::codegen::abi_c::ExternCInfo; +use crate::codegen::VariableInfo; use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::module::Module; +use inkwell::targets::TargetData; use inkwell::types::{BasicTypeEnum, StructType}; -use inkwell::values::{BasicValueEnum}; +use inkwell::values::BasicValueEnum; use parser::ast::Expression; use std::collections::HashMap; -use inkwell::targets::TargetData; pub mod dispatch; pub mod utils; -pub mod literals; -pub mod variables; -pub mod pointers; -pub mod calls; +pub mod arrays; +pub mod asm; pub mod assign; pub mod binary; +pub mod calls; +pub mod cast; +pub mod incdec; pub mod index; -pub mod asm; +pub mod literals; +pub mod pointers; pub mod structs; pub mod unary; -pub mod incdec; -pub mod arrays; +pub mod variables; pub struct ProtoInfo<'ctx> { pub vtable_ty: StructType<'ctx>, diff --git a/llvm/src/statement/asm.rs b/llvm/src/statement/asm.rs index a98558b2..1494660a 100644 --- a/llvm/src/statement/asm.rs +++ b/llvm/src/statement/asm.rs @@ -9,13 +9,16 @@ // // SPDX-License-Identifier: MPL-2.0 -use crate::codegen::VariableInfo; use crate::codegen::plan::*; +use crate::codegen::target::{require_supported_target_from_module, CodegenTarget}; use crate::codegen::types::{wave_type_to_llvm_type, TypeFlavor}; +use crate::codegen::VariableInfo; use inkwell::module::Module; -use inkwell::values::{BasicMetadataValueEnum, BasicValue, BasicValueEnum, PointerValue, ValueKind}; -use inkwell::{InlineAsmDialect}; +use inkwell::values::{ + BasicMetadataValueEnum, BasicValue, BasicValueEnum, PointerValue, ValueKind, +}; +use inkwell::InlineAsmDialect; use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum, StringRadix}; @@ -43,35 +46,63 @@ fn llvm_type_of_wave<'ctx>( fn reg_width_bits(reg: &str) -> Option { match reg { - "al" | "bl" | "cl" | "dl" | - "sil" | "dil" | - "r8b" | "r9b" | "r10b" | "r11b" | - "r12b" | "r13b" | "r14b" | "r15b" => Some(8), - - "ax" | "bx" | "cx" | "dx" | - "si" | "di" | - "r8w" | "r9w" | "r10w" | "r11w" | - "r12w" | "r13w" | "r14w" | "r15w" => Some(16), - - "eax" | "ebx" | "ecx" | "edx" | - "esi" | "edi" | - "r8d" | "r9d" | "r10d" | "r11d" | - "r12d" | "r13d" | "r14d" | "r15d" => Some(32), - - "rax" | "rbx" | "rcx" | "rdx" | - "rsi" | "rdi" | "rbp" | "rsp" | - "r8" | "r9" | "r10" | "r11" | - "r12" | "r13" | "r14" | "r15" => Some(64), + "al" | "bl" | "cl" | "dl" | "sil" | "dil" | "r8b" | "r9b" | "r10b" | "r11b" | "r12b" + | "r13b" | "r14b" | "r15b" => Some(8), + + "ax" | "bx" | "cx" | "dx" | "si" | "di" | "r8w" | "r9w" | "r10w" | "r11w" | "r12w" + | "r13w" | "r14w" | "r15w" => Some(16), + + "eax" | "ebx" | "ecx" | "edx" | "esi" | "edi" | "r8d" | "r9d" | "r10d" | "r11d" + | "r12d" | "r13d" | "r14d" | "r15d" => Some(32), + + "rax" | "rbx" | "rcx" | "rdx" | "rsi" | "rdi" | "rbp" | "rsp" | "r8" | "r9" | "r10" + | "r11" | "r12" | "r13" | "r14" | "r15" => Some(64), _ => None, } } +fn reg_width_bits_for_target(target: CodegenTarget, reg: &str) -> Option { + match target { + CodegenTarget::LinuxX86_64 => reg_width_bits(reg), + CodegenTarget::DarwinArm64 => { + if reg.len() >= 2 { + let (prefix, num) = reg.split_at(1); + if num.chars().all(|c| c.is_ascii_digit()) && !num.is_empty() { + if let Ok(n) = num.parse::() { + if n <= 30 { + return match prefix { + "w" => Some(32), + "x" => Some(64), + _ => None, + }; + } + } + } + } + None + } + } +} + fn extract_reg_from_constraint(c: &str) -> Option { if let Some(inner) = c.strip_prefix('{').and_then(|s| s.strip_suffix('}')) { return Some(inner.to_ascii_lowercase()); } - None + + let token = c.trim().trim_start_matches('%').to_ascii_lowercase(); + if token.is_empty() { + None + } else { + Some(token) + } +} + +fn inline_asm_dialect_for_target(target: CodegenTarget) -> InlineAsmDialect { + match target { + CodegenTarget::LinuxX86_64 => InlineAsmDialect::Intel, + CodegenTarget::DarwinArm64 => InlineAsmDialect::ATT, + } } pub(super) fn gen_asm_stmt_ir<'ctx>( @@ -86,18 +117,33 @@ pub(super) fn gen_asm_stmt_ir<'ctx>( global_consts: &HashMap>, struct_types: &HashMap>, ) { - let plan = AsmPlan::build(instructions, inputs, outputs, clobbers, AsmSafetyMode::ConservativeKernel); + let target = require_supported_target_from_module(module); + let plan = AsmPlan::build( + target, + instructions, + inputs, + outputs, + clobbers, + AsmSafetyMode::ConservativeKernel, + ); let constraints_str = plan.constraints_string(); let mut operand_vals: Vec> = Vec::with_capacity(plan.inputs.len()); let mut param_types: Vec> = Vec::with_capacity(plan.inputs.len()); for inp in &plan.inputs { - let mut val = asm_operand_to_value(context, builder, variables, global_consts, struct_types, inp.value); + let mut val = asm_operand_to_value( + context, + builder, + variables, + global_consts, + struct_types, + inp.value, + ); // reg width forcing if let Some(reg) = extract_reg_from_constraint(&inp.constraint) { - if let Some(bits) = reg_width_bits(®) { + if let Some(bits) = reg_width_bits_for_target(target, ®) { if val.is_int_value() { let iv = val.into_int_value(); let target_ty = context.custom_width_int_type(bits); @@ -138,11 +184,12 @@ pub(super) fn gen_asm_stmt_ir<'ctx>( let mut out_tys: Vec> = Vec::with_capacity(plan.outputs.len()); for o in &plan.outputs { - let (place, dst_ty) = resolve_out_place_and_type(context, builder, variables, struct_types, o.target); + let (place, dst_ty) = + resolve_out_place_and_type(context, builder, variables, struct_types, o.target); let mut asm_ty = dst_ty; if let Some(reg) = extract_reg_from_constraint(&o.reg_norm) { - if let Some(bits) = reg_width_bits(®) { + if let Some(bits) = reg_width_bits_for_target(target, ®) { if dst_ty.is_int_type() { asm_ty = context.custom_width_int_type(bits).as_basic_type_enum(); } @@ -168,7 +215,7 @@ pub(super) fn gen_asm_stmt_ir<'ctx>( constraints_str, plan.has_side_effects, false, - Some(InlineAsmDialect::Intel), + Some(inline_asm_dialect_for_target(target)), false, ); @@ -222,10 +269,16 @@ fn infer_signedness<'ctx>( }, _ => None, }) - } else { None } + } else { + None + } } Expression::Literal(Literal::Int(s)) => { - if s.trim_start().starts_with('-') { Some(true) } else { None } + if s.trim_start().starts_with('-') { + Some(true) + } else { + None + } } _ => None, } @@ -240,15 +293,25 @@ fn resolve_out_place_and_type<'ctx>( ) -> (AsmOutPlace<'ctx>, BasicTypeEnum<'ctx>) { match target { Expression::Variable(name) => { - let info = variables.get(name).unwrap_or_else(|| panic!("Output var '{}' not found", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Output var '{}' not found", name)); let elem_ty = llvm_type_of_wave(context, &info.ty, struct_types); - (AsmOutPlace::VarAlloca { ptr: info.ptr, elem_ty }, elem_ty) + ( + AsmOutPlace::VarAlloca { + ptr: info.ptr, + elem_ty, + }, + elem_ty, + ) } Expression::Deref(inner) => { match inner.as_ref() { Expression::Variable(name) => { - let info = variables.get(name).unwrap_or_else(|| panic!("Pointer var '{}' not found", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Pointer var '{}' not found", name)); // 1) load pointer value from var slot (typed) let ptr_ty = llvm_type_of_wave(context, &info.ty, struct_types); @@ -264,18 +327,29 @@ fn resolve_out_place_and_type<'ctx>( // 2) pointee type from WaveType (opaque pointer safe) let elem_ty = match &info.ty { - WaveType::Pointer(inner_ty) => llvm_type_of_wave(context, inner_ty, struct_types), + WaveType::Pointer(inner_ty) => { + llvm_type_of_wave(context, inner_ty, struct_types) + } WaveType::String => context.i8_type().as_basic_type_enum(), other => panic!("out(*{}) requires pointer/string, got {:?}", name, other), }; - (AsmOutPlace::MemPtr { ptr: dst_ptr, elem_ty }, elem_ty) + ( + AsmOutPlace::MemPtr { + ptr: dst_ptr, + elem_ty, + }, + elem_ty, + ) } other => panic!("Unsupported deref out target: {:?}", other), } } - other => panic!("out(...) target must be variable or deref var for now: {:?}", other), + other => panic!( + "out(...) target must be variable or deref var for now: {:?}", + other + ), } } @@ -321,18 +395,30 @@ fn coerce_basic_value_for_store<'ctx>( if src_bits == dst_bits { return v.as_basic_value_enum(); } else if src_bits > dst_bits { - return builder.build_int_truncate(v, dst_int, "asm_int_trunc").unwrap().as_basic_value_enum(); + return builder + .build_int_truncate(v, dst_int, "asm_int_trunc") + .unwrap() + .as_basic_value_enum(); } else { - return builder.build_int_z_extend(v, dst_int, "asm_int_zext").unwrap().as_basic_value_enum(); + return builder + .build_int_z_extend(v, dst_int, "asm_int_zext") + .unwrap() + .as_basic_value_enum(); } } if value.is_pointer_value() { - return builder.build_ptr_to_int(value.into_pointer_value(), dst_int, "asm_ptr_to_int").unwrap().as_basic_value_enum(); + return builder + .build_ptr_to_int(value.into_pointer_value(), dst_int, "asm_ptr_to_int") + .unwrap() + .as_basic_value_enum(); } if value.is_float_value() { - return builder.build_float_to_signed_int(value.into_float_value(), dst_int, "asm_fptosi").unwrap().as_basic_value_enum(); + return builder + .build_float_to_signed_int(value.into_float_value(), dst_int, "asm_fptosi") + .unwrap() + .as_basic_value_enum(); } panic!("Cannot coerce asm output '{}' to int {:?}", name, dst_ty); @@ -352,7 +438,12 @@ fn coerce_basic_value_for_store<'ctx>( return ex.as_basic_value_enum(); } - panic!("Cannot coerce asm output '{}' from {:?} to float {:?}", name, value.get_type(), dst_ty); + panic!( + "Cannot coerce asm output '{}' from {:?} to float {:?}", + name, + value.get_type(), + dst_ty + ); } if value.is_int_value() { @@ -362,7 +453,12 @@ fn coerce_basic_value_for_store<'ctx>( .as_basic_value_enum(); } - panic!("Cannot coerce asm output '{}' from {:?} to float {:?}", name, value.get_type(), dst_ty); + panic!( + "Cannot coerce asm output '{}' from {:?} to float {:?}", + name, + value.get_type(), + dst_ty + ); } // dst: pointer @@ -383,10 +479,18 @@ fn coerce_basic_value_for_store<'ctx>( .as_basic_value_enum(); } - panic!("Cannot coerce asm output '{}' from {:?} to ptr {:?}", name, value.get_type(), dst_ty); + panic!( + "Cannot coerce asm output '{}' from {:?} to ptr {:?}", + name, + value.get_type(), + dst_ty + ); } - panic!("Unsupported destination type for asm output '{}': {:?}", name, dst_ty); + panic!( + "Unsupported destination type for asm output '{}': {:?}", + name, dst_ty + ); } fn asm_operand_to_value<'ctx>( @@ -400,14 +504,20 @@ fn asm_operand_to_value<'ctx>( match expr { Expression::Literal(Literal::Int(n)) => { let s = n.as_str(); - let (neg, digits) = if let Some(rest) = s.strip_prefix('-') { (true, rest) } else { (false, s) }; + let (neg, digits) = if let Some(rest) = s.strip_prefix('-') { + (true, rest) + } else { + (false, s) + }; let mut iv = context .i64_type() .const_int_from_string(digits, StringRadix::Decimal) .unwrap_or_else(|| panic!("invalid int literal: {}", s)); - if neg { iv = iv.const_neg(); } + if neg { + iv = iv.const_neg(); + } iv.as_basic_value_enum() } @@ -415,7 +525,9 @@ fn asm_operand_to_value<'ctx>( if let Some(const_val) = global_consts.get(name) { *const_val } else { - let info = variables.get(name).unwrap_or_else(|| panic!("Input variable '{}' not found", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Input variable '{}' not found", name)); let ty = llvm_type_of_wave(context, &info.ty, struct_types); builder @@ -427,17 +539,28 @@ fn asm_operand_to_value<'ctx>( Expression::AddressOf(inner) => match inner.as_ref() { Expression::Variable(name) => { - let info = variables.get(name).unwrap_or_else(|| panic!("Input variable '{}' not found", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Input variable '{}' not found", name)); info.ptr.as_basic_value_enum() } _ => panic!("Unsupported asm address-of operand: {:?}", inner), }, - Expression::Grouped(inner) => asm_operand_to_value(context, builder, variables, global_consts, struct_types, inner), + Expression::Grouped(inner) => asm_operand_to_value( + context, + builder, + variables, + global_consts, + struct_types, + inner, + ), Expression::Deref(inner) => match inner.as_ref() { Expression::Variable(name) => { - let info = variables.get(name).unwrap_or_else(|| panic!("Input pointer var '{}' not found", name)); + let info = variables + .get(name) + .unwrap_or_else(|| panic!("Input pointer var '{}' not found", name)); // 1) load pointer value from slot (typed) let ptr_ty = llvm_type_of_wave(context, &info.ty, struct_types); @@ -453,7 +576,9 @@ fn asm_operand_to_value<'ctx>( // 2) load pointee value (typed) let pointee_ty = match &info.ty { - WaveType::Pointer(inner_ty) => llvm_type_of_wave(context, inner_ty, struct_types), + WaveType::Pointer(inner_ty) => { + llvm_type_of_wave(context, inner_ty, struct_types) + } WaveType::String => context.i8_type().as_basic_type_enum(), other => panic!("deref input '{}' is not pointer/string: {:?}", name, other), }; diff --git a/llvm/src/statement/assign.rs b/llvm/src/statement/assign.rs index 5cc31b7f..cf46149d 100644 --- a/llvm/src/statement/assign.rs +++ b/llvm/src/statement/assign.rs @@ -97,7 +97,7 @@ pub(super) fn gen_assign_ir<'ctx>( (info.ptr, info.mutability.clone(), info.ty.clone()) }; - if matches!(dst_mutability, Mutability::Let) { + if matches!(dst_mutability, Mutability::Let | Mutability::Const) { panic!("Cannot assign to immutable variable '{}'", variable); } diff --git a/src/runner.rs b/src/runner.rs index c34e15ff..ace79c9d 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -12,6 +12,7 @@ use crate::{DebugFlags, DepFlags, LinkFlags}; use ::error::*; use ::parser::*; +use ::parser::verification::validate_program; use lexer::Lexer; use llvm::backend::*; use llvm::codegen::*; @@ -24,7 +25,7 @@ use ::parser::ast::*; use ::parser::import::*; fn parse_wave_tokens_or_exit(file_path: &Path, source: &str, tokens: &[lexer::Token]) -> Vec { - parse(tokens).unwrap_or_else(|err| { + parse_syntax_only(tokens).unwrap_or_else(|err| { let (kind, title, code) = match &err { ParseError::Syntax(_) => ( WaveErrorKind::SyntaxError(err.message().to_string()), @@ -72,6 +73,25 @@ fn parse_wave_tokens_or_exit(file_path: &Path, source: &str, tokens: &[lexer::To }) } +fn validate_wave_ast_or_exit(file_path: &Path, source: &str, ast: &Vec) { + if let Err(msg) = validate_program(ast) { + WaveError::new( + WaveErrorKind::InvalidStatement(msg.clone()), + format!("semantic validation failed: {}", msg), + file_path.display().to_string(), + 1, + 1, + ) + .with_code("E3001") + .with_source_code(source.to_string()) + .with_context("semantic validation") + .with_help("fix mutability, scope, and expression validity issues") + .display(); + + process::exit(1); + } +} + fn panic_payload_to_string(payload: &(dyn std::any::Any + Send)) -> String { if let Some(s) = payload.downcast_ref::() { return s.clone(); @@ -595,6 +615,8 @@ pub(crate) unsafe fn run_wave_file( } }; + validate_wave_ast_or_exit(file_path, &code, &ast); + let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag) }) { Ok(ir) => ir, Err((msg, loc)) => { @@ -703,6 +725,8 @@ pub(crate) unsafe fn object_build_wave_file( process::exit(1); }); + validate_wave_ast_or_exit(file_path, &code, &ast); + let ir = match run_panic_guarded(|| unsafe { generate_ir(&ast, opt_flag) }) { Ok(ir) => ir, Err((msg, loc)) => { @@ -781,36 +805,13 @@ pub(crate) unsafe fn img_wave_file(file_path: &Path, dep: &DepFlags) { process::exit(1); }); - let mut ast = parse_wave_tokens_or_exit(file_path, &code, &tokens); - - let file_path = Path::new(file_path); - let base_dir = file_path - .canonicalize() - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())) - .unwrap_or_else(|| Path::new(".").to_path_buf()); - - let mut already_imported = HashSet::new(); - let mut extended_ast = vec![]; + let ast = parse_wave_tokens_or_exit(file_path, &code, &tokens); let import_config = build_import_config(dep); - - for node in &ast { - if let ASTNode::Statement(StatementNode::Import(path)) = node { - match local_import_with_config(&path, &mut already_imported, &base_dir, &import_config) { - Ok(mut imported_nodes) => { - extended_ast.append(&mut imported_nodes); - } - Err(err) => { - err.display(); - process::exit(1); - } - } - } else { - extended_ast.push(node.clone()); - } - } - - ast = extended_ast; + let ast = expand_imports_for_codegen(file_path, ast, &import_config).unwrap_or_else(|e| { + e.display(); + process::exit(1); + }); + validate_wave_ast_or_exit(file_path, &code, &ast); // println!("{}\n", code); // for token in tokens { diff --git a/std/buffer/alloc.wave b/std/buffer/alloc.wave index 90788358..92a835bd 100644 --- a/std/buffer/alloc.wave +++ b/std/buffer/alloc.wave @@ -20,6 +20,13 @@ fun buffer_new(capacity: i64) -> Buffer { } var data: ptr = mem_alloc(cap); + if (data == null) { + return Buffer { + data: null, + len: 0, + cap: 0 + }; + } return Buffer { data: data, @@ -64,6 +71,9 @@ fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { } var new_data: ptr = mem_alloc(new_cap); + if (new_data == null) { + return -1; + } if (deref buf.len > 0) { mem_copy(new_data, deref buf.data, deref buf.len); diff --git a/std/mem/alloc.wave b/std/mem/alloc.wave index d71d0586..78666bc2 100644 --- a/std/mem/alloc.wave +++ b/std/mem/alloc.wave @@ -10,6 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 fun mem_alloc(size: i64) -> ptr { + if (size <= 0) { + return null; + } + var prot: i64 = 1 | 2; var flags: i64 = 2 | 32; var ret: ptr; @@ -26,11 +30,20 @@ fun mem_alloc(size: i64) -> ptr { out("rax") ret } + // Linux syscall returns negative errno on failure. + if ((ret as i64) < 0) { + return null; + } + return ret; } fun mem_alloc_zeroed(size: i64) -> ptr { var p: ptr = mem_alloc(size); + if (p == null) { + return null; + } + var i: i64 = 0; while (i < size) { deref p[i] = 0; @@ -40,6 +53,10 @@ fun mem_alloc_zeroed(size: i64) -> ptr { } fun mem_free(p: ptr, size: i64) -> i64 { + if (p == null || size <= 0) { + return 0; + } + var ret: i64; asm { diff --git a/std/net/tcp.wave b/std/net/tcp.wave index 95beffb7..e719faf5 100644 --- a/std/net/tcp.wave +++ b/std/net/tcp.wave @@ -105,7 +105,7 @@ fun tcp_bind(port: i16) -> TcpListener { } fun tcp_accept(listener: TcpListener) -> TcpStream { - let fd: i64 = accept(listener.fd, 0, 0); + let fd: i64 = accept(listener.fd, null, null); return TcpStream { fd: fd }; } diff --git a/std/sys/linux/fs.wave b/std/sys/linux/fs.wave index 0c6d3097..f4fe83fb 100644 --- a/std/sys/linux/fs.wave +++ b/std/sys/linux/fs.wave @@ -29,7 +29,7 @@ import("std::sys::linux::syscall"); fun open(path: str, flags: i32, mode: i32) -> i64 { // syscall: open (2) - return syscall3(2, path, flags, mode); + return syscall3(2, path as i64, flags as i64, mode as i64); } fun close(fd: i64) -> i64 { @@ -44,12 +44,12 @@ fun close(fd: i64) -> i64 { fun read(fd: i64, buf: ptr, len: i64) -> i64 { // syscall: read (0) - return syscall3(0, fd, buf, len); + return syscall3(0, fd, buf as i64, len); } fun write(fd: i64, buf: ptr, len: i64) -> i64 { // syscall: write (1) - return syscall3(1, fd, buf, len); + return syscall3(1, fd, buf as i64, len); } @@ -59,7 +59,7 @@ fun write(fd: i64, buf: ptr, len: i64) -> i64 { fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { // syscall: lseek (8) - return syscall3(8, fd, offset, whence); + return syscall3(8, fd, offset, whence as i64); } @@ -69,17 +69,17 @@ fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { fun unlink(path: str) -> i64 { // syscall: unlink (87) - return syscall1(87, path); + return syscall1(87, path as i64); } fun mkdir(path: str, mode: i32) -> i64 { // syscall: mkdir (83) - return syscall2(83, path, mode); + return syscall2(83, path as i64, mode as i64); } fun rmdir(path: str) -> i64 { // syscall: rmdir (84) - return syscall1(84, path); + return syscall1(84, path as i64); } @@ -106,10 +106,10 @@ struct Stat { fun stat(path: str, st: ptr) -> i64 { // syscall: stat (4) - return syscall2(4, path, st); + return syscall2(4, path as i64, st as i64); } fun fstat(fd: i64, st: ptr) -> i64 { // syscall: fstat (5) - return syscall2(5, fd, st); + return syscall2(5, fd, st as i64); } diff --git a/std/sys/linux/memory.wave b/std/sys/linux/memory.wave index 99aba2f3..7f369b0a 100644 --- a/std/sys/linux/memory.wave +++ b/std/sys/linux/memory.wave @@ -34,12 +34,20 @@ fun mmap( offset: i64 ) -> ptr { // syscall: mmap (9) - return syscall6(9, addr, length, prot, flags, fd, offset); + return syscall6( + 9, + addr as i64, + length, + prot as i64, + flags as i64, + fd, + offset + ) as ptr; } fun munmap(addr: ptr, length: i64) -> i64 { // syscall: munmap (11) - return syscall2(11, addr, length); + return syscall2(11, addr as i64, length); } @@ -49,5 +57,5 @@ fun munmap(addr: ptr, length: i64) -> i64 { fun brk(addr: ptr) -> ptr { // syscall: brk (12) - return syscall1(12, addr); + return syscall1(12, addr as i64) as ptr; } diff --git a/std/sys/linux/process.wave b/std/sys/linux/process.wave index 15a70020..b194848d 100644 --- a/std/sys/linux/process.wave +++ b/std/sys/linux/process.wave @@ -27,7 +27,7 @@ import("std::sys::linux::syscall"); fun exit(code: i32) -> ! { // syscall: exit (60) - syscall1(60, code); + syscall1(60, code as i64); while (true) { } } @@ -57,7 +57,7 @@ fun execve( envp: ptr> ) -> i64 { // syscall: execve (59) - return syscall3(59, path, argv, envp); + return syscall3(59, path as i64, argv as i64, envp as i64); } @@ -68,7 +68,7 @@ fun execve( fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { // syscall: wait4 (61) // waitpid is implemented via wait4 - return syscall4(61, pid, status, options, 0); + return syscall4(61, pid, status as i64, options as i64, 0); } @@ -78,5 +78,5 @@ fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { fun kill(pid: i64, sig: i32) -> i64 { // syscall: kill (62) - return syscall2(62, pid, sig); + return syscall2(62, pid, sig as i64); } diff --git a/std/sys/linux/socket.wave b/std/sys/linux/socket.wave index 98111413..9db57564 100644 --- a/std/sys/linux/socket.wave +++ b/std/sys/linux/socket.wave @@ -54,32 +54,32 @@ const SO_REUSEADDR: i32 = 2; fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { // syscall: socket (41) - return syscall3(41, domain, ty, protocol); + return syscall3(41, domain as i64, ty as i64, protocol as i64); } fun bind(fd: i64, addr: ptr, len: i32) -> i64 { // syscall: bind (49) - return syscall3(49, fd, addr, len); + return syscall3(49, fd, addr as i64, len as i64); } fun listen(fd: i64, backlog: i32) -> i64 { // syscall: listen (50) - return syscall2(50, fd, backlog); + return syscall2(50, fd, backlog as i64); } fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { // syscall: accept (43) - return syscall3(43, fd, addr, len); + return syscall3(43, fd, addr as i64, len as i64); } fun connect(fd: i64, addr: ptr, len: i32) -> i64 { // syscall: connect (42) - return syscall3(42, fd, addr, len); + return syscall3(42, fd, addr as i64, len as i64); } fun shutdown(fd: i64, how: i32) -> i64 { // syscall: shutdown (48) - return syscall2(48, fd, how); + return syscall2(48, fd, how as i64); } fun setsockopt( @@ -90,7 +90,14 @@ fun setsockopt( optlen: i32 ) -> i64 { // syscall: setsockopt (54) - return syscall5(54, fd, level, optname, optval, optlen); + return syscall5( + 54, + fd, + level as i64, + optname as i64, + optval as i64, + optlen as i64 + ); } @@ -99,11 +106,11 @@ fun setsockopt( // ----------------------- fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { - return sendto(fd, buf, len, flags, 0, 0); + return sendto(fd, buf, len, flags, null, 0); } fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { - return recvfrom(fd, buf, len, flags, 0, 0); + return recvfrom(fd, buf, len, flags, null, null); } fun sendto( @@ -115,7 +122,15 @@ fun sendto( addrlen: i32 ) -> i64 { // syscall: sendto (44) - return syscall6(44, fd, buf, len, flags, addr, addrlen); + return syscall6( + 44, + fd, + buf as i64, + len, + flags as i64, + addr as i64, + addrlen as i64 + ); } fun recvfrom( @@ -127,5 +142,13 @@ fun recvfrom( addrlen: ptr ) -> i64 { // syscall: recvfrom (45) - return syscall6(45, fd, buf, len, flags, addr, addrlen); + return syscall6( + 45, + fd, + buf as i64, + len, + flags as i64, + addr as i64, + addrlen as i64 + ); } diff --git a/std/sys/linux/time.wave b/std/sys/linux/time.wave index 3f54a2ad..62c40a4b 100644 --- a/std/sys/linux/time.wave +++ b/std/sys/linux/time.wave @@ -37,7 +37,7 @@ struct TimeSpec { fun nanosleep(req: ptr, rem: ptr) -> i64 { // syscall: nanosleep (35) - return syscall2(35, req, rem); + return syscall2(35, req as i64, rem as i64); } @@ -47,5 +47,5 @@ fun nanosleep(req: ptr, rem: ptr) -> i64 { fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { // syscall: clock_gettime (228) - return syscall2(228, clock_id, tp); + return syscall2(228, clock_id as i64, tp as i64); } diff --git a/test/test87.wave b/test/test87.wave new file mode 100644 index 00000000..083d3cf5 --- /dev/null +++ b/test/test87.wave @@ -0,0 +1,21 @@ +fun main() -> i32 { + var addr: i64 = 0xb8000; + var vga: ptr = addr as ptr; + var back: i64 = vga as i64; + + if (back != addr) { + return 1; + } + + var narrowed: i8 = 300 as i8; + if (narrowed != 44) { + return 2; + } + + var p0: ptr = 0 as ptr; + if ((p0 as i64) != 0) { + return 3; + } + + return 0; +} diff --git a/test/test88.wave b/test/test88.wave new file mode 100644 index 00000000..345d1484 --- /dev/null +++ b/test/test88.wave @@ -0,0 +1,21 @@ +static COUNTER: i32 = 1; +static VGA_BUFFER: ptr = 0xb8000 as ptr; +const BASE: i32 = 41; + +fun main() -> i32 { + COUNTER = COUNTER + 1; + if (COUNTER != 2) { + return 1; + } + + var addr: i64 = VGA_BUFFER as i64; + if (addr != 0xb8000) { + return 2; + } + + if (BASE != 41) { + return 3; + } + + return 0; +} diff --git a/test/test89.wave b/test/test89.wave new file mode 100644 index 00000000..482203b7 --- /dev/null +++ b/test/test89.wave @@ -0,0 +1,25 @@ +fun main() -> i32 { + var base: ptr = 0x1000 as ptr; + + var p_add: ptr = base + 3; + if ((p_add as i64) != (0x1000 + 12)) { + return 1; + } + + var p_add_rev: ptr = 2 + base; + if ((p_add_rev as i64) != (0x1000 + 8)) { + return 2; + } + + var p_sub: ptr = base - 1; + if ((p_sub as i64) != (0x1000 - 4)) { + return 3; + } + + var diff: i64 = p_add - base; + if (diff != 12) { + return 4; + } + + return 0; +}