Skip to content
Open
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
11 changes: 8 additions & 3 deletions clang/include/clang/Sema/SemaHLSL.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,15 +250,20 @@ class SemaHLSL : public SemaBase {
const RecordType *RT);

void checkSemanticAnnotation(FunctionDecl *EntryPoint, const Decl *Param,
const HLSLAppliedSemanticAttr *SemanticAttr);
const HLSLAppliedSemanticAttr *SemanticAttr,
bool IsInput);

bool determineActiveSemanticOnScalar(FunctionDecl *FD,
DeclaratorDecl *OutputDecl,
DeclaratorDecl *D,
SemanticInfo &ActiveSemantic,
llvm::StringSet<> &ActiveInputSemantics);
llvm::StringSet<> &ActiveSemantics,
bool IsInput);

bool determineActiveSemantic(FunctionDecl *FD, DeclaratorDecl *OutputDecl,
DeclaratorDecl *D, SemanticInfo &ActiveSemantic,
llvm::StringSet<> &ActiveInputSemantics);
llvm::StringSet<> &ActiveSemantics,
bool IsInput);

void processExplicitBindingsOnDecl(VarDecl *D);

Expand Down
41 changes: 30 additions & 11 deletions clang/lib/CodeGen/CGHLSLRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -731,13 +731,22 @@ llvm::Value *CGHLSLRuntime::emitSystemSemanticLoad(
}

if (SemanticName == "SV_POSITION") {
if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel)
return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
Semantic->getAttrName()->getName(),
/* BuiltIn::FragCoord */ 15);
if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Pixel) {
if (CGM.getTarget().getTriple().isSPIRV())
return createSPIRVBuiltinLoad(B, CGM.getModule(), Type,
Semantic->getAttrName()->getName(),
/* BuiltIn::FragCoord */ 15);
if (CGM.getTarget().getTriple().isDXIL())
return emitDXILUserSemanticLoad(B, Type, Semantic, Index);
}

if (CGM.getTriple().getEnvironment() == Triple::EnvironmentType::Vertex) {
return emitUserSemanticLoad(B, Type, Decl, Semantic, Index);
}
}

llvm_unreachable("non-handled system semantic. FIXME.");
llvm_unreachable(
"Load hasn't been implemented yet for this system semantic. FIXME");
}

