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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/hir/src/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::{
ComponentMemberDeclaration, ComponentProperty, HirDeclaration, HirDeclarationKind,
HirStatement, HirStyleUsage, HirType,
},
modules::DeclarationInfo,
};
use common::Span;
use slynx_parser::{
Expand All @@ -13,6 +14,13 @@ use slynx_parser::{
};

impl SlynxHir {
pub(crate) fn hoist_enum(&mut self, name: &str, variants: &[String]) -> DeclarationInfo {
let variants = variants
.iter()
.map(|variant| self.intern_name(variant))
.collect();
self.modules.create_enum(name, variants)
}
///Hoists a `stylesheet` declaration
pub(crate) fn hoist_stylesheet(&mut self, name: &str, args: &[TypedName]) {
self.modules.create_declaration(
Expand Down
16 changes: 11 additions & 5 deletions crates/hir/src/expression.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use std::mem::discriminant;

use crate::{
ExpressionId, Result, SlynxHir, SymbolPointer, TypeId,
error::{HIRError, HIRErrorKind},
model::{FieldMethod, HirExpression, HirExpressionKind, HirStatementKind, HirType},
ExpressionId, HirDeclarationKind, HirSymbol, Result, SlynxHir, SymbolPointer, TypeId, error::{HIRError, HIRErrorKind}, model::{FieldMethod, HirExpression, HirExpressionKind, HirStatementKind, HirType}
};
use common::{Operator, Span};
use slynx_parser::{ASTExpression, ASTExpressionKind, ASTStatement, GenericIdentifier, NamedExpr};
Expand Down Expand Up @@ -121,7 +119,7 @@ impl SlynxHir {
};
Ok(fields[*index])
}
FieldMethod::Variable(variable_id, field_name) => {
FieldMethod::Symbol(HirSymbol::Variable(variable_id), field_name) => {
let variable_ty = *self
.get_variable_type(*variable_id)
.expect("variable type should exist before field access lowering");
Expand All @@ -140,6 +138,13 @@ impl SlynxHir {
None => Err(HIRError::property_unrecognized(vec![*field_name], *span)),
}
}
FieldMethod::Symbol(HirSymbol::Declaration(ty), field) => {
let decl = &self.declarations[ty.as_raw() as usize];
Ok(match decl.kind{
HirDeclarationKind::Enum => decl.ty,
_ => self.infer_type()
})
},
FieldMethod::Tuple(rf, index) => self.resolve_tuple_access_type(*rf, *index, span),
}
}
Expand Down Expand Up @@ -368,7 +373,8 @@ impl SlynxHir {
}
ASTExpressionKind::Identifier(name) => {
let name = self.modules.intern_name(name);
let id = self.get_variable(name, &expr.span)?;
let symbol = self.resolve_name(name, &expr.span)?;
match
let tyid = self.create_type(name, HirType::VarReference(id));
Ok(self.create_identifier_expression(id, tyid, expr.span))
}
Expand Down
2 changes: 1 addition & 1 deletion crates/hir/src/helpers/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{HIRError, Result, SlynxHir, SymbolPointer, VariableId};

impl SlynxHir {
///Tries to retrieve a variable with the provided `name` on the current active scope
pub fn get_variable(&mut self, symbol: SymbolPointer, span: &Span) -> Result<VariableId> {
pub fn get_variable(&self, symbol: SymbolPointer, span: &Span) -> Result<VariableId> {
if let Some(variable) = self.modules.find_variable(symbol) {
Ok(variable)
} else {
Expand Down
6 changes: 6 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,11 @@ impl SlynxHir {
ASTDeclarationKind::ComponentDeclaration { name, members, .. } => {
self.hoist_component(name, members)?
}
ASTDeclarationKind::EnumDeclaration { name, variants } => {
let info = self.hoist_enum(&name.identifier, &variants);
self.declarations
.push(HirDeclaration::new_enum(info.id, info.ty, ast.span));
}
}
Ok(())
}
Expand Down Expand Up @@ -446,6 +451,7 @@ impl SlynxHir {
usages,
body,
} => self.resolve_stylesheet(name, args, usages, body, ast.span),
ASTDeclarationKind::EnumDeclaration { .. } => Ok(()), //hoisted, and thats all what matters
}
}
}
10 changes: 10 additions & 0 deletions crates/hir/src/model/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ pub struct HirDeclaration {
#[derive(Debug)]
#[repr(C)]
pub enum HirDeclarationKind {
Enum,

/// A simple data object with fields.
Object,

Expand Down Expand Up @@ -349,6 +351,14 @@ impl ComponentMemberDeclaration {
}

impl HirDeclaration {
pub fn new_enum(id: DeclarationId, ty: TypeId, span: Span) -> Self {
Self {
kind: HirDeclarationKind::Enum,
id,
ty,
span,
}
}
/// Creates a new function declaration.
///
/// # Arguments
Expand Down
7 changes: 7 additions & 0 deletions crates/hir/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ pub use declarations::*;
pub use expression::*;
pub use statements::*;
pub use types::*;

use crate::{DeclarationId, VariableId};
#[derive(Debug, Clone)]
pub enum HirSymbol {
Variable(VariableId),
Declaration(DeclarationId),
}
7 changes: 5 additions & 2 deletions crates/hir/src/model/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
//! - [`crate::hir::TypeId`] — Type identifiers
//! - [`crate::hir::modules::TypesModule`] — Type management

use crate::{SymbolPointer, TypeId, VariableId};
use crate::{HirSymbol, SymbolPointer, TypeId, VariableId};

use slynx_parser::VisibilityModifier;

Expand Down Expand Up @@ -107,7 +107,7 @@ pub enum FieldMethod {
///
/// - `0` — The variable ID
/// - `1` — The field name as a symbol
Variable(VariableId, SymbolPointer),
Symbol(HirSymbol, SymbolPointer),

/// Access a field on a tuple.
///
Expand Down Expand Up @@ -562,6 +562,9 @@ pub enum HirType {
///
/// Represents a component that can work with generic type parameters.
GenericComponent,

///An enum type. The type checking when this happens should be made related to the TypeId instead, thus enum A != enum B because of their TypeId are different
Enum,
}

impl HirType {
Expand Down
17 changes: 17 additions & 0 deletions crates/hir/src/modules/declarations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct DeclarationsModule {
declaration_types: Vec<TypeId>,
/// Maps each object [`TypeId`] to its ordered list of field symbol pointers.
pub objects: HashMap<TypeId, Vec<SymbolPointer>>,
///Maps a enum type to its variants
pub enums: HashMap<TypeId, Vec<SymbolPointer>>,
}

impl DeclarationsModule {
Expand All @@ -19,6 +21,7 @@ impl DeclarationsModule {
decls: HashMap::new(),
objects: HashMap::new(),
declaration_types: Vec::new(),
enums: HashMap::new(),
}
}
/// Registers a new declaration with the given name symbol and type, returning its [`DeclarationId`].
Expand All @@ -28,6 +31,20 @@ impl DeclarationsModule {
self.declaration_types.push(ty);
id
}

pub fn create_enum(
&mut self,
name: SymbolPointer,
variants: Vec<SymbolPointer>,
ty: TypeId,
) -> DeclarationId {
let id = DeclarationId::from_raw(self.declaration_types.len() as u64);
self.decls.insert(id, name);
self.declaration_types.push(ty);
self.enums.insert(ty, variants);
id
}

///Creates an objct with the provided `name`, `ty` and `fields` and returns it's id
pub fn create_object(
&mut self,
Expand Down
12 changes: 11 additions & 1 deletion crates/hir/src/modules/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,17 @@ impl HirModules {
symbol,
}
}

///Creates a new declaration with the given `name` and `ty`. returns its symbol, type id, and declaration id.
pub fn create_enum(&mut self, name: &str, variants: Vec<SymbolPointer>) -> DeclarationInfo {
let symbol = self.symbols_resolver.intern(name);
let tyid = self.types_module.create_type(symbol, HirType::Enum);
let decl_id = self.declarations_module.create_enum(symbol, variants, tyid);
DeclarationInfo {
id: decl_id,
ty: tyid,
symbol,
}
}
/// Creates an object type with the given name and fields and registers it as a declaration.
pub fn create_object(&mut self, name: &str, fields: &[ObjectField]) {
let name = self.symbols_resolver.intern(name);
Expand Down
13 changes: 12 additions & 1 deletion crates/hir/src/names.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
use crate::{Result, SlynxHir, SymbolPointer, TypeId, VariableId};
use crate::{HIRError, HirSymbol, Result, SlynxHir, SymbolPointer, TypeId, VariableId};

use common::Span;

//file specific to implement things related to name resolution
impl SlynxHir {
pub fn resolve_name(&self, symbol: SymbolPointer, span: &Span) -> Result<HirSymbol> {
match true {
_ if let Ok(out) = self.get_variable(symbol, span) => Ok(HirSymbol::Variable(out)),
_ if let Some((out, _)) = self.modules.get_declaration_by_name(&symbol) => {
Ok(HirSymbol::Declaration(out))
}
_ => Err(HIRError::name_unrecognized(symbol, *span)),
}
}

pub fn intern_name(&mut self, name: &str) -> SymbolPointer {
self.modules.intern_name(name)
}
Expand Down
115 changes: 115 additions & 0 deletions crates/ir/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# IR TODO

## High Priority

### Stable Allocation Identity

Problem:
`Read` instruction returns a new `Value` handle instead of returning the slot itself, breaking slot identity tracking. When `y = read i32, x` and `write i32, y, 10`, the backend cannot tell that both refer to the same allocation without full data-flow analysis.

Code:
`crates/ir/src/model/instruction.rs` lines 261-269 (`read` constructor)

Impact:
- Backends must implement use-def chain analysis to track slot aliases
- Simple slot-to-register mapping is impossible
- Control flow slot merging requires sophisticated reconstruction

Suggested Direction:
Either (a) make `Read` produce the slot value with the fetched data, or (b) add explicit slot copy/alias instructions.

### Slot Phi Nodes for Control Flow

Problem:
When control flow converges, allocated slots written in different branches have no merging mechanism. A slot written in both branches of an if-statement has ambiguous value after the merge.

Code:
`crates/ir/src/cfg/mod.rs` - CFG only handles branch targets, no phi node support

Impact:
- Backends cannot correctly handle slots across control flow
- Programs with slots in conditionals will generate incorrect code

Suggested Direction:
Add `SlotPhi(Vec<(LabelId, Value)>)` instruction to merge slot values at control flow joins.

### Slot Liveness Information

Problem:
No way to determine when a slot is no longer needed. All slots must be conservatively allocated for the entire function.

Code:
`crates/ir/src/ir.rs` - No liveness tracking in SlynxIR

Impact:
- Cannot optimize slot reuse
- Cannot eliminate dead stores
- Memory usage is always maximal

Suggested Direction:
Add liveness analysis pass or store live ranges in slot metadata.

## Medium Priority

### Meaningless Write Return Value

Problem:
`Write` instruction has `value_type` set and returns a `Value` from the builder, but produces no useful value.

Code:
`crates/ir/src/model/instruction.rs` line 271-280, `crates/ir/src/builder/functions.rs` lines 364-367

Impact:
- Confusing API for backends
- Potential for misuse in generated code

Suggested Direction:
Either make `Write` void (set `value_type` to void) or clarify that the return is intentionally meaningless.

### Slot Metadata for Memory Model

Problem:
Slots have only type information, no size, alignment, or address space metadata.

Code:
`crates/ir/src/model/instruction.rs` - Allocate has only `value_type`

Impact:
- Backends cannot optimize for packed structs
- No custom alignment support
- Cannot distinguish stack vs heap slots

Suggested Direction:
Extend `Allocate` with optional size/alignment hints or add separate `Alloca` with metadata.

## Low Priority

### Consistent Value Naming

Problem:
Formatter treats `Allocate` specially with `$` prefix while other values use `%t{idx}`.

Code:
`crates/ir/src/visualize/formatter.rs` lines 269-276

Impact:
- Minor inconsistency in IR dumps
- No effect on compilation

Suggested Direction:
Unify naming convention in formatter output.

### Built-in Slot Analysis Passes

Problem:
Backends must reimplement common slot analysis patterns.

Impact:
- Code duplication across backends
- Potential for inconsistent analysis results

Suggested Direction:
Add utility methods to SlynxIR for:
- Finding all uses of a slot
- Computing slot live ranges
- Tracking slot ownership chains
4 changes: 4 additions & 0 deletions crates/lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ impl Lexer {
idx -= 1;
let span = Span { start, end: idx };
match buffer.as_str() {
"enum" => Token {
kind: TokenKind::Enum,
span,
},
"while" => Token {
kind: TokenKind::While,
span,
Expand Down
9 changes: 6 additions & 3 deletions crates/lexer/src/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ impl std::fmt::Display for TokenKind {
Self::Int(value) => value.to_string(),
Self::String(value) => value.to_string(),
Self::Identifier(value) => value.to_string(),
Self::Enum => "enum".to_string(),
};
write!(f, "{}", result)
}
Expand Down Expand Up @@ -123,13 +124,15 @@ pub enum TokenKind {

Component,
Func,
Enum,
StyleSheet,
Object,

Pub,
Prop,
Alias,
StyleSheet,
Styles,

Object,
Styles,

Let,
Mut,
Expand Down
Loading
Loading