Merge pull request #8757 from Snektron/spirv-stuff

SPIR-V: Features + some other stuff
This commit is contained in:
Andrew Kelley 2021-05-14 17:02:51 -04:00 committed by GitHub
commit 2dcec42b74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2807 additions and 238 deletions

View File

@ -431,6 +431,7 @@ pub const Target = struct {
pub const powerpc = @import("target/powerpc.zig");
pub const riscv = @import("target/riscv.zig");
pub const sparc = @import("target/sparc.zig");
pub const spirv = @import("target/spirv.zig");
pub const systemz = @import("target/systemz.zig");
pub const ve = @import("target/ve.zig");
pub const wasm = @import("target/wasm.zig");
@ -594,7 +595,7 @@ pub const Target = struct {
pub const Set = struct {
ints: [usize_count]usize,
pub const needed_bit_count = 172;
pub const needed_bit_count = 288;
pub const byte_count = (needed_bit_count + 7) / 8;
pub const usize_count = (byte_count + (@sizeOf(usize) - 1)) / @sizeOf(usize);
pub const Index = std.math.Log2Int(std.meta.Int(.unsigned, usize_count * @bitSizeOf(usize)));
@ -822,6 +823,13 @@ pub const Target = struct {
};
}
pub fn isSPIRV(arch: Arch) bool {
return switch (arch) {
.spirv32, .spirv64 => true,
else => false,
};
}
pub fn parseCpuModel(arch: Arch, cpu_name: []const u8) !*const Cpu.Model {
for (arch.allCpuModels()) |cpu| {
if (mem.eql(u8, cpu_name, cpu.name)) {
@ -1116,6 +1124,7 @@ pub const Target = struct {
.amdgcn => &amdgpu.all_features,
.riscv32, .riscv64 => &riscv.all_features,
.sparc, .sparcv9, .sparcel => &sparc.all_features,
.spirv32, .spirv64 => &spirv.all_features,
.s390x => &systemz.all_features,
.i386, .x86_64 => &x86.all_features,
.nvptx, .nvptx64 => &nvptx.all_features,
@ -1324,6 +1333,9 @@ pub const Target = struct {
if (cpu_arch.isWasm()) {
return .wasm;
}
if (cpu_arch.isSPIRV()) {
return .spirv;
}
return .elf;
}

2135
lib/std/target/spirv.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,13 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.codegen);
const spec = @import("spirv/spec.zig");
const Module = @import("../Module.zig");
const Decl = Module.Decl;
const Type = @import("../type.zig").Type;
pub const TypeMap = std.HashMap(Type, u32, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []const u32) !void {
const word_count = @intCast(u32, args.len + 1);
@ -12,38 +16,91 @@ pub fn writeInstruction(code: *std.ArrayList(u32), instr: spec.Opcode, args: []c
}
pub const SPIRVModule = struct {
next_id: u32 = 0,
free_id_list: std.ArrayList(u32),
next_result_id: u32 = 0,
pub fn init(allocator: *Allocator) SPIRVModule {
target: std.Target,
types: TypeMap,
types_and_globals: std.ArrayList(u32),
fn_decls: std.ArrayList(u32),
pub fn init(target: std.Target, allocator: *Allocator) SPIRVModule {
return .{
.free_id_list = std.ArrayList(u32).init(allocator),
.target = target,
.types = TypeMap.init(allocator),
.types_and_globals = std.ArrayList(u32).init(allocator),
.fn_decls = std.ArrayList(u32).init(allocator),
};
}
pub fn deinit(self: *SPIRVModule) void {
self.free_id_list.deinit();
self.fn_decls.deinit();
self.types_and_globals.deinit();
self.types.deinit();
self.* = undefined;
}
pub fn allocId(self: *SPIRVModule) u32 {
if (self.free_id_list.popOrNull()) |id| return id;
defer self.next_id += 1;
return self.next_id;
pub fn allocResultId(self: *SPIRVModule) u32 {
defer self.next_result_id += 1;
return self.next_result_id;
}
pub fn freeId(self: *SPIRVModule, id: u32) void {
if (id + 1 == self.next_id) {
self.next_id -= 1;
} else {
// If no more memory to append the id to the free list, just ignore it.
self.free_id_list.append(id) catch {};
pub fn resultIdBound(self: *SPIRVModule) u32 {
return self.next_result_id;
}
pub fn getOrGenType(self: *SPIRVModule, t: Type) !u32 {
// We can't use getOrPut here so we can recursively generate types.
if (self.types.get(t)) |already_generated| {
return already_generated;
}
const result = self.allocResultId();
switch (t.zigTypeTag()) {
.Void => try writeInstruction(&self.types_and_globals, .OpTypeVoid, &[_]u32{ result }),
.Bool => try writeInstruction(&self.types_and_globals, .OpTypeBool, &[_]u32{ result }),
.Int => {
const int_info = t.intInfo(self.target);
try writeInstruction(&self.types_and_globals, .OpTypeInt, &[_]u32{
result,
int_info.bits,
switch (int_info.signedness) {
.unsigned => 0,
.signed => 1,
},
});
},
// TODO: Verify that floatBits() will be correct.
.Float => try writeInstruction(&self.types_and_globals, .OpTypeFloat, &[_]u32{ result, t.floatBits(self.target) }),
.Null,
.Undefined,
.EnumLiteral,
.ComptimeFloat,
.ComptimeInt,
.Type,
=> unreachable, // Must be const or comptime.
.BoundFn => unreachable, // this type will be deleted from the language.
else => return error.TODO,
}
try self.types.put(t, result);
return result;
}
pub fn gen(self: *SPIRVModule, decl: *Decl) !void {
const typed_value = decl.typed_value.most_recent.typed_value;
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
log.debug("Generating code for function '{s}'", .{ std.mem.spanZ(decl.name) });
_ = try self.getOrGenType(typed_value.ty.fnReturnType());
},
else => return error.TODO,
}
}
pub fn idBound(self: *SPIRVModule) u32 {
return self.next_id;
}
pub fn genDecl(self: SPIRVModule, id: u32, code: *std.ArrayList(u32), decl: *Decl) !void {}
};

View File

@ -1,26 +1,5 @@
// Copyright (c) 2014-2020 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and/or associated documentation files (the "Materials"),
// to deal in the Materials without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Materials, and to permit persons to whom the
// Materials are furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS
// STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND
// HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS
// IN THE MATERIALS.
//! This file is auto-generated by tools/gen_spirv_spec.zig.
const Version = @import("builtin").Version;
pub const version = Version{ .major = 1, .minor = 5, .patch = 4 };
pub const magic_number: u32 = 0x07230203;
@ -443,8 +422,15 @@ pub const Opcode = extern enum(u16) {
OpUSubSatINTEL = 5596,
OpIMul32x16INTEL = 5597,
OpUMul32x16INTEL = 5598,
OpFunctionPointerINTEL = 5600,
OpConstFunctionPointerINTEL = 5600,
OpFunctionPointerCallINTEL = 5601,
OpAsmTargetINTEL = 5609,
OpAsmINTEL = 5610,
OpAsmCallINTEL = 5611,
OpAtomicFMinEXT = 5614,
OpAtomicFMaxEXT = 5615,
OpAssumeTrueKHR = 5630,
OpExpectKHR = 5631,
OpDecorateString = 5632,
OpDecorateStringGOOGLE = 5632,
OpMemberDecorateString = 5633,
@ -567,7 +553,12 @@ pub const Opcode = extern enum(u16) {
OpSubgroupAvcSicGetPackedSkcLumaCountThresholdINTEL = 5814,
OpSubgroupAvcSicGetPackedSkcLumaSumThresholdINTEL = 5815,
OpSubgroupAvcSicGetInterRawSadsINTEL = 5816,
OpVariableLengthArrayINTEL = 5818,
OpSaveMemoryINTEL = 5819,
OpRestoreMemoryINTEL = 5820,
OpLoopControlINTEL = 5887,
OpPtrCastToCrossWorkgroupINTEL = 5934,
OpCrossWorkgroupCastToPtrINTEL = 5938,
OpReadPipeBlockingINTEL = 5946,
OpWritePipeBlockingINTEL = 5947,
OpFPGARegINTEL = 5949,
@ -589,6 +580,10 @@ pub const Opcode = extern enum(u16) {
OpRayQueryGetIntersectionObjectToWorldKHR = 6031,
OpRayQueryGetIntersectionWorldToObjectKHR = 6032,
OpAtomicFAddEXT = 6035,
OpTypeBufferSurfaceINTEL = 6086,
OpTypeStructContinuedINTEL = 6090,
OpConstantCompositeContinuedINTEL = 6091,
OpSpecConstantCompositeContinuedINTEL = 6092,
_,
};
pub const ImageOperands = packed struct {
@ -642,8 +637,8 @@ pub const FPFastMathMode = packed struct {
_reserved_bit_13: bool = false,
_reserved_bit_14: bool = false,
_reserved_bit_15: bool = false,
_reserved_bit_16: bool = false,
_reserved_bit_17: bool = false,
AllowContractFastINTEL: bool = false,
AllowReassocINTEL: bool = false,
_reserved_bit_18: bool = false,
_reserved_bit_19: bool = false,
_reserved_bit_20: bool = false,
@ -717,7 +712,7 @@ pub const LoopControl = packed struct {
LoopCoalesceINTEL: bool = false,
MaxInterleavingINTEL: bool = false,
SpeculatedIterationsINTEL: bool = false,
_reserved_bit_23: bool = false,
NoFusionINTEL: bool = false,
_reserved_bit_24: bool = false,
_reserved_bit_25: bool = false,
_reserved_bit_26: bool = false,
@ -1037,10 +1032,16 @@ pub const ExecutionMode = extern enum(u32) {
SampleInterlockUnorderedEXT = 5369,
ShadingRateInterlockOrderedEXT = 5370,
ShadingRateInterlockUnorderedEXT = 5371,
SharedLocalMemorySizeINTEL = 5618,
RoundingModeRTPINTEL = 5620,
RoundingModeRTNINTEL = 5621,
FloatingPointModeALTINTEL = 5622,
FloatingPointModeIEEEINTEL = 5623,
MaxWorkgroupSizeINTEL = 5893,
MaxWorkDimINTEL = 5894,
NoGlobalOffsetINTEL = 5895,
NumSIMDWorkitemsINTEL = 5896,
SchedulerTargetFmaxMhzINTEL = 5903,
_,
};
pub const StorageClass = extern enum(u32) {
@ -1072,6 +1073,8 @@ pub const StorageClass = extern enum(u32) {
PhysicalStorageBuffer = 5349,
PhysicalStorageBufferEXT = 5349,
CodeSectionINTEL = 5605,
DeviceOnlyINTEL = 5936,
HostOnlyINTEL = 5937,
_,
};
pub const Dim = extern enum(u32) {
@ -1192,9 +1195,20 @@ pub const FPRoundingMode = extern enum(u32) {
RTN = 3,
_,
};
pub const FPDenormMode = extern enum(u32) {
Preserve = 0,
FlushToZero = 1,
_,
};
pub const FPOperationMode = extern enum(u32) {
IEEE = 0,
ALT = 1,
_,
};
pub const LinkageType = extern enum(u32) {
Export = 0,
Import = 1,
LinkOnceODR = 2,
_,
};
pub const AccessQualifier = extern enum(u32) {
@ -1279,12 +1293,22 @@ pub const Decoration = extern enum(u32) {
RestrictPointerEXT = 5355,
AliasedPointer = 5356,
AliasedPointerEXT = 5356,
SIMTCallINTEL = 5599,
ReferencedIndirectlyINTEL = 5602,
ClobberINTEL = 5607,
SideEffectsINTEL = 5608,
VectorComputeVariableINTEL = 5624,
FuncParamIOKindINTEL = 5625,
VectorComputeFunctionINTEL = 5626,
StackCallINTEL = 5627,
GlobalVariableOffsetINTEL = 5628,
CounterBuffer = 5634,
HlslCounterBufferGOOGLE = 5634,
UserSemantic = 5635,
HlslSemanticGOOGLE = 5635,
UserTypeGOOGLE = 5636,
FunctionRoundingModeINTEL = 5822,
FunctionDenormModeINTEL = 5823,
RegisterINTEL = 5825,
MemoryINTEL = 5826,
NumbanksINTEL = 5827,
@ -1297,6 +1321,17 @@ pub const Decoration = extern enum(u32) {
MergeINTEL = 5834,
BankBitsINTEL = 5835,
ForcePow2DepthINTEL = 5836,
BurstCoalesceINTEL = 5899,
CacheSizeINTEL = 5900,
DontStaticallyCoalesceINTEL = 5901,
PrefetchINTEL = 5902,
StallEnableINTEL = 5905,
FuseLoopsInFunctionINTEL = 5907,
BufferLocationINTEL = 5921,
IOPipeStorageINTEL = 5944,
FunctionFloatingPointModeINTEL = 6080,
SingleElementVectorINTEL = 6085,
VectorComputeCallableFunctionINTEL = 6087,
_,
};
pub const BuiltIn = extern enum(u32) {
@ -1342,14 +1377,14 @@ pub const BuiltIn = extern enum(u32) {
VertexIndex = 42,
InstanceIndex = 43,
SubgroupEqMask = 4416,
SubgroupGeMask = 4417,
SubgroupGtMask = 4418,
SubgroupLeMask = 4419,
SubgroupLtMask = 4420,
SubgroupEqMaskKHR = 4416,
SubgroupGeMask = 4417,
SubgroupGeMaskKHR = 4417,
SubgroupGtMask = 4418,
SubgroupGtMaskKHR = 4418,
SubgroupLeMask = 4419,
SubgroupLeMaskKHR = 4419,
SubgroupLtMask = 4420,
SubgroupLtMaskKHR = 4420,
BaseVertex = 4424,
BaseInstance = 4425,
@ -1520,6 +1555,9 @@ pub const Capability = extern enum(u32) {
FragmentShadingRateKHR = 4422,
SubgroupBallotKHR = 4423,
DrawParameters = 4427,
WorkgroupMemoryExplicitLayoutKHR = 4428,
WorkgroupMemoryExplicitLayout8BitAccessKHR = 4429,
WorkgroupMemoryExplicitLayout16BitAccessKHR = 4430,
SubgroupVoteKHR = 4431,
StorageBuffer16BitAccess = 4433,
StorageUniformBufferBlock16 = 4433,
@ -1610,21 +1648,41 @@ pub const Capability = extern enum(u32) {
SubgroupBufferBlockIOINTEL = 5569,
SubgroupImageBlockIOINTEL = 5570,
SubgroupImageMediaBlockIOINTEL = 5579,
RoundToInfinityINTEL = 5582,
FloatingPointModeINTEL = 5583,
IntegerFunctions2INTEL = 5584,
FunctionPointersINTEL = 5603,
IndirectReferencesINTEL = 5604,
AsmINTEL = 5606,
AtomicFloat32MinMaxEXT = 5612,
AtomicFloat64MinMaxEXT = 5613,
AtomicFloat16MinMaxEXT = 5616,
VectorComputeINTEL = 5617,
VectorAnyINTEL = 5619,
ExpectAssumeKHR = 5629,
SubgroupAvcMotionEstimationINTEL = 5696,
SubgroupAvcMotionEstimationIntraINTEL = 5697,
SubgroupAvcMotionEstimationChromaINTEL = 5698,
VariableLengthArrayINTEL = 5817,
FunctionFloatControlINTEL = 5821,
FPGAMemoryAttributesINTEL = 5824,
FPFastMathModeINTEL = 5837,
ArbitraryPrecisionIntegersINTEL = 5844,
UnstructuredLoopControlsINTEL = 5886,
FPGALoopControlsINTEL = 5888,
KernelAttributesINTEL = 5892,
FPGAKernelAttributesINTEL = 5897,
FPGAMemoryAccessesINTEL = 5898,
FPGAClusterAttributesINTEL = 5904,
LoopFuseINTEL = 5906,
FPGABufferLocationINTEL = 5920,
USMStorageClassesINTEL = 5935,
IOPipesINTEL = 5943,
BlockingPipesINTEL = 5945,
FPGARegINTEL = 5948,
AtomicFloat32AddEXT = 6033,
AtomicFloat64AddEXT = 6034,
LongConstantCompositeINTEL = 6089,
_,
};
pub const RayQueryIntersection = extern enum(u32) {

View File

@ -16,11 +16,16 @@
//! All function declarations without a body (extern functions presumably).
//! All regular functions.
// Because SPIR-V requires re-compilation anyway, and so hot swapping will not work
// anyway, we simply generate all the code in flushModule. This keeps
// things considerably simpler.
const SpirV = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.link);
const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
@ -30,16 +35,15 @@ const trace = @import("../tracy.zig").trace;
const build_options = @import("build_options");
const spec = @import("../codegen/spirv/spec.zig");
// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
pub const FnData = struct {
id: ?u32 = null,
code: std.ArrayListUnmanaged(u32) = .{},
// We're going to fill these in flushModule, and we're going to fill them unconditionally,
// so just set it to undefined.
id: u32 = undefined
};
base: link.File,
// TODO: Does this file need to support multiple independent modules?
spirv_module: codegen.SPIRVModule,
pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
const spirv = try gpa.create(SpirV);
spirv.* = .{
@ -49,7 +53,6 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
.file = null,
.allocator = gpa,
},
.spirv_module = codegen.SPIRVModule.init(gpa),
};
// TODO: Figure out where to put all of these
@ -87,28 +90,9 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
return spirv;
}
pub fn deinit(self: *SpirV) void {
self.spirv_module.deinit();
}
pub fn deinit(self: *SpirV) void {}
pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {
const tracy = trace(@src());
defer tracy.end();
const fn_data = &decl.fn_link.spirv;
if (fn_data.id == null) {
fn_data.id = self.spirv_module.allocId();
}
var managed_code = fn_data.code.toManaged(self.base.allocator);
managed_code.items.len = 0;
try self.spirv_module.genDecl(fn_data.id.?, &managed_code, decl);
fn_data.code = managed_code.toUnmanaged();
// Free excess allocated memory for this Decl.
fn_data.code.shrinkAndFree(self.base.allocator, fn_data.code.items.len);
}
pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {}
pub fn updateDeclExports(
self: *SpirV,
@ -117,12 +101,7 @@ pub fn updateDeclExports(
exports: []const *Module.Export,
) !void {}
pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {
var fn_data = decl.fn_link.spirv;
fn_data.code.deinit(self.base.allocator);
if (fn_data.id) |id| self.spirv_module.freeId(id);
decl.fn_link.spirv = undefined;
}
pub fn freeDecl(self: *SpirV, decl: *Module.Decl) void {}
pub fn flush(self: *SpirV, comp: *Compilation) !void {
if (build_options.have_llvm and self.base.options.use_lld) {
@ -139,55 +118,69 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
const module = self.base.options.module.?;
const target = comp.getTarget();
var spirv_module = codegen.SPIRVModule.init(target, self.base.allocator);
defer spirv_module.deinit();
// Allocate an ID for every declaration before generating code,
// so that we can access them before processing them.
// TODO: We're allocating an ID unconditionally now, are there
// declarations which don't generate a result?
// TODO: fn_link is used here, but thats probably not the right field. It will work anyway though.
{
for (module.decl_table.items()) |entry| {
const decl = entry.value;
if (decl.typed_value != .most_recent)
continue;
decl.fn_link.spirv.id = spirv_module.allocResultId();
log.debug("Allocating id {} to '{s}'", .{ decl.fn_link.spirv.id, std.mem.spanZ(decl.name) });
}
}
// Now, actually generate the code for all declarations.
{
for (module.decl_table.items()) |entry| {
const decl = entry.value;
if (decl.typed_value != .most_recent)
continue;
try spirv_module.gen(decl);
}
}
var binary = std.ArrayList(u32).init(self.base.allocator);
defer binary.deinit();
// Note: The order of adding sections to the final binary
// follows the SPIR-V logical module format!
try binary.appendSlice(&[_]u32{
spec.magic_number,
(spec.version.major << 16) | (spec.version.minor << 8),
0, // TODO: Register Zig compiler magic number.
self.spirv_module.idBound(),
spirv_module.resultIdBound(), // ID bound.
0, // Schema (currently reserved for future use in the SPIR-V spec).
});
try writeCapabilities(&binary, target);
try writeMemoryModel(&binary, target);
// Collect list of buffers to write.
// SPIR-V files support both little and big endian words. The actual format is
// disambiguated by the magic number, and so theoretically we don't need to worry
// about endian-ness when writing the final binary.
var all_buffers = std.ArrayList(std.os.iovec_const).init(self.base.allocator);
defer all_buffers.deinit();
// Note: The order of adding sections to the final binary
// follows the SPIR-V logical module format!
var all_buffers = [_]std.os.iovec_const{
wordsToIovConst(binary.items),
wordsToIovConst(spirv_module.types_and_globals.items),
wordsToIovConst(spirv_module.fn_decls.items),
};
// Pre-allocate enough for the binary info + all functions
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
all_buffers.appendAssumeCapacity(wordsToIovConst(binary.items));
for (module.decl_table.items()) |entry| {
const decl = entry.value;
switch (decl.typed_value) {
.most_recent => |tvm| {
const fn_data = &decl.fn_link.spirv;
all_buffers.appendAssumeCapacity(wordsToIovConst(fn_data.code.items));
},
.never_succeeded => continue,
}
}
const file = self.base.file.?;
const bytes = std.mem.sliceAsBytes(binary.items);
var file_size: u64 = 0;
for (all_buffers.items) |iov| {
for (all_buffers) |iov| {
file_size += iov.iov_len;
}
const file = self.base.file.?;
try file.seekTo(0);
try file.setEndPos(file_size);
try file.pwritevAll(all_buffers.items, 0);
try file.pwritevAll(&all_buffers, 0);
}
fn writeCapabilities(binary: *std.ArrayList(u32), target: std.Target) !void {
@ -231,4 +224,4 @@ fn wordsToIovConst(words: []const u32) std.os.iovec_const {
.iov_base = bytes.ptr,
.iov_len = bytes.len,
};
}
}

View File

@ -1,96 +1,5 @@
const std = @import("std");
const Writer = std.ArrayList(u8).Writer;
//! See https://www.khronos.org/registry/spir-v/specs/unified1/MachineReadableGrammar.html
//! and the files in https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/
//! Note: Non-canonical casing in these structs used to match SPIR-V spec json.
const Registry = union(enum) {
core: CoreRegistry,
extension: ExtensionRegistry,
};
const CoreRegistry = struct {
copyright: [][]const u8,
/// Hexadecimal representation of the magic number
magic_number: []const u8,
major_version: u32,
minor_version: u32,
revision: u32,
instruction_printing_class: []InstructionPrintingClass,
instructions: []Instruction,
operand_kinds: []OperandKind,
};
const ExtensionRegistry = struct {
copyright: [][]const u8,
version: u32,
revision: u32,
instructions: []Instruction,
operand_kinds: []OperandKind = &[_]OperandKind{},
};
const InstructionPrintingClass = struct {
tag: []const u8,
heading: ?[]const u8 = null,
};
const Instruction = struct {
opname: []const u8,
class: ?[]const u8 = null, // Note: Only available in the core registry.
opcode: u32,
operands: []Operand = &[_]Operand{},
capabilities: [][]const u8 = &[_][]const u8{},
extensions: [][]const u8 = &[_][]const u8{},
version: ?[]const u8 = null,
lastVersion: ?[]const u8 = null,
};
const Operand = struct {
kind: []const u8,
/// If this field is 'null', the operand is only expected once.
quantifier: ?Quantifier = null,
name: []const u8 = "",
};
const Quantifier = enum {
/// zero or once
@"?",
/// zero or more
@"*",
};
const OperandCategory = enum {
BitEnum,
ValueEnum,
Id,
Literal,
Composite,
};
const OperandKind = struct {
category: OperandCategory,
/// The name
kind: []const u8,
doc: ?[]const u8 = null,
enumerants: ?[]Enumerant = null,
bases: ?[]const []const u8 = null,
};
const Enumerant = struct {
enumerant: []const u8,
value: union(enum) {
bitflag: []const u8, // Hexadecimal representation of the value
int: u31,
},
capabilities: [][]const u8 = &[_][]const u8{},
/// Valid for .ValueEnum and .BitEnum
extensions: [][]const u8 = &[_][]const u8{},
/// `quantifier` will always be `null`.
parameters: []Operand = &[_]Operand{},
version: ?[]const u8 = null,
lastVersion: ?[]const u8 = null,
};
const g = @import("spirv/grammar.zig");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@ -106,24 +15,25 @@ pub fn main() !void {
const spec = try std.fs.cwd().readFileAlloc(allocator, spec_path, std.math.maxInt(usize));
var tokens = std.json.TokenStream.init(spec);
var registry = try std.json.parse(Registry, &tokens, .{.allocator = allocator});
var registry = try std.json.parse(g.Registry, &tokens, .{.allocator = allocator});
var buf = std.ArrayList(u8).init(allocator);
defer buf.deinit();
try render(buf.writer(), registry);
const tree = try std.zig.parse(allocator, buf.items);
_ = try std.zig.render(allocator, std.io.getStdOut().writer(), tree);
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
try render(bw.writer(), registry);
try bw.flush();
}
fn render(writer: Writer, registry: Registry) !void {
fn render(writer: anytype, registry: g.Registry) !void {
try writer.writeAll(
\\//! This file is auto-generated by tools/gen_spirv_spec.zig.
\\
\\const Version = @import("builtin").Version;
\\
);
switch (registry) {
.core => |core_reg| {
try renderCopyRight(writer, core_reg.copyright);
try writer.print(
\\const Version = @import("builtin").Version;
\\pub const version = Version{{.major = {}, .minor = {}, .patch = {}}};
\\pub const version = Version{{ .major = {}, .minor = {}, .patch = {} }};
\\pub const magic_number: u32 = {s};
\\
, .{ core_reg.major_version, core_reg.minor_version, core_reg.revision, core_reg.magic_number },
@ -132,10 +42,8 @@ fn render(writer: Writer, registry: Registry) !void {
try renderOperandKinds(writer, core_reg.operand_kinds);
},
.extension => |ext_reg| {
try renderCopyRight(writer, ext_reg.copyright);
try writer.print(
\\const Version = @import("builtin").Version;
\\pub const version = Version{{.major = {}, .minor = 0, .patch = {}}};
\\pub const version = Version{{ .major = {}, .minor = 0, .patch = {} }};
\\
, .{ ext_reg.version, ext_reg.revision },
);
@ -145,21 +53,15 @@ fn render(writer: Writer, registry: Registry) !void {
}
}
fn renderCopyRight(writer: Writer, copyright: []const []const u8) !void {
for (copyright) |line| {
try writer.print("// {s}\n", .{ line });
}
}
fn renderOpcodes(writer: Writer, instructions: []const Instruction) !void {
fn renderOpcodes(writer: anytype, instructions: []const g.Instruction) !void {
try writer.writeAll("pub const Opcode = extern enum(u16) {\n");
for (instructions) |instr| {
try writer.print("{} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode });
try writer.print(" {} = {},\n", .{ std.zig.fmtId(instr.opname), instr.opcode });
}
try writer.writeAll("_,\n};\n");
try writer.writeAll(" _,\n};\n");
}
fn renderOperandKinds(writer: Writer, kinds: []const OperandKind) !void {
fn renderOperandKinds(writer: anytype, kinds: []const g.OperandKind) !void {
for (kinds) |kind| {
switch (kind.category) {
.ValueEnum => try renderValueEnum(writer, kind),
@ -169,20 +71,20 @@ fn renderOperandKinds(writer: Writer, kinds: []const OperandKind) !void {
}
}
fn renderValueEnum(writer: Writer, enumeration: OperandKind) !void {
fn renderValueEnum(writer: anytype, enumeration: g.OperandKind) !void {
try writer.print("pub const {s} = extern enum(u32) {{\n", .{ enumeration.kind });
const enumerants = enumeration.enumerants orelse return error.InvalidRegistry;
for (enumerants) |enumerant| {
if (enumerant.value != .int) return error.InvalidRegistry;
try writer.print("{} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int });
try writer.print(" {} = {},\n", .{ std.zig.fmtId(enumerant.enumerant), enumerant.value.int });
}
try writer.writeAll("_,\n};\n");
try writer.writeAll(" _,\n};\n");
}
fn renderBitEnum(writer: Writer, enumeration: OperandKind) !void {
fn renderBitEnum(writer: anytype, enumeration: g.OperandKind) !void {
try writer.print("pub const {s} = packed struct {{\n", .{ enumeration.kind });
var flags_by_bitpos = [_]?[]const u8{null} ** 32;
@ -205,6 +107,7 @@ fn renderBitEnum(writer: Writer, enumeration: OperandKind) !void {
}
for (flags_by_bitpos) |maybe_flag_name, bitpos| {
try writer.writeAll(" ");
if (maybe_flag_name) |flag_name| {
try writer.writeAll(flag_name);
} else {
@ -215,7 +118,7 @@ fn renderBitEnum(writer: Writer, enumeration: OperandKind) !void {
if (bitpos == 0) { // Force alignment to integer boundaries
try writer.writeAll("align(@alignOf(u32)) ");
}
try writer.writeAll("= false, ");
try writer.writeAll("= false,\n");
}
try writer.writeAll("};\n");

90
tools/spirv/grammar.zig Normal file
View File

@ -0,0 +1,90 @@
//! See https://www.khronos.org/registry/spir-v/specs/unified1/MachineReadableGrammar.html
//! and the files in https://github.com/KhronosGroup/SPIRV-Headers/blob/master/include/spirv/unified1/
//! Note: Non-canonical casing in these structs used to match SPIR-V spec json.
pub const Registry = union(enum) {
core: CoreRegistry,
extension: ExtensionRegistry,
};
pub const CoreRegistry = struct {
copyright: [][]const u8,
/// Hexadecimal representation of the magic number
magic_number: []const u8,
major_version: u32,
minor_version: u32,
revision: u32,
instruction_printing_class: []InstructionPrintingClass,
instructions: []Instruction,
operand_kinds: []OperandKind,
};
pub const ExtensionRegistry = struct {
copyright: [][]const u8,
version: u32,
revision: u32,
instructions: []Instruction,
operand_kinds: []OperandKind = &[_]OperandKind{},
};
pub const InstructionPrintingClass = struct {
tag: []const u8,
heading: ?[]const u8 = null,
};
pub const Instruction = struct {
opname: []const u8,
class: ?[]const u8 = null, // Note: Only available in the core registry.
opcode: u32,
operands: []Operand = &[_]Operand{},
capabilities: [][]const u8 = &[_][]const u8{},
extensions: [][]const u8 = &[_][]const u8{},
version: ?[]const u8 = null,
lastVersion: ?[]const u8 = null,
};
pub const Operand = struct {
kind: []const u8,
/// If this field is 'null', the operand is only expected once.
quantifier: ?Quantifier = null,
name: []const u8 = "",
};
pub const Quantifier = enum {
/// zero or once
@"?",
/// zero or more
@"*",
};
pub const OperandCategory = enum {
BitEnum,
ValueEnum,
Id,
Literal,
Composite,
};
pub const OperandKind = struct {
category: OperandCategory,
/// The name
kind: []const u8,
doc: ?[]const u8 = null,
enumerants: ?[]Enumerant = null,
bases: ?[]const []const u8 = null,
};
pub const Enumerant = struct {
enumerant: []const u8,
value: union(enum) {
bitflag: []const u8, // Hexadecimal representation of the value
int: u31,
},
capabilities: [][]const u8 = &[_][]const u8{},
/// Valid for .ValueEnum and .BitEnum
extensions: [][]const u8 = &[_][]const u8{},
/// `quantifier` will always be `null`.
parameters: []Operand = &[_]Operand{},
version: ?[]const u8 = null,
lastVersion: ?[]const u8 = null,
};

View File

@ -0,0 +1,321 @@
const std = @import("std");
const fs = std.fs;
const Allocator = std.mem.Allocator;
const g = @import("spirv/grammar.zig");
//! This tool generates SPIR-V features from the grammar files in the SPIRV-Headers
//! (https://github.com/KhronosGroup/SPIRV-Headers/) and SPIRV-Registry (https://github.com/KhronosGroup/SPIRV-Registry/)
//! repositories. Currently it only generates a basic feature set definition consisting of versions, extensions and capabilities.
//! There is a lot left to be desired, as currently dependencies of extensions and dependencies on extensions aren't generated.
//! This is because there are some peculiarities in the SPIR-V registries:
//! - Capabilities may depend on multiple extensions, which cannot be modelled yet by std.Target.
//! - Extension dependencies are not documented in a machine-readable manner.
//! - Note that the grammar spec also contains definitions from extensions which aren't actually official. Most of these seem to be
//! from an intel project (https://github.com/intel/llvm/, https://github.com/intel/llvm/tree/sycl/sycl/doc/extensions/SPIRV),
//! and so ONLY extensions in the SPIRV-Registry should be included.
const Version = struct {
major: u32,
minor: u32,
fn parse(str: []const u8) !Version {
var it = std.mem.split(str, ".");
const major = it.next() orelse return error.InvalidVersion;
const minor = it.next() orelse return error.InvalidVersion;
if (it.next() != null) return error.InvalidVersion;
return Version{
.major = std.fmt.parseInt(u32, major, 10) catch return error.InvalidVersion,
.minor = std.fmt.parseInt(u32, minor, 10) catch return error.InvalidVersion,
};
}
fn eql(a: Version, b: Version) bool {
return a.major == b.major and a.minor == b.minor;
}
fn lessThan(ctx: void, a: Version, b: Version) bool {
return if (a.major == b.major)
a.minor < b.minor
else
a.major < b.major;
}
};
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = &arena.allocator;
const args = try std.process.argsAlloc(allocator);
if (args.len <= 1) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
if (std.mem.eql(u8, args[1], "--help")) {
usageAndExit(std.io.getStdErr(), args[0], 0);
}
if (args.len != 3) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
const spirv_headers_root = args[1];
const spirv_registry_root = args[2];
if (std.mem.startsWith(u8, spirv_headers_root, "-") or std.mem.startsWith(u8, spirv_registry_root, "-")) {
usageAndExit(std.io.getStdErr(), args[0], 1);
}
const registry_path = try fs.path.join(allocator, &.{ spirv_headers_root, "include", "spirv", "unified1", "spirv.core.grammar.json" });
const registry_json = try std.fs.cwd().readFileAlloc(allocator, registry_path, std.math.maxInt(usize));
var tokens = std.json.TokenStream.init(registry_json);
const registry = try std.json.parse(g.CoreRegistry, &tokens, .{ .allocator = allocator });
const capabilities = for (registry.operand_kinds) |opkind| {
if (std.mem.eql(u8, opkind.kind, "Capability"))
break opkind.enumerants orelse return error.InvalidRegistry;
} else return error.InvalidRegistry;
const extensions = try gather_extensions(allocator, spirv_registry_root);
const versions = try gatherVersions(allocator, registry);
var bw = std.io.bufferedWriter(std.io.getStdOut().writer());
const w = bw.writer();
try w.writeAll(
\\//! This file is auto-generated by tools/update_spirv_features.zig.
\\//! TODO: Dependencies of capabilities on extensions.
\\//! TODO: Dependencies of extensions on extensions.
\\//! TODO: Dependencies of extensions on versions.
\\
\\const std = @import("../std.zig");
\\const CpuFeature = std.Target.Cpu.Feature;
\\const CpuModel = std.Target.Cpu.Model;
\\
\\pub const Feature = enum {
\\
);
for (versions) |ver| {
try w.print(" v{}_{},\n", .{ ver.major, ver.minor });
}
for (extensions) |ext| {
try w.print(" {},\n", .{ std.zig.fmtId(ext) });
}
for (capabilities) |cap| {
try w.print(" {},\n", .{ std.zig.fmtId(cap.enumerant) });
}
try w.writeAll(
\\};
\\
\\pub usingnamespace CpuFeature.feature_set_fns(Feature);
\\
\\pub const all_features = blk: {
\\ @setEvalBranchQuota(2000);
\\ const len = @typeInfo(Feature).Enum.fields.len;
\\ std.debug.assert(len <= CpuFeature.Set.needed_bit_count);
\\ var result: [len]CpuFeature = undefined;
\\
);
for (versions) |ver, i| {
try w.print(
\\ result[@enumToInt(Feature.v{0}_{1})] = .{{
\\ .llvm_name = null,
\\ .description = "SPIR-V version {0}.{1}",
\\
, .{ ver.major, ver.minor }
);
if (i == 0) {
try w.writeAll(
\\ .dependencies = featureSet(&[_]Feature{}),
\\ };
\\
);
} else {
try w.print(
\\ .dependencies = featureSet(&[_]Feature{{
\\ .v{}_{},
\\ }}),
\\ }};
\\
, .{ versions[i - 1].major, versions[i - 1].minor }
);
}
}
// TODO: Extension dependencies.
for (extensions) |ext| {
try w.print(
\\ result[@enumToInt(Feature.{s})] = .{{
\\ .llvm_name = null,
\\ .description = "SPIR-V extension {s}",
\\ .dependencies = featureSet(&[_]Feature{{}}),
\\ }};
\\
, .{
std.zig.fmtId(ext),
ext,
}
);
}
// TODO: Capability extension dependencies.
for (capabilities) |cap| {
try w.print(
\\ result[@enumToInt(Feature.{s})] = .{{
\\ .llvm_name = null,
\\ .description = "Enable SPIR-V capability {s}",
\\ .dependencies = featureSet(&[_]Feature{{
\\
, .{
std.zig.fmtId(cap.enumerant),
cap.enumerant,
}
);
if (cap.version) |ver_str| {
if (!std.mem.eql(u8, ver_str, "None")) {
const ver = try Version.parse(ver_str);
try w.print(" .v{}_{},\n", .{ ver.major, ver.minor });
}
}
for (cap.capabilities) |cap_dep| {
try w.print(" .{},\n", .{ std.zig.fmtId(cap_dep) });
}
try w.writeAll(
\\ }),
\\ };
\\
);
}
try w.writeAll(
\\ const ti = @typeInfo(Feature);
\\ for (result) |*elem, i| {
\\ elem.index = i;
\\ elem.name = ti.Enum.fields[i].name;
\\ }
\\ break :blk result;
\\};
\\
);
try bw.flush();
}
/// SPIRV-Registry should hold all extensions currently registered for SPIR-V.
/// The *.grammar.json in SPIRV-Headers should have most of these as well, but with this we're sure to get only the actually
/// registered ones.
/// TODO: Unfortunately, neither repository contains a machine-readable list of extension dependencies.
fn gather_extensions(allocator: *Allocator, spirv_registry_root: []const u8) ![]const []const u8 {
const extensions_path = try fs.path.join(allocator, &.{spirv_registry_root, "extensions"});
var extensions_dir = try fs.cwd().openDir(extensions_path, .{ .iterate = true });
defer extensions_dir.close();
var extensions = std.ArrayList([]const u8).init(allocator);
var vendor_it = extensions_dir.iterate();
while (try vendor_it.next()) |vendor_entry| {
std.debug.assert(vendor_entry.kind == .Directory); // If this fails, the structure of SPIRV-Registry has changed.
const vendor_dir = try extensions_dir.openDir(vendor_entry.name, .{ .iterate = true });
var ext_it = vendor_dir.iterate();
while (try ext_it.next()) |ext_entry| {
// There is both a HTML and asciidoc version of every spec (as well as some other directories),
// we need just the name, but to avoid duplicates here we will just skip anything thats not asciidoc.
if (!std.mem.endsWith(u8, ext_entry.name, ".asciidoc"))
continue;
// Unfortunately, some extension filenames are incorrect, so we need to look for the string in tne 'Name Strings' section.
// This has the following format:
// ```
// Name Strings
// ------------
//
// SPV_EXT_name
// ```
// OR
// ```
// == Name Strings
//
// SPV_EXT_name
// ```
const ext_spec = try vendor_dir.readFileAlloc(allocator, ext_entry.name, std.math.maxInt(usize));
const name_strings = "Name Strings";
const name_strings_offset = std.mem.indexOf(u8, ext_spec, name_strings) orelse return error.InvalidRegistry;
// As the specs are inconsistent on this next part, just skip any newlines/minuses
var ext_start = name_strings_offset + name_strings.len + 1;
while (ext_spec[ext_start] == '\n' or ext_spec[ext_start] == '-') {
ext_start += 1;
}
const ext_end = std.mem.indexOfScalarPos(u8, ext_spec, ext_start, '\n') orelse return error.InvalidRegistry;
const ext = ext_spec[ext_start .. ext_end];
std.debug.assert(std.mem.startsWith(u8, ext, "SPV_")); // Sanity check, all extensions should have a name like SPV_VENDOR_extension.
try extensions.append(try allocator.dupe(u8, ext));
}
}
return extensions.items;
}
fn insertVersion(versions: *std.ArrayList(Version), version: ?[]const u8) !void {
const ver_str = version orelse return;
if (std.mem.eql(u8, ver_str, "None"))
return;
const ver = try Version.parse(ver_str);
for (versions.items) |existing_ver| {
if (ver.eql(existing_ver)) return;
}
try versions.append(ver);
}
fn gatherVersions(allocator: *Allocator, registry: g.CoreRegistry) ![]const Version {
// Expected number of versions is small
var versions = std.ArrayList(Version).init(allocator);
for (registry.instructions) |inst| {
try insertVersion(&versions, inst.version);
}
for (registry.operand_kinds) |opkind| {
const enumerants = opkind.enumerants orelse continue;
for (enumerants) |enumerant| {
try insertVersion(&versions, enumerant.version);
}
}
std.sort.sort(Version, versions.items, {}, Version.lessThan);
return versions.items;
}
fn usageAndExit(file: fs.File, arg0: []const u8, code: u8) noreturn {
file.writer().print(
\\Usage: {s} /path/git/SPIRV-Headers /path/git/SPIRV-Registry
\\
\\Prints to stdout Zig code which can be used to replace the file lib/std/target/spirv.zig.
\\
\\SPIRV-Headers can be cloned from https://github.com/KhronosGroup/SPIRV-Headers,
\\SPIRV-Registry can be cloned from https://github.com/KhronosGroup/SPIRV-Registry.
\\
, .{arg0}
) catch std.process.exit(1);
std.process.exit(code);
}