From dcdcc06b790a9282ff0ee1e0e9382330e3943570 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Mon, 2 Mar 2026 21:39:20 +0900 Subject: [PATCH] feat: implement static globals, type casts, pointer arithmetic, and enhanced diagnostics This commit introduces several core language features, including global static variables, explicit type casting, and pointer arithmetic. It also significantly upgrades the compiler's resilience by introducing a panic-guarded diagnostic system and cross-platform ABI support. Changes: - **Language Features**: - **Static Globals**: Added the `static` keyword for global variables that persist throughout the program. - **Type Casting**: Implemented the `as` operator (e.g., `expr as type`) for explicit type conversions, supported in both the parser and LLVM backend. - **Pointer Arithmetic**: Added support for `ptr + int`, `ptr - int`, and `ptr - ptr` (pointer difference) using LLVM `gep` and `sub` instructions. - **Match Statement**: Finalized the `match` expression syntax and verification, ensuring no duplicate patterns in arms. - **Compiler Infrastructure**: - **Panic-Guarded Diagnostics**: Introduced a robust runner that captures backend panics and uses "source-span inference" to map low-level LLVM errors back to the original Wave source code. - **Cross-ABI Support**: Overhauled `abi_c.rs` to support target-specific calling conventions for both **Linux x86_64 (SysV)** and **macOS arm64 (Darwin)**, handling aggregate splitting and SRet rules. - **Structured Errors**: Refactored the parser to return `ParseError` objects containing detailed diagnostics (expected/found tokens, help messages, and context). - **Standard Library Updates**: - Updated the Linux syscall layer (`fs`, `memory`, `socket`, `process`) with explicit `as i64` casts to comply with stricter type-checking rules for register arguments. - Improved memory allocation safety with `null` checks and syscall error handling. - **Documentation**: - Expanded `README.md` with build instructions, target support status, and a comprehensive CLI usage guide. These updates bridge the gap between low-level system access and high-level safety, making Wave more suitable for systems programming on multiple architectures. Signed-off-by: LunaStev --- README.md | 43 ++ front/lexer/src/ident.rs | 16 +- front/lexer/src/token.rs | 2 + front/parser/src/ast.rs | 16 +- front/parser/src/expr/binary.rs | 65 +++- front/parser/src/import.rs | 4 +- front/parser/src/parser/control.rs | 8 +- front/parser/src/parser/decl.rs | 205 +++++++--- front/parser/src/parser/functions.rs | 8 + front/parser/src/parser/mod.rs | 2 +- front/parser/src/parser/parse.rs | 24 +- front/parser/src/parser/stmt.rs | 10 +- front/parser/src/parser/types.rs | 25 +- front/parser/src/verification.rs | 19 +- llvm/src/codegen/abi_c.rs | 260 +++++++++++-- llvm/src/codegen/consts.rs | 100 ++++- llvm/src/codegen/ir.rs | 173 +++++++-- llvm/src/codegen/mod.rs | 9 +- llvm/src/codegen/plan.rs | 209 ++++++---- llvm/src/codegen/target.rs | 81 ++++ llvm/src/expression/rvalue/asm.rs | 31 +- llvm/src/expression/rvalue/binary.rs | 149 ++++++- llvm/src/expression/rvalue/calls.rs | 519 +++++++++++++++---------- llvm/src/expression/rvalue/cast.rs | 49 +++ llvm/src/expression/rvalue/dispatch.rs | 42 +- llvm/src/expression/rvalue/mod.rs | 21 +- llvm/src/statement/asm.rs | 229 ++++++++--- llvm/src/statement/assign.rs | 2 +- src/runner.rs | 61 +-- std/buffer/alloc.wave | 10 + std/mem/alloc.wave | 17 + std/net/tcp.wave | 2 +- std/sys/linux/fs.wave | 18 +- std/sys/linux/memory.wave | 14 +- std/sys/linux/process.wave | 8 +- std/sys/linux/socket.wave | 45 ++- std/sys/linux/time.wave | 4 +- test/test87.wave | 21 + test/test88.wave | 21 + test/test89.wave | 25 ++ 40 files changed, 1942 insertions(+), 625 deletions(-) create mode 100644 llvm/src/codegen/target.rs create mode 100644 llvm/src/expression/rvalue/cast.rs create mode 100644 test/test87.wave create mode 100644 test/test88.wave create mode 100644 test/test89.wave 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; +}