Much work from semantic analysis to lowering to ir/register alloc to assembly. WIP.

This commit is contained in:
Jesse Brault 2026-06-19 17:07:11 -05:00
parent d5bfe9ad28
commit 737d7b4232
39 changed files with 2496 additions and 988 deletions

View File

@ -48,8 +48,8 @@ impl Call {
&self.callee
}
pub fn arguments(&self) -> Vec<&Expression> {
self.arguments.iter().collect()
pub fn arguments(&self) -> &[Expression] {
&self.arguments
}
pub fn init_scopes(&mut self, symbol_table: &mut SymbolTable, container_scope: usize) {

View File

@ -60,6 +60,10 @@ impl LetStatement {
self.declared_name_source_range.clone()
}
pub fn is_mut(&self) -> bool {
self.is_mut
}
pub fn initializer(&self) -> &Expression {
&self.initializer
}

View File

@ -48,6 +48,10 @@ impl Parameter {
self.declared_name_source_range.clone()
}
pub fn type_use(&self) -> &TypeUse {
&self.type_use
}
pub fn scope_id(&self) -> usize {
self.scope_id.unwrap()
}

View File

@ -1,10 +1,28 @@
use crate::constants_table::ConstantsTable;
use dvm_lib::instruction::Instruction;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_binary_operation::IrBinaryOperator;
use crate::ir::ir_block::IrBlock;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_type_info::{IrTypeInfo, IrTypeInfoId};
use crate::ir::ir_variable::IrVariableId;
use crate::ir::variable_locations::{VariableLocation, VariableLocations};
use dvm_lib::instruction::{
AddOperand, Instruction, Location, LocationOrInteger, LocationOrNumber, MoveOperand,
MultiplyOperand, PushOperand, ReturnOperand, SubtractOperand,
};
use std::collections::HashMap;
#[deprecated]
pub trait Assemble {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable);
}
#[deprecated]
pub struct InstructionsBuilder {
instructions: Vec<Instruction>,
}
@ -24,3 +42,357 @@ impl InstructionsBuilder {
std::mem::take(&mut self.instructions)
}
}
struct FunctionAssemblyContext<'a> {
type_infos: &'a Vec<IrTypeInfo>,
variables_to_type_infos: &'a HashMap<IrVariableId, IrTypeInfoId>,
variable_locations: &'a VariableLocations,
constants_table: &'a mut ConstantsTable,
instructions: Vec<Instruction>,
}
pub fn assemble_ir_function(
ir_function: &IrFunction,
type_infos: &Vec<IrTypeInfo>,
variables_to_type_infos: &HashMap<IrVariableId, IrTypeInfoId>,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> Vec<Instruction> {
let mut ctx = FunctionAssemblyContext {
type_infos,
variables_to_type_infos,
variable_locations,
constants_table,
instructions: Vec::new(),
};
for block in ir_function.blocks() {
assemble_ir_block(block, &mut ctx);
}
ctx.instructions
}
fn assemble_ir_block(block: &IrBlock, ctx: &mut FunctionAssemblyContext) {
for statement in block.statements() {
assemble_ir_statement(statement, ctx);
}
}
fn assemble_ir_statement(statement: &IrStatement, ctx: &mut FunctionAssemblyContext) {
match statement {
IrStatement::Assign(ir_assign) => {
assemble_ir_assign(ir_assign, ctx);
}
IrStatement::Call(ir_call) => {
assemble_ir_call(ir_call, ctx);
}
IrStatement::Return(ir_return) => {
assemble_ir_return(ir_return, ctx);
}
IrStatement::SetField(ir_set_field) => {
todo!()
}
}
}
fn assemble_ir_assign(ir_assign: &IrAssign, ctx: &mut FunctionAssemblyContext) {
let destination_location = to_location(
ctx.variable_locations
.get_variable_location(&ir_assign.destination()),
);
match ir_assign.initializer() {
IrOperation::GetFieldRef(_) => {}
IrOperation::GetFieldRefMut(_) => {}
IrOperation::ReadField(_) => {}
IrOperation::Load(ir_expression) => {
let move_operand = to_move_operand(ir_expression, ctx);
let instruction = Instruction::Move(move_operand, destination_location);
ctx.instructions.push(instruction);
}
IrOperation::Binary(ir_binary_operation) => {
let instruction = match ir_binary_operation.op() {
IrBinaryOperator::Multiply => Instruction::Multiply(
to_multiply_operand(ir_binary_operation.left(), ctx),
to_multiply_operand(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::Divide => Instruction::Divide(
to_location_or_number(ir_binary_operation.left(), ctx),
to_location_or_number(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::Modulo => Instruction::Modulo(
to_location_or_number(ir_binary_operation.left(), ctx),
to_location_or_number(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::Add => Instruction::Add(
to_add_operand(ir_binary_operation.left(), ctx),
to_add_operand(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::Subtract => Instruction::Subtract(
to_subtract_operand(ir_binary_operation.left(), ctx),
to_subtract_operand(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::LeftShift => Instruction::LeftShift(
to_location_or_integer(ir_binary_operation.left(), ctx),
to_location_or_integer(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::RightShift => Instruction::RightShift(
to_location_or_integer(ir_binary_operation.left(), ctx),
to_location_or_integer(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::BitwiseAnd => Instruction::BitwiseAnd(
to_location_or_integer(ir_binary_operation.left(), ctx),
to_location_or_integer(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::BitwiseXor => Instruction::BitwiseXor(
to_location_or_integer(ir_binary_operation.left(), ctx),
to_location_or_integer(ir_binary_operation.right(), ctx),
destination_location,
),
IrBinaryOperator::BitwiseOr => Instruction::BitwiseOr(
to_location_or_integer(ir_binary_operation.left(), ctx),
to_location_or_integer(ir_binary_operation.right(), ctx),
destination_location,
),
};
ctx.instructions.push(instruction);
}
IrOperation::Call(_) => {
todo!()
}
IrOperation::Allocate(_) => {
todo!()
}
}
}
fn assemble_ir_call(ir_call: &IrCall, ctx: &mut FunctionAssemblyContext) {
// push all args
let args_as_push_operands = ir_call
.arguments()
.iter()
.map(|ir_expression| to_push_operand(ir_expression, ctx))
.collect::<Vec<_>>();
for push_operand in args_as_push_operands {
ctx.instructions.push(Instruction::Push(push_operand));
}
// push invoke instruction
if ir_call.is_extern() {
ctx.instructions.push(Instruction::InvokePlatformStatic(
ir_call.function_name_owned(),
ir_call.arguments().len(),
));
} else {
ctx.instructions.push(Instruction::InvokeStatic(
ir_call.function_name_owned(),
ir_call.arguments().len(),
));
}
}
fn assemble_ir_return(ir_return: &IrReturn, ctx: &mut FunctionAssemblyContext) {
if let Some(ir_expression) = ir_return.value() {
let return_operand = to_return_operand(ir_expression, ctx);
ctx.instructions
.push(Instruction::SetReturnValue(return_operand));
}
ctx.instructions.push(Instruction::Return);
}
fn to_move_operand(ir_expression: &IrExpression, ctx: &mut FunctionAssemblyContext) -> MoveOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
MoveOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable_id) => MoveOperand::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable_id),
)),
IrExpression::Int(i) => MoveOperand::Int(*i),
IrExpression::Double(d) => MoveOperand::Double(*d),
IrExpression::String(s) => {
let constant_name = ctx.constants_table.get_or_insert(s);
MoveOperand::String(constant_name)
}
}
}
fn to_multiply_operand(
ir_expression: &IrExpression,
ctx: &mut FunctionAssemblyContext,
) -> MultiplyOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
MultiplyOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable_id) => {
let ir_type_info_id = ctx.variables_to_type_infos[ir_variable_id];
let ir_type_info = &ctx.type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double => {
let location =
to_location(ctx.variable_locations.get_variable_location(ir_variable_id));
MultiplyOperand::Location(location)
}
_ => panic!("Attempt to multiply non-number (found {})", ir_type_info),
}
}
IrExpression::Int(i) => MultiplyOperand::Int(*i),
IrExpression::Double(d) => MultiplyOperand::Double(*d),
IrExpression::String(_) => {
panic!("Attempt to multiply with a string");
}
}
}
fn to_add_operand(ir_expression: &IrExpression, ctx: &mut FunctionAssemblyContext) -> AddOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
IrTypeInfo::Int | IrTypeInfo::Double | IrTypeInfo::String => {
AddOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
},
IrExpression::Variable(ir_variable_id) => {
let ir_type_info_id = ctx.variables_to_type_infos[ir_variable_id];
let ir_type_info = &ctx.type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double | IrTypeInfo::String => AddOperand::Location(
to_location(ctx.variable_locations.get_variable_location(ir_variable_id)),
),
}
}
IrExpression::Int(i) => AddOperand::Int(*i),
IrExpression::Double(d) => AddOperand::Double(*d),
IrExpression::String(s) => {
let constant_name = ctx.constants_table.get_or_insert(s);
AddOperand::String(constant_name)
}
}
}
fn to_subtract_operand(
ir_expression: &IrExpression,
ctx: &mut FunctionAssemblyContext,
) -> SubtractOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
IrTypeInfo::Int | IrTypeInfo::Double => {
SubtractOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
_ => panic!(
"Attempt to subtract with non-integer type (found {})",
ir_parameter.type_info()
),
},
IrExpression::Variable(ir_variable) => {
let ir_type_info_id = ctx.variables_to_type_infos[ir_variable];
let ir_type_info = &ctx.type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double => SubtractOperand::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable),
)),
_ => panic!(
"Attempt to subtract with non-number type (found {})",
ir_type_info
),
}
}
IrExpression::Int(i) => SubtractOperand::Int(*i),
IrExpression::Double(d) => SubtractOperand::Double(*d),
IrExpression::String(_) => {
panic!("Attempt to subtract with a string");
}
}
}
fn to_location_or_number(
ir_expression: &IrExpression,
ctx: &mut FunctionAssemblyContext,
) -> LocationOrNumber {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
LocationOrNumber::Location(ir_parameter.as_location())
}
IrExpression::Variable(ir_variable_id) => LocationOrNumber::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable_id),
)),
IrExpression::Int(i) => LocationOrNumber::Int(*i),
IrExpression::Double(d) => LocationOrNumber::Double(*d),
_ => panic!(
"Attempt to convert {} to a location or number",
ir_expression
),
}
}
fn to_push_operand(ir_expression: &IrExpression, ctx: &mut FunctionAssemblyContext) -> PushOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
PushOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable_id) => PushOperand::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable_id),
)),
IrExpression::Int(i) => PushOperand::Int(*i),
IrExpression::Double(d) => PushOperand::Double(*d),
IrExpression::String(s) => {
let constant_name = ctx.constants_table.get_or_insert(s);
PushOperand::String(constant_name)
}
}
}
fn to_return_operand(
ir_expression: &IrExpression,
ctx: &mut FunctionAssemblyContext,
) -> ReturnOperand {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
ReturnOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => ReturnOperand::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable),
)),
IrExpression::Int(i) => ReturnOperand::Int(*i),
IrExpression::Double(d) => ReturnOperand::Double(*d),
IrExpression::String(s) => {
let constant_name = ctx.constants_table.get_or_insert(s);
ReturnOperand::String(constant_name)
}
}
}
fn to_location_or_integer(
ir_expression: &IrExpression,
ctx: &mut FunctionAssemblyContext,
) -> LocationOrInteger {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
LocationOrInteger::Location(ir_parameter.as_location())
}
IrExpression::Variable(ir_variable_id) => LocationOrInteger::Location(to_location(
ctx.variable_locations.get_variable_location(ir_variable_id),
)),
IrExpression::Int(i) => LocationOrInteger::Int(*i),
_ => panic!(
"Attempt to convert {} to a location or integer",
ir_expression
),
}
}
fn to_location(variable_location: VariableLocation) -> Location {
match variable_location {
VariableLocation::Register(register_assignment) => Location::Register(register_assignment),
VariableLocation::Stack(stack_frame_offset) => {
Location::StackFrameOffset(stack_frame_offset)
}
}
}

View File

