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
53 changes: 53 additions & 0 deletions slang/src/main/java/io/github/refux/slang/BitfieldPacking.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.gen.BitfieldPackingRules;
import java.util.HashMap;
import java.util.Map;

/**
* How bitfield members are packed into storage ({@code slang::BitfieldPackingRules}), set with
* {@link SessionBuilder#bitfieldPacking}. {@link #value()} / {@link #of(int)} are escape hatches to
* the raw ABI values; unmapped (newer) values return {@link #UNKNOWN}.
*/
public enum BitfieldPacking {
/** Sentinel for ABI values this enum does not know (carries no raw value). */
UNKNOWN(Integer.MIN_VALUE),
/** Least-significant bit first; fields may share storage across underlying type sizes. */
DEFAULT(BitfieldPackingRules.Default),
/**
* As MSVC packs on little-endian platforms: least-significant bit first, and a change of
* underlying type size starts new storage. Rejects zero-width fields.
*/
MSVC(BitfieldPackingRules.MSVC),
/**
* What Slang's older {@code -msvc-style-bitfield-packing} flag did: most-significant bit first,
* new storage on a type-size change. Not what MSVC does; kept for existing data layouts only.
*/
LEGACY_MSB_FIRST_MSVC(BitfieldPackingRules.LegacyMSBFirstMSVC);

private static final Map<Integer, BitfieldPacking> BY_VALUE = new HashMap<>();

static {
for (BitfieldPacking rules : values()) {
if (rules != UNKNOWN) {
BY_VALUE.put(rules.value, rules);
}
}
}

private final int value;

BitfieldPacking(int value) {
this.value = value;
}

/** The raw {@code slang::BitfieldPackingRules} ABI value ({@code Integer.MIN_VALUE} for UNKNOWN). */
public int value() {
return value;
}

/** Maps a raw ABI value; unmapped (newer) values return {@link #UNKNOWN}. */
public static BitfieldPacking of(int value) {
return BY_VALUE.getOrDefault(value, UNKNOWN);
}
}
13 changes: 13 additions & 0 deletions slang/src/main/java/io/github/refux/slang/ComponentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ public byte[] entryPointCode(long entryPointIndex, long targetIndex) {
return componentHandle().getEntryPointCode(entryPointIndex, targetIndex);
}

/**
* Target code for a whole linked component — all its entry points in one artifact, such as a
* single SPIR-V module, or a WGSL or Metal library — where {@link #entryPointCode} gives one
* entry point at a time.
*
* @param targetIndex index into the session's target list, in {@link SessionBuilder#target}
* order
*/
public byte[] targetCode(long targetIndex) {
session.checkThread();
return componentHandle().getTargetCode(targetIndex);
}

