Skip to content

Add experimental async TCP runtime path - #27

Merged
serene1491 merged 17 commits into
poplanguage:masterfrom
OFC-KazzKyy:feat/experimental-ebpf-backend
Jul 14, 2026
Merged

Add experimental async TCP runtime path#27
serene1491 merged 17 commits into
poplanguage:masterfrom
OFC-KazzKyy:feat/experimental-ebpf-backend

Conversation

@OFC-KazzKyy

Copy link
Copy Markdown

Summary

  • add async task/await contracts and native suspend lowering
  • expose native Task.CancelSource and Net.Tcp standard adapters
  • add printable UInt64/Boolean output and a native TCP smoke example

Validation

  • cargo test -p pop-types --test bootstrap
  • cargo test -p pop-standard
  • cargo test -p pop-driver --test native_exports
  • cargo test -p pop-driver --test front_end_pipeline standard_print_overloads_are_identity_bound_and_survive_hir_and_mir -- --nocapture
  • cargo test -p pop-backend-llvm --test lowering qualified_task_and_tcp_standard_calls_lower_to_native_adapters -- --nocapture
  • cargo check -p pop-types -p pop-mir -p pop-backend-llvm -p pop-standard -p pop-driver --tests
  • cargo fmt --all -- --check
  • git diff --check
  • pop build examples/nativeTaskTcp.pop --output /home/kazzkyy/Documentos/pop-native-task-tcp-demo
  • /home/kazzkyy/Documentos/pop-native-task-tcp-demo

kazzkyy and others added 17 commits July 14, 2026 09:38
Introduce Blocks as a first-class effect distinct from Suspends and map it to a closed BlockingPool runtime contract. This keeps native blocking work out of coroutine scheduling contracts and gives later async/network slices a precise capability boundary.
Reserve async and await tokens and preserve the async calling convention on declarations, closures, cleanup blocks, awaits, and function types. Type resolution rejects async-only forms closed until Task<T> semantics are wired, avoiding an implicit sync fallback.
Keep the async task and suspension work authorized by ADR 0068, but\ndo not publish or implement the networking layer before its resource,\ncancellation, buffer, and runtime contracts are accepted.
The removed networking prototype contributed 30 entries.  Keep the\nmerged master baseline at its authoritative 83 entries.
Handle the Await instruction using the bootstrap task representation so\nthe reference backend shares the async completion semantics already used\nby the native suspension boundary.  Cover the path through parsed source,\ntyped HIR, verified MIR, and observable interpreter execution.
Factor the expanded MIR callable signature into one private alias and\nkeep function-type resolution local to its syntax branch.  This preserves\nthe async marker while satisfying the workspace's warning-free policy.
@serene1491
serene1491 merged commit 35d1c1c into poplanguage:master Jul 14, 2026
1 check passed
@kleeedolinux

Copy link
Copy Markdown
Collaborator

@cubic-dev-ai review this pull request

@cubic-dev-ai

cubic-dev-ai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review this pull request

@kleeedolinux I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

18 issues found across 53 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/compiler/backends/llvm/src/lowering.rs">

<violation number="1" location="crates/compiler/backends/llvm/src/lowering.rs:181">
P3: Generated LLVM modules now carry an unused `@pop_rt_resume` declaration because no lowering path emits a resume call. Consider removing it until resume lowering exists so the emitted IR reflects actual backend capabilities.</violation>
</file>

<file name="crates/compiler/compile-time/src/lowering.rs">

<violation number="1" location="crates/compiler/compile-time/src/lowering.rs:517">
P3: Unsupported async constructs are reported as `TypedFailure`, conflating suspension/async cleanup with result/error flow in the public `UnsupportedCompileTimeConstruct` classification. A dedicated async/suspension variant would keep lowering diagnostics and downstream classification accurate for both `Await` and `AsyncDefer`.</violation>
</file>

<file name="crates/compiler/syntax/src/body.rs">