@ -0,0 +1,194 @@
use crate::ir::ir_allocate::IrAllocate;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_binary_operation::{IrBinaryOperation, IrBinaryOperator};
use crate::ir::ir_block::IrBlock;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_variable::IrVariable;
use std::fmt::Formatter;
struct DebugPrintContext<'a> {
ir_variables: &'a [IrVariable],
}
pub fn debug_format(ir_function: &IrFunction, f: &mut Formatter) -> std::fmt::Result {
write!(f, "fn {}(", ir_function.fqn())?;
for (i, parameter) in ir_function.parameters().iter().enumerate() {
write!(f, "{}: {}", parameter, parameter.type_info())?;
if i < ir_function.parameters().len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")?;
if let Some(return_type_info) = ir_function.return_type_info() {
write!(f, " -> {}\n", return_type_info)?;
} else {
write!(f, " -> Void\n")?;
}
let ctx = DebugPrintContext {
ir_variables: ir_function.variables(),
};
for block in ir_function.blocks() {
debug_format_block(block, f, &ctx)?;
write!(f, "\n")?;
}
Ok(())
}
fn debug_format_block(
ir_block: &IrBlock,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
writeln!(f, " {}:", ir_block.debug_label())?;
for statement in ir_block.statements() {
write!(f, " ")?;
debug_format_statement(statement, f, ctx)?;
write!(f, "\n")?;
}
Ok(())
}
fn debug_format_statement(
ir_statement: &IrStatement,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
match ir_statement {
IrStatement::Assign(ir_assign) => debug_format_assign(ir_assign, f, ctx),
IrStatement::Call(ir_call) => debug_format_call(ir_call, f, ctx),
IrStatement::Return(ir_return) => debug_format_return(ir_return, f, ctx),
IrStatement::SetField(ir_set_field) => {
todo!()
}
}
}
fn debug_format_assign(
ir_assign: &IrAssign,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
let variable_name = ctx.ir_variables[ir_assign.destination()].name();
write!(f, "{} = ", variable_name)?;
debug_format_operation(ir_assign.initializer(), f, ctx)?;
Ok(())
}
fn debug_format_operation(
ir_operation: &IrOperation,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
match ir_operation {
IrOperation::GetFieldRef(ir_get_field_ref) => {
todo!()
}
IrOperation::GetFieldRefMut(ir_get_field_ref_mut) => {
todo!()
}
IrOperation::ReadField(ir_read_field) => {
todo!()
}
IrOperation::Load(ir_expression) => debug_format_expression(ir_expression, f, ctx),
IrOperation::Binary(ir_binary_operation) => {
debug_format_binary_operation(ir_binary_operation, f, ctx)
}
IrOperation::Call(ir_call) => debug_format_call(ir_call, f, ctx),
IrOperation::Allocate(ir_allocate) => debug_format_allocate(ir_allocate, f),
}
}
fn debug_format_expression(
ir_expression: &IrExpression,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
match ir_expression {
IrExpression::Parameter(ir_parameter) => {
todo!()
}
IrExpression::Variable(ir_variable_id) => {
let variable_name = ctx.ir_variables[*ir_variable_id].name();
write!(f, "{}", variable_name)
}
IrExpression::Int(i) => {
write!(f, "{}", i)
}
IrExpression::Double(d) => {
write!(f, "{}", d)
}
IrExpression::String(s) => {
write!(f, "\"{}\"", s)
}
}
}
fn debug_format_binary_operation(
ir_binary_operation: &IrBinaryOperation,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
let op_string = match ir_binary_operation.op() {
IrBinaryOperator::Multiply => "*",
IrBinaryOperator::Divide => "/",
IrBinaryOperator::Modulo => "%",
IrBinaryOperator::Add => "+",
IrBinaryOperator::Subtract => "-",
IrBinaryOperator::LeftShift => "<<",
IrBinaryOperator::RightShift => ">>",
IrBinaryOperator::BitwiseAnd => "&",
IrBinaryOperator::BitwiseXor => "^",
IrBinaryOperator::BitwiseOr => "|",
};
debug_format_expression(ir_binary_operation.left(), f, ctx)?;
write!(f, " {} ", op_string)?;
debug_format_expression(ir_binary_operation.right(), f, ctx)?;
Ok(())
}
fn debug_format_call(
ir_call: &IrCall,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
write!(f, "{}(", ir_call.function_name())?;
for (i, argument) in ir_call.arguments().iter().enumerate() {
debug_format_expression(argument, f, ctx)?;
if i < ir_call.arguments().len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ")")?;
Ok(())
}
fn debug_format_allocate(ir_allocate: &IrAllocate, f: &mut Formatter) -> std::fmt::Result {
write!(f, "alloc {}", ir_allocate.class_fqn())
}
fn debug_format_return(
ir_return: &IrReturn,
f: &mut Formatter,
ctx: &DebugPrintContext,
) -> std::fmt::Result {
if let Some(ir_expression) = ir_return.value() {
write!(f, "ret ")?;
debug_format_expression(ir_expression, f, ctx)?;
} else {
write!(f, "ret")?;
}
Ok(())
}

View File

@ -1,126 +1,36 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use crate::ir::util::propagate_spills;
use crate::offset_counter::OffsetCounter;
use dvm_lib::instruction::Instruction;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
use std::collections::HashSet;
pub struct IrAssign {
destination: Rc<RefCell<IrVariable>>,
destination: IrVariableId,
initializer: Box<IrOperation>,
}
impl IrAssign {
pub fn new(destination: Rc<RefCell<IrVariable>>, initializer: IrOperation) -> Self {
pub fn new(destination: IrVariableId, initializer: IrOperation) -> Self {
Self {
destination,
initializer: initializer.into(),
}
}
pub fn destination(&self) -> IrVariableId {
self.destination
}
pub fn initializer(&self) -> &IrOperation {
&self.initializer
}
}
impl VrUser for IrAssign {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
self.destination
.borrow()
.descriptor()
.vr_variable_descriptors()
fn vr_definitions(&self) -> HashSet<IrVariableId> {
HashSet::from([self.destination])
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
self.initializer.vr_uses()
}
fn propagate_spills(&mut self, spills: &HashSet<IrVrVariableDescriptor>) {
propagate_spills(&mut self.destination, spills);
}
fn propagate_register_assignments(
&mut self,
assignments: &HashMap<IrVrVariableDescriptor, usize>,
) {
let mut borrowed_destination = self.destination.borrow_mut();
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
borrowed_destination.descriptor_mut()
{
if assignments.contains_key(vr_variable) {
vr_variable.set_assigned_register(assignments[vr_variable]);
}
}
}
fn propagate_stack_offsets(&mut self, counter: &mut OffsetCounter) {
let mut borrowed_destination = self.destination.borrow_mut();
if let IrVariableDescriptor::Stack(stack_variable) = borrowed_destination.descriptor_mut() {
stack_variable.use_next_offset(counter);
}
}
}
impl Assemble for IrAssign {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
let destination = self.destination.borrow().descriptor().as_location();
match self.initializer.as_ref() {
IrOperation::GetFieldRef(ir_get_field_ref) => {
let self_location = ir_get_field_ref.self_parameter_or_variable().as_location();
builder.push(Instruction::GetFieldPointer(
self_location,
ir_get_field_ref.field_index(),
destination,
));
}
IrOperation::GetFieldRefMut(ir_get_field_ref_mut) => {
let self_location = ir_get_field_ref_mut
.self_parameter_or_variable()
.as_location();
builder.push(Instruction::GetFieldPointerMut(
self_location,
ir_get_field_ref_mut.field_index(),
destination,
));
}
IrOperation::ReadField(ir_read_field) => {
let field_ref_location = ir_read_field
.field_ref_variable()
.borrow()
.descriptor()
.as_location();
builder.push(Instruction::ReadField(field_ref_location, destination));
}
IrOperation::Load(ir_expression) => {
let move_operand = ir_expression.move_operand(constants_table);
builder.push(Instruction::Move(move_operand, destination));
}
IrOperation::Binary(ir_binary_operation) => {
ir_binary_operation.assemble_assign(builder, constants_table, destination);
}
IrOperation::Call(ir_call) => {
ir_call.assemble(builder, constants_table);
builder.push(Instruction::Pop(Some(destination)));
}
IrOperation::Allocate(ir_allocate) => builder.push(Instruction::Allocate(
ir_allocate.class_fqn_owned(),
destination,
)),
}
}
}
impl Display for IrAssign {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}: {} = {}",
self.destination.borrow(),
self.destination.borrow().type_info(),
self.initializer
)
}
}

View File

