I feel like I've seen this before in valgrind:
==260== Conditional jump or move depends on uninitialised value(s)
==260== at 0x1A082C: _16_d394208415ac8fe0ce8aa0ddf6a845c7cc74d818698e3d25c85705ce311f5ec (in /usr/src/app)
==260== by 0x1A235F: roc__mainForHost_0_caller (in /usr/src/app)
==260== by 0x7AC92F: roc_app::RocFunction_89::force_thunk (lib.rs:44)
anyone have any info on why it happens, or what it's referring to?
it means you're probably comparing against a memory cell that wasn't initialized or set to nullptr
so , probably some value in the condition at roc_app::RocFunction_89::force_thunk (lib.rs:44)
is not intialized or zeroed out
hm, here's the code in question:
impl RocFunction_89 {
pub fn force_thunk(self) -> roc_std::RocStr {
extern "C" {
fn roc__mainForHost_0_caller(
arg0: *const (),
closure_data: *mut u8,
output: *mut roc_std::RocStr,
);
}
let mut output = core::mem::MaybeUninit::uninit();
let closure_ptr =
(&mut core::mem::ManuallyDrop::new(self.closure_data)) as *mut _ as *mut u8;
unsafe {
roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());
output.assume_init()
}
}
}
line 44 is this one:
roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());
output is supposed to be uninitalized since it's being written to (so, nothing in the generated code should be looking at it, just writing to it)
the first argument is a pointer to nothing, so definitely nothing should be reading from that under any circumstances :thinking:
I guess it's possible the closure data hasn't been initialized, since that comes from a call to roc__mainForHost_1_exposed_generic
hm, I'll try manually initializing each of those and see if the error goes away for one of them
Are you running this with optimized roc or non-opt?
I think in a number of cases non-opt roc theoretically looks at uninitialized values, but in an impossible way. Like the branch is known false at compile time but we still emit it
yeah turns out the closure data is uninitialized
if I print its length, it's different on every run, and also valgrind gives additional errors for printing the length :laughing:
(reading from uninitialized memory errors, specifically)
Do you have closure data that should be loaded here?
Is main for host accidentally capturing something?
#[repr(C)]
#[derive(Debug, Clone)]
pub struct RocFunction_89 {
closure_data: roc_std::RocList<u8>,
}
impl RocFunction_89 {
pub fn force_thunk(self) -> roc_std::RocStr {
extern "C" {
fn roc__mainForHost_0_caller(
arg0: *const (),
closure_data: *mut u8,
output: *mut roc_std::RocStr,
);
}
dbg!(self.closure_data.len());
let mut output = core::mem::MaybeUninit::uninit();
let closure_ptr =
(&mut core::mem::ManuallyDrop::new(self.closure_data)) as *mut _ as *mut u8;
unsafe {
roc__mainForHost_0_caller(&(), closure_ptr, output.as_mut_ptr());
output.assume_init()
}
}
}
pub fn mainForHost(arg0: roc_std::RocStr) -> RocFunction_89 {
extern "C" {
fn roc__mainForHost_1_exposed_generic(
_: *mut RocFunction_89,
_: &mut core::mem::ManuallyDrop<roc_std::RocStr>,
);
}
let mut ret = core::mem::MaybeUninit::uninit();
unsafe {
roc__mainForHost_1_exposed_generic(
ret.as_mut_ptr(),
&mut core::mem::ManuallyDrop::new(arg0),
);
ret.assume_init()
}
}
this is from:
platform "webserver"
requires {} { main : Url -> Task Str [] }
exposes [ ... ]
packages {}
imports [Task.{ Task }, Url.{ Url }]
provides [mainForHost]
mainForHost : Str -> (Task Str [] as Fx)
mainForHost = \str -> main (Url.fromStr str)
so mainForHost
returns a Task
, which is the RocFunction_89
closure
Yeah, so main for host should have no real closure captures.
Though it could technically
but RocFunction_89
should, right? Because it's a bunch of chained Task.await
s
So really our force think function should take in closure capture data from when main for host 1 was called
Richard Feldman said:
but
RocFunction_89
should, right? Because it's a bunch of chainedTask.await
s
Depends on how main is written. This is only about the highest level closure captures, not the nested ones, I think :thinking:
Oh, I see, it comes from the RocFunction type. I'm just a bit mixed up...sorry if my comments were off
So is the issue that you have no closure capture data but are pretending you do? So all accesses to the maybe uninit ret and closure_data are incorrect.
this is the LLVM IR
define void @roc__mainForHost_1_exposed_generic({ [0 x i64], [3 x i64], i8, [7 x i8] }* %arg, { i8*, i64, i64 }* %arg1) {
entry:
%result_value = alloca { [0 x i64], [3 x i64], i8, [7 x i8] }, align 8
%to_cc_type_ptr = bitcast { i8*, i64, i64 }* %arg1 to %str.RocStr*
call fastcc void @_mainForHost_f03bf86f79d121cbfd774dec4a65912e99f5f17c33852bbc45e81916e62b53b(%str.RocStr* %to_cc_type_ptr, { [0 x i64], [3 x i64], i8, [7 x i8] }* %result_value)
%i = bitcast { [0 x i64], [3 x i64], i8, [7 x i8] }* %arg to i8*
%i2 = bitcast { [0 x i64], [3 x i64], i8, [7 x i8] }* %result_value to i8*
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 8 %i, i8* align 8 %i2, i64 ptrtoint ({ [0 x i64], [3 x i64], i8, [7 x i8] }* getelementptr ({ [0 x i64], [3 x i64], i8, [7 x i8] }, { [0 x i64], [3 x i64], i8, [7 x i8] }* null, i32 1) to i64), i1 false)
ret void
}
the type of the first argument (which I expect it to write the return value into) seems...different from what Rust is expecting:
{ [0 x i64], [3 x i64], i8, [7 x i8] }* %arg
that's a pointer, but not to a RocList<u8>
I don't think :sweat_smile:
ignoring the first [0 x i64 ]
it's three 8B i64
s and then another 8 bytes separated into one i8
and then another [7 x i8]
oh - maybe the [3 x i64]
is the RocList<u8>
and then the i8
is the lambda set ID?
and then the [7 x i8]
is padding
hm, but I guess even if in the closure data struct I specified the extra 8B after the RocList
, that still wouldn't explain the undefined reads - because the first 24B are still in the same place regardless :thinking:
When did we start putting a roc list there? I thought it was just the bytes directly.
that still wouldn't explain the undefined reads
Theory, you are only storing the roc list in RocFunction_89
. It should be should be storing the roc list followed by an extra i8
.
So you have 2 issues:
RocFunction_89
struct into arbitrary stack memory. This is due to writing out a full { [0 x i64], [3 x i64], i8, [7 x i8] }
force_thunk
on RocFunction_89
, it is only passing in [3 x i64]
and is missing the i8
. Then roc__mainForHost_0_caller
it tries to access the i8
leading to the undefined read.What is the llvm ir for the function signiture of roc__mainForHost_0_caller
?
Last updated: Jul 06 2025 at 12:14 UTC