mirror of
https://github.com/kmc7468/cs420.git
synced 2025-12-16 07:28:52 +00:00
Issue homework 2: irgen
This commit is contained in:
465
src/ir/mod.rs
465
src/ir/mod.rs
@@ -4,8 +4,9 @@ mod write_ir;
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::fmt;
|
||||
use core::ops::Deref;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use lang_c::ast;
|
||||
use ordered_float::OrderedFloat;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
@@ -60,6 +61,7 @@ impl TryFrom<Dtype> for Declaration {
|
||||
signature: FunctionSignature::new(dtype),
|
||||
definition: None,
|
||||
}),
|
||||
Dtype::Typedef { .. } => panic!("typedef should be replaced by real dtype"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,10 +102,6 @@ impl Declaration {
|
||||
}
|
||||
|
||||
/// Check if type is conflicting for pre-declared one
|
||||
///
|
||||
/// In case of `Variable`, need to check if the two types are exactly the same.
|
||||
/// On the other hand, in the case of `Function`, outermost `const` of return type and
|
||||
/// parameters one is not an issue of concern.
|
||||
pub fn is_compatible(&self, other: &Declaration) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Variable { dtype, .. }, Self::Variable { dtype: other, .. }) => dtype == other,
|
||||
@@ -112,7 +110,7 @@ impl Declaration {
|
||||
Self::Function {
|
||||
signature: other, ..
|
||||
},
|
||||
) => signature.dtype().is_compatible(&other.dtype()),
|
||||
) => signature.dtype() == other.dtype(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@@ -127,19 +125,6 @@ impl HasDtype for Declaration {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FunctionDefinition {
|
||||
/// Memory allocations for local variables. The allocation is performed at the beginning of a
|
||||
/// function invocation.
|
||||
pub allocations: Vec<Dtype>,
|
||||
|
||||
/// Basic blocks.
|
||||
pub blocks: HashMap<BlockId, Block>,
|
||||
|
||||
/// The initial block id.
|
||||
pub bid_init: BlockId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FunctionSignature {
|
||||
pub ret: Dtype,
|
||||
@@ -164,15 +149,229 @@ impl HasDtype for FunctionSignature {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct FunctionDefinition {
|
||||
/// Memory allocations for local variables. The allocation is performed at the beginning of a
|
||||
/// function invocation.
|
||||
pub allocations: Vec<Named<Dtype>>,
|
||||
|
||||
/// Basic blocks.
|
||||
pub blocks: HashMap<BlockId, Block>,
|
||||
|
||||
/// The initial block id.
|
||||
pub bid_init: BlockId,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct BlockId(pub usize);
|
||||
|
||||
impl fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "b{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Block {
|
||||
pub phinodes: Vec<Named<Dtype>>,
|
||||
pub instructions: Vec<Named<Instruction>>,
|
||||
pub exit: BlockExit,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Instruction {
|
||||
// TODO: the variants of Instruction will be added in the future
|
||||
Nop,
|
||||
BinOp {
|
||||
op: ast::BinaryOperator,
|
||||
lhs: Operand,
|
||||
rhs: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
UnaryOp {
|
||||
op: ast::UnaryOperator,
|
||||
operand: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
Store {
|
||||
ptr: Operand,
|
||||
value: Operand,
|
||||
},
|
||||
Load {
|
||||
ptr: Operand,
|
||||
},
|
||||
Call {
|
||||
callee: Operand,
|
||||
args: Vec<Operand>,
|
||||
return_type: Dtype,
|
||||
},
|
||||
TypeCast {
|
||||
value: Operand,
|
||||
target_dtype: Dtype,
|
||||
},
|
||||
}
|
||||
|
||||
impl HasDtype for Instruction {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Nop => Dtype::unit(),
|
||||
Self::BinOp { dtype, .. } => dtype.clone(),
|
||||
Self::UnaryOp { dtype, .. } => dtype.clone(),
|
||||
Self::Store { .. } => Dtype::unit(),
|
||||
Self::Load { ptr } => ptr
|
||||
.dtype()
|
||||
.get_pointer_inner()
|
||||
.expect("Load instruction must have pointer value as operand")
|
||||
.deref()
|
||||
.clone()
|
||||
.set_const(false),
|
||||
Self::Call { return_type, .. } => return_type.clone(),
|
||||
Self::TypeCast { target_dtype, .. } => target_dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Store { .. } => false,
|
||||
Self::Call { .. } => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BlockExit {
|
||||
Jump {
|
||||
arg: JumpArg,
|
||||
},
|
||||
ConditionalJump {
|
||||
condition: Operand,
|
||||
arg_then: JumpArg,
|
||||
arg_else: JumpArg,
|
||||
},
|
||||
Switch {
|
||||
value: Operand,
|
||||
default: JumpArg,
|
||||
cases: Vec<(Constant, JumpArg)>,
|
||||
},
|
||||
Return {
|
||||
value: Operand,
|
||||
},
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
impl BlockExit {
|
||||
pub fn walk_jump_args<F>(&mut self, mut f: F)
|
||||
where
|
||||
F: FnMut(&mut JumpArg),
|
||||
{
|
||||
match self {
|
||||
Self::Jump { arg } => f(arg),
|
||||
Self::ConditionalJump {
|
||||
arg_then, arg_else, ..
|
||||
} => {
|
||||
f(arg_then);
|
||||
f(arg_else);
|
||||
}
|
||||
Self::Switch { default, cases, .. } => {
|
||||
f(default);
|
||||
for (_, arg) in cases {
|
||||
f(arg);
|
||||
}
|
||||
}
|
||||
Self::Return { .. } | Self::Unreachable => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct JumpArg {
|
||||
pub bid: BlockId,
|
||||
pub args: Vec<Operand>,
|
||||
}
|
||||
|
||||
impl JumpArg {
|
||||
pub fn new(bid: BlockId, args: Vec<Operand>) -> Self {
|
||||
Self { bid, args }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for JumpArg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}({:?})", self.bid, self.args)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Operand {
|
||||
Constant(Constant),
|
||||
Register { rid: RegisterId, dtype: Dtype },
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
pub fn constant(value: Constant) -> Self {
|
||||
Self::Constant(value)
|
||||
}
|
||||
|
||||
pub fn register(rid: RegisterId, dtype: Dtype) -> Self {
|
||||
Self::Register { rid, dtype }
|
||||
}
|
||||
|
||||
pub fn get_constant(&self) -> Option<&Constant> {
|
||||
if let Self::Constant(constant) = self {
|
||||
Some(constant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self) -> Option<(&RegisterId, &Dtype)> {
|
||||
if let Self::Register { rid, dtype } = self {
|
||||
Some((rid, dtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register_mut(&mut self) -> Option<(&mut RegisterId, &mut Dtype)> {
|
||||
if let Self::Register { rid, dtype } = self {
|
||||
Some((rid, dtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Operand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Constant(value) => write!(f, "{}", value),
|
||||
Self::Register { rid, .. } => write!(f, "{}", rid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for Operand {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Constant(value) => value.dtype(),
|
||||
Self::Register { dtype, .. } => dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Clone)]
|
||||
pub enum RegisterId {
|
||||
/// Registers holding pointers to local allocations.
|
||||
///
|
||||
/// # Fields
|
||||
///
|
||||
/// - `name`: only for debugging purposes.
|
||||
/// - `id`: local allocation id.
|
||||
Local { name: String, id: usize },
|
||||
/// - `aid`: local allocation id.
|
||||
Local { aid: usize },
|
||||
|
||||
/// Registers holding block arguments.
|
||||
///
|
||||
@@ -193,8 +392,8 @@ pub enum RegisterId {
|
||||
}
|
||||
|
||||
impl RegisterId {
|
||||
pub fn local(name: String, id: usize) -> Self {
|
||||
Self::Local { name, id }
|
||||
pub fn local(aid: usize) -> Self {
|
||||
Self::Local { aid }
|
||||
}
|
||||
|
||||
pub fn arg(bid: BlockId, aid: usize) -> Self {
|
||||
@@ -209,9 +408,9 @@ impl RegisterId {
|
||||
impl fmt::Display for RegisterId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Local { name, id } => write!(f, "%(local:{}:{})", name, id),
|
||||
Self::Arg { bid, aid } => write!(f, "%(arg:{}:{})", bid, aid),
|
||||
Self::Temp { bid, iid } => write!(f, "%({}:{})", bid, iid),
|
||||
Self::Local { aid } => write!(f, "%l{}", aid),
|
||||
Self::Arg { bid, aid } => write!(f, "%{}:p{}", bid, aid),
|
||||
Self::Temp { bid, iid } => write!(f, "%{}:i{}", bid, iid),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +418,7 @@ impl fmt::Display for RegisterId {
|
||||
impl PartialEq<RegisterId> for RegisterId {
|
||||
fn eq(&self, other: &RegisterId) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Local { id, .. }, Self::Local { id: other_id, .. }) => id == other_id,
|
||||
(Self::Local { aid }, Self::Local { aid: other_aid }) => aid == other_aid,
|
||||
(
|
||||
Self::Arg { bid, aid },
|
||||
Self::Arg {
|
||||
@@ -242,7 +441,7 @@ impl PartialEq<RegisterId> for RegisterId {
|
||||
impl Hash for RegisterId {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Self::Local { id, .. } => id.hash(state),
|
||||
Self::Local { aid } => aid.hash(state),
|
||||
Self::Arg { bid, aid } => {
|
||||
// TODO: needs to distinguish arg/temp?
|
||||
bid.hash(state);
|
||||
@@ -256,7 +455,7 @@ impl Hash for RegisterId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Constant {
|
||||
Undef {
|
||||
dtype: Dtype,
|
||||
@@ -273,7 +472,7 @@ pub enum Constant {
|
||||
/// * Casting from an f32 to an f64 is perfect and lossless (f32 -> f64)
|
||||
/// * Casting from an f64 to an f32 will produce the closest possible value (f64 -> f32)
|
||||
/// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#type-cast-expressions
|
||||
value: f64,
|
||||
value: OrderedFloat<f64>,
|
||||
width: usize,
|
||||
},
|
||||
GlobalVariable {
|
||||
@@ -397,7 +596,10 @@ impl Constant {
|
||||
.get_float_width()
|
||||
.expect("`dtype` must be `Dtype::Float`");
|
||||
|
||||
Self::Float { value, width }
|
||||
Self::Float {
|
||||
value: value.into(),
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -405,6 +607,20 @@ impl Constant {
|
||||
Self::GlobalVariable { name, dtype }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_int(&self) -> Option<(u128, usize, bool)> {
|
||||
if let Self::Int {
|
||||
value,
|
||||
width,
|
||||
is_signed,
|
||||
} = self
|
||||
{
|
||||
Some((*value, *width, *is_signed))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_undef(&self) -> bool {
|
||||
if let Self::Undef { .. } = self {
|
||||
true
|
||||
@@ -441,179 +657,30 @@ impl HasDtype for Constant {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Operand {
|
||||
Constant(Constant),
|
||||
Register { rid: RegisterId, dtype: Dtype },
|
||||
pub struct Named<T> {
|
||||
name: Option<String>,
|
||||
inner: T,
|
||||
}
|
||||
|
||||
impl Operand {
|
||||
pub fn constant(value: Constant) -> Self {
|
||||
Self::Constant(value)
|
||||
}
|
||||
impl<T> Deref for Named<T> {
|
||||
type Target = T;
|
||||
|
||||
pub fn register(rid: RegisterId, dtype: Dtype) -> Self {
|
||||
Self::Register { rid, dtype }
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn get_constant(&self) -> Option<&Constant> {
|
||||
if let Self::Constant(constant) = self {
|
||||
Some(constant)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register(&self) -> Option<(&RegisterId, &Dtype)> {
|
||||
if let Self::Register { rid, dtype } = self {
|
||||
Some((rid, dtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_register_mut(&mut self) -> Option<(&mut RegisterId, &mut Dtype)> {
|
||||
if let Self::Register { rid, dtype } = self {
|
||||
Some((rid, dtype))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for Named<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Operand {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Constant(value) => write!(f, "{}", value),
|
||||
Self::Register { rid, .. } => write!(f, "{}", rid),
|
||||
}
|
||||
impl<T> Named<T> {
|
||||
pub fn new(name: Option<String>, inner: T) -> Self {
|
||||
Self { name, inner }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<&String> {
|
||||
self.name.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasDtype for Operand {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Constant(value) => value.dtype(),
|
||||
Self::Register { dtype, .. } => dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Instruction {
|
||||
// TODO: the variants of Instruction will be added in the future
|
||||
Nop,
|
||||
BinOp {
|
||||
op: ast::BinaryOperator,
|
||||
lhs: Operand,
|
||||
rhs: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
UnaryOp {
|
||||
op: ast::UnaryOperator,
|
||||
operand: Operand,
|
||||
dtype: Dtype,
|
||||
},
|
||||
Store {
|
||||
ptr: Operand,
|
||||
value: Operand,
|
||||
},
|
||||
Load {
|
||||
ptr: Operand,
|
||||
},
|
||||
Call {
|
||||
callee: Operand,
|
||||
args: Vec<Operand>,
|
||||
return_type: Dtype,
|
||||
},
|
||||
TypeCast {
|
||||
value: Operand,
|
||||
target_dtype: Dtype,
|
||||
},
|
||||
}
|
||||
|
||||
impl HasDtype for Instruction {
|
||||
fn dtype(&self) -> Dtype {
|
||||
match self {
|
||||
Self::Nop => Dtype::unit(),
|
||||
Self::BinOp { dtype, .. } => dtype.clone(),
|
||||
Self::UnaryOp { dtype, .. } => dtype.clone(),
|
||||
Self::Store { .. } => Dtype::unit(),
|
||||
Self::Load { ptr } => ptr
|
||||
.dtype()
|
||||
.get_pointer_inner()
|
||||
.expect("Load instruction must have pointer value as operand")
|
||||
.deref()
|
||||
.clone()
|
||||
.set_const(false),
|
||||
Self::Call { return_type, .. } => return_type.clone(),
|
||||
Self::TypeCast { target_dtype, .. } => target_dtype.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
Self::Store { .. } => false,
|
||||
Self::Call { .. } => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub struct BlockId(pub usize);
|
||||
|
||||
impl fmt::Display for BlockId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "b{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct JumpArg {
|
||||
pub bid: BlockId,
|
||||
pub args: Vec<Operand>,
|
||||
}
|
||||
|
||||
impl JumpArg {
|
||||
pub fn new(bid: BlockId, args: Vec<Operand>) -> Self {
|
||||
Self { bid, args }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for JumpArg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}({:?})", self.bid, self.args)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum BlockExit {
|
||||
Jump {
|
||||
arg: JumpArg,
|
||||
},
|
||||
ConditionalJump {
|
||||
condition: Operand,
|
||||
arg_then: JumpArg,
|
||||
arg_else: JumpArg,
|
||||
},
|
||||
Switch {
|
||||
value: Operand,
|
||||
default: JumpArg,
|
||||
cases: Vec<(Constant, JumpArg)>,
|
||||
},
|
||||
Return {
|
||||
value: Operand,
|
||||
},
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Block {
|
||||
pub phinodes: Vec<Dtype>,
|
||||
pub instructions: Vec<Instruction>,
|
||||
pub exit: BlockExit,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user