Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion crates/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3685,7 +3685,12 @@ impl Bindgen for FunctionBindgen<'_, '_> {
self.src.push_str(");\n");
}

Instruction::CallInterface { func, async_ } => {
Instruction::CallInterface {
func,
async_,
params_block,
} => {
let block = params_block.map(|_| operands.pop().unwrap());
let mut args = String::new();
for (i, (op, (byref, _))) in operands.iter().zip(&self.sig.params).enumerate() {
if i > 0 {
Expand Down Expand Up @@ -3719,6 +3724,13 @@ impl Bindgen for FunctionBindgen<'_, '_> {
"{snake}_callback_code_t {ret} = {}({args});",
self.sig.name,
);
// The arguments arrived in a block of memory. The user's
// function has returned, every argument was read out of
// the block before it ran, and nothing points into the
// block, so free it here.
if let Some(ptr) = &block {
uwriteln!(self.src, "free({ptr});");
}
uwriteln!(self.src, "return {ret};");
if func.result.is_some() {
results.push("ret".to_string());
Expand Down
64 changes: 50 additions & 14 deletions crates/core/src/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,10 +567,17 @@ def_instruction! {
/// Note that this will be used for async functions, and `async_`
/// indicates whether the function should be invoked in an async
/// fashion.
///
/// `params_block`: for an async export whose parameters arrived in a
/// block of memory, the size and alignment of that block. The block
/// pointer is the last operand. The generator frees the block after
/// it has evaluated every lifted argument and before the task can
/// wait.
CallInterface {
func: &'a Function,
async_: bool,
} : [func.params.len()] => [usize::from(func.result.is_some())],
params_block: Option<(ArchitectureSize, Alignment)>,
} : [func.params.len() + usize::from(params_block.is_some())] => [usize::from(func.result.is_some())],

/// Returns `amt` values on the stack. This is always the last
/// instruction.
Expand Down Expand Up @@ -1242,8 +1249,47 @@ impl<'a, B: Bindgen> Generator<'a, B> {
}
}

// This was dynamically allocated by the caller (or async start
// function) so after it's been read by the guest we need to
// deallocate it.
let export_params_block = if sig.indirect_params
&& matches!(
variant,
AbiVariant::GuestExport
| AbiVariant::GuestExportAsync
| AbiVariant::GuestExportAsyncStackful
) {
Some(
self.bindgen
.sizes()
.record(func.params.iter().map(|param| &param.ty)),
)
} else {
None
};

// A sync export frees the block after the call, below. An
// async export cannot free it there: the code after the call
// may sit in another function, or never run when the task is
// cancelled. So the call gets the block pointer as its last
// operand, and the generator frees the block itself.
let params_block = if async_ {
export_params_block
.as_ref()
.map(|info| (info.size, info.align))
} else {
None
};
if params_block.is_some() {
self.emit(&Instruction::GetArg { nth: 0 });
}

// ... and that allows us to call the interface types function
self.emit(&Instruction::CallInterface { func, async_ });
self.emit(&Instruction::CallInterface {
func,
async_,
params_block,
});

// The return value of an async function is *not* the result of the function
// itself or a pointer but rather a status code.
Expand All @@ -1269,18 +1315,8 @@ impl<'a, B: Bindgen> Generator<'a, B> {
(_is_async @ false, _) => (sig.retptr, None),
};

// This was dynamically allocated by the caller (or async start
// function) so after it's been read by the guest we need to
// deallocate it.
if let AbiVariant::GuestExport
| AbiVariant::GuestExportAsync
| AbiVariant::GuestExportAsyncStackful = variant
{
if sig.indirect_params && !async_ {
let ElementInfo { size, align } = self
.bindgen
.sizes()
.record(func.params.iter().map(|param| &param.ty));
if !async_ {
if let Some(ElementInfo { size, align }) = export_params_block {
self.emit(&Instruction::GetArg { nth: 0 });
self.emit(&Instruction::GuestDeallocate { size, align });
}
Expand Down
115 changes: 73 additions & 42 deletions crates/csharp/src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,9 @@ impl Bindgen for FunctionBindgen<'_, '_> {
}
}

Instruction::CallInterface { func, .. } => {
Instruction::CallInterface {
func, params_block, ..
} => {
let module = self.interface_gen.name;
let func_name = self.func_name.to_upper_camel_case();
let interface_name = CSharp::get_class_name_from_qualified_name(module).1;
Expand All @@ -1186,19 +1188,28 @@ impl Bindgen for FunctionBindgen<'_, '_> {
.unwrap()
.to_upper_camel_case();

let mut oper = String::new();
// The pointer to the block of parameters comes last; it is not an argument.
let block = params_block.map(|_| operands.pop().unwrap());

for (i, param) in operands.iter().enumerate() {
if i == 0 && matches!(self.kind, FunctionKind::Method(_)) {
continue;
}
let kind = self.kind;
let join_arguments = |operands: &[String]| {
let mut oper = String::new();

for (i, param) in operands.iter().enumerate() {
if i == 0 && matches!(kind, FunctionKind::Method(_)) {
continue;
}

oper.push_str(&format!("({param})"));
oper.push_str(&format!("({param})"));

if i < operands.len() && operands.len() != i + 1 {
oper.push_str(", ");
if i < operands.len() && operands.len() != i + 1 {
oper.push_str(", ");
}
}
}

oper
};
let mut oper = join_arguments(operands);

let is_async = InterfaceGenerator::is_async(self.kind);
if is_async && self.interface_gen.direction == Direction::Export {
Expand All @@ -1207,9 +1218,18 @@ impl Bindgen for FunctionBindgen<'_, '_> {
self.interface_gen.csharp_gen.needs_async_support = true;
let async_func_name = format!("{}Async", self.func_name.to_upper_camel_case());

// The arguments are evaluated before the async method starts, and the lifted
// values are managed copies, so the block can be freed right after the call.
let free = match &block {
Some(ptr) => format!(
"\nglobal::System.Runtime.InteropServices.NativeMemory.Free((void*){ptr});"
),
None => String::new(),
};

uwriteln!(
self.src,
r#"var task = {async_func_name}({oper});
r#"var task = {async_func_name}({oper});{free}
if (task.IsCompletedSuccessfully)
{{
return (int)CallbackCode.Exit;
Expand Down Expand Up @@ -1237,6 +1257,42 @@ impl Bindgen for FunctionBindgen<'_, '_> {
"#
);

// The async method takes the lifted values as its parameters. An operand that
// is not a plain name, such as a record built in place, gets a fresh parameter
// name, and the call inside the async method passes that name.
let mut params = Vec::new();
for (i, p) in func.params.iter().enumerate() {
let mut param_type =
self.interface_gen.type_name_with_qualifier(&p.ty, true);

// Resource types need the Impl class to be distinguised.
match p.ty {
Type::Id(type_id) => {
let id = dealias(self.interface_gen.resolve, type_id);

let kind = &self.interface_gen.resolve.types[id].kind;
match kind {
TypeDefKind::Handle(handle) => {
let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
param_type = self.interface_gen.csharp_gen.all_resources
[&ty]
.export_impl_name();
}
_ => {}
}
}
_ => {}
}

let mut name = strip_lift(&operands[i]);
if !is_identifier(&name) {
name = self.locals.tmp("arg");
operands[i] = name.clone();
}
params.push(format!("{param_type} {name}"));
}
oper = join_arguments(operands);

// Start the Async function
uwriteln!(
self.src,
Expand All @@ -1245,37 +1301,7 @@ impl Bindgen for FunctionBindgen<'_, '_> {
var cleanups = new global::System.Collections.Generic.List<global::System.Action>();

"#,
func.params
.iter()
.enumerate()
.map(|(i, p)| {
let mut param_type =
self.interface_gen.type_name_with_qualifier(&p.ty, false);

// Resource types need the Impl class to be distinguised.
match p.ty {
Type::Id(type_id) => {
let id = dealias(self.interface_gen.resolve, type_id);

let kind = &self.interface_gen.resolve.types[id].kind;
match kind {
TypeDefKind::Handle(handle) => {
let (Handle::Own(ty) | Handle::Borrow(ty)) = handle;
param_type =
self.interface_gen.csharp_gen.all_resources
[&ty]
.export_impl_name();
}
_ => {}
}
}
_ => {}
}

format!("{} {}", param_type, strip_lift(&operands[i]))
})
.collect::<Vec<_>>()
.join(", ")
params.join(", ")
);
self.needs_cleanup = true;
}
Expand Down Expand Up @@ -2047,6 +2073,11 @@ pub fn strip_lift(lifted_param: &String) -> String {
out.into_owned()
}

fn is_identifier(s: &str) -> bool {
s.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_')
&& s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}

/// Dereference any number `TypeDefKind::Type` aliases to retrieve the target type.
pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId {
loop {
Expand Down
2 changes: 1 addition & 1 deletion crates/d/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3422,7 +3422,7 @@ impl<'a, 'b> Bindgen for FunctionBindgen<'a, 'b> {
self.push_str(&format!("deallocate.purge();\n"));
}
}
abi::Instruction::CallInterface { func, async_ } => {
abi::Instruction::CallInterface { func, async_, .. } => {
if *async_ {
todo!("CallInterface async");
}
Expand Down
13 changes: 12 additions & 1 deletion crates/go/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2010,7 +2010,18 @@ for index := 0; index < int({length}); index++ {{
);
results.push(result);
}
Instruction::CallInterface { func, .. } => {
Instruction::CallInterface {
func, params_block, ..
} => {
// The block was allocated by this component's `cabi_realloc`
// in memory the Go runtime manages; `witRuntime.Unpin()`
// below releases it, so there is nothing to free here.
// `indirect_params` sets `unpin_params`, so that call is
// always emitted when a block exists.
if params_block.is_some() {
operands.pop();
}

if self.unpin_params {
self.imports.insert(remote_pkg("runtime"));
uwriteln!(self.src, "witRuntime.Unpin()");
Expand Down
4 changes: 4 additions & 0 deletions crates/moonbit/src/async/ev.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ fn finish_waitableset(
guard task_state.lane.blocking == 0 && task_state.lane.run_later.is_empty() else {
panic()
}
// `pop_front` leaves each vacated slot of the run queue pointing at the
// coroutine it held, and every coroutine points back at this lane. Replace
// the buffer so that cycle does not keep the finished task alive.
task_state.lane.run_later.shrink_to_fit()
drop_task_wakeup(task_state)
guard task_state.subscribers.is_empty() else { panic() }
component_tasks.remove(waitable_set)
Expand Down
23 changes: 22 additions & 1 deletion crates/moonbit/src/async_support.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2541,6 +2541,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
&mut self,
func: &Function,
operands: &[String],
params_block: Option<&str>,
results: &mut Vec<String>,
) {
let name = self.interface_gen.world_gen.pkg_resolver.func_call(
Expand All @@ -2550,7 +2551,27 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> {
);
let task_return_name = format!("{}_task_return", func.name.to_moonbit_ident());

let mut args = operands.to_vec();
let mut args = match params_block {
Some(ptr) => {
let args = operands
.iter()
.map(|op| {
let arg = self.locals.tmp("arg");
uwriteln!(self.src, "let {arg} = {op}");
arg
})
.collect::<Vec<_>>();
// The call below runs to completion before the next line, so a
// free after it would never run for a cancelled call. Free the
// block here, once every argument has been read out of it.
if !self.preserve_guest_allocations {
self.use_ffi(ffi::FREE);
uwriteln!(self.src, "mbt_ffi_free({ptr})");
}
args
}
None => operands.to_vec(),
};
if matches!(self.interface_gen.direction, Direction::Export) {
args.push(background_group_name(func));
}
Expand Down
Loading
Loading