✓
Passing This code compiles and runs correctly.
Code
// Test 390_105: self|mlir[gpu] — a second GPU-execution witness (rung C).
//
// Distinct op (+= 100.0) and distinct numbers from 390_103's *2.0, so a PASS
// can't come from a fixed blob or a cached result — the M2 Pro genuinely
// computes {1,2,3} -> {101,102,103}. Same path as 390_103: emit gpu.module ->
// convert-gpu-to-spirv -> spirv-val-gated blob, then the host binary @imports
// koru_gpu_dispatch, uploads, dispatches on the local Vulkan device (MoltenVK),
// reads back. The kernel runs on the GPU of the machine that compiled it.
//
// RESIDUAL: the call-site `[gpu]` parameter is scaffolding — target selection
// belongs to the (separate) per-event selection-language endeavor.
import std/kernel
import std/io
std/kernel:shape(Body) {
mass: f32,
}
std/kernel:init(Body) {
{ mass: 1.0 },
{ mass: 2.0 },
{ mass: 3.0 },
}
| kernel k |> std/kernel:self|mlir[gpu] { k.mass += 100.0 }
| computed c |> std/io:print.blk {
mass[0]={{ c[0].mass:f }} mass[1]={{ c[1].mass:f }} mass[2]={{ c[2].mass:f }}
}
Supporting Files
const std = @import("std");
const vk = @cImport({
@cInclude("stdlib.h");
@cInclude("vulkan/vulkan.h");
});
fn zi(comptime T: type, fields: anytype) T {
return std.mem.zeroInit(T, fields);
}
/// Dispatch one SPIR-V compute kernel over `buf` (n f32, one thread/element via
/// block_id x) on the local Vulkan device (M2 Pro / MoltenVK). Mutates in place.
pub fn koruGpuDispatchF32(spv: []const u8, entry: [*:0]const u8, buf: [*]f32, n: u32) void {
const bytes: vk.VkDeviceSize = @as(vk.VkDeviceSize, n) * @sizeOf(f32);
// Point the Vulkan loader at MoltenVK (portability ICD). Hardcoded PoC path,
// same family as the hardcoded LLVM toolchain path — resolves through
// deps/requires later. setenv before the first Vulkan call so the loader
// sees it at ICD enumeration.
_ = vk.setenv("VK_ICD_FILENAMES", "/usr/local/share/vulkan/icd.d/MoltenVK_icd.json", 1);
// instance (MoltenVK is a portability driver)
const inst_exts = [_][*:0]const u8{ "VK_KHR_portability_enumeration", "VK_KHR_get_physical_device_properties2" };
const app = zi(vk.VkApplicationInfo, .{ .sType = vk.VK_STRUCTURE_TYPE_APPLICATION_INFO, .apiVersion = vk.VK_API_VERSION_1_2 });
var inst: vk.VkInstance = undefined;
check(vk.vkCreateInstance(&zi(vk.VkInstanceCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.flags = 0x00000001, // ENUMERATE_PORTABILITY_BIT_KHR
.pApplicationInfo = &app,
.enabledExtensionCount = 2,
.ppEnabledExtensionNames = &inst_exts,
}), null, &inst), "vkCreateInstance");
var ndev: u32 = 0;
check(vk.vkEnumeratePhysicalDevices(inst, &ndev, null), "enumPD count");
if (ndev == 0) @panic("koru gpu: no Vulkan device");
var pds: [8]vk.VkPhysicalDevice = undefined;
if (ndev > 8) ndev = 8;
check(vk.vkEnumeratePhysicalDevices(inst, &ndev, &pds), "enumPD");
const pd = pds[0];
var nqf: u32 = 0;
vk.vkGetPhysicalDeviceQueueFamilyProperties(pd, &nqf, null);
var qfs: [16]vk.VkQueueFamilyProperties = undefined;
if (nqf > 16) nqf = 16;
vk.vkGetPhysicalDeviceQueueFamilyProperties(pd, &nqf, &qfs);
var qfi: u32 = 0xFFFF_FFFF;
var i: u32 = 0;
while (i < nqf) : (i += 1) {
if (qfs[i].queueFlags & vk.VK_QUEUE_COMPUTE_BIT != 0) {
qfi = i;
break;
}
}
if (qfi == 0xFFFF_FFFF) @panic("koru gpu: no compute queue");
const prio: f32 = 1.0;
const qci = zi(vk.VkDeviceQueueCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = qfi,
.queueCount = 1,
.pQueuePriorities = &prio,
});
const dev_exts = [_][*:0]const u8{"VK_KHR_portability_subset"};
var dev: vk.VkDevice = undefined;
check(vk.vkCreateDevice(pd, &zi(vk.VkDeviceCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &qci,
.enabledExtensionCount = 1,
.ppEnabledExtensionNames = &dev_exts,
}), null, &dev), "vkCreateDevice");
var queue: vk.VkQueue = undefined;
vk.vkGetDeviceQueue(dev, qfi, 0, &queue);
// storage buffer, host-visible
var vbuf: vk.VkBuffer = undefined;
check(vk.vkCreateBuffer(dev, &zi(vk.VkBufferCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = bytes,
.usage = vk.VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = vk.VK_SHARING_MODE_EXCLUSIVE,
}), null, &vbuf), "vkCreateBuffer");
var mr: vk.VkMemoryRequirements = undefined;
vk.vkGetBufferMemoryRequirements(dev, vbuf, &mr);
var mp: vk.VkPhysicalDeviceMemoryProperties = undefined;
vk.vkGetPhysicalDeviceMemoryProperties(pd, &mp);
const want = vk.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | vk.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
var mti: u32 = 0xFFFF_FFFF;
var m: u32 = 0;
while (m < mp.memoryTypeCount) : (m += 1) {
if ((mr.memoryTypeBits & (@as(u32, 1) << @intCast(m))) != 0 and
(mp.memoryTypes[m].propertyFlags & want) == want)
{
mti = m;
break;
}
}
if (mti == 0xFFFF_FFFF) @panic("koru gpu: no host-visible memory");
var mem: vk.VkDeviceMemory = undefined;
check(vk.vkAllocateMemory(dev, &zi(vk.VkMemoryAllocateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = mr.size,
.memoryTypeIndex = mti,
}), null, &mem), "vkAllocateMemory");
check(vk.vkBindBufferMemory(dev, vbuf, mem, 0), "vkBindBufferMemory");
// upload host buffer -> device
var mapped: ?*anyopaque = null;
check(vk.vkMapMemory(dev, mem, 0, bytes, 0, &mapped), "vkMapMemory in");
{
const dst: [*]f32 = @ptrCast(@alignCast(mapped.?));
@memcpy(dst[0..n], buf[0..n]);
}
vk.vkUnmapMemory(dev, mem);
// shader module from the compiler's blob
var sm: vk.VkShaderModule = undefined;
check(vk.vkCreateShaderModule(dev, &zi(vk.VkShaderModuleCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = spv.len,
.pCode = @as([*c]const u32, @ptrCast(@alignCast(spv.ptr))),
}), null, &sm), "vkCreateShaderModule");
// descriptor set layout: binding 0 storage buffer, compute
const dslb = zi(vk.VkDescriptorSetLayoutBinding, .{
.binding = 0,
.descriptorType = vk.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = vk.VK_SHADER_STAGE_COMPUTE_BIT,
});
var dsl: vk.VkDescriptorSetLayout = undefined;
check(vk.vkCreateDescriptorSetLayout(dev, &zi(vk.VkDescriptorSetLayoutCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &dslb,
}), null, &dsl), "vkCreateDescriptorSetLayout");
var pl: vk.VkPipelineLayout = undefined;
check(vk.vkCreatePipelineLayout(dev, &zi(vk.VkPipelineLayoutCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &dsl,
}), null, &pl), "vkCreatePipelineLayout");
// compute pipeline
var pipe: vk.VkPipeline = undefined;
const cpci = zi(vk.VkComputePipelineCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.stage = zi(vk.VkPipelineShaderStageCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = vk.VK_SHADER_STAGE_COMPUTE_BIT,
.module = sm,
.pName = entry,
}),
.layout = pl,
});
check(vk.vkCreateComputePipelines(dev, null, 1, &cpci, null, &pipe), "vkCreateComputePipelines");
// descriptor pool + set
const dps = zi(vk.VkDescriptorPoolSize, .{ .type = vk.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .descriptorCount = 1 });
var dp: vk.VkDescriptorPool = undefined;
check(vk.vkCreateDescriptorPool(dev, &zi(vk.VkDescriptorPoolCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.maxSets = 1,
.poolSizeCount = 1,
.pPoolSizes = &dps,
}), null, &dp), "vkCreateDescriptorPool");
var ds: vk.VkDescriptorSet = undefined;
check(vk.vkAllocateDescriptorSets(dev, &zi(vk.VkDescriptorSetAllocateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = dp,
.descriptorSetCount = 1,
.pSetLayouts = &dsl,
}), &ds), "vkAllocateDescriptorSets");
const dbi = zi(vk.VkDescriptorBufferInfo, .{ .buffer = vbuf, .offset = 0, .range = bytes });
const wds = zi(vk.VkWriteDescriptorSet, .{
.sType = vk.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = ds,
.dstBinding = 0,
.descriptorCount = 1,
.descriptorType = vk.VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.pBufferInfo = &dbi,
});
vk.vkUpdateDescriptorSets(dev, 1, &wds, 0, null);
// record + dispatch (n,1,1)
var cp: vk.VkCommandPool = undefined;
check(vk.vkCreateCommandPool(dev, &zi(vk.VkCommandPoolCreateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.queueFamilyIndex = qfi,
}), null, &cp), "vkCreateCommandPool");
var cb: vk.VkCommandBuffer = undefined;
check(vk.vkAllocateCommandBuffers(dev, &zi(vk.VkCommandBufferAllocateInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = cp,
.level = vk.VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
}), &cb), "vkAllocateCommandBuffers");
check(vk.vkBeginCommandBuffer(cb, &zi(vk.VkCommandBufferBeginInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = vk.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
})), "vkBeginCommandBuffer");
vk.vkCmdBindPipeline(cb, vk.VK_PIPELINE_BIND_POINT_COMPUTE, pipe);
vk.vkCmdBindDescriptorSets(cb, vk.VK_PIPELINE_BIND_POINT_COMPUTE, pl, 0, 1, &ds, 0, null);
vk.vkCmdDispatch(cb, n, 1, 1);
check(vk.vkEndCommandBuffer(cb), "vkEndCommandBuffer");
const si = zi(vk.VkSubmitInfo, .{
.sType = vk.VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &cb,
});
check(vk.vkQueueSubmit(queue, 1, &si, null), "vkQueueSubmit");
check(vk.vkQueueWaitIdle(queue), "vkQueueWaitIdle");
// readback device -> host buffer
check(vk.vkMapMemory(dev, mem, 0, bytes, 0, &mapped), "vkMapMemory out");
{
const src: [*]f32 = @ptrCast(@alignCast(mapped.?));
@memcpy(buf[0..n], src[0..n]);
}
vk.vkUnmapMemory(dev, mem);
// teardown (skeleton: destroy the big handles; process exit reclaims the rest)
vk.vkDestroyPipeline(dev, pipe, null);
vk.vkDestroyShaderModule(dev, sm, null);
vk.vkDestroyDevice(dev, null);
vk.vkDestroyInstance(inst, null);
}
fn check(r: vk.VkResult, comptime what: []const u8) void {
if (r != vk.VK_SUCCESS) {
std.debug.print("koru gpu: {s} failed = {d}\n", .{ what, r });
@panic("koru gpu dispatch failed");
}
}Actual
mass[0]=101 mass[1]=102 mass[2]=103
Expected output
mass[0]=101 mass[1]=102 mass[2]=103
Flows
flow ~shape click a branch to expand · @labels scroll to their anchor
shape (expr: Body, source: mass: f32,)
flow ~init click a branch to expand · @labels scroll to their anchor
init (expr: Body, source: { mass: 1.0 },
{ mass: 2.0 },
{ mass: 3.0 },)
Test Configuration
MUST_RUN