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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions API_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ can continue calling the generated functions directly.
## Shared conventions

- Preserve native result codes in typed errors and include the failed operation.
- Return `!T` from convenience operations instead of discarding status codes.
- Return `!&T` for owning convenience operations instead of discarding status
codes or copying resource wrappers across module boundaries.
- Hide count-then-fill enumeration without hiding the returned native handles.
- Use `close()` for reference-counted OpenCL ownership wrappers.
- Owning wrappers are copyable V values; copying does not transfer ownership. Exactly one copy may close the native handle until a future reference-backed ownership redesign.
- Owning wrappers are `@[nocopy]` and constructors return owned pointers.
`clone_ref()` is the only supported way to create another native reference;
SVM remains uniquely owned because OpenCL provides no retain operation.
- Keep constructors explicit about device choice and requested capabilities.
- Never enable an extension or feature merely because headers declare it; query runtime support.
- Keep unsafe pointers at the low-level boundary and expose slices or strings where ownership is clear.
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

- Prevent implicit copying of every owned OpenCL wrapper with `@[nocopy]` and
provide explicit `clone_ref()` operations backed by the corresponding native
retain calls. SVM allocations remain uniquely owned because OpenCL provides
no retain operation for them.
- Return owned pointers from constructors, asynchronous operations, and
`clone_ref()` so owners cross module boundaries without hidden copies on
released V and strict V3.
- Pin Vulkan and GLFW example dependencies to immutable revisions in CI rather
than relying on mutable VPM installs.
- Ignore local compiler products and caches so building the bundled examples
Expand Down
2 changes: 1 addition & 1 deletion GENERATOR_COMMIT
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ebbdf9433cd3ee2d7ec416bbe079634ed32b0174
185ac1cdff29e24c39a1fb0dffdbe21c184a31ae
30 changes: 15 additions & 15 deletions OWNERSHIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
The convenience API uses explicit `close()` methods. Close events, kernels,
programs, memory objects, and queues before their parent context.

V structs are values and can be copied. Each copy of an `OwnedContext`,
`OwnedCommandQueue`, `Buffer`, `Image2D`, `OwnedSampler`, `SvmAllocation`,
`OwnedProgram`, `OwnedKernel`, `OwnedEvent`, or external-interoperability owner
refers to the same OpenCL resource. Closing one value clears that value's
handle, but it does not clear copies made earlier.
`OwnedContext`, `OwnedCommandQueue`, `Buffer`, `Image2D`, `OwnedSampler`,
`SvmAllocation`, `OwnedProgram`, `OwnedKernel`, `OwnedEvent`, and
`OwnedExternalSemaphore` are `@[nocopy]`. Constructors return owned pointers so
resources cross module boundaries without copying. Pass those pointers directly
to convenience functions; do not add another `&`.

Until a breaking ownership redesign, follow these rules:
`close()` is mutable and idempotent: it releases one native reference and
clears the wrapper's handle. There are no implicit finalizers. For OpenCL
objects which support native reference counting, `clone_ref()` performs the
matching `clRetain*` operation and returns a separate owned pointer which must
also be closed. SVM has no retain operation and remains uniquely owned.

1. Treat owning wrappers as move-only by convention.
2. Pass references or raw handles to helpers instead of copying owners.
3. Register cleanup immediately and close children before parents.
4. Never close more than one copy of the same owned reference.
1. Keep each returned owner pointer and register cleanup immediately.
2. Pass owner pointers directly; copy only raw handles and discovery values.
3. Close children before parents.
4. Use `clone_ref()` only when a second independently retained reference is
required, and close both owners.

For coarse-grained SVM, call `map()` before accessing `SvmAllocation.handle`
from the host and `unmap()` before submitting device work. Wait for the unmap
event before using the allocation from a kernel. Blocking and asynchronous
`write`/`read` helpers perform OpenCL SVM copies and do not expose host access.

The intended post-`0.x` design is a reference-backed control block. All copies
would observe one closed state and the native reference would be released at
most once. Explicit retain/clone operations would remain separate when the
caller actually wants another OpenCL reference.
47 changes: 27 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ defer { context.close() or {} }
mut queue := context.command_queue(device, cl.CommandQueueProperties(0))!
defer { queue.close() or {} }

