use inkwell::AddressSpace;
use inkwell::IntPredicate;
use inkwell::module::Linkage;
use inkwell::types::StructType;
use inkwell::values::{FunctionValue, IntValue, PointerValue};
use sv_lang::KnownSystemName;
use sv_lang::kinds::ExpressionKind;
use crate::error::LoweringError;
use crate::expr::{LowerCtx, LoweredValue, lower_expr};
use crate::ops::OpBuilder;
use crate::types::TypeWidth;
use crate::value::{PackedValue, RealValue};
pub const SV_RT_FSCAN_SYMBOL: &str = "sv_rt_fscan";
pub const SV_RT_SSCAN_SYMBOL: &str = "sv_rt_sscan";
pub const SV_RT_FGETS_SYMBOL: &str = "sv_rt_fgets";
pub const SV_RT_FGETC_SYMBOL: &str = "sv_rt_fgetc";
pub const SV_RT_UNGETC_SYMBOL: &str = "sv_rt_ungetc";
pub const SV_RT_FTELL_SYMBOL: &str = "sv_rt_ftell";
pub const SV_RT_FERROR_SYMBOL: &str = "sv_rt_ferror";
pub(crate) fn lower_file_read_sysfunc<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
name: KnownSystemName,
e: sv_lang::Expression<'d>,
) -> Option<Result<LoweredValue<'ctx>, LoweringError>> {
Some(match name {
KnownSystemName::FScanf => lower_scan(cx, e, false),
KnownSystemName::SScanf => lower_scan(cx, e, true),
KnownSystemName::FGets => lower_fgets(cx, e),
KnownSystemName::FRead => lower_fread(cx, e),
KnownSystemName::FGetC => lower_fd_call(cx, e, SV_RT_FGETC_SYMBOL, &[Arg::Fd(0)], I32),
KnownSystemName::UngetC => {
lower_fd_call(cx, e, SV_RT_UNGETC_SYMBOL, &[Arg::Int(0), Arg::Fd(1)], I32)
}
KnownSystemName::FTell => lower_fd_call(cx, e, SV_RT_FTELL_SYMBOL, &[Arg::Fd(0)], I64),
KnownSystemName::Rewind => lower_rewind(cx, e),
KnownSystemName::FError => lower_ferror(cx, e),
_ => return None,
})
}
const I32: u32 = 32;
const I64: u32 = 64;
enum Arg {
Fd(usize),
Int(usize),
}
fn i32_operand<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
n: usize,
) -> Result<IntValue<'ctx>, LoweringError> {
let operand = e.operands().get(n).copied().ok_or_else(|| {
LoweringError::unsupported_at("a file system function missing an argument", &cx.fn_sym)
})?;
let v = lower_expr(cx, operand)?.into_packed("a file argument", &cx.fn_sym)?;
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
Ok(op.resize_unsigned(op.lane_val(v), v.width, 32))
}
fn declare<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
name: &str,
ty: inkwell::types::FunctionType<'ctx>,
) -> FunctionValue<'ctx> {
cx.module
.get_function(name)
.unwrap_or_else(|| cx.module.add_function(name, ty, Some(Linkage::External)))
}
fn int_result<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
v: IntValue<'ctx>,
width: u32,
) -> LoweredValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
LoweredValue::Packed(op.pack(v, op.i_n(width).const_zero(), width, cx.repr, true))
}
fn lower_fd_call<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
name: &str,
args: &[Arg],
ret_width: u32,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let i32t = cx.ctx.i32_type();
let params: Vec<_> = args.iter().map(|_| i32t.into()).collect();
let ret = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.i_n(ret_width);
let f = declare(cx, name, ret.fn_type(¶ms, false));
let vals = args
.iter()
.map(|a| match a {
Arg::Fd(n) | Arg::Int(n) => i32_operand(cx, e, *n).map(Into::into),
})
.collect::<Result<Vec<_>, _>>()?;
let r =
cx.b.build_call(f, &vals, "rt_file_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
if ret_width > I32 {
let r32 =
cx.b.build_int_truncate(r, cx.ctx.i32_type(), "rt_file_i32")
.unwrap();
return Ok(int_result(cx, r32, I32));
}
Ok(int_result(cx, r, ret_width))
}
fn lower_ferror<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let target = operands
.get(1)
.copied()
.map(output_target)
.ok_or_else(|| LoweringError::unsupported_at("$ferror needs (fd, str)", &cx.fn_sym))?;
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let f = declare(
cx,
SV_RT_FERROR_SYMBOL,
i32t.fn_type(&[i32t.into(), ptr.into()], false),
);
let fd = i32_operand(cx, e, 0)?;
let out = crate::expr::entry_alloca(cx, i64t, "ferror_out");
let errno =
cx.b.build_call(f, &[fd.into(), out.into()], "rt_ferror_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let id =
cx.b.build_load(i64t, out, "ferror_id")
.unwrap()
.into_int_value();
let s = crate::strings::from_storage(
cx,
PackedValue {
llvm: OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.pack(id, i64t.const_zero(), 64, cx.repr, false)
.llvm,
width: 64,
repr: cx.repr,
signed: false,
},
);
store_output(cx, target, LoweredValue::Str(s))?;
Ok(int_result(cx, errno, I32))
}
fn lower_rewind<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let f = declare(
cx,
sv_sim_runtime::file_io::SV_RT_FSEEK_SYMBOL,
i32t.fn_type(&[i32t.into(), i64t.into(), i32t.into()], false),
);
let fd = i32_operand(cx, e, 0)?;
let r =
cx.b.build_call(
f,
&[
fd.into(),
i64t.const_zero().into(),
i32t.const_zero().into(),
],
"rt_rewind_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
Ok(int_result(cx, r, I32))
}
fn output_target<'d>(arg: sv_lang::Expression<'d>) -> sv_lang::Expression<'d> {
if arg.kind() == ExpressionKind::Assignment
&& let Some(lhs) = arg.left()
{
lhs
} else {
arg
}
}
fn when<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
cond: IntValue<'ctx>,
body: impl FnOnce() -> Result<(), LoweringError>,
) -> Result<(), LoweringError> {
let here = cx.b.get_insert_block().expect("lowering inside a block");
let f = here.get_parent().expect("a block in a function");
let then_blk = cx.ctx.append_basic_block(f, "file_then");
let join = cx.ctx.append_basic_block(f, "file_join");
cx.b.build_conditional_branch(cond, then_blk, join).unwrap();
cx.b.position_at_end(then_blk);
body()?;
cx.b.build_unconditional_branch(join).unwrap();
cx.b.position_at_end(join);
Ok(())
}
fn lower_fgets<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let target = operands
.first()
.copied()
.map(output_target)
.ok_or_else(|| LoweringError::unsupported_at("$fgets needs (str, fd)", &cx.fn_sym))?;
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let ptr = cx.ctx.ptr_type(AddressSpace::default());
let f = declare(
cx,
SV_RT_FGETS_SYMBOL,
i32t.fn_type(&[i32t.into(), ptr.into(), i32t.into()], false),
);
let fd = i32_operand(cx, e, 1)?;
let out = crate::expr::entry_alloca(cx, i64t, "fgets_out");
let cap = match target.expr_type() {
Some(t) if t.is_integral() => i32::try_from(t.value_width() / 8).unwrap_or(i32::MAX),
_ => -1,
};
let n =
cx.b.build_call(
f,
&[
fd.into(),
out.into(),
i32t.const_int(cap as u32 as u64, false).into(),
],
"rt_fgets_call",
)
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let got =
cx.b.build_int_compare(IntPredicate::SGT, n, i32t.const_zero(), "fgets_got")
.unwrap();
when(cx, got, || {
let id =
cx.b.build_load(i64t, out, "fgets_id")
.unwrap()
.into_int_value();
let s = crate::strings::from_storage(
cx,
PackedValue {
llvm: OpBuilder {
ctx: cx.ctx,
b: &cx.b,
}
.pack(id, i64t.const_zero(), 64, cx.repr, false)
.llvm,
width: 64,
repr: cx.repr,
signed: false,
},
);
store_output(cx, target, LoweredValue::Str(s))
})?;
Ok(int_result(cx, n, I32))
}
fn lower_fread<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
let target = operands
.first()
.copied()
.map(output_target)
.ok_or_else(|| LoweringError::unsupported_at("$fread needs (target, fd)", &cx.fn_sym))?;
if let Some(mem) = fread_memory(cx, target) {
return lower_fread_memory(cx, e, mem);
}
let ty = target.expr_type();
if let Some(c) = ty.as_ref().and_then(crate::collections::coll_of)
&& !c.obj
&& c.elem.is_integral()
{
return lower_fread_collection(cx, e, target, c);
}
if matches!(&ty, Some(t) if !t.is_integral()) {
return Err(LoweringError::unsupported_at(
"$fread into a collection of non-integral elements (only integral variables, \
fixed-size unpacked arrays, and queues / dynamic / associative arrays of \
integral elements are supported; slang rejects an unpacked struct target)",
&cx.fn_sym,
));
}
let width = match &ty {
Some(t) if t.is_integral() && operands.len() == 2 => t.value_width(),
_ => {
return Err(LoweringError::unsupported_at(
"$fread with a start/count into a non-memory target",
&cx.fn_sym,
));
}
};
let i32t = cx.ctx.i32_type();
let fd = i32_operand(cx, e, 1)?;
let (raw, n) = fread_bytes(cx, fd, width);
let got =
cx.b.build_int_compare(IntPredicate::SGT, n, i32t.const_zero(), "fread_got")
.unwrap();
when(cx, got, || {
let p = fread_aligned(cx, raw, n, width);
store_output(cx, target, LoweredValue::Packed(p))
})?;
Ok(int_result(cx, n, I32))
}
fn fread_memory<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
) -> Option<crate::expr::MemorySignalInfo> {
let sym = if target.kind() == ExpressionKind::ArbitrarySymbol {
target.arbitrary_symbol()
} else if sv_sim_sim::symid::is_value_name(target.kind()) {
target.referenced_symbol()
} else {
None
}?;
cx.mem_signals
.get(&sv_sim_sim::symid::SymbolUid::uid(&sym))
.copied()
}
fn fread_bytes<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
fd: IntValue<'ctx>,
width: u32,
) -> (IntValue<'ctx>, IntValue<'ctx>) {
let i32t = cx.ctx.i32_type();
let accty = fread_acc_type(cx, width);
let f = declare(cx, SV_RT_FGETC_SYMBOL, i32t.fn_type(&[i32t.into()], false));
let acc = crate::expr::entry_alloca(cx, accty, "fread_acc");
let cnt = crate::expr::entry_alloca(cx, i32t, "fread_cnt");
let alive = crate::expr::entry_alloca(cx, cx.ctx.bool_type(), "fread_alive");
cx.b.build_store(acc, accty.const_zero()).unwrap();
cx.b.build_store(cnt, i32t.const_zero()).unwrap();
cx.b.build_store(alive, cx.ctx.bool_type().const_int(1, false))
.unwrap();
for _ in 0..width.div_ceil(8) {
let a =
cx.b.build_load(cx.ctx.bool_type(), alive, "fread_alive_v")
.unwrap()
.into_int_value();
when(cx, a, || {
let c =
cx.b.build_call(f, &[fd.into()], "rt_fgetc_call")
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let ok =
cx.b.build_int_compare(IntPredicate::SGE, c, i32t.const_zero(), "fread_ok")
.unwrap();
cx.b.build_store(alive, ok).unwrap();
when(cx, ok, || {
let old =
cx.b.build_load(accty, acc, "fread_old")
.unwrap()
.into_int_value();
let sh =
cx.b.build_left_shift(old, accty.const_int(8, false), "fread_shl")
.unwrap();
let byte = cx.b.build_int_cast(c, accty, "fread_byte").unwrap();
let v = cx.b.build_or(sh, byte, "fread_acc_next").unwrap();
cx.b.build_store(acc, v).unwrap();
let n =
cx.b.build_load(i32t, cnt, "fread_n")
.unwrap()
.into_int_value();
let n1 =
cx.b.build_int_add(n, i32t.const_int(1, false), "fread_n1")
.unwrap();
cx.b.build_store(cnt, n1).unwrap();
Ok(())
})
})
.expect("fread byte step has no fallible lowering");
}
let raw =
cx.b.build_load(accty, acc, "fread_raw")
.unwrap()
.into_int_value();
let n =
cx.b.build_load(i32t, cnt, "fread_count")
.unwrap()
.into_int_value();
(raw, n)
}
fn fread_acc_type<'ctx>(cx: &LowerCtx<'ctx, '_, '_>, width: u32) -> inkwell::types::IntType<'ctx> {
cx.ctx
.custom_width_int_type(std::num::NonZeroU32::new((width.div_ceil(8) * 8).max(16)).unwrap())
.unwrap()
}
fn fread_aligned<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
raw: IntValue<'ctx>,
n: IntValue<'ctx>,
width: u32,
) -> PackedValue<'ctx> {
let i32t = cx.ctx.i32_type();
let accty = fread_acc_type(cx, width);
let nbytes = width.div_ceil(8);
let missing =
cx.b.build_int_sub(i32t.const_int(u64::from(nbytes), false), n, "fread_missing")
.unwrap();
let bits =
cx.b.build_left_shift(missing, i32t.const_int(3, false), "fread_missing_bits")
.unwrap();
let bits_acc = cx.b.build_int_cast(bits, accty, "fread_bits_acc").unwrap();
let v =
cx.b.build_left_shift(raw, bits_acc, "fread_aligned")
.unwrap();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let w = op.resize_unsigned(v, (nbytes * 8).max(16), width);
op.pack(w, op.i_n(width).const_zero(), width, cx.repr, false)
}
fn lower_fread_memory<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
mem: crate::expr::MemorySignalInfo,
) -> Result<LoweredValue<'ctx>, LoweringError> {
let operands = e.operands();
if operands.len() > 4 {
return Err(LoweringError::unsupported_at(
"$fread takes (mem, fd [, start [, count]])",
&cx.fn_sym,
));
}
let acc = cx.mem_accessor.ok_or_else(|| {
LoweringError::unsupported_at("$fread into a memory with no accessor context", &cx.fn_sym)
})?;
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd = i32_operand(cx, e, 1)?;
let lo = i64t.const_int(mem.lower_index as u64, true);
let hi = i64t.const_int((mem.lower_index + i64::from(mem.num_rows) - 1) as u64, true);
let arg = |i: usize, default: IntValue<'ctx>| -> Result<IntValue<'ctx>, LoweringError> {
match operands.get(i) {
Some(x) if x.kind() != ExpressionKind::EmptyArgument => {
let pv = lower_expr(cx, *x)?
.into_packed("$fread start/count must be integral", &cx.fn_sym)?;
Ok(crate::expr::mem_row_index_i64(&op, cx, pv))
}
_ => Ok(default),
}
};
let start = arg(2, lo)?;
let count = arg(3, i64t.const_int(i64::MAX as u64, false))?;
let in_range = {
let a =
cx.b.build_int_compare(IntPredicate::SGE, start, lo, "fread_start_ge")
.unwrap();
let b =
cx.b.build_int_compare(IntPredicate::SLE, start, hi, "fread_start_le")
.unwrap();
cx.b.build_and(a, b, "fread_start_ok").unwrap()
};
let idx = crate::expr::entry_alloca(cx, i64t, "fread_idx");
let rem = crate::expr::entry_alloca(cx, i64t, "fread_rem");
let tot = crate::expr::entry_alloca(cx, i32t, "fread_total");
cx.b.build_store(idx, start).unwrap();
let rem0 =
cx.b.build_select(in_range, count, i64t.const_zero(), "fread_rem0")
.unwrap();
cx.b.build_store(rem, rem0).unwrap();
cx.b.build_store(tot, i32t.const_zero()).unwrap();
let here = cx.b.get_insert_block().expect("lowering inside a block");
let f = here.get_parent().expect("a block in a function");
let head = cx.ctx.append_basic_block(f, "fread_mem_head");
let body = cx.ctx.append_basic_block(f, "fread_mem_body");
let done = cx.ctx.append_basic_block(f, "fread_mem_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let i =
cx.b.build_load(i64t, idx, "fread_i")
.unwrap()
.into_int_value();
let r =
cx.b.build_load(i64t, rem, "fread_r")
.unwrap()
.into_int_value();
let c1 =
cx.b.build_int_compare(IntPredicate::SLE, i, hi, "fread_i_le")
.unwrap();
let c2 =
cx.b.build_int_compare(IntPredicate::SGT, r, i64t.const_zero(), "fread_r_gt")
.unwrap();
let go = cx.b.build_and(c1, c2, "fread_go").unwrap();
cx.b.build_conditional_branch(go, body, done).unwrap();
cx.b.position_at_end(body);
let (raw, n) = fread_bytes(cx, fd, mem.elem_width);
let got =
cx.b.build_int_compare(IntPredicate::SGT, n, i32t.const_zero(), "fread_got")
.unwrap();
when(cx, got, || {
let p = fread_aligned(cx, raw, n, mem.elem_width);
let row = cx.b.build_int_sub(i, lo, "fread_row").unwrap();
crate::accessor::build_set_mem_word(
cx,
acc.fns,
acc.handle,
mem.slot,
row,
p.llvm,
mem.elem_width,
);
let t =
cx.b.build_load(i32t, tot, "fread_t")
.unwrap()
.into_int_value();
let t1 = cx.b.build_int_add(t, n, "fread_t1").unwrap();
cx.b.build_store(tot, t1).unwrap();
let i1 =
cx.b.build_int_add(i, i64t.const_int(1, false), "fread_i1")
.unwrap();
cx.b.build_store(idx, i1).unwrap();
let r1 =
cx.b.build_int_sub(r, i64t.const_int(1, false), "fread_r1")
.unwrap();
cx.b.build_store(rem, r1).unwrap();
Ok(())
})?;
let full =
cx.b.build_int_compare(
IntPredicate::EQ,
n,
i32t.const_int(u64::from(mem.elem_width.div_ceil(8)), false),
"fread_full",
)
.unwrap();
cx.b.build_conditional_branch(full, head, done).unwrap();
cx.b.position_at_end(done);
let total =
cx.b.build_load(i32t, tot, "fread_total_v")
.unwrap()
.into_int_value();
Ok(int_result(cx, total, I32))
}
fn lower_fread_collection<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
target: sv_lang::Expression<'d>,
c: crate::collections::Coll<'d>,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use crate::collections::{Args, call, elem_buf, handle_of, set_handle, size_of_handle};
use sv_sim_runtime::collections_rt::CollOp;
let operands = e.operands();
if operands.len() > 4 {
return Err(LoweringError::unsupported_at(
"$fread takes (mem, fd [, start [, count]])",
&cx.fn_sym,
));
}
let i32t = cx.ctx.i32_type();
let i64t = cx.ctx.i64_type();
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let fd = i32_operand(cx, e, 1)?;
let hi = size_of_handle(cx, &c, handle_of(cx, target)?);
let has_start = operands
.get(2)
.is_some_and(|x| x.kind() != ExpressionKind::EmptyArgument);
if c.assoc && has_start && (c.key_kind == 2 || c.key_width > 63) {
return Err(LoweringError::unsupported_at(
"$fread into an associative array with a start key, for a string or wider \
than 63-bit index",
&cx.fn_sym,
));
}
let arg = |i: usize, default: IntValue<'ctx>| -> Result<IntValue<'ctx>, LoweringError> {
match operands.get(i) {
Some(x) if x.kind() != ExpressionKind::EmptyArgument => {
let pv = lower_expr(cx, *x)?
.into_packed("$fread start/count must be integral", &cx.fn_sym)?;
Ok(crate::expr::mem_row_index_i64(&op, cx, pv))
}
_ => Ok(default),
}
};
let start = arg(2, i64t.const_zero())?;
let count = arg(3, i64t.const_int(i64::MAX as u64, false))?;
let first_pos = if c.assoc { i64t.const_zero() } else { start };
let in_range = if c.assoc {
cx.ctx.bool_type().const_int(1, false)
} else {
let a =
cx.b.build_int_compare(
IntPredicate::SGE,
start,
i64t.const_zero(),
"fread_start_ge",
)
.unwrap();
let b =
cx.b.build_int_compare(IntPredicate::SLT, start, hi, "fread_start_lt")
.unwrap();
cx.b.build_and(a, b, "fread_start_ok").unwrap()
};
let idx = crate::expr::entry_alloca(cx, i64t, "fread_idx");
let rem = crate::expr::entry_alloca(cx, i64t, "fread_rem");
let tot = crate::expr::entry_alloca(cx, i32t, "fread_total");
cx.b.build_store(idx, first_pos).unwrap();
let rem0 =
cx.b.build_select(in_range, count, i64t.const_zero(), "fread_rem0")
.unwrap();
cx.b.build_store(rem, rem0).unwrap();
cx.b.build_store(tot, i32t.const_zero()).unwrap();
let here = cx.b.get_insert_block().expect("lowering inside a block");
let f = here.get_parent().expect("a block in a function");
let head = cx.ctx.append_basic_block(f, "fread_coll_head");
let body = cx.ctx.append_basic_block(f, "fread_coll_body");
let done = cx.ctx.append_basic_block(f, "fread_coll_done");
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(head);
let i =
cx.b.build_load(i64t, idx, "fread_i")
.unwrap()
.into_int_value();
let r =
cx.b.build_load(i64t, rem, "fread_r")
.unwrap()
.into_int_value();
let c1 =
cx.b.build_int_compare(IntPredicate::SLT, i, hi, "fread_i_lt")
.unwrap();
let c2 =
cx.b.build_int_compare(IntPredicate::SGT, r, i64t.const_zero(), "fread_r_gt")
.unwrap();
let go = cx.b.build_and(c1, c2, "fread_go").unwrap();
cx.b.build_conditional_branch(go, body, done).unwrap();
cx.b.position_at_end(body);
let key = if c.assoc {
let k = crate::collections::key_at_coll(cx, &c, handle_of(cx, target)?, i);
if has_start {
let ki = crate::expr::mem_row_index_i64(&op, cx, k);
let pred = if c.key_kind == 1 {
IntPredicate::SLT
} else {
IntPredicate::ULT
};
let skip =
cx.b.build_int_compare(pred, ki, start, "fread_skip")
.unwrap();
let skipped = cx.ctx.append_basic_block(f, "fread_key_skip");
let read = cx.ctx.append_basic_block(f, "fread_key_read");
cx.b.build_conditional_branch(skip, skipped, read).unwrap();
cx.b.position_at_end(skipped);
let i1 =
cx.b.build_int_add(i, i64t.const_int(1, false), "fread_skip_i")
.unwrap();
cx.b.build_store(idx, i1).unwrap();
cx.b.build_unconditional_branch(head).unwrap();
cx.b.position_at_end(read);
}
Some(k)
} else {
None
};
let (raw, n) = fread_bytes(cx, fd, c.width);
let got =
cx.b.build_int_compare(IntPredicate::SGT, n, i32t.const_zero(), "fread_got")
.unwrap();
when(cx, got, || {
let p = fread_aligned(cx, raw, n, c.width);
let val = elem_buf(cx, &c, LoweredValue::Packed(p))?;
let h = handle_of(cx, target)?;
let new = if let Some(k) = key {
let kb = crate::collections::words_buf(
cx,
c.key_words(),
crate::collections::four_state(cx, k),
"fread_key",
);
call(
cx,
&c,
CollOp::Store,
h,
Args {
val: Some(val),
key: Some(kb),
..Args::default()
},
)
} else {
call(
cx,
&c,
CollOp::Set,
h,
Args {
index: Some(i),
index2: Some(i64t.const_int(u64::from(c.queue), false)),
val: Some(val),
..Args::default()
},
)
};
set_handle(cx, target, new)?;
let t =
cx.b.build_load(i32t, tot, "fread_t")
.unwrap()
.into_int_value();
let t1 = cx.b.build_int_add(t, n, "fread_t1").unwrap();
cx.b.build_store(tot, t1).unwrap();
let i1 =
cx.b.build_int_add(i, i64t.const_int(1, false), "fread_i1")
.unwrap();
cx.b.build_store(idx, i1).unwrap();
let r1 =
cx.b.build_int_sub(r, i64t.const_int(1, false), "fread_r1")
.unwrap();
cx.b.build_store(rem, r1).unwrap();
Ok(())
})?;
let full =
cx.b.build_int_compare(
IntPredicate::EQ,
n,
i32t.const_int(u64::from(c.width.div_ceil(8)), false),
"fread_full",
)
.unwrap();
cx.b.build_conditional_branch(full, head, done).unwrap();
cx.b.position_at_end(done);
let total =
cx.b.build_load(i32t, tot, "fread_total_v")
.unwrap()
.into_int_value();
Ok(int_result(cx, total, I32))
}
pub(crate) fn store_output<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
target: sv_lang::Expression<'d>,
value: LoweredValue<'ctx>,
) -> Result<(), LoweringError> {
let ty = target.expr_type().ok_or_else(|| {
LoweringError::unsupported_at("an output argument with no type", &cx.fn_sym)
})?;
let value =
match value {
LoweredValue::Str(s) if ty.is_integral() => LoweredValue::Packed(
crate::strings::to_bits(cx, s, ty.value_width(), ty.is_signed())?,
),
v => v,
};
crate::expr::store_to(cx, target, value).map(|_| ())
}
fn scan_out_type<'ctx>(cx: &LowerCtx<'ctx, '_, '_>) -> StructType<'ctx> {
let c = cx.ctx;
c.struct_type(
&[
c.i32_type().into(),
c.i32_type().into(),
c.ptr_type(AddressSpace::default()).into(),
c.i64_type().into(),
c.f64_type().into(),
],
false,
)
}
fn lower_scan_format<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
fmt: sv_lang::Expression<'_>,
call: sv_lang::Expression<'_>,
) -> Result<inkwell::values::PointerValue<'ctx>, LoweringError> {
let mut lit = fmt;
while lit.kind() == ExpressionKind::Conversion {
match lit.conversion_operand() {
Some(inner) => lit = inner,
None => break,
}
}
let Some(text) = (lit.kind() == ExpressionKind::StringLiteral)
.then(|| lit.string_literal_value())
.flatten()
else {
return crate::systask::lower_format_expr(cx, fmt);
};
let scope_sym = call.system_call_scope().unwrap_or(cx.fn_sym);
let (time_unit, _) = crate::timescale::unit_and_precision(&scope_sym);
let text = sv_sim_runtime::fmt::tag_time_unit(&text, time_unit);
let scope = sv_sim_sim::process::cfg::shown_path(&scope_sym);
let text = sv_sim_runtime::fmt::tag_scan_scope(&text, &scope);
Ok(crate::systask::build_c_string_global(
cx.ctx, cx.module, &text, "scan_fmt",
))
}
fn scan_format_has_unknown(fmt: sv_lang::Expression<'_>) -> bool {
let mut lit = fmt;
while lit.kind() == ExpressionKind::Conversion {
match lit.conversion_operand() {
Some(inner) => lit = inner,
None => break,
}
}
lit.kind() == ExpressionKind::IntegerLiteral
&& lit
.integer_literal_value()
.and_then(|cv| cv.as_integer().map(|iv| iv.has_unknown()))
.unwrap_or(false)
}
fn lower_scan<'ctx, 'd>(
cx: &LowerCtx<'ctx, 'd, '_>,
e: sv_lang::Expression<'d>,
from_string: bool,
) -> Result<LoweredValue<'ctx>, LoweringError> {
use sv_sim_runtime::scan::{SCAN_INT, SCAN_REAL, SCAN_STR};
let what = if from_string { "$sscanf" } else { "$fscanf" };
let operands = e.operands();
if operands.len() < 2 {
return Err(LoweringError::unsupported_at(
&format!("{what} needs a source and a format"),
&cx.fn_sym,
));
}
let c = cx.ctx;
let i32t = c.i32_type();
let i64t = c.i64_type();
let ptr = c.ptr_type(AddressSpace::default());
let out_ty = scan_out_type(cx);
let targets: Vec<_> = operands[2..].iter().copied().map(output_target).collect();
let n = targets.len() as u32;
let outs = crate::expr::entry_alloca(cx, out_ty.array_type(n.max(1)), "scan_outs");
let mut words_of = Vec::with_capacity(targets.len());
for (i, t) in targets.iter().enumerate() {
let ty = t.expr_type().ok_or_else(|| {
LoweringError::unsupported_at(&format!("a {what} output with no type"), &cx.fn_sym)
})?;
let (kind, width) = if ty.is_string() {
(SCAN_STR, 0)
} else if ty.is_floating() {
(SCAN_REAL, 0)
} else if ty.is_integral() {
(SCAN_INT, ty.value_width())
} else {
return Err(LoweringError::unsupported_at(
&format!("a {what} output of type {}", ty.to_sv_string()),
&cx.fn_sym,
));
};
let words = if kind == SCAN_INT {
let nw = width.div_ceil(64).max(1);
Some(crate::expr::entry_alloca(
cx,
i64t.array_type(2 * nw),
"scan_words",
))
} else {
None
};
let slot = unsafe {
cx.b.build_gep(
out_ty.array_type(n.max(1)),
outs,
&[i32t.const_zero(), i32t.const_int(i as u64, false)],
"scan_out",
)
}
.unwrap();
let field = |k: u32| {
cx.b.build_struct_gep(out_ty, slot, k, "scan_field")
.unwrap()
};
cx.b.build_store(field(0), i32t.const_int(u64::from(kind), false))
.unwrap();
cx.b.build_store(field(1), i32t.const_int(u64::from(width), false))
.unwrap();
cx.b.build_store(field(2), words.unwrap_or(ptr.const_null()))
.unwrap();
words_of.push((slot, kind, width, words, ty));
}
let fmt = lower_scan_format(cx, operands[1], e)?;
let matched = if from_string {
let s = crate::strings::to_string(cx, lower_expr(cx, operands[0])?)?;
let id = crate::strings::as_packed(cx, s);
let op = OpBuilder { ctx: c, b: &cx.b };
let id = op.resize_unsigned(op.lane_val(id), id.width, 64);
let f = declare(
cx,
SV_RT_SSCAN_SYMBOL,
i32t.fn_type(&[i64t.into(), ptr.into(), ptr.into(), i32t.into()], false),
);
cx.b.build_call(
f,
&[
id.into(),
fmt.into(),
outs.into(),
i32t.const_int(u64::from(n), false).into(),
],
"rt_sscan_call",
)
} else {
let fd = i32_operand(cx, e, 0)?;
let f = declare(
cx,
SV_RT_FSCAN_SYMBOL,
i32t.fn_type(&[i32t.into(), ptr.into(), ptr.into(), i32t.into()], false),
);
cx.b.build_call(
f,
&[
fd.into(),
fmt.into(),
outs.into(),
i32t.const_int(u64::from(n), false).into(),
],
"rt_fscan_call",
)
}
.unwrap()
.try_as_basic_value()
.unwrap_basic()
.into_int_value();
let matched = if scan_format_has_unknown(operands[1]) {
i32t.const_all_ones()
} else {
matched
};
for (i, (target, (slot, kind, width, words, ty))) in targets.iter().zip(&words_of).enumerate() {
let got =
cx.b.build_int_compare(
IntPredicate::SGT,
matched,
i32t.const_int(i as u64, false),
"scan_got",
)
.unwrap();
when(cx, got, || {
let value = match *kind {
SCAN_STR => {
let f = cx.b.build_struct_gep(out_ty, *slot, 3, "scan_str").unwrap();
let id =
cx.b.build_load(i64t, f, "scan_id")
.unwrap()
.into_int_value();
let op = OpBuilder { ctx: c, b: &cx.b };
LoweredValue::Str(crate::strings::from_storage(
cx,
op.pack(id, i64t.const_zero(), 64, cx.repr, false),
))
}
SCAN_REAL => {
let f =
cx.b.build_struct_gep(out_ty, *slot, 4, "scan_real")
.unwrap();
let v = cx.b.build_load(c.f64_type(), f, "scan_rv").unwrap();
let real = RealValue {
llvm: v.into_float_value(),
};
if ty.floating_kind() == sv_lang::FloatKind::ShortReal {
LoweredValue::Real(RealValue {
llvm: cx
.b
.build_float_trunc(real.llvm, c.f32_type(), "scan_short")
.unwrap(),
})
} else {
LoweredValue::Real(real)
}
}
_ => LoweredValue::Packed(PackedValue {
signed: ty.is_signed(),
..words_to_packed(cx, words.expect("an integral output has words"), *width)
}),
};
store_output(cx, *target, value)
})?;
}
Ok(int_result(cx, matched, I32))
}
fn words_to_packed<'ctx>(
cx: &LowerCtx<'ctx, '_, '_>,
words: PointerValue<'ctx>,
width: u32,
) -> PackedValue<'ctx> {
let op = OpBuilder {
ctx: cx.ctx,
b: &cx.b,
};
let i64t = cx.ctx.i64_type();
let n = width.div_ceil(64).max(1);
let wide = op.i_n(64 * n);
let plane = |base: u32| {
let mut acc = wide.const_zero();
for k in 0..n {
let p = unsafe {
cx.b.build_gep(
i64t,
words,
&[i64t.const_int(u64::from(base + k), false)],
"scan_w",
)
}
.unwrap();
let w =
cx.b.build_load(i64t, p, "scan_word")
.unwrap()
.into_int_value();
let w =
cx.b.build_int_z_extend_or_bit_cast(w, wide, "scan_wext")
.unwrap();
let w =
cx.b.build_left_shift(w, wide.const_int(u64::from(64 * k), false), "scan_wsh")
.unwrap();
acc = cx.b.build_or(acc, w, "scan_acc").unwrap();
}
op.resize_unsigned(acc, 64 * n, width)
};
let aval = plane(0);
let bval = plane(n);
let val = cx.b.build_xor(aval, bval, "scan_val").unwrap();
op.pack(val, bval, width, cx.repr, false)
}