@ -1,9 +1,6 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::InstructionsBuilder;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use dvm_lib::instruction::{Instruction, Location};
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
@ -35,65 +32,16 @@ impl IrBinaryOperation {
}
}
pub fn assemble_assign(
&self,
builder: &mut InstructionsBuilder,
constants_table: &mut ConstantsTable,
destination: Location,
) {
let instruction = match &self.op {
IrBinaryOperator::Multiply => Instruction::Multiply(
self.left.multiply_operand(),
self.right.multiply_operand(),
destination,
),
IrBinaryOperator::Divide => Instruction::Divide(
self.left.as_location_or_number(),
self.right.as_location_or_number(),
destination,
),
IrBinaryOperator::Modulo => Instruction::Modulo(
self.left.as_location_or_number(),
self.right.as_location_or_number(),
destination,
),
IrBinaryOperator::Add => Instruction::Add(
self.left.add_operand(constants_table),
self.right.add_operand(constants_table),
destination,
),
IrBinaryOperator::Subtract => Instruction::Subtract(
self.left.subtract_operand(),
self.right.subtract_operand(),
destination,
),
IrBinaryOperator::LeftShift => Instruction::LeftShift(
self.left.as_location_or_integer(),
self.right.as_location_or_integer(),
destination,
),
IrBinaryOperator::RightShift => Instruction::RightShift(
self.left.as_location_or_integer(),
self.right.as_location_or_integer(),
destination,
),
IrBinaryOperator::BitwiseAnd => Instruction::BitwiseAnd(
self.left.as_location_or_integer(),
self.right.as_location_or_integer(),
destination,
),
IrBinaryOperator::BitwiseXor => Instruction::BitwiseXor(
self.left.as_location_or_integer(),
self.right.as_location_or_integer(),
destination,
),
IrBinaryOperator::BitwiseOr => Instruction::BitwiseOr(
self.left.as_location_or_integer(),
self.right.as_location_or_integer(),
destination,
),
};
builder.push(instruction);
pub fn left(&self) -> &IrExpression {
&self.left
}
pub fn right(&self) -> &IrExpression {
&self.right
}
pub fn op(&self) -> &IrBinaryOperator {
&self.op
}
}
@ -135,7 +83,7 @@ impl Display for IrBinaryOperation {
}
impl VrUser for IrBinaryOperation {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
[self.left.as_ref(), self.right.as_ref()]
.iter()
.flat_map(|e| e.vr_uses())

View File

@ -1,19 +1,14 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::register_allocation::{HasVrUsers, VrUser};
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::{HasVrUsers, RegisterAssignment, VrUser};
use crate::offset_counter::OffsetCounter;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub type IrBlockId = usize;
pub struct IrBlock {
id: usize,
id: IrBlockId,
debug_label: String,
predecessors: Vec<Rc<RefCell<IrBlock>>>,
successors: Vec<Rc<RefCell<IrBlock>>>,
statements: Vec<IrStatement>,
}
@ -22,16 +17,18 @@ impl IrBlock {
Self {
id,
debug_label: debug_label.into(),
predecessors: vec![],
successors: vec![],
statements,
}
}
pub fn id(&self) -> usize {
pub fn id(&self) -> IrBlockId {
self.id
}
pub fn debug_label(&self) -> &str {
&self.debug_label
}
pub fn statements(&self) -> &[IrStatement] {
&self.statements
}
@ -51,18 +48,18 @@ impl HasVrUsers for IrBlock {
}
impl VrUser for IrBlock {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_definitions(&self) -> HashSet<IrVariableId> {
self.statements
.iter()
.flat_map(|s| s.vr_definitions())
.collect()
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
self.statements.iter().flat_map(|s| s.vr_uses()).collect()
}
fn propagate_spills(&mut self, spills: &HashSet<IrVrVariableDescriptor>) {
fn propagate_spills(&mut self, spills: &HashSet<IrVariableId>) {
for statement in &mut self.statements {
statement.propagate_spills(spills);
}
@ -70,7 +67,7 @@ impl VrUser for IrBlock {
fn propagate_register_assignments(
&mut self,
assignments: &HashMap<IrVrVariableDescriptor, usize>,
assignments: &HashMap<IrVariableId, RegisterAssignment>,
) {
for statement in &mut self.statements {
statement.propagate_register_assignments(assignments);
@ -84,41 +81,34 @@ impl VrUser for IrBlock {
}
}
impl Assemble for IrBlock {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
for statement in &self.statements {
statement.assemble(builder, constants_table);
}
}
}
impl Display for IrBlock {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, " {}:", self.debug_label)?;
let (live_in, live_out) = self.live_in_live_out();
for (statement_index, statement) in self.statements.iter().enumerate() {
let statement_live_in = live_in.get(&statement_index).unwrap();
let statement_live_out = live_out.get(&statement_index).unwrap();
writeln!(
f,
" {} // live_in: {:?}, live_out: {:?}",
statement, statement_live_in, statement_live_out
)?;
}
writeln!(f, " // ---- {} meta ----", self.debug_label)?;
writeln!(f, " // definitions: {:?}", self.vr_definitions())?;
writeln!(f, " // uses: {:?}", self.vr_uses())?;
writeln!(
f,
" // interference graph: {:?}",
self.interference_graph()
)?;
Ok(())
}
}
//
// impl Display for IrBlock {
// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// writeln!(f, " {}:", self.debug_label)?;
//
// let (live_in, live_out) = self.live_in_live_out();
//
// for (statement_index, statement) in self.statements.iter().enumerate() {
// let statement_live_in = live_in.get(&statement_index).unwrap();
// let statement_live_out = live_out.get(&statement_index).unwrap();
//
// writeln!(
// f,
// " {} // live_in: {:?}, live_out: {:?}",
// statement, statement_live_in, statement_live_out
// )?;
// }
// writeln!(f, " // ---- {} meta ----", self.debug_label)?;
// writeln!(f, " // definitions: {:?}", self.vr_definitions())?;
// writeln!(f, " // uses: {:?}", self.vr_uses())?;
// writeln!(
// f,
// " // interference graph: {:?}",
// self.interference_graph()
// )?;
// Ok(())
// }
// }
#[cfg(test)]
mod tests {
@ -157,9 +147,9 @@ mod tests {
.find(|f| f.declared_name() == "main")
.unwrap();
let mut main_ir = main.to_ir(&symbol_table, &types_table, None);
let register_assignments = main_ir.assign_registers(2, &mut OffsetCounter::new());
assert_eq!(register_assignments.len(), 4);
let main_ir = main.to_ir(&symbol_table, &types_table, None);
let variable_locations = main_ir.assign_registers(2);
assert_eq!(variable_locations.register_variables_count(), 4);
Ok(())
}

View File

@ -1,9 +1,6 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use dvm_lib::instruction::Instruction;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
@ -22,35 +19,30 @@ impl IrCall {
is_extern,
}
}
pub fn function_name(&self) -> &str {
&self.function_name
}
pub fn function_name_owned(&self) -> Rc<str> {
self.function_name.clone()
}
pub fn arguments(&self) -> &[IrExpression] {
&self.arguments
}
pub fn is_extern(&self) -> bool {
self.is_extern
}
}
impl VrUser for IrCall {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
self.arguments.iter().flat_map(|a| a.vr_uses()).collect()
}
}
impl Assemble for IrCall {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
// push all args
self.arguments
.iter()
.map(|ir_expression| ir_expression.push_operand(constants_table))
.for_each(|push_operand| builder.push(Instruction::Push(push_operand)));
if self.is_extern {
builder.push(Instruction::InvokePlatformStatic(
self.function_name.clone(),
self.arguments.len(),
));
} else {
builder.push(Instruction::InvokeStatic(
self.function_name.clone(),
self.arguments.len(),
));
}
}
}
impl Display for IrCall {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}(", self.function_name)?;

View File

@ -1,39 +1,45 @@
use crate::constants_table::ConstantsTable;
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::ir_type_info::{IrTypeInfo, IrTypeInfoId};
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use crate::type_info::TypeInfo;
use crate::ir::variable_locations::{VariableLocation, VariableLocations};
use dvm_lib::instruction::{
AddOperand, Location, LocationOrInteger, LocationOrNumber, MoveOperand, MultiplyOperand,
PushOperand, ReturnOperand, SetFieldOperand, SubtractOperand,
};
use std::cell::RefCell;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub enum IrExpression {
Parameter(Rc<IrParameter>),
Variable(Rc<RefCell<IrVariable>>),
Variable(IrVariableId),
Int(i32),
Double(f64),
String(Rc<str>),
}
impl IrExpression {
pub fn move_operand(&self, constants_table: &mut ConstantsTable) -> MoveOperand {
pub fn move_operand(
&self,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> MoveOperand {
match self {
IrExpression::Parameter(ir_parameter) => {
MoveOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(register_variable) => {
MoveOperand::Location(Location::Register(register_variable.assigned_register()))
IrExpression::Variable(ir_variable_id) => {
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
MoveOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
MoveOperand::Location(Location::StackFrameOffset(stack_frame_offset))
}
}
IrVariableDescriptor::Stack(stack_variable) => {
MoveOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => MoveOperand::Int(*i),
IrExpression::Double(d) => MoveOperand::Double(*d),
IrExpression::String(s) => {
@ -43,19 +49,25 @@ impl IrExpression {
}
}
pub fn push_operand(&self, constants_table: &mut ConstantsTable) -> PushOperand {
pub fn push_operand(
&self,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> PushOperand {
match self {
IrExpression::Parameter(ir_parameter) => {
PushOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(register_variable) => {
PushOperand::Location(Location::Register(register_variable.assigned_register()))
IrExpression::Variable(ir_variable_id) => {
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
PushOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
PushOperand::Location(Location::StackFrameOffset(stack_frame_offset))
}
}
IrVariableDescriptor::Stack(stack_variable) => {
PushOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => PushOperand::Int(*i),
IrExpression::Double(d) => PushOperand::Double(*d),
IrExpression::String(s) => {
@ -65,35 +77,35 @@ impl IrExpression {
}
}
pub fn add_operand(&self, constants_table: &mut ConstantsTable) -> AddOperand {
pub fn add_operand(
&self,
type_infos: &Vec<IrTypeInfo>,
variables_to_type_infos: &HashMap<IrVariableId, IrTypeInfoId>,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> AddOperand {
match self {
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
TypeInfo::Integer | TypeInfo::Double | TypeInfo::String => {
IrTypeInfo::Int | IrTypeInfo::Double | IrTypeInfo::String => {
AddOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
_ => panic!(
"Attempt to add non- integer/double/string (found: {})",
ir_parameter.type_info()
),
},
IrExpression::Variable(ir_variable) => match ir_variable.borrow().type_info() {
TypeInfo::Integer | TypeInfo::Double | TypeInfo::String => {
match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(register_variable) => {
AddOperand::Location(Location::Register(
register_variable.assigned_register(),
))
IrExpression::Variable(ir_variable_id) => {
let ir_type_info_id = variables_to_type_infos[ir_variable_id];
let ir_type_info = &type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double | IrTypeInfo::String => {
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
AddOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
AddOperand::Location(Location::StackFrameOffset(stack_frame_offset))
}
}
}
IrVariableDescriptor::Stack(stack_variable) => AddOperand::Location(
Location::StackFrameOffset(stack_variable.offset()),
),
}
}
_ => panic!(
"Attempt to add non-integer/non-string (found: {})",
ir_variable.borrow().type_info()
),
},
IrExpression::Int(i) => AddOperand::Int(*i),
IrExpression::Double(d) => AddOperand::Double(*d),
IrExpression::String(s) => {
@ -103,10 +115,15 @@ impl IrExpression {
}
}
pub fn subtract_operand(&self) -> SubtractOperand {
pub fn subtract_operand(
&self,
type_infos: &Vec<IrTypeInfo>,
variables_to_type_infos: &HashMap<IrVariableId, IrTypeInfoId>,
variable_locations: &VariableLocations,
) -> SubtractOperand {
match self {
IrExpression::Parameter(ir_parameter) => match ir_parameter.type_info() {
TypeInfo::Integer | TypeInfo::Double => SubtractOperand::Location(
IrTypeInfo::Int | IrTypeInfo::Double => SubtractOperand::Location(
Location::StackFrameOffset(ir_parameter.stack_offset()),
),
_ => panic!(
@ -114,22 +131,26 @@ impl IrExpression {
ir_parameter.type_info()
),
},
IrExpression::Variable(ir_variable) => match ir_variable.borrow().type_info() {
TypeInfo::Integer | TypeInfo::Double => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
SubtractOperand::Location(Location::Register(
vr_variable.assigned_register(),
))
IrExpression::Variable(ir_variable) => {
let ir_type_info_id = variables_to_type_infos[ir_variable];
let ir_type_info = &type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double => match variable_locations
.get_variable_location(ir_variable)
{
VariableLocation::Register(register_assignment) => {
SubtractOperand::Location(Location::Register(register_assignment))
}
IrVariableDescriptor::Stack(stack_variable) => SubtractOperand::Location(
Location::StackFrameOffset(stack_variable.offset()),
VariableLocation::Stack(stack_frame_offset) => SubtractOperand::Location(
Location::StackFrameOffset(stack_frame_offset),
),
},
_ => panic!(
"Attempt to subtract with non-integer type (found {})",
ir_variable.borrow().type_info()
"Attempt to subtract with non-number type (found {})",
ir_type_info
),
},
}
}
IrExpression::Int(i) => SubtractOperand::Int(*i),
IrExpression::Double(d) => SubtractOperand::Double(*d),
IrExpression::String(_) => {
@ -138,19 +159,35 @@ impl IrExpression {
}
}
pub fn multiply_operand(&self) -> MultiplyOperand {
pub fn multiply_operand(
&self,
type_infos: &Vec<IrTypeInfo>,
variables_to_type_infos: &HashMap<IrVariableId, IrTypeInfoId>,
variables_locations: &VariableLocations,
) -> MultiplyOperand {
match self {
IrExpression::Parameter(ir_parameter) => {
MultiplyOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
MultiplyOperand::Location(Location::Register(vr_variable.assigned_register()))
IrExpression::Variable(ir_variable_id) => {
let ir_type_info_id = variables_to_type_infos[ir_variable_id];
let ir_type_info = &type_infos[ir_type_info_id];
match ir_type_info {
IrTypeInfo::Int | IrTypeInfo::Double => {
match variables_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
MultiplyOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
MultiplyOperand::Location(Location::StackFrameOffset(
stack_frame_offset,
))
}
}
}
_ => panic!("Attempt to multiply non-number (found {})", ir_type_info),
}
IrVariableDescriptor::Stack(stack_variable) => {
MultiplyOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => MultiplyOperand::Int(*i),
IrExpression::Double(d) => MultiplyOperand::Double(*d),
IrExpression::String(_) => {
@ -159,21 +196,25 @@ impl IrExpression {
}
}
pub fn return_operand(&self, constants_table: &mut ConstantsTable) -> ReturnOperand {
pub fn return_operand(
&self,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> ReturnOperand {
match self {
IrExpression::Parameter(ir_parameter) => {
ReturnOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(register_variable) => {
ReturnOperand::Location(Location::Register(
register_variable.assigned_register(),
))
IrExpression::Variable(ir_variable) => {
match variable_locations.get_variable_location(ir_variable) {
VariableLocation::Register(register_assignment) => {
ReturnOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
ReturnOperand::Location(Location::StackFrameOffset(stack_frame_offset))
}
}
IrVariableDescriptor::Stack(stack_variable) => {
ReturnOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => ReturnOperand::Int(*i),
IrExpression::Double(d) => ReturnOperand::Double(*d),
IrExpression::String(s) => {
@ -183,19 +224,25 @@ impl IrExpression {
}
}
pub fn set_field_operand(&self, constants_table: &mut ConstantsTable) -> SetFieldOperand {
pub fn set_field_operand(
&self,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> SetFieldOperand {
match self {
IrExpression::Parameter(ir_parameter) => {
SetFieldOperand::Location(Location::StackFrameOffset(ir_parameter.stack_offset()))
}
IrExpression::Variable(ir_variable) => match ir_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
SetFieldOperand::Location(Location::Register(vr_variable.assigned_register()))
IrExpression::Variable(ir_variable_id) => {
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
SetFieldOperand::Location(Location::Register(register_assignment))
}
VariableLocation::Stack(stack_frame_offset) => {
SetFieldOperand::Location(Location::StackFrameOffset(stack_frame_offset))
}
}
IrVariableDescriptor::Stack(stack_variable) => {
SetFieldOperand::Location(Location::StackFrameOffset(stack_variable.offset()))
}
},
IrExpression::Int(i) => SetFieldOperand::Int(*i),
IrExpression::Double(d) => SetFieldOperand::Double(*d),
IrExpression::String(s) => {
@ -205,28 +252,48 @@ impl IrExpression {
}
}
pub fn as_location_or_number(&self) -> LocationOrNumber {
pub fn as_location_or_number(
&self,
variable_locations: &VariableLocations,
) -> LocationOrNumber {
match self {
IrExpression::Parameter(ir_parameter) => {
LocationOrNumber::Location(ir_parameter.as_location())
}
IrExpression::Variable(ir_variable) => {
LocationOrNumber::Location(ir_variable.borrow().descriptor().as_location())
IrExpression::Variable(ir_variable_id) => LocationOrNumber::Location(
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
Location::Register(register_assignment)
}
VariableLocation::Stack(stack_frame_offset) => {
Location::StackFrameOffset(stack_frame_offset)
}
},
),
IrExpression::Int(i) => LocationOrNumber::Int(*i),
IrExpression::Double(d) => LocationOrNumber::Double(*d),
_ => panic!("Attempt to convert {} to a location or number", self),
}
}
pub fn as_location_or_integer(&self) -> LocationOrInteger {
pub fn as_location_or_integer(
&self,
variable_locations: &VariableLocations,
) -> LocationOrInteger {
match self {
IrExpression::Parameter(ir_parameter) => {
LocationOrInteger::Location(ir_parameter.as_location())
}
IrExpression::Variable(ir_variable) => {
LocationOrInteger::Location(ir_variable.borrow().descriptor().as_location())
IrExpression::Variable(ir_variable_id) => LocationOrInteger::Location(
match variable_locations.get_variable_location(ir_variable_id) {
VariableLocation::Register(register_assignment) => {
Location::Register(register_assignment)
}
VariableLocation::Stack(stack_frame_offset) => {
Location::StackFrameOffset(stack_frame_offset)
}
},
),
IrExpression::Int(i) => LocationOrInteger::Int(*i),
_ => panic!("Attempt to convert {} to a location or integer", self),
}
@ -239,8 +306,8 @@ impl Display for IrExpression {
IrExpression::Parameter(ir_parameter) => {
write!(f, "{}", ir_parameter)
}
IrExpression::Variable(ir_variable) => {
write!(f, "{}", ir_variable.borrow())
IrExpression::Variable(ir_variable_id) => {
write!(f, "{}", ir_variable_id)
}
IrExpression::Int(i) => {
write!(f, "{}", i)
@ -256,12 +323,10 @@ impl Display for IrExpression {
}
impl VrUser for IrExpression {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
match self {
IrExpression::Parameter(_) => HashSet::new(),
IrExpression::Variable(ir_variable) => {
ir_variable.borrow().descriptor().vr_variable_descriptors()
}
IrExpression::Variable(ir_variable) => HashSet::from([*ir_variable]),
IrExpression::Int(_) => HashSet::new(),
IrExpression::Double(_) => HashSet::new(),
IrExpression::String(_) => HashSet::new(),

View File

@ -1,72 +1,90 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::assemble::assemble_ir_function;
use crate::ir::ir_block::IrBlock;
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::register_allocation::HasVrUsers;
use crate::offset_counter::OffsetCounter;
use crate::type_info::TypeInfo;
use crate::ir::ir_type_info::{IrTypeInfo, IrTypeInfoId};
use crate::ir::ir_variable::{IrVariable, IrVariableId};
use crate::ir::register_allocation::block_assign_registers;
use crate::ir::variable_locations::VariableLocations;
use dvm_lib::vm::function::Function;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Display;
use std::rc::Rc;
pub struct IrFunction {
fqn: Rc<str>,
parameters: Vec<Rc<IrParameter>>,
return_type_info: TypeInfo,
entry: Rc<RefCell<IrBlock>>,
variables: Vec<IrVariable>,
return_type_info: Option<IrTypeInfo>,
blocks: Vec<IrBlock>,
}
impl IrFunction {
pub fn new(
fqn: Rc<str>,
parameters: Vec<Rc<IrParameter>>,
return_type_info: &TypeInfo,
entry: Rc<RefCell<IrBlock>>,
variables: Vec<IrVariable>,
return_type_info: Option<IrTypeInfo>,
blocks: Vec<IrBlock>,
) -> Self {
Self {
fqn,
parameters: parameters.to_vec(),
return_type_info: return_type_info.clone(),
entry,
parameters,
variables,
return_type_info,
blocks,
}
}
pub fn assign_registers(
&mut self,
register_count: usize,
offset_counter: &mut OffsetCounter,
) -> HashMap<IrVrVariableDescriptor, usize> {
self.entry
.borrow_mut()
.assign_registers(register_count, offset_counter)
pub fn fqn(&self) -> &str {
&self.fqn
}
pub fn assemble(&self, stack_size: usize, constants_table: &mut ConstantsTable) -> Function {
let mut builder = InstructionsBuilder::new();
self.entry.borrow().assemble(&mut builder, constants_table);
let instructions = builder.take_instructions();
pub fn blocks(&self) -> &[IrBlock] {
&self.blocks
}
pub fn parameters(&self) -> &[Rc<IrParameter>] {
&self.parameters
}
pub fn variables(&self) -> &[IrVariable] {
&self.variables
}
pub fn return_type_info(&self) -> Option<&IrTypeInfo> {
self.return_type_info.as_ref()
}
pub fn assign_registers(&self, register_count: usize) -> VariableLocations {
if self.blocks.is_empty() {
return VariableLocations::new(HashMap::new(), HashMap::new());
}
if self.blocks.len() > 1 {
unimplemented!("having more than one block in a function is not yet implemented.")
}
let block = &self.blocks[0];
block_assign_registers(block, register_count)
}
pub fn assemble(
&self,
type_infos: &Vec<IrTypeInfo>,
variables_to_type_infos: &HashMap<IrVariableId, IrTypeInfoId>,
variable_locations: &VariableLocations,
constants_table: &mut ConstantsTable,
) -> Function {
let instructions = assemble_ir_function(
self,
type_infos,
variables_to_type_infos,
variable_locations,
constants_table,
);
Function::new(
self.fqn.clone(),
self.parameters.len(),
stack_size,
variable_locations.stack_size(),
instructions,
)
}
}
impl Display for IrFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "fn {}(", self.fqn)?;
for (i, parameter) in self.parameters.iter().enumerate() {
write!(f, "{}: {}", parameter, parameter.type_info())?;
if i < self.parameters.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, ") -> {}\n{}", self.return_type_info, self.entry.borrow())?;
Ok(())
}
}

View File

@ -1,24 +1,23 @@
use crate::ir::ir_parameter_or_variable::IrParameterOrVariable;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
pub struct IrGetFieldRef {
self_parameter_or_variable: IrParameterOrVariable,
self_variable_id: IrVariableId,
field_index: usize,
}
impl IrGetFieldRef {
pub fn new(self_parameter_or_variable: IrParameterOrVariable, field_index: usize) -> Self {
pub fn new(self_variable_id: IrVariableId, field_index: usize) -> Self {
Self {
self_parameter_or_variable,
self_variable_id,
field_index,
}
}
pub fn self_parameter_or_variable(&self) -> &IrParameterOrVariable {
&self.self_parameter_or_variable
pub fn self_variable_id(self) -> IrVariableId {
self.self_variable_id
}
pub fn field_index(&self) -> usize {
@ -28,16 +27,12 @@ impl IrGetFieldRef {
impl Display for IrGetFieldRef {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"&{}.{}",
self.self_parameter_or_variable, self.field_index
)
write!(f, "&{}.{}", self.self_variable_id, self.field_index)
}
}
impl VrUser for IrGetFieldRef {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
self.self_parameter_or_variable().vr_uses()
fn vr_uses(&self) -> HashSet<IrVariableId> {
todo!()
}
}

View File

@ -1,24 +1,23 @@
use crate::ir::ir_parameter_or_variable::IrParameterOrVariable;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
pub struct IrGetFieldRefMut {
self_parameter_or_variable: IrParameterOrVariable,
self_ir_variable: IrVariableId,
field_index: usize,
}
impl IrGetFieldRefMut {
pub fn new(self_parameter_or_variable: IrParameterOrVariable, field_index: usize) -> Self {
pub fn new(self_ir_variable: IrVariableId, field_index: usize) -> Self {
Self {
self_parameter_or_variable,
self_ir_variable,
field_index,
}
}
pub fn self_parameter_or_variable(&self) -> &IrParameterOrVariable {
&self.self_parameter_or_variable
pub fn self_ir_variable(&self) -> IrVariableId {
self.self_ir_variable
}
pub fn field_index(&self) -> usize {
@ -28,17 +27,12 @@ impl IrGetFieldRefMut {
impl Display for IrGetFieldRefMut {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"&mut {}.{}",
self.self_parameter_or_variable,
self.field_index()
)
write!(f, "&mut {}.{}", self.self_ir_variable, self.field_index())
}
}
impl VrUser for IrGetFieldRefMut {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
self.self_parameter_or_variable.vr_uses()
fn vr_uses(&self) -> HashSet<IrVariableId> {
todo!()
}
}

View File

@ -5,7 +5,7 @@ use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_get_field_ref::IrGetFieldRef;
use crate::ir::ir_get_field_ref_mut::IrGetFieldRefMut;
use crate::ir::ir_read_field::IrReadField;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
@ -49,7 +49,7 @@ impl Display for IrOperation {
}
impl VrUser for IrOperation {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
match self {
IrOperation::GetFieldRef(ir_get_field_ref) => ir_get_field_ref.vr_uses(),
IrOperation::GetFieldRefMut(ir_get_field_ref_mut) => ir_get_field_ref_mut.vr_uses(),

View File

@ -1,16 +1,16 @@
use crate::type_info::TypeInfo;
use crate::ir::ir_type_info::IrTypeInfo;
use dvm_lib::instruction::Location;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub struct IrParameter {
name: Rc<str>,
type_info: TypeInfo,
type_info: IrTypeInfo,
stack_offset: isize,
}
impl IrParameter {
pub fn new(name: &str, type_info: TypeInfo, stack_offset: isize) -> Self {
pub fn new(name: &str, type_info: IrTypeInfo, stack_offset: isize) -> Self {
Self {
name: name.into(),
type_info,
@ -18,7 +18,7 @@ impl IrParameter {
}
}
pub fn type_info(&self) -> &TypeInfo {
pub fn type_info(&self) -> &IrTypeInfo {
&self.type_info
}

View File

@ -1,5 +1,5 @@
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVirtualRegisterVariable};
use dvm_lib::instruction::Location;
use std::cell::RefCell;
use std::collections::HashSet;
@ -7,6 +7,7 @@ use std::fmt::{Display, Formatter};
use std::rc::Rc;
#[derive(Clone)]
#[deprecated]
pub enum IrParameterOrVariable {
IrParameter(Rc<IrParameter>),
Variable(Rc<RefCell<IrVariable>>),
@ -24,7 +25,7 @@ impl IrParameterOrVariable {
}
}
pub fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
pub fn vr_uses(&self) -> HashSet<IrVirtualRegisterVariable> {
if let IrParameterOrVariable::Variable(ir_variable) = &self {
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
ir_variable.borrow().descriptor()

View File

@ -1,38 +1,30 @@
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use std::cell::RefCell;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub struct IrReadField {
field_ref_variable: Rc<RefCell<IrVariable>>,
field_ref_variable: IrVariableId,
}
impl IrReadField {
pub fn new(field_ref_variable: Rc<RefCell<IrVariable>>) -> Self {
pub fn new(field_ref_variable: IrVariableId) -> Self {
Self { field_ref_variable }
}
pub fn field_ref_variable(&self) -> &Rc<RefCell<IrVariable>> {
&self.field_ref_variable
pub fn field_ref_variable(&self) -> IrVariableId {
self.field_ref_variable
}
}
impl VrUser for IrReadField {
fn vr_uses(&self) -> HashSet<IrVariableId> {
todo!()
}
}
impl Display for IrReadField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.field_ref_variable.borrow(),)
}
}
impl VrUser for IrReadField {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
let mut set = HashSet::new();
if let IrVariableDescriptor::VirtualRegister(vr_variable) =
self.field_ref_variable.borrow().descriptor()
{
set.insert(vr_variable.clone());
}
set
write!(f, "IrReadField({})", self.field_ref_variable)
}
}

View File

@ -1,9 +1,6 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use dvm_lib::instruction::Instruction;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
@ -15,10 +12,14 @@ impl IrReturn {
pub fn new(value: Option<IrExpression>) -> Self {
Self { value }
}
pub fn value(&self) -> Option<&IrExpression> {
self.value.as_ref()
}
}
impl VrUser for IrReturn {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
if let Some(ir_expression) = self.value.as_ref() {
ir_expression.vr_uses()
} else {
@ -27,16 +28,6 @@ impl VrUser for IrReturn {
}
}
impl Assemble for IrReturn {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
if let Some(ir_expression) = self.value.as_ref() {
let return_operand = ir_expression.return_operand(constants_table);
builder.push(Instruction::SetReturnValue(return_operand));
}
builder.push(Instruction::Return);
}
}
impl Display for IrReturn {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "return")?;

View File

@ -1,9 +1,6 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_variable::{IrVariable, IrVariableDescriptor, IrVrVariableDescriptor};
use crate::ir::ir_variable::{IrVariable, IrVariableId};
use crate::ir::register_allocation::VrUser;
use dvm_lib::instruction::Instruction;
use std::cell::RefCell;
use std::collections::HashSet;
use std::fmt::{Display, Formatter};
@ -24,26 +21,27 @@ impl IrSetField {
}
impl VrUser for IrSetField {
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
let mut set = HashSet::new();
match self.field_ref_variable.borrow().descriptor() {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
set.insert(vr_variable.clone());
}
IrVariableDescriptor::Stack(_) => {}
}
set.extend(self.initializer.vr_uses());
set
fn vr_uses(&self) -> HashSet<IrVariableId> {
// let mut set = HashSet::new();
// match self.field_ref_variable.borrow().descriptor() {
// IrVariableDescriptor::VirtualRegister(vr_variable) => {
// set.insert(vr_variable.clone());
// }
// IrVariableDescriptor::Stack(_) => {}
// }
// set.extend(self.initializer.vr_uses());
// set
todo!()
}
}
impl Assemble for IrSetField {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
let field_ref_location = self.field_ref_variable.borrow().descriptor().as_location();
let set_field_operand = self.initializer.set_field_operand(constants_table);
builder.push(Instruction::SetField(field_ref_location, set_field_operand));
}
}
// impl Assemble for IrSetField {
// fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
// let field_ref_location = self.field_ref_variable.borrow().descriptor().as_location();
// let set_field_operand = self.initializer.set_field_operand(constants_table);
// builder.push(Instruction::SetField(field_ref_location, set_field_operand));
// }
// }
impl Display for IrSetField {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {

View File

@ -1,14 +1,10 @@
use crate::constants_table::ConstantsTable;
use crate::ir::assemble::{Assemble, InstructionsBuilder};
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_call::IrCall;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_set_field::IrSetField;
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::VrUser;
use crate::offset_counter::OffsetCounter;
use std::collections::{HashMap, HashSet};
use std::fmt::{Display, Formatter};
use std::collections::HashSet;
pub enum IrStatement {
Assign(IrAssign),
@ -18,7 +14,7 @@ pub enum IrStatement {
}
impl VrUser for IrStatement {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_definitions(&self) -> HashSet<IrVariableId> {
match self {
IrStatement::Assign(ir_assign) => ir_assign.vr_definitions(),
IrStatement::Call(ir_call) => ir_call.vr_definitions(),
@ -27,7 +23,7 @@ impl VrUser for IrStatement {
}
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_uses(&self) -> HashSet<IrVariableId> {
match self {
IrStatement::Assign(ir_assign) => ir_assign.vr_uses(),
IrStatement::Call(ir_call) => ir_call.vr_uses(),
@ -35,96 +31,4 @@ impl VrUser for IrStatement {
IrStatement::SetField(ir_set_field) => ir_set_field.vr_uses(),
}
}
fn propagate_spills(&mut self, spills: &HashSet<IrVrVariableDescriptor>) {
match self {
IrStatement::Assign(ir_assign) => {
ir_assign.propagate_spills(spills);
}
IrStatement::Call(ir_call) => {
ir_call.propagate_spills(spills);
}
IrStatement::Return(ir_return) => {
ir_return.propagate_spills(spills);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_spills(spills);
}
}
}
fn propagate_register_assignments(
&mut self,
assignments: &HashMap<IrVrVariableDescriptor, usize>,
) {
match self {
IrStatement::Assign(ir_assign) => {
ir_assign.propagate_register_assignments(assignments);
}
IrStatement::Call(ir_call) => {
ir_call.propagate_register_assignments(assignments);
}
IrStatement::Return(ir_return) => {
ir_return.propagate_register_assignments(assignments);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_register_assignments(assignments);
}
}
}
fn propagate_stack_offsets(&mut self, counter: &mut OffsetCounter) {
match self {
IrStatement::Assign(ir_assign) => {
ir_assign.propagate_stack_offsets(counter);
}
IrStatement::Call(ir_call) => {
ir_call.propagate_stack_offsets(counter);
}
IrStatement::Return(ir_return) => {
ir_return.propagate_stack_offsets(counter);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.propagate_stack_offsets(counter);
}
}
}
}
impl Assemble for IrStatement {
fn assemble(&self, builder: &mut InstructionsBuilder, constants_table: &mut ConstantsTable) {
match self {
IrStatement::Assign(ir_assign) => {
ir_assign.assemble(builder, constants_table);
}
IrStatement::Call(ir_call) => {
ir_call.assemble(builder, constants_table);
}
IrStatement::Return(ir_return) => {
ir_return.assemble(builder, constants_table);
}
IrStatement::SetField(ir_set_field) => {
ir_set_field.assemble(builder, constants_table);
}
}
}
}
impl Display for IrStatement {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
IrStatement::Assign(ir_assign) => {
write!(f, "{}", ir_assign)
}
IrStatement::Call(ir_call) => {
write!(f, "{}", ir_call)
}
IrStatement::Return(ir_return) => {
write!(f, "{}", ir_return)
}
IrStatement::SetField(ir_set_field) => {
write!(f, "{}", ir_set_field)
}
}
}
}

View File

@ -0,0 +1,16 @@
use std::fmt::{Display, Formatter};
pub type IrTypeInfoId = usize;
#[derive(Debug)]
pub enum IrTypeInfo {
String,
Int,
Double,
}
impl Display for IrTypeInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@ -1,211 +1,33 @@
use crate::offset_counter::OffsetCounter;
use crate::type_info::TypeInfo;
use dvm_lib::instruction::Location;
use std::collections::HashSet;
use std::fmt::{Debug, Display, Formatter};
use std::hash::Hash;
use crate::ir::ir_type_info::IrTypeInfo;
use std::fmt::{Display, Formatter};
use std::rc::Rc;
pub type IrVariableId = usize;
pub struct IrVariable {
descriptor: IrVariableDescriptor,
type_info: TypeInfo,
name: Rc<str>,
type_info: IrTypeInfo,
}
impl IrVariable {
pub fn new_vr(name: Rc<str>, block_id: usize, type_info: &TypeInfo) -> Self {
pub fn new(name: &str, type_info: IrTypeInfo) -> Self {
Self {
descriptor: IrVariableDescriptor::VirtualRegister(IrVrVariableDescriptor::new(
name, block_id,
)),
type_info: type_info.clone(),
name: name.into(),
type_info,
}
}
pub fn new_stack_with_offset(
name: Rc<str>,
block_id: usize,
type_info: &TypeInfo,
offset: isize,
) -> Self {
let mut descriptor = IrStackVariableDescriptor::new(name, block_id);
descriptor.set_offset(offset);
Self {
descriptor: IrVariableDescriptor::Stack(descriptor),
type_info: type_info.clone(),
}
pub fn name(&self) -> &str {
&self.name
}
pub fn type_info(&self) -> &TypeInfo {
pub fn type_info(&self) -> &IrTypeInfo {
&self.type_info
}
pub fn descriptor(&self) -> &IrVariableDescriptor {
&self.descriptor
}
pub fn descriptor_mut(&mut self) -> &mut IrVariableDescriptor {
&mut self.descriptor
}
pub fn set_descriptor(&mut self, descriptor: IrVariableDescriptor) {
self.descriptor = descriptor;
}
}
impl Display for IrVariable {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.descriptor)
}
}
pub enum IrVariableDescriptor {
VirtualRegister(IrVrVariableDescriptor),
Stack(IrStackVariableDescriptor),
}
impl Display for IrVariableDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
IrVariableDescriptor::VirtualRegister(vr_variable) => {
write!(f, "{}", vr_variable)
}
IrVariableDescriptor::Stack(stack_variable) => {
write!(f, "{}", stack_variable)
}
}
}
}
impl IrVariableDescriptor {
pub fn name(&self) -> &str {
match self {
IrVariableDescriptor::VirtualRegister(vr_variable) => vr_variable.name(),
IrVariableDescriptor::Stack(stack_variable) => stack_variable.name(),
}
}
pub fn name_owned(&self) -> Rc<str> {
match self {
IrVariableDescriptor::VirtualRegister(vr_variable) => vr_variable.name_owned(),
IrVariableDescriptor::Stack(stack_variable) => stack_variable.name_owned(),
}
}
pub fn as_location(&self) -> Location {
match self {
IrVariableDescriptor::VirtualRegister(register_variable) => {
register_variable.as_location()
}
IrVariableDescriptor::Stack(stack_variable) => stack_variable.as_location(),
}
}
pub fn vr_variable_descriptors(&self) -> HashSet<IrVrVariableDescriptor> {
if let IrVariableDescriptor::VirtualRegister(register_variable) = self {
HashSet::from([register_variable.clone()])
} else {
HashSet::new()
}
}
}
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct IrVrVariableDescriptor {
name: Rc<str>,
block_id: usize,
assigned_register: Option<usize>,
}
impl IrVrVariableDescriptor {
pub fn new(name: Rc<str>, block_id: usize) -> Self {
Self {
name,
block_id,
assigned_register: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn block_id(&self) -> usize {
self.block_id
}
pub fn set_assigned_register(&mut self, register: usize) {
self.assigned_register = Some(register);
}
pub fn assigned_register(&self) -> usize {
self.assigned_register.unwrap()
}
pub fn as_location(&self) -> Location {
Location::Register(self.assigned_register.unwrap())
}
}
impl Display for IrVrVariableDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl Debug for IrVrVariableDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
pub struct IrStackVariableDescriptor {
name: Rc<str>,
block_id: usize,
offset: Option<isize>,
}
impl IrStackVariableDescriptor {
pub fn new(name: Rc<str>, block_id: usize) -> Self {
Self {
name,
block_id,
offset: None,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn name_owned(&self) -> Rc<str> {
self.name.clone()
}
pub fn use_next_offset(&mut self, offset_counter: &mut OffsetCounter) {
if self.offset.is_none() {
self.offset = Some(offset_counter.next() as isize);
}
}
pub fn set_offset(&mut self, offset: isize) {
self.offset = Some(offset);
}
pub fn offset(&self) -> isize {
self.offset.unwrap()
}
pub fn as_location(&self) -> Location {
Location::StackFrameOffset(self.offset.unwrap())
}
}
impl Display for IrStackVariableDescriptor {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}_{}", self.name, self.block_id)
}
}

View File

@ -1,4 +1,5 @@
mod assemble;
mod debug_print;
pub mod ir_allocate;
pub mod ir_assign;
pub mod ir_binary_operation;
@ -16,6 +17,8 @@ pub mod ir_read_field;
pub mod ir_return;
pub mod ir_set_field;
pub mod ir_statement;
pub mod ir_type_info;
pub mod ir_variable;
mod register_allocation;
mod util;
mod variable_locations;

View File

@ -1,158 +1,153 @@
use crate::ir::ir_variable::IrVrVariableDescriptor;
use crate::ir::ir_block::IrBlock;
use crate::ir::ir_variable::IrVariableId;
use crate::ir::variable_locations::VariableLocations;
use crate::offset_counter::OffsetCounter;
use dvm_lib::instruction::{Register, StackFrameOffset};
use std::collections::{HashMap, HashSet};
pub type InterferenceGraph = HashMap<IrVrVariableDescriptor, HashSet<IrVrVariableDescriptor>>;
pub type LivenessMap = HashMap<usize, HashSet<IrVrVariableDescriptor>>;
pub type RegisterAssignment = Register;
pub type InterferenceGraph = HashMap<IrVariableId, HashSet<IrVariableId>>;
pub type LivenessSets = Vec<HashSet<IrVariableId>>;
pub trait HasVrUsers {
fn vr_users(&self) -> Vec<&dyn VrUser>;
fn vr_users_mut(&mut self) -> Vec<&mut dyn VrUser>;
fn live_in_live_out(&self) -> (LivenessMap, LivenessMap) {
let mut live_in: LivenessMap = HashMap::new();
let mut live_out: LivenessMap = HashMap::new();
pub fn block_live_in_live_out(ir_block: &IrBlock) -> (LivenessSets, LivenessSets) {
// init
let n_statements = ir_block.statements().len();
let mut live_in: LivenessSets = vec![HashSet::new(); n_statements];
let mut live_out: LivenessSets = vec![HashSet::new(); n_statements];
loop {
let mut did_work = false;
for (block_index, statement) in self.vr_users().iter().enumerate().rev() {
// init if necessary
if !live_in.contains_key(&block_index) {
live_in.insert(block_index, HashSet::new());
}
if !live_out.contains_key(&block_index) {
live_out.insert(block_index, HashSet::new());
}
// Go backwards for efficiency
for (statement_index, ir_statement) in ir_block.statements().iter().enumerate().rev() {
// out (union of successors ins)
// for now, a statement can only have one successor
// this will need to be updated when we add jumps
if let Some(successor_live_in) = live_in.get(&(block_index + 1)) {
let statement_live_out = live_out.get_mut(&block_index).unwrap();
for vr_variable in successor_live_in {
if statement_live_out.insert(vr_variable.clone()) {
let statement_live_out = &mut live_out[statement_index];
let successor_live_in = &live_in[statement_index + 1];
for ir_variable_id in successor_live_in {
if statement_live_out.insert(*ir_variable_id) {
did_work = true;
}
}
}
// in: use(s) U ( out(s) - def(s) )
let mut new_ins = statement
let use_s = ir_statement
.vr_uses()
.iter()
.map(|u| (*u).clone())
.map(|u| *u)
.collect::<HashSet<_>>();
let statement_live_out = live_out.get(&block_index).unwrap();
let defs = statement
let out_s = &live_out[statement_index];
let def_s = ir_statement
.vr_definitions()
.iter()
.map(|d| (*d).clone())
.map(|d| *d)
.collect::<HashSet<_>>();
let rhs = statement_live_out - &defs;
new_ins.extend(rhs);
let rhs = out_s - &def_s;
let new_ins = use_s.union(&rhs).map(|v| *v).collect::<HashSet<_>>();
let statement_live_in = live_in.get_mut(&block_index).unwrap();
// add new ins to statement's live in
let statement_live_in = &mut live_in[statement_index];
for new_in in new_ins {
if statement_live_in.insert(new_in) {
// if we added a previously unadded variable, we did work
did_work = true;
}
}
}
// break only if we did nothing!
if !did_work {
break;
}
}
(live_in, live_out)
}
pub fn block_interference_graph(
ir_block: &IrBlock,
spilled: &HashSet<IrVariableId>,
) -> InterferenceGraph {
// create a set of all variables used in the block
let mut all_vr_variables: HashSet<IrVariableId> = HashSet::new();
for statement in ir_block.statements() {
all_vr_variables.extend(statement.vr_definitions());
all_vr_variables.extend(statement.vr_uses());
}
fn interference_graph(&self) -> InterferenceGraph {
let mut all_vr_variables: HashSet<IrVrVariableDescriptor> = HashSet::new();
for vr_user in self.vr_users() {
all_vr_variables.extend(vr_user.vr_definitions());
all_vr_variables.extend(vr_user.vr_uses());
}
// create graph and init all variables' outgoing-edge sets
let mut graph: InterferenceGraph = HashMap::new();
for variable in all_vr_variables {
graph.insert(variable, HashSet::new());
}
let (_, live_out) = self.live_in_live_out();
let (_, live_out) = block_live_in_live_out(ir_block);
for (index, vr_user) in self.vr_users().iter().enumerate() {
let user_live_in = live_out.get(&index).unwrap();
for definition_vr_variable in vr_user.vr_definitions() {
for live_out_variable in user_live_in {
for (statement_index, ir_statement) in ir_block.statements().iter().enumerate() {
let statement_live_out = &live_out[statement_index];
for definition_vr_variable in ir_statement.vr_definitions() {
for live_out_variable in statement_live_out {
// we do the following check to avoid adding an edge to itself
if definition_vr_variable.ne(live_out_variable) {
graph
.get_mut(&definition_vr_variable)
.unwrap()
.insert(live_out_variable.clone());
graph
.get_mut(live_out_variable)
.unwrap()
.insert(definition_vr_variable.clone());
if definition_vr_variable != *live_out_variable {
// add edges to both sets (two-way)
let definition_edges = graph.get_mut(&definition_vr_variable).unwrap();
definition_edges.insert(*live_out_variable);
let live_out_variable_edges = graph.get_mut(live_out_variable).unwrap();
live_out_variable_edges.insert(definition_vr_variable);
}
}
}
}
graph
}
}
fn assign_registers(
&mut self,
register_count: usize,
offset_counter: &mut OffsetCounter,
) -> HashMap<IrVrVariableDescriptor, usize> {
let mut spills: HashSet<IrVrVariableDescriptor> = HashSet::new();
pub fn block_assign_registers(ir_block: &IrBlock, register_count: usize) -> VariableLocations {
let mut spilled: HashSet<IrVariableId> = HashSet::new();
loop {
let mut interference_graph = self.interference_graph();
let (registers, new_spills) =
registers_and_spills(&mut interference_graph, register_count);
let mut interference_graph = block_interference_graph(ir_block, &spilled);
let (registers, new_spills) = registers_and_spills(&mut interference_graph, register_count);
if spills != new_spills {
spills = new_spills;
// propagate all spills, since those won't be used for the next interference graph
for vr_user in &mut self.vr_users_mut() {
vr_user.propagate_spills(&spills);
}
if spilled != new_spills {
spilled = new_spills; // todo: figure out if this works algorithmically
} else {
// we've calculated final assignments, so propagate them
for vr_user in self.vr_users_mut() {
vr_user.propagate_register_assignments(&registers);
}
// also set offsets
for vr_user in self.vr_users_mut() {
vr_user.propagate_stack_offsets(offset_counter);
}
return registers;
let mut spills_to_stack_offsets: HashMap<IrVariableId, StackFrameOffset> =
HashMap::new();
let mut offset_counter = 0isize;
for spill in spilled {
spills_to_stack_offsets.insert(spill, offset_counter);
offset_counter += 1;
}
return VariableLocations::new(registers, spills_to_stack_offsets);
}
}
}
pub trait HasVrUsers {
fn vr_users(&self) -> Vec<&dyn VrUser>;
fn vr_users_mut(&mut self) -> Vec<&mut dyn VrUser>;
}
pub trait VrUser {
fn vr_definitions(&self) -> HashSet<IrVrVariableDescriptor> {
fn vr_definitions(&self) -> HashSet<IrVariableId> {
HashSet::new()
}
fn vr_uses(&self) -> HashSet<IrVrVariableDescriptor>;
#[deprecated]
fn vr_uses(&self) -> HashSet<IrVariableId>;
fn propagate_spills(&mut self, _spills: &HashSet<IrVrVariableDescriptor>) {
#[deprecated]
fn propagate_spills(&mut self, _spills: &HashSet<IrVariableId>) {
// default no-op
}
#[deprecated]
fn propagate_register_assignments(
&mut self,
_assignments: &HashMap<IrVrVariableDescriptor, usize>,
_assignments: &HashMap<IrVariableId, RegisterAssignment>,
) {
// default no-op
}
#[deprecated]
fn propagate_stack_offsets(&mut self, _counter: &mut OffsetCounter) {
// default no-op
}
@ -160,8 +155,8 @@ pub trait VrUser {
#[derive(Debug)]
struct WorkItem {
vr: IrVrVariableDescriptor,
edges: HashSet<IrVrVariableDescriptor>,
vr: IrVariableId,
edges: HashSet<IrVariableId>,
color: bool,
}
@ -169,8 +164,8 @@ pub fn registers_and_spills(
interference_graph: &mut InterferenceGraph,
k: usize,
) -> (
HashMap<IrVrVariableDescriptor, usize>,
HashSet<IrVrVariableDescriptor>,
HashMap<IrVariableId, RegisterAssignment>,
HashSet<IrVariableId>,
) {
let mut work_stack: Vec<WorkItem> = vec![];
@ -180,8 +175,8 @@ pub fn registers_and_spills(
// 3. assign colors to registers
let mut rebuilt_graph: InterferenceGraph = HashMap::new();
let mut register_assignments: HashMap<IrVrVariableDescriptor, usize> = HashMap::new();
let mut spills: HashSet<IrVrVariableDescriptor> = HashSet::new();
let mut register_assignments: HashMap<IrVariableId, RegisterAssignment> = HashMap::new();
let mut spills: HashSet<IrVariableId> = HashSet::new();
while let Some(work_item) = work_stack.pop() {
if work_item.color {
@ -201,7 +196,7 @@ fn assign_register(
work_item: &WorkItem,
graph: &mut InterferenceGraph,
k: usize,
register_assignments: &mut HashMap<IrVrVariableDescriptor, usize>,
register_assignments: &mut HashMap<IrVariableId, RegisterAssignment>,
) {
rebuild_vr_and_edges(graph, work_item);
@ -220,21 +215,19 @@ fn assign_register(
}
}
fn find_vr_lt_k(
interference_graph: &InterferenceGraph,
k: usize,
) -> Option<&IrVrVariableDescriptor> {
fn find_vr_lt_k(interference_graph: &InterferenceGraph, k: usize) -> Option<IrVariableId> {
interference_graph.iter().find_map(
|(vr, neighbors)| {
if neighbors.len() < k { Some(vr) } else { None }
if neighbors.len() < k { Some(*vr) } else { None }
},
)
}
/// Returns the (removed) outgoing edges for the given vr
fn remove_vr_and_edges(
interference_graph: &mut InterferenceGraph,
vr: &IrVrVariableDescriptor,
) -> HashSet<IrVrVariableDescriptor> {
vr: &IrVariableId,
) -> HashSet<IrVariableId> {
// first, outgoing
let outgoing_edges = interference_graph.remove(vr).unwrap();
@ -311,7 +304,7 @@ fn rebuild_vr_and_edges(graph: &mut InterferenceGraph, work_item: &WorkItem) {
fn can_optimistically_color(
work_item: &WorkItem,
register_assignments: &HashMap<IrVrVariableDescriptor, usize>,
register_assignments: &HashMap<IrVariableId, usize>,
k: usize,
) -> bool {
// see if we can optimistically color
@ -332,38 +325,29 @@ mod tests {
fn line_graph() -> InterferenceGraph {
let mut graph: InterferenceGraph = HashMap::new();
let v0 = IrVrVariableDescriptor::new("v0".into(), 0);
let v1 = IrVrVariableDescriptor::new("v1".into(), 0);
let v2 = IrVrVariableDescriptor::new("v2".into(), 0);
// v1 -- v0 -- v2
graph.insert(v0.clone(), HashSet::from([v1.clone(), v2.clone()]));
graph.insert(v1.clone(), HashSet::from([v0.clone()]));
graph.insert(v2.clone(), HashSet::from([v0.clone()]));
graph.insert(0, HashSet::from([1, 2]));
graph.insert(1, HashSet::from([0]));
graph.insert(2, HashSet::from([0]));
graph
}
fn triangle_graph() -> InterferenceGraph {
let mut graph: InterferenceGraph = HashMap::new();
let v0 = IrVrVariableDescriptor::new("v0".into(), 0);
let v1 = IrVrVariableDescriptor::new("v1".into(), 0);
let v2 = IrVrVariableDescriptor::new("v2".into(), 0);
// triangle: each has two edges
// v0
// | \
// v1--v2
graph.insert(v0.clone(), HashSet::from([v1.clone(), v2.clone()]));
graph.insert(v1.clone(), HashSet::from([v0.clone(), v2.clone()]));
graph.insert(v2.clone(), HashSet::from([v0.clone(), v1.clone()]));
graph.insert(0, HashSet::from([1, 2]));
graph.insert(1, HashSet::from([0, 2]));
graph.insert(2, HashSet::from([0, 1]));
graph
}
fn get_vrs(graph: &InterferenceGraph) -> Vec<IrVrVariableDescriptor> {
let v0 = graph.keys().find(|k| k.name() == "v0").unwrap();
let v1 = graph.keys().find(|k| k.name() == "v1").unwrap();
let v2 = graph.keys().find(|k| k.name() == "v2").unwrap();
vec![v0.clone(), v1.clone(), v2.clone()]
fn get_vrs() -> Vec<IrVariableId> {
vec![0, 1, 2]
}
#[test]
@ -371,7 +355,7 @@ mod tests {
let graph = line_graph();
let found = find_vr_lt_k(&graph, 2);
assert!(found.is_some());
assert!(found.unwrap().name() == "v1" || found.unwrap().name() == "v2");
assert!(found.unwrap() == 1 || found.unwrap() == 2);
}
#[test]
@ -384,7 +368,7 @@ mod tests {
#[test]
fn remove_edges_v0() {
let mut graph = line_graph();
let vrs = get_vrs(&graph);
let vrs = get_vrs();
let v0_outgoing = remove_vr_and_edges(&mut graph, &vrs[0]);
assert!(v0_outgoing.contains(&vrs[1]));
@ -437,7 +421,7 @@ mod tests {
}
// we should have a triangle graph again
let vrs = get_vrs(&rebuilt_graph);
let vrs = get_vrs();
for vr in &vrs {
assert!(rebuilt_graph.contains_key(vr));
assert_eq!(rebuilt_graph.get(vr).unwrap().len(), 2);

View File

@ -1,22 +1,15 @@
use crate::ir::ir_variable::{
IrStackVariableDescriptor, IrVariable, IrVariableDescriptor, IrVrVariableDescriptor,
};
use std::cell::RefCell;
use crate::ir::ir_variable::IrVariableId;
use std::collections::HashSet;
use std::rc::Rc;
pub fn propagate_spills(
ir_variable: &mut Rc<RefCell<IrVariable>>,
spills: &HashSet<IrVrVariableDescriptor>,
target_ir_variable: IrVariableId,
register_variables: &mut HashSet<IrVariableId>,
stack_variables: &mut HashSet<IrVariableId>,
new_spills: &HashSet<IrVariableId>,
) {
let mut borrowed_ir_variable = ir_variable.borrow_mut();
if let IrVariableDescriptor::VirtualRegister(vr_variable) = borrowed_ir_variable.descriptor() {
if spills.contains(vr_variable) {
let name = vr_variable.name_owned();
let block_id = vr_variable.block_id();
borrowed_ir_variable.set_descriptor(IrVariableDescriptor::Stack(
IrStackVariableDescriptor::new(name, block_id),
));
}
if new_spills.contains(&target_ir_variable) && register_variables.contains(&target_ir_variable)
{
register_variables.remove(&target_ir_variable);
stack_variables.insert(target_ir_variable);
}
}

View File

@ -0,0 +1,41 @@
use crate::ir::ir_variable::IrVariableId;
use crate::ir::register_allocation::RegisterAssignment;
use dvm_lib::instruction::StackFrameOffset;
use std::collections::HashMap;
pub enum VariableLocation {
Register(RegisterAssignment),
Stack(StackFrameOffset),
}
pub struct VariableLocations {
register_variables: HashMap<IrVariableId, RegisterAssignment>,
stack_variables: HashMap<IrVariableId, StackFrameOffset>,
}
impl VariableLocations {
pub fn new(
register_variables: HashMap<IrVariableId, RegisterAssignment>,
stack_variables: HashMap<IrVariableId, StackFrameOffset>,
) -> Self {
Self {
register_variables,
stack_variables,
}
}
pub fn get_variable_location(&self, id: &IrVariableId) -> VariableLocation {
match self.register_variables.get(id) {
Some(register_assignment) => VariableLocation::Register(*register_assignment),
None => VariableLocation::Stack(self.stack_variables[id]),
}
}
pub fn register_variables_count(&self) -> usize {
self.register_variables.len()
}
pub fn stack_size(&self) -> usize {
self.stack_variables.len()
}
}

View File

@ -7,6 +7,7 @@ pub mod error_codes;
pub mod intrinsics;
pub mod ir;
pub mod lexer;
pub mod lowering;
pub mod offset_counter;
pub mod parser;
pub mod scope;

426
dmc-lib/src/lowering/mod.rs Normal file
View File

@ -0,0 +1,426 @@
mod util;
use crate::ast::NodeId;
use crate::ast::assign_statement::AssignStatement;
use crate::ast::binary_expression::BinaryOperation;
use crate::ast::call::Call;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::expression::Expression;
use crate::ast::expression_statement::ExpressionStatement;
use crate::ast::function::Function;
use crate::ast::let_statement::LetStatement;
use crate::ast::statement::Statement;
use crate::ir::ir_assign::IrAssign;
use crate::ir::ir_binary_operation::{IrBinaryOperation, IrBinaryOperator};
use crate::ir::ir_block::{IrBlock, IrBlockId};
use crate::ir::ir_call::IrCall;
use crate::ir::ir_expression::IrExpression;
use crate::ir::ir_function::IrFunction;
use crate::ir::ir_operation::IrOperation;
use crate::ir::ir_parameter::IrParameter;
use crate::ir::ir_return::IrReturn;
use crate::ir::ir_statement::IrStatement;
use crate::ir::ir_type_info::IrTypeInfo;
use crate::ir::ir_variable::{IrVariable, IrVariableId};
use crate::lowering::util::{return_type_info_to_ir_type_info, to_ir_type_info};
use crate::semantic_analysis::symbol::{Symbol, SymbolId};
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
use std::ops::Neg;
use std::rc::Rc;
pub struct LowerToIrResult {
pub functions: Vec<IrFunction>,
}
struct LowerToIrContext<'a> {
symbols: &'a [Symbol],
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a [TypeInfo],
symbols_to_type_infos: &'a HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &'a HashMap<NodeId, TypeInfoId>,
ir_functions: Vec<IrFunction>,
}
pub fn lower_to_ir(
compilation_unit: &CompilationUnit,
symbols: &[Symbol],
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
type_infos: &[TypeInfo],
symbols_to_type_infos: &HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: &HashMap<NodeId, TypeInfoId>,
) -> LowerToIrResult {
let mut ctx = LowerToIrContext {
symbols,
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos,
ir_functions: Vec::new(),
};
for function in compilation_unit.functions() {
lower_to_ir_function(function, &mut ctx);
}
LowerToIrResult {
functions: ctx.ir_functions,
}
}
struct LowerToIrFunctionContext {
blocks: Vec<IrBlock>,
ir_variables: Vec<IrVariable>,
symbols_to_variables: HashMap<SymbolId, IrVariableId>,
current_block_statements: Vec<IrStatement>,
t_var_counter: usize,
}
impl LowerToIrFunctionContext {
fn new() -> Self {
Self {
blocks: Vec::new(),
ir_variables: Vec::new(),
symbols_to_variables: HashMap::new(),
current_block_statements: Vec::new(),
t_var_counter: 0,
}
}
fn current_block_id(&self) -> IrBlockId {
self.blocks.len()
}
fn finish_block(&mut self) {
let current_block_id = self.current_block_id();
let ir_block = IrBlock::new(
current_block_id,
&format!("#{}", current_block_id),
std::mem::take(&mut self.current_block_statements),
);
self.blocks.push(ir_block);
}
fn insert_ir_variable(
&mut self,
ir_variable: IrVariable,
maybe_associated_symbol_id: Option<SymbolId>,
) -> IrVariableId {
self.ir_variables.push(ir_variable);
let ir_variable_id = self.ir_variables.len() - 1;
if let Some(associated_symbol_id) = maybe_associated_symbol_id {
self.symbols_to_variables
.insert(associated_symbol_id, ir_variable_id);
}
ir_variable_id
}
fn make_t_var(&mut self, ir_type_info: IrTypeInfo) -> IrVariableId {
let t_var_id = self.t_var_counter;
self.t_var_counter += 1;
let t_var_ir_variable = IrVariable::new(&format!("t_{}", t_var_id), ir_type_info);
self.insert_ir_variable(t_var_ir_variable, None)
}
}
fn lower_to_ir_function(function: &Function, ctx: &mut LowerToIrContext) {
fn lower_to_ir_parameters(function: &Function, ctx: &mut LowerToIrContext) -> Vec<IrParameter> {
let mut ir_parameters = Vec::new();
let n_parameters = function.parameters().len() as isize;
for (i, parameter) in function.parameters().iter().enumerate() {
let parameter_type_info_id = ctx.nodes_to_type_infos[&parameter.node_id()];
let parameter_type_info = &ctx.type_infos[parameter_type_info_id];
let ir_parameter = IrParameter::new(
parameter.declared_name(),
to_ir_type_info(parameter_type_info),
n_parameters.neg() + (i as isize),
);
ir_parameters.push(ir_parameter);
}
ir_parameters
}
let mut fn_ctx = LowerToIrFunctionContext::new();
let n_statements = function.statements().len();
for (i, statement) in function.statements().iter().enumerate() {
let can_return_value = i == n_statements - 1;
lower_to_ir_statement(statement, ctx, &mut fn_ctx, can_return_value);
}
fn_ctx.finish_block();
let function_symbol_id = ctx.nodes_to_symbols[&function.node_id()];
let function_symbol = match &ctx.symbols[function_symbol_id] {
Symbol::Function(function_symbol) => function_symbol,
_ => panic!("Expected function symbol"),
};
let return_type_info_id = ctx.symbols_to_type_infos[&function_symbol_id];
let return_type_info = &ctx.type_infos[return_type_info_id];
let ir_function = IrFunction::new(
function_symbol.fqn_owned(),
lower_to_ir_parameters(function, ctx)
.into_iter()
.map(|ir_parameter| Rc::new(ir_parameter))
.collect(),
fn_ctx.ir_variables,
return_type_info_to_ir_type_info(return_type_info),
fn_ctx.blocks,
);
ctx.ir_functions.push(ir_function);
}
fn lower_to_ir_statement(
statement: &Statement,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
can_return_value: bool,
) {
match statement {
Statement::Let(let_statement) => {
lower_to_ir_let_statement(let_statement, ctx, fn_ctx);
}
Statement::Expression(expression_statement) => {
lower_to_ir_expression_statement(expression_statement, ctx, fn_ctx, can_return_value);
}
Statement::Assign(assign_statement) => {
lower_to_ir_assign_statement(assign_statement, ctx, fn_ctx);
}
}
}
fn lower_binary_operator(binary_operation: &BinaryOperation) -> IrBinaryOperator {
match binary_operation {
BinaryOperation::Multiply => IrBinaryOperator::Multiply,
BinaryOperation::Divide => IrBinaryOperator::Divide,
BinaryOperation::Modulo => IrBinaryOperator::Modulo,
BinaryOperation::Add => IrBinaryOperator::Add,
BinaryOperation::Subtract => IrBinaryOperator::Subtract,
BinaryOperation::LeftShift => IrBinaryOperator::LeftShift,
BinaryOperation::RightShift => IrBinaryOperator::RightShift,
BinaryOperation::BitwiseAnd => IrBinaryOperator::BitwiseAnd,
BinaryOperation::BitwiseXor => IrBinaryOperator::BitwiseXor,
BinaryOperation::BitwiseOr => IrBinaryOperator::BitwiseOr,
}
}
fn lower_to_ir_let_statement(
let_statement: &LetStatement,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
) {
let symbol_id = ctx.nodes_to_symbols[&let_statement.node_id()];
let type_info_id = ctx.nodes_to_type_infos[&let_statement.node_id()];
let type_info = &ctx.type_infos[type_info_id];
let ir_variable = IrVariable::new(let_statement.declared_name(), to_ir_type_info(type_info));
let ir_variable_id = fn_ctx.insert_ir_variable(ir_variable, Some(symbol_id));
let initializer_ir_operation =
lower_expression_to_ir_operation(let_statement.initializer(), ctx, fn_ctx);
let ir_assign = IrAssign::new(ir_variable_id, initializer_ir_operation);
let ir_statement = IrStatement::Assign(ir_assign);
fn_ctx.current_block_statements.push(ir_statement);
}
fn lower_to_ir_expression_statement(
expression_statement: &ExpressionStatement,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
returns_value: bool,
) {
if returns_value {
let ir_expression =
lower_expression_to_ir_expression(expression_statement.expression(), ctx, fn_ctx);
let ir_statement = IrStatement::Return(IrReturn::new(Some(ir_expression)));
fn_ctx.current_block_statements.push(ir_statement);
} else {
// we could try to filter out useless code, but it's not guaranteed that there aren't
// intended side effects from the lowering, so, for now, let's throw the result in a t_var.
// in the future we may want to somehow enforce with the type system a way that we can't
// create useless, dead ir code
let ir_operation =
lower_expression_to_ir_operation(expression_statement.expression(), ctx, fn_ctx);
let result_type_info_id =
ctx.nodes_to_type_infos[&expression_statement.expression().node_id()];
let result_type_info = &ctx.type_infos[result_type_info_id];
let t_var_ir_variable_id = fn_ctx.make_t_var(to_ir_type_info(result_type_info));
let ir_statement = IrStatement::Assign(IrAssign::new(t_var_ir_variable_id, ir_operation));
fn_ctx.current_block_statements.push(ir_statement);
}
}
fn lower_to_ir_assign_statement(
assign_statement: &AssignStatement,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
) {
match assign_statement.destination() {
Expression::Identifier(identifier) => {
let destination_symbol_id = ctx.nodes_to_symbols[&identifier.node_id()];
let destination_ir_variable_id = fn_ctx.symbols_to_variables[&destination_symbol_id];
let ir_operation =
lower_expression_to_ir_operation(assign_statement.value(), ctx, fn_ctx);
let ir_assign = IrAssign::new(destination_ir_variable_id, ir_operation);
let ir_statement = IrStatement::Assign(ir_assign);
fn_ctx.current_block_statements.push(ir_statement);
}
_ => panic!("Assigning to non-Identifiers is not yet supported."),
}
}
fn lower_expression_to_ir_operation(
expression: &Expression,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
) -> IrOperation {
match expression {
Expression::Binary(binary_expression) => {
let lhs = lower_expression_to_ir_expression(binary_expression.lhs(), ctx, fn_ctx);
let rhs = lower_expression_to_ir_expression(binary_expression.rhs(), ctx, fn_ctx);
IrOperation::Binary(IrBinaryOperation::new(
lhs,
rhs,
lower_binary_operator(binary_expression.op()),
))
}
Expression::Negative(negative_expression) => {
let operand =
lower_expression_to_ir_expression(negative_expression.operand(), ctx, fn_ctx);
IrOperation::Binary(IrBinaryOperation::new(
operand,
IrExpression::Int(-1),
IrBinaryOperator::Multiply,
))
}
Expression::Call(call) => IrOperation::Call(lower_to_ir_call(call, ctx, fn_ctx)),
Expression::Identifier(identifier) => {
let identifier_symbol_id = ctx.nodes_to_symbols[&identifier.node_id()];
let identifier_ir_variable_id = fn_ctx.symbols_to_variables[&identifier_symbol_id];
let ir_expression = IrExpression::Variable(identifier_ir_variable_id);
IrOperation::Load(ir_expression)
}
Expression::Integer(integer_literal) => {
IrOperation::Load(IrExpression::Int(integer_literal.value()))
}
Expression::Double(double_literal) => {
IrOperation::Load(IrExpression::Double(double_literal.value()))
}
Expression::String(string_literal) => {
IrOperation::Load(IrExpression::String(string_literal.content().into()))
}
}
}
fn lower_expression_to_ir_expression(
expression: &Expression,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
) -> IrExpression {
match expression {
Expression::Binary(binary_expression) => {
// make binary operation
let lhs = lower_expression_to_ir_expression(binary_expression.lhs(), ctx, fn_ctx);
let rhs = lower_expression_to_ir_expression(binary_expression.rhs(), ctx, fn_ctx);
let ir_operation = IrOperation::Binary(IrBinaryOperation::new(
lhs,
rhs,
lower_binary_operator(binary_expression.op()),
));
// make destination temp var
let result_type_info_id = ctx.nodes_to_type_infos[&binary_expression.node_id()];
let result_type_info = &ctx.type_infos[result_type_info_id];
let destination_ir_variable_id = fn_ctx.make_t_var(to_ir_type_info(result_type_info));
// make assign statement to destination temp var
let ir_assign = IrAssign::new(destination_ir_variable_id, ir_operation);
fn_ctx
.current_block_statements
.push(IrStatement::Assign(ir_assign));
// return location of temp var
IrExpression::Variable(destination_ir_variable_id)
}
Expression::Negative(negative_expression) => {
let operand =
lower_expression_to_ir_expression(negative_expression.operand(), ctx, fn_ctx);
let negative_one = IrExpression::Int(-1);
let ir_operation = IrOperation::Binary(IrBinaryOperation::new(
operand,
negative_one,
IrBinaryOperator::Multiply,
));
let result_type_info_id = ctx.nodes_to_type_infos[&negative_expression.node_id()];
let result_type_info = &ctx.type_infos[result_type_info_id];
let destination_ir_variable_id = fn_ctx.make_t_var(to_ir_type_info(result_type_info));
let ir_assign = IrAssign::new(destination_ir_variable_id, ir_operation);
// push the statement which does the multiply by negative one
fn_ctx
.current_block_statements
.push(IrStatement::Assign(ir_assign));
IrExpression::Variable(destination_ir_variable_id)
}
Expression::Call(call) => {
let ir_call = lower_to_ir_call(call, ctx, fn_ctx);
// make temp var
let return_type_info_id = ctx.nodes_to_type_infos[&call.node_id()];
let return_type_info = &ctx.type_infos[return_type_info_id];
let return_ir_type_info = to_ir_type_info(return_type_info);
let t_var_ir_variable_id = fn_ctx.make_t_var(return_ir_type_info);
// assign call to temp var, return temp var expression
let ir_operation = IrOperation::Call(ir_call);
let ir_assign = IrAssign::new(t_var_ir_variable_id, ir_operation);
fn_ctx
.current_block_statements
.push(IrStatement::Assign(ir_assign));
// return an expression referencing the temp var
IrExpression::Variable(t_var_ir_variable_id)
}
Expression::Identifier(identifier) => {
let rhs_symbol_id = ctx.nodes_to_symbols[&identifier.node_id()];
let rhs_ir_variable_id = fn_ctx.symbols_to_variables[&rhs_symbol_id];
IrExpression::Variable(rhs_ir_variable_id)
}
Expression::Integer(integer_literal) => IrExpression::Int(integer_literal.value()),
Expression::Double(double_literal) => IrExpression::Double(double_literal.value()),
Expression::String(string_literal) => IrExpression::String(string_literal.content().into()),
}
}
fn lower_to_ir_call(
call: &Call,
ctx: &LowerToIrContext,
fn_ctx: &mut LowerToIrFunctionContext,
) -> IrCall {
let callee_symbol_id = ctx.nodes_to_symbols[&call.callee().node_id()];
let callee_symbol = &ctx.symbols[callee_symbol_id];
match callee_symbol {
Symbol::Function(function_symbol) => {
let arguments = call
.arguments()
.iter()
.map(|e| lower_expression_to_ir_expression(e, ctx, fn_ctx))
.collect();
IrCall::new(
function_symbol.fqn_owned(),
arguments,
function_symbol.is_extern(),
)
}
_ => panic!("Expected function symbol"),
}
}

View File

@ -0,0 +1,23 @@
use crate::ir::ir_type_info::IrTypeInfo;
use crate::semantic_analysis::type_info::TypeInfo;
pub fn to_ir_type_info(sa_type_info: &TypeInfo) -> IrTypeInfo {
match sa_type_info {
TypeInfo::String => IrTypeInfo::String,
TypeInfo::Int => IrTypeInfo::Int,
TypeInfo::Double => IrTypeInfo::Double,
_ => {
panic!()
}
}
}
pub fn return_type_info_to_ir_type_info(return_type_info: &TypeInfo) -> Option<IrTypeInfo> {
match return_type_info {
TypeInfo::String | TypeInfo::Int | TypeInfo::Double => {
Some(to_ir_type_info(return_type_info))
}
TypeInfo::Void => None,
_ => panic!(),
}
}

View File

@ -1674,7 +1674,7 @@ mod concrete_tests {
}
let arguments = call.arguments();
assert_eq!(arguments.len(), 1);
let first_argument = arguments[0];
let first_argument = &arguments[0];
match first_argument {
Expression::String(s) => {
assert_eq!(s.content(), "Hello, World!");

View File

@ -14,7 +14,12 @@ use crate::ast::statement::Statement;
use crate::semantic_analysis::scope::{Scope, ScopeId};
use std::collections::HashMap;
pub struct ScopeCollectionContext {
pub struct ScopeCollectionResult {
pub scopes: Vec<Scope>,
pub nodes_to_scopes: HashMap<NodeId, ScopeId>,
}
struct ScopeCollectionContext {
scopes: Vec<Scope>,
current_scope_id: Option<ScopeId>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
@ -36,24 +41,19 @@ impl ScopeCollectionContext {
}
pub fn pop_scope(&mut self) {
let popped_scope = self.scopes.pop().unwrap();
self.current_scope_id = popped_scope.parent_id();
self.current_scope_id = self.scopes[self.current_scope_id.unwrap()].parent_id();
}
pub fn current_scope_id(&self) -> Option<ScopeId> {
self.current_scope_id
}
pub fn nodes_to_scopes(&self) -> &HashMap<NodeId, ScopeId> {
&self.nodes_to_scopes
}
pub fn nodes_to_scopes_mut(&mut self) -> &mut HashMap<NodeId, ScopeId> {
&mut self.nodes_to_scopes
}
}
pub fn collect_scopes(compilation_unit: &CompilationUnit) -> ScopeCollectionContext {
pub fn collect_scopes(compilation_unit: &CompilationUnit) -> ScopeCollectionResult {
let mut ctx = ScopeCollectionContext::new();
ctx.push_scope(Scope::new(None));
for function in compilation_unit.functions() {
@ -63,7 +63,10 @@ pub fn collect_scopes(compilation_unit: &CompilationUnit) -> ScopeCollectionCont
collect_scopes_extern_function(extern_function, &mut ctx);
}
ctx.pop_scope();
ctx
ScopeCollectionResult {
scopes: ctx.scopes,
nodes_to_scopes: ctx.nodes_to_scopes,
}
}
fn collect_scopes_function(function: &Function, ctx: &mut ScopeCollectionContext) {

View File

@ -8,19 +8,27 @@ use crate::semantic_analysis::diagnostic_helpers::symbol_already_declared;
use crate::semantic_analysis::scope::{Scope, ScopeId};
use crate::semantic_analysis::symbol::{FunctionSymbol, ParameterSymbol, Symbol};
use std::collections::HashMap;
use std::rc::Rc;
pub struct SymbolCollectionContext {
scopes: Vec<Scope>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
pub struct SymbolCollectionResult {
pub symbols: Vec<Symbol>,
pub diagnostics: Diagnostics,
}
struct SymbolCollectionContext<'a> {
scopes: &'a mut Vec<Scope>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
fqn_context: Vec<Rc<str>>,
symbols: Vec<Symbol>,
diagnostics: Diagnostics,
}
impl SymbolCollectionContext {
pub fn new(scopes: Vec<Scope>, nodes_to_scopes: HashMap<NodeId, ScopeId>) -> Self {
impl<'a> SymbolCollectionContext<'a> {
pub fn new(scopes: &'a mut Vec<Scope>, nodes_to_scopes: &'a HashMap<NodeId, ScopeId>) -> Self {
Self {
scopes,
nodes_to_scopes,
fqn_context: Vec::new(),
symbols: Vec::new(),
diagnostics: Diagnostics::new(),
}
@ -45,26 +53,56 @@ impl SymbolCollectionContext {
scope.symbols_mut().insert(declared_name, symbol_id);
}
pub fn symbols_mut(&mut self) -> &mut Vec<Symbol> {
&mut self.symbols
}
pub fn diagnostics_mut(&mut self) -> &mut Diagnostics {
&mut self.diagnostics
}
fn join_fqn_parts(parts: &[Rc<str>]) -> String {
parts.join("::")
}
pub fn get_fqn_base(&self) -> String {
Self::join_fqn_parts(&self.fqn_context)
}
pub fn resolve_fqn(&self, suffix: &Rc<str>) -> String {
Self::join_fqn_parts(&[self.get_fqn_base().into(), suffix.clone()])
}
}
pub fn collect_symbols(compilation_unit: &CompilationUnit, ctx: &mut SymbolCollectionContext) {
pub fn collect_symbols(
compilation_unit: &CompilationUnit,
scopes: &mut Vec<Scope>,
nodes_to_scopes: &HashMap<NodeId, ScopeId>,
) -> SymbolCollectionResult {
let mut ctx = SymbolCollectionContext::new(scopes, nodes_to_scopes);
for function in compilation_unit.functions() {
collect_symbols_function(function, ctx);
collect_symbols_function(function, &mut ctx);
}
for extern_function in compilation_unit.extern_functions() {
collect_symbols_extern_function(extern_function, ctx);
collect_symbols_extern_function(extern_function, &mut ctx);
}
SymbolCollectionResult {
symbols: ctx.symbols,
diagnostics: ctx.diagnostics,
}
}
fn collect_symbols_function(function: &Function, ctx: &mut SymbolCollectionContext) {
let fqn = ctx.resolve_fqn(&function.declared_name_owned()).into();
// function itself
let function_symbol = Symbol::Function(FunctionSymbol::new(
function.declared_name_owned(),
Some(function.declared_name_source_range()),
fqn,
false,
));
@ -90,10 +128,15 @@ fn collect_symbols_extern_function(
extern_function: &ExternFunction,
ctx: &mut SymbolCollectionContext,
) {
let fqn = ctx
.resolve_fqn(&extern_function.declared_name_owned())
.into();
// function itself
let function_symbol = Symbol::Function(FunctionSymbol::new(
extern_function.declared_name_owned(),
Some(extern_function.declared_name_source_range()),
fqn,
true,
));

View File

@ -0,0 +1,116 @@
use crate::ast::NodeId;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::extern_function::ExternFunction;
use crate::ast::function::Function;
use crate::ast::parameter::Parameter;
use crate::semantic_analysis::symbol::SymbolId;
use crate::semantic_analysis::type_info::{FunctionTypeInfo, TypeInfo, TypeInfoId};
use std::collections::HashMap;
pub struct CollectTypesResult {
pub type_infos: Vec<TypeInfo>,
pub symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
}
struct CollectTypesContext<'a> {
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: Vec<TypeInfo>,
symbols_to_type_infos: HashMap<SymbolId, TypeInfoId>,
}
impl<'a> CollectTypesContext<'a> {
pub fn new(nodes_to_symbols: &'a HashMap<NodeId, SymbolId>) -> Self {
Self {
nodes_to_symbols,
type_infos: Vec::new(),
symbols_to_type_infos: HashMap::new(),
}
}
pub fn insert_type_info(&mut self, type_info: TypeInfo) -> TypeInfoId {
self.type_infos.push(type_info);
self.type_infos.len() - 1
}
}
pub fn collect_types(
compilation_unit: &CompilationUnit,
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
) -> CollectTypesResult {
let mut ctx = CollectTypesContext::new(nodes_to_symbols);
for function in compilation_unit.functions() {
collect_types_function(function, &mut ctx);
}
for extern_function in compilation_unit.extern_functions() {
collect_types_extern_function(extern_function, &mut ctx);
}
CollectTypesResult {
type_infos: ctx.type_infos,
symbols_to_type_infos: ctx.symbols_to_type_infos,
}
}
fn collect_types_function(function: &Function, ctx: &mut CollectTypesContext) {
let parameter_type_info_ids = get_parameter_type_info_ids(function.parameters(), ctx);
let return_type_info = match function.return_type() {
None => TypeInfo::Void,
Some(type_use) => declared_name_to_type_info(type_use.declared_name()),
};
let return_type_info_id = ctx.insert_type_info(return_type_info);
let function_type_info = TypeInfo::Function(FunctionTypeInfo::new(
parameter_type_info_ids,
return_type_info_id,
));
let function_type_info_id = ctx.insert_type_info(function_type_info);
let symbol_id = ctx.nodes_to_symbols[&function.node_id()];
ctx.symbols_to_type_infos
.insert(symbol_id, function_type_info_id);
}
fn declared_name_to_type_info(declared_name: &str) -> TypeInfo {
match declared_name {
"Any" => TypeInfo::Any,
"String" => TypeInfo::String,
"Int" => TypeInfo::Int,
"Double" => TypeInfo::Double,
"Void" => TypeInfo::Void,
_ => TypeInfo::__Error,
}
}
fn get_parameter_type_info_ids(
parameters: &[Parameter],
ctx: &mut CollectTypesContext,
) -> Vec<TypeInfoId> {
let mut parameter_type_info_ids: Vec<TypeInfoId> = Vec::new();
for parameter in parameters {
let type_info = declared_name_to_type_info(parameter.type_use().declared_name());
let type_info_id = ctx.insert_type_info(type_info);
parameter_type_info_ids.push(type_info_id);
}
parameter_type_info_ids
}
fn collect_types_extern_function(extern_function: &ExternFunction, ctx: &mut CollectTypesContext) {
let parameter_type_info_ids = get_parameter_type_info_ids(extern_function.parameters(), ctx);
let return_type_info =
declared_name_to_type_info(extern_function.return_type().declared_name());
let return_type_info_id = ctx.insert_type_info(return_type_info);
let function_type_info = TypeInfo::Function(FunctionTypeInfo::new(
parameter_type_info_ids,
return_type_info_id,
));
let function_type_info_id = ctx.insert_type_info(function_type_info);
let symbol_id = ctx.nodes_to_symbols[&extern_function.node_id()];
ctx.symbols_to_type_infos
.insert(symbol_id, function_type_info_id);
}

View File

@ -1,7 +1,112 @@
use crate::ast::NodeId;
use crate::ast::compilation_unit::CompilationUnit;
use crate::compile_pipeline::FileId;
use crate::diagnostic::Diagnostics;
use crate::semantic_analysis::collect_scopes::collect_scopes;
use crate::semantic_analysis::collect_symbols::collect_symbols;
use crate::semantic_analysis::collect_types::collect_types;
use crate::semantic_analysis::resolve_names::resolve_names;
use crate::semantic_analysis::resolve_types::resolve_types;
use crate::semantic_analysis::scope::{Scope, ScopeId};
use crate::semantic_analysis::symbol::{Symbol, SymbolId};
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
mod collect_scopes;
mod collect_symbols;
mod collect_types;
mod diagnostic_helpers;
mod resolve_names;
mod resolve_types;
mod scope;
mod semantic_context;
mod symbol;
pub mod symbol;
pub mod type_info;
pub struct AnalysisResult {
pub type_infos: Vec<TypeInfo>,
pub nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
}
pub fn analyze(
compilation_units: &HashMap<FileId, CompilationUnit>,
) -> HashMap<FileId, AnalysisResult> {
let mut diagnostics = Diagnostics::new();
let mut files_to_scopes: HashMap<FileId, Vec<Scope>> = HashMap::new();
let mut files_to_nodes_to_scopes: HashMap<FileId, HashMap<NodeId, ScopeId>> = HashMap::new();
for (file_id, compilation_unit) in compilation_units {
let result = collect_scopes(compilation_unit);
files_to_scopes.insert(*file_id, result.scopes);
files_to_nodes_to_scopes.insert(*file_id, result.nodes_to_scopes);
}
let mut files_to_symbols: HashMap<FileId, Vec<Symbol>> = HashMap::new();
for (file_id, compilation_unit) in compilation_units {
let scopes = files_to_scopes.get_mut(file_id).unwrap();
let nodes_to_scopes = files_to_nodes_to_scopes.get(file_id).unwrap();
let mut result = collect_symbols(compilation_unit, scopes, nodes_to_scopes);
files_to_symbols.insert(*file_id, result.symbols);
diagnostics.append(&mut result.diagnostics);
}
let mut files_to_nodes_to_symbols: HashMap<FileId, HashMap<NodeId, SymbolId>> = HashMap::new();
for (file_id, compilation_unit) in compilation_units {
let scopes = files_to_scopes.get_mut(file_id).unwrap();
let symbols = files_to_symbols.get_mut(file_id).unwrap();
let nodes_to_scopes = files_to_nodes_to_scopes.get(file_id).unwrap();
let mut result = resolve_names(compilation_unit, scopes, symbols, nodes_to_scopes);
files_to_nodes_to_symbols.insert(*file_id, result.nodes_to_symbols);
diagnostics.append(&mut result.diagnostics);
}
let mut files_to_type_infos: HashMap<FileId, Vec<TypeInfo>> = HashMap::new();
let mut files_to_symbols_to_type_infos: HashMap<FileId, HashMap<SymbolId, TypeInfoId>> =
HashMap::new();
for (file_id, compilation_unit) in compilation_units {
let nodes_to_symbols = files_to_nodes_to_symbols.get(&file_id).unwrap();
let result = collect_types(compilation_unit, nodes_to_symbols);
files_to_type_infos.insert(*file_id, result.type_infos);
files_to_symbols_to_type_infos.insert(*file_id, result.symbols_to_type_infos);
}
let mut files_to_nodes_to_type_infos: HashMap<FileId, HashMap<NodeId, TypeInfoId>> =
HashMap::new();
for (file_id, compilation_unit) in compilation_units {
let nodes_to_symbols = files_to_nodes_to_symbols.get_mut(file_id).unwrap();
let type_infos = files_to_type_infos.get_mut(file_id).unwrap();
let symbols_to_type_infos = files_to_symbols_to_type_infos.get_mut(file_id).unwrap();
let mut result = resolve_types(
compilation_unit,
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
);
files_to_nodes_to_type_infos.insert(*file_id, result.nodes_to_type_infos);
diagnostics.append(&mut result.diagnostics);
}
let mut files_to_results: HashMap<FileId, AnalysisResult> = HashMap::new();
for (file_id, _) in compilation_units {
let type_infos = files_to_type_infos.remove(file_id).unwrap();
let nodes_to_type_infos = files_to_nodes_to_type_infos.remove(file_id).unwrap();
let analysis_result = AnalysisResult {
type_infos,
nodes_to_type_infos,
};
files_to_results.insert(*file_id, analysis_result);
}
files_to_results
}

View File

@ -16,19 +16,24 @@ use crate::semantic_analysis::scope::{Scope, ScopeId};
use crate::semantic_analysis::symbol::{Symbol, SymbolId, VariableSymbol};
use std::collections::HashMap;
pub struct NameResolutionContext {
scopes: Vec<Scope>,
symbols: Vec<Symbol>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
pub struct NameResolutionResult {
pub nodes_to_symbols: HashMap<NodeId, SymbolId>,
pub diagnostics: Diagnostics,
}
struct NameResolutionContext<'a> {
scopes: &'a mut Vec<Scope>,
symbols: &'a mut Vec<Symbol>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
nodes_to_symbols: HashMap<NodeId, SymbolId>,
diagnostics: Diagnostics,
}
impl NameResolutionContext {
impl<'a> NameResolutionContext<'a> {
pub fn new(
scopes: Vec<Scope>,
symbols: Vec<Symbol>,
nodes_to_scopes: HashMap<NodeId, ScopeId>,
scopes: &'a mut Vec<Scope>,
symbols: &'a mut Vec<Symbol>,
nodes_to_scopes: &'a HashMap<NodeId, ScopeId>,
) -> Self {
Self {
scopes,
@ -81,9 +86,21 @@ enum ExpressionResolutionPhase {
Static,
}
pub fn resolve_names(compilation_unit: &CompilationUnit, ctx: &mut NameResolutionContext) {
pub fn resolve_names(
compilation_unit: &CompilationUnit,
scopes: &mut Vec<Scope>,
symbols: &mut Vec<Symbol>,
nodes_to_scopes: &HashMap<NodeId, ScopeId>,
) -> NameResolutionResult {
let mut ctx = NameResolutionContext::new(scopes, symbols, nodes_to_scopes);
for function in compilation_unit.functions() {
resolve_names_function(function, ctx);
resolve_names_function(function, &mut ctx);
}
NameResolutionResult {
nodes_to_symbols: ctx.nodes_to_symbols,
diagnostics: ctx.diagnostics,
}
}
@ -129,6 +146,7 @@ fn resolve_names_let_statement(
let symbol = Symbol::Variable(VariableSymbol::new(
let_statement.declared_name_owned(),
Some(let_statement.declared_name_source_range()),
let_statement.is_mut(),
));
// the following could be a method, but this is probably the only place in this phase
// where we are pushing symbols still
@ -228,7 +246,7 @@ fn resolve_names_identifier(
) {
match phase {
ExpressionResolutionPhase::Static => {
let scope_id = ctx.nodes_to_scopes()[&identifier.scope_id()];
let scope_id = ctx.nodes_to_scopes()[&identifier.node_id()];
let mut maybe_scope = Some(&ctx.scopes()[scope_id]);
let mut found_symbol_id: Option<SymbolId> = None;
while let Some(scope) = maybe_scope {

View File

@ -0,0 +1,336 @@
mod type_analysis;
use crate::ast::NodeId;
use crate::ast::assign_statement::AssignStatement;
use crate::ast::binary_expression::{BinaryExpression, BinaryOperation};
use crate::ast::call::Call;
use crate::ast::compilation_unit::CompilationUnit;
use crate::ast::expression::Expression;
use crate::ast::function::Function;
use crate::ast::identifier::Identifier;
use crate::ast::let_statement::LetStatement;
use crate::ast::negative_expression::NegativeExpression;
use crate::ast::statement::Statement;
use crate::diagnostic::{Diagnostic, Diagnostics};
use crate::error_codes::BINARY_INCOMPATIBLE_TYPES;
use crate::semantic_analysis::resolve_types::type_analysis::{
are_binary_op_compatible, binary_op_result, can_assign_right_to_left, can_negate, negate_result,
};
use crate::semantic_analysis::symbol::SymbolId;
use crate::semantic_analysis::type_info::{TypeInfo, TypeInfoId};
use std::collections::HashMap;
pub struct ResolveTypesResult {
pub nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
pub diagnostics: Diagnostics,
}
struct ResolveTypesContext<'a> {
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
nodes_to_type_infos: HashMap<NodeId, TypeInfoId>,
diagnostics: Diagnostics,
}
impl<'a> ResolveTypesContext<'a> {
pub fn new(
nodes_to_symbols: &'a HashMap<NodeId, SymbolId>,
type_infos: &'a mut Vec<TypeInfo>,
symbols_to_type_infos: &'a mut HashMap<SymbolId, TypeInfoId>,
) -> Self {
Self {
nodes_to_symbols,
type_infos,
symbols_to_type_infos,
nodes_to_type_infos: HashMap::new(),
diagnostics: Diagnostics::new(),
}
}
}
pub fn resolve_types(
compilation_unit: &CompilationUnit,
nodes_to_symbols: &HashMap<NodeId, SymbolId>,
type_infos: &mut Vec<TypeInfo>,
symbols_to_type_infos: &mut HashMap<SymbolId, TypeInfoId>,
) -> ResolveTypesResult {
let mut ctx = ResolveTypesContext::new(nodes_to_symbols, type_infos, symbols_to_type_infos);
for function in compilation_unit.functions() {
resolve_types_function(function, &mut ctx);
}
ResolveTypesResult {
nodes_to_type_infos: ctx.nodes_to_type_infos,
diagnostics: ctx.diagnostics,
}
}
fn resolve_types_function(function: &Function, ctx: &mut ResolveTypesContext) {
for statement in function.statements() {
resolve_types_statement(statement, ctx);
}
}
fn resolve_types_statement(statement: &Statement, ctx: &mut ResolveTypesContext) {
match statement {
Statement::Let(let_statement) => {
resolve_types_let_statement(let_statement, ctx);
}
Statement::Expression(expression_statement) => {
resolve_types_expression(expression_statement.expression(), ctx);
}
Statement::Assign(assign_statement) => {
resolve_types_assign_statement(assign_statement, ctx);
}
}
}
fn resolve_types_let_statement(let_statement: &LetStatement, ctx: &mut ResolveTypesContext) {
resolve_types_expression(let_statement.initializer(), ctx);
let initializer_type_info_id = ctx.nodes_to_type_infos[&let_statement.initializer().node_id()];
// update symbol's type
let symbol_id = ctx.nodes_to_symbols[&let_statement.node_id()];
ctx.symbols_to_type_infos
.insert(symbol_id, initializer_type_info_id);
}
fn resolve_types_assign_statement(
assign_statement: &AssignStatement,
ctx: &mut ResolveTypesContext,
) {
resolve_types_expression(assign_statement.value(), ctx);
resolve_types_expression(assign_statement.destination(), ctx);
// check assignability
let value_type_info_id = ctx.nodes_to_type_infos[&assign_statement.value().node_id()];
let value_type_info = &ctx.type_infos[value_type_info_id];
let destination_type_info_id =
ctx.nodes_to_type_infos[&assign_statement.destination().node_id()];
let destination_type_info = &ctx.type_infos[destination_type_info_id];
if !can_assign_right_to_left(destination_type_info, value_type_info) {
let message = format!(
"Incompatible types: cannot assign {} from {}",
destination_type_info, value_type_info
);
let diagnostic = Diagnostic::new(
&message,
assign_statement.destination().source_range().start(),
assign_statement.destination().source_range().end(),
);
ctx.diagnostics.push(diagnostic);
}
}
fn resolve_types_expression(expression: &Expression, ctx: &mut ResolveTypesContext) {
match expression {
Expression::Binary(binary_expression) => {
resolve_types_binary_expression(binary_expression, ctx);
}
Expression::Negative(negative_expression) => {
resolve_types_negative_expression(negative_expression, ctx);
}
Expression::Call(call) => {
resolve_types_call(call, ctx);
}
Expression::Identifier(identifier) => {
resolve_types_identifier(identifier, ctx);
}
Expression::Integer(integer_literal) => {
// yes, this is slightly wasteful now, but keeping it simple for mental model.
ctx.type_infos.push(TypeInfo::Int);
let int_literal_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(integer_literal.node_id(), int_literal_type_info_id);
}
Expression::Double(double_literal) => {
ctx.type_infos.push(TypeInfo::Double);
let double_literal_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(double_literal.node_id(), double_literal_type_info_id);
}
Expression::String(string_literal) => {
ctx.type_infos.push(TypeInfo::String);
let string_literal_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(string_literal.node_id(), string_literal_type_info_id);
}
}
}
fn resolve_types_binary_expression(
binary_expression: &BinaryExpression,
ctx: &mut ResolveTypesContext,
) {
resolve_types_expression(binary_expression.lhs(), ctx);
resolve_types_expression(binary_expression.rhs(), ctx);
let lhs_type_info_id = ctx.nodes_to_type_infos[&binary_expression.lhs().node_id()];
let lhs_type_info = &ctx.type_infos[lhs_type_info_id];
let rhs_type_info_id = ctx.nodes_to_type_infos[&binary_expression.rhs().node_id()];
let rhs_type_info = &ctx.type_infos[rhs_type_info_id];
if are_binary_op_compatible(binary_expression.op(), lhs_type_info, rhs_type_info) {
let result_type_info =
binary_op_result(binary_expression.op(), lhs_type_info, rhs_type_info);
ctx.type_infos.push(result_type_info);
let result_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(binary_expression.node_id(), result_type_info_id);
} else {
let op_name = match binary_expression.op() {
BinaryOperation::Multiply => "multiply",
BinaryOperation::Divide => "divide",
BinaryOperation::Modulo => "modulo",
BinaryOperation::Add => "add",
BinaryOperation::Subtract => "subtract",
BinaryOperation::LeftShift => "left shift",
BinaryOperation::RightShift => "right shift",
BinaryOperation::BitwiseAnd => "bitwise and",
BinaryOperation::BitwiseXor => "bitwise xor",
BinaryOperation::BitwiseOr => "bitwise or",
};
let message = format!(
"Incompatible types: cannot {} {} and {}",
op_name, lhs_type_info, rhs_type_info
);
let diagnostic = Diagnostic::new(
&message,
binary_expression.source_range().start(),
binary_expression.source_range().end(),
)
.with_error_code(BINARY_INCOMPATIBLE_TYPES);
ctx.diagnostics.push(diagnostic);
// push error type so it bubbles up
ctx.type_infos.push(TypeInfo::__Error);
let result_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(binary_expression.node_id(), result_type_info_id);
}
}
fn resolve_types_negative_expression(
negative_expression: &NegativeExpression,
ctx: &mut ResolveTypesContext,
) {
resolve_types_expression(negative_expression.operand(), ctx);
let operand_type_info_id = ctx.nodes_to_type_infos[&negative_expression.operand().node_id()];
let operand_type_info = &ctx.type_infos[operand_type_info_id];
if can_negate(operand_type_info) {
let result_type_info = negate_result(operand_type_info);
ctx.type_infos.push(result_type_info);
let result_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(negative_expression.node_id(), result_type_info_id);
} else {
let message = format!("Incompatible type: cannot negate {}", operand_type_info);
let diagnostic = Diagnostic::new(
&message,
negative_expression.source_range().start(),
negative_expression.source_range().end(),
);
ctx.diagnostics.push(diagnostic);
// bubble up error type
ctx.type_infos.push(TypeInfo::__Error);
let result_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(negative_expression.node_id(), result_type_info_id);
}
}
fn resolve_types_call(call: &Call, ctx: &mut ResolveTypesContext) {
resolve_types_expression(call.callee(), ctx);
let callee_type_info_id = ctx.nodes_to_type_infos[&call.callee().node_id()];
let callee_type_info = &ctx.type_infos[callee_type_info_id];
match callee_type_info {
TypeInfo::Function(function_type_info) => {
// check arguments length
let arguments = call.arguments();
if arguments.len() != function_type_info.parameter_type_ids().len() {
let message = format!(
"Wrong number of arguments: expected {} but found {}",
function_type_info.parameter_type_ids().len(),
arguments.len()
);
let diagnostic = Diagnostic::new(
&message,
call.source_range().start(),
call.source_range().end(),
);
ctx.diagnostics.push(diagnostic);
// do not push an error type because the result of the whole call is the return type
// of the function
}
// check argument types
let parameter_type_ids = function_type_info.parameter_type_ids();
for i in 0..parameter_type_ids.len() {
let argument_type_info_id = ctx.nodes_to_type_infos[&arguments[i].node_id()];
let argument_type_info = &ctx.type_infos[argument_type_info_id];
let parameter_type_info = &ctx.type_infos[parameter_type_ids[i]];
if !can_assign_right_to_left(parameter_type_info, argument_type_info) {
let message = format!(
"Incompatible types: cannot assign {} to {}",
argument_type_info, parameter_type_info
);
let diagnostic = Diagnostic::new(
&message,
arguments[i].source_range().start(),
arguments[i].source_range().end(),
);
ctx.diagnostics.push(diagnostic);
// do not push an error type because the result of the whole call is the return type
// of the function
}
}
// set return type as type of call expression
let return_type_info_id = function_type_info.return_type_id();
ctx.nodes_to_type_infos
.insert(call.node_id(), *return_type_info_id);
}
TypeInfo::__Error => {
// bubble it up
ctx.type_infos.push(TypeInfo::__Error);
let error_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(call.node_id(), error_type_info_id);
}
_ => {
let message = format!("Incompatible type: cannot call type {}", callee_type_info);
let diagnostic = Diagnostic::new(
&message,
call.callee().source_range().start(),
call.callee().source_range().end(),
);
ctx.diagnostics.push(diagnostic);
// bubble up error type
ctx.type_infos.push(TypeInfo::__Error);
let error_type_info_id = ctx.type_infos.len() - 1;
ctx.nodes_to_type_infos
.insert(call.node_id(), error_type_info_id);
}
}
}
fn resolve_types_identifier(identifier: &Identifier, ctx: &mut ResolveTypesContext) {
let symbol_id = ctx.nodes_to_symbols[&identifier.node_id()];
let symbol_type_info_id = ctx.symbols_to_type_infos[&symbol_id];
// bubble up
ctx.nodes_to_type_infos
.insert(identifier.node_id(), symbol_type_info_id);
}

View File

@ -0,0 +1,127 @@
use crate::ast::binary_expression::BinaryOperation;
use crate::semantic_analysis::type_info::TypeInfo;
pub fn are_binary_op_compatible(op: &BinaryOperation, left: &TypeInfo, right: &TypeInfo) -> bool {
match op {
BinaryOperation::Multiply
| BinaryOperation::Divide
| BinaryOperation::Modulo
| BinaryOperation::Subtract => are_numbers(left, right),
BinaryOperation::Add => are_numbers_or_strings(left, right),
BinaryOperation::LeftShift
| BinaryOperation::RightShift
| BinaryOperation::BitwiseAnd
| BinaryOperation::BitwiseXor
| BinaryOperation::BitwiseOr => are_ints(left, right),
}
}
fn is_number(type_info: &TypeInfo) -> bool {
matches!(type_info, TypeInfo::Int | TypeInfo::Double)
}
fn are_numbers(t0: &TypeInfo, t1: &TypeInfo) -> bool {
is_number(t0) && is_number(t1)
}
fn are_numbers_or_strings(t0: &TypeInfo, t1: &TypeInfo) -> bool {
(is_number(t0) || matches!(t0, TypeInfo::String))
&& (is_number(t1) || matches!(t1, TypeInfo::String))
}
fn are_ints(t0: &TypeInfo, t1: &TypeInfo) -> bool {
matches!(t0, TypeInfo::Int) && matches!(t1, TypeInfo::Int)
}
pub fn binary_op_result(op: &BinaryOperation, left: &TypeInfo, right: &TypeInfo) -> TypeInfo {
match op {
BinaryOperation::Multiply => numbers_binary_result(left, right),
BinaryOperation::Divide => TypeInfo::Double, // okay for now
BinaryOperation::Modulo => numbers_binary_result(left, right), // same properties as multiplication
BinaryOperation::Add => add_result(left, right),
BinaryOperation::Subtract => numbers_binary_result(left, right),
BinaryOperation::LeftShift
| BinaryOperation::RightShift
| BinaryOperation::BitwiseAnd
| BinaryOperation::BitwiseXor
| BinaryOperation::BitwiseOr => TypeInfo::Int,
}
}
fn numbers_binary_result(left: &TypeInfo, right: &TypeInfo) -> TypeInfo {
match left {
TypeInfo::Int => match right {
TypeInfo::Int => TypeInfo::Int,
TypeInfo::Double => TypeInfo::Double,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
},
TypeInfo::Double => match right {
TypeInfo::Int | TypeInfo::Double => TypeInfo::Double,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
},
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
}
}
fn add_result(left: &TypeInfo, right: &TypeInfo) -> TypeInfo {
match left {
TypeInfo::Int => match right {
TypeInfo::Int => TypeInfo::Int,
TypeInfo::Double => TypeInfo::Double,
TypeInfo::String => TypeInfo::String,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
},
TypeInfo::Double => match right {
TypeInfo::Int | TypeInfo::Double => TypeInfo::Double,
TypeInfo::String => TypeInfo::String,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
},
TypeInfo::String => TypeInfo::String,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
}
}
pub fn can_negate(operand: &TypeInfo) -> bool {
matches!(
operand,
TypeInfo::Int | TypeInfo::Double | TypeInfo::__Error
)
}
pub fn negate_result(operand: &TypeInfo) -> TypeInfo {
match operand {
TypeInfo::Int => TypeInfo::Int,
TypeInfo::Double => TypeInfo::Double,
TypeInfo::__Error => TypeInfo::__Error,
_ => panic!(),
}
}
pub fn can_assign_right_to_left(left: &TypeInfo, right: &TypeInfo) -> bool {
match left {
TypeInfo::Any => true,
TypeInfo::Function(_) => {
panic!()
}
TypeInfo::String => match right {
TypeInfo::String => true,
_ => false,
},
TypeInfo::Int => match right {
TypeInfo::Int => true,
_ => false,
},
TypeInfo::Double => match right {
TypeInfo::Double | TypeInfo::Int => true,
_ => false,
},
TypeInfo::Void => false,
TypeInfo::__Error => true,
}
}

View File

@ -36,30 +36,49 @@ impl Symbol {
}
pub struct FunctionSymbol {
name: Rc<str>,
source_range: Option<SourceRange>,
declared_name: Rc<str>,
declared_name_source_range: Option<SourceRange>,
fqn: Rc<str>,
is_extern: bool,
}
impl FunctionSymbol {
pub fn new(name: Rc<str>, source_range: Option<SourceRange>, is_extern: bool) -> Self {
pub fn new(
declared_name: Rc<str>,
declared_name_source_range: Option<SourceRange>,
fqn: Rc<str>,
is_extern: bool,
) -> Self {
Self {
name,
source_range,
declared_name,
declared_name_source_range,
fqn,
is_extern,
}
}
pub fn declared_name(&self) -> &str {
&self.name
&self.declared_name
}
pub fn declared_name_owned(&self) -> Rc<str> {
self.name.clone()
self.declared_name.clone()
}
pub fn source_range(&self) -> Option<&SourceRange> {
self.source_range.as_ref()
self.declared_name_source_range.as_ref()
}
pub fn fqn(&self) -> &str {
&self.fqn
}
pub fn fqn_owned(&self) -> Rc<str> {
self.fqn.clone()
}
pub fn is_extern(&self) -> bool {
self.is_extern
}
}
@ -89,11 +108,16 @@ impl ParameterSymbol {
pub struct VariableSymbol {
name: Rc<str>,
source_range: Option<SourceRange>,
is_mut: bool,
}
impl VariableSymbol {
pub fn new(name: Rc<str>, source_range: Option<SourceRange>) -> Self {
Self { name, source_range }
pub fn new(name: Rc<str>, source_range: Option<SourceRange>, is_mut: bool) -> Self {
Self {
name,
source_range,
is_mut,
}
}
pub fn declared_name(&self) -> &str {

View File

@ -0,0 +1,55 @@
use std::fmt::{Display, Formatter};
pub type TypeInfoId = usize;
#[derive(Clone)]
pub enum TypeInfo {
Any,
Function(FunctionTypeInfo),
String,
Int,
Double,
Void,
__Error,
}
impl Display for TypeInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
TypeInfo::Any => "Any",
TypeInfo::Function(_) => "Function",
TypeInfo::String => "String",
TypeInfo::Int => "Int",
TypeInfo::Double => "Double",
TypeInfo::Void => "Void",
TypeInfo::__Error => panic!("This should never be shown to a user."),
}
)
}
}
#[derive(Clone)]
pub struct FunctionTypeInfo {
parameter_type_info_ids: Vec<TypeInfoId>,
return_type_info_id: TypeInfoId,
}
impl FunctionTypeInfo {
pub fn new(parameter_type_info_ids: Vec<TypeInfoId>, return_type_info_id: TypeInfoId) -> Self {
Self {
parameter_type_info_ids,
return_type_info_id,
}
}
pub fn parameter_type_ids(&self) -> &[TypeInfoId] {
&self.parameter_type_info_ids
}
pub fn return_type_id(&self) -> &TypeInfoId {
&self.return_type_info_id
}
}