From 96b9261ecc92099850e7b5b20c85aca8b9f84deb Mon Sep 17 00:00:00 2001 From: LunaStev Date: Mon, 9 Feb 2026 15:33:13 +0900 Subject: [PATCH] feat: implement enums, type aliases, and advanced constant evaluation This commit introduces support for enums and type aliases, along with a significantly more powerful constant evaluation engine. It also improves ABI-related type coercion and provides better frontend validation. Changes: - **Enums & Type Aliases**: - Introduced `enum Name -> Repr { Variant, ... }` syntax. Enum variants are automatically treated as global constants. - Added `type Alias = Target` syntax for type aliasing. - Implemented a pre-codegen **Type Resolution Pass** that flattens aliases and resolves enum types across functions, structs, and variables. - **Advanced Constant Evaluation**: - Overhauled the constant evaluator to support complex expressions, including **struct literals** and **array literals** in constants. - Implemented **iterative (multi-round) resolution** for constants, allowing constants to depend on other constants defined elsewhere in the program. - Added support for `true`, `false`, and `null` keywords in constant contexts. - **Backend & ABI Improvements**: - Refactored aggregate packing (`pack_agg_to_int`) and unpacking to use `build_memcpy` instead of bit-casting, ensuring safer handling of alignment requirements during FFI calls. - Added robust coercion logic between aggregates (structs/arrays) and LLVM **Vector types**, improving support for Homogeneous Floating-point Aggregates (HFAs). - Added automatic creation of the `target/` directory in the backend. - **Parser & Verification**: - Added keywords `type` and `enum` to the lexer and parser. - Enhanced the verification pass to detect usage of undeclared identifiers in expressions. - Updated `validate_program` to register enum variants as global constants. - **Maintenance**: - Bumped version to `0.1.7-pre-beta`. - Added comprehensive test cases (`test81.wave`, etc.) for enums, aliases, and nested constant dependencies. This update significantly increases the language's type system flexibility and enables more complex compile-time computations. Signed-off-by: LunaStev --- Cargo.toml | 2 +- examples/type_enum.wave | 31 ++ front/lexer/src/lexer/ident.rs | 10 + front/lexer/src/token.rs | 2 + front/parser/src/ast.rs | 24 +- front/parser/src/parser/decl.rs | 171 +++++++- front/parser/src/parser/parse.rs | 16 + front/parser/src/verification.rs | 26 +- .../llvm_temporary/expression/rvalue/calls.rs | 157 +++++-- .../src/llvm_temporary/llvm_backend.rs | 11 + .../src/llvm_temporary/llvm_codegen/abi_c.rs | 1 - .../src/llvm_temporary/llvm_codegen/consts.rs | 408 ++++++++++++++++-- .../src/llvm_temporary/llvm_codegen/ir.rs | 326 ++++++++++++-- test/test79.wave | 14 + test/test80.wave | 24 ++ test/test81.wave | 31 ++ 16 files changed, 1159 insertions(+), 95 deletions(-) create mode 100644 examples/type_enum.wave create mode 100644 test/test79.wave create mode 100644 test/test80.wave create mode 100644 test/test81.wave diff --git a/Cargo.toml b/Cargo.toml index 972fa43c..e9b055d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wavec" -version = "0.1.6-pre-beta" +version = "0.1.7-pre-beta" edition = "2021" [lib] diff --git a/examples/type_enum.wave b/examples/type_enum.wave new file mode 100644 index 00000000..48d66ca4 --- /dev/null +++ b/examples/type_enum.wave @@ -0,0 +1,31 @@ +type MyInt = i32; + +enum ShaderUniformType -> MyInt { + A = 0, + B, + C = 10, + D +} + +const X: MyInt = 123; +const Y: MyInt = B; +const Z: ShaderUniformType = D; + +fun f(t: ShaderUniformType) -> MyInt { + return t; +} + +fun g(v: MyInt) -> MyInt { + return v; +} + +fun main() { + println("{}", f(A)); // 0 + println("{}", f(B)); // 1 + println("{}", f(C)); // 10 + println("{}", f(D)); // 11 + + println("{}", g(X)); // 123 + println("{}", g(Y)); // 1 + println("{}", f(Z)); // 11 +} diff --git a/front/lexer/src/lexer/ident.rs b/front/lexer/src/lexer/ident.rs index fa883317..2de24a26 100644 --- a/front/lexer/src/lexer/ident.rs +++ b/front/lexer/src/lexer/ident.rs @@ -40,6 +40,16 @@ impl<'a> Lexer<'a> { lexeme: "extern".to_string(), line: self.line, }, + "type" => Token { + token_type: TokenType::Type, + lexeme: "type".to_string(), + line: self.line, + }, + "enum" => Token { + token_type: TokenType::Enum, + lexeme: "enum".to_string(), + line: self.line, + }, "var" => Token { token_type: TokenType::Var, lexeme: "var".to_string(), diff --git a/front/lexer/src/token.rs b/front/lexer/src/token.rs index cd13f74c..85dfd9dd 100644 --- a/front/lexer/src/token.rs +++ b/front/lexer/src/token.rs @@ -81,6 +81,8 @@ impl fmt::Display for UnsignedIntegerType { pub enum TokenType { Fun, Extern, + Type, + Enum, Var, Let, Mut, diff --git a/front/parser/src/ast.rs b/front/parser/src/ast.rs index 9da42f82..27d36347 100644 --- a/front/parser/src/ast.rs +++ b/front/parser/src/ast.rs @@ -43,6 +43,27 @@ pub enum ASTNode { Expression(Expression), Struct(StructNode), ProtoImpl(ProtoImplNode), + TypeAlias(TypeAliasNode), + Enum(EnumNode), +} + +#[derive(Debug, Clone)] +pub struct TypeAliasNode { + pub name: String, + pub target: WaveType, +} + +#[derive(Debug, Clone)] +pub struct EnumNode { + pub name: String, + pub repr_type: WaveType, + pub variants: Vec, +} + +#[derive(Debug, Clone)] +pub struct EnumVariantNode { + pub name: String, + pub explicit_value: Option, } #[derive(Debug, Clone)] @@ -259,8 +280,7 @@ pub enum StatementNode { Expression(Expression), } -#[derive(Debug, Clone, PartialEq)] -#[derive(Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum Mutability { Var, Let, diff --git a/front/parser/src/parser/decl.rs b/front/parser/src/parser/decl.rs index 8ec78a13..d7f189db 100644 --- a/front/parser/src/parser/decl.rs +++ b/front/parser/src/parser/decl.rs @@ -13,7 +13,7 @@ use std::iter::Peekable; use std::slice::Iter; use lexer::Token; use lexer::token::TokenType; -use crate::ast::{ASTNode, Expression, ExternFunctionNode, Mutability, 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; @@ -613,4 +613,173 @@ pub fn parse_extern(tokens: &mut Peekable>) -> Option>) -> Option { + // type = ; + let name = match tokens.next() { + Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + other => { + println!("Error: Expected identifier after 'type', found {:?}", other); + return None; + } + }; + + match tokens.next() { + Some(Token { token_type: TokenType::Equal, .. }) => {} + other => { + println!("Error: Expected '=' in type alias, found {:?}", other); + return None; + } + } + + let target = match parse_type_from_stream(tokens) { + Some(t) => t, + None => { + println!("Error: Expected type after '=' in type alias '{}'", name); + return None; + } + }; + + match tokens.next() { + Some(Token { token_type: TokenType::SemiColon, .. }) => {} + other => { + println!("Error: Expected ';' after type alias, found {:?}", other); + return None; + } + } + + Some(ASTNode::TypeAlias(TypeAliasNode { name, target })) +} + +fn token_text(tok: &Token) -> Option { + if !tok.lexeme.is_empty() { + return Some(tok.lexeme.clone()); + } + if let TokenType::Identifier(s) = &tok.token_type { + return Some(s.clone()); + } + None +} + +pub fn parse_enum(tokens: &mut Peekable>) -> Option { + // enum -> { (=)? (, ...)* } + let name = match tokens.next() { + Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + other => { + println!("Error: Expected enum name after 'enum', found {:?}", other); + return None; + } + }; + + match tokens.next() { + Some(Token { token_type: TokenType::Arrow, .. }) => {} + other => { + println!("Error: Expected '->' after enum name, found {:?}", other); + return None; + } + } + + let repr_type = match parse_type_from_stream(tokens) { + Some(t) => t, + None => { + println!("Error: Expected repr type after '->' in enum '{}'", name); + return None; + } + }; + + match tokens.next() { + Some(Token { token_type: TokenType::Lbrace, .. }) => {} + other => { + println!("Error: Expected '{{' to start enum body, found {:?}", other); + return None; + } + } + + let mut variants: Vec = Vec::new(); + + loop { + let next_ty = match tokens.peek() { + Some(t) => t.token_type.clone(), + None => { + println!("Error: Unexpected end of file inside enum '{}'", name); + return None; + } + }; + + match next_ty { + TokenType::Rbrace => { + tokens.next(); // consume '}' + break; + } + TokenType::Identifier(_) => { + // variant name + let vname = match tokens.next() { + Some(Token { token_type: TokenType::Identifier(n), .. }) => n.clone(), + _ => unreachable!(), + }; + + // optional '= ' + let mut explicit_value: Option = None; + if matches!(tokens.peek().map(|t| &t.token_type), Some(TokenType::Equal)) { + tokens.next(); // consume '=' + + let val_tok = match tokens.next() { + Some(t) => t, + None => { + println!("Error: Expected integer literal after '=' in enum '{}'", name); + return None; + } + }; + + let raw = match token_text(val_tok) { + Some(s) => s, + None => { + println!("Error: Expected integer literal after '=' in enum '{}', found {:?}", name, val_tok); + return None; + } + }; + + explicit_value = Some(raw); + } + + variants.push(EnumVariantNode { + name: vname, + explicit_value, + }); + + // after variant: ',' or '}' + match tokens.peek().map(|t| t.token_type.clone()) { + Some(TokenType::Comma) => { + tokens.next(); // consume ',' + + continue; + } + Some(TokenType::Rbrace) => { + continue; + } + other => { + println!( + "Error: Expected ',' or '}}' after enum variant in '{}', found {:?}", + name, other + ); + return None; + } + } + } + other => { + println!( + "Error: Expected enum variant name or '}}' in '{}', found {:?}", + name, other + ); + return None; + } + } + } + + Some(ASTNode::Enum(EnumNode { + name, + repr_type, + variants, + })) } \ No newline at end of file diff --git a/front/parser/src/parser/parse.rs b/front/parser/src/parser/parse.rs index cf5014b7..333c2b03 100644 --- a/front/parser/src/parser/parse.rs +++ b/front/parser/src/parser/parse.rs @@ -60,6 +60,22 @@ pub fn parse(tokens: &Vec) -> Option> { return None; } } + TokenType::Type => { + iter.next(); // consume 'type' + if let Some(node) = parse_type_alias(&mut iter) { + nodes.push(node); + } else { + return None; + } + } + TokenType::Enum => { + iter.next(); // consume 'enum' + if let Some(node) = parse_enum(&mut iter) { + nodes.push(node); + } else { + return None; + } + } TokenType::Struct => { iter.next(); if let Some(struct_node) = parse_struct(&mut iter) { diff --git a/front/parser/src/verification.rs b/front/parser/src/verification.rs index c2958206..50a59558 100644 --- a/front/parser/src/verification.rs +++ b/front/parser/src/verification.rs @@ -139,7 +139,13 @@ fn validate_expr( validate_expr(inner, scopes, globals)?; } - Expression::Literal(_) | Expression::Variable(_) => {} + Expression::Literal(_) => {} + + Expression::Variable(name) => { + if lookup_mutability(name, scopes, globals).is_none() { + return Err(format!("use of undeclared identifier `{}`", name)); + } + } _ => {} } @@ -249,11 +255,23 @@ fn validate_node( pub fn validate_program(nodes: &Vec) -> Result<(), String> { let mut globals: HashMap = HashMap::new(); + for n in nodes { - if let ASTNode::Variable(v) = n { - if v.mutability == Mutability::Const { - globals.insert(v.name.clone(), Mutability::Const); + match n { + ASTNode::Variable(v) => { + if v.mutability == Mutability::Const { + globals.insert(v.name.clone(), Mutability::Const); + } } + + // NEW: enum variants are constants + ASTNode::Enum(e) => { + for v in &e.variants { + globals.insert(v.name.clone(), Mutability::Const); + } + } + + _ => {} } } diff --git a/llvm_temporary/src/llvm_temporary/expression/rvalue/calls.rs b/llvm_temporary/src/llvm_temporary/expression/rvalue/calls.rs index 9feddfcb..96da2986 100644 --- a/llvm_temporary/src/llvm_temporary/expression/rvalue/calls.rs +++ b/llvm_temporary/src/llvm_temporary/expression/rvalue/calls.rs @@ -23,17 +23,21 @@ fn pack_agg_to_int<'ctx, 'a>( tag: &str, ) -> BasicValueEnum<'ctx> { let agg_ty = agg.get_type(); - let tmp = env.builder.build_alloca(agg_ty, &format!("{}_agg_tmp", tag)).unwrap(); - env.builder.build_store(tmp, agg).unwrap(); - let int_ptr_ty = dst.ptr_type(tmp.get_type().get_address_space()); - let casted = env.builder - .build_bit_cast(tmp.as_basic_value_enum(), int_ptr_ty.as_basic_type_enum(), &format!("{}_agg_i_ptr", tag)) - .unwrap() - .into_pointer_value(); + 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 bytes = env.target_data.get_store_size(&agg_ty) as u64; + let size_v = env.context.i64_type().const_int(bytes, false); env.builder - .build_load(casted, &format!("{}_agg_i", tag)) + .build_memcpy(int_tmp, 1, agg_tmp, 1, size_v) + .unwrap(); + + env.builder + .build_load(int_tmp, &format!("{}_agg_i", tag)) .unwrap() .as_basic_value_enum() } @@ -44,22 +48,25 @@ fn unpack_int_to_agg<'ctx, 'a>( dst_agg_ty: BasicTypeEnum<'ctx>, tag: &str, ) -> BasicValueEnum<'ctx> { - let tmp = env.builder.build_alloca(dst_agg_ty, &format!("{}_i2agg_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(); - let int_ptr_ty = iv.get_type().ptr_type(tmp.get_type().get_address_space()); - let casted = env.builder - .build_bit_cast(tmp.as_basic_value_enum(), int_ptr_ty.as_basic_type_enum(), &format!("{}_i_ptr", tag)) - .unwrap() - .into_pointer_value(); + env.builder.build_store(int_tmp, iv).unwrap(); + + let bytes = env.target_data.get_store_size(&dst_agg_ty) as u64; + let size_v = env.context.i64_type().const_int(bytes, false); - env.builder.build_store(casted, iv).unwrap(); + env.builder + .build_memcpy(agg_tmp, 1, int_tmp, 1, size_v) + .unwrap(); env.builder - .build_load(tmp, &format!("{}_i2agg_load", tag)) + .build_load(agg_tmp, &format!("{}_i2agg_load", tag)) .unwrap() .as_basic_value_enum() } + fn normalize_struct_name(raw: &str) -> &str { raw.strip_prefix("struct.").unwrap_or(raw).trim_start_matches('%') } @@ -510,18 +517,114 @@ fn coerce_to_expected<'ctx, 'a>( .as_basic_value_enum() } - // (Struct|Array) -> Int : store-size가 정확히 맞으면 bit-pack - (got_ty @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_)), BasicTypeEnum::IntType(dst)) => { - let sz = env.target_data.get_store_size(&got_ty) 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)); + // 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)); + } + + 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 + ); + } + + let tmp = env.builder + .build_alloca(got_agg, &format!("arg{}_agg_tmp", arg_index)) + .unwrap(); + env.builder.build_store(tmp, val).unwrap(); + + let vptr_ty = vt.ptr_type(tmp.get_type().get_address_space()); + let vptr = env.builder + .build_bit_cast( + tmp.as_basic_value_enum(), + vptr_ty.as_basic_type_enum(), + &format!("arg{}_agg2v_ptr", arg_index), + ) + .unwrap() + .into_pointer_value(); + + env.builder + .build_load(vptr, &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 + ); + } + + let tmp = env.builder + .build_alloca(dst_agg, &format!("arg{}_v2agg_tmp", arg_index)) + .unwrap(); + + let vptr_ty = vt.ptr_type(tmp.get_type().get_address_space()); + let vptr = env.builder + .build_bit_cast( + tmp.as_basic_value_enum(), + vptr_ty.as_basic_type_enum(), + &format!("arg{}_v_ptr", arg_index), + ) + .unwrap() + .into_pointer_value(); + + env.builder.build_store(vptr, val).unwrap(); + + env.builder + .build_load(tmp, &format!("arg{}_v2agg", arg_index)) + .unwrap() + .as_basic_value_enum() + } + + // 4.7) ptr-to-agg -> vector (bitcast ptr and load vector) + (BasicTypeEnum::PointerType(p), BasicTypeEnum::VectorType(vt)) + if p.get_element_type().is_struct_type() || p.get_element_type().is_array_type() => + { + let pv = val.into_pointer_value(); + + let vptr_ty = vt.ptr_type(pv.get_type().get_address_space()); + let casted = env.builder + .build_bit_cast( + pv.as_basic_value_enum(), + vptr_ty.as_basic_type_enum(), + &format!("arg{}_pagg2v_ptr", arg_index), + ) + .unwrap() + .into_pointer_value(); + + env.builder + .build_load(casted, &format!("arg{}_pagg2v", arg_index)) + .unwrap() + .as_basic_value_enum() } - panic!( - "Cannot pack aggregate to int: agg bits {} != dst bits {} (arg {} of {})", - bits, dst.get_bit_width(), arg_index, name - ); - } (BasicTypeEnum::IntType(src), dst_agg @ (BasicTypeEnum::StructType(_) | BasicTypeEnum::ArrayType(_))) => { let sz = env.target_data.get_store_size(&dst_agg) as u64; diff --git a/llvm_temporary/src/llvm_temporary/llvm_backend.rs b/llvm_temporary/src/llvm_temporary/llvm_backend.rs index 0478ec54..cceb091e 100644 --- a/llvm_temporary/src/llvm_temporary/llvm_backend.rs +++ b/llvm_temporary/src/llvm_temporary/llvm_backend.rs @@ -13,7 +13,16 @@ use std::fs; use std::path::Path; use std::process::Command; +fn ensure_target_dir() { + let target_dir = Path::new("target"); + if !target_dir.exists() { + fs::create_dir_all(target_dir) + .expect("Unable to create target directory"); + } +} + pub fn compile_ir_to_object(ir: &str, file_stem: &str, opt_flag: &str) -> String { + ensure_target_dir(); let object_path = format!("target/{}.o", file_stem); let mut cmd = Command::new("clang"); @@ -76,6 +85,8 @@ pub fn link_objects(objects: &[String], output: &str, libs: &[String], lib_paths } pub fn compile_ir_to_img_code(ir: &str, file_stem: &str) -> String { + ensure_target_dir(); + let target_dir = Path::new("target"); if !target_dir.exists() { fs::create_dir_all(target_dir).expect("Unable to create target directory"); diff --git a/llvm_temporary/src/llvm_temporary/llvm_codegen/abi_c.rs b/llvm_temporary/src/llvm_temporary/llvm_codegen/abi_c.rs index fd9b232b..c08a32a9 100644 --- a/llvm_temporary/src/llvm_temporary/llvm_codegen/abi_c.rs +++ b/llvm_temporary/src/llvm_temporary/llvm_codegen/abi_c.rs @@ -228,7 +228,6 @@ fn classify_ret<'ctx>( 2 => RetLowering::Direct(f.vec_type(2).as_basic_type_enum()), 4 => RetLowering::Direct(f.vec_type(4).as_basic_type_enum()), _ => { - // 3-float ret 같은 건 일단 sret로 안전하게 let align = td.get_abi_alignment(&t) as u32; RetLowering::SRet { ty: t.as_any_type_enum(), align } } diff --git a/llvm_temporary/src/llvm_temporary/llvm_codegen/consts.rs b/llvm_temporary/src/llvm_temporary/llvm_codegen/consts.rs index 977aecc1..4f2d317e 100644 --- a/llvm_temporary/src/llvm_temporary/llvm_codegen/consts.rs +++ b/llvm_temporary/src/llvm_temporary/llvm_codegen/consts.rs @@ -10,66 +10,406 @@ // SPDX-License-Identifier: MPL-2.0 use inkwell::context::Context; -use inkwell::types::{BasicTypeEnum, StringRadix}; +use inkwell::types::{BasicType, BasicTypeEnum, StringRadix, StructType}; use inkwell::values::{BasicValue, BasicValueEnum}; use parser::ast::{Expression, Literal, WaveType}; use std::collections::HashMap; +use std::fmt; use super::types::{wave_type_to_llvm_type, TypeFlavor}; -fn parse_signed_decimal<'a>(s: &'a str) -> (bool, &'a str) { - if let Some(rest) = s.strip_prefix('-') { - (true, rest) - } else { - (false, s) +#[derive(Debug, Clone)] +pub enum ConstEvalError { + UnknownIdentifier(String), + TypeMismatch { + expected: String, + got: String, + note: String, + }, + InvalidLiteral(String), + Unsupported(String), +} + +impl fmt::Display for ConstEvalError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ConstEvalError::UnknownIdentifier(n) => write!(f, "unknown const identifier `{}`", n), + ConstEvalError::TypeMismatch { expected, got, note } => { + write!(f, "type mismatch (expected {}, got {}): {}", expected, got, note) + } + ConstEvalError::InvalidLiteral(s) => write!(f, "invalid literal: {}", s), + ConstEvalError::Unsupported(s) => write!(f, "unsupported const expression: {}", s), + } + } +} + +fn type_name<'ctx>(t: BasicTypeEnum<'ctx>) -> String { + format!("{:?}", t) +} + +fn value_type_name<'ctx>(v: BasicValueEnum<'ctx>) -> String { + format!("{:?}", v.get_type()) +} + +fn parse_signed_and_radix(s: &str) -> (bool, StringRadix, String) { + let mut t = s.trim().replace('_', ""); + if t.is_empty() { + return (false, StringRadix::Decimal, "".to_string()); } + + let mut neg = false; + if let Some(rest) = t.strip_prefix('-') { + neg = true; + t = rest.to_string(); + } else if let Some(rest) = t.strip_prefix('+') { + t = rest.to_string(); + } + + let (radix, digits) = if let Some(rest) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) { + (StringRadix::Hexadecimal, rest) + } else if let Some(rest) = t.strip_prefix("0b").or_else(|| t.strip_prefix("0B")) { + (StringRadix::Binary, rest) + } else if let Some(rest) = t.strip_prefix("0o").or_else(|| t.strip_prefix("0O")) { + (StringRadix::Octal, rest) + } else { + (StringRadix::Decimal, t.as_str()) + }; + + (neg, radix, digits.to_string()) } -fn is_zero_decimal(s: &str) -> bool { - let s = s.trim(); - let s = s.strip_prefix('+').unwrap_or(s); +fn is_zero_like(s: &str) -> bool { + let s = s.trim().replace('_', ""); + let s = s.strip_prefix('+').unwrap_or(&s); let s = s.strip_prefix('-').unwrap_or(s); + + let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); + let s = s.strip_prefix("0b").or_else(|| s.strip_prefix("0B")).unwrap_or(s); + let s = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")).unwrap_or(s); + !s.is_empty() && s.chars().all(|c| c == '0') } -pub(super) fn create_llvm_const_value<'ctx>( +fn strip_struct_prefix(raw: &str) -> &str { + raw.strip_prefix("struct.").unwrap_or(raw) +} + +fn const_keyword_value<'ctx>( context: &'ctx Context, - ty: &WaveType, + expected: BasicTypeEnum<'ctx>, + name: &str, +) -> Option, ConstEvalError>> { + match name { + "true" => match expected { + BasicTypeEnum::IntType(it) => Some(Ok(it.const_int(1, false).as_basic_value_enum())), + _ => Some(Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "bool(true)".to_string(), + note: "true can only be used where an integer-like(bool ABI) is expected".to_string(), + })), + }, + "false" => match expected { + BasicTypeEnum::IntType(it) => Some(Ok(it.const_int(0, false).as_basic_value_enum())), + _ => Some(Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "bool(false)".to_string(), + note: "false can only be used where an integer-like(bool ABI) is expected".to_string(), + })), + }, + "null" => match expected { + BasicTypeEnum::PointerType(pt) => Some(Ok(pt.const_null().as_basic_value_enum())), + _ => Some(Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "null".to_string(), + note: "null can only be used where a pointer is expected".to_string(), + })), + }, + _ => None, + } +} + +fn const_from_expected<'ctx>( + context: &'ctx Context, + expected: BasicTypeEnum<'ctx>, expr: &Expression, -) -> BasicValueEnum<'ctx> { - let struct_types = HashMap::new(); - let llvm_type = wave_type_to_llvm_type(context, ty, &struct_types, TypeFlavor::AbiC); + struct_types: &HashMap>, + struct_field_indices: &HashMap>, + const_env: &HashMap>, +) -> Result, ConstEvalError> { + match expr { + Expression::Grouped(inner) => { + return const_from_expected(context, expected, inner, struct_types, struct_field_indices, const_env); + } - match (expr, llvm_type) { - // new: int literal is string-based - (Expression::Literal(Literal::Int(s)), BasicTypeEnum::IntType(int_ty)) => { - let (neg, digits) = parse_signed_decimal(s.as_str()); + Expression::Variable(name) => { + if let Some(r) = const_keyword_value(context, expected, name) { + return r; + } - let mut iv = int_ty - .const_int_from_string(digits, StringRadix::Decimal) - .unwrap_or_else(|| panic!("invalid int literal: {}", s)); + let v = match const_env.get(name) { + Some(v) => *v, + None => return Err(ConstEvalError::UnknownIdentifier(name.clone())), + }; - if neg { - iv = iv.const_neg(); + if v.get_type() != expected { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: value_type_name(v), + note: format!("identifier `{}` resolved to a const of different LLVM type", name), + }); } - iv.as_basic_value_enum() + Ok(v) } - (Expression::Literal(Literal::Float(f)), BasicTypeEnum::FloatType(float_ty)) => { - float_ty.const_float(*f).as_basic_value_enum() - } + // --- ints --- + Expression::Literal(Literal::Int(s)) => match expected { + BasicTypeEnum::IntType(int_ty) => { + let (neg, radix, digits) = parse_signed_and_radix(s); + let mut iv = int_ty + .const_int_from_string(&digits, radix) + .ok_or_else(|| ConstEvalError::InvalidLiteral(s.clone()))?; + + if neg { + iv = iv.const_neg(); + } + Ok(iv.as_basic_value_enum()) + } + BasicTypeEnum::PointerType(ptr_ty) => { + if is_zero_like(s) { + Ok(ptr_ty.const_null().as_basic_value_enum()) + } else { + Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: format!("int({})", s), + note: "only 0 can be used as a const null pointer literal".to_string(), + }) + } + } + _ => Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: format!("int({})", s), + note: "const int literal not compatible with expected type".to_string(), + }), + }, + + // --- floats --- + Expression::Literal(Literal::Float(fv)) => match expected { + BasicTypeEnum::FloatType(float_ty) => Ok(float_ty.const_float(*fv).as_basic_value_enum()), + _ => Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "float".to_string(), + note: "const float literal not compatible with expected type".to_string(), + }), + }, + + // --- struct literal --- + Expression::StructLiteral { name, fields } => { + let st = match expected { + BasicTypeEnum::StructType(st) => st, + _ => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "struct-literal".to_string(), + note: format!("StructLiteral '{}' used where non-struct expected", name), + }) + } + }; + + let field_count = st.count_fields() as usize; + + let struct_name = if !name.is_empty() { + name.as_str() + } else { + st.get_name() + .and_then(|c| c.to_str().ok()) + .map(strip_struct_prefix) + .unwrap_or("") + }; + + let positional = fields.iter().all(|(n, _)| n.is_empty()); + let mut slots: Vec>> = vec![None; field_count]; + + if positional { + if fields.len() != field_count { + return Err(ConstEvalError::Unsupported(format!( + "StructLiteral '{}' positional init expects {} fields, got {}", + struct_name, field_count, fields.len() + ))); + } - // allow const null pointer only via 0 - (Expression::Literal(Literal::Int(s)), BasicTypeEnum::PointerType(ptr_ty)) => { - if is_zero_decimal(s) { - ptr_ty.const_null().as_basic_value_enum() + for (i, (_, vexpr)) in fields.iter().enumerate() { + let fty = st + .get_field_type_at_index(i as u32) + .ok_or_else(|| ConstEvalError::Unsupported(format!( + "Struct '{}' has no field index {}", + struct_name, i + )))?; + + let cv = const_from_expected(context, fty, vexpr, struct_types, struct_field_indices, const_env)?; + slots[i] = Some(cv); + } } else { - panic!("Only 0 can be used as a const null pointer literal"); + let idx_map = struct_field_indices.get(struct_name).ok_or_else(|| { + ConstEvalError::Unsupported(format!("Struct '{}' field map not found", struct_name)) + })?; + + for (fname, vexpr) in fields { + let idx = *idx_map.get(fname).ok_or_else(|| { + ConstEvalError::Unsupported(format!("Field '{}' not found in struct '{}'", fname, struct_name)) + })? as usize; + + let fty = st.get_field_type_at_index(idx as u32).ok_or_else(|| { + ConstEvalError::Unsupported(format!("Struct '{}' has no field index {}", struct_name, idx)) + })?; + + let cv = const_from_expected(context, fty, vexpr, struct_types, struct_field_indices, const_env)?; + slots[idx] = Some(cv); + } } + + let mut ordered: Vec> = Vec::with_capacity(field_count); + for i in 0..field_count { + let fty = st.get_field_type_at_index(i as u32).unwrap(); + ordered.push(slots[i].unwrap_or_else(|| fty.const_zero())); + } + + Ok(st.const_named_struct(&ordered).as_basic_value_enum()) } - _ => panic!("Constant expression must be a literal of a compatible type."), + // --- array literal --- + Expression::ArrayLiteral(elems) => match expected { + BasicTypeEnum::ArrayType(at) => { + let len = at.len() as usize; + if elems.len() != len { + return Err(ConstEvalError::Unsupported(format!( + "Array literal length mismatch: expected {}, got {}", + len, elems.len() + ))); + } + + let elem_ty = at.get_element_type(); + + let elem_vals: Vec> = elems + .iter() + .map(|e| const_from_expected(context, elem_ty, e, struct_types, struct_field_indices, const_env)) + .collect::>()?; + + match elem_ty { + BasicTypeEnum::IntType(int_ty) => { + let mut vs = Vec::with_capacity(len); + for v in elem_vals { + match v { + BasicValueEnum::IntValue(iv) => vs.push(iv), + other => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(elem_ty), + got: value_type_name(other), + note: "array element expected int".to_string(), + }) + } + } + } + Ok(int_ty.const_array(&vs).as_basic_value_enum()) + } + + BasicTypeEnum::FloatType(float_ty) => { + let mut vs = Vec::with_capacity(len); + for v in elem_vals { + match v { + BasicValueEnum::FloatValue(fv) => vs.push(fv), + other => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(elem_ty), + got: value_type_name(other), + note: "array element expected float".to_string(), + }) + } + } + } + Ok(float_ty.const_array(&vs).as_basic_value_enum()) + } + + BasicTypeEnum::PointerType(ptr_ty) => { + let mut vs = Vec::with_capacity(len); + for v in elem_vals { + match v { + BasicValueEnum::PointerValue(pv) => vs.push(pv), + other => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(elem_ty), + got: value_type_name(other), + note: "array element expected pointer".to_string(), + }) + } + } + } + Ok(ptr_ty.const_array(&vs).as_basic_value_enum()) + } + + BasicTypeEnum::StructType(st_ty) => { + let mut vs = Vec::with_capacity(len); + for v in elem_vals { + match v { + BasicValueEnum::StructValue(sv) => vs.push(sv), + other => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(elem_ty), + got: value_type_name(other), + note: "array element expected struct".to_string(), + }) + } + } + } + Ok(st_ty.const_array(&vs).as_basic_value_enum()) + } + + BasicTypeEnum::ArrayType(inner_at) => { + let mut vs = Vec::with_capacity(len); + for v in elem_vals { + match v { + BasicValueEnum::ArrayValue(av) => vs.push(av), + other => { + return Err(ConstEvalError::TypeMismatch { + expected: type_name(elem_ty), + got: value_type_name(other), + note: "array element expected array".to_string(), + }) + } + } + } + Ok(inner_at.const_array(&vs).as_basic_value_enum()) + } + + other => Err(ConstEvalError::Unsupported(format!( + "Unsupported const array element type: {:?}", + other + ))), + } + } + _ => Err(ConstEvalError::TypeMismatch { + expected: type_name(expected), + got: "array-literal".to_string(), + note: "Array literal used where non-array expected".to_string(), + }), + }, + + _ => Err(ConstEvalError::Unsupported(format!( + "Constant expression must be a literal/struct/array/identifier, got {:?}", + expr + ))), } -} \ No newline at end of file +} + +pub(super) fn create_llvm_const_value<'ctx>( + context: &'ctx Context, + ty: &WaveType, + expr: &Expression, + struct_types: &HashMap>, + struct_field_indices: &HashMap>, + const_env: &HashMap>, +) -> Result, ConstEvalError> { + let expected = wave_type_to_llvm_type(context, ty, struct_types, TypeFlavor::AbiC); + const_from_expected(context, expected, expr, struct_types, struct_field_indices, const_env) +} diff --git a/llvm_temporary/src/llvm_temporary/llvm_codegen/ir.rs b/llvm_temporary/src/llvm_temporary/llvm_codegen/ir.rs index 2ed914b5..badfb464 100644 --- a/llvm_temporary/src/llvm_temporary/llvm_codegen/ir.rs +++ b/llvm_temporary/src/llvm_temporary/llvm_codegen/ir.rs @@ -15,13 +15,13 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum}; use inkwell::values::{BasicValueEnum, FunctionValue}; use inkwell::OptimizationLevel; -use parser::ast::{ASTNode, ExternFunctionNode, FunctionNode, Mutability, VariableNode, WaveType}; -use std::collections::HashMap; +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::llvm_temporary::statement::generate_statement_ir; -use super::consts::create_llvm_const_value; +use super::consts::{create_llvm_const_value, ConstEvalError}; use super::types::{wave_type_to_llvm_type, TypeFlavor, VariableInfo}; use crate::llvm_temporary::llvm_codegen::abi_c::{ @@ -33,6 +33,12 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode]) -> String { let module: &'static _ = Box::leak(Box::new(context.create_module("main"))); let builder: &'static _ = Box::leak(Box::new(context.create_builder())); + let named_types = collect_named_types(ast_nodes); + let ast_nodes: Vec = ast_nodes + .iter() + .map(|n| resolve_ast_node(n, &named_types)) + .collect(); + Target::initialize_native(&InitializationConfig::default()).unwrap(); let triple = TargetMachine::get_default_triple(); let target = Target::from_triple(&triple).unwrap(); @@ -61,28 +67,12 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode]) -> String { let pass_manager: PassManager = PassManager::create(()); pass_manager_builder.populate_module_pass_manager(&pass_manager); - let mut struct_field_indices: HashMap> = HashMap::new(); - let mut global_consts: HashMap = HashMap::new(); - - for ast in ast_nodes { - if let ASTNode::Variable(VariableNode { - name, - type_name, - initial_value, - mutability, - }) = ast - { - if *mutability == Mutability::Const { - let initial_value = initial_value.as_ref().expect("Constant must be initialized."); - let const_val = create_llvm_const_value(context, type_name, initial_value); - global_consts.insert(name.clone(), const_val); - } - } - } + let mut global_consts: HashMap> = HashMap::new(); let mut struct_types: HashMap = HashMap::new(); - - for ast in ast_nodes { + let mut struct_field_indices: HashMap> = HashMap::new(); + // (1) struct opaque + field index map + for ast in &ast_nodes { if let ASTNode::Struct(struct_node) = ast { let st = context.opaque_struct_type(&struct_node.name); struct_types.insert(struct_node.name.clone(), st); @@ -95,7 +85,7 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode]) -> String { } } - for ast in ast_nodes { + for ast in &ast_nodes { if let ASTNode::Struct(struct_node) = ast { let st = *struct_types .get(&struct_node.name) @@ -111,8 +101,69 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode]) -> String { } } + for ast in &ast_nodes { + if let ASTNode::Enum(e) = ast { + add_enum_consts_to_globals(context, e, &mut global_consts); + } + } + + let mut pending: Vec<&VariableNode> = ast_nodes + .iter() + .filter_map(|ast| match ast { + ASTNode::Variable(v) if v.mutability == Mutability::Const => Some(v), + _ => None, + }) + .collect(); + + let mut round = 0; + while !pending.is_empty() { + round += 1; + + let mut progressed = false; + 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) + }); + + match create_llvm_const_value( + context, + &v.type_name, + init, + &struct_types, + &struct_field_indices, + &global_consts, + ) { + Ok(val) => { + global_consts.insert(v.name.clone(), val); + progressed = true; + } + Err(ConstEvalError::UnknownIdentifier(_)) => { + next_pending.push(v); + } + Err(e) => { + panic!("const '{}' evaluation failed: {}", v.name, e); + } + } + } + + if next_pending.is_empty() { + break; + } + if !progressed { + let names: Vec = next_pending.iter().map(|v| v.name.clone()).collect(); + panic!( + "unresolved const cycle or missing symbols after {} rounds: {:?}", + round, names + ); + } + + pending = next_pending; + } + let mut proto_functions: Vec<(String, FunctionNode)> = Vec::new(); - for ast in ast_nodes { + for ast in &ast_nodes { if let ASTNode::ProtoImpl(proto_impl) = ast { for method in &proto_impl.methods { let new_name = format!("{}_{}", proto_impl.target, method.name); @@ -233,3 +284,228 @@ pub unsafe fn generate_ir(ast_nodes: &[ASTNode]) -> String { pass_manager.run_on(module); module.print_to_string().to_string() } + +fn parse_int_literal(raw: &str) -> Option { + let mut s = raw.trim().replace('_', ""); + if s.is_empty() { return None; } + + let neg = if let Some(rest) = s.strip_prefix('-') { + s = rest.to_string(); + true + } else if let Some(rest) = s.strip_prefix('+') { + s = rest.to_string(); + false + } else { + false + }; + + 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) + } else if let Some(rest) = s.strip_prefix("0o").or_else(|| s.strip_prefix("0O")) { + (8, rest) + } else { + (10, s.as_str()) + }; + + let v = i128::from_str_radix(digits, radix).ok()?; + Some(if neg { -v } else { v }) +} + +fn repr_bits_signed(ty: &WaveType) -> Option<(u32, bool)> { + match ty { + WaveType::Int(b) => Some((*b as u32, true)), + WaveType::Uint(b) => Some((*b as u32, false)), + WaveType::Bool => Some((1, false)), + WaveType::Byte => Some((8, false)), + WaveType::Char => Some((8, false)), + _ => None, + } +} + +fn fits_in_int(v: i128, bits: u32, signed: bool) -> bool { + if bits == 0 || bits > 64 { + return false; + } + + if signed { + if bits == 64 { + return v >= i64::MIN as i128 && v <= i64::MAX as i128; + } + let min = -(1i128 << (bits - 1)); + let max = (1i128 << (bits - 1)) - 1; + v >= min && v <= max + } else { + if v < 0 { return false; } + if bits == 64 { + return (v as u128) <= u64::MAX as u128; + } + let max = (1u128 << bits) - 1; + (v as u128) <= max + } +} + +fn collect_named_types(nodes: &[ASTNode]) -> HashMap { + let mut m = HashMap::new(); + for n in nodes { + match n { + ASTNode::TypeAlias(TypeAliasNode { name, target }) => { + m.insert(name.clone(), target.clone()); + } + ASTNode::Enum(EnumNode { name, repr_type, .. }) => { + m.insert(name.clone(), repr_type.clone()); + } + _ => {} + } + } + m +} + +fn resolve_wave_type_impl( + ty: &WaveType, + named: &HashMap, + visiting: &mut HashSet, +) -> WaveType { + match ty { + WaveType::Pointer(inner) => { + WaveType::Pointer(Box::new(resolve_wave_type_impl(inner, named, visiting))) + } + WaveType::Array(inner, n) => { + WaveType::Array(Box::new(resolve_wave_type_impl(inner, named, visiting)), *n) + } + WaveType::Struct(name) => { + if let Some(t) = named.get(name) { + if !visiting.insert(name.clone()) { + panic!("Type alias/enum cycle detected at '{}'", name); + } + let out = resolve_wave_type_impl(t, named, visiting); + visiting.remove(name); + out + } else { + WaveType::Struct(name.clone()) + } + } + _ => ty.clone(), + } +} + +fn resolve_wave_type(ty: &WaveType, named: &HashMap) -> WaveType { + let mut visiting = HashSet::new(); + resolve_wave_type_impl(ty, named, &mut visiting) +} + +fn resolve_parameter(p: &ParameterNode, named: &HashMap) -> ParameterNode { + let mut out = p.clone(); + out.param_type = resolve_wave_type(&out.param_type, named); + out +} + +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 +} + +fn resolve_struct(s: &StructNode, named: &HashMap) -> StructNode { + let mut out = s.clone(); + out.fields = out + .fields + .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 +} + +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 +} + +fn resolve_extern(e: &ExternFunctionNode, named: &HashMap) -> ExternFunctionNode { + let mut out = e.clone(); + out.params = out + .params + .iter() + .map(|(n, t)| (n.clone(), resolve_wave_type(t, named))) + .collect(); + out.return_type = resolve_wave_type(&out.return_type, named); + out +} + +fn resolve_variable(v: &VariableNode, named: &HashMap) -> VariableNode { + let mut out = v.clone(); + out.type_name = resolve_wave_type(&out.type_name, named); + out +} + +fn resolve_enum(e: &EnumNode, named: &HashMap) -> EnumNode { + let mut out = e.clone(); + out.repr_type = resolve_wave_type(&out.repr_type, named); + out +} + +fn resolve_ast_node(n: &ASTNode, named: &HashMap) -> ASTNode { + match n { + ASTNode::Enum(e) => ASTNode::Enum(resolve_enum(e, named)), + ASTNode::Function(f) => ASTNode::Function(resolve_function(f, named)), + ASTNode::ExternFunction(e) => ASTNode::ExternFunction(resolve_extern(e, named)), + ASTNode::Struct(s) => ASTNode::Struct(resolve_struct(s, named)), + ASTNode::ProtoImpl(p) => ASTNode::ProtoImpl(resolve_proto(p, named)), + ASTNode::Variable(v) => ASTNode::Variable(resolve_variable(v, named)), + + ASTNode::TypeAlias(_) | ASTNode::Enum(_) => n.clone(), + + _ => n.clone(), + } +} + +fn add_enum_consts_to_globals( + context: &'static Context, + 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)); + + if bits > 64 || bits == 0 { + panic!("enum '{}' repr bit-width unsupported: {}", e.name, bits); + } + + let int_ty = context.custom_width_int_type(bits); + + let mut next: i128 = 0; + + 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) + }); + } + + if !fits_in_int(next, bits, signed) { + panic!( + "enum '{}' variant '{}' value {} does not fit in {}{}", + e.name, + v.name, + next, + if signed { "i" } else { "u" }, + bits + ); + } + + let c = if signed { + int_ty.const_int(next as u64, true) + } else { + int_ty.const_int(next as u64, false) + }; + + global_consts.insert(v.name.clone(), c.into()); + + next += 1; + } +} \ No newline at end of file diff --git a/test/test79.wave b/test/test79.wave new file mode 100644 index 00000000..2cd8f10f --- /dev/null +++ b/test/test79.wave @@ -0,0 +1,14 @@ +enum ShaderUniformType -> i32 { A = 0, B } + +fun f(t: ShaderUniformType) -> i32 { + return t; +} + +fun n() -> i32 { + return f(B); +} + +fun main() { + println("Result: "); + println("{}", n()); +} \ No newline at end of file diff --git a/test/test80.wave b/test/test80.wave new file mode 100644 index 00000000..567f3140 --- /dev/null +++ b/test/test80.wave @@ -0,0 +1,24 @@ +enum ShaderUniformType -> i32 { + FLOAT = 0, + VEC2, + VEC3, + VEC4 +} + +type UniformType = ShaderUniformType; + +fun get_id(t: UniformType) -> i32 { + return t; +} + +fun choose() -> UniformType { + return VEC3; +} + +fun main() { + let a: UniformType = FLOAT; + let b: UniformType = choose(); + + println("a = {}", get_id(a)); // 0 + println("b = {}", get_id(b)); // 2 +} diff --git a/test/test81.wave b/test/test81.wave new file mode 100644 index 00000000..48d66ca4 --- /dev/null +++ b/test/test81.wave @@ -0,0 +1,31 @@ +type MyInt = i32; + +enum ShaderUniformType -> MyInt { + A = 0, + B, + C = 10, + D +} + +const X: MyInt = 123; +const Y: MyInt = B; +const Z: ShaderUniformType = D; + +fun f(t: ShaderUniformType) -> MyInt { + return t; +} + +fun g(v: MyInt) -> MyInt { + return v; +} + +fun main() { + println("{}", f(A)); // 0 + println("{}", f(B)); // 1 + println("{}", f(C)); // 10 + println("{}", f(D)); // 11 + + println("{}", g(X)); // 123 + println("{}", g(Y)); // 1 + println("{}", f(Z)); // 11 +}