<violation number="1" location="crates/compiler/syntax/src/body.rs:647">
P3: Async-defer block parsing duplicates nearly all of `parse_defer`, so future block-grammar or recovery changes can diverge between the two forms. Consider sharing the body/`end` parsing after each introducer is consumed.</violation>
</file>

<file name="crates/compiler/syntax/src/signature.rs">

<violation number="1" location="crates/compiler/syntax/src/signature.rs:244">
P3: Diagnostics using `FunctionWork.span` omit the `async` qualifier even though it is part of the resolved signature. Preserving the consumed token and starting the range there would match async function-type and capture-function spans.</violation>

<violation number="2" location="crates/compiler/syntax/src/signature.rs:499">
P3: The new `peek_kind` in `SignatureParser` uses `saturating_add(1)` while the identical `peek_kind` methods in `BodyParser` and `AttributeParser` use plain `+ 1`. For `usize` positions, both are functionally equivalent (overflow past `usize::MAX` is impossible for token indices). Consider using `+ 1` here to match the established pattern in the other parsers, or update all three for consistency if `saturating_add` is preferred.</violation>
</file>

<file name="crates/compiler/driver/src/reference.rs">

<violation number="1" location="crates/compiler/driver/src/reference.rs:270">
P2: Existing version-1 artifacts become unreadable while new artifacts are still labeled version 1 because this required serialized field changes both reference and capsule encodings. Bump the affected schema versions (and their manifest/validation constants) so the incompatibility is reported as `UnsupportedSchema` rather than `InvalidJson`.</violation>

<violation number="2" location="crates/compiler/driver/src/reference.rs:270">
P0: Public async functions imported from reference metadata are rehydrated as synchronous because only nested function types preserve `is_async`; `ReferenceFunction` emission and `referenced_generic` still omit the top-level flag. Persist and pass `HirFunction::is_async` through the reference signature so dependent Bubbles infer `Task` results correctly.</violation>
</file>

<file name="crates/compiler/types/src/lib.rs">

<violation number="1" location="crates/compiler/types/src/lib.rs:303">
P1: Existing schema-v1 metadata silently decodes old `UnsafeMemory` as `Blocks`, and every later effect is shifted because `EffectSummary` persists the raw `u16` while `bit()` uses enum ordinals. Preserve existing discriminants, or bump and migrate the artifact schema when adding `Blocks`.</violation>
</file>

<file name="crates/compiler/driver/src/api.rs">

<violation number="1" location="crates/compiler/driver/src/api.rs:130">
P2: Documented overloads whose function-typed parameters differ only by asyncness now produce the same member ID, causing `pop documentation` to fail with `DuplicateMemberId`. Include the async marker when `reference_type_text` formats this newly distinguished field.</violation>
</file>

<file name="crates/runtime/interface/src/operation.rs">

<violation number="1" location="crates/runtime/interface/src/operation.rs:48">
P1: ABI 1.11 negotiation can now accept a runtime that predates these task symbols, leading to missing-symbol link/load failures. This native ABI extension should advance the minor version and update identity/capability tests and documentation.</violation>
</file>

<file name="crates/compiler/hir/src/verification.rs">

<violation number="1" location="crates/compiler/hir/src/verification.rs:2466">
P3: HIR verification reparses and cross-validates the complete bootstrap schema for every `await`, making compile cost scale with the number of await expressions. Resolve/cache the `Task` definition once per verifier or schema instead.</violation>
</file>

<file name="crates/compiler/hir/src/ir.rs">

<violation number="1" location="crates/compiler/hir/src/ir.rs:1453">
P3: HIR dumps omit the new async-function state, so sync and async functions with the same body/signature can render identically and regression snapshots cannot observe this semantic distinction. Including an `async` marker in `dump_function` would keep diagnostic output complete.</violation>
</file>

<file name="crates/compiler/backends/llvm/tests/lowering.rs">

<violation number="1" location="crates/compiler/backends/llvm/tests/lowering.rs:202">
P2: The test validates the async/task suspend lowering only through text pattern matching. The adjacent optional-pattern test validates generated IR with `llvm-as` after string checks. For a newly introduced code-generation path (async/task suspend), adding an `llvm-as` validation step would catch malformed IR early and build confidence in the lowering.</violation>
</file>