mut buffer := cl.new_buffer[f32](&context, cl.mem_read_write, 1024)!
mut buffer := cl.new_buffer[f32](context, cl.mem_read_write, 1024)!
defer { buffer.close() or {} }
buffer.write(&queue, 0, []f32{len: 1024, init: f32(index)})!
buffer.write(queue, 0, []f32{len: 1024, init: f32(index)})!
```

The element type used by `Buffer[T]`, typed transfers, and kernel arguments
Expand All @@ -100,30 +100,37 @@ Source compilation preserves compiler diagnostics through `ProgramBuildError`. O
kernels support typed scalar and buffer arguments plus one-dimensional dispatch:

```v
mut program := cl.build_source_program(&context, device, source, '')!
mut program := cl.build_source_program(context, device, source, '')!
defer { program.close() or {} }
mut kernel := program.kernel('transform')!
defer { kernel.close() or {} }
kernel.set_buffer_arg(0, buffer.handle)!
kernel.set_slice_arg(1, [f32(0.5), 1.0])! // e.g. an OpenCL float2
kernel.enqueue_1d(&queue, usize(buffer.count), 0)!
kernel.enqueue_1d(queue, usize(buffer.count), 0)!
```

Non-blocking transfers and dispatch return owned events and accept native event
dependency lists. Host slices must remain alive until their transfer event completes:

```v
mut uploaded := buffer.write_async(&queue, 0, values, []cl.Event{})!
mut dispatched := kernel.enqueue_1d_after(&queue, usize(buffer.count), 0,
mut uploaded := buffer.write_async(queue, 0, values, []cl.Event{})!
mut dispatched := kernel.enqueue_1d_after(queue, usize(buffer.count), 0,
[uploaded.handle])!
mut downloaded := buffer.read_async(&queue, 0, mut result, [dispatched.handle])!
mut downloaded := buffer.read_async(queue, 0, mut result, [dispatched.handle])!
downloaded.wait()!
profile := downloaded.profile()! // queue must use cl.queue_profiling_enable
downloaded.close()!
dispatched.close()!
uploaded.close()!
```

Owned contexts, queues, buffers, images, samplers, programs, kernels, events,
and external semaphores are `@[nocopy]`, preventing accidental double release.
Constructors return owned pointers; pass them directly without adding another
`&`. When two independently closable owners are required,
call `clone_ref()`; it performs the matching OpenCL retain operation. SVM
allocations cannot be retained and therefore always have one unique owner.

For multidimensional kernels, `enqueue_nd_after()` accepts one to three global
dimensions and either a matching local-size slice or an empty slice for an
implementation-selected work-group size.
Expand All @@ -137,14 +144,14 @@ format := cl.ImageFormat{
image_channel_order: cl.rgba
image_channel_data_type: cl.unorm_int8
}
mut image := cl.new_image_2d[u32](&context, cl.mem_read_write, format, 64, 64)!
mut image := cl.new_image_2d[u32](context, cl.mem_read_write, format, 64, 64)!
defer { image.close() or {} }
mut sampler := cl.new_sampler(&context, false, cl.address_clamp_to_edge,
mut sampler := cl.new_sampler(context, false, cl.address_clamp_to_edge,
cl.filter_nearest)!
defer { sampler.close() or {} }
image.write(&queue, pixels)!
image.set_kernel_arg(&kernel, 0)!
kernel.set_sampler_arg(1, &sampler)!
image.write(queue, pixels)!
image.set_kernel_arg(kernel, 0)!
kernel.set_sampler_arg(1, sampler)!
```

Shared virtual memory is similarly typed and capability-gated. Coarse-grained
Expand All @@ -155,10 +162,10 @@ bound directly to a kernel:
svm_capabilities := cl.device_svm_support(device)!
if svm_capabilities & (cl.device_svm_coarse_grain_buffer |
cl.device_svm_fine_grain_buffer) != 0 {
mut shared := cl.new_svm[u32](&context, cl.mem_read_write, 1024, 0)!
mut shared := cl.new_svm[u32](context, cl.mem_read_write, 1024, 0)!
defer { shared.close() }
shared.write(&queue, 0, values)!
shared.set_kernel_arg(&kernel, 0)!
shared.write(queue, 0, values)!
shared.set_kernel_arg(kernel, 0)!
}
```

Expand All @@ -182,15 +189,15 @@ descriptors are obtained from the exporting API; its handle-ownership rules stil

```v
memory_interop := cl.load_external_memory_interop(platform, capabilities)!
mut shared := memory_interop.import_opaque_fd_buffer[f32](&context, memory_fd,
mut shared := memory_interop.import_opaque_fd_buffer[f32](context, memory_fd,
element_count, cl.mem_read_write)!
defer { shared.close() or {} }

semaphore_interop := cl.load_external_semaphore_interop(platform, capabilities)!
mut ready := semaphore_interop.import_opaque_fd(&context, semaphore_fd)!
mut ready := semaphore_interop.import_opaque_fd(context, semaphore_fd)!
defer { ready.close() or {} }
mut waited := ready.wait(&queue, [])!
mut acquired := memory_interop.acquire(&queue, [shared.handle], [waited.handle])!
mut waited := ready.wait(queue, [])!
mut acquired := memory_interop.acquire(queue, [shared.handle], [waited.handle])!
defer { acquired.close() or {} }
defer { waited.close() or {} }
```
Expand All @@ -207,7 +214,7 @@ ready.wait()!

See [`API_DESIGN.md`](API_DESIGN.md) for the conventions shared with the companion
Vulkan convenience layer.
See [`OWNERSHIP.md`](OWNERSHIP.md) for the current copy and cleanup rules.
See [`OWNERSHIP.md`](OWNERSHIP.md) for the current ownership and cleanup rules.

## Advanced example

Expand Down
Loading
Loading