Skip to content

Commit

Permalink
[AMDGPU] implemented pal metadata
Browse files Browse the repository at this point in the history
Summary:
For the amdpal OS type:

We write an AMDGPU_PAL_METADATA record in the .note section in the ELF
(or as an assembler directive). It contains key=value pairs of 32 bit
ints. It is a merge of metadata from codegen of the shaders, and
metadata provided by the frontend as _amdgpu_pal_metadata IR metadata.
Where both sources have a key=value with the same key, the two values
are ORed together.

This .note record is part of the amdpal ABI and will be documented in
docs/AMDGPUUsage.rst in a future commit.

Eventually the amdpal OS type will stop generating the .AMDGPU.config
section once the frontend has safely moved over to using the .note
records above instead of .AMDGPU.config.

Reviewers: arsenm, nhaehnle, dstuttard

Subscribers: kzhuravl, wdng, yaxunl, llvm-commits, t-tye

Differential Revision: https://reviews.llvm.org/D37753

llvm-svn: 314829
  • Loading branch information
Tim Renouf committed Oct 3, 2017
1 parent 4651396 commit 72800f0
Show file tree
Hide file tree
Showing 14 changed files with 221 additions and 25 deletions.
114 changes: 111 additions & 3 deletions llvm/lib/Target/AMDGPU/AMDGPUAsmPrinter.cpp
Expand Up @@ -110,19 +110,36 @@ AMDGPUTargetStreamer& AMDGPUAsmPrinter::getTargetStreamer() const {
}

void AMDGPUAsmPrinter::EmitStartOfAsmFile(Module &M) {
if (TM.getTargetTriple().getOS() != Triple::AMDHSA)
return;

AMDGPU::IsaInfo::IsaVersion ISA =
AMDGPU::IsaInfo::getIsaVersion(getSTI()->getFeatureBits());

if (TM.getTargetTriple().getOS() == Triple::AMDPAL) {
readPalMetadata(M);
// AMDPAL wants an HSA_ISA .note.
getTargetStreamer().EmitDirectiveHSACodeObjectISA(
ISA.Major, ISA.Minor, ISA.Stepping, "AMD", "AMDGPU");
}
if (TM.getTargetTriple().getOS() != Triple::AMDHSA)
return;

getTargetStreamer().EmitDirectiveHSACodeObjectVersion(2, 1);
getTargetStreamer().EmitDirectiveHSACodeObjectISA(
ISA.Major, ISA.Minor, ISA.Stepping, "AMD", "AMDGPU");
getTargetStreamer().EmitStartOfCodeObjectMetadata(M);
}