static void createSPIRVBuiltinStore(IRBuilder<> &B, llvm::Module &M,
Expand All @@ -760,12 +769,22 @@ void CGHLSLRuntime::emitSystemSemanticStore(IRBuilder<> &B, llvm::Value *Source,
std::optional<unsigned> Index) {

std::string SemanticName = Semantic->getAttrName()->getName().upper();
if (SemanticName == "SV_POSITION")
createSPIRVBuiltinStore(B, CGM.getModule(), Source,
Semantic->getAttrName()->getName(),
/* BuiltIn::Position */ 0);
else
llvm_unreachable("non-handled system semantic. FIXME.");
if (SemanticName == "SV_POSITION") {
if (CGM.getTarget().getTriple().isDXIL()) {
emitDXILUserSemanticStore(B, Source, Semantic, Index);
return;
}

if (CGM.getTarget().getTriple().isSPIRV()) {
createSPIRVBuiltinStore(B, CGM.getModule(), Source,
Semantic->getAttrName()->getName(),
/* BuiltIn::Position */ 0);
return;
}
}

llvm_unreachable(
"Store hasn't been implemented yet for this system semantic. FIXME");
}

llvm::Value *CGHLSLRuntime::handleScalarSemanticLoad(
Expand Down
35 changes: 21 additions & 14 deletions clang/lib/Sema/SemaHLSL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -771,9 +771,12 @@ void SemaHLSL::ActOnTopLevelFunction(FunctionDecl *FD) {
}
}

bool SemaHLSL::determineActiveSemanticOnScalar(
FunctionDecl *FD, DeclaratorDecl *OutputDecl, DeclaratorDecl *D,
SemanticInfo &ActiveSemantic, llvm::StringSet<> &UsedSemantics) {
bool SemaHLSL::determineActiveSemanticOnScalar(FunctionDecl *FD,
DeclaratorDecl *OutputDecl,
DeclaratorDecl *D,
SemanticInfo &ActiveSemantic,
llvm::StringSet<> &UsedSemantics,
bool IsInput) {
if (ActiveSemantic.Semantic == nullptr) {
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
if (ActiveSemantic.Semantic)
Expand All @@ -792,7 +795,7 @@ bool SemaHLSL::determineActiveSemanticOnScalar(
if (!A)
return false;

checkSemanticAnnotation(FD, D, A);
checkSemanticAnnotation(FD, D, A, IsInput);
OutputDecl->addAttr(A);

unsigned Location = ActiveSemantic.Index.value_or(0);
Expand Down Expand Up @@ -820,7 +823,8 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
DeclaratorDecl *OutputDecl,
DeclaratorDecl *D,
SemanticInfo &ActiveSemantic,
llvm::StringSet<> &UsedSemantics) {
llvm::StringSet<> &UsedSemantics,
bool IsInput) {
if (ActiveSemantic.Semantic == nullptr) {
ActiveSemantic.Semantic = D->getAttr<HLSLParsedSemanticAttr>();
if (ActiveSemantic.Semantic)
Expand All @@ -833,12 +837,13 @@ bool SemaHLSL::determineActiveSemantic(FunctionDecl *FD,
const RecordType *RT = dyn_cast<RecordType>(T);
if (!RT)
return determineActiveSemanticOnScalar(FD, OutputDecl, D, ActiveSemantic,
UsedSemantics);
UsedSemantics, IsInput);

const RecordDecl *RD = RT->getDecl();
for (FieldDecl *Field : RD->fields()) {
SemanticInfo Info = ActiveSemantic;
if (!determineActiveSemantic(FD, OutputDecl, Field, Info, UsedSemantics)) {
if (!determineActiveSemantic(FD, OutputDecl, Field, Info, UsedSemantics,
IsInput)) {
Diag(Field->getLocation(), diag::note_hlsl_semantic_used_here) << Field;
return false;
}
Expand Down Expand Up @@ -920,7 +925,7 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {

// FIXME: Verify output semantics in parameters.
if (!determineActiveSemantic(FD, Param, Param, ActiveSemantic,
ActiveInputSemantics)) {
ActiveInputSemantics, /* IsInput= */ true)) {
Diag(Param->getLocation(), diag::note_previous_decl) << Param;
FD->setInvalidDecl();
}
Expand All @@ -932,12 +937,13 @@ void SemaHLSL::CheckEntryPoint(FunctionDecl *FD) {
if (ActiveSemantic.Semantic)
ActiveSemantic.Index = ActiveSemantic.Semantic->getSemanticIndex();
if (!FD->getReturnType()->isVoidType())
determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics);
determineActiveSemantic(FD, FD, FD, ActiveSemantic, ActiveOutputSemantics,
/* IsInput= */ false);
}

void SemaHLSL::checkSemanticAnnotation(
FunctionDecl *EntryPoint, const Decl *Param,
const HLSLAppliedSemanticAttr *SemanticAttr) {
const HLSLAppliedSemanticAttr *SemanticAttr, bool IsInput) {
auto *ShaderAttr = EntryPoint->getAttr<HLSLShaderAttr>();
assert(ShaderAttr && "Entry point has no shader attribute");
llvm::Triple::EnvironmentType ST = ShaderAttr->getType();
Expand All @@ -961,11 +967,12 @@ void SemaHLSL::checkSemanticAnnotation(
}

if (SemanticName == "SV_POSITION") {
// TODO(#143523): allow use on other shader types & output once the overall
// semantic logic is implemented.
if (ST == llvm::Triple::Pixel)
// SV_Position can be an input or output in vertex shaders,
// but only an input in pixel shaders.
if (ST == llvm::Triple::Vertex || (ST == llvm::Triple::Pixel && IsInput))
return;
DiagnoseAttrStageMismatch(SemanticAttr, ST, {llvm::Triple::Pixel});
DiagnoseAttrStageMismatch(SemanticAttr, ST,
{llvm::Triple::Pixel, llvm::Triple::Vertex});
return;
}

Expand Down
21 changes: 21 additions & 0 deletions clang/test/AST/HLSL/semantic-input-struct-shadow.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s


// CHECK: CXXRecordDecl {{.*}} referenced struct S definition
// CHECK: FieldDecl {{.*}} field1 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "A" 0
// CHECK: FieldDecl {{.*}} field2 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "B" 4

struct S {
int field1 : A;
int field2 : B4;
};

// CHECK: FunctionDecl {{.*}} main 'void (S)'
// CHECK-NEXT: ParmVarDecl {{.*}} s 'S'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "C" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "C" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "C" 1
void main(S s : C) {}
20 changes: 20 additions & 0 deletions clang/test/AST/HLSL/semantic-input-struct.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s


// CHECK: CXXRecordDecl {{.*}} referenced struct S definition
// CHECK: FieldDecl {{.*}} field1 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "A" 0
// CHECK: FieldDecl {{.*}} field2 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "B" 4

struct S {
int field1 : A;
int field2 : B4;
};

// CHECK: FunctionDecl {{.*}} main 'void (S)'
// CHECK-NEXT: ParmVarDecl {{.*}} s 'S'
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "A" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "B" 4
void main(S s) {}
9 changes: 9 additions & 0 deletions clang/test/AST/HLSL/semantic-input.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s

// CHECK: ParmVarDecl {{.*}} a 'float4':'vector<float, 4>'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "ABC" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "ABC" 0

void main(float4 a : ABC) {
}
23 changes: 23 additions & 0 deletions clang/test/AST/HLSL/semantic-output-struct-shadow.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s


// CHECK: CXXRecordDecl {{.*}} referenced struct S definition
// CHECK: FieldDecl {{.*}} referenced field1 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "A" 0
// CHECK: FieldDecl {{.*}} referenced field2 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "B" 4

struct S {
int field1 : A;
int field2 : B4;
};

// CHECK: FunctionDecl {{.*}} main 'S ()'
// CHECK: HLSLParsedSemanticAttr {{.*}} "DEF" 0
// CHECK: HLSLAppliedSemanticAttr {{.*}} "DEF" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "DEF" 1
S main() : DEF {
S tmp;
return tmp;
}
22 changes: 22 additions & 0 deletions clang/test/AST/HLSL/semantic-output-struct.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s


// CHECK: CXXRecordDecl {{.*}} referenced struct S definition
// CHECK: FieldDecl {{.*}} referenced field1 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "A" 0
// CHECK: FieldDecl {{.*}} referenced field2 'int'
// CHECK-NEXT: HLSLParsedSemanticAttr {{.*}} "B" 4

struct S {
int field1 : A;
int field2 : B4;
};

// CHECK: FunctionDecl {{.*}} main 'S ()'
// CHECK: HLSLAppliedSemanticAttr {{.*}} "A" 0
// CHECK-NEXT: HLSLAppliedSemanticAttr {{.*}} "B" 4
S main() {
S tmp;
return tmp;
}
9 changes: 9 additions & 0 deletions clang/test/AST/HLSL/semantic-output.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.8-vertex -finclude-default-header -ast-dump -o - %s | FileCheck %s

// CHECK: FunctionDecl {{.*}} main 'uint ()'
// CHECK: HLSLParsedSemanticAttr {{.*}} "ABC" 0
// CHECK: HLSLAppliedSemanticAttr {{.*}} "ABC" 0
uint main() : ABC {
return 0;
}
20 changes: 15 additions & 5 deletions clang/test/CodeGenHLSL/semantics/SV_Position.ps.hlsl
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CHECK-SPIRV
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck %s --check-prefix=CHECK-DXIL

// CHECK: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]

// CHECK: define void @main() {{.*}} {
float4 main(float4 p : SV_Position) : A {
// CHECK: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
// CHECK: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
// CHECK: store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 16
// CHECK-SPIRV: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
// CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @A0, align 16

// CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
// CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
return p;
}

// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#MD_1:]]}
// CHECK-SPIRV-DAG: ![[#MD_1]] = !{i32 11, i32 15}
// | `-> BuiltIn Position
// `-> SPIR-V decoration 'FragCoord'
26 changes: 26 additions & 0 deletions clang/test/CodeGenHLSL/semantics/SV_Position.vs.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %clang_cc1 -triple dxil-unknown-shadermodel6.8-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefix=CHECK-DXIL %s
// RUN: %clang_cc1 -triple spirv-unknown-vulkan1.3-vertex -x hlsl -emit-llvm -finclude-default-header -disable-llvm-passes -o - %s | FileCheck --check-prefix=CHECK-SPIRV %s

// CHECK-SPIRV: @SV_Position0 = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations ![[#MD_0:]]
// CHECK-SPIRV: @SV_Position = external hidden thread_local addrspace(8) global <4 x float>, !spirv.Decorations ![[#MD_2:]]

// CHECK: define void @main() {{.*}} {
float4 main(float4 p : SV_Position) : SV_Position {
// CHECK-SPIRV: %[[#P:]] = load <4 x float>, ptr addrspace(7) @SV_Position0, align 16
// CHECK-SPIRV: %[[#R:]] = call spir_func <4 x float> @_Z4mainDv4_f(<4 x float> %[[#P]])
// CHECK-SPIRV: store <4 x float> %[[#R]], ptr addrspace(8) @SV_Position, align 16

// CHECK-DXIL: %SV_Position0 = call <4 x float> @llvm.dx.load.input.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison)
// CHECK-DXIL: %[[#TMP:]] = call <4 x float> @_Z4mainDv4_f(<4 x float> %SV_Position0)
// CHECK-DXIL: call void @llvm.dx.store.output.v4f32(i32 4, i32 0, i32 0, i8 0, i32 poison, <4 x float> %[[#TMP]])
return p;
}

// CHECK-SPIRV-DAG: ![[#MD_0]] = !{![[#MD_1:]]}
// CHECK-SPIRV-DAG: ![[#MD_2]] = !{![[#MD_3:]]}
// CHECK-SPIRV-DAG: ![[#MD_1]] = !{i32 30, i32 0}
// | `-> Location 0
// `-> SPIR-V decoration 'Location'
// CHECK-SPIRV-DAG: ![[#MD_3]] = !{i32 11, i32 0}
// | `-> BuiltIn Position
// `-> SPIR-V decoration 'BuiltIn'
14 changes: 4 additions & 10 deletions clang/test/SemaHLSL/Semantics/position.ps.hlsl
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.0-pixel -x hlsl -finclude-default-header -o - %s -ast-dump | FileCheck %s
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-pixel -finclude-default-header -x hlsl -verify -o - %s
// RUN: %clang_cc1 -triple spirv-pc-vulkan1.3-pixel -finclude-default-header -x hlsl -verify -o - %s

// FIXME(Keenuts): change output semantic to something valid for pixels shaders
float4 main(float4 a : SV_Position2) : A {
// CHECK: FunctionDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> line:[[@LINE-1]]:8 main 'float4 (float4)'
// CHECK-NEXT: ParmVarDecl 0x{{[0-9a-fA-F]+}} <{{.*}}> col:20 used a 'float4':'vector<float, 4>'
// CHECK-NEXT: HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <col:24> "SV_Position" 2
// CHECK-NEXT: HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:24> "SV_Position" 2

// CHECK: HLSLParsedSemanticAttr 0x{{[0-9a-f]+}} <line:4:40> "A" 0
// CHECK: HLSLAppliedSemanticAttr 0x{{[0-9a-f]+}} <col:40> "A" 0
float4 main(float4 a : A) : SV_Position {
// expected-error@-1 {{attribute 'SV_Position' is unsupported in 'pixel' shaders, requires one of the following: pixel, vertex}}
return a;
}
6 changes: 0 additions & 6 deletions clang/test/SemaHLSL/Semantics/position.vs.hlsl

This file was deleted.

32 changes: 32 additions & 0 deletions llvm/test/CodeGen/SPIRV/semantics/position.ps.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
; RUN: llc -O0 -verify-machineinstrs -mtriple=spirv-vulkan-unknown %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown %s -o - -filetype=obj | spirv-val %}

; CHECK-DAG: OpDecorate %[[#INPUT:]] BuiltIn FragCoord
; CHECK-DAG: OpDecorate %[[#OUTPUT:]] Location 0

; CHECK-DAG: %[[#float:]] = OpTypeFloat 32
; CHECK-DAG: %[[#v4:]] = OpTypeVector %[[#float]] 4
; CHECK-DAG: %[[#ptr_i:]] = OpTypePointer Input %[[#v4]]
; CHECK-DAG: %[[#ptr_o:]] = OpTypePointer Output %[[#v4]]

; CHECK-DAG: %[[#INPUT]] = OpVariable %[[#ptr_i]] Input
; CHECK-DAG: %[[#OUTPUT]] = OpVariable %[[#ptr_o]] Output

@SV_Position = external hidden thread_local addrspace(7) externally_initialized constant <4 x float>, !spirv.Decorations !0
@A0 = external hidden thread_local addrspace(8) global <4 x float>, !spirv.Decorations !2

define void @main() #1 {
entry:
%0 = load <4 x float>, ptr addrspace(7) @SV_Position, align 16
store <4 x float> %0, ptr addrspace(8) @A0, align 16
ret void

; CHECK: %[[#TMP:]] = OpLoad %[[#v4]] %[[#INPUT]] Aligned 16
; CHECK: OpStore %[[#OUTPUT]] %[[#TMP]] Aligned 16
}

!0 = !{!1}
!1 = !{i32 11, i32 15}
!2 = !{!3}
!3 = !{i32 30, i32 0}

Loading
Loading