use inkwell::AddressSpace;
use inkwell::module::Linkage;
use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum, StructType};
use inkwell::values::{BasicMetadataValueEnum, FunctionValue, IntValue, PointerValue};
use sv_lang::kinds::ExpressionKind;
use sv_lang::{BinaryOp, Expression, KnownSystemName};
use sv_sim_runtime::strings_rt::{
SV_RT_STR_CSTR_SYMBOL, SV_RT_STR_FROM_REAL_SYMBOL, SV_RT_STR_INTERN_SYMBOL,
SV_RT_STR_OP_SYMBOL, SV_RT_STR_SFORMATF_SYMBOL, SV_RT_STR_TO_REAL_SYMBOL,
};
use sv_sim_sim::strings::StrOp;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr, lower_expr_h};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue, ValueRepr};
#[derive(Clone, Copy, Debug)]
pub struct StrValue<'ctx> {
pub llvm: IntValue<'ctx>,
}
fn rt_fn<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
name: &str,
ret: Option<BasicTypeEnum<'ctx>>,
params: &[BasicMetadataTypeEnum<'ctx>],
) -> FunctionValue<'ctx> {
cx.module.get_function(name).unwrap_or_else(|| {
let ty = match ret {
Some(r) => r.fn_type(params, false),
None => cx.ctx.void_type().fn_type(params, false),
};
cx.module.add_function(name, ty, Some(Linkage::External))
})
}
fn call_i64<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
f: FunctionValue<'ctx>,
args: &[BasicMetadataValueEnum<'ctx>],
) -> IntValue<'ctx> {
cx.b.build_call(f, args, "str_call")
.unwrap()
.try_as_basic_value()
.basic()
.expect("returns a value")
.into_int_value()
}
pub(crate) fn str_op<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
op: StrOp,
a: IntValue<'ctx>,
b: IntValue<'ctx>,
c: IntValue<'ctx>,
) -> IntValue<'ctx> {
let i64t = cx.ctx.i64_type();
let f = rt_fn(
cx,
SV_RT_STR_OP_SYMBOL,
Some(i64t.into()),
&[
cx.ctx.i32_type().into(),
i64t.into(),
i64t.into(),
i64t.into(),
],
);
call_i64(
cx,
f,
&[
cx.ctx.i32_type().const_int(op as u64, false).into(),
a.into(),
b.into(),
c.into(),
],
)
}
fn i64c<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, v: u64) -> IntValue<'ctx> {
cx.ctx.i64_type().const_int(v, false)
}
pub(crate) fn literal<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, text: &[u8]) -> StrValue<'ctx> {
if text.iter().all(|b| *b == 0) {
return StrValue { llvm: i64c(cx, 0) };
}
let i64t = cx.ctx.i64_type();
let bytes = cx.ctx.const_string(text, false);
let data = cx.module.add_global(bytes.get_type(), None, "strlit_bytes");
data.set_initializer(&bytes);
data.set_constant(true);
data.set_linkage(Linkage::Private);
let cache = cx.module.add_global(i64t, None, "strlit_id");
cache.set_initializer(&i64t.const_zero());
cache.set_linkage(Linkage::Private);
let cur_bb = cx.b.get_insert_block().expect("positioned");
let func = cur_bb.get_parent().expect("in a function");
let miss = cx.ctx.append_basic_block(func, "strlit_intern");
let done = cx.ctx.append_basic_block(func, "strlit_done");
let cached =
cx.b.build_load(i64t, cache.as_pointer_value(), "strlit_cached")
.unwrap()
.into_int_value();
let hit =
cx.b.build_int_compare(
inkwell::IntPredicate::NE,
cached,
i64t.const_zero(),
"strlit_hit",
)
.unwrap();
cx.b.build_conditional_branch(hit, done, miss).unwrap();
cx.b.position_at_end(miss);
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let intern = rt_fn(
cx,
SV_RT_STR_INTERN_SYMBOL,
Some(i64t.into()),
&[ptr_ty.into(), i64t.into()],
);
let id = call_i64(
cx,
intern,
&[
data.as_pointer_value().into(),
i64c(cx, text.len() as u64).into(),
],
);
cx.b.build_store(cache.as_pointer_value(), id).unwrap();
let miss_end = cx.b.get_insert_block().unwrap();
cx.b.build_unconditional_branch(done).unwrap();
cx.b.position_at_end(done);
let phi = cx.b.build_phi(i64t, "strlit").unwrap();
phi.add_incoming(&[(&cached, cur_bb), (&id, miss_end)]);
StrValue {
llvm: phi.as_basic_value().into_int_value(),
}
}
pub(crate) fn intern_c<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ptr: PointerValue<'ctx>,
) -> StrValue<'ctx> {
let i64t = cx.ctx.i64_type();
let f = rt_fn(
cx,
SV_RT_STR_INTERN_SYMBOL,
Some(i64t.into()),
&[cx.ctx.ptr_type(AddressSpace::default()).into(), i64t.into()],
);
StrValue {
llvm: call_i64(cx, f, &[ptr.into(), i64c(cx, u64::MAX).into()]),
}
}
pub(crate) fn intern_bytes<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
ptr: PointerValue<'ctx>,
len: IntValue<'ctx>,
) -> StrValue<'ctx> {
let i64t = cx.ctx.i64_type();
let f = rt_fn(
cx,
SV_RT_STR_INTERN_SYMBOL,
Some(i64t.into()),
&[cx.ctx.ptr_type(AddressSpace::default()).into(), i64t.into()],
);
StrValue {
llvm: call_i64(cx, f, &[ptr.into(), len.into()]),
}
}
pub(crate) fn c_str<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, s: StrValue<'ctx>) -> PointerValue<'ctx> {
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = rt_fn(
cx,
SV_RT_STR_CSTR_SYMBOL,
Some(ptr_ty.into()),
&[cx.ctx.i64_type().into()],
);
cx.b.build_call(f, &[s.llvm.into()], "cstr")
.unwrap()
.try_as_basic_value()
.basic()
.unwrap()
.into_pointer_value()
}
pub(crate) fn as_packed<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, s: StrValue<'ctx>) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
op.pack(s.llvm, op.i_n(64).const_zero(), 64, cx.repr, false)
}
pub(crate) fn packed_or_id<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: LoweredValue<'ctx>,
what: &str,
) -> Result<PackedValue<'ctx>, LoweringError> {
match v {
LoweredValue::Str(s) => Ok(as_packed(cx, s)),
other => other.into_packed(what, &cx.fn_sym),
}
}
pub(crate) fn from_storage<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
pv: PackedValue<'ctx>,
) -> StrValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let pv = crate::select::fit_width(&op, pv, 64);
StrValue {
llvm: op.lane_val(pv),
}
}
pub(crate) fn to_string<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: LoweredValue<'ctx>,
) -> Result<StrValue<'ctx>, LoweringError> {
match v {
LoweredValue::Str(s) => Ok(s),
LoweredValue::Packed(pv) => {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let known = op.lane_val(op.to_2state4(pv));
if pv.width > 128 {
let mut acc: Option<IntValue<'ctx>> = None;
for k in (0..pv.width.div_ceil(64)).rev() {
let cw = (pv.width - 64 * k).min(64);
let sh =
cx.b.build_right_shift(
known,
op.i_n(pv.width).const_int(u64::from(64 * k), false),
false,
"str_chunk_sh",
)
.unwrap();
let chunk =
cx.b.build_int_truncate(sh, cx.ctx.i64_type(), "str_chunk")
.unwrap();
let part = str_op(
cx,
StrOp::FromBits,
chunk,
i64c(cx, 0),
i64c(cx, u64::from(cw)),
);
acc = Some(match acc {
None => part,
Some(prev) => str_op(cx, StrOp::Concat, prev, part, i64c(cx, 0)),
});
}
return Ok(StrValue {
llvm: acc.expect("a value has at least one chunk"),
});
}
let w = pv.width.min(128);
let v128 = crate::select::fit_width(
&op,
PackedValue {
llvm: known,
width: pv.width,
repr: ValueRepr::TwoState,
signed: false,
},
128,
)
.llvm;
let lo =
cx.b.build_int_truncate(v128, cx.ctx.i64_type(), "str_lo")
.unwrap();
let hi_sh =
cx.b.build_right_shift(v128, cx.ctx.i128_type().const_int(64, false), false, "hi")
.unwrap();
let hi =
cx.b.build_int_truncate(hi_sh, cx.ctx.i64_type(), "str_hi")
.unwrap();
Ok(StrValue {
llvm: str_op(cx, StrOp::FromBits, lo, hi, i64c(cx, u64::from(w))),
})
}
LoweredValue::Huge(h) => Ok(crate::huge::to_string(cx, h)),
LoweredValue::Real(_) | LoweredValue::Wide(_) => Err(LoweringError::unsupported_at(
"a real or >128-bit value converted to a string",
&cx.fn_sym,
)),
}
}
const WORD_LOOP_BITS: u32 = 1024;
pub(crate) fn to_bits<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
s: StrValue<'ctx>,
width: u32,
signed: bool,
) -> Result<PackedValue<'ctx>, LoweringError> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
if width > 128 {
let t = op.i_n(width);
let nwords = width.div_ceil(64);
let word = |k: u32| {
str_op(
cx,
StrOp::ToBitsWord,
s.llvm,
i64c(cx, u64::from(width)),
i64c(cx, u64::from(k)),
)
};
let v = if width > WORD_LOOP_BITS {
let buf = crate::huge::acquire(cx, nwords);
let i64t = cx.ctx.i64_type();
for k in 0..nwords {
let slot = unsafe {
cx.b.build_in_bounds_gep(
i64t.array_type(nwords),
buf,
&[i64t.const_zero(), i64t.const_int(u64::from(k), false)],
"str_word_slot",
)
.unwrap()
};
cx.b.build_store(slot, word(k)).unwrap();
}
let v =
cx.b.build_load(t, buf, "str_bits")
.unwrap()
.into_int_value();
crate::huge::release(buf);
v
} else {
let mut v = t.const_zero();
for k in 0..nwords {
let w = cx.b.build_int_z_extend(word(k), t, "str_word").unwrap();
let w =
cx.b.build_left_shift(w, t.const_int(u64::from(64 * k), false), "str_word_sh")
.unwrap();
v = cx.b.build_or(v, w, "str_bits").unwrap();
}
v
};
return Ok(op.pack(v, t.const_zero(), width, cx.repr, signed));
}
let lo = str_op(
cx,
StrOp::ToBitsLo,
s.llvm,
i64c(cx, u64::from(width)),
i64c(cx, 0),
);
let hi = str_op(
cx,
StrOp::ToBitsHi,
s.llvm,
i64c(cx, u64::from(width)),
i64c(cx, 0),
);
let i128t = cx.ctx.i128_type();
let lo = cx.b.build_int_z_extend(lo, i128t, "lo").unwrap();
let hi = cx.b.build_int_z_extend(hi, i128t, "hi").unwrap();
let hi =
cx.b.build_left_shift(hi, i128t.const_int(64, false), "hi_sh")
.unwrap();
let v = cx.b.build_or(lo, hi, "bits").unwrap();
let v = crate::select::fit_width(
&op,
PackedValue {
llvm: v,
width: 128,
repr: ValueRepr::TwoState,
signed: false,
},
width,
)
.llvm;
Ok(op.pack(v, op.i_n(width).const_zero(), width, cx.repr, signed))
}
pub(crate) fn lower_string_expr<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<Option<StrValue<'ctx>>, LoweringError> {
let kind = e.kind();
let stream = if kind == ExpressionKind::Streaming {
Some(e)
} else if kind == ExpressionKind::Conversion {
e.conversion_operand().filter(|i| {
i.kind() == ExpressionKind::Streaming || crate::stream::is_collection_operand(*i)
})
} else {
None
};
if let Some(inner) = stream {
return Ok(Some(crate::stream::stream_to_string(cx, inner)?));
}
Ok(Some(if kind == ExpressionKind::StringLiteral {
literal(cx, e.string_literal_value().unwrap_or_default().as_bytes())
} else if kind == ExpressionKind::Conversion {
let inner = e.conversion_operand().ok_or_else(|| {
LoweringError::unsupported_at("conversion missing operand", &cx.fn_sym)
})?;
if inner.kind() == ExpressionKind::StringLiteral {
literal(
cx,
inner.string_literal_value().unwrap_or_default().as_bytes(),
)
} else {
to_string(cx, lower_expr_h(cx, inner)?)?
}
} else if let Some(text) = (kind == ExpressionKind::Call
&& e.is_system_call() == Some(true)
&& !e.system_subroutine().is_some_and(|s| {
matches!(
s.known_name_id(),
KnownSystemName::SFormatF | KnownSystemName::PSPrintF
)
}))
.then(|| e.constant_value())
.flatten()
.and_then(|cv| cv.as_str().map(str::to_owned))
{
literal(cx, text.as_bytes())
} else if kind == ExpressionKind::Concatenation {
let mut acc: Option<IntValue<'ctx>> = None;
for part in e.operands() {
let s = to_string(cx, lower_expr_h(cx, part)?)?;
acc = Some(match acc {
None => s.llvm,
Some(a) => str_op(cx, StrOp::Concat, a, s.llvm, i64c(cx, 0)),
});
}
StrValue {
llvm: acc.unwrap_or_else(|| i64c(cx, 0)),
}
} else if kind == ExpressionKind::Replication {
let ops = e.operands();
let (count, inner) = match ops.as_slice() {
[count, inner] => (*count, *inner),
_ => {
return Err(LoweringError::unsupported_at(
"string replication needs a count and an operand",
&cx.fn_sym,
));
}
};
let n = lower_expr(cx, count)?.into_packed("replication count", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let n = crate::select::fit_width(&op, op.to_2state4(n), 64);
let s = to_string(cx, lower_expr_h(cx, inner)?)?;
StrValue {
llvm: str_op(cx, StrOp::Replicate, s.llvm, op.lane_val(n), i64c(cx, 0)),
}
} else {
return Ok(None);
}))
}
pub(crate) fn lower_string_binary<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
bop: BinaryOp,
) -> Result<Option<LoweredValue<'ctx>>, LoweringError> {
let (Some(l), Some(r)) = (e.left(), e.right()) else {
return Ok(None);
};
let (a, b) = (
to_string(cx, lower_expr_h(cx, l)?)?,
to_string(cx, lower_expr_h(cx, r)?)?,
);
use inkwell::IntPredicate as P;
let (pred, by_compare) = match bop {
BinaryOp::Equality | BinaryOp::CaseEquality | BinaryOp::WildcardEquality => (P::EQ, false),
BinaryOp::Inequality | BinaryOp::CaseInequality | BinaryOp::WildcardInequality => {
(P::NE, false)
}
BinaryOp::LessThan => (P::SLT, true),
BinaryOp::LessThanEqual => (P::SLE, true),
BinaryOp::GreaterThan => (P::SGT, true),
BinaryOp::GreaterThanEqual => (P::SGE, true),
_ => return Ok(None),
};
let bit = if by_compare {
let c = str_op(cx, StrOp::Compare, a.llvm, b.llvm, i64c(cx, 0));
cx.b.build_int_compare(pred, c, i64c(cx, 0), "str_rel")
.unwrap()
} else {
cx.b.build_int_compare(pred, a.llvm, b.llvm, "str_eq")
.unwrap()
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(Some(LoweredValue::Packed(op.pack(
bit,
op.i_n(1).const_zero(),
1,
cx.repr,
false,
))))
}
pub(crate) fn lower_string_inside<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
left: Expression<'d>,
candidates: Vec<Expression<'d>>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use inkwell::IntPredicate as P;
if candidates.is_empty() {
return Err(LoweringError::unsupported_at(
"inside with an empty candidate list",
&cx.fn_sym,
));
}
let a = to_string(cx, lower_expr_h(cx, left)?)?;
let missing = |which: &str| {
LoweringError::unsupported_at(&format!("inside range missing {which} bound"), &cx.fn_sym)
};
let mut acc: Option<IntValue<'ctx>> = None;
for cand in candidates {
let hit = if cand.kind() == ExpressionKind::ValueRange {
crate::expr::refuse_tolerance_range(cand, &cx.fn_sym)?;
let lo = cand.left().ok_or_else(|| missing("low"))?;
let hi = cand.right().ok_or_else(|| missing("high"))?;
let lo = to_string(cx, lower_expr_h(cx, lo)?)?;
let hi = to_string(cx, lower_expr_h(cx, hi)?)?;
let zero = i64c(cx, 0);
let c_lo = str_op(cx, StrOp::Compare, a.llvm, lo.llvm, zero);
let c_hi = str_op(cx, StrOp::Compare, a.llvm, hi.llvm, zero);
let ge = cx.b.build_int_compare(P::SGE, c_lo, zero, "in_lo").unwrap();
let le = cx.b.build_int_compare(P::SLE, c_hi, zero, "in_hi").unwrap();
cx.b.build_and(ge, le, "in_rng").unwrap()
} else {
let c = to_string(cx, lower_expr_h(cx, cand)?)?;
cx.b.build_int_compare(P::EQ, a.llvm, c.llvm, "in_eq")
.unwrap()
};
acc = Some(match acc {
None => hit,
Some(prev) => cx.b.build_or(prev, hit, "in_or").unwrap(),
});
}
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(LoweredValue::Packed(op.pack(
acc.expect("candidates non-empty"),
op.i_n(1).const_zero(),
1,
cx.repr,
false,
)))
}
pub(crate) fn len_of<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let s = to_string(cx, lower_expr_h(cx, e)?)?;
Ok(str_op(cx, StrOp::Len, s.llvm, i64c(cx, 0), i64c(cx, 0)))
}
pub(crate) fn lower_getc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
base: Expression<'d>,
index: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let s = to_string(cx, lower_expr_h(cx, base)?)?;
let i = index_i64(cx, index)?;
let c = str_op(cx, StrOp::Getc, s.llvm, i, i64c(cx, 0));
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let byte = cx.b.build_int_truncate(c, op.i_n(8), "getc").unwrap();
Ok(LoweredValue::Packed(op.pack(
byte,
op.i_n(8).const_zero(),
8,
cx.repr,
false,
)))
}
pub(crate) fn index_i64<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: Expression<'d>,
) -> Result<IntValue<'ctx>, LoweringError> {
let pv = lower_expr(cx, e)?.into_packed("a string index", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let known = op.to_2state4(pv);
let v = op.lane_val(known);
Ok(if pv.width < 64 {
if pv.signed {
cx.b.build_int_s_extend(v, cx.ctx.i64_type(), "idx")
.unwrap()
} else {
cx.b.build_int_z_extend(v, cx.ctx.i64_type(), "idx")
.unwrap()
}
} else {
crate::select::fit_width(&op, known, 64).llvm
})
}
pub(crate) fn assign_char<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
lhs: Expression<'d>,
value: PackedValue<'ctx>,
) -> Result<bool, LoweringError> {
if lhs.kind() != ExpressionKind::ElementSelect {
return Ok(false);
}
let (Some(base), Some(index)) = (lhs.select_value(), lhs.selector()) else {
return Ok(false);
};
if !base.expr_type().is_some_and(|t| t.is_string()) {
return Ok(false);
}
let s = to_string(cx, lower_expr_h(cx, base)?)?;
let i = index_i64(cx, index)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let c = crate::select::fit_width(&op, op.to_2state4(value), 64);
let new = str_op(cx, StrOp::Putc, s.llvm, i, op.lane_val(c));
crate::select::lower_assign_to(cx, base, as_packed(cx, StrValue { llvm: new }))?;
Ok(true)
}
pub(crate) fn lower_string_sysfunc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
use KnownSystemName as K;
let is_method = matches!(
name,
K::Len
| K::Getc
| K::Putc
| K::ToUpper
| K::ToLower
| K::Compare
| K::ICompare
| K::Substr
| K::AToI
| K::AToHex
| K::AToOct
| K::AToBin
| K::AToReal
| K::IToA
| K::HexToA
| K::OctToA
| K::BinToA
| K::RealToA
| K::Left
| K::Right
| K::Low
| K::High
| K::Size
);
let args = e.operands();
if is_method
&& !args
.first()
.and_then(|a| a.expr_type())
.is_some_and(|t| t.is_string())
{
return None;
}
if !is_method
&& !matches!(
name,
K::SFormatF
| K::PSPrintF
| K::SFormat
| K::SWrite
| K::SWriteB
| K::SWriteO
| K::SWriteH
)
{
return None;
}
Some(lower_string_call(cx, name, e))
}
fn lower_string_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use KnownSystemName as K;
let args = e.operands();
let arg = |i: usize| {
args.get(i).copied().ok_or_else(|| {
LoweringError::unsupported_at("string method missing an argument", &cx.fn_sym)
})
};
let target = |i: usize| -> Result<Expression<'d>, LoweringError> {
let a = arg(i)?;
Ok(if a.kind() == ExpressionKind::Assignment {
a.left().unwrap_or(a)
} else {
a
})
};
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let int = |v: IntValue<'ctx>, width: u32, signed: bool| {
let t =
cx.b.build_int_truncate_or_bit_cast(v, op.i_n(width), "str_int")
.unwrap();
LoweredValue::Packed(op.pack(t, op.i_n(width).const_zero(), width, cx.repr, signed))
};
let this =
|| -> Result<StrValue<'ctx>, LoweringError> { to_string(cx, lower_expr_h(cx, arg(0)?)?) };
let zero = i64c(cx, 0);
let placeholder = || {
LoweredValue::Packed(op.pack(
op.i_n(1).const_zero(),
op.i_n(1).const_zero(),
1,
cx.repr,
false,
))
};
let store_this = |new: IntValue<'ctx>| -> Result<LoweredValue<'ctx>, LoweringError> {
crate::select::lower_assign_to(cx, target(0)?, as_packed(cx, StrValue { llvm: new }))?;
Ok(placeholder())
};
Ok(match name {
K::Len | K::Size => int(str_op(cx, StrOp::Len, this()?.llvm, zero, zero), 32, true),
K::Left | K::Low => int(zero, 32, true),
K::Right | K::High => {
let len = str_op(cx, StrOp::Len, this()?.llvm, zero, zero);
let last = cx.b.build_int_sub(len, i64c(cx, 1), "str_high").unwrap();
int(last, 32, true)
}
K::Getc => return lower_getc(cx, arg(0)?, arg(1)?),
K::ToUpper => LoweredValue::Str(StrValue {
llvm: str_op(cx, StrOp::ToUpper, this()?.llvm, zero, zero),
}),
K::ToLower => LoweredValue::Str(StrValue {
llvm: str_op(cx, StrOp::ToLower, this()?.llvm, zero, zero),
}),
K::Compare | K::ICompare => {
let other = to_string(cx, lower_expr_h(cx, arg(1)?)?)?;
let which = if name == K::Compare {
StrOp::Compare
} else {
StrOp::ICompare
};
int(str_op(cx, which, this()?.llvm, other.llvm, zero), 32, true)
}
K::Substr => {
let s = this()?;
let (i, j) = (index_i64(cx, arg(1)?)?, index_i64(cx, arg(2)?)?);
LoweredValue::Str(StrValue {
llvm: str_op(cx, StrOp::Substr, s.llvm, i, j),
})
}
K::AToI | K::AToHex | K::AToOct | K::AToBin => {
let which = match name {
K::AToI => StrOp::AToI,
K::AToHex => StrOp::AToHex,
K::AToOct => StrOp::AToOct,
_ => StrOp::AToBin,
};
int(str_op(cx, which, this()?.llvm, zero, zero), 32, true)
}
K::AToReal => {
let f = rt_fn(
cx,
SV_RT_STR_TO_REAL_SYMBOL,
Some(cx.ctx.f64_type().into()),
&[cx.ctx.i64_type().into()],
);
let r =
cx.b.build_call(f, &[this()?.llvm.into()], "atoreal")
.unwrap()
.try_as_basic_value()
.basic()
.unwrap()
.into_float_value();
LoweredValue::Real(RealValue { llvm: r })
}
K::Putc => {
let s = this()?;
let i = index_i64(cx, arg(1)?)?;
let c = lower_expr(cx, arg(2)?)?.into_packed("putc character", &cx.fn_sym)?;
let c = crate::select::fit_width(&op, op.to_2state4(c), 64);
store_this(str_op(cx, StrOp::Putc, s.llvm, i, op.lane_val(c)))?
}
K::IToA | K::HexToA | K::OctToA | K::BinToA => {
let v = lower_expr(cx, arg(1)?)?.into_packed("itoa value", &cx.fn_sym)?;
let width = v.width.min(64);
let bits = crate::select::fit_width(&op, op.to_2state4(v), 64);
let which = match name {
K::IToA => StrOp::IToA,
K::HexToA => StrOp::HexToA,
K::OctToA => StrOp::OctToA,
_ => StrOp::BinToA,
};
let flags = u64::from(width) | if v.signed { 0x100 } else { 0 };
store_this(str_op(cx, which, op.lane_val(bits), zero, i64c(cx, flags)))?
}
K::RealToA => {
let r = lower_expr(cx, arg(1)?)?.into_real("realtoa value", &cx.fn_sym)?;
let f = rt_fn(
cx,
SV_RT_STR_FROM_REAL_SYMBOL,
Some(cx.ctx.i64_type().into()),
&[cx.ctx.f64_type().into()],
);
let id = call_i64(cx, f, &[crate::real::as_f64(cx, r.llvm).into()]);
store_this(id)?
}
K::SFormatF | K::PSPrintF => LoweredValue::Str(sformat(cx, &args, false, e)?),
K::SFormat => {
let s = sformat(cx, &args[1..], false, e)?;
crate::select::lower_assign_to(cx, target(0)?, dest_value(cx, target(0)?, s)?)?;
placeholder()
}
K::SWrite | K::SWriteB | K::SWriteO | K::SWriteH => {
let s = sformat(cx, &args[1..], true, e)?;
crate::select::lower_assign_to(cx, target(0)?, dest_value(cx, target(0)?, s)?)?;
placeholder()
}
_ => unreachable!("filtered by lower_string_sysfunc"),
})
}
fn dest_value<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
dest: Expression<'d>,
s: StrValue<'ctx>,
) -> Result<PackedValue<'ctx>, LoweringError> {
let ty = dest
.expr_type()
.ok_or_else(|| LoweringError::unsupported_at("$sformat target has no type", &cx.fn_sym))?;
if ty.is_string() {
Ok(as_packed(cx, s))
} else {
to_bits(cx, s, ty.value_width(), ty.is_signed())
}
}
fn sformat<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
args: &[Expression<'d>],
arg_list: bool,
call: Expression<'d>,
) -> Result<StrValue<'ctx>, LoweringError> {
let literal_format = args.first().is_some_and(|f| {
let mut f = *f;
while f.kind() == ExpressionKind::Conversion {
match f.conversion_operand() {
Some(inner) => f = inner,
None => break,
}
}
f.kind() == ExpressionKind::StringLiteral
});
let (fmt_ptr, vals) = if arg_list || literal_format {
crate::systask::display_format_and_values(cx, args, call)?
} else {
let (fmt, rest) = args.split_first().ok_or_else(|| {
LoweringError::unsupported_at("$sformatf needs a format string", &cx.fn_sym)
})?;
let fmt_ptr = crate::systask::lower_display_format(cx, *fmt, call)?;
let mut vals = Vec::with_capacity(rest.len());
for v in rest {
vals.push(if v.kind() == ExpressionKind::StringLiteral {
LoweredValue::Str(literal(
cx,
v.string_literal_value().unwrap_or_default().as_bytes(),
))
} else {
lower_expr(cx, *v)?
});
}
(fmt_ptr, vals)
};
sformat_with(cx, fmt_ptr, vals)
}
pub(crate) fn sformat_with<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
fmt_ptr: PointerValue<'ctx>,
vals: Vec<LoweredValue<'ctx>>,
) -> Result<StrValue<'ctx>, LoweringError> {
let rtarg_ty = crate::systask::rtarg_llvm_type(cx.ctx);
let (args_ptr, n) = crate::systask::build_rtarg_array(cx, rtarg_ty, vals)?;
let ptr_ty = cx.ctx.ptr_type(AddressSpace::default());
let f = rt_fn(
cx,
SV_RT_STR_SFORMATF_SYMBOL,
Some(cx.ctx.i64_type().into()),
&[ptr_ty.into(), ptr_ty.into(), cx.ctx.i32_type().into()],
);
Ok(StrValue {
llvm: call_i64(cx, f, &[fmt_ptr.into(), args_ptr.into(), n.into()]),
})
}
pub(crate) fn store_str_rtarg<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
rtarg_ty: StructType<'ctx>,
elem_ptr: PointerValue<'ctx>,
s: StrValue<'ctx>,
) {
let ctx = cx.ctx;
let b = &cx.b;
let i32_ty = ctx.i32_type();
let ptr_ty = ctx.ptr_type(AddressSpace::default());
let field = |idx: u32, name: &str| b.build_struct_gep(rtarg_ty, elem_ptr, idx, name).unwrap();
let len = str_op(cx, StrOp::Len, s.llvm, i64c(cx, 0), i64c(cx, 0));
let len32 = b.build_int_truncate(len, i32_ty, "str_len").unwrap();
let data = c_str(cx, s);
b.build_store(field(0, "rtarg_kind"), i32_ty.const_int(2, false))
.unwrap();
b.build_store(
field(1, "rtarg_width"),
b.build_int_mul(len32, i32_ty.const_int(8, false), "w")
.unwrap(),
)
.unwrap();
b.build_store(field(2, "rtarg_signed"), ctx.i8_type().const_zero())
.unwrap();
b.build_store(field(3, "rtarg_narrow"), ctx.i128_type().const_zero())
.unwrap();
b.build_store(field(4, "rtarg_real"), ctx.f64_type().const_zero())
.unwrap();
b.build_store(field(5, "rtarg_wide_aval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(6, "rtarg_wide_bval"), ptr_ty.const_null())
.unwrap();
b.build_store(field(7, "rtarg_wide_words"), i32_ty.const_zero())
.unwrap();
b.build_store(field(8, "rtarg_str_ptr"), data).unwrap();
b.build_store(field(9, "rtarg_str_len"), len32).unwrap();
}