void AMDGPUAsmPrinter::EmitEndOfAsmFile(Module &M) {
if (TM.getTargetTriple().getOS() == Triple::AMDPAL) {
// Copy the PAL metadata from the map where we collected it into a vector,
// then write it as a .note.
std::vector<uint32_t> Data;
for (auto i : PalMetadata) {
Data.push_back(i.first);
Data.push_back(i.second);
}
getTargetStreamer().EmitPalMetadata(Data);
}

if (TM.getTargetTriple().getOS() != Triple::AMDHSA)
return;

Expand Down Expand Up @@ -190,6 +207,27 @@ bool AMDGPUAsmPrinter::doFinalization(Module &M) {
return AsmPrinter::doFinalization(M);
}

// For the amdpal OS type, read the amdgpu.pal.metadata supplied by the
// frontend into our PalMetadata map, ready for per-function modification. It
// is a NamedMD containing an MDTuple containing a number of MDNodes each of
// which is an integer value, and each two integer values forms a key=value
// pair that we store as PalMetadata[key]=value in the map.
void AMDGPUAsmPrinter::readPalMetadata(Module &M) {
auto NamedMD = M.getNamedMetadata("amdgpu.pal.metadata");
if (!NamedMD || !NamedMD->getNumOperands())
return;
auto Tuple = dyn_cast<MDTuple>(NamedMD->getOperand(0));
if (!Tuple)
return;
for (unsigned I = 0, E = Tuple->getNumOperands() & -2; I != E; I += 2) {
auto Key = mdconst::dyn_extract<ConstantInt>(Tuple->getOperand(I));
auto Val = mdconst::dyn_extract<ConstantInt>(Tuple->getOperand(I + 1));
if (!Key || !Val)
continue;
PalMetadata[Key->getZExtValue()] = Val->getZExtValue();
}
}

// Print comments that apply to both callable functions and entry points.
void AMDGPUAsmPrinter::emitCommonFunctionComments(
uint32_t NumVGPR,
Expand Down Expand Up @@ -232,6 +270,8 @@ bool AMDGPUAsmPrinter::runOnMachineFunction(MachineFunction &MF) {
Info = analyzeResourceUsage(MF);
}

if (STM.isAmdPalOS())
EmitPalMetadata(MF, CurrentProgramInfo);
if (!STM.isAmdHsaOS()) {
EmitProgramInfoSI(MF, CurrentProgramInfo);
}
Expand Down Expand Up @@ -923,6 +963,74 @@ void AMDGPUAsmPrinter::EmitProgramInfoSI(const MachineFunction &MF,
OutStreamer->EmitIntValue(MFI->getNumSpilledVGPRs(), 4);
}

// This is the equivalent of EmitProgramInfoSI above, but for when the OS type
// is AMDPAL. It stores each compute/SPI register setting and other PAL
// metadata items into the PalMetadata map, combining with any provided by the
// frontend as LLVM metadata. Once all functions are written, PalMetadata is
// then written as a single block in the .note section.
void AMDGPUAsmPrinter::EmitPalMetadata(const MachineFunction &MF,
const SIProgramInfo &CurrentProgramInfo) {
const SIMachineFunctionInfo *MFI = MF.getInfo<SIMachineFunctionInfo>();
// Given the calling convention, calculate the register number for rsrc1. In
// principle the register number could change in future hardware, but we know
// it is the same for gfx6-9 (except that LS and ES don't exist on gfx9), so
// we can use the same fixed value that .AMDGPU.config has for Mesa. Note
// that we use a register number rather than a byte offset, so we need to
// divide by 4.
unsigned Rsrc1Reg = getRsrcReg(MF.getFunction()->getCallingConv()) / 4;
unsigned Rsrc2Reg = Rsrc1Reg + 1;
// Also calculate the PAL metadata key for *S_SCRATCH_SIZE. It can be used
// with a constant offset to access any non-register shader-specific PAL
// metadata key.
unsigned ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_CS_SCRATCH_SIZE;
switch (MF.getFunction()->getCallingConv()) {
case CallingConv::AMDGPU_PS:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_PS_SCRATCH_SIZE;
break;
case CallingConv::AMDGPU_VS:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_VS_SCRATCH_SIZE;
break;
case CallingConv::AMDGPU_GS:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_GS_SCRATCH_SIZE;
break;
case CallingConv::AMDGPU_ES:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_ES_SCRATCH_SIZE;
break;
case CallingConv::AMDGPU_HS:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_HS_SCRATCH_SIZE;
break;
case CallingConv::AMDGPU_LS:
ScratchSizeKey = AMDGPU::ElfNote::AMDGPU_PAL_METADATA_LS_SCRATCH_SIZE;
break;
}
unsigned NumUsedVgprsKey = ScratchSizeKey
+ AMDGPU::ElfNote::AMDGPU_PAL_METADATA_VS_NUM_USED_VGPRS
- AMDGPU::ElfNote::AMDGPU_PAL_METADATA_VS_SCRATCH_SIZE;
unsigned NumUsedSgprsKey = ScratchSizeKey
+ AMDGPU::ElfNote::AMDGPU_PAL_METADATA_VS_NUM_USED_SGPRS
- AMDGPU::ElfNote::AMDGPU_PAL_METADATA_VS_SCRATCH_SIZE;
PalMetadata[NumUsedVgprsKey] = CurrentProgramInfo.NumVGPRsForWavesPerEU;
PalMetadata[NumUsedSgprsKey] = CurrentProgramInfo.NumSGPRsForWavesPerEU;
if (AMDGPU::isCompute(MF.getFunction()->getCallingConv())) {
PalMetadata[Rsrc1Reg] |= CurrentProgramInfo.ComputePGMRSrc1;
PalMetadata[Rsrc2Reg] |= CurrentProgramInfo.ComputePGMRSrc2;
// ScratchSize is in bytes, 16 aligned.
PalMetadata[ScratchSizeKey] |= alignTo(CurrentProgramInfo.ScratchSize, 16);
} else {
PalMetadata[Rsrc1Reg] |= S_00B028_VGPRS(CurrentProgramInfo.VGPRBlocks)
| S_00B028_SGPRS(CurrentProgramInfo.SGPRBlocks);
if (CurrentProgramInfo.ScratchBlocks > 0)
PalMetadata[Rsrc2Reg] |= S_00B84C_SCRATCH_EN(1);
// ScratchSize is in bytes, 16 aligned.
PalMetadata[ScratchSizeKey] |= alignTo(CurrentProgramInfo.ScratchSize, 16);
}
if (MF.getFunction()->getCallingConv() == CallingConv::AMDGPU_PS) {
PalMetadata[Rsrc2Reg] |= S_00B02C_EXTRA_LDS_SIZE(CurrentProgramInfo.LDSBlocks);
PalMetadata[R_0286CC_SPI_PS_INPUT_ENA / 4] |= MFI->getPSInputEnable();
PalMetadata[R_0286D0_SPI_PS_INPUT_ADDR / 4] |= MFI->getPSInputAddr();
}
}

// This is supposed to be log2(Size)
static amd_element_byte_size_t getElementByteSizeValue(unsigned Size) {
switch (Size) {
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/AMDGPU/AMDGPUAsmPrinter.h
Expand Up @@ -112,10 +112,12 @@ class AMDGPUAsmPrinter final : public AsmPrinter {

SIProgramInfo CurrentProgramInfo;
DenseMap<const Function *, SIFunctionResourceInfo> CallGraphResourceInfo;
std::map<uint32_t, uint32_t> PalMetadata;

uint64_t getFunctionCodeSize(const MachineFunction &MF) const;
SIFunctionResourceInfo analyzeResourceUsage(const MachineFunction &MF) const;

void readPalMetadata(Module &M);
void getSIProgramInfo(SIProgramInfo &Out, const MachineFunction &MF);
void getAmdKernelCode(amd_kernel_code_t &Out, const SIProgramInfo &KernelInfo,
const MachineFunction &MF) const;
Expand All @@ -127,6 +129,7 @@ class AMDGPUAsmPrinter final : public AsmPrinter {
/// can correctly setup the GPU state.
void EmitProgramInfoR600(const MachineFunction &MF);
void EmitProgramInfoSI(const MachineFunction &MF, const SIProgramInfo &KernelInfo);
void EmitPalMetadata(const MachineFunction &MF, const SIProgramInfo &KernelInfo);
void emitCommonFunctionComments(uint32_t NumVGPR,
uint32_t NumSGPR,
uint32_t ScratchSize,
Expand Down
33 changes: 33 additions & 0 deletions llvm/lib/Target/AMDGPU/AMDGPUPTNote.h
Expand Up @@ -27,16 +27,49 @@ const char NoteName[] = "AMD";

// TODO: Move this enum to include/llvm/Support so it can be used in tools?
enum NoteType{
NT_AMDGPU_HSA_RESERVED_0 = 0,
NT_AMDGPU_HSA_CODE_OBJECT_VERSION = 1,
NT_AMDGPU_HSA_HSAIL = 2,
NT_AMDGPU_HSA_ISA = 3,
NT_AMDGPU_HSA_PRODUCER = 4,
NT_AMDGPU_HSA_PRODUCER_OPTIONS = 5,
NT_AMDGPU_HSA_EXTENSION = 6,
NT_AMDGPU_HSA_RESERVED_7 = 7,
NT_AMDGPU_HSA_RESERVED_8 = 8,
NT_AMDGPU_HSA_RESERVED_9 = 9,
NT_AMDGPU_HSA_CODE_OBJECT_METADATA = 10,
NT_AMD_AMDGPU_ISA = 11,
NT_AMDGPU_PAL_METADATA = 12,
NT_AMDGPU_HSA_HLDEBUG_DEBUG = 101,
NT_AMDGPU_HSA_HLDEBUG_TARGET = 102
};

enum NoteAmdGpuPalMetadataKey {
AMDGPU_PAL_METADATA_LS_NUM_USED_VGPRS = 0x10000015,
AMDGPU_PAL_METADATA_HS_NUM_USED_VGPRS = 0x10000016,
AMDGPU_PAL_METADATA_ES_NUM_USED_VGPRS = 0x10000017,
AMDGPU_PAL_METADATA_GS_NUM_USED_VGPRS = 0x10000018,
AMDGPU_PAL_METADATA_VS_NUM_USED_VGPRS = 0x10000019,
AMDGPU_PAL_METADATA_PS_NUM_USED_VGPRS = 0x1000001a,
AMDGPU_PAL_METADATA_CS_NUM_USED_VGPRS = 0x1000001b,

AMDGPU_PAL_METADATA_LS_NUM_USED_SGPRS = 0x1000001c,
AMDGPU_PAL_METADATA_HS_NUM_USED_SGPRS = 0x1000001d,
AMDGPU_PAL_METADATA_ES_NUM_USED_SGPRS = 0x1000001e,
AMDGPU_PAL_METADATA_GS_NUM_USED_SGPRS = 0x1000001f,
AMDGPU_PAL_METADATA_VS_NUM_USED_SGPRS = 0x10000020,
AMDGPU_PAL_METADATA_PS_NUM_USED_SGPRS = 0x10000021,
AMDGPU_PAL_METADATA_CS_NUM_USED_SGPRS = 0x10000022,

AMDGPU_PAL_METADATA_LS_SCRATCH_SIZE = 0x10000038,
AMDGPU_PAL_METADATA_HS_SCRATCH_SIZE = 0x10000039,
AMDGPU_PAL_METADATA_ES_SCRATCH_SIZE = 0x1000003a,
AMDGPU_PAL_METADATA_GS_SCRATCH_SIZE = 0x1000003b,
AMDGPU_PAL_METADATA_VS_SCRATCH_SIZE = 0x1000003c,
AMDGPU_PAL_METADATA_PS_SCRATCH_SIZE = 0x1000003d,
AMDGPU_PAL_METADATA_CS_SCRATCH_SIZE = 0x1000003e,
};

}
}

Expand Down
19 changes: 19 additions & 0 deletions llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
Expand Up @@ -833,6 +833,7 @@ class AMDGPUAsmParser : public MCTargetAsmParser {
bool ParseDirectiveAMDKernelCodeT();
bool subtargetHasRegister(const MCRegisterInfo &MRI, unsigned RegNo) const;
bool ParseDirectiveAMDGPUHsaKernel();
bool ParseDirectivePalMetadata();
bool AddNextRegisterToList(unsigned& Reg, unsigned& RegWidth,
RegisterKind RegKind, unsigned Reg1,
unsigned RegNum);
Expand Down Expand Up @@ -2493,6 +2494,21 @@ bool AMDGPUAsmParser::ParseDirectiveAMDGPUHsaKernel() {
return false;
}

bool AMDGPUAsmParser::ParseDirectivePalMetadata() {
std::vector<uint32_t> Data;
for (;;) {
uint32_t Value;
if (ParseAsAbsoluteExpression(Value))
return TokError("invalid value in .amdgpu_pal_metadata");
Data.push_back(Value);
if (getLexer().isNot(AsmToken::Comma))
break;
Lex();
}
getTargetStreamer().EmitPalMetadata(Data);
return false;
}

bool AMDGPUAsmParser::ParseDirective(AsmToken DirectiveID) {
StringRef IDVal = DirectiveID.getString();

Expand All @@ -2511,6 +2527,9 @@ bool AMDGPUAsmParser::ParseDirective(AsmToken DirectiveID) {
if (IDVal == ".amdgpu_hsa_kernel")
return ParseDirectiveAMDGPUHsaKernel();

if (IDVal == ".amdgpu_pal_metadata")
return ParseDirectivePalMetadata();

return true;
}

Expand Down
21 changes: 21 additions & 0 deletions llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
Expand Up @@ -112,6 +112,14 @@ bool AMDGPUTargetAsmStreamer::EmitCodeObjectMetadata(StringRef YamlString) {
return true;
}

bool AMDGPUTargetAsmStreamer::EmitPalMetadata(ArrayRef<uint32_t> Data) {
OS << "\t.amdgpu_pal_metadata";
for (auto I = Data.begin(), E = Data.end(); I != E; ++I)
OS << (I == Data.begin() ? " 0x" : ",0x") << Twine::utohexstr(*I);
OS << "\n";
return true;
}

//===----------------------------------------------------------------------===//
// AMDGPUTargetELFStreamer
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -230,3 +238,16 @@ bool AMDGPUTargetELFStreamer::EmitCodeObjectMetadata(StringRef YamlString) {

return true;
}

bool AMDGPUTargetELFStreamer::EmitPalMetadata(ArrayRef<uint32_t> Data) {
EmitAMDGPUNote(
MCConstantExpr::create(Data.size() * sizeof(uint32_t), getContext()),
ElfNote::NT_AMDGPU_PAL_METADATA,
[&](MCELFStreamer &OS){
for (auto I : Data)
OS.EmitIntValue(I, sizeof(uint32_t));
}
);
return true;
}

6 changes: 6 additions & 0 deletions llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.h
Expand Up @@ -53,6 +53,8 @@ class AMDGPUTargetStreamer : public MCTargetStreamer {

/// \returns True on success, false on failure.
virtual bool EmitCodeObjectMetadata(StringRef YamlString) = 0;

virtual bool EmitPalMetadata(ArrayRef<uint32_t> Data) = 0;
};

class AMDGPUTargetAsmStreamer final : public AMDGPUTargetStreamer {
Expand All @@ -72,6 +74,8 @@ class AMDGPUTargetAsmStreamer final : public AMDGPUTargetStreamer {

/// \returns True on success, false on failure.
bool EmitCodeObjectMetadata(StringRef YamlString) override;

bool EmitPalMetadata(ArrayRef<uint32_t> data) override;
};

class AMDGPUTargetELFStreamer final : public AMDGPUTargetStreamer {
Expand Down Expand Up @@ -99,6 +103,8 @@ class AMDGPUTargetELFStreamer final : public AMDGPUTargetStreamer {

/// \returns True on success, false on failure.
bool EmitCodeObjectMetadata(StringRef YamlString) override;

bool EmitPalMetadata(ArrayRef<uint32_t> data) override;
};

}
Expand Down
5 changes: 2 additions & 3 deletions llvm/test/CodeGen/AMDGPU/amdpal-cs.ll
Expand Up @@ -2,10 +2,9 @@
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=tonga -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=VI -enable-var-scope %s
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=gfx900 -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=GFX9 -enable-var-scope %s

; amdpal compute shader: check for 47176 (COMPUTE_PGM_RSRC1) in .AMDGPU.config
; GCN-LABEL: .AMDGPU.config
; GCN: .long 47176
; amdpal compute shader: check for 0x2e12 (COMPUTE_PGM_RSRC1) in pal metadata
; GCN-LABEL: {{^}}cs_amdpal:
; GCN: .amdgpu_pal_metadata{{.*}}0x2e12,
define amdgpu_cs half @cs_amdpal(half %arg0) {
%add = fadd half %arg0, 1.0
ret half %add
Expand Down
5 changes: 2 additions & 3 deletions llvm/test/CodeGen/AMDGPU/amdpal-es.ll
@@ -1,10 +1,9 @@
; RUN: llc -mtriple=amdgcn--amdpal -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=SI %s
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=tonga -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=VI %s

; amdpal pixel shader: check for 45864 (SPI_SHADER_PGM_RSRC1_ES) in .AMDGPU.config
; GCN-LABEL: .AMDGPU.config
; GCN: .long 45864
; amdpal evaluation shader: check for 0x2cca (SPI_SHADER_PGM_RSRC1_ES) in pal metadata
; GCN-LABEL: {{^}}es_amdpal:
; GCN: .amdgpu_pal_metadata{{.*}}0x2cca,
define amdgpu_es half @es_amdpal(half %arg0) {
%add = fadd half %arg0, 1.0
ret half %add
Expand Down
5 changes: 2 additions & 3 deletions llvm/test/CodeGen/AMDGPU/amdpal-gs.ll
Expand Up @@ -2,10 +2,9 @@
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=tonga -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=VI %s
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=gfx900 -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=GFX9 -enable-var-scope %s

; amdpal pixel shader: check for 45608 (SPI_SHADER_PGM_RSRC1_GS) in .AMDGPU.config
; GCN-LABEL: .AMDGPU.config
; GCN: .long 45608
; amdpal geometry shader: check for 0x2c8a (SPI_SHADER_PGM_RSRC1_GS) in pal metadata
; GCN-LABEL: {{^}}gs_amdpal:
; GCN: .amdgpu_pal_metadata{{.*}}0x2c8a,
define amdgpu_gs half @gs_amdpal(half %arg0) {
%add = fadd half %arg0, 1.0
ret half %add
Expand Down
5 changes: 2 additions & 3 deletions llvm/test/CodeGen/AMDGPU/amdpal-hs.ll
Expand Up @@ -2,10 +2,9 @@
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=tonga -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=VI %s
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=gfx900 -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=GFX9 -enable-var-scope %s

; amdpal pixel shader: check for 46120 (SPI_SHADER_PGM_RSRC1_HS) in .AMDGPU.config
; GCN-LABEL: .AMDGPU.config
; GCN: .long 46120
; amdpal hull shader: check for 0x2d0a (SPI_SHADER_PGM_RSRC1_HS) in pal metadata
; GCN-LABEL: {{^}}hs_amdpal:
; GCN: .amdgpu_pal_metadata{{.*}}0x2d0a,
define amdgpu_hs half @hs_amdpal(half %arg0) {
%add = fadd half %arg0, 1.0
ret half %add
Expand Down
5 changes: 2 additions & 3 deletions llvm/test/CodeGen/AMDGPU/amdpal-ls.ll
@@ -1,10 +1,9 @@
; RUN: llc -mtriple=amdgcn--amdpal -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=SI %s
; RUN: llc -mtriple=amdgcn--amdpal -mcpu=tonga -verify-machineinstrs < %s | FileCheck -check-prefix=GCN -check-prefix=VI %s

; amdpal pixel shader: check for 46376 (SPI_SHADER_PGM_RSRC1_LS) in .AMDGPU.config
; GCN-LABEL: .AMDGPU.config
; GCN: .long 46376
; amdpal load shader: check for 0x2d4a (SPI_SHADER_PGM_RSRC1_LS) in pal metadata
; GCN-LABEL: {{^}}ls_amdpal:
; GCN: .amdgpu_pal_metadata{{.*}}0x2d4a,
define amdgpu_ls half @ls_amdpal(half %arg0) {
%add = fadd half %arg0, 1.0
ret half %add
Expand Down

0 comments on commit 72800f0

Please sign in to comment.