/** The lazy reflection tree for {@code targetIndex} (parameters, entry points, layouts). */
public ShaderReflection layout(long targetIndex) {
session.checkThread();
Expand Down
46 changes: 46 additions & 0 deletions slang/src/main/java/io/github/refux/slang/DebugInfoLevel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.gen.SlangDebugInfoLevel;
import java.util.HashMap;
import java.util.Map;

/**
* How much debug information Slang emits ({@code SlangDebugInfoLevel}), set with
* {@link SessionBuilder#debugInfo}. {@link #value()} / {@link #of(int)} are escape hatches to the
* raw ABI values; unmapped (newer) values return {@link #UNKNOWN}.
*/
public enum DebugInfoLevel {
/** Sentinel for ABI values this enum does not know (carries no raw value). */
UNKNOWN(Integer.MIN_VALUE),
/** No debug information — Slang's default. */
NONE(SlangDebugInfoLevel.SLANG_DEBUG_INFO_LEVEL_NONE),
MINIMAL(SlangDebugInfoLevel.SLANG_DEBUG_INFO_LEVEL_MINIMAL),
STANDARD(SlangDebugInfoLevel.SLANG_DEBUG_INFO_LEVEL_STANDARD),
MAXIMAL(SlangDebugInfoLevel.SLANG_DEBUG_INFO_LEVEL_MAXIMAL);

private static final Map<Integer, DebugInfoLevel> BY_VALUE = new HashMap<>();

static {
for (DebugInfoLevel level : values()) {
if (level != UNKNOWN) {
BY_VALUE.put(level.value, level);
}
}
}

private final int value;

DebugInfoLevel(int value) {
this.value = value;
}

/** The raw {@code SlangDebugInfoLevel} ABI value ({@code Integer.MIN_VALUE} for UNKNOWN). */
public int value() {
return value;
}

/** Maps a raw ABI value; unmapped (newer) values return {@link #UNKNOWN}. */
public static DebugInfoLevel of(int value) {
return BY_VALUE.getOrDefault(value, UNKNOWN);
}
}
15 changes: 15 additions & 0 deletions slang/src/main/java/io/github/refux/slang/Enums.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.refux.slang;

/** Shared handling for the ABI-mirroring enums, whose {@code UNKNOWN} sentinel has no raw value. */
final class Enums {
private Enums() {}

/** {@code constant}'s raw ABI value, refusing the {@code UNKNOWN} sentinel, which carries none. */
static int raw(Enum<?> constant, int value) {
if (value == Integer.MIN_VALUE) {
throw new IllegalArgumentException(
constant.getDeclaringClass().getSimpleName() + "." + constant.name() + " has no raw value");
}
return value;
}
}
38 changes: 38 additions & 0 deletions slang/src/main/java/io/github/refux/slang/GlobalSession.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.IGlobalSession;
import io.github.refux.slang.ffi.SlangNative;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -54,6 +57,41 @@ public String buildTagString() {
return ffi().getBuildTagString();
}

/**
* Looks up a capability id by name (e.g. {@code "spirv_1_5"}, {@code "SPV_KHR_ray_query"}).
* {@link SessionBuilder#capability(String)} takes the name directly; this is for checking one
* up front. Returns 0 ({@code SLANG_CAPABILITY_UNKNOWN}) for unknown names.
*/
public int findCapability(String name) {
return ffi().findCapability(name);
}

/**
* Whether this Slang build can produce {@code target}: false when the target needs a downstream
* compiler that cannot be found (DXC for {@link CompileTarget#DXIL}, say).
*/
public boolean isTargetSupported(CompileTarget target) {
return SlangNative.succeeded(ffi().checkCompileTargetSupport(target.value()));
}

/** Whether the downstream compiler {@code passThrough} is available to this Slang build. */
public boolean isPassThroughSupported(PassThrough passThrough) {
return SlangNative.succeeded(ffi().checkPassThroughSupport(Enums.raw(passThrough, passThrough.value())));
}

/**
* Where the library Slang would load for the downstream compiler {@code passThrough} lives,
* found the same way compilation finds it — e.g. the {@code slang-glslang} in the natives
* payload for {@link PassThrough#GLSLANG}, or the exact NVRTC that would compile CUDA.
*
* @return the path, or empty when the compiler cannot be found or is not a shared library
* (executable-based compilers such as Clang, GCC or Metal)
*/
public Optional<Path> downstreamCompilerPath(PassThrough passThrough) {
return Optional.ofNullable(ffi().getDownstreamCompilerPath(Enums.raw(passThrough, passThrough.value())))
.map(Path::of);
}

/**
* The serialized-module version this Slang build writes — and therefore one it certainly reads.
*
Expand Down
58 changes: 56 additions & 2 deletions slang/src/main/java/io/github/refux/slang/Module.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.IModule;
import java.util.List;

/**
* A compiled Slang module. Modules are owned by their {@link Session} — this wrapper is
Expand All @@ -24,7 +25,56 @@ public final class Module extends ComponentType {
*/
public EntryPoint entryPoint(String name) {
session().checkThread();
return new EntryPoint(session(), ((IModule) componentHandle()).findEntryPointByName(name));
return new EntryPoint(session(), module().findEntryPointByName(name));
}

/**
* Finds a function by name and checks it as an entry point for {@code stage} — for functions
* without a {@code [shader("...")]} attribute (one that has it is returned as is).
*
* @throws SlangCompileException with the compiler's diagnostics when there is no such function or
* it is not a valid {@code stage} entry point
*/
public EntryPoint entryPoint(String name, Stage stage) {
session().checkThread();
return new EntryPoint(session(), module().findAndCheckEntryPoint(name, Enums.raw(stage, stage.value())));
}

/**
* The entry points Slang found when checking this module, in declaration order: functions marked
* {@code [shader("...")]}, and compute functions marked only {@code [numthreads]}. Others need
* {@link #entryPoint(String, Stage)}.
*/
public List<EntryPoint> entryPoints() {
session().checkThread();
return module().getDefinedEntryPoints().stream()
.map(entryPoint -> new EntryPoint(session(), entryPoint))
.toList();
}

/** The module's name — what an {@code import} refers to it by. */
public String name() {
session().checkThread();
return module().getName();
}

/**
* The path the module was loaded from: the source file {@link Session#loadModule} found, or for
* {@link Session#loadModuleFromSource} the synthetic {@code <name>.slang}.
*/
public String filePath() {
session().checkThread();
return module().getFilePath();
}

/**
* The files this module was compiled from — its own source, everything it {@code #include}s, and
* the sources of the modules it {@code import}s — which is what a build system or hot-reload
* watcher should watch to know when to recompile it.
*/
public List<String> dependencyFiles() {
session().checkThread();
return List.copyOf(module().getDependencyFilePaths());
}

/**
Expand All @@ -35,6 +85,10 @@ public EntryPoint entryPoint(String name) {
*/
public byte[] serialize() {
session().checkThread();
return ((IModule) componentHandle()).serialize();
return module().serialize();
}

private IModule module() {
return (IModule) componentHandle();
}
}
47 changes: 47 additions & 0 deletions slang/src/main/java/io/github/refux/slang/OptimizationLevel.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.gen.SlangOptimizationLevel;
import java.util.HashMap;
import java.util.Map;

