diff --git a/crates/moonbit/src/async/async_abi.mbt b/crates/moonbit/src/async/async_abi.mbt new file mode 100644 index 000000000..f4491a0a5 --- /dev/null +++ b/crates/moonbit/src/async/async_abi.mbt @@ -0,0 +1,266 @@ +// #region subtask + +///| +priv struct SubTask { + handle : Int + state : SubTaskState +} + +///| +priv enum SubTaskState { + Starting = 0 + Started = 1 + Returned = 2 + Cancelled_before_started = 3 + Cancelled_before_returned = 4 +} + +///| +fn SubTaskState::from(int : Int) -> SubTaskState { + match int { + 0 => Starting + 1 => Started + 2 => Returned + 3 => Cancelled_before_started + 4 => Cancelled_before_returned + _ => panic() + } +} + +///| +fn SubTask::from(code : Int) -> SubTask { + { handle: code >> 4, state: SubTaskState::from(code & 0xf) } +} + +///| +/// None : the subtask is blocked +fn SubTask::cancel(self : SubTask) -> SubTaskState? { + let result = subtask_cancel(self.handle) + if result == -1 { + None + } else { + Some(SubTaskState::from(subtask_cancel(self.handle))) + } +} + +// #endregion + +// #region events + +///| +priv enum EventCode { + None = 0 + SubTask = 1 + StreamRead = 2 + StreamWrite = 3 + FutureRead = 4 + FutureWrite = 5 + TaskCancelled = 6 +} + +///| +fn EventCode::from(int : Int) -> EventCode { + match int { + 0 => EventCode::None + 1 => EventCode::SubTask + 2 => EventCode::StreamRead + 3 => EventCode::StreamWrite + 4 => EventCode::FutureRead + 5 => EventCode::FutureWrite + 6 => EventCode::TaskCancelled + _ => panic() + } +} + +///| +priv enum Events { + None + Subtask(Int, SubTaskState) + StreamRead(Int, StreamResult) + StreamWrite(Int, StreamResult) + FutureRead(Int, FutureReadResult) + FutureWrite(Int, FutureWriteResult) + TaskCancelled +} + +///| +fn Events::new(code : EventCode, i : Int, j : Int) -> Events { + match code { + None => None + SubTask => Subtask(i, SubTaskState::from(j)) + StreamRead => StreamRead(i, StreamResult::from(j)) + StreamWrite => StreamWrite(i, StreamResult::from(j)) + FutureRead => FutureRead(i, FutureReadResult::from(j)) + FutureWrite => FutureWrite(i, FutureWriteResult::from(j)) + TaskCancelled => TaskCancelled + } +} + +// #endregion + +// #region waitable set + +///| +struct WaitableSet(Int) derive(Eq, Show, Hash) + +///| +fn WaitableSet::new() -> WaitableSet { + WaitableSet(waitable_set_new()) +} + +///| +fn WaitableSet::drop(self : Self) -> Unit { + waitable_set_drop(self.0) +} + +// #endregion + +// #region Future + +///| +priv enum FutureReadResult { + Completed = 0 + Cancelled = 1 +} + +///| +fn FutureReadResult::from(int : Int) -> FutureReadResult { + match int { + 0 => Completed + 1 => Cancelled + _ => panic() + } +} + +///| +priv enum FutureWriteResult { + Completed = 0 + Dropped = 1 + Cancelled = 2 +} + +///| +fn FutureWriteResult::from(int : Int) -> FutureWriteResult { + match int { + 0 => Completed + 1 => Dropped + 2 => Cancelled + _ => panic() + } +} + +// #endregion + +// #region Stream + +///| +priv struct StreamResult { + progress : Int + copy_result : CopyResult +} + +///| +fn StreamResult::from(int : Int) -> StreamResult { + let progress = int >> 4 + let copy_result = CopyResult::from(int & 0xf) + { progress, copy_result } +} + +///| +priv enum CopyResult { + Completed = 0 + Dropped = 1 + Cancelled = 2 +} + +///| +fn CopyResult::from(int : Int) -> CopyResult { + match int { + 0 => Completed + 1 => Dropped + 2 => Cancelled + _ => panic() + } +} + +// #endregion + +// #region callback code + +///| +/// Code to let the runtime know what to do in the callback +priv enum CallbackCode { + Completed + Yield + Wait(WaitableSet) + Poll(WaitableSet) +} + +///| +fn CallbackCode::encode(self : Self) -> Int { + match self { + Completed => 0 + Yield => 1 + Wait(id) => 2 | (id.0 << 4) + Poll(id) => 3 | (id.0 << 4) + } +} + +///| +fn CallbackCode::decode(int : Int) -> CallbackCode { + let id = int >> 4 + match int & 0xf { + 0 => Completed + 1 => Yield + 2 => Wait(id) + 3 => Poll(id) + _ => panic() + } +} + +// #endregion + +// #region Component async primitives + +///| +/// Return whether is cancelled. +/// Use for non-callback implementation. +fn _yield() -> Bool = "$root" "[cancellable][yield]" + +///| +fn _backpressure_inc() = "$root" "[backpressure-inc]" + +///| +fn _backpressure_dec() = "$root" "[backpressure-dec]" + +///| +fn subtask_cancel(id : Int) -> Int = "$root" "[subtask-cancel]" + +///| +fn subtask_drop(id : Int) = "$root" "[subtask-drop]" + +///| +pub fn context_set(task : Int) = "$root" "[context-set-0]" + +///| +pub fn context_get() -> Int = "$root" "[context-get-0]" + +///| +fn tls_set(tls : Int) = "$root" "[context-set-0]" + +///| +fn tls_get() -> Int = "$root" "[context-get-0]" + +///| +pub fn task_cancel() = "[export]$root" "[task-cancel]" + +///| +fn waitable_set_new() -> Int = "$root" "[waitable-set-new]" + +///| +fn waitable_set_drop(set : Int) = "$root" "[waitable-set-drop]" + +///| +fn waitable_join(waitable : Int, set : Int) = "$root" "[waitable-join]" + +// #endregion diff --git a/crates/moonbit/src/async/async_primitive.mbt b/crates/moonbit/src/async/async_primitive.mbt new file mode 100644 index 000000000..b603b1cc9 --- /dev/null +++ b/crates/moonbit/src/async/async_primitive.mbt @@ -0,0 +1,21 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +async fn[T] async_suspend( + cb : ((T) -> Unit, (Cancelled) -> Unit) -> Unit, +) -> T raise Cancelled = "%async.suspend" + +///| +fn run_async(f : async () -> Unit noraise) = "%async.run" diff --git a/crates/moonbit/src/async/coroutine.mbt b/crates/moonbit/src/async/coroutine.mbt new file mode 100644 index 000000000..cfd962695 --- /dev/null +++ b/crates/moonbit/src/async/coroutine.mbt @@ -0,0 +1,180 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv enum State { + Done + Fail(Error) + Running + Suspend(ok_cont~ : (Unit) -> Unit, err_cont~ : (Cancelled) -> Unit) +} + +///| +struct Coroutine { + coro_id : Int + mut state : State + mut shielded : Bool + mut cancelled : Bool + mut ready : Bool + downstream : Set[Coroutine] +} + +///| +impl Eq for Coroutine with equal(c1, c2) { + c1.coro_id == c2.coro_id +} + +///| +impl Hash for Coroutine with hash_combine(self, hasher) { + self.coro_id.hash_combine(hasher) +} + +///| +fn Coroutine::wake(self : Coroutine) -> Unit { + self.ready = true + scheduler.run_later.push_back(self) +} + +///| +pub fn is_being_cancelled() -> Bool { + let coro = current_coroutine() + coro.cancelled && not(coro.shielded) +} + +///| +pub(all) suberror Cancelled derive(Show) + +///| +fn Coroutine::cancel(self : Coroutine) -> Unit { + self.cancelled = true + if not(self.shielded || self.ready) { + self.wake() + } +} + +///| +pub async fn pause() -> Unit raise Cancelled { + guard scheduler.curr_coro is Some(coro) + if coro.cancelled && not(coro.shielded) { + raise Cancelled::Cancelled + } + async_suspend(fn(ok_cont, err_cont) { + guard coro.state is Running + coro.state = Suspend(ok_cont~, err_cont~) + coro.ready = true + scheduler.run_later.push_back(coro) + }) +} + +///| +pub async fn suspend() -> Unit raise Cancelled { + guard scheduler.curr_coro is Some(coro) + if coro.cancelled && not(coro.shielded) { + raise Cancelled::Cancelled + } + scheduler.blocking += 1 + defer { + scheduler.blocking -= 1 + } + async_suspend(fn(ok_cont, err_cont) { + guard coro.state is Running + coro.state = Suspend(ok_cont~, err_cont~) + }) +} + +///| +fn spawn(f : async () -> Unit) -> Coroutine { + scheduler.coro_id += 1 + let coro = { + state: Running, + ready: true, + shielded: true, + downstream: Set::new(), + coro_id: scheduler.coro_id, + cancelled: false, + } + fn run(_) { + run_async(fn() { + coro.shielded = false + try f() catch { + err => coro.state = Fail(err) + } noraise { + _ => coro.state = Done + } + for coro in coro.downstream { + coro.wake() + } + coro.downstream.clear() + }) + } + + coro.state = Suspend(ok_cont=run, err_cont=_ => ()) + scheduler.run_later.push_back(coro) + coro +} + +///| +fn Coroutine::unwrap(self : Coroutine) -> Unit raise { + match self.state { + Done => () + Fail(err) => raise err + Running | Suspend(_) => panic() + } +} + +///| +async fn Coroutine::wait(target : Coroutine) -> Unit { + guard scheduler.curr_coro is Some(coro) + guard not(physical_equal(coro, target)) + match target.state { + Done => return + Fail(err) => raise err + Running | Suspend(_) => () + } + target.downstream.add(coro) + try suspend() catch { + err => { + target.downstream.remove(coro) + raise err + } + } noraise { + _ => target.unwrap() + } +} + +///| +fn Coroutine::check_error(coro : Coroutine) -> Unit raise { + match coro.state { + Fail(err) => raise err + Done | Running | Suspend(_) => () + } +} + +///| +pub async fn protect_from_cancel(f : async () -> Unit) -> Unit { + guard scheduler.curr_coro is Some(coro) + if coro.shielded { + // already in a shield, do nothing + f() + } else { + coro.shielded = true + defer { + coro.shielded = false + } + f() + if coro.cancelled { + raise Cancelled::Cancelled + } + } +} diff --git a/crates/moonbit/src/async/ev.mbt b/crates/moonbit/src/async/ev.mbt new file mode 100644 index 000000000..23c642cdd --- /dev/null +++ b/crates/moonbit/src/async/ev.mbt @@ -0,0 +1,264 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv struct EventLoop { + subscribes : Map[Int, Subscriber] + tasks : Map[WaitableSet, Coroutine] + finished : Map[WaitableSet, Bool] +} + +///| +priv struct Subscriber { + mut event : Events? + coro : Coroutine +} + +///| +fn current_waitableset() -> WaitableSet { + WaitableSet(tls_get()) +} + +///| +pub fn with_waitableset(f : async () -> Unit) -> Int { + let waitable_set = WaitableSet::new() + tls_set(waitable_set.0) + let coro = spawn(async fn() -> Unit { + defer ev.finished.set(waitable_set, true) + f() + }) + ev.tasks.set(waitable_set, coro) + ev.finished.set(waitable_set, false) + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } else { + return CallbackCode::Wait(waitable_set.0).encode() + } +} + +///| +pub fn cb(event : Int, waitable_id : Int, code : Int) -> Int { + let waitable_set = current_waitableset() + let events = Events::new(EventCode::from(event), waitable_id, code) + match events { + TaskCancelled => { + guard ev.tasks.get(waitable_set) is Some(coro) + coro.cancel() + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + task_cancel() + return CallbackCode::Completed.encode() + } else { + // Unlikely to reach here + return CallbackCode::Wait(waitable_set.0).encode() + } + } + _ => { + let sub = ev.subscribes.get(waitable_id) + guard sub is Some(subscriber) + subscriber.event = Some(events) + subscriber.coro.wake() + reschedule() + if ev.finished.get(waitable_set) is Some(true) { + ev.tasks.remove(waitable_set) + ev.finished.remove(waitable_set) + waitable_set.drop() + return CallbackCode::Completed.encode() + } else { + return CallbackCode::Wait(waitable_set.0).encode() + } + } + } +} + +///| +let ev : EventLoop = { subscribes: {}, tasks: {}, finished: {} } + +///| +pub async fn suspend_for_subtask( + val : Int, + cleanup_after_started : () -> Unit, +) -> Unit { + let task = SubTask::from(val) + defer subtask_drop(task.handle) + let mut cleaned = false + + // Helper: ensure cleanup is called once we've moved past Starting state + fn ensure_cleanup(state : SubTaskState) -> Unit { + if not(cleaned) && !(state is Starting) { + cleanup_after_started() + cleaned = true + } + } + + // Initial state, return if finished + ensure_cleanup(task.state) + match task.state { + Returned => return + Cancelled_before_started => raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => raise SubTaskCancelled(before_started=false) + _ => () + } + + // Create subscriber to wait for events + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(task.handle, subscriber) + defer ev.subscribes.remove(task.handle) + waitable_join(task.handle, current_waitableset().0) + defer waitable_join(task.handle, 0) + for { + suspend() catch { + Cancelled::Cancelled => + // Cancel the subtask + return protect_from_cancel(() => { + subscriber.event = task + .cancel() + .map(state => Subtask(task.handle, state)) + while subscriber.event is None { + suspend() + } + guard subscriber.event is Some(Subtask(i, state)) && i == task.handle + ensure_cleanup(state) + match state { + Returned => return + Cancelled_before_started => + raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => + raise SubTaskCancelled(before_started=false) + _ => panic() // should not happen + } + }) + } + + // Subsequent state, return if finished + if subscriber.event is Some(Subtask(i, state)) { + guard i == task.handle + ensure_cleanup(state) + match state { + Returned => return + Cancelled_before_started => raise SubTaskCancelled(before_started=true) + Cancelled_before_returned => + raise SubTaskCancelled(before_started=false) + _ => subscriber.event = None + } + } + } +} + +///| +pub async fn suspend_for_future_read(idx : Int, val : Int) -> Unit { + let result = if val == -1 { + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(FutureRead(i, result)) && i == idx + result + } else { + FutureReadResult::from(val) + } + match result { + Completed => return + Cancelled => raise FutureReadCancelled + } +} + +///| +pub async fn suspend_for_future_write(idx : Int) -> Bool { + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(FutureWrite(i, result)) && i == idx + match result { + Completed => true + Dropped => false + Cancelled => raise FutureWriteCancelled + } +} + +///| +pub async fn suspend_for_stream_read(idx : Int, val : Int) -> (Int, Bool) { + let { progress, copy_result } = if val == -1 { + // Blocked, wait for event + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(StreamRead(i, result)) && i == idx + result + } else { + StreamResult::from(val) + } + match copy_result { + Completed => return (progress, false) + Dropped => return (progress, true) + Cancelled => + if progress > 0 { + return (progress, false) + } else { + raise StreamReadCancelled + } + } +} + +///| +pub async fn suspend_for_stream_write(idx : Int, val : Int) -> (Int, Bool) { + let { progress, copy_result } = if val != -1 { + // Not blocked + StreamResult::from(val) + } else { + // Blocked, wait for event + let subscriber = { event: None, coro: current_coroutine() } + ev.subscribes.set(idx, subscriber) + defer ev.subscribes.remove(idx) + waitable_join(idx, current_waitableset().0) + defer waitable_join(idx, 0) + suspend() + guard subscriber.event is Some(StreamWrite(i, result)) && i == idx + result + } + match copy_result { + Completed => return (progress, false) + Dropped => return (progress, true) + Cancelled => + if progress > 0 { + return (progress, false) + } else { + raise StreamWriteCancelled + } + } +} + +///| +pub suberror OpCancelled { + SubTaskCancelled(before_started~ : Bool) + StreamWriteCancelled + StreamReadCancelled + FutureWriteCancelled + FutureReadCancelled +} diff --git a/crates/moonbit/src/async/moon.pkg.json b/crates/moonbit/src/async/moon.pkg.json new file mode 100644 index 000000000..b7a5c45c3 --- /dev/null +++ b/crates/moonbit/src/async/moon.pkg.json @@ -0,0 +1 @@ +{ "warn-list": "-44", "supported-targets": ["wasm"] } \ No newline at end of file diff --git a/crates/moonbit/src/async/scheduler.mbt b/crates/moonbit/src/async/scheduler.mbt new file mode 100644 index 000000000..c99947b54 --- /dev/null +++ b/crates/moonbit/src/async/scheduler.mbt @@ -0,0 +1,61 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv struct Scheduler { + mut coro_id : Int + mut curr_coro : Coroutine? + mut blocking : Int + run_later : @deque.Deque[Coroutine] +} + +///| +let scheduler : Scheduler = { + coro_id: 0, + curr_coro: None, + blocking: 0, + run_later: @deque.new(), +} + +///| +pub fn current_coroutine() -> Coroutine { + scheduler.curr_coro.unwrap() +} + +///| +pub fn has_immediately_ready_task() -> Bool { + !scheduler.run_later.is_empty() +} + +///| +pub fn no_more_work() -> Bool { + scheduler.blocking == 0 && scheduler.run_later.is_empty() +} + +///| +pub fn reschedule() -> Unit { + while scheduler.run_later.pop_front() is Some(coro) { + coro.ready = false + guard coro.state is Suspend(ok_cont~, err_cont~) else { } + coro.state = Running + let last_coro = scheduler.curr_coro + scheduler.curr_coro = Some(coro) + if coro.cancelled && not(coro.shielded) { + err_cont(Cancelled::Cancelled) + } else { + ok_cont(()) + } + scheduler.curr_coro = last_coro + } +} diff --git a/crates/moonbit/src/async/task.mbt b/crates/moonbit/src/async/task.mbt new file mode 100644 index 000000000..676936293 --- /dev/null +++ b/crates/moonbit/src/async/task.mbt @@ -0,0 +1,50 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +/// `Task[X]` represents a running task with result type `X`, +/// it can be used to wait and retrieve the result value of the task. +struct Task_[X] { + value : Ref[X?] + coro : Coroutine +} + +///| +/// Wait for a task and retrieve its result value. +/// If the task fails, `wait` will also fail with the same error. +/// +/// If the current task is cancelled, `wait` return immediately with error. +pub async fn[X] Task_::wait(self : Task_[X]) -> X { + self.coro.wait() + self.value.val.unwrap() +} + +///| +/// Try to obtain the result of the task. +/// If the task already terminated, its result value will be returned. +/// If the task already failed, `try_wait` will fail immediately. +/// If the task is still running, `try_wait` returns `None`. +/// `try_wait` is a synchoronous function: it never blocks. +pub fn[X] Task_::try_wait(self : Task_[X]) -> X? raise { + self.coro.check_error() + self.value.val +} + +///| +/// Cancel a task. Subsequent attempt to wait for the task will receive error. +/// Note that if the task is *not* spawned with `allow_failure=true`, +/// the whole task group will fail too. +pub fn[X] Task_::cancel(self : Task_[X]) -> Unit { + self.coro.cancel() +} diff --git a/crates/moonbit/src/async/task_group.mbt b/crates/moonbit/src/async/task_group.mbt new file mode 100644 index 000000000..03384b941 --- /dev/null +++ b/crates/moonbit/src/async/task_group.mbt @@ -0,0 +1,249 @@ +// Copyright 2025 International Digital Economy Academy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///| +priv enum TaskGroupState { + Done + Fail(Error) + Running +} + +///| +/// A `TaskGroup` can be used to spawn children tasks that run in parallel. +/// Task groups implements *structured concurrency*: +/// a task group will only return after all its children task terminates. +/// +/// Task groups also handles *error propagation*: +/// by default, if any child task raises error, +/// the whole task group will also raise that error, +/// and all other remaining child tasks will be cancelled. +/// +/// The type parameter `X` in `TaskGroup[X]` is the result type of the group, +/// see `with_task_group` for more detail. +struct TaskGroup[X] { + children : Set[Coroutine] + parent : Coroutine + mut waiting : Int + mut state : TaskGroupState + mut result : X? + group_defer : Array[async () -> Unit] +} + +///| +#deprecated("this error is no longer emitted") +pub suberror AlreadyTerminated derive(Show) + +///| +fn[X] TaskGroup::spawn_coroutine( + self : TaskGroup[X], + f : async () -> Unit, + no_wait~ : Bool, + allow_failure~ : Bool, +) -> Coroutine { + guard self.state is Running else { + abort("trying to spawn from a terminated task group") + } + if not(no_wait) { + self.waiting += 1 + } + async fn worker() { + let coro = current_coroutine() + defer { + self.children.remove(coro) + if not(no_wait) { + self.waiting -= 1 + if self.waiting == 0 && self.state is Running { + for child in self.children { + child.cancel() + } + self.state = Done + } + } + if self.children.is_empty() { + self.parent.wake() + } + } + guard self.state is Running else { } + f() catch { + err if allow_failure => raise err + err => { + if self.state is Running { + for child in self.children { + child.cancel() + } + self.state = Fail(err) + } else if not(err is Cancelled::Cancelled) { + self.state = Fail(err) + } + raise err + } + } + } + + let coro = spawn(worker) + self.children.add(coro) + coro +} + +///| +/// Spawn a child task in a task group, and run it asynchronously in the background. +/// +/// Unless `no_wait` (`false` by default) is `true`, +/// the whole task group will only exit after this child task terminates. +/// +/// Unless `allow_failure` (`false` by default) is `true`, +/// Ithe whole task group will also fail if the spawned task fails, +/// other tasks in the group will be cancelled in this case. +/// +/// If the task group is already cancelled or has been terminated, +/// `spawn_bg` will fail with error and the child task will not be spawned. +/// +/// It is undefined whether the child task will start running immediately +/// before `spawn_bg` returns. +pub fn[X] TaskGroup::spawn_bg( + self : TaskGroup[X], + f : async () -> Unit, + no_wait? : Bool = false, + allow_failure? : Bool = false, +) -> Unit { + ignore(self.spawn_coroutine(f, no_wait~, allow_failure~)) +} + +///| +/// Spawn a child task in a task group, compute a result asynchronously. +/// A task handle will be returned, the result value of the task can be waited +/// and retrieved using `.wait()`, or cancelled using `.cancel()`. +/// +/// Unless `no_wait` (`false` by default) is `true`, +/// the whole task group will only exit after this child task terminates. +/// +/// Unless `allow_failure` (`false` by default) is `true`, +/// Ithe whole task group will also fail if the spawned task fails, +/// other tasks in the group will be cancelled in this case. +/// +/// If the task group is already cancelled or has been terminated, +/// `spawn` will fail with error and the child task will not be spawned. +/// +/// It is undefined whether the child task will start running immediately +/// before `spawn` returns. +pub fn[G, X] TaskGroup::spawn( + self : TaskGroup[G], + f : async () -> X, + no_wait? : Bool = false, + allow_failure? : Bool = false, +) -> Task_[X] { + let value = @ref.new(Option::None) + let coro = self.spawn_coroutine( + () => value.val = Some(f()), + no_wait~, + allow_failure~, + ) + { value, coro } +} + +///| +/// Attach a defer block, represented as a cleanup function, to a task group. +/// The clenaup function will be invoked when the group terminates. +/// Group scoped defer blocks are executed in FILO order, just like normal `defer`. +/// `with_task_group` will only exit after all group defer blocks terminate. +/// +/// Note that if the whole task group is cancelled, +/// async operations in group defer block will be cancelled immediately too. +/// Users can use `protect_from_cancel` to prevent async tasks from being cancelled. +/// It is highly recommended to add a hard timeout to async defer block in this case, +/// to avoid infinite hanging due to blocked operation. +pub fn[X] TaskGroup::add_defer( + self : TaskGroup[X], + block : async () -> Unit, +) -> Unit { + guard self.state is Running else { + abort("trying to attach defer to a terminated task group") + } + self.group_defer.push(block) +} + +///| +/// `with_task_group(f)` creates a new task group and run `f` with the new group. +/// `f` itself will be run in a child task of the new group. +/// `with_task_group` exits after all the whole group terminates, +/// which means all child tasks in the group have terminated, including `f`. +/// +/// If all children task terminate successfully, +/// `with_task_group` will return the result of `f`. +pub async fn[X] with_task_group(f : async (TaskGroup[X]) -> X) -> X { + let tg = { + children: Set::new(), + parent: current_coroutine(), + waiting: 0, + state: Running, + result: None, + group_defer: [], + } + tg.spawn_bg(fn() { + let value = f(tg) + if tg.result is None { + tg.result = Some(value) + } + }) + if not(tg.children.is_empty()) { + suspend() catch { + err => + if tg.state is Running { + tg.state = Fail(err) + for child in tg.children { + child.cancel() + } + } + } + } + if not(tg.children.is_empty()) { + protect_from_cancel(() => suspend()) catch { + _ => () + } + } + tg.children.clear() + while tg.group_defer.pop() is Some(defer_block) { + defer_block() catch { + err => if tg.state is Done { tg.state = Fail(err) } + } + } + match tg.state { + Done => tg.result.unwrap() + Fail(err) => raise err + Running => panic() + } +} + +///| +/// Force a task group to terminate immediately with the given result value. +/// All child tasks in the group, including potentially the current one, +/// will be cancelled. +pub fn[X] TaskGroup::return_immediately( + self : TaskGroup[X], + value : X, +) -> Unit raise { + if self.result is None { + self.result = Some(value) + } + if self.state is Running { + self.state = Done + let curr_coro = current_coroutine() + for child in self.children { + if child != curr_coro { + child.cancel() + } + } + } + raise Cancelled::Cancelled +} diff --git a/crates/moonbit/src/async/trait.mbt b/crates/moonbit/src/async/trait.mbt new file mode 100644 index 000000000..ad2c20b76 --- /dev/null +++ b/crates/moonbit/src/async/trait.mbt @@ -0,0 +1,50 @@ +///| +pub(all) struct FutureR[X](async () -> X) + +///| +pub(all) struct StreamR[X] { + read : async (Int) -> ArrayView[X]? + close : async () -> Unit +} + +///| +pub(all) struct StreamW[X] { + write : async (ArrayView[X]) -> Int + close : async () -> Unit +} + +///| +struct OutStream[X] { + mut stream : StreamW[X]? + mut coroutine : Coroutine? +} derive(Default) + +///| +pub async fn[X] OutStream::get_stream(self : OutStream[X]) -> StreamW[X] { + if self.stream is Some(s) { + return s + } else { + guard self.coroutine is None + self.coroutine = Some(current_coroutine()) + suspend() catch { + e => { + if self.stream is Some(s) { + (s.close)() + } + raise e + } + } + self.stream.unwrap() + } +} + +///| +pub fn[X] OutStream::put_stream( + self : OutStream[X], + stream : StreamW[X], +) -> Unit { + self.stream = Some(stream) + if self.coroutine is Some(coro) { + coro.wake() + } +} diff --git a/crates/moonbit/src/async_support.rs b/crates/moonbit/src/async_support.rs index 7975c6990..47ec5933e 100644 --- a/crates/moonbit/src/async_support.rs +++ b/crates/moonbit/src/async_support.rs @@ -24,6 +24,17 @@ const ASYNC_WASM_PRIMITIVE: &str = include_str!("./ffi/wasm_primitive.mbt"); const ASYNC_WAITABLE_SET: &str = include_str!("./ffi/waitable_task.mbt"); const ASYNC_SUBTASK: &str = include_str!("./ffi/subtask.mbt"); +// NEW Async Impl +const ASYNC_ABI: &str = include_str!("./async/async_abi.mbt"); +const ASYNC_CORO: &str = include_str!("./async/coroutine.mbt"); +const ASYNC_EV: &str = include_str!("./async/ev.mbt"); +const ASYNC_SCHEDULER: &str = include_str!("./async/scheduler.mbt"); +const ASYNC_TASK: &str = include_str!("./async/task.mbt"); +const ASYNC_TASK_GROUP: &str = include_str!("./async/task_group.mbt"); +const ASYNC_TRAIT: &str = include_str!("./async/trait.mbt"); +const ASYNC_PKG_JSON: &str = include_str!("./async/moon.pkg.json"); +const ASYNC_PRIM: &str = include_str!("./async/async_primitive.mbt"); + struct Segment<'a> { name: &'a str, src: &'a str, @@ -52,6 +63,43 @@ const ASYNC_UTILS: [&Segment; 5] = [ }, ]; +const ASYNC_IMPL: [&Segment; 8] = [ + &Segment { + name: "async_abi", + src: ASYNC_ABI, + }, + &Segment { + name: "async_coro", + src: ASYNC_CORO, + }, + &Segment { + name: "async_ev", + src: ASYNC_EV, + }, + &Segment { + name: "async_scheduler", + src: ASYNC_SCHEDULER, + }, + &Segment { + name: "async_task", + src: ASYNC_TASK, + }, + &Segment { + name: "async_task_group", + src: ASYNC_TASK_GROUP, + }, + &Segment { + name: "async_trait", + src: ASYNC_TRAIT, + }, + &Segment { + name: "async_primitive", + src: ASYNC_PRIM, + }, +]; + +pub(crate) const ASYNC_DIR: &str = "async"; + #[derive(Default)] pub(crate) struct AsyncSupport { is_async: bool, @@ -85,6 +133,16 @@ impl AsyncSupport { indent(s.src).as_bytes(), ); }); + ASYNC_IMPL.iter().for_each(|s| { + files.push( + &format!("{ASYNC_DIR}/{}.mbt", s.name), + indent(s.src).as_bytes(), + ); + }); + files.push( + &format!("{ASYNC_DIR}/moon.pkg.json"), + indent(ASYNC_PKG_JSON).as_bytes(), + ); files.push( &format!("{FFI_DIR}/moon.pkg.json"), "{ \"warn-list\": \"-44\", \"supported-targets\": [\"wasm\"] }".as_bytes(), @@ -97,6 +155,7 @@ impl AsyncSupport { impl<'a> InterfaceGenerator<'a> { /// Builds the MoonBit body for async imports, wiring wasm subtasks into the /// runtime and lowering/lifting payloads as needed. + #[allow(dead_code, reason = "used by follow-up async generation work")] pub(super) fn generate_async_import_function( &mut self, func: &Function, @@ -115,14 +174,17 @@ impl<'a> InterfaceGenerator<'a> { } multiple_params => { let params = multiple_params.iter().map(|Param { ty, .. }| ty); - let offsets = self.r#gen.sizes.field_offsets(params.clone()); - let elem_info = self.r#gen.sizes.params(params); + let offsets = self.world_gen.sizes.field_offsets(params.clone()); + let elem_info = self.world_gen.sizes.params(params); body.push_str(&format!( r#" let _lower_ptr : Int = {ffi}malloc({}) "#, elem_info.size.size_wasm32(), - ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR) + ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR) )); for ((offset, ty), name) in offsets.iter().zip( @@ -143,16 +205,24 @@ impl<'a> InterfaceGenerator<'a> { } } } else { - let mut f = FunctionBindgen::new(self, "INVALID", self.name, Box::new([])); + let mut f = FunctionBindgen::new(self, Box::new([])); for (name, ty) in mbt_sig.params.iter() { - lower_params.extend(abi::lower_flat(f.r#gen.resolve, &mut f, name.clone(), ty)); + lower_params.extend(abi::lower_flat( + f.interface_gen.resolve, + &mut f, + name.clone(), + ty, + )); } lower_results.push(f.src.clone()); } let func_name = func.name.to_upper_camel_case(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); let call_import = |params: &Vec| { format!( @@ -255,18 +325,21 @@ impl<'a> InterfaceGenerator<'a> { result_type: Option<&Type>, ) { if !self - .r#gen + .world_gen .async_support .register_future_or_stream(module, ty) { return; } let result = match result_type { - Some(ty) => self.r#gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), None => "Unit".into(), }; - let type_name = self.r#gen.pkg_resolver.type_name(self.name, &Type::Id(ty)); + let type_name = self + .world_gen + .pkg_resolver + .type_name(self.name, &Type::Id(ty)); let name = result.to_upper_camel_case(); let kind = match payload_for { PayloadFor::Future => "future", @@ -283,7 +356,10 @@ impl<'a> InterfaceGenerator<'a> { PayloadFor::Future => "", PayloadFor::Stream => "List", }; - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); let mut dealloc_list; let malloc; @@ -412,29 +488,38 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ types: &[Type], operands: &[String], indirect: bool, - module: &str, + _module: &str, ) -> String { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::deallocate_lists_in_types(f.r#gen.resolve, types, operands, indirect, &mut f); + let mut f = FunctionBindgen::new(self, Box::new([])); + abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f); f.src } - fn lift_from_memory(&mut self, address: &str, ty: &Type, module: &str) -> (String, String) { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); + fn lift_from_memory(&mut self, address: &str, ty: &Type, _module: &str) -> (String, String) { + let mut f = FunctionBindgen::new(self, Box::new([])); - let result = abi::lift_from_memory(f.r#gen.resolve, &mut f, address.into(), ty); + let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty); (f.src, result) } - fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, module: &str) -> String { - let mut f = FunctionBindgen::new(self, "INVALID", module, Box::new([])); - abi::lower_to_memory(f.r#gen.resolve, &mut f, address.into(), value.into(), ty); + fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, _module: &str) -> String { + let mut f = FunctionBindgen::new(self, Box::new([])); + abi::lower_to_memory( + f.interface_gen.resolve, + &mut f, + address.into(), + value.into(), + ty, + ); f.src } fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String { - let size = self.r#gen.sizes.size(ty).size_wasm32(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let size = self.world_gen.sizes.size(ty).size_wasm32(); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); format!("let {address} = {ffi}malloc({size} * {length});") } @@ -452,7 +537,10 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ lift_func: &str, ty: &Type, ) -> String { - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}ptr2bytes({address}, {length})"); @@ -469,7 +557,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ return format!("{ffi}ptr2{ty}_array({address}, {length})"); } - let size = self.r#gen.sizes.size(ty).size_wasm32(); + let size = self.world_gen.sizes.size(ty).size_wasm32(); format!( r#" FixedArray::makei( @@ -485,7 +573,10 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ fn list_lower_to_memory(&mut self, lower_func: &str, value: &str, ty: &Type) -> String { // Align the address, moonbit only supports wasm32 for now - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); if self.is_list_canonical(self.resolve, ty) { if ty == &Type::U8 { return format!("{ffi}bytes2ptr({value})"); @@ -502,7 +593,7 @@ fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{ }; return format!("{ffi}{ty}_array2ptr({value})"); } - let size = self.r#gen.sizes.size(ty).size_wasm32(); + let size = self.world_gen.sizes.size(ty).size_wasm32(); format!( r#" let address = {ffi}malloc(({value}).length() * {size}); diff --git a/crates/moonbit/src/lib.rs b/crates/moonbit/src/lib.rs index 7f8edbc6c..f5ba95a6b 100644 --- a/crates/moonbit/src/lib.rs +++ b/crates/moonbit/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::Result; use core::panic; -use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; +use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use std::{ collections::{HashMap, HashSet}, fmt::Write, @@ -14,7 +14,8 @@ use wit_bindgen_core::{ uwrite, uwriteln, wit_parser::{ Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Int, InterfaceId, - Param, Record, Resolve, Result_, SizeAlign, Tuple, Type, TypeId, Variant, WorldId, + LiftLowerAbi, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic, Result_, + SizeAlign, Tuple, Type, TypeId, Variant, WasmExport, WasmExportKind, WasmImport, WorldId, WorldKey, }, }; @@ -33,11 +34,17 @@ mod pkg; // Organization: // - one package per interface (export and import are treated as different interfaces) // - ffi utils are under `./ffi`, and the project entrance (package as link target) is under `./gen` + +// We use Legacy mangling for MoonBit (no specific reason, just because we haven't switched yet) +// We use AsyncCallback ABI for async functions + // TODO: Export will share the type signatures with the import by using a newtype alias pub(crate) const FFI_DIR: &str = "ffi"; pub(crate) const FFI: &str = include_str!("./ffi/ffi.mbt"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); + #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Parser))] pub struct Opts { @@ -67,6 +74,10 @@ pub struct Opts { #[derive(Default, Debug, Clone)] #[cfg_attr(feature = "clap", derive(clap::Args))] pub struct DeriveOpts { + /// Whether or not to derive Debug for all types + #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] + pub derive_debug: bool, + /// Whether or not to derive Show for all types #[cfg_attr(feature = "clap", arg(long, default_value_t = false))] pub derive_show: bool, @@ -93,7 +104,6 @@ impl Opts { struct InterfaceFragment { src: String, ffi: String, - stub: String, builtins: HashSet<&'static str>, } @@ -101,7 +111,6 @@ impl InterfaceFragment { fn concat(&mut self, other: Self) { self.src.push_str(&other.src); self.ffi.push_str(&other.ffi); - self.stub.push_str(&other.stub); self.builtins.extend(other.builtins); } } @@ -114,22 +123,19 @@ enum PayloadFor { #[derive(Default)] pub struct MoonBit { opts: Opts, - name: String, - needs_cleanup: bool, - import_interface_fragments: HashMap, - export_interface_fragments: HashMap, + project_name: String, import_world_fragment: InterfaceFragment, - export_world_fragment: InterfaceFragment, sizes: SizeAlign, + // Collision may happen when a package is imported with multiple versions. + // see multiverison interface_ns: Ns, // dependencies between packages pkg_resolver: PkgResolver, - export: HashMap, + // Wasm export name -> (exported function name, func) + export: HashMap, + export_ns: Ns, - // return area allocation - return_area_size: ArchitectureSize, - return_area_align: Alignment, async_support: AsyncSupport, } @@ -139,29 +145,116 @@ impl MoonBit { &'a mut self, resolve: &'a Resolve, name: &'a str, - module: &'a str, direction: Direction, + interface: Option<&'a WorldKey>, ) -> InterfaceGenerator<'a> { let derive_opts = self.opts.derive.clone(); InterfaceGenerator { src: String::new(), - stub: String::new(), ffi: String::new(), - r#gen: self, + world_gen: self, resolve, name, - module, direction, ffi_imports: HashSet::new(), derive_opts, + interface, + } + } + + fn write_moon_pkg(&self, moon_pkg: &mut Source, imports: Option<&Imports>, link: bool) { + // Disable warning for invalid inline wasm + moon_pkg.push_str("{\n\"warn-list\": \"-44\""); + // Dependencies + if let Some(imports) = imports { + moon_pkg.push_str(",\n\"import\": [\n"); + moon_pkg.indent(1); + let mut deps = imports + .packages + .iter() + .map(|(k, v)| { + format!( + "{{ \"path\" : \"{}/{}\", \"alias\" : \"{}\" }}", + self.project_name, + k.replace(".", "/"), + v + ) + }) + .collect::>(); + deps.sort(); + uwrite!(moon_pkg, "{}", deps.join(",\n")); + moon_pkg.deindent(1); + moon_pkg.push_str("\n]"); + } + // Link target + if link { + let memory_name = self.pkg_resolver.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Memory, + ); + moon_pkg.push_str(",\n\"link\": {\n\"wasm\": {\n"); + moon_pkg.push_str(&format!("\"export-memory-name\": \"{memory_name}\",\n")); + moon_pkg.push_str("\"heap-start-address\": 16,\n"); + moon_pkg.push_str("\"exports\": [\n"); + moon_pkg.indent(1); + let mut exports = self + .export + .iter() + .map(|(export_name, (func_name, _))| format!("\"{func_name}:{export_name}\"")) + .collect::>(); + exports.push(format!( + "\"mbt_ffi_cabi_realloc:{}\"", + self.pkg_resolver.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Realloc, + ), + )); + exports.sort(); + uwrite!(moon_pkg, "{}", exports.join(",\n")); + moon_pkg.deindent(1); + moon_pkg.push_str("\n]\n}\n}\n"); } + moon_pkg.push_str("\n}\n"); } } +/// World generator implementation for MoonBit. +/// +/// This implementation connects the generic `wit-bindgen` world generation +/// workflow with MoonBit-specific codegen details. It consumes the parsed +/// WIT `Resolve` structure and emits MoonBit source (`*.mbt`) and package +/// metadata files into the provided `Files` collection. +/// +/// Responsibilities and behavior: +/// - `preprocess`: Initialize generator-wide state (package resolver, +/// project name, and size/align information) for the current world. +/// - `import_interface` / `export_interface`: Generate per-interface +/// sources, FFI glue, README documentation and `moon.pkg.json` metadata. +/// - `import_funcs` / `export_funcs` / `import_types`: Collect and accumulate +/// world-level functions and types (the `$root` module) into fragments +/// that are later written out by `finish_imports` or `finish`. +/// - `finish_imports` / `finish`: Emit aggregated import artifacts and the +/// final project entrypoints such as the combined FFI module and package +/// descriptor files. +/// +/// Implementation notes: +/// - Namespacing and collision avoidance are handled using `PkgResolver` and +/// an internal `Ns` to make import/export package names stable even when +/// multiple package versions are present. +/// - Inline FFI helpers and builtins are collected and written once into the +/// final export FFI module. Async helpers are emitted when required. impl WorldGenerator for MoonBit { fn preprocess(&mut self, resolve: &Resolve, world: WorldId) { self.pkg_resolver.resolve = resolve.clone(); - self.name = PkgResolver::world_name(resolve, world); + self.project_name = self + .opts + .project_name + .clone() + .or(resolve.worlds[world].package.map(|id| { + let package = &resolve.packages[id].name; + format!("{}/{}", package.namespace, package.name) + })) + .unwrap_or("generated".into()); self.sizes.fill(resolve); } @@ -178,110 +271,69 @@ impl WorldGenerator for MoonBit { .import_interface_names .insert(id, name.clone()); - if let Some(content) = &resolve.interfaces[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); - } - } - - let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Import); + let mut r#gen = self.interface(resolve, &name, Direction::Import, Some(key)); r#gen.types(id); for (_, func) in resolve.interfaces[id].functions.iter() { - r#gen.import(Some(key), func); + r#gen.import(func); } - let result = r#gen.finish(); - self.import_interface_fragments - .insert(name.to_owned(), result); - - Ok(()) - } - - fn import_funcs( - &mut self, - resolve: &Resolve, - world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); - - for (_, func) in funcs { - r#gen.import(None, func); // None is "$root" - } + let fragment = r#gen.finish(); + // Write files + { + let directory = name.replace('.', "/"); - let result = r#gen.finish(); - self.import_world_fragment.concat(result); - } + // README + if let Some(content) = &resolve.interfaces[id].docs.contents + && !content.is_empty() + { + files.push(&format!("{directory}/README.md"), content.as_bytes()); + } - fn export_interface( - &mut self, - resolve: &Resolve, - key: &WorldKey, - id: InterfaceId, - files: &mut Files, - ) -> Result<()> { - let name = format!( - "{}.{}", - self.opts.r#gen_dir, - PkgResolver::interface_name(resolve, key) - ); - let name = self.interface_ns.tmp(&name); - self.pkg_resolver - .export_interface_names - .insert(id, name.clone()); + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); + files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - if let Some(content) = &resolve.interfaces[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); + // FFI + let mut ffi = Source::default(); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); + uwriteln!(ffi, "{}", fragment.ffi); + for builtin in fragment.builtins { + uwriteln!(ffi, "{}", builtin); } - } - - let module = &resolve.name_world_key(key); - let mut r#gen = self.interface(resolve, &name, module, Direction::Export); - r#gen.types(id); + files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); - for (_, func) in resolve.interfaces[id].functions.iter() { - r#gen.export(Some(key), func); + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); } - let result = r#gen.finish(); - self.export_interface_fragments - .insert(name.to_owned(), result); - Ok(()) } - fn export_funcs( + fn import_funcs( &mut self, resolve: &Resolve, world: WorldId, funcs: &[(&str, &Function)], _files: &mut Files, - ) -> Result<()> { - let name = format!( - "{}.{}", - self.opts.r#gen_dir, - PkgResolver::world_name(resolve, world) - ); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Export); + ) { + let name = PkgResolver::world_name(resolve, world); + let mut r#gen = self.interface(resolve, &name, Direction::Import, None); for (_, func) in funcs { - r#gen.export(None, func); + r#gen.import(func); } let result = r#gen.finish(); - self.export_world_fragment.concat(result); - Ok(()) + self.import_world_fragment.concat(result); } fn import_types( @@ -292,7 +344,7 @@ impl WorldGenerator for MoonBit { _files: &mut Files, ) { let name = PkgResolver::world_name(resolve, world); - let mut r#gen = self.interface(resolve, &name, "$root", Direction::Import); + let mut r#gen = self.interface(resolve, &name, Direction::Import, None); for (ty_name, ty) in types { r#gen.define_type(ty_name, *ty); @@ -302,254 +354,208 @@ impl WorldGenerator for MoonBit { self.import_world_fragment.concat(result); } - fn finish(&mut self, resolve: &Resolve, id: WorldId, files: &mut Files) -> Result<()> { - let project_name = self - .opts - .project_name - .clone() - .or(resolve.worlds[id].package.map(|id| { - let package = &resolve.packages[id].name; - format!("{}/{}", package.namespace, package.name) - })) - .unwrap_or("generated".into()); - let name = PkgResolver::world_name(resolve, id); + fn finish_imports(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) { + let name = PkgResolver::world_name(resolve, world); + let directory = name.replace('.', "/"); - if let Some(content) = &resolve.worlds[id].docs.contents { - if !content.is_empty() { - files.push( - &format!("{}/README.md", name.replace(".", "/")), - content.as_bytes(), - ); - } + // README + if let Some(content) = &resolve.worlds[world].docs.contents + && !content.is_empty() + { + files.push(&format!("{directory}/README.md"), content.as_bytes()); } - - let version = env!("CARGO_PKG_VERSION"); - - let generate_pkg_definition = |name: &String, files: &mut Files| { - let directory = name.replace('.', "/"); - let imports: Option<&Imports> = self.pkg_resolver.package_import.get(name); - if let Some(imports) = imports { - let mut deps = imports - .packages - .iter() - .map(|(k, v)| { - format!( - "{{ \"path\" : \"{project_name}/{}\", \"alias\" : \"{}\" }}", - k.replace(".", "/"), - v - ) - }) - .collect::>(); - deps.sort(); - - files.push( - &format!("{directory}/moon.pkg.json"), - format!( - "{{ \"import\": [{}], \"warn-list\": \"-44\" }}", - deps.join(", ") - ) - .as_bytes(), - ); - } else { - files.push( - &format!("{directory}/moon.pkg.json"), - "{ \"warn-list\": \"-44\" }".to_string().as_bytes(), - ); - } - }; - - // Import world fragments + // Source let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", self.import_world_fragment.src); + files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); + // FFI let mut ffi = Source::default(); let mut builtins: HashSet<&'static str> = HashSet::new(); - wit_bindgen_core::generated_preamble(&mut src, version); - wit_bindgen_core::generated_preamble(&mut ffi, version); - uwriteln!(src, "{}", self.import_world_fragment.src); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); uwriteln!(ffi, "{}", self.import_world_fragment.ffi); builtins.extend(self.import_world_fragment.builtins.iter()); - assert!(self.import_world_fragment.stub.is_empty()); for b in builtins.iter() { uwriteln!(ffi, "{}", b); } - - let directory = name.replace('.', "/"); - files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes()); files.push( &format!("{directory}/ffi_import.mbt"), indent(&ffi).as_bytes(), ); - generate_pkg_definition(&name, files); + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } - // Export world fragments - let mut src = Source::default(); - let mut stub = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - generated_preamble(&mut stub, version); - uwriteln!(src, "{}", self.export_world_fragment.src); - uwriteln!(stub, "{}", self.export_world_fragment.stub); - - files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - if !self.opts.ignore_stub { - files.push( - &format!("{}/{directory}/stub.mbt", self.opts.r#gen_dir), - indent(&stub).as_bytes(), - ); - generate_pkg_definition(&format!("{}.{}", self.opts.r#gen_dir, name), files); - } + fn export_interface( + &mut self, + resolve: &Resolve, + key: &WorldKey, + id: InterfaceId, + files: &mut Files, + ) -> Result<()> { + let name = format!( + "{}.{}", + self.opts.r#gen_dir, + PkgResolver::interface_name(resolve, key) + ); + let name = self.interface_ns.tmp(&name); + self.pkg_resolver + .export_interface_names + .insert(id, name.clone()); - let mut builtins: HashSet<&'static str> = HashSet::new(); - builtins.insert(ffi::MALLOC); - builtins.insert(ffi::FREE); - let mut generate_ffi = - |directory: String, fragment: &InterfaceFragment, files: &mut Files| { - // For cabi_realloc + let mut r#gen = self.interface(resolve, &name, Direction::Export, Some(key)); + r#gen.types(id); + + for (_, func) in resolve.interfaces[id].functions.iter() { + r#gen.export(func); + } - let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); + let fragment = r#gen.finish(); - uwriteln!(&mut body, "{}", fragment.ffi); - builtins.extend(fragment.builtins.iter()); + // Write files + { + let directory = name.replace('.', "/"); + // README + if let Some(content) = &resolve.interfaces[id].docs.contents + && !content.is_empty() + { files.push( - &format!( - "{}/{}_export.mbt", - self.opts.r#gen_dir, - directory.to_snake_case() - ), - indent(&body).as_bytes(), + &format!("{}/README.md", name.replace(".", "/")), + content.as_bytes(), ); - }; + } + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); + files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - generate_ffi(directory, &self.export_world_fragment, files); + if !self.opts.ignore_stub { + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); + } - // Import interface fragments - for (name, fragment) in &self.import_interface_fragments { - let mut src = Source::default(); + // FFI let mut ffi = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - wit_bindgen_core::generated_preamble(&mut ffi, version); - let mut builtins: HashSet<&'static str> = HashSet::new(); - uwriteln!(src, "{}", fragment.src); - uwriteln!(ffi, "{}", fragment.ffi); - builtins.extend(fragment.builtins.iter()); - assert!(fragment.stub.is_empty()); - for builtin in builtins { - uwriteln!(ffi, "{}", builtin); + wit_bindgen_core::generated_preamble(&mut ffi, VERSION); + + uwriteln!(&mut ffi, "{}", fragment.ffi); + for b in fragment.builtins.iter() { + uwriteln!(ffi, "{}", b); } + files.push(&format!("{directory}/ffi.mbt",), indent(&ffi).as_bytes()); + } - let directory = name.replace('.', "/"); - files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); - files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes()); - generate_pkg_definition(name, files); + Ok(()) + } + + fn export_funcs( + &mut self, + resolve: &Resolve, + world: WorldId, + funcs: &[(&str, &Function)], + files: &mut Files, + ) -> Result<()> { + let name = format!( + "{}.{}", + self.opts.r#gen_dir, + PkgResolver::world_name(resolve, world) + ); + let mut r#gen = self.interface(resolve, &name, Direction::Export, None); + + for (_, func) in funcs { + r#gen.export(func); } - // Export interface fragments - for (name, fragment) in &self.export_interface_fragments { - let mut src = Source::default(); - let mut stub = Source::default(); - wit_bindgen_core::generated_preamble(&mut src, version); - generated_preamble(&mut stub, version); - uwriteln!(src, "{}", fragment.src); - uwriteln!(stub, "{}", fragment.stub); + let fragment = r#gen.finish(); + // Write files + { let directory = name.replace('.', "/"); + // Source + let mut src = Source::default(); + wit_bindgen_core::generated_preamble(&mut src, VERSION); + uwriteln!(src, "{}", fragment.src); files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes()); + if !self.opts.ignore_stub { - files.push(&format!("{directory}/stub.mbt"), indent(&stub).as_bytes()); - generate_pkg_definition(name, files); + // moon.pkg.json + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&name), + false, + ); + files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes()); } - generate_ffi(directory, fragment, files); + + // FFI + let mut export = Source::default(); + wit_bindgen_core::generated_preamble(&mut export, VERSION); + uwriteln!(&mut export, "{}", fragment.ffi); + for b in fragment.builtins.iter() { + uwriteln!(&mut export, "{}", b); + } + files.push(&format!("{directory}/ffi.mbt",), indent(&export).as_bytes()); } - // Export FFI Utils - // Export Async utils + Ok(()) + } + fn finish(&mut self, _resolve: &Resolve, _id: WorldId, files: &mut Files) -> Result<()> { // If async is used, export async utils - self.async_support.emit_utils(files, version); + self.async_support.emit_utils(files, VERSION); // Export project files if !self.opts.ignore_stub && !self.opts.ignore_module_file { let mut body = Source::default(); uwriteln!( &mut body, - "{{ \"name\": \"{project_name}\", \"preferred-target\": \"wasm\" }}" + "{{ \"name\": \"{}\", \"preferred-target\": \"wasm\" }}", + self.project_name ); files.push("moon.mod.json", body.as_bytes()); } // Export project entry point let mut body = Source::default(); - wit_bindgen_core::generated_preamble(&mut body, version); - uwriteln!(&mut body, "{}", ffi::CABI_REALLOC); - - if !self.return_area_size.is_empty() { - uwriteln!( - &mut body, - " - let return_area : Int = mbt_ffi_malloc({}) - ", - self.return_area_size.size_wasm32(), - ); - } - for builtin in builtins { + wit_bindgen_core::generated_preamble(&mut body, VERSION); + // CABI Realloc + for builtin in [ffi::CABI_REALLOC, ffi::MALLOC, ffi::FREE] { uwriteln!(&mut body, "{}", builtin); } + // Import all exported interfaces + for (_, (_, impl_)) in self.export.iter() { + uwriteln!(&mut body, "{impl_}"); + } + files.push( &format!("{}/ffi.mbt", self.opts.r#gen_dir), indent(&body).as_bytes(), ); - self.export - .insert("mbt_ffi_cabi_realloc".into(), "cabi_realloc".into()); - - let mut body = Source::default(); - let mut exports = self - .export - .iter() - .map(|(k, v)| format!("\"{k}:{v}\"")) - .collect::>(); - exports.sort(); - - uwrite!( - &mut body, - r#" - {{ - "link": {{ - "wasm": {{ - "exports": [{}], - "export-memory-name": "memory", - "heap-start-address": 16 - }} - }} - "#, - exports.join(", ") - ); - if let Some(imports) = self.pkg_resolver.package_import.get(&self.opts.r#gen_dir) { - let mut deps = imports - .packages - .iter() - .map(|(k, v)| { - format!( - "{{ \"path\" : \"{project_name}/{}\", \"alias\" : \"{}\" }}", - k.replace(".", "/"), - v - ) - }) - .collect::>(); - deps.sort(); - - uwrite!(&mut body, " ,\"import\": [{}]", deps.join(", ")); - } - uwrite!( - &mut body, - " - , \"warn-list\": \"-44\" - }} - ", + let mut moon_pkg = Source::default(); + self.write_moon_pkg( + &mut moon_pkg, + self.pkg_resolver.package_import.get(&self.opts.r#gen_dir), + true, ); files.push( - &format!("{}/moon.pkg.json", self.opts.r#gen_dir,), - indent(&body).as_bytes(), + &format!("{}/moon.pkg.json", self.opts.r#gen_dir), + indent(&moon_pkg).as_bytes(), ); Ok(()) @@ -558,17 +564,16 @@ impl WorldGenerator for MoonBit { struct InterfaceGenerator<'a> { src: String, - stub: String, ffi: String, // Collect of FFI imports used in this interface ffi_imports: HashSet<&'static str>, - r#gen: &'a mut MoonBit, + world_gen: &'a mut MoonBit, resolve: &'a Resolve, // The current interface getting generated name: &'a str, - module: &'a str, direction: Direction, + interface: Option<&'a WorldKey>, // Options for deriving traits derive_opts: DeriveOpts, @@ -578,44 +583,33 @@ impl InterfaceGenerator<'_> { fn finish(self) -> InterfaceFragment { InterfaceFragment { src: self.src, - stub: self.stub, ffi: self.ffi, builtins: self.ffi_imports, } } - fn import(&mut self, module: Option<&WorldKey>, func: &Function) { + fn import(&mut self, func: &Function) { + // Determine if the function is async let async_ = self - .r#gen + .world_gen .opts .async_ - .is_async(self.resolve, module, func, false); + .is_async(self.resolve, self.interface, func, false); if async_ { - self.r#gen.async_support.mark_async(); + self.world_gen.async_support.mark_async(); } - let interface_name = match module { - Some(key) => &self.resolve.name_world_key(key), - None => "$root", - }; + let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case()); let mut bindgen = FunctionBindgen::new( self, - &func.name, - self.name, func.params .iter() .map(|Param { name, .. }| name.to_moonbit_ident()) .collect(), ); - let (variant, async_prefix) = if async_ { - (AbiVariant::GuestImportAsync, "[async-lower]") - } else { - (AbiVariant::GuestImport, "") - }; - abi::call( - bindgen.r#gen.resolve, + bindgen.interface_gen.resolve, AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults, func, @@ -626,81 +620,106 @@ impl InterfaceGenerator<'_> { let mut src = bindgen.src.clone(); let cleanup_list = if bindgen.needs_cleanup_list { - self.r#gen.needs_cleanup = true; - - " - let cleanup_list : Array[Int] = [] - " - .into() + "let cleanup_list : Array[Int] = []" } else { - String::new() + "" }; - let name = &func.name; - - let wasm_sig = self.resolve.wasm_signature(variant, func); - - let result_type = match &wasm_sig.results[..] { - [] => "".into(), - [result] => format!("-> {}", wasm_type(*result)), - _ => unreachable!(), - }; - - let camel_name = func.name.to_upper_camel_case(); - - let params = wasm_sig - .params - .iter() - .enumerate() - .map(|(i, param)| { - let ty = wasm_type(*param); - format!("p{i} : {ty}") - }) - .collect::>() - .join(", "); - - let mbt_sig = self.r#gen.pkg_resolver.mbt_sig(self.name, func, false); + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); let sig = self.sig_string(&mbt_sig, async_); - let module = match module { - Some(key) => self.resolve.name_world_key(key), - None => "$root".into(), - }; - - self.r#generation_futures_and_streams_import("", func, interface_name); - - uwriteln!( - self.ffi, - r#"fn wasmImport{camel_name}({params}) {result_type} = "{module}" "{async_prefix}{name}""# + // Generate the core wasm abi + let wasm_sig = self.resolve.wasm_signature( + if async_ { + AbiVariant::GuestImportAsync + } else { + AbiVariant::GuestImport + }, + func, ); + let (import_module, import_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(if async_ { + LiftLowerAbi::AsyncCallback + } else { + LiftLowerAbi::Sync + }), + WasmImport::Func { + interface: self.interface, + func, + }, + ); + { + let result_type = match &wasm_sig.results[..] { + [] => "".into(), + [result] => format!("-> {}", wasm_type(*result)), + _ => unimplemented!("multi-value results are not supported yet"), + }; - print_docs(&mut self.src, &func.docs); + let params = wasm_sig + .params + .iter() + .enumerate() + .map(|(i, param)| format!("p{i} : {}", wasm_type(*param))) + .collect::>() + .join(", "); + + uwriteln!( + self.ffi, + r#" + fn {ffi_import_name}({params}) {result_type} = "{import_module}" "{import_name}" + "# + ); + } + // Generate the MoonBit wrapper if async_ { - src = self.r#generate_async_import_function(func, mbt_sig, &wasm_sig); + let interface_name = match self.interface { + Some(key) => self.resolve.name_world_key(key), + None => "$root".into(), + }; + self.generation_futures_and_streams_import("", func, &interface_name); + src = self.generate_async_import_function(func, mbt_sig, &wasm_sig); } + print_docs(&mut self.src, &func.docs); + uwrite!( self.src, r#" {sig} {{ - {cleanup_list} - {src} - }} - "# + {cleanup_list} + {src} + }} + "# ); } - fn export(&mut self, interface: Option<&WorldKey>, func: &Function) { + fn export(&mut self, func: &Function) { + // Determine if is async let async_ = self - .r#gen + .world_gen .opts .async_ - .is_async(self.resolve, interface, func, false); + .is_async(self.resolve, self.interface, func, false); if async_ { - self.r#gen.async_support.mark_async(); + self.world_gen.async_support.mark_async(); } + // Generate declarations for user implementations. + { + let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false); + let func_sig = self.sig_string(&mbt_sig, async_); + + print_docs(&mut self.src, &func.docs); + uwrite!( + self.src, + r#" + declare {func_sig} + "# + ); + } + + // Generate the caller function let variant = if async_ { AbiVariant::GuestExportAsync } else { @@ -708,27 +727,14 @@ impl InterfaceGenerator<'_> { }; let sig = self.resolve.wasm_signature(variant, func); - let mbt_sig = self.r#gen.pkg_resolver.mbt_sig(self.name, func, false); - - let func_sig = self.sig_string(&mbt_sig, async_); - let export_dir = self.r#gen.opts.r#gen_dir.clone(); - - let mut toplevel_generator = self.r#gen.interface( - self.resolve, - export_dir.as_str(), - self.module, - Direction::Export, - ); let mut bindgen = FunctionBindgen::new( - &mut toplevel_generator, - &func.name, - self.name, + self, (0..sig.params.len()).map(|i| format!("p{i}")).collect(), ); abi::call( - bindgen.r#gen.resolve, + bindgen.interface_gen.resolve, variant, LiftLower::LiftArgsLowerResults, func, @@ -743,12 +749,6 @@ impl InterfaceGenerator<'_> { let deferred_task_return = bindgen.deferred_task_return.clone(); let src = bindgen.src; - assert!(toplevel_generator.src.is_empty()); - assert!(toplevel_generator.ffi.is_empty()); - - // Transfer ffi_imports from toplevel_generator to self - self.ffi_imports - .extend(toplevel_generator.ffi_imports.iter()); let result_type = match &sig.results[..] { [] => "Unit", @@ -758,7 +758,10 @@ impl InterfaceGenerator<'_> { let camel_name = func.name.to_upper_camel_case(); - let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{camel_name}")); + let func_name = self + .world_gen + .export_ns + .tmp(&format!("wasmExport{camel_name}")); let params = sig .params @@ -771,37 +774,63 @@ impl InterfaceGenerator<'_> { .collect::>() .join(", "); - // Async export prefix for FFI - let async_export_prefix = if async_ { "[async-lift]" } else { "" }; // Async functions return type - let interface_name = match interface { + let interface_name = match self.interface { Some(key) => Some(self.resolve.name_world_key(key)), None => None, }; - let export_name = func.legacy_core_export_name(interface_name.as_deref()); let module_name = interface_name.as_deref().unwrap_or("$root"); self.r#generation_futures_and_streams_import("[export]", func, module_name); uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}({params}) -> {result_type} {{ {src} }} "#, ); + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(if async_ { + LiftLowerAbi::AsyncCallback + } else { + LiftLowerAbi::Sync + }), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::Normal, + }, + ); + + let export = format!( + r#" + #doc(hidden) + pub fn {func_name}({params}) -> {result_type} {{ + {}{func_name}({}) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + (0..sig.params.len()) + .map(|i| format!("p{i}")) + .collect::>() + .join(", "), + ); - self.r#gen + self.world_gen .export - .insert(func_name, format!("{async_export_prefix}{export_name}")); + .insert(export_name, (func_name, export)); + // If async, we also need a callback function and a task_return intrinsic if async_ { - let snake = self.r#gen.name.to_lower_camel_case(); let export_func_name = self - .r#gen + .world_gen .export_ns - .tmp(&format!("wasmExport{snake}Async{camel_name}")); + .tmp(&format!("wasmExportAsync{camel_name}")); let DeferredTaskReturn::Emitted { body: task_return_body, params: task_return_params, @@ -810,12 +839,15 @@ impl InterfaceGenerator<'_> { else { unreachable!() }; - let func_name = func.name.clone(); - let import_module = self.resolve.name_world_key(interface.unwrap()); - self.r#gen.export.insert( - export_func_name.clone(), - format!("[callback]{async_export_prefix}{export_name}"), + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::Callback, + }, ); + let task_return_param_tys = task_return_params .iter() .enumerate() @@ -829,7 +861,7 @@ impl InterfaceGenerator<'_> { .join(", "); let return_ty = match &func.result { Some(result) => self - .r#gen + .world_gen .pkg_resolver .type_name(self.name, result) .to_string(), @@ -840,12 +872,23 @@ impl InterfaceGenerator<'_> { _ => format!("{return_param}: {return_ty}",), }; let snake_func_name = func.name.to_moonbit_ident().to_string(); - let ffi = self.r#gen.pkg_resolver.qualify_package(self.name, FFI_DIR); + let ffi = self + .world_gen + .pkg_resolver + .qualify_package(self.name, FFI_DIR); + + let (task_return_module, task_return_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback), + WasmImport::Func { + interface: self.interface, + func, + }, + ); uwriteln!( self.src, r#" - fn {export_func_name}TaskReturn({task_return_param_tys}) = "[export]{import_module}" "[task-return]{func_name}" + fn {export_func_name}TaskReturn({task_return_param_tys}) = "{task_return_module}" "{task_return_name}" pub fn {snake_func_name}_task_return({return_expr}) -> Unit {{ {task_return_body} @@ -862,7 +905,24 @@ impl InterfaceGenerator<'_> { }} "# ); - } else if abi::guest_export_needs_post_return(self.resolve, func) { + let export = format!( + r#" + pub fn {export_func_name}(event_raw: Int, waitable: Int, code: Int) -> Int {{ + {}{snake_func_name}_callback(event_raw, waitable, code) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + ); + + self.world_gen + .export + .insert(export_name, (export_func_name.clone(), export)); + } + + // If post return is needed, generate it + if abi::guest_export_needs_post_return(self.resolve, func) { let params = sig .results .iter() @@ -876,42 +936,54 @@ impl InterfaceGenerator<'_> { let mut bindgen = FunctionBindgen::new( self, - "INVALID", - self.name, (0..sig.results.len()).map(|i| format!("p{i}")).collect(), ); - abi::post_return(bindgen.r#gen.resolve, func, &mut bindgen); + abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen); let src = bindgen.src; let func_name = self - .r#gen + .world_gen .export_ns .tmp(&format!("wasmExport{camel_name}PostReturn")); uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}({params}) -> Unit {{ {src} }} "# ); - self.r#gen + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::Func { + interface: self.interface, + func, + kind: WasmExportKind::PostReturn, + }, + ); + let export = format!( + r#" + #doc(hidden) + pub fn {func_name}({params}) -> Unit {{ + {}{func_name}({}) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + (0..sig.results.len()) + .map(|i| format!("p{i}")) + .collect::>() + .join(", "), + ); + self.world_gen .export - .insert(func_name, format!("cabi_post_{export_name}")); + .insert(export_name, (func_name, export)); } - - print_docs(&mut self.stub, &func.docs); - uwrite!( - self.stub, - r#" - {func_sig} {{ - ... - }} - "# - ); } fn sig_string(&mut self, sig: &MoonbitSignature, async_: bool) -> String { @@ -919,20 +991,21 @@ impl InterfaceGenerator<'_> { .params .iter() .map(|(name, ty)| { - let ty = self.r#gen.pkg_resolver.type_name(self.name, ty); + let ty = self.world_gen.pkg_resolver.type_name(self.name, ty); format!("{name} : {ty}") }) .collect::>(); let params = params.join(", "); - let (async_prefix, async_suffix) = if async_ { ("async ", "") } else { ("", "") }; let result_type = match &sig.result_type { None => "Unit".into(), - Some(ty) => self.r#gen.pkg_resolver.type_name(self.name, ty), + Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty), }; format!( - "pub {async_prefix}fn {}({params}) -> {}{async_suffix}", - sig.name, result_type + "pub {}fn {}({params}) -> {}", + if async_ { "async " } else { "" }, + sig.name, + result_type ) } } @@ -954,13 +1027,16 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { format!( "{} : {}", field.name.to_moonbit_ident(), - self.r#gen.pkg_resolver.type_name(self.name, &field.ty), + self.world_gen.pkg_resolver.type_name(self.name, &field.ty), ) }) .collect::>() .join("; "); let mut deriviation: Vec<_> = Vec::new(); + if self.derive_opts.derive_debug { + deriviation.push("Debug") + } if self.derive_opts.derive_show { deriviation.push("Show") } @@ -979,12 +1055,14 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { ); } - fn type_resource(&mut self, _id: TypeId, name: &str, docs: &Docs) { + fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) { print_docs(&mut self.src, docs); - let type_name = name; let name = name.to_moonbit_type_ident(); let mut deriviation: Vec<_> = Vec::new(); + if self.derive_opts.derive_debug { + deriviation.push("Debug") + } if self.derive_opts.derive_show { deriviation.push("Show") } @@ -1005,9 +1083,15 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { deriviation.join(", "), ); - let module = self.module; - if self.direction == Direction::Import { + let (drop_module, drop_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ImportedDrop, + }, + ); uwrite!( &mut self.src, r#" @@ -1022,10 +1106,34 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { uwrite!( &mut self.ffi, r#" - fn wasmImportResourceDrop{name}(resource : Int) = "{module}" "[resource-drop]{type_name}" + fn wasmImportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}" "#, ) } else { + let (drop_module, drop_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedDrop, + }, + ); + let (new_module, new_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedNew, + }, + ); + let (rep_module, rep_name) = self.resolve.wasm_import_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmImport::ResourceIntrinsic { + resource: id, + interface: self.interface, + intrinsic: ResourceIntrinsic::ExportedRep, + }, + ); uwrite!( &mut self.src, r#" @@ -1033,58 +1141,69 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { pub fn {name}::new(rep : Int) -> {name} {{ {name}::{name}(wasmExportResourceNew{name}(rep)) }} - fn wasmExportResourceNew{name}(rep : Int) -> Int = "[export]{module}" "[resource-new]{type_name}" + fn wasmExportResourceNew{name}(rep : Int) -> Int = "{new_module}" "{new_name}" /// Drops a resource handle. pub fn {name}::drop(self : Self) -> Unit {{ let {name}(resource) = self wasmExportResourceDrop{name}(resource) }} - fn wasmExportResourceDrop{name}(resource : Int) = "[export]{module}" "[resource-drop]{type_name}" + fn wasmExportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}" /// Gets the `Int` representation of the resource pointed to the given handle. pub fn {name}::rep(self : Self) -> Int {{ let {name}(resource) = self wasmExportResourceRep{name}(resource) }} - fn wasmExportResourceRep{name}(resource : Int) -> Int = "[export]{module}" "[resource-rep]{type_name}" + fn wasmExportResourceRep{name}(resource : Int) -> Int = "{rep_module}" "{rep_name}" "#, ); uwrite!( - &mut self.stub, + &mut self.src, r#" /// Destructor of the resource. - pub fn {name}::dtor(_self : {name}) -> Unit {{ - ... - }} + declare pub fn {name}::dtor(_self : {name}) -> Unit "# ); - let func_name = self.r#gen.export_ns.tmp(&format!("wasmExport{name}Dtor")); - - let export_dir = self.r#gen.opts.r#gen_dir.clone(); - - let r#gen = - self.r#gen - .interface(self.resolve, export_dir.as_str(), "", Direction::Export); + let func_name = self + .world_gen + .export_ns + .tmp(&format!("wasmExport{name}Dtor")); uwrite!( self.ffi, r#" + #doc(hidden) pub fn {func_name}(handle : Int) -> Unit {{ - {}{name}::dtor(handle) + {name}::dtor(handle) }} "#, - r#gen - .r#gen - .pkg_resolver - .qualify_package(r#gen.name, self.name) ); - self.r#gen + let export_name = self.resolve.wasm_export_name( + ManglingAndAbi::Legacy(LiftLowerAbi::Sync), + WasmExport::ResourceDtor { + interface: self.interface.unwrap(), + resource: id, + }, + ); + + let export = format!( + r#" + #doc(hidden) + pub fn {func_name}(handle : Int) -> Unit {{ + {}{func_name}(handle) + }} + "#, + self.world_gen + .pkg_resolver + .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name), + ); + self.world_gen .export - .insert(func_name, format!("{module}#[dtor]{type_name}")); + .insert(export_name, (func_name, export)); } } @@ -1129,6 +1248,9 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("\n "); let mut deriviation: Vec<_> = Vec::new(); + if self.derive_opts.derive_debug { + deriviation.push("Debug") + } if self.derive_opts.derive_show { deriviation.push("Show") } @@ -1194,7 +1316,7 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .map(|case| { let name = case.name.to_upper_camel_case(); if let Some(ty) = case.ty { - let ty = self.r#gen.pkg_resolver.type_name(self.name, &ty); + let ty = self.world_gen.pkg_resolver.type_name(self.name, &ty); format!("{name}({ty})") } else { name.to_string() @@ -1204,6 +1326,9 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("\n "); let mut deriviation: Vec<_> = Vec::new(); + if self.derive_opts.derive_debug { + deriviation.push("Debug") + } if self.derive_opts.derive_show { deriviation.push("Show") } @@ -1249,6 +1374,9 @@ impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> { .join("; "); let mut deriviation: Vec<_> = Vec::new(); + if self.derive_opts.derive_debug { + deriviation.push("Debug") + } if self.derive_opts.derive_show { deriviation.push("Show") } @@ -1372,9 +1500,7 @@ enum DeferredTaskReturn { } struct FunctionBindgen<'a, 'b> { - r#gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, - func_interface: &'b str, + interface_gen: &'b mut InterfaceGenerator<'a>, params: Box<[String]>, src: String, locals: Ns, @@ -1389,8 +1515,6 @@ struct FunctionBindgen<'a, 'b> { impl<'a, 'b> FunctionBindgen<'a, 'b> { fn new( r#gen: &'b mut InterfaceGenerator<'a>, - func_name: &'b str, - func_interface: &'b str, params: Box<[String]>, ) -> FunctionBindgen<'a, 'b> { let mut locals = Ns::default(); @@ -1398,9 +1522,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { locals.tmp(str); }); Self { - r#gen, - func_name, - func_interface, + interface_gen: r#gen, params, src: String::new(), locals, @@ -1453,8 +1575,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .join(", "); let payload = if self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver .non_empty_type(ty.as_ref()) .is_some() @@ -1520,11 +1642,7 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .collect::>(); // Hacky way to get the type name without type parameter - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, ty); + let ty = self.resolve_constructor(ty); let lifted = self.locals.tmp("lifted"); let cases = cases @@ -1533,8 +1651,8 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { .enumerate() .map(|(i, ((case_name, case_ty), Block { body, results, .. }))| { let payload = if self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver .non_empty_type(case_ty.as_ref()) .is_some() @@ -1582,6 +1700,32 @@ impl<'a, 'b> FunctionBindgen<'a, 'b> { results.push(lifted); } + + // Utilities + fn resolve_constructor(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_constructor(self.interface_gen.name, ty) + } + + fn resolve_type_name(&mut self, ty: &Type) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .type_name(self.interface_gen.name, ty) + } + + fn resolve_pkg(&mut self, pkg: &str) -> String { + self.interface_gen + .world_gen + .pkg_resolver + .qualify_package(self.interface_gen.name, pkg) + } + + fn use_ffi(&mut self, str: &'static str) { + self.interface_gen.ffi_imports.insert(str); + } } impl Bindgen for FunctionBindgen<'_, '_> { @@ -1638,13 +1782,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::U8FromI32 => results.push(format!("({}).to_byte()", operands[0])), Instruction::I32FromS8 => { - self.r#gen.ffi_imports.insert(ffi::EXTEND8); + self.use_ffi(ffi::EXTEND8); results.push(format!("mbt_ffi_extend8({})", operands[0])) } Instruction::S8FromI32 => results.push(format!("({} - 0x100)", operands[0])), Instruction::S16FromI32 => results.push(format!("({} - 0x10000)", operands[0])), Instruction::I32FromS16 => { - self.r#gen.ffi_imports.insert(ffi::EXTEND16); + self.use_ffi(ffi::EXTEND16); results.push(format!("mbt_ffi_extend16({})", operands[0])) } Instruction::U16FromI32 => results.push(format!( @@ -1674,11 +1818,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1690,11 +1830,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U16 | Int::U32 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1706,11 +1842,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U64 => { let op = &operands[0]; let flag = self.locals.tmp("flag"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwriteln!( self.src, r#" @@ -1726,27 +1858,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { Int::U8 => { results.push(format!( "{}({}.to_byte())", - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )); } Int::U16 | Int::U32 => { results.push(format!( "{}({}.reinterpret_as_uint())", - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )); } Int::U64 => { results.push(format!( "{}(({}).reinterpret_as_uint().to_uint64() | (({}).reinterpret_as_uint().to_uint64() << 32))", - self.r#gen.r#gen.pkg_resolver.type_name(self.r#gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0], operands[1] )); @@ -1756,11 +1882,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::HandleLower { ty, .. } => { let op = &operands[0]; let handle = self.locals.tmp("handle"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_constructor(&Type::Id(*ty)); uwrite!( self.src, r#" @@ -1771,12 +1893,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::HandleLift { ty, .. } => { let op = &operands[0]; - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_constructor(self.r#gen.name, &Type::Id(*ty)); - + let ty = self.resolve_constructor(&Type::Id(*ty)); results.push(format!( "{}::{}({})", ty, @@ -1805,10 +1922,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { results.push(format!( "{}::{{{ops}}}", - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)) + self.resolve_type_name(&Type::Id(*ty)) )); } @@ -1928,29 +2042,15 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - Instruction::OptionLift { payload, ty } => { + Instruction::OptionLift { ty, .. } => { let some = self.blocks.pop().unwrap(); let _none = self.blocks.pop().unwrap(); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_type_name(&Type::Id(*ty)); let lifted = self.locals.tmp("lifted"); let op = &operands[0]; - let payload = if self - .r#gen - .r#gen - .pkg_resolver - .non_empty_type(Some(*payload)) - .is_some() - { - some.results.into_iter().next().unwrap() - } else { - "None".into() - }; + let assignment = some.results.first().unwrap(); let some = some.body; @@ -1961,7 +2061,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { 0 => Option::None 1 => {{ {some} - Option::Some({payload}) + Option::Some({assignment}) }} _ => panic() }} @@ -1995,10 +2095,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::EnumLift { ty, .. } => results.push(format!( "{}::from({})", - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)), + self.resolve_type_name(&Type::Id(*ty)), operands[0] )), @@ -2006,7 +2103,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U8 => { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.r#gen.ffi_imports.insert(ffi::BYTES2PTR); + self.use_ffi(ffi::BYTES2PTR); uwriteln!( self.src, " @@ -2024,27 +2121,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { let ptr = self.locals.tmp("ptr"); let ty = match element { Type::U32 => { - self.r#gen.ffi_imports.insert(ffi::UINT_ARRAY2PTR); + self.use_ffi(ffi::UINT_ARRAY2PTR); "uint" } Type::U64 => { - self.r#gen.ffi_imports.insert(ffi::UINT64_ARRAY2PTR); + self.use_ffi(ffi::UINT64_ARRAY2PTR); "uint64" } Type::S32 => { - self.r#gen.ffi_imports.insert(ffi::INT_ARRAY2PTR); + self.use_ffi(ffi::INT_ARRAY2PTR); "int" } Type::S64 => { - self.r#gen.ffi_imports.insert(ffi::INT64_ARRAY2PTR); + self.use_ffi(ffi::INT64_ARRAY2PTR); "int64" } Type::F32 => { - self.r#gen.ffi_imports.insert(ffi::FLOAT_ARRAY2PTR); + self.use_ffi(ffi::FLOAT_ARRAY2PTR); "float" } Type::F64 => { - self.r#gen.ffi_imports.insert(ffi::DOUBLE_ARRAY2PTR); + self.use_ffi(ffi::DOUBLE_ARRAY2PTR); "double" } _ => unreachable!(), @@ -2070,7 +2167,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let address = &operands[0]; let length = &operands[1]; - self.r#gen.ffi_imports.insert(ffi::PTR2BYTES); + self.use_ffi(ffi::PTR2BYTES); uwrite!( self.src, " @@ -2083,27 +2180,27 @@ impl Bindgen for FunctionBindgen<'_, '_> { Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 => { let ty = match element { Type::U32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2UINT_ARRAY); + self.use_ffi(ffi::PTR2UINT_ARRAY); "uint" } Type::U64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2UINT64_ARRAY); + self.use_ffi(ffi::PTR2UINT64_ARRAY); "uint64" } Type::S32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2INT_ARRAY); + self.use_ffi(ffi::PTR2INT_ARRAY); "int" } Type::S64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2INT64_ARRAY); + self.use_ffi(ffi::PTR2INT64_ARRAY); "int64" } Type::F32 => { - self.r#gen.ffi_imports.insert(ffi::PTR2FLOAT_ARRAY); + self.use_ffi(ffi::PTR2FLOAT_ARRAY); "float" } Type::F64 => { - self.r#gen.ffi_imports.insert(ffi::PTR2DOUBLE_ARRAY); + self.use_ffi(ffi::PTR2DOUBLE_ARRAY); "double" } _ => unreachable!(), @@ -2129,7 +2226,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let op = &operands[0]; let ptr = self.locals.tmp("ptr"); - self.r#gen.ffi_imports.insert(ffi::STR2PTR); + self.use_ffi(ffi::STR2PTR); uwrite!( self.src, " @@ -2149,7 +2246,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - self.r#gen.ffi_imports.insert(ffi::PTR2STR); + self.use_ffi(ffi::PTR2STR); uwrite!( self.src, " @@ -2168,17 +2265,23 @@ impl Bindgen for FunctionBindgen<'_, '_> { assert!(block_results.is_empty()); let op = &operands[0]; - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); - let _align = self.r#gen.r#gen.sizes.align(element).align_wasm32(); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); + let _align = self + .interface_gen + .world_gen + .sizes + .align(element) + .align_wasm32(); let address = self.locals.tmp("address"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, element); + let ty = self.resolve_type_name(element); let index = self.locals.tmp("index"); - self.r#gen.ffi_imports.insert(ffi::MALLOC); + self.use_ffi(ffi::MALLOC); uwrite!( self.src, " @@ -2207,12 +2310,13 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; let array = self.locals.tmp("array"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, element); - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let ty = self.resolve_type_name(element); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); // let align = self.r#gen.r#gen.sizes.align(element); let index = self.locals.tmp("index"); @@ -2221,7 +2325,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => todo!("result count == {}", results.len()), }; - self.r#gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwrite!( self.src, " @@ -2242,7 +2346,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::IterBasePointer => results.push("iter_base".into()), - Instruction::CallWasm { sig, .. } => { + Instruction::CallWasm { sig, name } => { let assignment = match &sig.results[..] { [result] => { let ty = wasm_type(*result); @@ -2257,7 +2361,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { _ => unreachable!(), }; - let func_name = self.func_name.to_upper_camel_case(); + let func_name = name.to_upper_camel_case(); let operands = operands.join(", "); // TODO: handle this to support async functions @@ -2265,10 +2369,10 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::CallInterface { func, async_ } => { - let name = self.r#gen.r#gen.pkg_resolver.func_call( - self.r#gen.name, + let name = self.interface_gen.world_gen.pkg_resolver.func_call( + self.interface_gen.name, func, - self.func_interface, + self.interface_gen.name, ); let args = operands.join(", "); @@ -2278,14 +2382,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { match func.result { Some(ty) => { let res = self.locals.tmp("return_result"); - ( - res.clone(), - res, - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &ty), - ) + (res.clone(), res, self.resolve_type_name(&ty)) } None => ("_ignore".into(), "".into(), "Unit".into()), }; @@ -2293,11 +2390,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if func.result.is_some() { results.push(async_func_result.clone()); } - let ffi = self - .r#gen - .r#gen - .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + let ffi = self.resolve_pkg(FFI_DIR); uwrite!( self.src, r#" @@ -2344,13 +2437,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let assignment = match func.result { None => "let _ = ".into(), Some(ty) => { - let ty = format!( - "({})", - self.r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &ty) - ); + let ty = format!("({})", self.resolve_type_name(&ty)); let result = self.locals.tmp("result"); if func.result.is_some() { results.push(result.clone()); @@ -2384,16 +2471,21 @@ impl Bindgen for FunctionBindgen<'_, '_> { } else { Vec::new() }; - + if !self.cleanup.is_empty() || self.needs_cleanup_list { + self.use_ffi(ffi::FREE); + } for clean in &self.cleanup { let address = &clean.address; - self.r#gen.ffi_imports.insert(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } if self.needs_cleanup_list { - self.r#gen.ffi_imports.insert(ffi::FREE); - uwriteln!(self.src, "cleanup_list.each(mbt_ffi_free)",); + uwrite!( + self.src, + " + cleanup_list.each(mbt_ffi_free) + ", + ); } match *amt { @@ -2409,7 +2501,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Load { offset } | Instruction::PointerLoad { offset } | Instruction::LengthLoad { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD32); + self.use_ffi(ffi::LOAD32); results.push(format!( "mbt_ffi_load32(({}) + {offset})", operands[0], @@ -2418,7 +2510,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8U { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD8_U); + self.use_ffi(ffi::LOAD8_U); results.push(format!( "mbt_ffi_load8_u(({}) + {offset})", operands[0], @@ -2427,7 +2519,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load8S { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD8); + self.use_ffi(ffi::LOAD8); results.push(format!( "mbt_ffi_load8(({}) + {offset})", operands[0], @@ -2436,7 +2528,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16U { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD16_U); + self.use_ffi(ffi::LOAD16_U); results.push(format!( "mbt_ffi_load16_u(({}) + {offset})", operands[0], @@ -2445,7 +2537,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Load16S { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD16); + self.use_ffi(ffi::LOAD16); results.push(format!( "mbt_ffi_load16(({}) + {offset})", operands[0], @@ -2454,7 +2546,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOAD64); + self.use_ffi(ffi::LOAD64); results.push(format!( "mbt_ffi_load64(({}) + {offset})", operands[0], @@ -2463,7 +2555,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOADF32); + self.use_ffi(ffi::LOADF32); results.push(format!( "mbt_ffi_loadf32(({}) + {offset})", operands[0], @@ -2472,7 +2564,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Load { offset } => { - self.r#gen.ffi_imports.insert(ffi::LOADF64); + self.use_ffi(ffi::LOADF64); results.push(format!( "mbt_ffi_loadf64(({}) + {offset})", operands[0], @@ -2483,7 +2575,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::I32Store { offset } | Instruction::PointerStore { offset } | Instruction::LengthStore { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE32); + self.use_ffi(ffi::STORE32); uwriteln!( self.src, "mbt_ffi_store32(({}) + {offset}, {})", @@ -2494,7 +2586,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store8 { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE8); + self.use_ffi(ffi::STORE8); uwriteln!( self.src, "mbt_ffi_store8(({}) + {offset}, {})", @@ -2505,7 +2597,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I32Store16 { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE16); + self.use_ffi(ffi::STORE16); uwriteln!( self.src, "mbt_ffi_store16(({}) + {offset}, {})", @@ -2516,7 +2608,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::I64Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STORE64); + self.use_ffi(ffi::STORE64); uwriteln!( self.src, "mbt_ffi_store64(({}) + {offset}, {})", @@ -2527,7 +2619,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F32Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STOREF32); + self.use_ffi(ffi::STOREF32); uwriteln!( self.src, "mbt_ffi_storef32(({}) + {offset}, {})", @@ -2538,7 +2630,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } Instruction::F64Store { offset } => { - self.r#gen.ffi_imports.insert(ffi::STOREF64); + self.use_ffi(ffi::STOREF64); uwriteln!( self.src, "mbt_ffi_storef64(({}) + {offset}, {})", @@ -2549,17 +2641,17 @@ impl Bindgen for FunctionBindgen<'_, '_> { } // TODO: see what we can do with align Instruction::Malloc { size, .. } => { - self.r#gen.ffi_imports.insert(ffi::MALLOC); + self.use_ffi(ffi::MALLOC); uwriteln!(self.src, "mbt_ffi_malloc({})", size.size_wasm32()) } Instruction::GuestDeallocate { .. } => { - self.r#gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } Instruction::GuestDeallocateString => { - self.r#gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({})", operands[0]) } @@ -2603,7 +2695,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { let address = &operands[0]; let length = &operands[1]; - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let size = self + .interface_gen + .world_gen + .sizes + .size(element) + .size_wasm32(); // let align = self.r#gen.r#gen.sizes.align(element); if !body.trim().is_empty() { @@ -2620,7 +2717,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { ); } - self.r#gen.ffi_imports.insert(ffi::FREE); + self.use_ffi(ffi::FREE); uwriteln!(self.src, "mbt_ffi_free({address})",); } @@ -2632,16 +2729,12 @@ impl Bindgen for FunctionBindgen<'_, '_> { let result = self.locals.tmp("result"); let op = &operands[0]; // let qualifier = self.r#gen.qualify_package(self.func_interface); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); + let ty = self.resolve_type_name(&Type::Id(*ty)); let ffi = self - .r#gen - .r#gen + .interface_gen + .world_gen .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + .qualify_package(self.interface_gen.name, FFI_DIR); let snake_name = format!("static_{}_future_table", ty.to_snake_case(),); @@ -2689,21 +2782,9 @@ impl Bindgen for FunctionBindgen<'_, '_> { Instruction::StreamLift { ty, .. } => { let result = self.locals.tmp("result"); let op = &operands[0]; - let qualifier = self - .r#gen - .r#gen - .pkg_resolver - .qualify_package(self.r#gen.name, self.func_interface); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, &Type::Id(*ty)); - let ffi = self - .r#gen - .r#gen - .pkg_resolver - .qualify_package(self.r#gen.name, FFI_DIR); + let qualifier = self.resolve_pkg(self.interface_gen.name); + let ty = self.resolve_type_name(&Type::Id(*ty)); + let ffi = self.resolve_pkg(FFI_DIR); let snake_name = format!( "static_{}_stream_table", ty.replace(&qualifier, "").to_snake_case(), @@ -2755,7 +2836,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { let vec = operands[0].clone(); let target = operands[1].clone(); - let size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let size = self.sizes().size(element).size_wasm32(); let index = self.locals.tmp("index"); uwrite!( @@ -2780,12 +2861,8 @@ impl Bindgen for FunctionBindgen<'_, '_> { } = self.blocks.pop().unwrap(); let address = &operands[0]; let array = self.locals.tmp("array"); - let ty = self - .r#gen - .r#gen - .pkg_resolver - .type_name(self.r#gen.name, element); - let elem_size = self.r#gen.r#gen.sizes.size(element).size_wasm32(); + let ty = self.resolve_type_name(element); + let elem_size = self.sizes().size(element).size_wasm32(); let index = self.locals.tmp("index"); let result = match &block_results[..] { @@ -2810,24 +2887,22 @@ impl Bindgen for FunctionBindgen<'_, '_> { } } - fn return_pointer(&mut self, size: ArchitectureSize, align: Alignment) -> String { - if self.r#gen.direction == Direction::Import { - self.r#gen.ffi_imports.insert(ffi::MALLOC); - let address = self.locals.tmp("return_area"); - uwriteln!( - self.src, - "let {address} = mbt_ffi_malloc({})", - size.size_wasm32(), - ); + fn return_pointer(&mut self, size: ArchitectureSize, _align: Alignment) -> String { + self.use_ffi(ffi::MALLOC); + let address = self.locals.tmp("return_area"); + uwriteln!( + self.src, + "let {address} = mbt_ffi_malloc({})", + size.size_wasm32(), + ); + // If the interface is an import, we need to track this for cleanup + // Otherwise, the caller is responsible for cleaning up in post_return + if self.interface_gen.direction == Direction::Import { self.cleanup.push(Cleanup { address: address.clone(), }); - address - } else { - self.r#gen.r#gen.return_area_size = self.r#gen.r#gen.return_area_size.max(size); - self.r#gen.r#gen.return_area_align = self.r#gen.r#gen.return_area_align.max(align); - "return_area".into() } + address } fn push_block(&mut self) { @@ -2842,6 +2917,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { if !self.cleanup.is_empty() { self.needs_cleanup_list = true; + self.use_ffi(ffi::FREE); for cleanup in &self.cleanup { let address = &cleanup.address; @@ -2858,7 +2934,7 @@ impl Bindgen for FunctionBindgen<'_, '_> { } fn sizes(&self) -> &SizeAlign { - &self.r#gen.r#gen.sizes + &self.interface_gen.world_gen.sizes } fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool { @@ -2952,10 +3028,6 @@ fn indent(code: &str) -> Source { indented } -fn generated_preamble(src: &mut Source, version: &str) { - uwriteln!(src, "// Generated by `wit-bindgen` {version}.") -} - fn print_docs(src: &mut String, docs: &Docs) { uwrite!(src, "///|"); if let Some(docs) = &docs.contents { diff --git a/crates/test/src/moonbit.rs b/crates/test/src/moonbit.rs index e22d9489d..a837ae93e 100644 --- a/crates/test/src/moonbit.rs +++ b/crates/test/src/moonbit.rs @@ -1,5 +1,5 @@ use crate::{LanguageMethods, Runner}; -use anyhow::bail; +use anyhow::{Context, bail}; use serde::Deserialize; use std::process::Command; @@ -25,7 +25,12 @@ impl LanguageMethods for MoonBit { } fn default_bindgen_args(&self) -> &[&str] { - &["--derive-show", "--derive-eq", "--derive-error"] + &[ + "--derive-debug", + "--derive-show", + "--derive-eq", + "--derive-error", + ] } fn prepare(&self, runner: &mut Runner) -> anyhow::Result<()> { @@ -60,24 +65,45 @@ impl LanguageMethods for MoonBit { } // Compile the MoonBit bindings to a wasm file - let manifest = compile.bindings_dir.join("moon.mod.json"); let mut cmd = Command::new("moon"); cmd.arg("build") + .arg("--target") + .arg("wasm") + .arg("--release") .arg("--no-strip") // for debugging - .arg("--manifest-path") - .arg(&manifest); + .current_dir(&compile.bindings_dir); runner.run_command(&mut cmd)?; - // Build the component - let artifact = compile - .bindings_dir - .join("target/wasm/release/build/gen/gen.wasm"); + // Build the component. MoonBit toolchains may use either `_build` or + // `target` output roots depending on version/configuration. + let artifact_candidates = [ + compile + .bindings_dir + .join("_build/wasm/release/build/gen/gen.wasm"), + compile + .bindings_dir + .join("target/wasm/release/build/gen/gen.wasm"), + compile + .bindings_dir + .join("_build/wasm/debug/build/gen/gen.wasm"), + compile + .bindings_dir + .join("target/wasm/debug/build/gen/gen.wasm"), + ]; + let artifact = artifact_candidates + .iter() + .find(|path| path.exists()) + .cloned() + .with_context(|| { + format!("failed to locate MoonBit output wasm, looked in: {artifact_candidates:?}",) + })?; // Embed WIT files let manifest_dir = compile.component.path.parent().unwrap(); + let embedded = artifact.with_extension("embedded.wasm"); let mut cmd = Command::new("wasm-tools"); cmd.arg("component") .arg("embed") .args(["--encoding", "utf16"]) - .args(["-o", artifact.to_str().unwrap()]) + .args(["-o", embedded.to_str().unwrap()]) .args(["-w", &compile.component.bindgen.world]) .arg(manifest_dir) .arg(&artifact); @@ -87,7 +113,7 @@ impl LanguageMethods for MoonBit { cmd.arg("component") .arg("new") .args(["-o", compile.output.to_str().unwrap()]) - .arg(&artifact); + .arg(&embedded); runner.run_command(&mut cmd)?; Ok(()) } @@ -104,18 +130,15 @@ impl LanguageMethods for MoonBit { } fn verify(&self, runner: &Runner, verify: &crate::Verify) -> anyhow::Result<()> { - let manifest = verify.bindings_dir.join("moon.mod.json"); let mut cmd = Command::new("moon"); cmd.arg("check") .arg("--warn-list") - .arg("-28") - // .arg("--deny-warn") - .arg("--manifest-path") - .arg(&manifest); + .arg("-28") // avoid warning noise in generated bindings + .current_dir(&verify.bindings_dir); runner.run_command(&mut cmd)?; let mut cmd = Command::new("moon"); - cmd.arg("build").arg("--manifest-path").arg(&manifest); + cmd.arg("build").current_dir(&verify.bindings_dir); runner.run_command(&mut cmd)?; Ok(()) diff --git a/tests/runtime/resources/resources.mbt b/tests/runtime/resources/resources.mbt index 001535071..247fd8690 100644 --- a/tests/runtime/resources/resources.mbt +++ b/tests/runtime/resources/resources.mbt @@ -1,6 +1,6 @@ //@ [lang] //@ path = 'gen/interface/exports/stub.mbt' -//@ pkg_config = """{ "import": ["test/resources/interface/imports"] }""" +//@ pkg_config = """{ "warn-list": "-44", "import": ["test/resources/interface/imports"] }""" ///| let x : Map[Int, Int] = {}