1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Lowering of sv-lang `Type`s to LLVM types: iN for two-state integral, i(2N)
//! (value lane + unknown lane) for four-state integral, and (Task 8, CG-037)
//! a plain LLVM `FloatType` for `real`/`shortreal`/`realtime` -- floats have
//! no unknown plane, so they are never packed the way integral types are.
use std::num::NonZeroU32;
use inkwell::context::Context;
use inkwell::types::{BasicMetadataTypeEnum, BasicTypeEnum, FloatType, FunctionType, IntType};
use crate::error::LoweringError;
use crate::value::ValueRepr;
// Corrected vs. plan: inkwell 0.10 (LLVM22) `Context::custom_width_int_type`
// takes `NonZeroU32` and returns `Result<IntType, &'static str>`, not a bare
// `u32` -> `IntType`.
pub(crate) fn int_type(ctx: &Context, w: u32) -> IntType<'_> {
ctx.custom_width_int_type(NonZeroU32::new(w).expect("zero-width int type"))
.expect("custom_width_int_type")
}
/// Task 8 (CG-037): `lower_type`'s result is no longer always an `IntType`
/// -- a `real`/`shortreal` `Type` lowers to an LLVM `FloatType` instead.
/// Deviation from the plan text (which left `lower_type`'s 3 call sites in
/// `func.rs` unspecified beyond "update them"): each call site needs both a
/// `BasicTypeEnum` (for `build_alloca`) and a `BasicMetadataTypeEnum` (for
/// `fn_type`'s parameter list), so this enum carries small conversion
/// helpers for both rather than making every call site match by hand.
#[derive(Debug, Clone, Copy)]
pub enum LoweredType<'ctx> {
Int(IntType<'ctx>),
Float(FloatType<'ctx>),
}
impl<'ctx> LoweredType<'ctx> {
pub fn into_basic_type_enum(self) -> BasicTypeEnum<'ctx> {
match self {
LoweredType::Int(t) => t.into(),
LoweredType::Float(t) => t.into(),
}
}
pub fn into_basic_metadata_type_enum(self) -> BasicMetadataTypeEnum<'ctx> {
match self {
LoweredType::Int(t) => t.into(),
LoweredType::Float(t) => t.into(),
}
}
pub fn fn_type(
&self,
param_types: &[BasicMetadataTypeEnum<'ctx>],
is_var_args: bool,
) -> FunctionType<'ctx> {
match self {
LoweredType::Int(t) => t.fn_type(param_types, is_var_args),
LoweredType::Float(t) => t.fn_type(param_types, is_var_args),
}
}
}
/// A value's width in bits: `Type::bit_width()`, except for the types
/// slang reports as 0 bits that sv-sim stores as bits anyway -- a `chandle`,
/// `string`, class handle (64, classes plan Task 5 -- LRM 1800-2023 ยง8.4: a
/// class variable is an OBJECT HANDLE, never the object's contents, so its
/// own stored representation is a plain 64-bit reference exactly like a
/// `chandle`'s) or collection handle (64) and an unpacked struct/union or
/// fixed-size unpacked array (its flat stored form,
/// `sv_sim_sim::state::storage`).
pub trait TypeWidth {
fn value_width(&self) -> u32;
}
impl TypeWidth for sv_lang::Type<'_> {
fn value_width(&self) -> u32 {
if self.is_chandle()
|| self.is_covergroup()
|| self.is_virtual_interface()
|| self.is_string()
|| self.is_class()
|| sv_sim_sim::state::storage::is_collection(self)
{
64
} else if sv_sim_sim::state::storage::is_unpacked_aggregate(self)
|| (self.is_unpacked_array() && !self.is_dynamically_sized_array())
{
sv_sim_sim::state::storage::storage_width(self).unwrap_or(0) as u32
} else {
self.bit_width() as u32
}
}
}
pub fn lower_type<'ctx>(
ctx: &'ctx Context,
ty: &sv_lang::Type,
repr: ValueRepr,
) -> Result<LoweredType<'ctx>, LoweringError> {
// Task 8 (CG-037): `real`/`shortreal`/`realtime` -- checked before the
// integral check below, since a floating `Type` is never `is_integral()`
// and must not fall into that arm's error.
if ty.is_floating() {
return Ok(LoweredType::Float(match ty.floating_kind() {
sv_lang::FloatKind::ShortReal => ctx.f32_type(),
sv_lang::FloatKind::Real | sv_lang::FloatKind::RealTime => ctx.f64_type(),
}));
}
if !ty.is_integral()
&& !ty.is_chandle()
&& !ty.is_covergroup()
&& !ty.is_virtual_interface()
&& !ty.is_string()
&& !ty.is_class()
&& !sv_sim_sim::state::storage::is_collection(ty)
&& !sv_sim_sim::state::storage::is_unpacked_aggregate(ty)
{
return Err(LoweringError::unsupported(
&format!("non-integral type {}", ty.to_sv_string()),
"<type>",
));
}
// Corrected vs. plan: `Type::bit_width()` returns `u64`, not `u32`
// (do not confuse with `SVInt::bit_width() -> u32`). Cast at the call
// site before building the LLVM int type.
let n = ty.value_width();
// A packed `i(2N)` that wide is the 50-115 s JIT compile `crate::huge`
// exists to avoid: past `WIDE_THRESHOLD` it is memory-resident only. A
// value between the huge threshold and that ceiling is still an LLVM
// integer where it is stored (an argument, a return, a local, an array
// element); the huge operators read and write its bytes as an image.
if n > crate::value::WIDE_THRESHOLD {
return Err(LoweringError::unsupported(
&format!(
"a {n}-bit value of type {} here (LRM 1800-2023 6.9.1): vectors wider than {} \
bits are supported as module-level signals, not as function arguments, \
returns or locals",
ty.to_sv_string(),
crate::value::WIDE_THRESHOLD,
),
"<type>",
));
}
Ok(LoweredType::Int(match repr {
ValueRepr::TwoState => int_type(ctx, n),
ValueRepr::FourState => int_type(ctx, n * 2),
}))
}
/// An `alloca` in the entry block of the function `b` is positioned in:
/// code inside an SV loop runs on a back-edge within one activation, so an
/// `alloca` at the use site would grow the stack on every iteration (a
/// 200000-iteration loop writing a memory row overflowed the 8 MiB stack).
pub(crate) fn entry_alloca_in<'ctx, T: inkwell::types::BasicType<'ctx>>(
b: &inkwell::builder::Builder<'ctx>,
ty: T,
name: &str,
) -> inkwell::values::PointerValue<'ctx> {
let entry = b
.get_insert_block()
.and_then(|bb| bb.get_parent())
.and_then(|f| f.get_first_basic_block())
.expect("lowering happens inside a function with an entry block");
let tmp = entry.get_context().create_builder();
match entry.get_first_instruction() {
Some(first) => tmp.position_before(&first),
None => tmp.position_at_end(entry),
}
tmp.build_alloca(ty, name).unwrap()
}