/**
* How hard Slang optimizes generated code ({@code SlangOptimizationLevel}), set with
* {@link SessionBuilder#optimization}. {@link #value()} / {@link #of(int)} are escape hatches to the
* raw ABI values; unmapped (newer) values return {@link #UNKNOWN}.
*/
public enum OptimizationLevel {
/** Sentinel for ABI values this enum does not know (carries no raw value). */
UNKNOWN(Integer.MIN_VALUE),
NONE(SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_NONE),
/** Slang's default: balances code quality against compile time. */
DEFAULT(SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_DEFAULT),
HIGH(SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_HIGH),
/** Includes optimizations that may take a very long time or be unsafe. */
MAXIMAL(SlangOptimizationLevel.SLANG_OPTIMIZATION_LEVEL_MAXIMAL);

private static final Map<Integer, OptimizationLevel> BY_VALUE = new HashMap<>();

static {
for (OptimizationLevel level : values()) {
if (level != UNKNOWN) {
BY_VALUE.put(level.value, level);
}
}
}

private final int value;

OptimizationLevel(int value) {
this.value = value;
}

/** The raw {@code SlangOptimizationLevel} ABI value ({@code Integer.MIN_VALUE} for UNKNOWN). */
public int value() {
return value;
}

/** Maps a raw ABI value; unmapped (newer) values return {@link #UNKNOWN}. */
public static OptimizationLevel of(int value) {
return BY_VALUE.getOrDefault(value, UNKNOWN);
}
}
45 changes: 45 additions & 0 deletions slang/src/main/java/io/github/refux/slang/OptionList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.github.refux.slang;

import io.github.refux.slang.ffi.CompilerOptionEntry;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
* Compiler option entries collected by {@link SessionBuilder} and {@link TargetOptions}, kept in
* call order: Slang treats some options (capabilities, warning groups) as repeatable.
*/
final class OptionList {
private record Entry(int name, int int0, int int1, String string) {}

private final List<Entry> entries = new ArrayList<>();

void add(int name, int value0, int value1) {
entries.add(new Entry(name, value0, value1, null));
}

void add(int name, String value) {
entries.add(new Entry(name, 0, 0, Objects.requireNonNull(value, "value")));
}

int size() {
return entries.size();
}

/** Writes the entries as a native {@code CompilerOptionEntry[]} whose strings live in {@code arena}. */
MemorySegment write(Arena arena) {
MemorySegment array = CompilerOptionEntry.allocateArray(arena, entries.size());
for (int i = 0; i < entries.size(); i++) {
Entry entry = entries.get(i);
if (entry.string() == null) {
CompilerOptionEntry.setInt(array, i, entry.name(), entry.int0(), entry.int1());
} else {
CompilerOptionEntry.setString(
array, i, entry.name(), arena.allocateFrom(entry.string()), MemorySegment.NULL);
}
}
return array;
}
}
Loading
Loading