<file name="crates/compiler/mir/src/lowering.rs">

<violation number="1" location="crates/compiler/mir/src/lowering.rs:2917">
P1: Awaiting a valid async result of `0` or `false` traps at runtime because the bootstrap task representation stores the completion directly in the handle while `pop_rt_suspend` reserves zero as invalid. The task encoding or suspend boundary needs to preserve zero-valued completions.</violation>

<violation number="2" location="crates/compiler/mir/src/lowering.rs:4383">
P2: Functions containing `await` underreport `MayTrap`, even though `pop_rt_suspend` traps on a zero handle. Include `MirEffect::MayTrap` while that runtime behavior remains part of the await contract.</violation>
</file>

<file name="crates/compiler/backends/llvm/src/instruction_lowering.rs">

<violation number="1" location="crates/compiler/backends/llvm/src/instruction_lowering.rs:3207">
P2: Async functions with floating completion types can type-check but fail in LLVM lowering when awaited. The new `lower_await` match rejects `float`/`double` completions via the fallback error path; adding float decode arms keeps await behavior consistent with other scalar runtime-slot conversions.</violation>
</file>

<file name="crates/runtime/native/src/task.rs">

<violation number="1" location="crates/runtime/native/src/task.rs:29">
P2: The cancellation ABI pair (`pop_rt_task_cancel` / `pop_rt_task_cancellation_requested`) has no shared mutable state, so cancellation cannot be observed end-to-end through these functions. `pop_rt_task_cancel` returns `u8::from(token != 0)` — effectively reporting success for any non-zero token value without recording anything. `pop_rt_task_cancellation_requested` discards the token and always returns 0, so even after a "successful" cancel call the request is never visible. If these are intentionally bootstrap stubs, consider updating the doc comments to match the actual no-op semantics (similar to how `pop_rt_suspend` already documents its bootstrap limitation with "A full coroutine scheduler can replace this boundary") and noting that cancellation is not yet wired through shared state.</violation>
</file>

<file name="crates/compiler/mir/src/ir.rs">

<violation number="1" location="crates/compiler/mir/src/ir.rs:717">
P2: The MIR text dump/parse format does not support the new `is_async` field on `MirFunction`. `dump_function` in render.rs doesn't emit an async marker, and `parse_function` in text.rs always sets `is_async: false`. This means async functions cannot round-trip through the text format — dumping an async function and re-parsing it loses the async identity entirely. Consider adding an `async` keyword or marker to the function header in both dump and parse sides.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

results,
effects,
}) => Ok(ReferenceType::Function {
is_async: *is_async,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Public async functions imported from reference metadata are rehydrated as synchronous because only nested function types preserve is_async; ReferenceFunction emission and referenced_generic still omit the top-level flag. Persist and pass HirFunction::is_async through the reference signature so dependent Bubbles infer Task results correctly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/driver/src/reference.rs, line 270:

<comment>Public async functions imported from reference metadata are rehydrated as synchronous because only nested function types preserve `is_async`; `ReferenceFunction` emission and `referenced_generic` still omit the top-level flag. Persist and pass `HirFunction::is_async` through the reference signature so dependent Bubbles infer `Task` results correctly.</comment>

<file context>
@@ -262,10 +262,12 @@ fn reference_type_with_parameters(
             results,
             effects,
         }) => Ok(ReferenceType::Function {
+            is_async: *is_async,
             parameters: parameters
                 .iter()
</file context>
Fix with cubic

Comment on lines +303 to 304
Blocks,
UnsafeMemory,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Existing schema-v1 metadata silently decodes old UnsafeMemory as Blocks, and every later effect is shifted because EffectSummary persists the raw u16 while bit() uses enum ordinals. Preserve existing discriminants, or bump and migrate the artifact schema when adding Blocks.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/types/src/lib.rs, line 303:

<comment>Existing schema-v1 metadata silently decodes old `UnsafeMemory` as `Blocks`, and every later effect is shifted because `EffectSummary` persists the raw `u16` while `bit()` uses enum ordinals. Preserve existing discriminants, or bump and migrate the artifact schema when adding `Blocks`.</comment>

<file context>
@@ -299,6 +300,7 @@ pub enum Effect {
     MayTrap,
     MayUnwind,
     Suspends,
+    Blocks,
     UnsafeMemory,
     ForeignFunction,
</file context>
Suggested change
Blocks,
UnsafeMemory,
Blocks = 11,
UnsafeMemory = 5,
Fix with cubic

Suspend,
Resume,
TaskCancel,
TaskCancellationRequested,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: ABI 1.11 negotiation can now accept a runtime that predates these task symbols, leading to missing-symbol link/load failures. This native ABI extension should advance the minor version and update identity/capability tests and documentation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/runtime/interface/src/operation.rs, line 48:

<comment>ABI 1.11 negotiation can now accept a runtime that predates these task symbols, leading to missing-symbol link/load failures. This native ABI extension should advance the minor version and update identity/capability tests and documentation.</comment>

<file context>
@@ -44,6 +44,8 @@ pub enum RuntimeOperation {
     Suspend,
     Resume,
+    TaskCancel,
+    TaskCancellationRequested,
     InitializeModule,
     InitializeBubble,
</file context>
Fix with cubic

HirExpressionKind::Function(function) => {
MirInstructionKind::FunctionReference(*function)
}
HirExpressionKind::Await { task } => MirInstructionKind::Await {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Awaiting a valid async result of 0 or false traps at runtime because the bootstrap task representation stores the completion directly in the handle while pop_rt_suspend reserves zero as invalid. The task encoding or suspend boundary needs to preserve zero-valued completions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/mir/src/lowering.rs, line 2917:

<comment>Awaiting a valid async result of `0` or `false` traps at runtime because the bootstrap task representation stores the completion directly in the handle while `pop_rt_suspend` reserves zero as invalid. The task encoding or suspend boundary needs to preserve zero-valued completions.</comment>

<file context>
@@ -2905,6 +2914,9 @@ impl<'hir> FunctionBuilder<'hir> {
             HirExpressionKind::Function(function) => {
                 MirInstructionKind::FunctionReference(*function)
             }
+            HirExpressionKind::Await { task } => MirInstructionKind::Await {
+                task: self.lower_expression(task),
+            },
</file context>
Fix with cubic

results,
effects,
}) => Ok(ReferenceType::Function {
is_async: *is_async,

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Existing version-1 artifacts become unreadable while new artifacts are still labeled version 1 because this required serialized field changes both reference and capsule encodings. Bump the affected schema versions (and their manifest/validation constants) so the incompatibility is reported as UnsupportedSchema rather than InvalidJson.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/driver/src/reference.rs, line 270:

<comment>Existing version-1 artifacts become unreadable while new artifacts are still labeled version 1 because this required serialized field changes both reference and capsule encodings. Bump the affected schema versions (and their manifest/validation constants) so the incompatibility is reported as `UnsupportedSchema` rather than `InvalidJson`.</comment>

<file context>
@@ -262,10 +262,12 @@ fn reference_type_with_parameters(
             results,
             effects,
         }) => Ok(ReferenceType::Function {
+            is_async: *is_async,
             parameters: parameters
                 .iter()
</file context>
Fix with cubic

})
}

fn parse_async_defer(&mut self) -> Result<StatementSyntax, FunctionBodyError> {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Async-defer block parsing duplicates nearly all of parse_defer, so future block-grammar or recovery changes can diverge between the two forms. Consider sharing the body/end parsing after each introducer is consumed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/syntax/src/body.rs, line 647:

<comment>Async-defer block parsing duplicates nearly all of `parse_defer`, so future block-grammar or recovery changes can diverge between the two forms. Consider sharing the body/`end` parsing after each introducer is consumed.</comment>

<file context>
@@ -629,6 +644,21 @@ impl BodyParser<'_> {
         })
     }
 
+    fn parse_async_defer(&mut self) -> Result<StatementSyntax, FunctionBodyError> {
+        let start = self.expect(TokenKind::Async, "`async`")?.range().start();
+        self.expect(TokenKind::Defer, "`defer`")?;
</file context>
Fix with cubic

Comment on lines +244 to 247
let is_async = self.consume(TokenKind::Async).is_some();
let start = self
.expect(TokenKind::Function, "`function`")?
.range()

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Diagnostics using FunctionWork.span omit the async qualifier even though it is part of the resolved signature. Preserving the consumed token and starting the range there would match async function-type and capture-function spans.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/syntax/src/signature.rs, line 244:

<comment>Diagnostics using `FunctionWork.span` omit the `async` qualifier even though it is part of the resolved signature. Preserving the consumed token and starting the range there would match async function-type and capture-function spans.</comment>

<file context>
@@ -230,10 +237,11 @@ impl SignatureParser<'_> {
         {
             self.position += 1;
         }
+        let is_async = self.consume(TokenKind::Async).is_some();
         let start = self
             .expect(TokenKind::Function, "`function`")?
</file context>
Fix with cubic

}
HirExpressionKind::Await { task } => {
self.verify_expression(task, visible);
let Some(task_definition) = pop_types::embedded_bootstrap_schema()

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: HIR verification reparses and cross-validates the complete bootstrap schema for every await, making compile cost scale with the number of await expressions. Resolve/cache the Task definition once per verifier or schema instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/hir/src/verification.rs, line 2466:

<comment>HIR verification reparses and cross-validates the complete bootstrap schema for every `await`, making compile cost scale with the number of await expressions. Resolve/cache the `Task` definition once per verifier or schema instead.</comment>

<file context>
@@ -2461,6 +2461,32 @@ impl Verifier<'_> {
             }
+            HirExpressionKind::Await { task } => {
+                self.verify_expression(task, visible);
+                let Some(task_definition) = pop_types::embedded_bootstrap_schema()
+                    .ok()
+                    .and_then(|schema| schema.type_by_source_name("Task").copied())
</file context>
Fix with cubic

}

#[must_use]
pub const fn is_async(&self) -> bool {

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: HIR dumps omit the new async-function state, so sync and async functions with the same body/signature can render identically and regression snapshots cannot observe this semantic distinction. Including an async marker in dump_function would keep diagnostic output complete.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/hir/src/ir.rs, line 1453:

<comment>HIR dumps omit the new async-function state, so sync and async functions with the same body/signature can render identically and regression snapshots cannot observe this semantic distinction. Including an `async` marker in `dump_function` would keep diagnostic output complete.</comment>

<file context>
@@ -1448,6 +1449,11 @@ impl HirFunction {
     }
 
+    #[must_use]
+    pub const fn is_async(&self) -> bool {
+        self.is_async
+    }
</file context>
Fix with cubic

}

fn peek_kind(&self) -> Option<TokenKind> {
self.tokens

@cubic-dev-ai cubic-dev-ai Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new peek_kind in SignatureParser uses saturating_add(1) while the identical peek_kind methods in BodyParser and AttributeParser use plain + 1. For usize positions, both are functionally equivalent (overflow past usize::MAX is impossible for token indices). Consider using + 1 here to match the established pattern in the other parsers, or update all three for consistency if saturating_add is preferred.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/compiler/syntax/src/signature.rs, line 499:

<comment>The new `peek_kind` in `SignatureParser` uses `saturating_add(1)` while the identical `peek_kind` methods in `BodyParser` and `AttributeParser` use plain `+ 1`. For `usize` positions, both are functionally equivalent (overflow past `usize::MAX` is impossible for token indices). Consider using `+ 1` here to match the established pattern in the other parsers, or update all three for consistency if `saturating_add` is preferred.</comment>

<file context>
@@ -472,6 +495,12 @@ impl SignatureParser<'_> {
     }
 
+    fn peek_kind(&self) -> Option<TokenKind> {
+        self.tokens
+            .get(self.position.saturating_add(1))
+            .map(|token| token.kind())
</file context>
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants